Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement more keybinds for inputs #2143

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions yazi-config/preset/keymap-default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,13 @@ keymap = [
{ on = "<C-[>", run = "escape", desc = "Go back the normal mode, or cancel input" },

# Mode
{ on = "i", run = "insert", desc = "Enter insert mode" },
{ on = "a", run = "insert --append", desc = "Enter append mode" },
{ on = "I", run = [ "move -999", "insert" ], desc = "Move to the BOL, and enter insert mode" },
{ on = "A", run = [ "move 999", "insert --append" ], desc = "Move to the EOL, and enter append mode" },
{ on = "v", run = "visual", desc = "Enter visual mode" },
{ on = "V", run = [ "move -999", "visual", "move 999" ], desc = "Enter visual mode and select all" },
{ on = "i", run = "insert", desc = "Enter insert mode" },
{ on = "I", run = [ "move first-char", "insert" ], desc = "Move to the BOL, and enter insert mode" },
{ on = "a", run = "insert --append", desc = "Enter append mode" },
{ on = "A", run = [ "move eol", "insert --append" ], desc = "Move to the EOL, and enter append mode" },
{ on = "v", run = "visual", desc = "Enter visual mode" },
{ on = "V", run = [ "move bol", "visual", "move eol" ], desc = "Enter visual mode and select all" },
{ on = "r", run = "replace", desc = "Replace a single character" },

# Character-wise movement
{ on = "h", run = "move -1", desc = "Move back a character" },
Expand All @@ -248,19 +249,24 @@ keymap = [
{ on = "<C-f>", run = "move 1", desc = "Move forward a character" },

# Word-wise movement
{ on = "b", run = "backward", desc = "Move back to the start of the current or previous word" },
{ on = "w", run = "forward", desc = "Move forward to the start of the next word" },
{ on = "e", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" },
{ on = "<A-b>", run = "backward", desc = "Move back to the start of the current or previous word" },
{ on = "<A-f>", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" },
{ on = "b", run = "backward", desc = "Move back to the start of the current or previous word" },
{ on = "B", run = "backward --big", desc = "Move back to the start of the current or previous WORD" },
{ on = "w", run = "forward", desc = "Move forward to the start of the next word" },
{ on = "W", run = "forward --big", desc = "Move forward to the start of the next WORD" },
{ on = "e", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" },
{ on = "E", run = "forward --big --end-of-word", desc = "Move forward to the end of the current or next WORD" },
{ on = "<A-b>", run = "backward", desc = "Move back to the start of the current or previous word" },
{ on = "<A-f>", run = "forward --end-of-word", desc = "Move forward to the end of the current or next word" },

# Line-wise movement
{ on = "0", run = "move -999", desc = "Move to the BOL" },
{ on = "$", run = "move 999", desc = "Move to the EOL" },
{ on = "<C-a>", run = "move -999", desc = "Move to the BOL" },
{ on = "<C-e>", run = "move 999", desc = "Move to the EOL" },
{ on = "<Home>", run = "move -999", desc = "Move to the BOL" },
{ on = "<End>", run = "move 999", desc = "Move to the EOL" },
{ on = "0", run = "move bol", desc = "Move to the BOL" },
{ on = "$", run = "move eol", desc = "Move to the EOL" },
{ on = "_", run = "move first-char", desc = "Move to the first non-whitespace character" },
{ on = "^", run = "move first-char", desc = "Move to the first non-whitespace character" },
{ on = "<C-a>", run = "move bol", desc = "Move to the BOL" },
{ on = "<C-e>", run = "move eol", desc = "Move to the EOL" },
{ on = "<Home>", run = "move bol", desc = "Move to the BOL" },
{ on = "<End>", run = "move eol", desc = "Move to the EOL" },

# Delete
{ on = "<Backspace>", run = "backspace", desc = "Delete the character before the cursor" },
Expand All @@ -276,9 +282,9 @@ keymap = [

# Cut/Yank/Paste
{ on = "d", run = "delete --cut", desc = "Cut the selected characters" },
{ on = "D", run = [ "delete --cut", "move 999" ], desc = "Cut until the EOL" },
{ on = "D", run = [ "delete --cut", "move eol" ], desc = "Cut until the EOL" },
{ on = "c", run = "delete --cut --insert", desc = "Cut the selected characters, and enter insert mode" },
{ on = "C", run = [ "delete --cut --insert", "move 999" ], desc = "Cut until the EOL, and enter insert mode" },
{ on = "C", run = [ "delete --cut --insert", "move eol" ], desc = "Cut until the EOL, and enter insert mode" },
{ on = "x", run = [ "delete --cut", "move 1 --in-operating" ], desc = "Cut the current character" },
{ on = "y", run = "yank", desc = "Copy the selected characters" },
{ on = "p", run = "paste", desc = "Paste the copied characters after the cursor" },
Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/input/commands/backspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ impl From<bool> for Opt {
impl Input {
#[yazi_codegen::command]
pub fn backspace(&mut self, opt: Opt) {
let snap = self.snaps.current_mut();
let snap = self.snap_mut();
if !opt.under && snap.cursor < 1 {
return;
} else if opt.under && snap.cursor >= snap.count() {
Expand Down
19 changes: 17 additions & 2 deletions yazi-core/src/input/commands/backward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,18 @@ use yazi_shared::{CharKind, event::CmdCow};

use crate::input::Input;

struct Opt {
big: bool,
}

impl From<CmdCow> for Opt {
fn from(c: CmdCow) -> Self { Self { big: c.bool("big") }
}
}

impl Input {
pub fn backward(&mut self, _: CmdCow) {
#[yazi_codegen::command]
pub fn backward(&mut self, opt: Opt) {
let snap = self.snap();
if snap.cursor == 0 {
return self.move_(0);
Expand All @@ -14,7 +24,12 @@ impl Input {
let mut prev = CharKind::new(it.next().unwrap().1);
for (i, c) in it {
let c = CharKind::new(c);
if prev != CharKind::Space && prev != c {
let new_char_kind = if opt.big {
(c == CharKind::Space) != (prev == CharKind::Space)
} else {
c != prev
};
if prev != CharKind::Space && new_char_kind {
return self.move_(-(i as isize));
}
prev = c;
Expand Down
2 changes: 1 addition & 1 deletion yazi-core/src/input/commands/complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Input {
format!("{}{after}", opt.word).replace(SEPARATOR, MAIN_SEPARATOR_STR)
};

let snap = self.snaps.current_mut();
let snap = self.snap_mut();
if new == snap.value {
return;
}
Expand Down
3 changes: 3 additions & 0 deletions yazi-core/src/input/commands/escape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ impl Input {
CompletionProxy::close();
}
}
InputMode::Replace => {
snap.mode = InputMode::Normal;
}
}

self.snaps.tag(self.limit());
Expand Down
17 changes: 14 additions & 3 deletions yazi-core/src/input/commands/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ use crate::input::{Input, op::InputOp};

struct Opt {
end_of_word: bool,
big: bool,
}

impl From<CmdCow> for Opt {
fn from(c: CmdCow) -> Self { Self { end_of_word: c.bool("end-of-word") } }
fn from(c: CmdCow) -> Self {
Self {
end_of_word: c.bool("end-of-word"),
big: c.bool("big"),
}
}
}

impl Input {
Expand All @@ -22,10 +28,15 @@ impl Input {

for (i, c) in it {
let c = CharKind::new(c);
let new_char_kind = if opt.big {
(c == CharKind::Space) != (prev == CharKind::Space)
} else {
c != prev
};
let b = if opt.end_of_word {
prev != CharKind::Space && prev != c && i != 1
prev != CharKind::Space && new_char_kind && i != 1
} else {
c != CharKind::Space && c != prev
c != CharKind::Space && new_char_kind
};
if b && !matches!(snap.op, InputOp::None | InputOp::Select(_)) {
return self.move_(i as isize);
Expand Down
5 changes: 1 addition & 4 deletions yazi-core/src/input/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
yazi_macro::mod_flat!(
backspace backward close complete delete escape forward insert kill move_ paste redo
show type_ undo visual yank
);
yazi_macro::mod_flat!(backspace backward close complete delete escape forward insert kill move_ paste redo replace show type_ undo visual yank);
72 changes: 61 additions & 11 deletions yazi-core/src/input/commands/move_.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
use std::str::FromStr;

use unicode_width::UnicodeWidthStr;
use yazi_macro::render;
use yazi_shared::event::{CmdCow, Data};

use crate::input::{Input, op::InputOp, snap::InputSnap};

struct Opt {
step: isize,
step: OptStep,
in_operating: bool,
}

impl From<CmdCow> for Opt {
fn from(c: CmdCow) -> Self {
Self {
step: c.first().and_then(Data::as_isize).unwrap_or(0),
step: c.first().and_then(|d| d.try_into().ok()).unwrap_or_default(),
in_operating: c.bool("in-operating"),
}
}
}
impl From<isize> for Opt {
fn from(step: isize) -> Self { Self { step, in_operating: false } }
fn from(step: isize) -> Self { Self { step: step.into(), in_operating: false } }
}

impl Input {
Expand All @@ -29,14 +31,7 @@ impl Input {
return;
}

render!(self.handle_op(
if opt.step <= 0 {
snap.cursor.saturating_sub(opt.step.unsigned_abs())
} else {
snap.count().min(snap.cursor + opt.step as usize)
},
false,
));
render!(self.handle_op(opt.step.cursor(snap), false));

let (limit, snap) = (self.limit(), self.snap_mut());
if snap.offset > snap.cursor {
Expand All @@ -53,3 +48,58 @@ impl Input {
}
}
}

// --- Step
enum OptStep {
Offset(isize),
Bol,
Eol,
FirstChar,
}

impl OptStep {
fn cursor(self, snap: &InputSnap) -> usize {
match self {
Self::Offset(n) if n <= 0 => snap.cursor.saturating_add_signed(n),
Self::Offset(n) => snap.count().min(snap.cursor + n as usize),
Self::Bol => 0,
Self::Eol => snap.count(),
Self::FirstChar => {
snap.value.chars().enumerate().find(|(_, c)| !c.is_whitespace()).map_or(0, |(i, _)| i)
}
}
}
}

impl Default for OptStep {
fn default() -> Self { 0.into() }
}

impl FromStr for OptStep {
type Err = ();

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"bol" => Self::Bol,
"eol" => Self::Eol,
"first-char" => Self::FirstChar,
s => Self::Offset(s.parse().map_err(|_| ())?),
})
}
}

impl From<isize> for OptStep {
fn from(value: isize) -> Self { Self::Offset(value) }
}

impl TryFrom<&Data> for OptStep {
type Error = ();

fn try_from(value: &Data) -> Result<Self, Self::Error> {
match value {
Data::String(s) => s.parse().map_err(|_| ()),
Data::Integer(i) => Ok(Self::from(*i as isize)),
_ => Err(()),
}
}
}
31 changes: 31 additions & 0 deletions yazi-core/src/input/commands/replace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use yazi_macro::render;
use yazi_shared::event::CmdCow;

use crate::input::{Input, InputMode, op::InputOp};

impl Input {
#[yazi_codegen::command]
pub fn replace(&mut self, _: CmdCow) {
let snap = self.snap_mut();
if snap.mode == InputMode::Normal {
snap.op = InputOp::None;
snap.mode = InputMode::Replace;
render!();
}
}

// FIXME: need to create a new snap for each replace operation, otherwise we
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix these two FIXMEs

// can't undo/redo it with `u` and `Ctrl-r`
pub fn replace_str(&mut self, s: &str) {
let snap = self.snap_mut();

let start = snap.idx(snap.cursor).unwrap();
let end = snap.idx(snap.cursor + 1).unwrap(); // FIXME: panic if the input is empty while in replace mode

snap.mode = InputMode::Normal;
snap.value.replace_range(start..end, s);

self.flush_value();
render!();
}
}
2 changes: 1 addition & 1 deletion yazi-core/src/input/commands/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ impl Input {

// Set cursor after reset
if let Some(cursor) = opt.cfg.cursor {
self.snaps.current_mut().cursor = cursor;
self.snap_mut().cursor = cursor;
self.move_(0);
}

Expand Down
23 changes: 19 additions & 4 deletions yazi-core/src/input/commands/type_.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use yazi_config::keymap::Key;
use yazi_macro::render;
use yazi_shared::event::CmdCow;

use crate::input::{Input, InputMode};
Expand All @@ -11,15 +12,29 @@ impl From<CmdCow> for Opt {

impl Input {
pub fn type_(&mut self, key: &Key) -> bool {
if self.mode() != InputMode::Insert {
return false;
}
let Some(c) = key.plain() else { return false };

if let Some(c) = key.plain() {
if self.mode() == InputMode::Insert {
self.type_str(c.encode_utf8(&mut [0; 4]));
return true;
} else if self.mode() == InputMode::Replace {
self.replace_str(c.encode_utf8(&mut [0; 4]));
return true;
}

false
}

pub fn type_str(&mut self, s: &str) {
let snap = self.snap_mut();
if snap.cursor < 1 {
snap.value.insert_str(0, s);
} else {
snap.value.insert_str(snap.idx(snap.cursor).unwrap(), s);
}

self.move_(s.chars().count() as isize);
self.flush_value();
render!();
}
}
16 changes: 1 addition & 15 deletions yazi-core/src/input/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::ops::Range;
use tokio::sync::mpsc::UnboundedSender;
use unicode_width::UnicodeWidthStr;
use yazi_config::{INPUT, popup::Position};
use yazi_macro::render;
use yazi_plugin::CLIPBOARD;
use yazi_shared::errors::InputError;

Expand Down Expand Up @@ -33,22 +32,9 @@ impl Input {
self.position.offset.width.saturating_sub(INPUT.border()) as usize
}

pub fn type_str(&mut self, s: &str) {
let snap = self.snaps.current_mut();
if snap.cursor < 1 {
snap.value.insert_str(0, s);
} else {
snap.value.insert_str(snap.idx(snap.cursor).unwrap(), s);
}

self.move_(s.chars().count() as isize);
self.flush_value();
render!();
}

pub(super) fn handle_op(&mut self, cursor: usize, include: bool) -> bool {
let old = self.snap().clone();
let snap = self.snaps.current_mut();
let snap = self.snap_mut();

match snap.op {
InputOp::None | InputOp::Select(_) => {
Expand Down
1 change: 1 addition & 0 deletions yazi-core/src/input/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub enum InputMode {
Normal,
#[default]
Insert,
Replace,
}

impl InputMode {
Expand Down
Loading
Loading