continued work on vi line editing
This commit is contained in:
@@ -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!(),
|
||||||
|
|||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user