mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 23:01:05 +00:00
feat: help (#93)
This commit is contained in:
parent
e31bc6a075
commit
4d4586409d
21 changed files with 613 additions and 181 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use core::{input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
|
||||
use core::{help::Help, input::Input, manager::Manager, select::Select, tasks::Tasks, which::Which, Position};
|
||||
|
||||
use config::keymap::KeymapLayer;
|
||||
use crossterm::terminal::WindowSize;
|
||||
|
|
@ -8,8 +8,9 @@ use shared::Term;
|
|||
pub struct Ctx {
|
||||
pub manager: Manager,
|
||||
pub which: Which,
|
||||
pub select: Select,
|
||||
pub help: Help,
|
||||
pub input: Input,
|
||||
pub select: Select,
|
||||
pub tasks: Tasks,
|
||||
}
|
||||
|
||||
|
|
@ -18,8 +19,9 @@ impl Ctx {
|
|||
Self {
|
||||
manager: Manager::make(),
|
||||
which: Default::default(),
|
||||
select: Default::default(),
|
||||
help: Default::default(),
|
||||
input: Default::default(),
|
||||
select: Default::default(),
|
||||
tasks: Tasks::start(),
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +62,9 @@ impl Ctx {
|
|||
let Rect { x, y, .. } = self.area(&self.input.position);
|
||||
return Some((x + 1 + self.input.cursor(), y + 1));
|
||||
}
|
||||
if let Some((x, y)) = self.help.cursor() {
|
||||
return Some((x, y));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +72,8 @@ impl Ctx {
|
|||
pub(super) fn layer(&self) -> KeymapLayer {
|
||||
if self.which.visible {
|
||||
KeymapLayer::Which
|
||||
} else if self.help.visible() {
|
||||
KeymapLayer::Help
|
||||
} else if self.input.visible {
|
||||
KeymapLayer::Input
|
||||
} else if self.select.visible {
|
||||
|
|
@ -80,6 +87,6 @@ impl Ctx {
|
|||
|
||||
#[inline]
|
||||
pub(super) fn image_layer(&self) -> bool {
|
||||
!matches!(self.layer(), KeymapLayer::Which | KeymapLayer::Tasks)
|
||||
!matches!(self.layer(), KeymapLayer::Which | KeymapLayer::Help | KeymapLayer::Tasks)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,13 +15,15 @@ impl Executor {
|
|||
return cx.which.press(key);
|
||||
}
|
||||
|
||||
if layer == KeymapLayer::Input && cx.input.mode() == InputMode::Insert {
|
||||
if let Some(c) = key.plain() {
|
||||
return cx.input.type_(c);
|
||||
}
|
||||
if layer == KeymapLayer::Input && cx.input.type_(&key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for Control { on, exec } in KEYMAP.get(layer) {
|
||||
if layer == KeymapLayer::Help && cx.help.type_(&key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for Control { on, exec, .. } in KEYMAP.get(layer) {
|
||||
if on.is_empty() || on[0] != key {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -44,6 +46,7 @@ impl Executor {
|
|||
KeymapLayer::Tasks => Self::tasks(cx, e),
|
||||
KeymapLayer::Select => Self::select(cx, e),
|
||||
KeymapLayer::Input => Self::input(cx, e),
|
||||
KeymapLayer::Help => Self::help(cx, e),
|
||||
KeymapLayer::Which => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
|
@ -173,6 +176,9 @@ impl Executor {
|
|||
// Tasks
|
||||
"tasks_show" => cx.tasks.toggle(),
|
||||
|
||||
// Help
|
||||
"help" => cx.help.toggle(cx.layer()),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -187,8 +193,9 @@ impl Executor {
|
|||
}
|
||||
|
||||
"inspect" => cx.tasks.inspect(),
|
||||
|
||||
"cancel" => cx.tasks.cancel(),
|
||||
|
||||
"help" => cx.help.toggle(cx.layer()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -202,6 +209,7 @@ impl Executor {
|
|||
if step > 0 { cx.select.next(step as usize) } else { cx.select.prev(step.unsigned_abs()) }
|
||||
}
|
||||
|
||||
"help" => cx.help.toggle(cx.layer()),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
@ -235,12 +243,27 @@ impl Executor {
|
|||
|
||||
"undo" => cx.input.undo(),
|
||||
"redo" => cx.input.redo(),
|
||||
|
||||
"help" => cx.help.toggle(cx.layer()),
|
||||
_ => false,
|
||||
},
|
||||
InputMode::Insert => match exec.cmd.as_str() {
|
||||
"backspace" => cx.input.backspace(),
|
||||
_ => false,
|
||||
},
|
||||
InputMode::Insert => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn help(cx: &mut Ctx, exec: &Exec) -> bool {
|
||||
match exec.cmd.as_str() {
|
||||
"close" => cx.help.toggle(cx.layer()),
|
||||
"escape" => cx.help.escape(),
|
||||
|
||||
"arrow" => {
|
||||
let step = exec.args.get(0).and_then(|s| s.parse().ok()).unwrap_or(0);
|
||||
cx.help.arrow(step)
|
||||
}
|
||||
|
||||
"filter" => cx.help.filter(),
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
50
app/src/help/bindings.rs
Normal file
50
app/src/help/bindings.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use ratatui::{layout::{self, Constraint}, prelude::{Buffer, Direction, Rect}, style::{Color, Style, Stylize}, widgets::{List, ListItem, Widget}};
|
||||
|
||||
use crate::context::Ctx;
|
||||
|
||||
pub(super) struct Bindings<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Bindings<'a> {
|
||||
pub(super) fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl Widget for Bindings<'_> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let bindings = &self.cx.help.window();
|
||||
if bindings.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let col1 = bindings
|
||||
.iter()
|
||||
.map(|c| ListItem::new(c.on()).style(Style::new().fg(Color::Yellow)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let col2 = bindings
|
||||
.iter()
|
||||
.map(|c| ListItem::new(c.exec()).style(Style::new().fg(Color::Cyan)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let col3 = bindings
|
||||
.iter()
|
||||
.map(|c| ListItem::new(if let Some(ref desc) = c.desc { desc } else { "-" }))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let chunks = layout::Layout::new()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Ratio(2, 10), Constraint::Ratio(3, 10), Constraint::Ratio(5, 10)])
|
||||
.split(area);
|
||||
|
||||
let cursor = self.cx.help.rel_cursor() as u16;
|
||||
buf.set_style(
|
||||
Rect { x: area.x, y: area.y + cursor, width: area.width, height: 1 },
|
||||
Style::new().bg(Color::Black).bold(),
|
||||
);
|
||||
|
||||
List::new(col1).render(chunks[0], buf);
|
||||
List::new(col2).render(chunks[1], buf);
|
||||
List::new(col3).render(chunks[2], buf);
|
||||
}
|
||||
}
|
||||
30
app/src/help/layout.rs
Normal file
30
app/src/help/layout.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use ratatui::{buffer::Buffer, layout::{self, Rect}, prelude::{Constraint, Direction}, style::{Color, Style}, widgets::{Clear, Paragraph, Widget}};
|
||||
|
||||
use super::Bindings;
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
||||
impl<'a> Layout<'a> {
|
||||
pub fn new(cx: &'a Ctx) -> Self { Self { cx } }
|
||||
}
|
||||
|
||||
impl<'a> Widget for Layout<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let chunks = layout::Layout::new()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Length(1)].as_ref())
|
||||
.split(area);
|
||||
|
||||
Clear.render(area, buf);
|
||||
|
||||
let help = &self.cx.help;
|
||||
Paragraph::new(help.keyword().unwrap_or_else(|| format!("{}.help", help.layer())))
|
||||
.style(Style::new().fg(Color::Black).bg(Color::White))
|
||||
.render(chunks[1], buf);
|
||||
|
||||
Bindings::new(self.cx).render(chunks[0], buf);
|
||||
}
|
||||
}
|
||||
5
app/src/help/mod.rs
Normal file
5
app/src/help/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod bindings;
|
||||
mod layout;
|
||||
|
||||
use bindings::*;
|
||||
pub(super) use layout::*;
|
||||
|
|
@ -4,6 +4,7 @@ mod app;
|
|||
mod context;
|
||||
mod executor;
|
||||
mod header;
|
||||
mod help;
|
||||
mod input;
|
||||
mod logs;
|
||||
mod manager;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use ratatui::{buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, widgets::Widget};
|
||||
|
||||
use super::{header, input, manager, select, status, tasks, which, Ctx};
|
||||
use crate::help;
|
||||
|
||||
pub(super) struct Root<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -33,6 +34,10 @@ impl<'a> Widget for Root<'a> {
|
|||
input::Input::new(self.cx).render(area, buf);
|
||||
}
|
||||
|
||||
if self.cx.help.visible() {
|
||||
help::Layout::new(self.cx).render(area, buf);
|
||||
}
|
||||
|
||||
if self.cx.which.visible {
|
||||
which::Which::new(self.cx).render(area, buf);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ impl Widget for Side<'_> {
|
|||
spans.push(Span::styled(" ".to_string(), Style::new().fg(Color::DarkGray)));
|
||||
|
||||
// Exec
|
||||
let exec = c.exec.iter().map(ToString::to_string).collect::<Vec<_>>().join("; ");
|
||||
spans.push(Span::styled(exec, Style::new().fg(Color::Magenta)));
|
||||
spans.push(Span::styled(c.desc_or_exec(), Style::new().fg(Color::Magenta)));
|
||||
|
||||
ListItem::new(Line::from(spans))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@
|
|||
- enter: Enter the child directory.
|
||||
- back: Go back to the previous directory.
|
||||
- forward: Go forward to the next directory.
|
||||
- peek
|
||||
|
||||
- `n`: Peek up or down at file contents in the preview. Use negative values to peek up and positive values to peek down.
|
||||
|
||||
- cd: Change the current directory.
|
||||
|
||||
- `path`: the path to change to.
|
||||
|
|
@ -54,15 +58,15 @@
|
|||
- `--force`: Overwrite the destination file if it exists.
|
||||
- `--follow`: Copy the file pointed to by a symbolic link, rather than the link itself. Only valid during copying.
|
||||
|
||||
- remove: Move the file to the trash/recycle bin.
|
||||
- remove: Move the files to the trash/recycle bin.
|
||||
|
||||
- `--permanently`: Permanently delete the file.
|
||||
- `--permanently`: Permanently delete the files.
|
||||
|
||||
- create: Create a file or directory (ends with `/` for directory).
|
||||
- create: Create a file or directory (ends with `/` for directories).
|
||||
- rename: Rename a file or directory.
|
||||
- copy: Copy the path of files or directories that are selected or hovered on.
|
||||
|
||||
- `path`: Copy the full absolute path.
|
||||
- `path`: Copy the absolute path.
|
||||
- `dirname`: Copy the path of the parent directory.
|
||||
- `filename`: Copy the name of the file.
|
||||
- `name_without_ext`: Copy the name of the file without the extension.
|
||||
|
|
@ -90,12 +94,23 @@
|
|||
- `fzf`: Jump to a directory, or reveal a file using fzf.
|
||||
- `zoxide`: Jump to a directory using zoxide.
|
||||
|
||||
- sort
|
||||
|
||||
- `by`
|
||||
- `"alphabetical"`: Sort alphabetically, e.g. `1.md` < `10.md` < `2.md`
|
||||
- `"created"`: Sort by creation time.
|
||||
- `"modified"`: Sort by last modified time.
|
||||
- `"natural"`: Sort naturally, e.g. `1.md` < `2.md` < `10.md`
|
||||
- `"size"`: Sort by file size.
|
||||
- `--reverse`: Display files in reverse order.
|
||||
- `--dir_first`: Display directories first.
|
||||
|
||||
### Tabs
|
||||
|
||||
- tab_create
|
||||
|
||||
- `path`: Create a new tab using the specified path.
|
||||
- `--current`: Create a new tab based on the current directory.
|
||||
- `--current`: Create a new tab using the current path.
|
||||
|
||||
- tab_close
|
||||
|
||||
|
|
@ -112,7 +127,11 @@
|
|||
|
||||
### Tasks
|
||||
|
||||
- tasks_show: Display the task manager.
|
||||
- tasks_show: Show the task manager.
|
||||
|
||||
### Help
|
||||
|
||||
- help: Open the help menu.
|
||||
|
||||
## tasks
|
||||
|
||||
|
|
@ -120,7 +139,9 @@
|
|||
- arrow:
|
||||
- `-1`: Move the cursor up 1 line.
|
||||
- `1`: Move the cursor down 1 line.
|
||||
- inspect: Inspect the task.
|
||||
- cancel: Cancel the task.
|
||||
- help: Open the help menu.
|
||||
|
||||
## select
|
||||
|
||||
|
|
@ -128,6 +149,7 @@
|
|||
- `--submit`: Submit the selection.
|
||||
- arrow
|
||||
- `n`: Move the cursor up or down n lines. Negative value for up, positive value for down.
|
||||
- help: Open the help menu.
|
||||
|
||||
## input
|
||||
|
||||
|
|
@ -135,7 +157,7 @@
|
|||
|
||||
- `--submit`: Submit the input.
|
||||
|
||||
- escape: Cancel visual mode and enter normal mode.
|
||||
- escape: Go back the normal mode, or cancel input.
|
||||
- move: Move the cursor left or right.
|
||||
|
||||
- `n`: Move the cursor n characters left or right. Negative value for left, positive value for right.
|
||||
|
|
@ -155,7 +177,7 @@
|
|||
|
||||
- delete: Delete the selected characters.
|
||||
|
||||
- `--cut`: Cut the selected characters into the clipboard, instead of only deleting them.
|
||||
- `--cut`: Cut the selected characters into clipboard, instead of only deleting them.
|
||||
- `--insert`: Delete and enter insert mode.
|
||||
|
||||
- yank: Copy the selected characters.
|
||||
|
|
@ -166,6 +188,8 @@
|
|||
- undo: Undo the last operation.
|
||||
- redo: Redo the last operation.
|
||||
|
||||
- help: Open the help menu.
|
||||
|
||||
### Insert mode
|
||||
|
||||
- close: Cancel input.
|
||||
|
|
@ -173,4 +197,11 @@
|
|||
- `--submit`: Submit the input.
|
||||
|
||||
- escape: Cancel insert mode and enter normal mode.
|
||||
- backspace: Delete the character before the cursor.
|
||||
|
||||
## Help
|
||||
|
||||
- close: Hide the help menu.
|
||||
- escape: Clear the filter, or hide the help menu.
|
||||
- arrow
|
||||
- `n`: Move the cursor up or down n lines. Negative value for up, positive value for down.
|
||||
- filter: Apply a filter for the help items.
|
||||
|
|
|
|||
|
|
@ -1,185 +1,214 @@
|
|||
[manager]
|
||||
|
||||
keymap = [
|
||||
{ on = [ "<Esc>" ], exec = "escape" },
|
||||
{ on = [ "q" ], exec = "quit" },
|
||||
# close current tab and exit if it is last tab
|
||||
{ on = [ "<C-q>" ], exec = "close" },
|
||||
{ on = [ "<Esc>" ], exec = "escape", desc = "Exit visual mode, clear selected, or cancel search" },
|
||||
{ on = [ "q" ], exec = "quit", desc = "Exit the process" },
|
||||
{ on = [ "<C-q>" ], exec = "close", desc = "Close the current tab, or quit if it is last tab" },
|
||||
|
||||
# Navigation
|
||||
{ on = [ "k" ], exec = "arrow -1" },
|
||||
{ on = [ "j" ], exec = "arrow 1" },
|
||||
{ on = [ "k" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "j" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "K" ], exec = "arrow -5" },
|
||||
{ on = [ "J" ], exec = "arrow 5" },
|
||||
{ on = [ "K" ], exec = "arrow -5", desc = "Move cursor up 5 lines" },
|
||||
{ on = [ "J" ], exec = "arrow 5", desc = "Move cursor down 5 lines" },
|
||||
|
||||
{ on = [ "h" ], exec = "leave" },
|
||||
{ on = [ "l" ], exec = "enter" },
|
||||
{ on = [ "h" ], exec = "leave", desc = "Go back to the parent directory" },
|
||||
{ on = [ "l" ], exec = "enter", desc = "Enter the child directory" },
|
||||
|
||||
{ on = [ "H" ], exec = "back" },
|
||||
{ on = [ "L" ], exec = "forward" },
|
||||
{ on = [ "H" ], exec = "back", desc = "Go back to the previous directory" },
|
||||
{ on = [ "L" ], exec = "forward", desc = "Go forward to the next directory" },
|
||||
|
||||
{ on = [ "<C-k>" ], exec = "peek -5" },
|
||||
{ on = [ "<C-j>" ], exec = "peek 5" },
|
||||
{ on = [ "<C-k>" ], exec = "peek -5", desc = "Peek up 5 units in the preview" },
|
||||
{ on = [ "<C-j>" ], exec = "peek 5", desc = "Peek down 5 units in the preview" },
|
||||
|
||||
{ on = [ "<Up>" ], exec = "arrow -1" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1" },
|
||||
{ on = [ "<Left>" ], exec = "leave" },
|
||||
{ on = [ "<Right>" ], exec = "enter" },
|
||||
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
{ on = [ "<Left>" ], exec = "leave", desc = "Go back to the parent directory" },
|
||||
{ on = [ "<Right>" ], exec = "enter", desc = "Enter the child directory" },
|
||||
|
||||
# Selection
|
||||
{ on = [ "<Space>" ], exec = [ "select --state=none", "arrow 1" ] },
|
||||
{ on = [ "v" ], exec = "visual_mode" },
|
||||
{ on = [ "V" ], exec = "visual_mode --unset" },
|
||||
{ on = [ "<C-a>" ], exec = "select_all --state=true" },
|
||||
{ on = [ "<C-r>" ], exec = "select_all --state=none" },
|
||||
{ on = [ "<Space>" ], exec = [ "select --state=none", "arrow 1" ], desc = "Toggle the current selection state" },
|
||||
{ on = [ "v" ], exec = "visual_mode", desc = "Enter visual mode (selection mode)" },
|
||||
{ on = [ "V" ], exec = "visual_mode --unset", desc = "Enter visual mode (unset mode)" },
|
||||
{ on = [ "<C-a>" ], exec = "select_all --state=true", desc = "Select all files" },
|
||||
{ on = [ "<C-r>" ], exec = "select_all --state=none", desc = "Inverse selection of all files" },
|
||||
|
||||
# Operation
|
||||
{ on = [ "o" ], exec = "open" },
|
||||
{ on = [ "O" ], exec = "open --interactive" },
|
||||
{ on = [ "<Enter>" ], exec = "open" },
|
||||
{ on = [ "<C-Enter>" ], exec = "open --interactive" }, # It's cool if you're using a terminal that supports CSI u
|
||||
{ on = [ "y" ], exec = "yank" },
|
||||
{ on = [ "x" ], exec = "yank --cut" },
|
||||
{ on = [ "p" ], exec = "paste" },
|
||||
{ on = [ "P" ], exec = "paste --force" },
|
||||
{ on = [ "k" ], exec = "paste --follow" },
|
||||
{ on = [ "K" ], exec = "paste --follow --force" },
|
||||
{ on = [ "d" ], exec = "remove" },
|
||||
{ on = [ "D" ], exec = "remove --permanently" },
|
||||
{ on = [ "a" ], exec = "create" },
|
||||
{ on = [ "r" ], exec = "rename" },
|
||||
{ on = [ ";" ], exec = "shell" },
|
||||
{ on = [ ":" ], exec = "shell --block" },
|
||||
{ on = [ "." ], exec = "hidden toggle" },
|
||||
{ on = [ "s" ], exec = "search fd" },
|
||||
{ on = [ "S" ], exec = "search rg" },
|
||||
{ on = [ "<C-s>" ], exec = "search none" },
|
||||
{ on = [ "z" ], exec = "jump zoxide" },
|
||||
{ on = [ "Z" ], exec = "jump fzf" },
|
||||
{ on = [ "o" ], exec = "open", desc = "Open the selected files" },
|
||||
{ on = [ "O" ], exec = "open --interactive", desc = "Open the selected files interactively" },
|
||||
{ on = [ "<Enter>" ], exec = "open", desc = "Open the selected files" },
|
||||
{ on = [ "<C-Enter>" ], exec = "open --interactive", desc = "Open the selected files interactively" }, # It's cool if you're using a terminal that supports CSI u
|
||||
{ on = [ "y" ], exec = "yank", desc = "Copy the selected files" },
|
||||
{ on = [ "x" ], exec = "yank --cut", desc = "Cut the selected files" },
|
||||
{ on = [ "p" ], exec = "paste", desc = "Paste the files" },
|
||||
{ on = [ "P" ], exec = "paste --force", desc = "Paste the files (overwrite if the destination exists)" },
|
||||
{ on = [ "k" ], exec = "paste --follow", desc = "Paste the files (follow the symlinks)" },
|
||||
{ on = [ "K" ], exec = "paste --follow --force", desc = "Paste the files (overwrite + follow)" },
|
||||
{ on = [ "d" ], exec = "remove", desc = "Move the files to the trash" },
|
||||
{ on = [ "D" ], exec = "remove --permanently", desc = "Permanently delete the files" },
|
||||
{ on = [ "a" ], exec = "create", desc = "Create a file or directory (ends with / for directories)" },
|
||||
{ on = [ "r" ], exec = "rename", desc = "Rename a file or directory" },
|
||||
{ on = [ ";" ], exec = "shell", desc = "Run a shell command" },
|
||||
{ on = [ ":" ], exec = "shell --block", desc = "Run a shell command (block the UI until the command finishes)" },
|
||||
{ on = [ "." ], exec = "hidden toggle", desc = "Toggle the visibility of hidden files" },
|
||||
{ on = [ "s" ], exec = "search fd", desc = "Search files by content using ripgrep" },
|
||||
{ on = [ "S" ], exec = "search rg", desc = "Search files by name using fd" },
|
||||
{ on = [ "<C-s>" ], exec = "search none", desc = "Cancel the ongoing search" },
|
||||
{ on = [ "z" ], exec = "jump zoxide", desc = "Jump to a directory using zoxide" },
|
||||
{ on = [ "Z" ], exec = "jump fzf", desc = "Jump to a directory, or reveal a file using fzf" },
|
||||
|
||||
# Copy
|
||||
{ on = [ "c", "c" ], exec = "copy path" },
|
||||
{ on = [ "c", "d" ], exec = "copy dirname" },
|
||||
{ on = [ "c", "f" ], exec = "copy filename" },
|
||||
{ on = [ "c", "n" ], exec = "copy name_without_ext" },
|
||||
{ on = [ "c", "c" ], exec = "copy path", desc = "Copy the absolute path" },
|
||||
{ on = [ "c", "d" ], exec = "copy dirname", desc = "Copy the path of the parent directory" },
|
||||
{ on = [ "c", "f" ], exec = "copy filename", desc = "Copy the name of the file" },
|
||||
{ on = [ "c", "n" ], exec = "copy name_without_ext", desc = "Copy the name of the file without the extension" },
|
||||
|
||||
# Sorting
|
||||
{ on = [ ",", "a" ], exec = "sort alphabetical --dir_first" },
|
||||
{ on = [ ",", "A" ], exec = "sort alphabetical --reverse --dir_first" },
|
||||
{ on = [ ",", "c" ], exec = "sort created --dir_first" },
|
||||
{ on = [ ",", "C" ], exec = "sort created --reverse --dir_first" },
|
||||
{ on = [ ",", "m" ], exec = "sort modified --dir_first" },
|
||||
{ on = [ ",", "M" ], exec = "sort modified --reverse --dir_first" },
|
||||
{ on = [ ",", "n" ], exec = "sort natural --dir_first" },
|
||||
{ on = [ ",", "N" ], exec = "sort natural --reverse --dir_first" },
|
||||
{ on = [ ",", "s" ], exec = "sort size --dir_first" },
|
||||
{ on = [ ",", "S" ], exec = "sort size --reverse --dir_first" },
|
||||
{ on = [ ",", "a" ], exec = "sort alphabetical --dir_first", desc = "Sort alphabetically, directories first" },
|
||||
{ on = [ ",", "A" ], exec = "sort alphabetical --reverse --dir_first", desc = "Sort alphabetically, directories first (reverse)" },
|
||||
{ on = [ ",", "c" ], exec = "sort created --dir_first", desc = "Sort by creation time, directories first" },
|
||||
{ on = [ ",", "C" ], exec = "sort created --reverse --dir_first", desc = "Sort by creation time, directories first (reverse)" },
|
||||
{ on = [ ",", "m" ], exec = "sort modified --dir_first", desc = "Sort by modified time, directories first" },
|
||||
{ on = [ ",", "M" ], exec = "sort modified --reverse --dir_first", desc = "Sort by modified time, directories first (reverse)" },
|
||||
{ on = [ ",", "n" ], exec = "sort natural --dir_first", desc = "Sort naturally, directories first" },
|
||||
{ on = [ ",", "N" ], exec = "sort natural --reverse --dir_first", desc = "Sort naturally, directories first (reverse)" },
|
||||
{ on = [ ",", "s" ], exec = "sort size --dir_first", desc = "Sort by size, directories first" },
|
||||
{ on = [ ",", "S" ], exec = "sort size --reverse --dir_first", desc = "Sort by size, directories first (reverse)" },
|
||||
|
||||
# Tabs
|
||||
{ on = [ "t" ], exec = "tab_create --current" },
|
||||
{ on = [ "t" ], exec = "tab_create --current", desc = "Create a new tab using the current path" },
|
||||
|
||||
{ on = [ "1" ], exec = "tab_switch 0" },
|
||||
{ on = [ "2" ], exec = "tab_switch 1" },
|
||||
{ on = [ "3" ], exec = "tab_switch 2" },
|
||||
{ on = [ "4" ], exec = "tab_switch 3" },
|
||||
{ on = [ "5" ], exec = "tab_switch 4" },
|
||||
{ on = [ "6" ], exec = "tab_switch 5" },
|
||||
{ on = [ "7" ], exec = "tab_switch 6" },
|
||||
{ on = [ "8" ], exec = "tab_switch 7" },
|
||||
{ on = [ "9" ], exec = "tab_switch 8" },
|
||||
{ on = [ "1" ], exec = "tab_switch 0", desc = "Switch to the first tab" },
|
||||
{ on = [ "2" ], exec = "tab_switch 1", desc = "Switch to the second tab" },
|
||||
{ on = [ "3" ], exec = "tab_switch 2", desc = "Switch to the third tab" },
|
||||
{ on = [ "4" ], exec = "tab_switch 3", desc = "Switch to the fourth tab" },
|
||||
{ on = [ "5" ], exec = "tab_switch 4", desc = "Switch to the fifth tab" },
|
||||
{ on = [ "6" ], exec = "tab_switch 5", desc = "Switch to the sixth tab" },
|
||||
{ on = [ "7" ], exec = "tab_switch 6", desc = "Switch to the seventh tab" },
|
||||
{ on = [ "8" ], exec = "tab_switch 7", desc = "Switch to the eighth tab" },
|
||||
{ on = [ "9" ], exec = "tab_switch 8", desc = "Switch to the ninth tab" },
|
||||
|
||||
{ on = [ "[" ], exec = "tab_switch -1 --relative" },
|
||||
{ on = [ "]" ], exec = "tab_switch 1 --relative" },
|
||||
{ on = [ "[" ], exec = "tab_switch -1 --relative", desc = "Switch to the previous tab" },
|
||||
{ on = [ "]" ], exec = "tab_switch 1 --relative", desc = "Switch to the next tab" },
|
||||
|
||||
{ on = [ "{" ], exec = "tab_swap -1" },
|
||||
{ on = [ "}" ], exec = "tab_swap 1" },
|
||||
{ on = [ "{" ], exec = "tab_swap -1", desc = "Swap the current tab with the previous tab" },
|
||||
{ on = [ "}" ], exec = "tab_swap 1", desc = "Swap the current tab with the next tab" },
|
||||
|
||||
# Tasks
|
||||
{ on = [ "w" ], exec = "tasks_show" },
|
||||
{ on = [ "w" ], exec = "tasks_show", desc = "Show the tasks manager" },
|
||||
|
||||
# Goto
|
||||
{ on = [ "g", "h" ], exec = "cd ~" },
|
||||
{ on = [ "g", "c" ], exec = "cd ~/.config" },
|
||||
{ on = [ "g", "d" ], exec = "cd ~/Downloads" },
|
||||
{ on = [ "g", "t" ], exec = "cd /tmp" },
|
||||
{ on = [ "g", "<Space>" ], exec = "cd --interactive" },
|
||||
{ on = [ "g", "h" ], exec = "cd ~", desc = "Go to the home directory" },
|
||||
{ on = [ "g", "c" ], exec = "cd ~/.config", desc = "Go to the config directory" },
|
||||
{ on = [ "g", "d" ], exec = "cd ~/Downloads", desc = "Go to the downloads directory" },
|
||||
{ on = [ "g", "t" ], exec = "cd /tmp", desc = "Go to the temporary directory" },
|
||||
{ on = [ "g", "<Space>" ], exec = "cd --interactive", desc = "Go to a directory interactively" },
|
||||
|
||||
# Help
|
||||
{ on = [ "?" ], exec = "help", desc = "Open help" },
|
||||
]
|
||||
|
||||
[tasks]
|
||||
|
||||
keymap = [
|
||||
{ on = [ "<C-q>" ], exec = "close" },
|
||||
{ on = [ "<Esc>" ], exec = "close" },
|
||||
{ on = [ "w" ], exec = "close" },
|
||||
{ on = [ "<Esc>" ], exec = "close", desc = "Hide the task manager" },
|
||||
{ on = [ "<C-q>" ], exec = "close", desc = "Hide the task manager" },
|
||||
{ on = [ "w" ], exec = "close", desc = "Hide the task manager" },
|
||||
|
||||
{ on = [ "k" ], exec = "arrow -1" },
|
||||
{ on = [ "j" ], exec = "arrow 1" },
|
||||
{ on = [ "k" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "j" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "<Up>" ], exec = "arrow -1" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1" },
|
||||
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "<Enter>" ], exec = "inspect" },
|
||||
{ on = [ "x" ], exec = "cancel" },
|
||||
{ on = [ "<Enter>" ], exec = "inspect", desc = "Inspect the task" },
|
||||
{ on = [ "x" ], exec = "cancel", desc = "Cancel the task" },
|
||||
|
||||
{ on = [ "?" ], exec = "help", desc = "Open help" }
|
||||
]
|
||||
|
||||
[select]
|
||||
|
||||
keymap = [
|
||||
{ on = [ "<C-q>" ], exec = "close" },
|
||||
{ on = [ "<Esc>" ], exec = "close" },
|
||||
{ on = [ "<Enter>" ], exec = "close --submit" },
|
||||
{ on = [ "<C-q>" ], exec = "close", desc = "Cancel selection" },
|
||||
{ on = [ "<Esc>" ], exec = "close", desc = "Cancel selection" },
|
||||
{ on = [ "<Enter>" ], exec = "close --submit", desc = "Submit the selection" },
|
||||
|
||||
{ on = [ "k" ], exec = "arrow -1" },
|
||||
{ on = [ "j" ], exec = "arrow 1" },
|
||||
{ on = [ "k" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "j" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "K" ], exec = "arrow -5" },
|
||||
{ on = [ "J" ], exec = "arrow 5" },
|
||||
{ on = [ "K" ], exec = "arrow -5", desc = "Move cursor up 5 lines" },
|
||||
{ on = [ "J" ], exec = "arrow 5", desc = "Move cursor down 5 lines" },
|
||||
|
||||
{ on = [ "<Up>" ], exec = "arrow -1" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1" },
|
||||
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "?" ], exec = "help", desc = "Open help" }
|
||||
]
|
||||
|
||||
[input]
|
||||
|
||||
keymap = [
|
||||
{ on = [ "<C-q>" ], exec = "close" },
|
||||
{ on = [ "<Enter>" ], exec = "close --submit" },
|
||||
{ on = [ "<Esc>" ], exec = "escape" },
|
||||
{ on = [ "<Backspace>" ], exec = "backspace" },
|
||||
{ on = [ "<C-q>" ], exec = "close", desc = "Cancel input" },
|
||||
{ on = [ "<Enter>" ], exec = "close --submit", desc = "Submit the input" },
|
||||
{ on = [ "<Esc>" ], exec = "escape", desc = "Go back the normal mode, or cancel input" },
|
||||
|
||||
# Mode
|
||||
{ on = [ "i" ], exec = "insert" },
|
||||
{ on = [ "a" ], exec = "insert --append" },
|
||||
{ on = [ "v" ], exec = "visual" },
|
||||
{ on = [ "i" ], exec = "insert", desc = "Enter insert mode" },
|
||||
{ on = [ "a" ], exec = "insert --append", desc = "Enter append mode" },
|
||||
{ on = [ "v" ], exec = "visual", desc = "Enter visual mode" },
|
||||
|
||||
# Navigation
|
||||
{ on = [ "h" ], exec = "move -1" },
|
||||
{ on = [ "l" ], exec = "move 1" },
|
||||
{ on = [ "h" ], exec = "move -1", desc = "Move cursor left" },
|
||||
{ on = [ "l" ], exec = "move 1", desc = "Move cursor right" },
|
||||
|
||||
{ on = [ "0" ], exec = "move -999" },
|
||||
{ on = [ "$" ], exec = "move 999" },
|
||||
{ on = [ "I" ], exec = [ "move -999", "insert" ] },
|
||||
{ on = [ "A" ], exec = [ "move 999", "insert --append" ] },
|
||||
{ on = [ "0" ], exec = "move -999", desc = "Move to the BOL" },
|
||||
{ on = [ "$" ], exec = "move 999", desc = "Move to the EOL" },
|
||||
{ on = [ "I" ], exec = [ "move -999", "insert" ], desc = "Move to the BOL, and enter insert mode" },
|
||||
{ on = [ "A" ], exec = [ "move 999", "insert --append" ], desc = "Move to the EOL, and enter append mode" },
|
||||
|
||||
{ on = [ "<Left>" ], exec = "move -1" },
|
||||
{ on = [ "<Right>" ], exec = "move 1" },
|
||||
{ on = [ "<Left>" ], exec = "move -1", desc = "Move cursor left" },
|
||||
{ on = [ "<Right>" ], exec = "move 1", desc = "Move cursor right" },
|
||||
|
||||
{ on = [ "b" ], exec = "backward" },
|
||||
{ on = [ "w" ], exec = "forward" },
|
||||
{ on = [ "e" ], exec = "forward --end-of-word" },
|
||||
{ on = [ "b" ], exec = "backward", desc = "Move to the beginning of the previous word" },
|
||||
{ on = [ "w" ], exec = "forward", desc = "Move to the beginning of the next word" },
|
||||
{ on = [ "e" ], exec = "forward --end-of-word", desc = "Move to the end of the next word" },
|
||||
|
||||
# Deletion
|
||||
{ on = [ "d" ], exec = "delete --cut" },
|
||||
{ on = [ "c" ], exec = "delete --cut --insert" },
|
||||
{ on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ] },
|
||||
{ on = [ "d" ], exec = "delete --cut", desc = "Cut the selected characters" },
|
||||
{ on = [ "c" ], exec = "delete --cut --insert", desc = "Cut the selected characters, and enter insert mode" },
|
||||
{ on = [ "x" ], exec = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" },
|
||||
|
||||
# Yank/Paste
|
||||
{ on = [ "y" ], exec = "yank" },
|
||||
{ on = [ "p" ], exec = "paste" },
|
||||
{ on = [ "P" ], exec = "paste --before" },
|
||||
{ on = [ "y" ], exec = "yank", desc = "Copy the selected characters" },
|
||||
{ on = [ "p" ], exec = "paste", desc = "Paste the copied characters after the cursor" },
|
||||
{ on = [ "P" ], exec = "paste --before", desc = "Paste the copied characters before the cursor" },
|
||||
|
||||
# Undo/Redo
|
||||
{ on = [ "u" ], exec = "undo" },
|
||||
{ on = [ "<C-r>" ], exec = "redo" },
|
||||
{ on = [ "u" ], exec = "undo", desc = "Undo the last operation" },
|
||||
{ on = [ "<C-r>" ], exec = "redo", desc = "Redo the last operation" },
|
||||
|
||||
# Help
|
||||
{ on = [ "?" ], exec = "help", desc = "Open help" }
|
||||
]
|
||||
|
||||
[help]
|
||||
|
||||
keymap = [
|
||||
{ on = [ "<Esc>" ], exec = "escape", desc = "Clear the filter, or hide the help" },
|
||||
{ on = [ "q" ], exec = "close", desc = "Exit the process" },
|
||||
{ on = [ "<C-q>" ], exec = "close", desc = "Hide the help" },
|
||||
|
||||
# Navigation
|
||||
{ on = [ "k" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "j" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "K" ], exec = "arrow -5", desc = "Move cursor up 5 lines" },
|
||||
{ on = [ "J" ], exec = "arrow 5", desc = "Move cursor down 5 lines" },
|
||||
|
||||
{ on = [ "<Up>" ], exec = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<Down>" ], exec = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
# Filtering
|
||||
{ on = [ "/" ], exec = "filter", desc = "Apply a filter for the help items" },
|
||||
]
|
||||
|
|
|
|||
35
config/src/keymap/control.rs
Normal file
35
config/src/keymap/control.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::{Exec, Key};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Control {
|
||||
pub on: Vec<Key>,
|
||||
#[serde(deserialize_with = "Exec::deserialize")]
|
||||
pub exec: Vec<Exec>,
|
||||
pub desc: Option<String>,
|
||||
}
|
||||
|
||||
impl Control {
|
||||
#[inline]
|
||||
pub fn on(&self) -> String { self.on.iter().map(ToString::to_string).collect() }
|
||||
|
||||
#[inline]
|
||||
pub fn exec(&self) -> String {
|
||||
self.exec.iter().map(|e| e.to_string()).collect::<Vec<_>>().join("; ")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn desc_or_exec(&self) -> Cow<str> {
|
||||
if let Some(ref s) = self.desc { Cow::Borrowed(s) } else { self.exec().into() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn contains(&self, s: &str) -> bool {
|
||||
self.desc.as_ref().map(|d| d.contains(s)).unwrap_or(false)
|
||||
|| self.exec().contains(s)
|
||||
|| self.on().contains(s)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::BTreeMap, fmt::{self, Debug}};
|
||||
use std::{collections::BTreeMap, fmt::{self, Debug, Display}};
|
||||
|
||||
use anyhow::bail;
|
||||
use serde::{de::{self, Visitor}, Deserializer};
|
||||
|
|
@ -34,16 +34,19 @@ impl TryFrom<&str> for Exec {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToString for Exec {
|
||||
fn to_string(&self) -> String {
|
||||
let mut s = Vec::with_capacity(self.args.len() + self.named.len() + 1);
|
||||
s.push(self.cmd.clone());
|
||||
s.extend(self.args.iter().cloned());
|
||||
for (key, val) in self.named.iter() {
|
||||
s.push(format!("--{}={}", key, val));
|
||||
impl Display for Exec {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.cmd)?;
|
||||
if !self.args.is_empty() {
|
||||
write!(f, " {}", self.args.join(" "))?;
|
||||
}
|
||||
|
||||
shell_words::join(s)
|
||||
for (k, v) in &self.named {
|
||||
write!(f, " --{k}")?;
|
||||
if !v.is_empty() {
|
||||
write!(f, "={v}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,11 @@ impl Key {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_enter(&self) -> bool {
|
||||
matches!(self, Key { code: KeyCode::Enter, shift: false, ctrl: false, alt: false })
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Key {
|
||||
|
|
|
|||
|
|
@ -1,30 +1,17 @@
|
|||
use std::fmt::{self, Display};
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use super::{Exec, Key};
|
||||
use super::Control;
|
||||
use crate::MERGED_KEYMAP;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
pub struct Control {
|
||||
pub on: Vec<Key>,
|
||||
#[serde(deserialize_with = "Exec::deserialize")]
|
||||
pub exec: Vec<Exec>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Keymap {
|
||||
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,
|
||||
pub help: Vec<Control>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Keymap {
|
||||
|
|
@ -38,6 +25,7 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
tasks: Inner,
|
||||
select: Inner,
|
||||
input: Inner,
|
||||
help: Inner,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Inner {
|
||||
|
|
@ -50,6 +38,7 @@ impl<'de> Deserialize<'de> for Keymap {
|
|||
tasks: shadow.tasks.keymap,
|
||||
select: shadow.select.keymap,
|
||||
input: shadow.input.keymap,
|
||||
help: shadow.help.keymap,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -66,7 +55,32 @@ impl Keymap {
|
|||
KeymapLayer::Tasks => &self.tasks,
|
||||
KeymapLayer::Select => &self.select,
|
||||
KeymapLayer::Input => &self.input,
|
||||
KeymapLayer::Help => &self.help,
|
||||
KeymapLayer::Which => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
pub enum KeymapLayer {
|
||||
#[default]
|
||||
Manager,
|
||||
Tasks,
|
||||
Select,
|
||||
Input,
|
||||
Help,
|
||||
Which,
|
||||
}
|
||||
|
||||
impl Display for KeymapLayer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
KeymapLayer::Manager => write!(f, "manager"),
|
||||
KeymapLayer::Tasks => write!(f, "tasks"),
|
||||
KeymapLayer::Select => write!(f, "select"),
|
||||
KeymapLayer::Input => write!(f, "input"),
|
||||
KeymapLayer::Help => write!(f, "help"),
|
||||
KeymapLayer::Which => write!(f, "which"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
mod control;
|
||||
mod exec;
|
||||
mod key;
|
||||
mod keymap;
|
||||
|
||||
pub use control::*;
|
||||
pub use exec::*;
|
||||
pub use key::*;
|
||||
pub use keymap::*;
|
||||
|
|
|
|||
170
core/src/help/help.rs
Normal file
170
core/src/help/help.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
use config::{keymap::{Control, Key, KeymapLayer}, KEYMAP};
|
||||
use shared::Term;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use super::HELP_MARGIN;
|
||||
use crate::{emit, input::Input};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Help {
|
||||
visible: bool,
|
||||
layer: KeymapLayer,
|
||||
bindings: Vec<Control>,
|
||||
|
||||
// Filter
|
||||
keyword: Option<String>,
|
||||
in_filter: Option<Input>,
|
||||
|
||||
offset: usize,
|
||||
cursor: usize,
|
||||
}
|
||||
|
||||
impl Help {
|
||||
#[inline]
|
||||
pub fn limit() -> usize { Term::size().rows.saturating_sub(HELP_MARGIN) as usize }
|
||||
|
||||
pub fn toggle(&mut self, layer: KeymapLayer) -> bool {
|
||||
self.visible = !self.visible;
|
||||
self.layer = layer;
|
||||
|
||||
self.keyword = Some(String::new());
|
||||
self.in_filter = None;
|
||||
self.filter_apply();
|
||||
|
||||
self.offset = 0;
|
||||
self.cursor = 0;
|
||||
|
||||
emit!(Peek); // Show/hide preview for images
|
||||
true
|
||||
}
|
||||
|
||||
pub fn escape(&mut self) -> bool {
|
||||
if self.in_filter.is_some() {
|
||||
self.in_filter = None;
|
||||
self.filter_apply();
|
||||
true
|
||||
} else {
|
||||
self.toggle(self.layer)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn arrow(&mut self, step: isize) -> bool {
|
||||
let max = self.bindings.len().saturating_sub(1);
|
||||
self.offset = self.offset.min(max);
|
||||
self.cursor = self.cursor.min(max);
|
||||
|
||||
if step > 0 { self.next(step as usize) } else { self.prev(step.unsigned_abs()) }
|
||||
}
|
||||
|
||||
pub fn next(&mut self, step: usize) -> bool {
|
||||
let len = self.bindings.len();
|
||||
if len == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let old = self.cursor;
|
||||
self.cursor = (self.cursor + step).min(len - 1);
|
||||
|
||||
let limit = Self::limit();
|
||||
if self.cursor >= (self.offset + limit).min(len).saturating_sub(5) {
|
||||
self.offset = len.saturating_sub(limit).min(self.offset + self.cursor - old);
|
||||
}
|
||||
|
||||
old != self.cursor
|
||||
}
|
||||
|
||||
pub fn prev(&mut self, step: usize) -> bool {
|
||||
let old = self.cursor;
|
||||
self.cursor = self.cursor.saturating_sub(step);
|
||||
|
||||
if self.cursor < self.offset + 5 {
|
||||
self.offset = self.offset.saturating_sub(old - self.cursor);
|
||||
}
|
||||
|
||||
old != self.cursor
|
||||
}
|
||||
|
||||
pub fn filter(&mut self) -> bool {
|
||||
self.in_filter = Some(Default::default());
|
||||
self.filter_apply();
|
||||
true
|
||||
}
|
||||
|
||||
fn filter_apply(&mut self) -> bool {
|
||||
let kw = self.in_filter.as_ref().map(|i| i.value()).filter(|v| !v.is_empty());
|
||||
if self.keyword.as_deref() == kw {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(kw) = kw {
|
||||
self.bindings = KEYMAP.get(self.layer).iter().filter(|&c| c.contains(kw)).cloned().collect();
|
||||
} else {
|
||||
self.bindings = KEYMAP.get(self.layer).clone();
|
||||
}
|
||||
|
||||
self.keyword = kw.map(|s| s.to_owned());
|
||||
self.arrow(0);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn type_(&mut self, key: &Key) -> bool {
|
||||
let Some(input) = &mut self.in_filter else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if key.is_enter() {
|
||||
self.in_filter = None;
|
||||
return true;
|
||||
}
|
||||
|
||||
if input.type_(key) {
|
||||
return self.filter_apply();
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl Help {
|
||||
// --- Visible
|
||||
#[inline]
|
||||
pub fn visible(&self) -> bool { self.visible }
|
||||
|
||||
// --- Layer
|
||||
#[inline]
|
||||
pub fn layer(&self) -> KeymapLayer { self.layer }
|
||||
|
||||
// --- Keyword
|
||||
#[inline]
|
||||
pub fn keyword(&self) -> Option<String> {
|
||||
self
|
||||
.in_filter
|
||||
.as_ref()
|
||||
.map(|i| i.value())
|
||||
.or(self.keyword.as_deref())
|
||||
.map(|s| format!("/{}", s))
|
||||
}
|
||||
|
||||
// --- Bindings
|
||||
#[inline]
|
||||
pub fn window(&self) -> &[Control] {
|
||||
let end = (self.offset + Self::limit()).min(self.bindings.len());
|
||||
&self.bindings[self.offset..end]
|
||||
}
|
||||
|
||||
// --- Cursor
|
||||
#[inline]
|
||||
pub fn cursor(&self) -> Option<(u16, u16)> {
|
||||
if !self.visible || self.in_filter.is_none() {
|
||||
return None;
|
||||
}
|
||||
if let Some(kw) = self.keyword() {
|
||||
return Some((kw.width() as u16, Term::size().rows));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
|
||||
}
|
||||
5
core/src/help/mod.rs
Normal file
5
core/src/help/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod help;
|
||||
|
||||
pub use help::*;
|
||||
|
||||
pub const HELP_MARGIN: u16 = 1;
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use config::keymap::Key;
|
||||
use crossterm::event::KeyCode;
|
||||
use shared::CharKind;
|
||||
use tokio::sync::oneshot::Sender;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
|
@ -177,8 +179,23 @@ impl Input {
|
|||
self.move_(snap.len() as isize)
|
||||
}
|
||||
|
||||
pub fn type_(&mut self, key: &Key) -> bool {
|
||||
if self.mode() != InputMode::Insert {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(c) = key.plain() {
|
||||
return self.type_char(c);
|
||||
}
|
||||
|
||||
match key {
|
||||
Key { code: KeyCode::Backspace, shift: false, ctrl: false, alt: false } => self.backspace(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn type_(&mut self, c: char) -> bool {
|
||||
pub fn type_char(&mut self, c: char) -> bool {
|
||||
let mut bits = [0; 4];
|
||||
self.type_str(c.encode_utf8(&mut bits))
|
||||
}
|
||||
|
|
@ -253,7 +270,7 @@ impl Input {
|
|||
|
||||
self.insert(!before);
|
||||
for c in s.to_string_lossy().chars() {
|
||||
self.type_(c);
|
||||
self.type_char(c);
|
||||
}
|
||||
self.escape();
|
||||
true
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ mod blocker;
|
|||
mod event;
|
||||
pub mod external;
|
||||
pub mod files;
|
||||
pub mod help;
|
||||
mod highlighter;
|
||||
pub mod input;
|
||||
pub mod manager;
|
||||
|
|
|
|||
|
|
@ -35,9 +35,9 @@ impl Folder {
|
|||
return false;
|
||||
}
|
||||
|
||||
let len = self.files.len();
|
||||
self.offset = self.offset.min(len);
|
||||
self.cursor = self.cursor.min(len.saturating_sub(1));
|
||||
let max = self.files.len().saturating_sub(1);
|
||||
self.offset = self.offset.min(max);
|
||||
self.cursor = self.cursor.min(max);
|
||||
self.set_page(true);
|
||||
|
||||
self.hover_repos();
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord"],"language":"en","version":"0.2"}
|
||||
{"language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord"],"version":"0.2"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue