This commit is contained in:
sxyazi 2023-07-23 23:48:41 +08:00
parent ca3c7c90b5
commit 5c69beb104
No known key found for this signature in database
16 changed files with 295 additions and 82 deletions

View file

@ -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>,

View file

@ -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 + ">"
}
}

View file

@ -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!(),
}
}
}

View file

@ -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();

View file

@ -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
View file

@ -0,0 +1,4 @@
mod which;
pub use which::*;

49
src/core/which/which.rs Normal file
View 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;
}
}

View file

@ -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;

View file

@ -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),

View file

@ -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;
} else {
render = Self::input(cx, None, &key) || render;
break;
}
}
}
return if on.len() > 1 {
cx.which.show(&key, layer)
} else {
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(),

View file

@ -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();

View file

@ -11,6 +11,7 @@ mod signals;
mod status;
mod tasks;
mod term;
mod which;
pub use app::*;
pub use context::*;

View file

@ -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
View 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
View file

@ -0,0 +1,5 @@
mod layout;
mod side;
pub use layout::*;
pub use side::*;

27
src/ui/which/side.rs Normal file
View 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);
}
}