mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
up
This commit is contained in:
parent
ca3c7c90b5
commit
5c69beb104
16 changed files with 295 additions and 82 deletions
|
|
@ -2,7 +2,7 @@ use std::{collections::BTreeMap, fmt};
|
|||
|
||||
use serde::{de::{self, Visitor}, Deserializer};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Exec {
|
||||
pub cmd: String,
|
||||
pub args: Vec<String>,
|
||||
|
|
|
|||
|
|
@ -102,3 +102,60 @@ impl TryFrom<String> for Key {
|
|||
Ok(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl ToString for Key {
|
||||
fn to_string(&self) -> String {
|
||||
if let Some(c) = self.plain() {
|
||||
return if self.shift { c.to_ascii_uppercase() } else { c }.to_string();
|
||||
}
|
||||
|
||||
let mut s = "<".to_string();
|
||||
if self.ctrl {
|
||||
s += "C-";
|
||||
}
|
||||
if self.alt {
|
||||
s += "A-";
|
||||
}
|
||||
if self.shift && !matches!(self.code, KeyCode::Char(_)) {
|
||||
s += "S-";
|
||||
}
|
||||
|
||||
let code = match self.code {
|
||||
KeyCode::Char(' ') => "Space",
|
||||
KeyCode::Backspace => "Backspace",
|
||||
KeyCode::Enter => "Enter",
|
||||
KeyCode::Left => "Left",
|
||||
KeyCode::Right => "Right",
|
||||
KeyCode::Up => "Up",
|
||||
KeyCode::Down => "Down",
|
||||
KeyCode::Home => "Home",
|
||||
KeyCode::End => "End",
|
||||
KeyCode::PageUp => "PageUp",
|
||||
KeyCode::PageDown => "PageDown",
|
||||
KeyCode::Tab => "Tab",
|
||||
KeyCode::Delete => "Delete",
|
||||
KeyCode::Insert => "Insert",
|
||||
KeyCode::F(1) => "F1",
|
||||
KeyCode::F(2) => "F2",
|
||||
KeyCode::F(3) => "F3",
|
||||
KeyCode::F(4) => "F4",
|
||||
KeyCode::F(5) => "F5",
|
||||
KeyCode::F(6) => "F6",
|
||||
KeyCode::F(7) => "F7",
|
||||
KeyCode::F(8) => "F8",
|
||||
KeyCode::F(9) => "F9",
|
||||
KeyCode::F(10) => "F10",
|
||||
KeyCode::F(11) => "F11",
|
||||
KeyCode::F(12) => "F12",
|
||||
KeyCode::Esc => "Esc",
|
||||
|
||||
KeyCode::Char(c) => {
|
||||
s.push(if self.shift { c.to_ascii_uppercase() } else { c });
|
||||
""
|
||||
}
|
||||
_ => "Unknown",
|
||||
};
|
||||
|
||||
s + code + ">"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use serde::{Deserialize, Deserializer};
|
|||
use super::{Exec, Key};
|
||||
use crate::config::MERGED_KEYMAP;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Single {
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Control {
|
||||
pub on: Vec<Key>,
|
||||
#[serde(deserialize_with = "Exec::deserialize")]
|
||||
pub exec: Vec<Exec>,
|
||||
|
|
@ -12,10 +12,19 @@ pub struct Single {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Keymap {
|
||||
pub manager: Vec<Single>,
|
||||
pub tasks: Vec<Single>,
|
||||
pub select: Vec<Single>,
|
||||
pub input: Vec<Single>,
|
||||
pub manager: Vec<Control>,
|
||||
pub tasks: Vec<Control>,
|
||||
pub select: Vec<Control>,
|
||||
pub input: Vec<Control>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum KeymapLayer {
|
||||
Manager,
|
||||
Tasks,
|
||||
Select,
|
||||
Input,
|
||||
Which,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Keymap {
|
||||
|
|
@ -32,7 +41,7 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Inner {
|
||||
keymap: Vec<Single>,
|
||||
keymap: Vec<Control>,
|
||||
}
|
||||
|
||||
let shadow = Shadow::deserialize(deserializer)?;
|
||||
|
|
@ -47,4 +56,15 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
|
||||
impl Keymap {
|
||||
pub fn new() -> Self { toml::from_str(&MERGED_KEYMAP).unwrap() }
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, layer: KeymapLayer) -> &Vec<Control> {
|
||||
match layer {
|
||||
KeymapLayer::Manager => &self.manager,
|
||||
KeymapLayer::Tasks => &self.tasks,
|
||||
KeymapLayer::Select => &self.select,
|
||||
KeymapLayer::Input => &self.input,
|
||||
KeymapLayer::Which => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ use crossterm::event::KeyEvent;
|
|||
use tokio::sync::{mpsc::UnboundedSender, oneshot};
|
||||
|
||||
use super::{files::FilesOp, input::InputOpt, manager::PreviewData, select::SelectOpt};
|
||||
use crate::config::open::Opener;
|
||||
use crate::config::{keymap::{Control, KeymapLayer}, open::Opener};
|
||||
|
||||
static mut TX: Option<UnboundedSender<Event>> = None;
|
||||
|
||||
pub enum Event {
|
||||
Quit,
|
||||
Stop(bool, Option<oneshot::Sender<()>>),
|
||||
Key(KeyEvent),
|
||||
Render(String),
|
||||
Resize(u16, u16),
|
||||
Stop(bool, Option<oneshot::Sender<()>>),
|
||||
Ctrl(Control, KeymapLayer),
|
||||
|
||||
// Manager
|
||||
Cd(PathBuf),
|
||||
|
|
@ -57,10 +58,6 @@ impl Event {
|
|||
|
||||
#[macro_export]
|
||||
macro_rules! emit {
|
||||
(Stop($state:expr)) => {{
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
$crate::core::Event::Stop($state, Some(tx)).wait(rx)
|
||||
}};
|
||||
(Key($key:expr)) => {
|
||||
$crate::core::Event::Key($key).emit();
|
||||
};
|
||||
|
|
@ -70,6 +67,13 @@ macro_rules! emit {
|
|||
(Resize($cols:expr, $rows:expr)) => {
|
||||
$crate::core::Event::Resize($cols, $rows).emit();
|
||||
};
|
||||
(Stop($state:expr)) => {{
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
$crate::core::Event::Stop($state, Some(tx)).wait(rx)
|
||||
}};
|
||||
(Ctrl($exec:expr, $layer:expr)) => {
|
||||
$crate::core::Event::Ctrl($exec, $layer).emit();
|
||||
};
|
||||
|
||||
(Cd($op:expr)) => {
|
||||
$crate::core::Event::Cd($op).emit();
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub mod manager;
|
|||
pub mod position;
|
||||
pub mod select;
|
||||
pub mod tasks;
|
||||
pub mod which;
|
||||
|
||||
pub use blocker::*;
|
||||
pub use event::*;
|
||||
|
|
|
|||
4
src/core/which/mod.rs
Normal file
4
src/core/which/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
mod which;
|
||||
|
||||
pub use which::*;
|
||||
|
||||
49
src/core/which/which.rs
Normal file
49
src/core/which/which.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use std::mem;
|
||||
|
||||
use crate::{config::{keymap::{Control, Key, KeymapLayer}, KEYMAP}, emit};
|
||||
|
||||
pub struct Which {
|
||||
layer: KeymapLayer,
|
||||
pub times: usize,
|
||||
pub cands: Vec<Control>,
|
||||
|
||||
pub visible: bool,
|
||||
}
|
||||
|
||||
impl Default for Which {
|
||||
fn default() -> Self {
|
||||
Self { layer: KeymapLayer::Manager, times: 0, cands: Default::default(), visible: false }
|
||||
}
|
||||
}
|
||||
|
||||
impl Which {
|
||||
pub fn show(&mut self, key: &Key, layer: KeymapLayer) -> bool {
|
||||
self.layer = layer;
|
||||
self.times = 1;
|
||||
self.cands = KEYMAP
|
||||
.get(layer)
|
||||
.into_iter()
|
||||
.filter(|s| !s.on.is_empty() && s.on[0] == *key)
|
||||
.cloned()
|
||||
.collect();
|
||||
self.visible = true;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn press(&mut self, key: Key) -> bool {
|
||||
self.cands = mem::replace(&mut self.cands, Vec::new())
|
||||
.into_iter()
|
||||
.filter(|s| s.on.len() > self.times && s.on[self.times] == key)
|
||||
.collect();
|
||||
|
||||
if self.cands.is_empty() {
|
||||
self.visible = false;
|
||||
} else if self.cands.len() == 1 {
|
||||
self.visible = false;
|
||||
emit!(Ctrl(self.cands.remove(0), self.layer));
|
||||
}
|
||||
|
||||
self.times += 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ use crossterm::event::KeyEvent;
|
|||
use tokio::sync::oneshot::{self};
|
||||
|
||||
use super::{root::Root, Ctx, Executor, Logs, Signals, Term};
|
||||
use crate::{config::keymap::Key, core::{files::FilesOp, Event}, emit};
|
||||
use crate::{config::keymap::{Control, Key, KeymapLayer}, core::{files::FilesOp, Event}, emit};
|
||||
|
||||
pub struct App {
|
||||
cx: Ctx,
|
||||
|
|
@ -22,30 +22,17 @@ impl App {
|
|||
while let Some(event) = app.signals.rx.recv().await {
|
||||
match event {
|
||||
Event::Quit => break,
|
||||
Event::Stop(state, tx) => app.dispatch_stop(state, tx),
|
||||
Event::Key(key) => app.dispatch_key(key),
|
||||
Event::Render(_) => app.dispatch_render(),
|
||||
Event::Resize(..) => app.dispatch_resize(),
|
||||
Event::Stop(state, tx) => app.dispatch_stop(state, tx),
|
||||
Event::Ctrl(ctrl, layer) => app.dispatch_ctrl(ctrl, layer),
|
||||
event => app.dispatch_module(event).await,
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_stop(&mut self, state: bool, tx: Option<oneshot::Sender<()>>) {
|
||||
if state {
|
||||
self.signals.stop_term(true);
|
||||
self.term = None;
|
||||
} else {
|
||||
self.term = Some(Term::start().unwrap());
|
||||
self.signals.stop_term(false);
|
||||
emit!(Render);
|
||||
}
|
||||
if let Some(tx) = tx {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_key(&mut self, key: KeyEvent) {
|
||||
let key = Key::from(key);
|
||||
if Executor::handle(&mut self.cx, key) {
|
||||
|
|
@ -71,6 +58,27 @@ impl App {
|
|||
emit!(Render);
|
||||
}
|
||||
|
||||
fn dispatch_stop(&mut self, state: bool, tx: Option<oneshot::Sender<()>>) {
|
||||
if state {
|
||||
self.signals.stop_term(true);
|
||||
self.term = None;
|
||||
} else {
|
||||
self.term = Some(Term::start().unwrap());
|
||||
self.signals.stop_term(false);
|
||||
emit!(Render);
|
||||
}
|
||||
if let Some(tx) = tx {
|
||||
tx.send(()).ok();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn dispatch_ctrl(&mut self, ctrl: Control, layer: KeymapLayer) {
|
||||
if Executor::dispatch(&mut self.cx, &ctrl.exec, layer) {
|
||||
emit!(Render);
|
||||
}
|
||||
}
|
||||
|
||||
async fn dispatch_module(&mut self, event: Event) {
|
||||
let manager = &mut self.cx.manager;
|
||||
let tasks = &mut self.cx.tasks;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use crate::{core::{input::Input, manager::Manager, select::Select, tasks::Tasks, Position}, misc::tty_size};
|
||||
use crate::{config::keymap::KeymapLayer, core::{input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position}, misc::tty_size};
|
||||
|
||||
pub struct Ctx {
|
||||
pub cursor: Option<(u16, u16)>,
|
||||
|
||||
pub manager: Manager,
|
||||
pub which: Which,
|
||||
pub select: Select,
|
||||
pub input: Input,
|
||||
pub tasks: Tasks,
|
||||
|
|
@ -15,12 +16,28 @@ impl Ctx {
|
|||
cursor: None,
|
||||
|
||||
manager: Manager::new(),
|
||||
select: Select::default(),
|
||||
input: Input::default(),
|
||||
which: Default::default(),
|
||||
select: Default::default(),
|
||||
input: Default::default(),
|
||||
tasks: Tasks::start(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn layer(&self) -> KeymapLayer {
|
||||
if self.which.visible {
|
||||
KeymapLayer::Which
|
||||
} else if self.input.visible {
|
||||
KeymapLayer::Input
|
||||
} else if self.select.visible {
|
||||
KeymapLayer::Select
|
||||
} else if self.tasks.visible {
|
||||
KeymapLayer::Tasks
|
||||
} else {
|
||||
KeymapLayer::Manager
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self, pos: Position) -> Position {
|
||||
match pos {
|
||||
Position::Top => Position::Coords((tty_size().ws_col / 2).saturating_sub(25), 2),
|
||||
|
|
|
|||
|
|
@ -1,52 +1,47 @@
|
|||
use super::Ctx;
|
||||
use crate::{config::{keymap::{Exec, Key, Single}, KEYMAP}, core::input::InputMode, misc::optional_bool};
|
||||
use crate::{config::{keymap::{Control, Exec, Key, KeymapLayer}, KEYMAP}, core::input::InputMode, misc::optional_bool};
|
||||
|
||||
pub struct Executor;
|
||||
|
||||
impl Executor {
|
||||
pub fn handle(cx: &mut Ctx, key: Key) -> bool {
|
||||
let layer = if cx.input.visible {
|
||||
3
|
||||
} else if cx.select.visible {
|
||||
2
|
||||
} else if cx.tasks.visible {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let layer = cx.layer();
|
||||
if layer == KeymapLayer::Which {
|
||||
return cx.which.press(key);
|
||||
}
|
||||
|
||||
let mut render = false;
|
||||
let mut matched = false;
|
||||
let keymap = [&KEYMAP.manager, &KEYMAP.tasks, &KEYMAP.select, &KEYMAP.input][layer];
|
||||
if layer == KeymapLayer::Input && cx.input.mode() == InputMode::Insert {
|
||||
if let Some(c) = key.plain() {
|
||||
return cx.input.type_(c);
|
||||
}
|
||||
}
|
||||
|
||||
for Single { on, exec } in keymap {
|
||||
if on.len() < 1 || on[0] != key {
|
||||
for Control { on, exec } in KEYMAP.get(layer) {
|
||||
if on.is_empty() || on[0] != key {
|
||||
continue;
|
||||
}
|
||||
|
||||
matched = true;
|
||||
for e in exec {
|
||||
if layer == 0 {
|
||||
render = Self::manager(cx, e) || render;
|
||||
} else if layer == 1 {
|
||||
render = Self::tasks(cx, e) || render;
|
||||
} else if layer == 2 {
|
||||
render = Self::select(cx, e) || render;
|
||||
} else if layer == 3 {
|
||||
if cx.input.mode() != InputMode::Insert || key.plain().is_none() {
|
||||
render = Self::input(cx, Some(e), &key) || render;
|
||||
return if on.len() > 1 {
|
||||
cx.which.show(&key, layer)
|
||||
} else {
|
||||
render = Self::input(cx, None, &key) || render;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Self::dispatch(cx, exec, layer)
|
||||
};
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
if layer == 3 && !matched {
|
||||
render = Self::input(cx, None, &key);
|
||||
#[inline]
|
||||
pub fn dispatch(cx: &mut Ctx, exec: &Vec<Exec>, layer: KeymapLayer) -> bool {
|
||||
let mut render = false;
|
||||
for e in exec {
|
||||
render |= match layer {
|
||||
KeymapLayer::Manager => Self::manager(cx, e),
|
||||
KeymapLayer::Tasks => Self::tasks(cx, e),
|
||||
KeymapLayer::Select => Self::select(cx, e),
|
||||
KeymapLayer::Input => Self::input(cx, e),
|
||||
KeymapLayer::Which => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
render
|
||||
}
|
||||
|
||||
|
|
@ -170,18 +165,7 @@ impl Executor {
|
|||
}
|
||||
}
|
||||
|
||||
fn input(cx: &mut Ctx, exec: Option<&Exec>, key: &Key) -> bool {
|
||||
let exec = if let Some(e) = exec {
|
||||
e
|
||||
} else {
|
||||
if cx.input.mode() == InputMode::Insert {
|
||||
if let Some(c) = key.plain() {
|
||||
return cx.input.type_(c);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
fn input(cx: &mut Ctx, exec: &Exec) -> bool {
|
||||
match exec.cmd.as_str() {
|
||||
"close" => return cx.input.close(exec.named.contains_key("submit")),
|
||||
"escape" => return cx.input.escape(),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ impl Logs {
|
|||
.context("failed to get XDG base directories")?
|
||||
.get_state_home();
|
||||
|
||||
let appender = tracing_appender::rolling::hourly(root, "yazi.log");
|
||||
let appender = tracing_appender::rolling::never(root, "yazi.log");
|
||||
let (handle, guard) = tracing_appender::non_blocking(appender);
|
||||
|
||||
// let filter = EnvFilter::from_default_env();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ mod signals;
|
|||
mod status;
|
||||
mod tasks;
|
||||
mod term;
|
||||
mod which;
|
||||
|
||||
pub use app::*;
|
||||
pub use context::*;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, widgets::Widget};
|
||||
|
||||
use super::{header, manager, status, tasks, Ctx, Input, Select};
|
||||
use super::{header, manager, status, tasks, which::Which, Ctx, Input, Select};
|
||||
|
||||
pub struct Root<'a> {
|
||||
cx: &'a mut Ctx,
|
||||
|
|
@ -35,5 +35,9 @@ impl<'a> Widget for Root<'a> {
|
|||
} else {
|
||||
self.cx.cursor = None;
|
||||
}
|
||||
|
||||
if self.cx.which.visible {
|
||||
Which::new(self.cx).render(area, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
32
src/ui/which/layout.rs
Normal file
32
src/ui/which/layout.rs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
use ratatui::{layout, prelude::{Buffer, Constraint, Direction, Rect}, widgets::Widget};
|
||||
|
||||
use super::Side;
|
||||
use crate::ui::Ctx;
|
||||
|
||||
pub struct Which<'a> {
|
||||
cx: &'a mut Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Which<'a> {
|
||||
pub fn new(cx: &'a mut Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl Widget for Which<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let chunks = layout::Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(area);
|
||||
|
||||
let chunks = layout::Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||
.split(chunks[1]);
|
||||
|
||||
let which = &self.cx.which;
|
||||
let cands: (Vec<_>, Vec<_>) = which.cands.iter().enumerate().partition(|(i, _)| i % 2 == 0);
|
||||
|
||||
Side::new(which.times, cands.0).render(chunks[0], buf);
|
||||
Side::new(which.times, cands.1).render(chunks[1], buf);
|
||||
}
|
||||
}
|
||||
5
src/ui/which/mod.rs
Normal file
5
src/ui/which/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod layout;
|
||||
mod side;
|
||||
|
||||
pub use layout::*;
|
||||
pub use side::*;
|
||||
27
src/ui/which/side.rs
Normal file
27
src/ui/which/side.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use ratatui::{prelude::{Buffer, Rect}, widgets::{List, ListItem, Widget}};
|
||||
|
||||
use crate::config::keymap::Control;
|
||||
|
||||
pub struct Side<'a> {
|
||||
times: usize,
|
||||
cands: Vec<(usize, &'a Control)>,
|
||||
}
|
||||
|
||||
impl<'a> Side<'a> {
|
||||
pub fn new(times: usize, cands: Vec<(usize, &'a Control)>) -> Self { Self { times, cands } }
|
||||
}
|
||||
|
||||
impl Widget for Side<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let items = self
|
||||
.cands
|
||||
.into_iter()
|
||||
.map(|(_, c)| {
|
||||
let s = c.on[self.times..].into_iter().map(ToString::to_string).collect::<String>();
|
||||
ListItem::new(format!("{:?}", s))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
List::new(items).render(area, buf);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue