From 1c42a36810234b220a237084c4ff6dc6fee77dc0 Mon Sep 17 00:00:00 2001 From: pagedmov Date: Sat, 21 Mar 2026 01:19:55 -0400 Subject: [PATCH] chore: extracted some logic in ShedVi into helpers, cleaned up compiler warnings --- src/builtin/help.rs | 9 +- src/jobs.rs | 2 +- src/readline/linebuf.rs | 24 +- src/readline/mod.rs | 737 +++++++++++++++++++------------------- src/readline/term.rs | 2 +- src/readline/vimode/ex.rs | 8 +- 6 files changed, 389 insertions(+), 393 deletions(-) diff --git a/src/builtin/help.rs b/src/builtin/help.rs index 17c0c41..68643fe 100644 --- a/src/builtin/help.rs +++ b/src/builtin/help.rs @@ -1,7 +1,6 @@ use std::{env, io::Write, path::Path}; use ariadne::Span as ASpan; -use nix::libc::STDIN_FILENO; use crate::{ libsh::{ @@ -9,11 +8,10 @@ use crate::{ guards::RawModeGuard, }, parse::{ - NdRule, Node, Redir, RedirType, + NdRule, Node, execute::{exec_input, prepare_argv}, lex::{QuoteState, Span}, }, - procio::{IoFrame, IoMode}, readline::{complete::ScoredCandidate, markers}, state, }; @@ -100,13 +98,12 @@ pub fn help(node: Node) -> ShResult<()> { let expanded = expand_help(&unescaped); let tags = read_tags(&expanded); - for (tag, line) in &tags {} + for (_tag, _line) in &tags {} - if let Some((matched_tag, line)) = get_best_match(&topic, &tags) { + if let Some((_matched_tag, line)) = get_best_match(&topic, &tags) { open_help(&expanded, Some(line), Some(filename))?; state::set_status(0); return Ok(()); - } else { } } } diff --git a/src/jobs.rs b/src/jobs.rs index d4f18d0..deb6792 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -13,7 +13,7 @@ use crate::{ prelude::*, procio::{IoMode, borrow_fd}, signal::{disable_reaping, enable_reaping}, - state::{self, ShellParam, Var, VarFlags, VarKind, set_status, write_jobs, write_vars}, + state::{self, ShellParam, VarFlags, VarKind, set_status, write_jobs, write_vars}, }; pub const SIG_EXIT_OFFSET: i32 = 128; diff --git a/src/readline/linebuf.rs b/src/readline/linebuf.rs index aa5d580..2080bd1 100644 --- a/src/readline/linebuf.rs +++ b/src/readline/linebuf.rs @@ -26,7 +26,6 @@ use crate::{ state::{self, VarFlags, VarKind, read_shopts, read_vars, write_meta, write_vars}, }; -const PUNCTUATION: [&str; 3] = ["?", "!", "."]; const DEFAULT_VIEWPORT_HEIGHT: usize = 40; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] @@ -551,6 +550,7 @@ impl Default for LineBuf { } } +#[allow(dead_code,unused_variables)] impl LineBuf { pub fn new() -> Self { Self::default() @@ -2432,7 +2432,7 @@ impl LineBuf { self.insert_str(&contents); } ReadSrc::Cmd(cmd) => { - let output = match expand_cmd_sub(&cmd) { + let output = match expand_cmd_sub(cmd) { Ok(out) => out, Err(e) => { e.print_error(); @@ -2582,11 +2582,10 @@ impl LineBuf { let new_cursor = self.cursor.pos; // Stop merging on any non-char-insert command, even if buffer didn't change - if !is_char_insert && !is_undo_op { - if let Some(edit) = self.undo_stack.last_mut() { - edit.merging = false; - } - } + if !is_char_insert && !is_undo_op + && let Some(edit) = self.undo_stack.last_mut() { + edit.merging = false; + } if self.lines != before && !is_undo_op { self.redo_stack.clear(); @@ -2607,11 +2606,10 @@ impl LineBuf { } else { self.handle_edit(before, new_cursor, old_cursor); // Change starts a new merge chain so subsequent InsertChars merge into it - if starts_merge { - if let Some(edit) = self.undo_stack.last_mut() { - edit.merging = true; - } - } + if starts_merge + && let Some(edit) = self.undo_stack.last_mut() { + edit.merging = true; + } } } @@ -3040,7 +3038,7 @@ impl Display for LineBuf { } cloned[e].push_char(markers::VISUAL_MODE_END); } - SelectMode::Block(pos) => todo!(), + SelectMode::Block(_pos) => todo!(), } let mut lines = vec![]; for line in &cloned { diff --git a/src/readline/mod.rs b/src/readline/mod.rs index 16f977c..35c8c32 100644 --- a/src/readline/mod.rs +++ b/src/readline/mod.rs @@ -1,6 +1,6 @@ use history::History; use keys::{KeyCode, KeyEvent, ModKeys}; -use linebuf::{LineBuf, SelectMode}; +use linebuf::LineBuf; use std::collections::VecDeque; use std::fmt::Write; use term::{KeyReader, Layout, LineWriter, PollReader, TermWriter, get_win_size}; @@ -10,14 +10,13 @@ use vimode::{CmdReplay, ModeReport, ViInsert, ViMode, ViNormal, ViReplace, ViVis use crate::builtin::keymap::{KeyMapFlags, KeyMapMatch}; use crate::expand::expand_prompt; -use crate::libsh::error::{ShErr, ShErrKind}; use crate::libsh::utils::AutoCmdVecUtils; use crate::parse::lex::{LexStream, QuoteState}; use crate::readline::complete::{FuzzyCompleter, SelectorResponse}; use crate::readline::term::{Pos, TermReader, calc_str_width}; use crate::readline::vimode::{ViEx, ViVerbatim}; use crate::state::{ - AutoCmdKind, ShellParam, Var, VarFlags, VarKind, read_logic, read_meta, read_shopts, with_vars, write_meta, write_vars + AutoCmdKind, ShellParam, Var, VarFlags, VarKind, read_logic, read_shopts, with_vars, write_meta, write_vars }; use crate::{ libsh::error::ShResult, @@ -278,45 +277,24 @@ pub struct ShedVi { impl ShedVi { pub fn new(prompt: Prompt, tty: RawFd) -> ShResult { - let mut new = Self { - reader: PollReader::new(), - writer: TermWriter::new(tty), - prompt, - tty, - completer: Box::new(FuzzyCompleter::default()), - highlighter: Highlighter::new(), - mode: Box::new(ViInsert::new()), - saved_mode: None, - pending_keymap: Vec::new(), - old_layout: None, - repeat_action: None, - repeat_motion: None, - editor: LineBuf::new(), - history: History::new()?, - ex_history: History::empty(), - needs_redraw: true, - ctrl_d_warning_counter: 0, - status_msgs: VecDeque::new() - }; - write_vars(|v| { - v.set_var( - "SHED_VI_MODE", - VarKind::Str(new.mode.report_mode().to_string()), - VarFlags::NONE, - ) - })?; - new.prompt.refresh(); - new.writer.flush_write("\n")?; // ensure we start on a new line, in case the previous command didn't end with a newline - new.print_line(false)?; - Ok(new) + Self::new_private(prompt, tty, true) } pub fn new_no_hist(prompt: Prompt, tty: RawFd) -> ShResult { + Self::new_private(prompt, tty, false) + } + + fn new_private(prompt: Prompt, tty: RawFd, with_hist: bool) -> ShResult { + let history = if with_hist { + History::new()? + } else { + History::empty() + }; let mut new = Self { reader: PollReader::new(), writer: TermWriter::new(tty), - tty, prompt, + tty, completer: Box::new(FuzzyCompleter::default()), highlighter: Highlighter::new(), mode: Box::new(ViInsert::new()), @@ -326,7 +304,7 @@ impl ShedVi { repeat_action: None, repeat_motion: None, editor: LineBuf::new(), - history: History::empty(), + history, ex_history: History::empty(), needs_redraw: true, ctrl_d_warning_counter: 0, @@ -343,7 +321,7 @@ impl ShedVi { new.writer.flush_write("\n")?; // ensure we start on a new line, in case the previous command didn't end with a newline new.print_line(false)?; Ok(new) - } + } pub fn with_initial(mut self, initial: &str) -> Self { self.editor = LineBuf::new().with_initial(initial, 0); @@ -677,181 +655,213 @@ impl ShedVi { Ok(ReadlineEvent::Pending) } + fn accept_hint(&mut self) -> ShResult> { + self.editor.accept_hint(); + if !self.history.at_pending() { + self.history.reset_to_pending(); + } + self + .history + .update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat())); + self.needs_redraw = true; + + Ok(None) + } + + fn handle_tab(&mut self, key: KeyEvent) -> ShResult> { + let KeyEvent(KeyCode::Tab, mod_keys) = key else { + return Ok(None) + }; + + if self.mode.report_mode() != ModeReport::Ex + && self.editor.attempt_history_expansion(&self.history) + { + // If history expansion occurred, don't attempt completion yet + // allow the user to see the expanded command and accept or edit it before completing + return Ok(None); + } + + let direction = match mod_keys { + ModKeys::SHIFT => -1, + _ => 1, + }; + let line = self.focused_editor().joined(); + let cursor_pos = self.focused_editor().cursor_byte_pos(); + + match self.completer.complete(line, cursor_pos, direction) { + Err(e) => { + e.print_error(); + // Printing the error invalidates the layout + self.old_layout = None; + } + Ok(Some(line)) => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect)); + let cand = self.completer.selected_candidate().unwrap_or_default(); + with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || { + post_cmds.exec_with(&cand); + }); + + let span_start = self.completer.token_span().0; + + let new_cursor = span_start + + self + .completer + .selected_candidate() + .map(|c| c.len()) + .unwrap_or_default(); + + self.focused_editor().set_buffer(line.clone()); + self.focused_editor().set_cursor_from_flat(new_cursor); + + if !self.history.at_pending() { + self.history.reset_to_pending(); + } + self + .history + .update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat())); + let hint = self.history.get_hint(); + self.editor.set_hint(hint); + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str(self.mode.report_mode().to_string()), + VarFlags::NONE, + ) + }) + .ok(); + + // If we are here, we hit a case where pressing tab returned a single candidate + // So we can just go ahead and reset the completer after this + self.completer.reset(); + } + Ok(None) => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionStart)); + let candidates = self.completer.all_candidates(); + let num_candidates = candidates.len(); + with_vars( + [ + ("_NUM_MATCHES".into(), Into::::into(num_candidates)), + ("_MATCHES".into(), Into::::into(candidates)), + ( + "_SEARCH_STR".into(), + Into::::into(self.completer.token()), + ), + ], + || { + post_cmds.exec(); + }, + ); + + if self.completer.is_active() { + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str("COMPLETE".to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); + self.needs_redraw = true; + self.editor.set_hint(None); + } else { + self.writer.send_bell().ok(); + } + } + } + + self.needs_redraw = true; + Ok(None) + } + + fn start_hist_search(&mut self) { + let initial = self.focused_editor().joined(); + match self.focused_history().start_search(&initial) { + Some(entry) => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); + with_vars([("_HIST_ENTRY".into(), entry.clone())], || { + post_cmds.exec_with(&entry); + }); + + self.focused_editor().set_buffer(entry); + self.focused_editor().move_cursor_to_end(); + self + .history + .update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat())); + self.editor.set_hint(None); + } + None => { + let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen)); + let entries = self.focused_history().fuzzy_finder.candidates().to_vec(); + let matches = self + .focused_history() + .fuzzy_finder + .filtered() + .iter() + .cloned() + .map(|sc| sc.content) + .collect::>(); + + let num_entries = entries.len(); + let num_matches = matches.len(); + with_vars( + [ + ("_ENTRIES".into(), Into::::into(entries)), + ("_NUM_ENTRIES".into(), Into::::into(num_entries)), + ("_MATCHES".into(), Into::::into(matches)), + ("_NUM_MATCHES".into(), Into::::into(num_matches)), + ("_SEARCH_STR".into(), Into::::into(initial)), + ], + || { + post_cmds.exec(); + }, + ); + + if self.focused_history().fuzzy_finder.is_active() { + write_vars(|v| { + v.set_var( + "SHED_VI_MODE", + VarKind::Str("SEARCH".to_string()), + VarFlags::NONE, + ) + }) + .ok(); + self.prompt.refresh(); + self.needs_redraw = true; + self.editor.set_hint(None); + } else { + self.writer.send_bell().ok(); + } + } + } + } + + fn submit(&mut self) -> ShResult> { + if self.editor.attempt_history_expansion(&self.history) { + // If history expansion occurred, don't submit yet + // allow the user to see the expanded command and accept or edit it before submitting + return Ok(None); + } + + self.editor.set_hint(None); + self.editor.set_cursor_from_flat(self.editor.cursor_max()); + self.print_line(true)?; + self.writer.flush_write("\n")?; + let buf = self.editor.take_buf(); + self.history.reset(); + Ok(Some(ReadlineEvent::Line(buf))) + } + pub fn handle_key(&mut self, key: KeyEvent) -> ShResult> { if self.should_accept_hint(&key) { - self.editor.accept_hint(); - if !self.history.at_pending() { - self.history.reset_to_pending(); - } - self - .history - .update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat())); - self.needs_redraw = true; - return Ok(None); + return self.accept_hint(); } - if let KeyEvent(KeyCode::Tab, mod_keys) = key { - if self.mode.report_mode() != ModeReport::Ex - && self.editor.attempt_history_expansion(&self.history) - { - // If history expansion occurred, don't attempt completion yet - // allow the user to see the expanded command and accept or edit it before completing - return Ok(None); - } - - let direction = match mod_keys { - ModKeys::SHIFT => -1, - _ => 1, - }; - let line = self.focused_editor().joined(); - let cursor_pos = self.focused_editor().cursor_byte_pos(); - - match self.completer.complete(line, cursor_pos, direction) { - Err(e) => { - e.print_error(); - // Printing the error invalidates the layout - self.old_layout = None; - } - Ok(Some(line)) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionSelect)); - let cand = self.completer.selected_candidate().unwrap_or_default(); - with_vars([("_COMP_CANDIDATE".into(), cand.clone())], || { - post_cmds.exec_with(&cand); - }); - - let span_start = self.completer.token_span().0; - - let new_cursor = span_start - + self - .completer - .selected_candidate() - .map(|c| c.len()) - .unwrap_or_default(); - - self.focused_editor().set_buffer(line.clone()); - self.focused_editor().set_cursor_from_flat(new_cursor); - - if !self.history.at_pending() { - self.history.reset_to_pending(); - } - self - .history - .update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat())); - let hint = self.history.get_hint(); - self.editor.set_hint(hint); - write_vars(|v| { - v.set_var( - "SHED_VI_MODE", - VarKind::Str(self.mode.report_mode().to_string()), - VarFlags::NONE, - ) - }) - .ok(); - - // If we are here, we hit a case where pressing tab returned a single candidate - // So we can just go ahead and reset the completer after this - self.completer.reset(); - } - Ok(None) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnCompletionStart)); - let candidates = self.completer.all_candidates(); - let num_candidates = candidates.len(); - with_vars( - [ - ("_NUM_MATCHES".into(), Into::::into(num_candidates)), - ("_MATCHES".into(), Into::::into(candidates)), - ( - "_SEARCH_STR".into(), - Into::::into(self.completer.token()), - ), - ], - || { - post_cmds.exec(); - }, - ); - - if self.completer.is_active() { - write_vars(|v| { - v.set_var( - "SHED_VI_MODE", - VarKind::Str("COMPLETE".to_string()), - VarFlags::NONE, - ) - }) - .ok(); - self.prompt.refresh(); - self.needs_redraw = true; - self.editor.set_hint(None); - } else { - self.writer.send_bell().ok(); - } - } - } - - self.needs_redraw = true; - return Ok(None); + if let KeyEvent(KeyCode::Tab, _) = key { + return self.handle_tab(key); } else if let KeyEvent(KeyCode::Char('R'), ModKeys::CTRL) = key - && matches!(self.mode.report_mode(), ModeReport::Insert | ModeReport::Ex) - { - let initial = self.focused_editor().joined(); - match self.focused_history().start_search(&initial) { - Some(entry) => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistorySelect)); - with_vars([("_HIST_ENTRY".into(), entry.clone())], || { - post_cmds.exec_with(&entry); - }); - - self.focused_editor().set_buffer(entry); - self.focused_editor().move_cursor_to_end(); - self - .history - .update_pending_cmd((&self.editor.joined(), self.editor.cursor_to_flat())); - self.editor.set_hint(None); - } - None => { - let post_cmds = read_logic(|l| l.get_autocmds(AutoCmdKind::OnHistoryOpen)); - let entries = self.focused_history().fuzzy_finder.candidates().to_vec(); - let matches = self - .focused_history() - .fuzzy_finder - .filtered() - .iter() - .cloned() - .map(|sc| sc.content) - .collect::>(); - - let num_entries = entries.len(); - let num_matches = matches.len(); - with_vars( - [ - ("_ENTRIES".into(), Into::::into(entries)), - ("_NUM_ENTRIES".into(), Into::::into(num_entries)), - ("_MATCHES".into(), Into::::into(matches)), - ("_NUM_MATCHES".into(), Into::::into(num_matches)), - ("_SEARCH_STR".into(), Into::::into(initial)), - ], - || { - post_cmds.exec(); - }, - ); - - if self.focused_history().fuzzy_finder.is_active() { - write_vars(|v| { - v.set_var( - "SHED_VI_MODE", - VarKind::Str("SEARCH".to_string()), - VarFlags::NONE, - ) - }) - .ok(); - self.prompt.refresh(); - self.needs_redraw = true; - self.editor.set_hint(None); - } else { - self.writer.send_bell().ok(); - } - } - } + && matches!(self.mode.report_mode(), ModeReport::Insert | ModeReport::Ex) { + self.start_hist_search(); } let Ok(cmd) = self.mode.handle_key_fallible(key) else { @@ -871,22 +881,9 @@ impl ShedVi { } if cmd.is_submit_action() - && !self.editor.cursor_is_escaped() - && (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete)) - { - if self.editor.attempt_history_expansion(&self.history) { - // If history expansion occurred, don't submit yet - // allow the user to see the expanded command and accept or edit it before submitting - return Ok(None); - } - - self.editor.set_hint(None); - self.editor.set_cursor_from_flat(self.editor.cursor_max()); - self.print_line(true)?; - self.writer.flush_write("\n")?; - let buf = self.editor.take_buf(); - self.history.reset(); - return Ok(Some(ReadlineEvent::Line(buf))); + && !self.editor.cursor_is_escaped() + && (self.should_submit()? || !read_shopts(|o| o.prompt.linebreak_on_incomplete)) { + return self.submit(); } if (cmd.verb().is_some_and(|v| v.1 == Verb::EndOfFile) @@ -1354,166 +1351,170 @@ impl ShedVi { } } + pub fn handle_cmd_repeat(&mut self, cmd: ViCmd) -> ShResult<()> { + let Some(replay) = self.repeat_action.clone() else { + return Ok(()); + }; + let ViCmd { verb, .. } = cmd; + let VerbCmd(count, _) = verb.unwrap(); + match replay { + CmdReplay::ModeReplay { cmds, mut repeat } => { + if count > 1 { + repeat = count as u16; + } + + let old_mode = self.mode.report_mode(); + + for _ in 0..repeat { + let cmds = cmds.clone(); + for (i, cmd) in cmds.iter().enumerate() { + self.exec_cmd(cmd.clone(), true)?; + // After the first command, start merging so all subsequent + // edits fold into one undo entry (e.g. cw + inserted chars) + if i == 0 + && let Some(edit) = self.editor.undo_stack.last_mut() + { + edit.start_merge(); + } + } + // Stop merging at the end of the replay + if let Some(edit) = self.editor.undo_stack.last_mut() { + edit.stop_merge(); + } + + let old_mode_clone = match old_mode { + ModeReport::Normal => Box::new(ViNormal::new()) as Box, + ModeReport::Insert => Box::new(ViInsert::new()) as Box, + ModeReport::Visual => Box::new(ViVisual::new()) as Box, + ModeReport::Ex => Box::new(ViEx::new(self.ex_history.clone())) as Box, + ModeReport::Replace => Box::new(ViReplace::new()) as Box, + ModeReport::Verbatim => Box::new(ViVerbatim::new()) as Box, + ModeReport::Unknown => unreachable!(), + }; + self.mode = old_mode_clone; + } + } + CmdReplay::Single(mut cmd) => { + if count > 1 { + // Override the counts with the one passed to the '.' command + if cmd.verb.is_some() { + if let Some(v_mut) = cmd.verb.as_mut() { + v_mut.0 = count + } + if let Some(m_mut) = cmd.motion.as_mut() { + m_mut.0 = 1 + } + } else { + return Ok(()); // it has to have a verb to be repeatable, + // something weird happened + } + } + self.editor.exec_cmd(cmd)?; + } + _ => unreachable!("motions should be handled in the other branch"), + } + Ok(()) + } + + pub fn handle_motion_repeat(&mut self, cmd: ViCmd) -> ShResult<()> { + match cmd.motion.as_ref().unwrap() { + MotionCmd(count, Motion::RepeatMotion) => { + let Some(motion) = self.repeat_motion.clone() else { + return Ok(()); + }; + let repeat_cmd = ViCmd { + register: RegisterName::default(), + verb: cmd.verb, + motion: Some(motion), + raw_seq: format!("{count};"), + flags: CmdFlags::empty(), + }; + self.editor.exec_cmd(repeat_cmd) + } + MotionCmd(count, Motion::RepeatMotionRev) => { + let Some(motion) = self.repeat_motion.clone() else { + return Ok(()); + }; + let mut new_motion = motion.invert_char_motion(); + new_motion.0 = *count; + let repeat_cmd = ViCmd { + register: RegisterName::default(), + verb: cmd.verb, + motion: Some(new_motion), + raw_seq: format!("{count},"), + flags: CmdFlags::empty(), + }; + self.editor.exec_cmd(repeat_cmd) + } + _ => unreachable!(), + } + } pub fn exec_cmd(&mut self, mut cmd: ViCmd, from_replay: bool) -> ShResult<()> { if cmd.verb().is_some() && let Some(range) = self.editor.select_range() { cmd.motion = Some(MotionCmd(1, range)) }; if cmd.is_mode_transition() { - return self.exec_mode_transition(cmd, from_replay); + self.exec_mode_transition(cmd, from_replay) } else if cmd.is_cmd_repeat() { - let Some(replay) = self.repeat_action.clone() else { - return Ok(()); - }; - let ViCmd { verb, .. } = cmd; - let VerbCmd(count, _) = verb.unwrap(); - match replay { - CmdReplay::ModeReplay { cmds, mut repeat } => { - if count > 1 { - repeat = count as u16; - } - - let old_mode = self.mode.report_mode(); - - for _ in 0..repeat { - let cmds = cmds.clone(); - for (i, cmd) in cmds.iter().enumerate() { - self.exec_cmd(cmd.clone(), true)?; - // After the first command, start merging so all subsequent - // edits fold into one undo entry (e.g. cw + inserted chars) - if i == 0 - && let Some(edit) = self.editor.undo_stack.last_mut() - { - edit.start_merge(); - } - } - // Stop merging at the end of the replay - if let Some(edit) = self.editor.undo_stack.last_mut() { - edit.stop_merge(); - } - - let old_mode_clone = match old_mode { - ModeReport::Normal => Box::new(ViNormal::new()) as Box, - ModeReport::Insert => Box::new(ViInsert::new()) as Box, - ModeReport::Visual => Box::new(ViVisual::new()) as Box, - ModeReport::Ex => Box::new(ViEx::new(self.ex_history.clone())) as Box, - ModeReport::Replace => Box::new(ViReplace::new()) as Box, - ModeReport::Verbatim => Box::new(ViVerbatim::new()) as Box, - ModeReport::Unknown => unreachable!(), - }; - self.mode = old_mode_clone; - } - } - CmdReplay::Single(mut cmd) => { - if count > 1 { - // Override the counts with the one passed to the '.' command - if cmd.verb.is_some() { - if let Some(v_mut) = cmd.verb.as_mut() { - v_mut.0 = count - } - if let Some(m_mut) = cmd.motion.as_mut() { - m_mut.0 = 1 - } - } else { - return Ok(()); // it has to have a verb to be repeatable, - // something weird happened - } - } - self.editor.exec_cmd(cmd)?; - } - _ => unreachable!("motions should be handled in the other branch"), - } - return Ok(()); + self.handle_cmd_repeat(cmd) } else if cmd.is_motion_repeat() { - match cmd.motion.as_ref().unwrap() { - MotionCmd(count, Motion::RepeatMotion) => { - let Some(motion) = self.repeat_motion.clone() else { - return Ok(()); - }; - let repeat_cmd = ViCmd { - register: RegisterName::default(), - verb: cmd.verb, - motion: Some(motion), - raw_seq: format!("{count};"), - flags: CmdFlags::empty(), - }; - return self.editor.exec_cmd(repeat_cmd); - } - MotionCmd(count, Motion::RepeatMotionRev) => { - let Some(motion) = self.repeat_motion.clone() else { - return Ok(()); - }; - let mut new_motion = motion.invert_char_motion(); - new_motion.0 = *count; - let repeat_cmd = ViCmd { - register: RegisterName::default(), - verb: cmd.verb, - motion: Some(new_motion), - raw_seq: format!("{count},"), - flags: CmdFlags::empty(), - }; - return self.editor.exec_cmd(repeat_cmd); - } - _ => unreachable!(), - } - } + self.handle_motion_repeat(cmd) + } else { + if self.mode.report_mode() == ModeReport::Visual && self.editor.select_range().is_none() { + self.editor.stop_selecting(); + let mut mode: Box = Box::new(ViNormal::new()); + self.swap_mode(&mut mode); + } - if self.mode.report_mode() == ModeReport::Visual && self.editor.select_range().is_none() { - self.editor.stop_selecting(); - let mut mode: Box = Box::new(ViNormal::new()); - self.swap_mode(&mut mode); - } + if cmd.is_repeatable() && !from_replay { + if self.mode.report_mode() == ModeReport::Visual { + // The motion is assigned in the line buffer execution, so we also have to + // assign it here in order to be able to repeat it + if let Some(range) = self.editor.select_range() { + cmd.motion = Some(MotionCmd(1, range)) + } else { + log::warn!("You're in visual mode with no select range??"); + }; + } + self.repeat_action = Some(CmdReplay::Single(cmd.clone())); + } - if cmd.is_repeatable() && !from_replay { - if self.mode.report_mode() == ModeReport::Visual { - // The motion is assigned in the line buffer execution, so we also have to - // assign it here in order to be able to repeat it - if let Some(range) = self.editor.select_range() { - cmd.motion = Some(MotionCmd(1, range)) - } else { - log::warn!("You're in visual mode with no select range??"); - }; - } - self.repeat_action = Some(CmdReplay::Single(cmd.clone())); - } + if cmd.is_char_search() { + self.repeat_motion = cmd.motion.clone() + } - if cmd.is_char_search() { - self.repeat_motion = cmd.motion.clone() - } + self.editor.exec_cmd(cmd.clone())?; - self.editor.exec_cmd(cmd.clone())?; + if self.mode.report_mode() == ModeReport::Visual + && cmd.verb().is_some_and(|v| v.1.is_edit() || v.1 == Verb::Yank) { + self.editor.stop_selecting(); + let mut mode: Box = Box::new(ViNormal::new()); + self.swap_mode(&mut mode); + } - if self.mode.report_mode() == ModeReport::Visual - && cmd - .verb() - .is_some_and(|v| v.1.is_edit() || v.1 == Verb::Yank) - { - self.editor.stop_selecting(); - let mut mode: Box = Box::new(ViNormal::new()); - self.swap_mode(&mut mode); - } + if self.mode.report_mode() != ModeReport::Visual && self.editor.select_range().is_some() { + self.editor.stop_selecting(); + } - if self.mode.report_mode() != ModeReport::Visual && self.editor.select_range().is_some() { - self.editor.stop_selecting(); - } + if cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) { + let mut mode: Box = if matches!( + self.mode.report_mode(), + ModeReport::Ex | ModeReport::Verbatim + ) { + if let Some(saved) = self.saved_mode.take() { + saved + } else { + Box::new(ViNormal::new()) + } + } else { + Box::new(ViNormal::new()) + }; + self.swap_mode(&mut mode); + } - if cmd.flags.contains(CmdFlags::EXIT_CUR_MODE) { - let mut mode: Box = if matches!( - self.mode.report_mode(), - ModeReport::Ex | ModeReport::Verbatim - ) { - if let Some(saved) = self.saved_mode.take() { - saved - } else { - Box::new(ViNormal::new()) - } - } else { - Box::new(ViNormal::new()) - }; - self.swap_mode(&mut mode); - } - - Ok(()) + Ok(()) + } } } diff --git a/src/readline/term.rs b/src/readline/term.rs index 25232b6..6a9cfbd 100644 --- a/src/readline/term.rs +++ b/src/readline/term.rs @@ -69,7 +69,7 @@ pub fn get_win_size(fd: RawFd) -> (Col, Row) { } } -fn enumerate_lines(s: &str, left_pad: usize, show_numbers: bool, offset: usize, total_buf_lines: usize) -> String { +fn enumerate_lines(s: &str, left_pad: usize, show_numbers: bool, offset: usize, _total_buf_lines: usize) -> String { let lines: Vec<&str> = s.split('\n').collect(); let visible_count = lines.len(); let max_num_len = (offset + visible_count).to_string().len(); diff --git a/src/readline/vimode/ex.rs b/src/readline/vimode/ex.rs index e2dc258..8171bdb 100644 --- a/src/readline/vimode/ex.rs +++ b/src/readline/vimode/ex.rs @@ -5,10 +5,9 @@ use std::str::Chars; use itertools::Itertools; use crate::bitflags; -use crate::expand::{Expander, expand_raw}; +use crate::expand::Expander; use crate::libsh::error::{ShErr, ShErrKind, ShResult}; use crate::parse::lex::TkFlags; -use crate::readline::complete::SimpleCompleter; use crate::readline::history::History; use crate::readline::keys::KeyEvent; use crate::readline::linebuf::LineBuf; @@ -17,7 +16,7 @@ use crate::readline::vicmd::{ WriteDest, }; use crate::readline::vimode::{ModeReport, ViInsert, ViMode}; -use crate::state::{get_home, write_meta}; +use crate::state::write_meta; bitflags! { #[derive(Debug,Clone,Copy,PartialEq,Eq)] @@ -83,7 +82,7 @@ impl ExEditor { } } pub fn handle_key(&mut self, key: KeyEvent) -> ShResult<()> { - let Some(mut cmd) = self.mode.handle_key(key) else { + let Some(cmd) = self.mode.handle_key(key) else { return Ok(()); }; log::debug!("ExEditor got cmd: {:?}", cmd); @@ -268,6 +267,7 @@ fn parse_ex_command(chars: &mut Peekable>) -> Result, Opt } _ if "help".starts_with(&cmd_name) => { let cmd = "help ".to_string() + chars.collect::().trim(); + log::debug!("Parsed help command: {}", cmd); Ok(Some(Verb::ShellCmd(cmd))) } "normal!" => parse_normal(chars),