added option to pass opened file to stdout

Signed-off-by: aserowy <serowy@hotmail.com>
This commit is contained in:
aserowy 2024-01-06 15:01:30 +01:00
parent 93c8d90a51
commit d78eafb37c
10 changed files with 159 additions and 58 deletions

View file

@ -15,6 +15,9 @@ pub struct Args {
/// Write the selected files on open emitted by the chooser mode /// Write the selected files on open emitted by the chooser mode
#[arg(long)] #[arg(long)]
pub chooser_file: Option<PathBuf>, pub chooser_file: Option<PathBuf>,
/// Write the selected files on open to stdout
#[arg(long, action)]
pub chooser_stdout: bool,
/// Clear the cache directory /// Clear the cache directory
#[arg(long, action)] #[arg(long, action)]

View file

@ -1,10 +1,15 @@
use std::ffi::OsString; use std::ffi::OsString;
use tokio::fs;
use tracing::error; use tracing::error;
use yazi_config::{popup::SelectCfg, ARGS, OPEN}; use yazi_config::{popup::SelectCfg, ARGS, OPEN};
use yazi_plugin::isolate; use yazi_plugin::isolate;
use yazi_shared::{emit, event::Exec, fs::{File, Url}, Layer, MIME_DIR}; use yazi_shared::event::QuitAction;
use yazi_shared::{
emit,
event::Exec,
fs::{File, Url},
Layer, MIME_DIR,
};
use crate::{manager::Manager, select::Select, tasks::Tasks}; use crate::{manager::Manager, select::Select, tasks::Tasks};
@ -24,7 +29,7 @@ impl Manager {
let selected = self.selected(); let selected = self.selected();
if selected.is_empty() { if selected.is_empty() {
return; return;
} else if Self::quit_with_selected(&selected) { } else if Self::quit_with_chooser(&selected) {
return; return;
} }
@ -95,10 +100,9 @@ impl Manager {
}); });
} }
fn quit_with_selected(selected: &[&File]) -> bool { fn quit_with_chooser(selected: &[&File]) -> bool {
let Some(p) = ARGS.chooser_file.clone() else { let mut quit_actions = vec![QuitAction::CwdToFile];
return false; let mut initiated_quit = false;
};
let paths = selected.iter().fold(OsString::new(), |mut s, &f| { let paths = selected.iter().fold(OsString::new(), |mut s, &f| {
s.push(f.url.as_os_str()); s.push(f.url.as_os_str());
@ -106,10 +110,20 @@ impl Manager {
s s
}); });
if ARGS.chooser_file.is_some() {
quit_actions.push(QuitAction::SelectToFile(paths.clone()));
initiated_quit = true;
};
if ARGS.chooser_stdout {
quit_actions.push(QuitAction::SelectToStdout(paths.clone()));
initiated_quit = true;
};
tokio::spawn(async move { tokio::spawn(async move {
fs::write(p, paths.as_encoded_bytes()).await.ok(); emit!(Quit(quit_actions));
emit!(Quit(false));
}); });
true
initiated_quit
} }
} }

View file

@ -1,5 +1,8 @@
use yazi_config::popup::InputCfg; use yazi_config::popup::InputCfg;
use yazi_shared::{emit, event::Exec}; use yazi_shared::{
emit,
event::{Exec, QuitAction},
};
use crate::{input::Input, manager::Manager, tasks::Tasks}; use crate::{input::Input, manager::Manager, tasks::Tasks};
@ -8,19 +11,26 @@ pub struct Opt {
no_cwd_file: bool, no_cwd_file: bool,
} }
impl From<()> for Opt { impl From<()> for Opt {
fn from(_: ()) -> Self { Self::default() } fn from(_: ()) -> Self {
Self::default()
}
} }
impl From<&Exec> for Opt { impl From<&Exec> for Opt {
fn from(e: &Exec) -> Self { Self { no_cwd_file: e.named.contains_key("no-cwd-file") } } fn from(e: &Exec) -> Self {
Self { no_cwd_file: e.named.contains_key("no-cwd-file") }
}
} }
impl Manager { impl Manager {
pub fn quit(&self, opt: impl Into<Opt>, tasks: &Tasks) { pub fn quit(&self, opt: impl Into<Opt>, tasks: &Tasks) {
let opt = opt.into() as Opt; let opt = opt.into() as Opt;
let quit_actions =
if opt.no_cwd_file { vec![QuitAction::None] } else { vec![QuitAction::CwdToFile] };
let tasks = tasks.len(); let tasks = tasks.len();
if tasks == 0 { if tasks == 0 {
emit!(Quit(opt.no_cwd_file)); emit!(Quit(quit_actions));
return; return;
} }
@ -28,7 +38,7 @@ impl Manager {
let mut result = Input::_show(InputCfg::quit(tasks)); let mut result = Input::_show(InputCfg::quit(tasks));
if let Some(Ok(choice)) = result.recv().await { if let Some(Ok(choice)) = result.recv().await {
if choice == "y" || choice == "Y" { if choice == "y" || choice == "Y" {
emit!(Quit(opt.no_cwd_file)); emit!(Quit(quit_actions));
} }
} }
}); });

View file

@ -20,7 +20,7 @@ impl Select {
pub async fn _show(cfg: SelectCfg) -> Result<usize> { pub async fn _show(cfg: SelectCfg) -> Result<usize> {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
emit!(Call(Exec::call("show", vec![]).with_data(Opt { cfg, tx }).vec(), Layer::Select)); emit!(Call(Exec::call("show", vec![]).with_data(Opt { cfg, tx }).vec(), Layer::Select));
rx.await.unwrap_or_else(|_| Term::goodbye(|| false)) rx.await.unwrap_or_else(|_| Term::goodbye(|| false, None))
} }
pub fn show(&mut self, opt: impl TryInto<Opt>) { pub fn show(&mut self, opt: impl TryInto<Opt>) {

View file

@ -4,7 +4,12 @@ use anyhow::{Ok, Result};
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use yazi_config::keymap::Key; use yazi_config::keymap::Key;
use yazi_core::input::InputMode; use yazi_core::input::InputMode;
use yazi_shared::{emit, event::{Event, Exec, NEED_RENDER}, term::Term, Layer}; use yazi_shared::{
emit,
event::{Event, Exec, NEED_RENDER},
term::Term,
Layer,
};
use crate::{lives::Lives, Ctx, Executor, Logs, Panic, Signals}; use crate::{lives::Lives, Ctx, Executor, Logs, Panic, Signals};
@ -35,8 +40,8 @@ impl App {
Event::Key(key) => app.dispatch_key(key), Event::Key(key) => app.dispatch_key(key),
Event::Resize(cols, rows) => app.dispatch_resize(cols, rows)?, Event::Resize(cols, rows) => app.dispatch_resize(cols, rows)?,
Event::Paste(str) => app.dispatch_paste(str), Event::Paste(str) => app.dispatch_paste(str),
Event::Quit(no_cwd_file) => { Event::Quit(quit_actions) => {
app.quit(no_cwd_file)?; app.quit(quit_actions)?;
return Ok(()); return Ok(());
} }
} }
@ -54,7 +59,9 @@ impl App {
} }
#[inline] #[inline]
fn dispatch_key(&mut self, key: KeyEvent) { Executor::new(self).handle(Key::from(key)); } fn dispatch_key(&mut self, key: KeyEvent) {
Executor::new(self).handle(Key::from(key));
}
fn dispatch_paste(&mut self, str: String) { fn dispatch_paste(&mut self, str: String) {
if self.cx.input.visible { if self.cx.input.visible {

View file

@ -1,26 +1,42 @@
use std::ffi::OsString;
use anyhow::Result; use anyhow::Result;
use yazi_config::ARGS; use yazi_config::ARGS;
use yazi_shared::term::Term; use yazi_shared::{event::QuitAction, term::Term};
use crate::app::App; use crate::app::App;
pub struct Opt {
no_cwd_file: bool,
}
impl From<bool> for Opt {
fn from(no_cwd_file: bool) -> Self { Self { no_cwd_file } }
}
impl App { impl App {
pub(crate) fn quit(&mut self, opt: impl Into<Opt>) -> Result<()> { pub(crate) fn quit(&mut self, quit_actions: Vec<QuitAction>) -> Result<()> {
let opt = opt.into() as Opt; if quit_actions.contains(&QuitAction::None) {
Term::goodbye(|| false, None)
}
if let Some(p) = ARGS.cwd_file.as_ref().filter(|_| !opt.no_cwd_file) { let mut stdout = None;
for quit_action in quit_actions {
match quit_action {
QuitAction::None => unreachable!(),
QuitAction::CwdToFile => self.cwd_to_file(),
QuitAction::SelectToFile(selected) => self.select_to_file(selected),
QuitAction::SelectToStdout(selected) => {
stdout = Some(selected.as_encoded_bytes().to_owned())
}
}
}
Term::goodbye(|| false, stdout.as_deref());
}
fn cwd_to_file(&self) {
if let Some(p) = ARGS.cwd_file.as_ref() {
let cwd = self.cx.manager.cwd().as_os_str(); let cwd = self.cx.manager.cwd().as_os_str();
std::fs::write(p, cwd.as_encoded_bytes()).ok(); std::fs::write(p, cwd.as_encoded_bytes()).ok();
} }
}
Term::goodbye(|| false); fn select_to_file(&self, selected: OsString) {
if let Some(p) = ARGS.chooser_file.clone() {
std::fs::write(p, selected.as_encoded_bytes()).ok();
}
} }
} }

View file

@ -8,10 +8,13 @@ impl Panic {
let hook = std::panic::take_hook(); let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| { std::panic::set_hook(Box::new(move |info| {
Term::goodbye(|| { Term::goodbye(
|| {
hook(info); hook(info);
true true
}); },
None,
);
})); }));
} }
} }

View file

@ -1,7 +1,14 @@
use anyhow::Result; use anyhow::Result;
use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind}; use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind};
use futures::StreamExt; use futures::StreamExt;
use tokio::{select, sync::{mpsc::{self, UnboundedReceiver, UnboundedSender}, oneshot}, task::JoinHandle}; use tokio::{
select,
sync::{
mpsc::{self, UnboundedReceiver, UnboundedSender},
oneshot,
},
task::JoinHandle,
};
use yazi_shared::event::Event; use yazi_shared::event::Event;
pub(super) struct Signals { pub(super) struct Signals {
@ -42,12 +49,15 @@ impl Signals {
} }
#[cfg(windows)] #[cfg(windows)]
fn spawn_system_task(&self) -> Result<()> { Ok(()) } fn spawn_system_task(&self) -> Result<()> {
Ok(())
}
#[cfg(unix)] #[cfg(unix)]
fn spawn_system_task(&self) -> Result<JoinHandle<()>> { fn spawn_system_task(&self) -> Result<JoinHandle<()>> {
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM}; use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM};
use yazi_scheduler::Scheduler; use yazi_scheduler::Scheduler;
use yazi_shared::event::QuitAction;
let tx = self.tx.clone(); let tx = self.tx.clone();
let mut signals = signal_hook_tokio::Signals::new([ let mut signals = signal_hook_tokio::Signals::new([
@ -61,7 +71,7 @@ impl Signals {
while let Some(signal) = signals.next().await { while let Some(signal) = signals.next().await {
match signal { match signal {
SIGHUP | SIGTERM | SIGQUIT | SIGINT => { SIGHUP | SIGTERM | SIGQUIT | SIGINT => {
if tx.send(Event::Quit(false)).is_err() { if tx.send(Event::Quit(vec![QuitAction::CwdToFile])).is_err() {
break; break;
} }
} }

View file

@ -1,3 +1,5 @@
use std::ffi::OsString;
use crossterm::event::KeyEvent; use crossterm::event::KeyEvent;
use tokio::sync::{mpsc::UnboundedSender, oneshot}; use tokio::sync::{mpsc::UnboundedSender, oneshot};
@ -13,31 +15,42 @@ pub enum Event {
Key(KeyEvent), Key(KeyEvent),
Resize(u16, u16), Resize(u16, u16),
Paste(String), Paste(String),
Quit(bool), // no-cwd-file Quit(Vec<QuitAction>),
}
#[derive(Debug, PartialEq)]
pub enum QuitAction {
None,
CwdToFile,
SelectToFile(OsString),
SelectToStdout(OsString),
} }
impl Event { impl Event {
#[inline] #[inline]
pub fn init(tx: UnboundedSender<Event>) { TX.init(tx); } pub fn init(tx: UnboundedSender<Event>) {
TX.init(tx);
}
#[inline] #[inline]
pub fn emit(self) { TX.send(self).ok(); } pub fn emit(self) {
TX.send(self).ok();
}
pub async fn wait<T>(self, rx: oneshot::Receiver<T>) -> T { pub async fn wait<T>(self, rx: oneshot::Receiver<T>) -> T {
TX.send(self).ok(); TX.send(self).ok();
rx.await.unwrap_or_else(|_| Term::goodbye(|| false)) rx.await.unwrap_or_else(|_| Term::goodbye(|| false, None))
} }
} }
#[macro_export] #[macro_export]
macro_rules! emit { macro_rules! emit {
(Quit($no_cwd_file:expr)) => { (Quit($quit_actions:expr)) => {
$crate::event::Event::Quit($no_cwd_file).emit(); $crate::event::Event::Quit($quit_actions).emit();
}; };
(Call($exec:expr, $layer:expr)) => { (Call($exec:expr, $layer:expr)) => {
$crate::event::Event::Call($exec, $layer).emit(); $crate::event::Event::Call($exec, $layer).emit();
}; };
($event:ident) => { ($event:ident) => {
$crate::event::Event::$event.emit(); $crate::event::Event::$event.emit();
}; };

View file

@ -1,7 +1,21 @@
use std::{io::{stdout, Stdout, Write}, mem, ops::{Deref, DerefMut}}; use std::{
io::{stdout, Stdout, Write},
mem,
ops::{Deref, DerefMut},
};
use anyhow::Result; use anyhow::Result;
use crossterm::{event::{DisableBracketedPaste, DisableFocusChange, EnableBracketedPaste, EnableFocusChange, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, terminal::{disable_raw_mode, enable_raw_mode, supports_keyboard_enhancement, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, WindowSize}}; use crossterm::{
event::{
DisableBracketedPaste, DisableFocusChange, EnableBracketedPaste, EnableFocusChange,
KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
},
execute, queue,
terminal::{
disable_raw_mode, enable_raw_mode, supports_keyboard_enhancement, Clear, ClearType,
EnterAlternateScreen, LeaveAlternateScreen, WindowSize,
},
};
use ratatui::{backend::CrosstermBackend, Terminal}; use ratatui::{backend::CrosstermBackend, Terminal};
pub struct Term { pub struct Term {
@ -49,7 +63,7 @@ impl Term {
Ok(disable_raw_mode()?) Ok(disable_raw_mode()?)
} }
pub fn goodbye(f: impl FnOnce() -> bool) -> ! { pub fn goodbye(f: impl FnOnce() -> bool, last_words: Option<&[u8]>) -> ! {
execute!( execute!(
stdout(), stdout(),
PopKeyboardEnhancementFlags, PopKeyboardEnhancementFlags,
@ -62,6 +76,11 @@ impl Term {
.ok(); .ok();
disable_raw_mode().ok(); disable_raw_mode().ok();
if let Some(words) = last_words {
std::io::stdout().write_all(words).ok();
}
std::process::exit(f() as i32); std::process::exit(f() as i32);
} }
@ -102,15 +121,21 @@ impl Term {
} }
impl Drop for Term { impl Drop for Term {
fn drop(&mut self) { self.stop().ok(); } fn drop(&mut self) {
self.stop().ok();
}
} }
impl Deref for Term { impl Deref for Term {
type Target = Terminal<CrosstermBackend<Stdout>>; type Target = Terminal<CrosstermBackend<Stdout>>;
fn deref(&self) -> &Self::Target { &self.inner } fn deref(&self) -> &Self::Target {
&self.inner
}
} }
impl DerefMut for Term { impl DerefMut for Term {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner } fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
} }