diff --git a/yazi-config/src/boot/args.rs b/yazi-config/src/boot/args.rs index 08c4a11a..4ba5bf45 100644 --- a/yazi-config/src/boot/args.rs +++ b/yazi-config/src/boot/args.rs @@ -11,10 +11,13 @@ pub struct Args { /// Write the cwd on exit to this file #[arg(long)] - pub cwd_file: Option, + pub cwd_file: Option, /// Write the selected files on open emitted by the chooser mode #[arg(long)] pub chooser_file: Option, + /// Write the selected files on open to stdout + #[arg(long, action)] + pub chooser_stdout: bool, /// Clear the cache directory #[arg(long, action)] diff --git a/yazi-core/src/manager/commands/open.rs b/yazi-core/src/manager/commands/open.rs index a519d70c..22c25d53 100644 --- a/yazi-core/src/manager/commands/open.rs +++ b/yazi-core/src/manager/commands/open.rs @@ -1,15 +1,20 @@ use std::ffi::OsString; -use tokio::fs; use tracing::error; use yazi_config::{popup::SelectCfg, ARGS, OPEN}; 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}; pub struct Opt { - targets: Option)>>, + targets: Option)>>, interactive: bool, } @@ -24,7 +29,7 @@ impl Manager { let selected = self.selected(); if selected.is_empty() { return; - } else if Self::quit_with_selected(&selected) { + } else if Self::quit_with_chooser(&selected) { return; } @@ -95,10 +100,9 @@ impl Manager { }); } - fn quit_with_selected(selected: &[&File]) -> bool { - let Some(p) = ARGS.chooser_file.clone() else { - return false; - }; + fn quit_with_chooser(selected: &[&File]) -> bool { + let mut quit_actions = vec![QuitAction::CwdToFile]; + let mut initiated_quit = false; let paths = selected.iter().fold(OsString::new(), |mut s, &f| { s.push(f.url.as_os_str()); @@ -106,10 +110,20 @@ impl Manager { 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 { - fs::write(p, paths.as_encoded_bytes()).await.ok(); - emit!(Quit(false)); + emit!(Quit(quit_actions)); }); - true + + initiated_quit } } diff --git a/yazi-core/src/manager/commands/quit.rs b/yazi-core/src/manager/commands/quit.rs index d47b4fca..00c1f839 100644 --- a/yazi-core/src/manager/commands/quit.rs +++ b/yazi-core/src/manager/commands/quit.rs @@ -1,5 +1,8 @@ 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}; @@ -8,19 +11,26 @@ pub struct Opt { no_cwd_file: bool, } impl From<()> for Opt { - fn from(_: ()) -> Self { Self::default() } + fn from(_: ()) -> Self { + Self::default() + } } 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 { pub fn quit(&self, opt: impl Into, tasks: &Tasks) { 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(); if tasks == 0 { - emit!(Quit(opt.no_cwd_file)); + emit!(Quit(quit_actions)); return; } @@ -28,7 +38,7 @@ impl Manager { let mut result = Input::_show(InputCfg::quit(tasks)); if let Some(Ok(choice)) = result.recv().await { if choice == "y" || choice == "Y" { - emit!(Quit(opt.no_cwd_file)); + emit!(Quit(quit_actions)); } } }); diff --git a/yazi-core/src/select/commands/show.rs b/yazi-core/src/select/commands/show.rs index d817df5e..cf491dc8 100644 --- a/yazi-core/src/select/commands/show.rs +++ b/yazi-core/src/select/commands/show.rs @@ -20,7 +20,7 @@ impl Select { pub async fn _show(cfg: SelectCfg) -> Result { let (tx, rx) = oneshot::channel(); 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) { diff --git a/yazi-fm/src/app/app.rs b/yazi-fm/src/app/app.rs index 2288f3be..9ea36edc 100644 --- a/yazi-fm/src/app/app.rs +++ b/yazi-fm/src/app/app.rs @@ -4,13 +4,18 @@ use anyhow::{Ok, Result}; use crossterm::event::KeyEvent; use yazi_config::keymap::Key; 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}; pub(crate) struct App { - pub(crate) cx: Ctx, - pub(crate) term: Option, + pub(crate) cx: Ctx, + pub(crate) term: Option, pub(crate) signals: Signals, } @@ -35,8 +40,8 @@ impl App { Event::Key(key) => app.dispatch_key(key), Event::Resize(cols, rows) => app.dispatch_resize(cols, rows)?, Event::Paste(str) => app.dispatch_paste(str), - Event::Quit(no_cwd_file) => { - app.quit(no_cwd_file)?; + Event::Quit(quit_actions) => { + app.quit(quit_actions)?; return Ok(()); } } @@ -54,7 +59,9 @@ impl App { } #[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) { if self.cx.input.visible { diff --git a/yazi-fm/src/app/commands/quit.rs b/yazi-fm/src/app/commands/quit.rs index 758f5036..bce1d819 100644 --- a/yazi-fm/src/app/commands/quit.rs +++ b/yazi-fm/src/app/commands/quit.rs @@ -1,26 +1,42 @@ +use std::ffi::OsString; + use anyhow::Result; use yazi_config::ARGS; -use yazi_shared::term::Term; +use yazi_shared::{event::QuitAction, term::Term}; use crate::app::App; -pub struct Opt { - no_cwd_file: bool, -} - -impl From for Opt { - fn from(no_cwd_file: bool) -> Self { Self { no_cwd_file } } -} - impl App { - pub(crate) fn quit(&mut self, opt: impl Into) -> Result<()> { - let opt = opt.into() as Opt; + pub(crate) fn quit(&mut self, quit_actions: Vec) -> Result<()> { + 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(); 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(); + } } } diff --git a/yazi-fm/src/panic.rs b/yazi-fm/src/panic.rs index f0773886..5de13bcb 100644 --- a/yazi-fm/src/panic.rs +++ b/yazi-fm/src/panic.rs @@ -8,10 +8,13 @@ impl Panic { let hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |info| { - Term::goodbye(|| { - hook(info); - true - }); + Term::goodbye( + || { + hook(info); + true + }, + None, + ); })); } } diff --git a/yazi-fm/src/signals.rs b/yazi-fm/src/signals.rs index 17fdfda1..ef4de9a3 100644 --- a/yazi-fm/src/signals.rs +++ b/yazi-fm/src/signals.rs @@ -1,11 +1,18 @@ use anyhow::Result; use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind}; 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; pub(super) struct Signals { - tx: UnboundedSender, + tx: UnboundedSender, pub(super) rx: UnboundedReceiver, term_stop_tx: Option>, @@ -42,12 +49,15 @@ impl Signals { } #[cfg(windows)] - fn spawn_system_task(&self) -> Result<()> { Ok(()) } + fn spawn_system_task(&self) -> Result<()> { + Ok(()) + } #[cfg(unix)] fn spawn_system_task(&self) -> Result> { use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM}; use yazi_scheduler::Scheduler; + use yazi_shared::event::QuitAction; let tx = self.tx.clone(); let mut signals = signal_hook_tokio::Signals::new([ @@ -61,7 +71,7 @@ impl Signals { while let Some(signal) = signals.next().await { match signal { SIGHUP | SIGTERM | SIGQUIT | SIGINT => { - if tx.send(Event::Quit(false)).is_err() { + if tx.send(Event::Quit(vec![QuitAction::CwdToFile])).is_err() { break; } } diff --git a/yazi-shared/src/event/event.rs b/yazi-shared/src/event/event.rs index a71d2a55..572bf9f4 100644 --- a/yazi-shared/src/event/event.rs +++ b/yazi-shared/src/event/event.rs @@ -1,3 +1,5 @@ +use std::ffi::OsString; + use crossterm::event::KeyEvent; use tokio::sync::{mpsc::UnboundedSender, oneshot}; @@ -13,31 +15,42 @@ pub enum Event { Key(KeyEvent), Resize(u16, u16), Paste(String), - Quit(bool), // no-cwd-file + Quit(Vec), +} + +#[derive(Debug, PartialEq)] +pub enum QuitAction { + None, + CwdToFile, + SelectToFile(OsString), + SelectToStdout(OsString), } impl Event { #[inline] - pub fn init(tx: UnboundedSender) { TX.init(tx); } + pub fn init(tx: UnboundedSender) { + TX.init(tx); + } #[inline] - pub fn emit(self) { TX.send(self).ok(); } + pub fn emit(self) { + TX.send(self).ok(); + } pub async fn wait(self, rx: oneshot::Receiver) -> T { TX.send(self).ok(); - rx.await.unwrap_or_else(|_| Term::goodbye(|| false)) + rx.await.unwrap_or_else(|_| Term::goodbye(|| false, None)) } } #[macro_export] macro_rules! emit { - (Quit($no_cwd_file:expr)) => { - $crate::event::Event::Quit($no_cwd_file).emit(); + (Quit($quit_actions:expr)) => { + $crate::event::Event::Quit($quit_actions).emit(); }; (Call($exec:expr, $layer:expr)) => { $crate::event::Event::Call($exec, $layer).emit(); }; - ($event:ident) => { $crate::event::Event::$event.emit(); }; diff --git a/yazi-shared/src/term/term.rs b/yazi-shared/src/term/term.rs index d4ee2437..ddde459a 100644 --- a/yazi-shared/src/term/term.rs +++ b/yazi-shared/src/term/term.rs @@ -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 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}; pub struct Term { @@ -49,7 +63,7 @@ impl Term { Ok(disable_raw_mode()?) } - pub fn goodbye(f: impl FnOnce() -> bool) -> ! { + pub fn goodbye(f: impl FnOnce() -> bool, last_words: Option<&[u8]>) -> ! { execute!( stdout(), PopKeyboardEnhancementFlags, @@ -62,6 +76,11 @@ impl Term { .ok(); disable_raw_mode().ok(); + + if let Some(words) = last_words { + std::io::stdout().write_all(words).ok(); + } + std::process::exit(f() as i32); } @@ -102,15 +121,21 @@ impl Term { } impl Drop for Term { - fn drop(&mut self) { self.stop().ok(); } + fn drop(&mut self) { + self.stop().ok(); + } } impl Deref for Term { type Target = Terminal>; - fn deref(&self) -> &Self::Target { &self.inner } + fn deref(&self) -> &Self::Target { + &self.inner + } } 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 + } }