continued work on vi line editing

This commit is contained in:
2025-05-20 18:03:43 -04:00
parent 25534d4cd9
commit 47759a05d4
3 changed files with 114 additions and 22 deletions

View File

@@ -2,12 +2,14 @@ use std::ops::Range;
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prompt::readline::linecmd::Anchor}; use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prompt::readline::linecmd::Anchor};
use super::linecmd::{At, CharSearch, MoveCmd, Movement, Verb, VerbCmd, Word}; use super::linecmd::{At, CharSearch, MoveCmd, Movement, Repeat, Verb, VerbCmd, Word};
#[derive(Default,Debug)] #[derive(Default,Debug)]
pub struct LineBuf { pub struct LineBuf {
pub buffer: Vec<char>, pub buffer: Vec<char>,
pub inserting: bool,
pub last_insert: String,
cursor: usize cursor: usize
} }
@@ -19,6 +21,15 @@ impl LineBuf {
self.buffer = init.to_string().chars().collect(); self.buffer = init.to_string().chars().collect();
self self
} }
pub fn begin_insert(&mut self) {
self.inserting = true;
}
pub fn finish_insert(&mut self) {
self.inserting = false;
}
pub fn take_ins_text(&mut self) -> String {
std::mem::take(&mut self.last_insert)
}
pub fn display_lines(&self) -> Vec<String> { pub fn display_lines(&self) -> Vec<String> {
let line_bullet = "".styled(Style::Dim); let line_bullet = "".styled(Style::Dim);
self.split_lines() self.split_lines()
@@ -519,7 +530,20 @@ impl LineBuf {
} }
} }
} }
Verb::InsertChar(ch) => self.insert_at_cursor(ch), Verb::InsertChar(ch) => {
if self.inserting {
self.last_insert.push(ch);
}
self.insert_at_cursor(ch)
}
Verb::Insert(text) => {
for ch in text.chars() {
if self.inserting {
self.last_insert.push(ch);
}
self.insert_at_cursor(ch);
}
}
Verb::InsertMode => todo!(), Verb::InsertMode => todo!(),
Verb::JoinLines => todo!(), Verb::JoinLines => todo!(),
Verb::ToggleCase => todo!(), Verb::ToggleCase => todo!(),
@@ -579,6 +603,9 @@ impl LineBuf {
self.buffer.drain(range); self.buffer.drain(range);
self.repos_cursor(); self.repos_cursor();
}); });
}
Verb::Repeat(rep) => {
} }
Verb::DeleteOne(anchor) => todo!(), Verb::DeleteOne(anchor) => todo!(),
Verb::Breakline(anchor) => todo!(), Verb::Breakline(anchor) => todo!(),

View File

@@ -110,6 +110,54 @@ pub struct MoveCmd {
pub movement: Movement pub movement: Movement
} }
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub struct Repeat {
pub movement: Option<MoveCmd>,
pub verb: Option<Box<VerbCmd>>,
pub ins_text: Option<String>
}
impl Repeat {
pub fn from_cmd(cmd: ViCmd) -> Self {
match cmd {
ViCmd::MoveVerb(verb_cmd, move_cmd) => {
Self {
movement: Some(move_cmd),
verb: Some(Box::new(verb_cmd)),
ins_text: None,
}
}
ViCmd::Verb(verb_cmd) => {
Self {
movement: None,
verb: Some(Box::new(verb_cmd)),
ins_text: None,
}
}
ViCmd::Move(move_cmd) => {
Self {
movement: Some(move_cmd),
verb: None,
ins_text: None,
}
}
}
}
pub fn set_ins_text(&mut self, ins_text: String) {
self.ins_text = Some(ins_text);
}
fn is_empty(&self) -> bool {
self.movement.is_none() && self.verb.is_none() && self.ins_text.is_none()
}
pub fn to_option(self) -> Option<Self> {
if self.is_empty() {
None
} else {
Some(self)
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive] #[non_exhaustive]
pub enum Verb { pub enum Verb {
@@ -139,7 +187,9 @@ pub enum Verb {
InsertMode, InsertMode,
/// `J` — join lines /// `J` — join lines
JoinLines, JoinLines,
Repeat(Repeat),
InsertChar(char), InsertChar(char),
Insert(String),
Breakline(Anchor), Breakline(Anchor),
Indent, Indent,
Dedent Dedent
@@ -161,6 +211,8 @@ impl Verb {
Verb::Indent | Verb::Indent |
Verb::InsertChar(_) | Verb::InsertChar(_) |
Verb::Breakline(_) | Verb::Breakline(_) |
Verb::Repeat(_) |
Verb::Insert(_) |
Verb::ReplaceChar(_) => false, Verb::ReplaceChar(_) => false,
Verb::Delete | Verb::Delete |
Verb::Change | Verb::Change |

View File

@@ -8,6 +8,7 @@ use term::Terminal;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, sys::sh_quit}, prelude::*}; use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, sys::sh_quit}, prelude::*};
use linecmd::Repeat;
pub mod term; pub mod term;
pub mod line; pub mod line;
pub mod keys; pub mod keys;
@@ -46,9 +47,7 @@ pub struct FernReader {
pub prompt: String, pub prompt: String,
pub line: LineBuf, pub line: LineBuf,
pub edit_mode: InputMode, pub edit_mode: InputMode,
pub count_arg: u16, pub last_vicmd: Option<Repeat>,
pub last_effect: Option<VerbCmd>,
pub last_movement: Option<MoveCmd>
} }
impl FernReader { impl FernReader {
@@ -59,9 +58,7 @@ impl FernReader {
prompt, prompt,
line, line,
edit_mode: Default::default(), edit_mode: Default::default(),
count_arg: Default::default(), last_vicmd: Default::default()
last_effect: Default::default(),
last_movement: Default::default(),
} }
} }
fn pack_line(&mut self) -> String { fn pack_line(&mut self) -> String {
@@ -142,6 +139,16 @@ impl FernReader {
} }
} }
} }
pub fn set_normal_mode(&mut self) {
self.edit_mode = InputMode::Normal;
self.line.finish_insert();
let ins_text = self.line.take_ins_text();
self.last_vicmd.as_mut().map(|cmd| cmd.set_ins_text(ins_text));
}
pub fn set_insert_mode(&mut self) {
self.edit_mode = InputMode::Insert;
self.line.begin_insert();
}
pub fn next_cmd(&mut self) -> ShResult<LineCmd> { pub fn next_cmd(&mut self) -> ShResult<LineCmd> {
let vi_cmd = ViCmdBuilder::new(); let vi_cmd = ViCmdBuilder::new();
match self.edit_mode { match self.edit_mode {
@@ -166,10 +173,8 @@ impl FernReader {
E(K::Tab, M::NONE) => LineCmd::Complete, E(K::Tab, M::NONE) => LineCmd::Complete,
E(K::Esc, M::NONE) => { E(K::Esc, M::NONE) => {
self.edit_mode = InputMode::Normal;
build_movement!(pending_cmd, Movement::BackwardChar)? build_movement!(pending_cmd, Movement::BackwardChar)?
} }
E(K::Char('D'), M::CTRL) => LineCmd::EndOfFile,
_ => { _ => {
flog!(INFO, "unhandled key in get_insert_cmd, trying common_cmd..."); flog!(INFO, "unhandled key in get_insert_cmd, trying common_cmd...");
return self.common_cmd(key, pending_cmd) return self.common_cmd(key, pending_cmd)
@@ -217,6 +222,14 @@ impl FernReader {
.build()?; .build()?;
LineCmd::ViCmd(cmd) LineCmd::ViCmd(cmd)
} }
E(K::Char('.'), M::NONE) => {
match &self.last_vicmd {
None => LineCmd::Null,
Some(cmd) => {
build_verb!(pending_cmd, Verb::Repeat(cmd.clone()))?
}
}
}
E(K::Char('j'), M::NONE) => LineCmd::LineDownOrNextHistory, E(K::Char('j'), M::NONE) => LineCmd::LineDownOrNextHistory,
E(K::Char('k'), M::NONE) => LineCmd::LineUpOrPreviousHistory, E(K::Char('k'), M::NONE) => LineCmd::LineUpOrPreviousHistory,
E(K::Char('D'), M::NONE) => build_moveverb!(pending_cmd,Verb::Delete,Movement::EndOfLine)?, E(K::Char('D'), M::NONE) => build_moveverb!(pending_cmd,Verb::Delete,Movement::EndOfLine)?,
@@ -234,27 +247,27 @@ impl FernReader {
E(K::Char('$'), M::NONE) => build_movement!(pending_cmd,Movement::EndOfLine)?, E(K::Char('$'), M::NONE) => build_movement!(pending_cmd,Movement::EndOfLine)?,
E(K::Char('x'), M::NONE) => build_verb!(pending_cmd,Verb::DeleteOne(Anchor::After))?, E(K::Char('x'), M::NONE) => build_verb!(pending_cmd,Verb::DeleteOne(Anchor::After))?,
E(K::Char('o'), M::NONE) => { E(K::Char('o'), M::NONE) => {
self.edit_mode = InputMode::Insert; self.set_insert_mode();
build_verb!(pending_cmd,Verb::Breakline(Anchor::After))? build_verb!(pending_cmd,Verb::Breakline(Anchor::After))?
} }
E(K::Char('O'), M::NONE) => { E(K::Char('O'), M::NONE) => {
self.edit_mode = InputMode::Insert; self.set_insert_mode();
build_verb!(pending_cmd,Verb::Breakline(Anchor::Before))? build_verb!(pending_cmd,Verb::Breakline(Anchor::Before))?
} }
E(K::Char('i'), M::NONE) => { E(K::Char('i'), M::NONE) => {
self.edit_mode = InputMode::Insert; self.set_insert_mode();
LineCmd::Null LineCmd::Null
} }
E(K::Char('I'), M::NONE) => { E(K::Char('I'), M::NONE) => {
self.edit_mode = InputMode::Insert; self.set_insert_mode();
build_movement!(pending_cmd,Movement::BeginningOfFirstWord)? build_movement!(pending_cmd,Movement::BeginningOfFirstWord)?
} }
E(K::Char('a'), M::NONE) => { E(K::Char('a'), M::NONE) => {
self.edit_mode = InputMode::Insert; self.set_insert_mode();
build_movement!(pending_cmd,Movement::ForwardChar)? build_movement!(pending_cmd,Movement::ForwardChar)?
} }
E(K::Char('A'), M::NONE) => { E(K::Char('A'), M::NONE) => {
self.edit_mode = InputMode::Insert; self.set_insert_mode();
build_movement!(pending_cmd,Movement::EndOfLine)? build_movement!(pending_cmd,Movement::EndOfLine)?
} }
E(K::Char('c'), M::NONE) => { E(K::Char('c'), M::NONE) => {
@@ -324,6 +337,7 @@ impl FernReader {
E(K::Up, M::NONE) => Ok(LineCmd::LineUpOrPreviousHistory), E(K::Up, M::NONE) => Ok(LineCmd::LineUpOrPreviousHistory),
E(K::Down, M::NONE) => Ok(LineCmd::LineDownOrNextHistory), E(K::Down, M::NONE) => Ok(LineCmd::LineDownOrNextHistory),
E(K::Enter, M::NONE) => Ok(LineCmd::AcceptLine), E(K::Enter, M::NONE) => Ok(LineCmd::AcceptLine),
E(K::Char('D'), M::CTRL) => Ok(LineCmd::EndOfFile),
E(K::Backspace, M::NONE) | E(K::Backspace, M::NONE) |
E(K::Char('h'), M::CTRL) => { E(K::Char('h'), M::CTRL) => {
Ok(LineCmd::backspace()) Ok(LineCmd::backspace())
@@ -331,29 +345,28 @@ impl FernReader {
_ => Err(ShErr::simple(ShErrKind::ReadlineErr,format!("Unhandled common key event: {key:?}"))) _ => Err(ShErr::simple(ShErrKind::ReadlineErr,format!("Unhandled common key event: {key:?}")))
} }
} }
pub fn handle_repeat(&mut self, cmd: &ViCmd) -> ShResult<()> {
Ok(())
}
pub fn exec_vi_cmd(&mut self, cmd: ViCmd) -> ShResult<()> { pub fn exec_vi_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
self.last_vicmd = Some(Repeat::from_cmd(cmd.clone()));
match cmd { match cmd {
ViCmd::MoveVerb(verb_cmd, move_cmd) => { ViCmd::MoveVerb(verb_cmd, move_cmd) => {
self.last_effect = Some(verb_cmd.clone());
self.last_movement = Some(move_cmd.clone());
let VerbCmd { verb_count, verb } = verb_cmd; let VerbCmd { verb_count, verb } = verb_cmd;
for _ in 0..verb_count { for _ in 0..verb_count {
self.line.exec_vi_cmd(Some(verb.clone()), Some(move_cmd.clone()))?; self.line.exec_vi_cmd(Some(verb.clone()), Some(move_cmd.clone()))?;
} }
if verb == Verb::Change { if verb == Verb::Change {
self.edit_mode = InputMode::Insert self.set_insert_mode();
} }
} }
ViCmd::Verb(verb_cmd) => { ViCmd::Verb(verb_cmd) => {
self.last_effect = Some(verb_cmd.clone());
let VerbCmd { verb_count, verb } = verb_cmd; let VerbCmd { verb_count, verb } = verb_cmd;
for _ in 0..verb_count { for _ in 0..verb_count {
self.line.exec_vi_cmd(Some(verb.clone()), None)?; self.line.exec_vi_cmd(Some(verb.clone()), None)?;
} }
} }
ViCmd::Move(move_cmd) => { ViCmd::Move(move_cmd) => {
self.last_movement = Some(move_cmd.clone());
self.line.exec_vi_cmd(None, Some(move_cmd))?; self.line.exec_vi_cmd(None, Some(move_cmd))?;
} }
} }