This commit is contained in:
sxyazi 2023-11-28 01:51:30 +08:00
parent a281c3f81a
commit a4629adc30
No known key found for this signature in database
21 changed files with 215 additions and 127 deletions

View file

@ -64,6 +64,7 @@ impl Keymap {
#[inline]
pub fn get(&self, layer: Layer) -> &Vec<Control> {
match layer {
Layer::App => unreachable!(),
Layer::Manager => &self.manager,
Layer::Tasks => &self.tasks,
Layer::Select => &self.select,

View file

@ -1,7 +1,9 @@
use ratatui::prelude::Rect;
use tokio::sync::oneshot;
use yazi_config::popup::{Origin, Position};
use yazi_shared::{Exec, Layer};
use crate::{completion::Completion, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which};
use crate::{completion::Completion, emit, help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which};
pub struct Ctx {
pub manager: Manager,
@ -26,6 +28,21 @@ impl Ctx {
}
}
#[inline]
pub async fn stop() {
let (tx, rx) = oneshot::channel::<()>();
emit!(Call(Exec::call("stop", vec!["true".to_string()]).with_data(Some(tx)).vec(), Layer::App));
rx.await.ok();
}
#[inline]
pub fn resume() {
emit!(Call(
Exec::call("stop", vec!["false".to_string()]).with_data(None::<oneshot::Sender<()>>).vec(),
Layer::App
));
}
pub fn area(&self, position: &Position) -> Rect {
if position.origin != Origin::Hovered {
return position.rect();

View file

@ -1,8 +1,7 @@
use std::{collections::BTreeMap, ffi::OsString};
use std::collections::BTreeMap;
use crossterm::event::KeyEvent;
use tokio::sync::{mpsc::UnboundedSender, oneshot};
use yazi_config::open::Opener;
use yazi_shared::{fs::Url, term::Term, Exec, Layer, RoCell};
use super::files::FilesOp;
@ -16,7 +15,6 @@ pub enum Event {
Paste(String),
Render(String),
Resize(u16, u16),
Stop(bool, Option<oneshot::Sender<()>>),
Call(Vec<Exec>, Layer),
// Manager
@ -24,9 +22,6 @@ pub enum Event {
Pages(usize),
Mimetype(BTreeMap<Url, String>),
Preview(PreviewLock),
// Tasks
Open(Vec<(OsString, String)>, Option<Opener>),
}
impl Event {
@ -56,10 +51,6 @@ macro_rules! emit {
(Resize($cols:expr, $rows:expr)) => {
$crate::Event::Resize($cols, $rows).emit();
};
(Stop($state:expr)) => {{
let (tx, rx) = tokio::sync::oneshot::channel();
$crate::Event::Stop($state, Some(tx)).wait(rx)
}};
(Call($exec:expr, $layer:expr)) => {
$crate::Event::Call($exec, $layer).emit();
};

View file

@ -3,7 +3,7 @@ use std::ffi::OsString;
use yazi_config::{popup::SelectCfg, OPEN};
use yazi_shared::{Exec, MIME_DIR};
use crate::{emit, external, manager::Manager, select::Select};
use crate::{external, manager::Manager, select::Select, tasks::Tasks};
pub struct Opt {
interactive: bool,
@ -22,7 +22,7 @@ impl Manager {
let result = Select::_show(SelectCfg::open(openers.iter().map(|o| o.desc.clone()).collect()));
if let Ok(choice) = result.await {
emit!(Open(files, Some(openers[choice].clone())));
Tasks::_open(files, Some(openers[choice].clone()));
}
}
@ -63,7 +63,7 @@ impl Manager {
return;
}
emit!(Open(files, None));
Tasks::_open(files, None);
});
false
}

View file

@ -5,7 +5,7 @@ use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
use yazi_config::{popup::InputCfg, OPEN, PREVIEW};
use yazi_shared::{fs::{max_common_root, Url}, term::Term, Defer, Exec};
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::Input, manager::Manager, Event, BLOCKER};
use crate::{emit, external::{self, ShellOpt}, files::{File, FilesOp}, input::Input, manager::Manager, Ctx, BLOCKER};
pub struct Opt {
force: bool,
@ -86,10 +86,10 @@ impl Manager {
let _guard = BLOCKER.acquire().await.unwrap();
let _defer = Defer::new(|| {
Event::Stop(false, None).emit();
Ctx::resume();
tokio::spawn(fs::remove_file(tmp.clone()))
});
emit!(Stop(true)).await;
Ctx::stop().await;
let mut child = external::shell(ShellOpt {
cmd: (*opener.exec).into(),

View file

@ -1,6 +1,6 @@
use yazi_shared::Exec;
use crate::manager::Manager;
use crate::{manager::Manager, Ctx};
pub struct Opt;
impl From<&Exec> for Opt {
@ -11,7 +11,7 @@ impl Manager {
pub fn suspend(&mut self, _: impl Into<Opt>) -> bool {
#[cfg(unix)]
tokio::spawn(async move {
crate::emit!(Stop(true)).await;
Ctx::stop().await;
unsafe { libc::raise(libc::SIGTSTP) };
});
false

View file

@ -1,7 +1,6 @@
use yazi_shared::Exec;
use yazi_shared::{fs::ends_with_slash, Defer};
use yazi_shared::{fs::ends_with_slash, Defer, Exec};
use crate::{emit, external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Event, BLOCKER};
use crate::{external::{self, FzfOpt, ZoxideOpt}, tab::Tab, Ctx, BLOCKER};
pub struct Opt {
type_: OptType,
@ -36,8 +35,8 @@ impl Tab {
let cwd = self.current.cwd.clone();
tokio::spawn(async move {
let _guard = BLOCKER.acquire().await.unwrap();
let _defer = Defer::new(|| Event::Stop(false, None).emit());
emit!(Stop(true)).await;
let _defer = Defer::new(Ctx::resume);
Ctx::stop().await;
let result = if opt.type_ == OptType::Fzf {
external::fzf(FzfOpt { cwd }).await

View file

@ -1,7 +1,7 @@
use yazi_config::{open::Opener, popup::InputCfg};
use yazi_shared::Exec;
use crate::{emit, input::Input, tab::Tab};
use crate::{input::Input, tab::Tab, tasks::Tasks};
pub struct Opt {
cmd: String,
@ -37,7 +37,7 @@ impl Tab {
}
}
emit!(Open(
Tasks::_open(
selected,
Some(Opener {
exec: opt.cmd,
@ -46,8 +46,8 @@ impl Tab {
desc: Default::default(),
for_: None,
spread: true,
})
));
}),
);
});
false

View file

@ -2,10 +2,9 @@ use std::io::{stdout, Write};
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
use yazi_shared::Exec;
use yazi_shared::{term::Term, Defer};
use yazi_shared::{term::Term, Defer, Exec};
use crate::{emit, tasks::Tasks, Event, BLOCKER};
use crate::{tasks::Tasks, Ctx, BLOCKER};
pub struct Opt;
@ -32,10 +31,10 @@ impl Tasks {
task.logs.clone()
};
emit!(Stop(true)).await;
Ctx::stop().await;
let _defer = Defer::new(|| {
disable_raw_mode().ok();
Event::Stop(false, None).emit();
Ctx::resume();
});
Term::clear(&mut stdout()).ok();

View file

@ -1,5 +1,6 @@
mod arrow;
mod cancel;
mod inspect;
mod open;
mod toggle;
mod update;

View file

@ -0,0 +1,51 @@
use std::ffi::OsString;
use anyhow::anyhow;
use yazi_config::{open::Opener, BOOT};
use yazi_shared::{Exec, Layer};
use crate::{emit, tasks::Tasks};
pub struct Opt {
targets: Vec<(OsString, String)>,
opener: Option<Opener>,
}
impl TryFrom<&Exec> for Opt {
type Error = anyhow::Error;
fn try_from(e: &Exec) -> Result<Self, Self::Error> {
e.take_data().ok_or_else(|| anyhow!("invalid data"))
}
}
impl Tasks {
pub fn _open(targets: Vec<(OsString, String)>, opener: Option<Opener>) {
emit!(Call(Exec::call("open", vec![]).with_data(Opt { targets, opener }).vec(), Layer::Tasks));
}
pub fn open(&mut self, opt: impl TryInto<Opt>) -> bool {
let Ok(opt) = opt.try_into() else {
return false;
};
if let Some(p) = &BOOT.chooser_file {
let paths = opt.targets.into_iter().fold(OsString::new(), |mut s, (p, _)| {
s.push(p);
s.push("\n");
s
});
std::fs::write(p, paths.as_encoded_bytes()).ok();
emit!(Quit(false));
return false;
}
if let Some(opener) = opt.opener {
self.file_open_with(&opener, &opt.targets.into_iter().map(|(f, _)| f).collect::<Vec<_>>());
} else {
self.file_open(&opt.targets);
}
false
}
}

View file

@ -3,7 +3,7 @@ use std::{ffi::OsString, mem};
use anyhow::Result;
use tokio::{io::{AsyncBufReadExt, BufReader}, select, sync::{mpsc, oneshot}};
use crate::{emit, external::{self, ShellOpt}, tasks::TaskOp, BLOCKER};
use crate::{external::{self, ShellOpt}, tasks::TaskOp, Ctx, BLOCKER};
pub(crate) struct Process {
sch: mpsc::UnboundedSender<TaskOp>,
@ -37,7 +37,7 @@ impl Process {
let opt = ShellOpt::from(&mut task);
if task.block {
let _guard = BLOCKER.acquire().await.unwrap();
emit!(Stop(true)).await;
Ctx::stop().await;
match external::shell(opt) {
Ok(mut child) => {
@ -49,7 +49,7 @@ impl Process {
self.fail(task.id, format!("Failed to spawn process: {e}"))?;
}
}
return Ok(emit!(Stop(false)).await);
return Ok(Ctx::resume());
}
if task.orphan {

View file

@ -1,23 +1,22 @@
use std::{ffi::OsString, sync::atomic::Ordering};
use std::sync::atomic::Ordering;
use anyhow::{Ok, Result};
use crossterm::event::KeyEvent;
use ratatui::{backend::Backend, prelude::Rect};
use tokio::sync::oneshot;
use yazi_config::{keymap::Key, BOOT};
use yazi_core::{emit, files::FilesOp, input::InputMode, manager::Manager, preview::COLLISION, Ctx, Event};
use yazi_core::{emit, files::FilesOp, input::InputMode, preview::COLLISION, Ctx, Event};
use yazi_shared::{term::Term, Exec, Layer};
use crate::{Executor, Logs, Panic, Root, Signals};
pub(super) struct App {
cx: Ctx,
term: Option<Term>,
signals: Signals,
pub(crate) struct App {
pub(crate) cx: Ctx,
pub(crate) term: Option<Term>,
pub(crate) signals: Signals,
}
impl App {
pub(super) async fn run() -> Result<()> {
pub(crate) async fn run() -> Result<()> {
Panic::install();
let _log = Logs::init()?;
let term = Term::start()?;
@ -35,7 +34,6 @@ impl App {
Event::Paste(str) => app.dispatch_paste(str),
Event::Render(_) => app.dispatch_render()?,
Event::Resize(cols, rows) => app.dispatch_resize(cols, rows),
Event::Stop(state, tx) => app.dispatch_stop(state, tx),
Event::Call(exec, layer) => app.dispatch_call(exec, layer),
event => app.dispatch_module(event),
}
@ -53,7 +51,7 @@ impl App {
fn dispatch_key(&mut self, key: KeyEvent) {
let key = Key::from(key);
if Executor::new(&mut self.cx).handle(key) {
if Executor::new(self).handle(key) {
emit!(Render);
}
}
@ -121,25 +119,9 @@ impl App {
emit!(Render);
}
fn dispatch_stop(&mut self, state: bool, tx: Option<oneshot::Sender<()>>) {
self.cx.manager.active_mut().preview.reset_image();
if state {
self.signals.stop_term(true);
self.term = None;
} else {
self.term = Some(Term::start().unwrap());
self.signals.stop_term(false);
emit!(Render);
Manager::_hover(None);
}
if let Some(tx) = tx {
tx.send(()).ok();
}
}
#[inline]
fn dispatch_call(&mut self, exec: Vec<Exec>, layer: Layer) {
if Executor::new(&mut self.cx).dispatch(&exec, layer) {
if Executor::new(self).dispatch(&exec, layer) {
emit!(Render);
}
}
@ -176,26 +158,6 @@ impl App {
emit!(Render);
}
}
Event::Open(targets, opener) => {
if let Some(p) = &BOOT.chooser_file {
let paths = targets.into_iter().fold(OsString::new(), |mut s, (p, _)| {
s.push(p);
s.push("\n");
s
});
std::fs::write(p, paths.as_encoded_bytes()).ok();
return emit!(Quit(false));
}
if let Some(opener) = opener {
tasks.file_open_with(&opener, &targets.into_iter().map(|(f, _)| f).collect::<Vec<_>>());
} else {
tasks.file_open(&targets);
}
}
_ => unreachable!(),
}
}

View file

@ -0,0 +1 @@
mod stop;

View file

@ -0,0 +1,42 @@
use anyhow::Result;
use tokio::sync::oneshot;
use yazi_core::{emit, manager::Manager};
use yazi_shared::{term::Term, Exec};
use crate::app::App;
pub struct Opt {
state: bool,
tx: Option<oneshot::Sender<()>>,
}
impl TryFrom<&Exec> for Opt {
type Error = anyhow::Error;
fn try_from(e: &Exec) -> Result<Self, Self::Error> {
Ok(Self { state: e.args.first().map_or(false, |s| s == "true"), tx: e.take_data() })
}
}
impl App {
pub(crate) fn stop(&mut self, opt: impl TryInto<Opt>) -> bool {
let Ok(opt) = opt.try_into() else {
return false;
};
self.cx.manager.active_mut().preview.reset_image();
if opt.state {
self.signals.stop_term(true);
self.term = None;
} else {
self.term = Some(Term::start().unwrap());
self.signals.stop_term(false);
emit!(Render);
Manager::_hover(None);
}
if let Some(tx) = opt.tx {
tx.send(()).ok();
}
false
}
}

4
yazi-fm/src/app/mod.rs Normal file
View file

@ -0,0 +1,4 @@
mod app;
mod commands;
pub(crate) use app::*;

View file

@ -1,35 +1,39 @@
use yazi_config::{keymap::{Control, Key}, KEYMAP};
use yazi_core::{input::InputMode, Ctx};
use yazi_core::input::InputMode;
use yazi_shared::{Exec, Layer};
use crate::app::App;
pub(super) struct Executor<'a> {
cx: &'a mut Ctx,
app: &'a mut App,
}
impl<'a> Executor<'a> {
#[inline]
pub(super) fn new(cx: &'a mut Ctx) -> Self { Self { cx } }
pub(super) fn new(app: &'a mut App) -> Self { Self { app } }
pub(super) fn handle(&mut self, key: Key) -> bool {
if self.cx.which.visible {
return self.cx.which.press(key);
let cx = &mut self.app.cx;
if cx.which.visible {
return cx.which.press(key);
}
if self.cx.help.visible && self.cx.help.type_(&key) {
if cx.help.visible && cx.help.type_(&key) {
return true;
}
if self.cx.input.visible && self.cx.input.type_(&key) {
if cx.input.visible && cx.input.type_(&key) {
return true;
}
let b = if self.cx.completion.visible {
let b = if cx.completion.visible {
self.matches(Layer::Completion, key).or_else(|| self.matches(Layer::Input, key))
} else if self.cx.help.visible {
} else if cx.help.visible {
self.matches(Layer::Help, key)
} else if self.cx.input.visible {
} else if cx.input.visible {
self.matches(Layer::Input, key)
} else if self.cx.select.visible {
} else if cx.select.visible {
self.matches(Layer::Select, key)
} else if self.cx.tasks.visible {
} else if cx.tasks.visible {
self.matches(Layer::Tasks, key)
} else {
self.matches(Layer::Manager, key)
@ -45,7 +49,7 @@ impl<'a> Executor<'a> {
}
return Some(if on.len() > 1 {
self.cx.which.show(&key, layer)
self.app.cx.which.show(&key, layer)
} else {
self.dispatch(exec, layer)
});
@ -58,6 +62,7 @@ impl<'a> Executor<'a> {
let mut render = false;
for e in exec {
render |= match layer {
Layer::App => self.app(e),
Layer::Manager => self.manager(e),
Layer::Tasks => self.tasks(e),
Layer::Select => self.select(e),
@ -70,21 +75,35 @@ impl<'a> Executor<'a> {
render
}
fn app(&mut self, exec: &Exec) -> bool {
macro_rules! on {
($name:ident) => {
if exec.cmd == stringify!($name) {
return self.app.$name(exec);
}
};
}
on!(stop);
false
}
fn manager(&mut self, exec: &Exec) -> bool {
macro_rules! on {
(MANAGER, $name:ident $(,$args:expr)*) => {
if exec.cmd == stringify!($name) {
return self.cx.manager.$name(exec, $($args),*);
return self.app.cx.manager.$name(exec, $($args),*);
}
};
(ACTIVE, $name:ident) => {
if exec.cmd == stringify!($name) {
return self.cx.manager.active_mut().$name(exec);
return self.app.cx.manager.active_mut().$name(exec);
}
};
(TABS, $name:ident) => {
if exec.cmd == concat!("tab_", stringify!($name)) {
return self.cx.manager.tabs.$name(exec);
return self.app.cx.manager.tabs.$name(exec);
}
};
}
@ -92,8 +111,8 @@ impl<'a> Executor<'a> {
on!(MANAGER, peek);
on!(MANAGER, hover);
on!(MANAGER, refresh);
on!(MANAGER, quit, &self.cx.tasks);
on!(MANAGER, close, &self.cx.tasks);
on!(MANAGER, quit, &self.app.cx.tasks);
on!(MANAGER, close, &self.app.cx.tasks);
on!(MANAGER, suspend);
on!(ACTIVE, escape);
@ -114,9 +133,9 @@ impl<'a> Executor<'a> {
// Operation
on!(MANAGER, open);
on!(MANAGER, yank);
on!(MANAGER, paste, &self.cx.tasks);
on!(MANAGER, link, &self.cx.tasks);
on!(MANAGER, remove, &self.cx.tasks);
on!(MANAGER, paste, &self.app.cx.tasks);
on!(MANAGER, link, &self.app.cx.tasks);
on!(MANAGER, remove, &self.app.cx.tasks);
on!(MANAGER, create);
on!(MANAGER, rename);
on!(ACTIVE, copy);
@ -142,9 +161,9 @@ impl<'a> Executor<'a> {
match exec.cmd.as_bytes() {
// Tasks
b"tasks_show" => self.cx.tasks.toggle(()),
b"tasks_show" => self.app.cx.tasks.toggle(()),
// Help
b"help" => self.cx.help.toggle(Layer::Manager),
b"help" => self.app.cx.help.toggle(Layer::Manager),
_ => false,
}
}
@ -153,24 +172,25 @@ impl<'a> Executor<'a> {
macro_rules! on {
($name:ident) => {
if exec.cmd == stringify!($name) {
return self.cx.tasks.$name(exec);
return self.app.cx.tasks.$name(exec);
}
};
($name:ident, $alias:literal) => {
if exec.cmd == $alias {
return self.cx.tasks.$name(exec);
return self.app.cx.tasks.$name(exec);
}
};
}
on!(update);
on!(open);
on!(toggle, "close");
on!(arrow);
on!(inspect);
on!(cancel);
match exec.cmd.as_str() {
"help" => self.cx.help.toggle(Layer::Tasks),
"help" => self.app.cx.help.toggle(Layer::Tasks),
_ => false,
}
}
@ -179,7 +199,7 @@ impl<'a> Executor<'a> {
macro_rules! on {
($name:ident) => {
if exec.cmd == stringify!($name) {
return self.cx.select.$name(exec);
return self.app.cx.select.$name(exec);
}
};
}
@ -189,7 +209,7 @@ impl<'a> Executor<'a> {
on!(arrow);
match exec.cmd.as_str() {
"help" => self.cx.help.toggle(Layer::Select),
"help" => self.app.cx.help.toggle(Layer::Select),
_ => false,
}
}
@ -198,12 +218,12 @@ impl<'a> Executor<'a> {
macro_rules! on {
($name:ident) => {
if exec.cmd == stringify!($name) {
return self.cx.input.$name(exec);
return self.app.cx.input.$name(exec);
}
};
($name:ident, $alias:literal) => {
if exec.cmd == $alias {
return self.cx.input.$name(exec);
return self.app.cx.input.$name(exec);
}
};
}
@ -217,13 +237,13 @@ impl<'a> Executor<'a> {
if exec.cmd.as_str() == "complete" {
return if exec.named.contains_key("trigger") {
self.cx.completion.trigger(exec)
self.app.cx.completion.trigger(exec)
} else {
self.cx.input.complete(exec)
self.app.cx.input.complete(exec)
};
}
match self.cx.input.mode() {
match self.app.cx.input.mode() {
InputMode::Normal => {
on!(insert);
on!(visual);
@ -236,7 +256,7 @@ impl<'a> Executor<'a> {
on!(redo);
match exec.cmd.as_str() {
"help" => self.cx.help.toggle(Layer::Input),
"help" => self.app.cx.help.toggle(Layer::Input),
_ => false,
}
}
@ -253,7 +273,7 @@ impl<'a> Executor<'a> {
macro_rules! on {
($name:ident) => {
if exec.cmd == stringify!($name) {
return self.cx.help.$name(exec);
return self.app.cx.help.$name(exec);
}
};
}
@ -263,7 +283,7 @@ impl<'a> Executor<'a> {
on!(filter);
match exec.cmd.as_str() {
"close" => self.cx.help.toggle(Layer::Help),
"close" => self.app.cx.help.toggle(Layer::Help),
_ => false,
}
}
@ -272,7 +292,7 @@ impl<'a> Executor<'a> {
macro_rules! on {
($name:ident) => {
if exec.cmd == stringify!($name) {
return self.cx.completion.$name(exec);
return self.app.cx.completion.$name(exec);
}
};
}
@ -283,7 +303,7 @@ impl<'a> Executor<'a> {
on!(arrow);
match exec.cmd.as_str() {
"help" => self.cx.help.toggle(Layer::Completion),
"help" => self.app.cx.help.toggle(Layer::Completion),
_ => false,
}
}

View file

@ -14,7 +14,6 @@ mod tasks;
mod which;
mod widgets;
use app::*;
use executor::*;
use logs::*;
use panic::*;
@ -34,5 +33,5 @@ async fn main() -> anyhow::Result<()> {
yazi_adaptor::init();
App::run().await
app::App::run().await
}

View file

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

View file

@ -50,6 +50,7 @@ impl Signals {
#[cfg(unix)]
fn spawn_system_task(&self) -> Result<JoinHandle<()>> {
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM};
use yazi_core::Ctx;
let tx = self.tx.clone();
let mut signals = signal_hook_tokio::Signals::new([
@ -67,9 +68,7 @@ impl Signals {
break;
}
}
SIGCONT => {
tx.send(Event::Stop(false, None)).ok();
}
SIGCONT => Ctx::resume(),
_ => {}
}
}

View file

@ -3,6 +3,7 @@ use std::fmt::{self, Display};
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Layer {
#[default]
App,
Manager,
Tasks,
Select,
@ -15,6 +16,7 @@ pub enum Layer {
impl Display for Layer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::App => write!(f, "app"),
Self::Manager => write!(f, "manager"),
Self::Tasks => write!(f, "tasks"),
Self::Select => write!(f, "select"),