mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
added option to pass opened file to stdout
Signed-off-by: aserowy <serowy@hotmail.com>
This commit is contained in:
parent
93c8d90a51
commit
d78eafb37c
10 changed files with 159 additions and 58 deletions
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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>) {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue