Added ex mode to line editor, a 'keymap' builtin, and a zsh-like widget system using ':!<shellcmd>' ex mode commands

This commit is contained in:
2026-03-03 03:19:02 -05:00
parent a28446329e
commit 9d13565176
23 changed files with 3141 additions and 2016 deletions

View File

@@ -0,0 +1,124 @@
use super::{common_cmds, CmdReplay, ModeReport, ViMode};
use crate::readline::keys::{KeyCode as K, KeyEvent as E, ModKeys as M};
use crate::readline::vicmd::{
Direction, Motion, MotionCmd, To, Verb, VerbCmd, ViCmd, Word,
};
#[derive(Default, Clone, Debug)]
pub struct ViInsert {
cmds: Vec<ViCmd>,
pending_cmd: ViCmd,
repeat_count: u16,
}
impl ViInsert {
pub fn new() -> Self {
Self::default()
}
pub fn with_count(mut self, repeat_count: u16) -> Self {
self.repeat_count = repeat_count;
self
}
pub fn register_and_return(&mut self) -> Option<ViCmd> {
let mut cmd = self.take_cmd();
cmd.normalize_counts();
self.register_cmd(&cmd);
Some(cmd)
}
pub fn ctrl_w_is_undo(&self) -> bool {
let insert_count = self
.cmds
.iter()
.filter(|cmd: &&ViCmd| matches!(cmd.verb(), Some(VerbCmd(1, Verb::InsertChar(_)))))
.count();
let backspace_count = self
.cmds
.iter()
.filter(|cmd: &&ViCmd| matches!(cmd.verb(), Some(VerbCmd(1, Verb::Delete))))
.count();
insert_count > backspace_count
}
pub fn register_cmd(&mut self, cmd: &ViCmd) {
self.cmds.push(cmd.clone())
}
pub fn take_cmd(&mut self) -> ViCmd {
std::mem::take(&mut self.pending_cmd)
}
}
impl ViMode for ViInsert {
fn handle_key(&mut self, key: E) -> Option<ViCmd> {
match key {
E(K::Char(ch), M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::InsertChar(ch)));
self
.pending_cmd
.set_motion(MotionCmd(1, Motion::ForwardChar));
self.register_and_return()
}
E(K::Char('W'), M::CTRL) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
self.pending_cmd.set_motion(MotionCmd(
1,
Motion::WordMotion(To::Start, Word::Normal, Direction::Backward),
));
self.register_and_return()
}
E(K::Char('H'), M::CTRL) | E(K::Backspace, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::Delete));
self
.pending_cmd
.set_motion(MotionCmd(1, Motion::BackwardCharForced));
self.register_and_return()
}
E(K::BackTab, M::NONE) => {
self
.pending_cmd
.set_verb(VerbCmd(1, Verb::CompleteBackward));
self.register_and_return()
}
E(K::Char('I'), M::CTRL) | E(K::Tab, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::Complete));
self.register_and_return()
}
E(K::Esc, M::NONE) => {
self.pending_cmd.set_verb(VerbCmd(1, Verb::NormalMode));
self
.pending_cmd
.set_motion(MotionCmd(1, Motion::BackwardChar));
self.register_and_return()
}
_ => common_cmds(key),
}
}
fn is_repeatable(&self) -> bool {
true
}
fn as_replay(&self) -> Option<CmdReplay> {
Some(CmdReplay::mode(self.cmds.clone(), self.repeat_count))
}
fn cursor_style(&self) -> String {
"\x1b[6 q".to_string()
}
fn pending_seq(&self) -> Option<String> {
None
}
fn move_cursor_on_undo(&self) -> bool {
true
}
fn clamp_cursor(&self) -> bool {
false
}
fn hist_scroll_start_pos(&self) -> Option<To> {
Some(To::End)
}
fn report_mode(&self) -> ModeReport {
ModeReport::Insert
}
}