mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
The built-in command editor intercepts ctrl-w before the shell sees it, and its whitespace-only word boundaries killed a whole path in one stroke. fish binds ctrl-w to backward-kill-path-component, so users coming from kitty or Terminal.app expect /usr/local/bin to go one segment at a time. Mirror fish's path-component word motion: at most one run per character class, separators (slash, equals, quotes, ...) end a kill next to whitespace on their own. alt-backspace keeps the coarse whitespace-delimited kill, matching fish's split between the two chords. Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
This commit is contained in:
@@ -320,6 +320,50 @@ impl CmdEditor {
|
||||
self.kill_range(self.cursor, end);
|
||||
}
|
||||
|
||||
fn eat_left(&mut self, pred: impl Fn(char) -> bool) {
|
||||
while self.cursor > 0 && pred(self.chars[self.cursor - 1]) {
|
||||
self.cursor -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// fish's ⌃W (`backward-kill-path-component`): stop at `/` and friends
|
||||
/// instead of eating a whole whitespace-delimited token, so a path is
|
||||
/// killed one component at a time (#658). Mirrors fish's word-motion
|
||||
/// state machine: at most one run of each character class, and a
|
||||
/// separator run next to whitespace ends the kill on its own.
|
||||
pub fn delete_path_component_left(&mut self) {
|
||||
fn sep(c: char) -> bool {
|
||||
!c.is_whitespace() && "/={,}'\":@|;<>&".contains(c)
|
||||
}
|
||||
fn word(c: char) -> bool {
|
||||
!c.is_whitespace() && !sep(c)
|
||||
}
|
||||
self.checkpoint();
|
||||
let end = self.cursor;
|
||||
let before = |cursor: usize, chars: &[char]| (cursor > 0).then(|| chars[cursor - 1]);
|
||||
match before(self.cursor, &self.chars) {
|
||||
Some(c) if c.is_whitespace() => {
|
||||
self.eat_left(char::is_whitespace);
|
||||
match before(self.cursor, &self.chars) {
|
||||
Some('/') => {
|
||||
self.eat_left(|c| c == '/');
|
||||
self.eat_left(word);
|
||||
}
|
||||
Some(c) if word(c) => self.eat_left(word),
|
||||
Some(_) => self.eat_left(sep),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Some(c) if word(c) => self.eat_left(word),
|
||||
Some(_) => {
|
||||
self.eat_left(sep);
|
||||
self.eat_left(word);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
self.kill_range(self.cursor, end);
|
||||
}
|
||||
|
||||
pub fn delete_to_start(&mut self) {
|
||||
self.checkpoint();
|
||||
let end = self.cursor;
|
||||
@@ -452,6 +496,38 @@ mod tests {
|
||||
assert_eq!(d.cursor(), 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_component_delete_walks_a_path_one_segment_at_a_time() {
|
||||
let mut e = ed("ls /usr/local/bin", 17);
|
||||
e.delete_path_component_left();
|
||||
assert_eq!(e.text(), "ls /usr/local/");
|
||||
e.delete_path_component_left();
|
||||
assert_eq!(e.text(), "ls /usr/");
|
||||
e.delete_path_component_left();
|
||||
assert_eq!(e.text(), "ls /");
|
||||
e.delete_path_component_left();
|
||||
assert_eq!(e.text(), "ls ");
|
||||
e.delete_path_component_left();
|
||||
assert_eq!((e.text().as_str(), e.cursor()), ("", 0));
|
||||
e.yank();
|
||||
assert_eq!(e.text(), "ls ");
|
||||
|
||||
let mut f = ed("--out=/tmp/x", 12);
|
||||
f.delete_path_component_left();
|
||||
assert_eq!(f.text(), "--out=/tmp/");
|
||||
f.delete_path_component_left();
|
||||
assert_eq!(f.text(), "--out=/");
|
||||
f.delete_path_component_left();
|
||||
assert_eq!(f.text(), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path_component_delete_matches_word_delete_on_plain_words() {
|
||||
let mut e = ed("echo hello world", 16);
|
||||
e.delete_path_component_left();
|
||||
assert_eq!((e.text().as_str(), e.cursor()), ("echo hello ", 11));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kills_fill_the_kill_buffer_and_yank_puts_it_back() {
|
||||
let mut e = ed("git push origin", 15);
|
||||
|
||||
@@ -2251,7 +2251,7 @@ impl TerminalView {
|
||||
}
|
||||
"w" => {
|
||||
if !self.cmd.delete_selection() {
|
||||
self.cmd.delete_word_left();
|
||||
self.cmd.delete_path_component_left();
|
||||
}
|
||||
}
|
||||
"u" => {
|
||||
|
||||
Reference in New Issue
Block a user