chore: extracted some logic in ShedVi into helpers, cleaned up compiler warnings

This commit is contained in:
2026-03-21 01:19:55 -04:00
parent 4b07990fc5
commit 1c42a36810
6 changed files with 389 additions and 393 deletions

View File

@@ -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 {
}
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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<Self> {
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> {
Self::new_private(prompt, tty, false)
}
fn new_private(prompt: Prompt, tty: RawFd, with_hist: bool) -> ShResult<Self> {
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<Option<ReadlineEvent>> {
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<Option<ReadlineEvent>> {
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::<Var>::into(num_candidates)),
("_MATCHES".into(), Into::<Var>::into(candidates)),
(
"_SEARCH_STR".into(),
Into::<Var>::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::<Vec<_>>();
let num_entries = entries.len();
let num_matches = matches.len();
with_vars(
[
("_ENTRIES".into(), Into::<Var>::into(entries)),
("_NUM_ENTRIES".into(), Into::<Var>::into(num_entries)),
("_MATCHES".into(), Into::<Var>::into(matches)),
("_NUM_MATCHES".into(), Into::<Var>::into(num_matches)),
("_SEARCH_STR".into(), Into::<Var>::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<Option<ReadlineEvent>> {
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<Option<ReadlineEvent>> {
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::<Var>::into(num_candidates)),
("_MATCHES".into(), Into::<Var>::into(candidates)),
(
"_SEARCH_STR".into(),
Into::<Var>::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::<Vec<_>>();
let num_entries = entries.len();
let num_matches = matches.len();
with_vars(
[
("_ENTRIES".into(), Into::<Var>::into(entries)),
("_NUM_ENTRIES".into(), Into::<Var>::into(num_entries)),
("_MATCHES".into(), Into::<Var>::into(matches)),
("_NUM_MATCHES".into(), Into::<Var>::into(num_matches)),
("_SEARCH_STR".into(), Into::<Var>::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<dyn ViMode>,
ModeReport::Insert => Box::new(ViInsert::new()) as Box<dyn ViMode>,
ModeReport::Visual => Box::new(ViVisual::new()) as Box<dyn ViMode>,
ModeReport::Ex => Box::new(ViEx::new(self.ex_history.clone())) as Box<dyn ViMode>,
ModeReport::Replace => Box::new(ViReplace::new()) as Box<dyn ViMode>,
ModeReport::Verbatim => Box::new(ViVerbatim::new()) as Box<dyn ViMode>,
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<dyn ViMode>,
ModeReport::Insert => Box::new(ViInsert::new()) as Box<dyn ViMode>,
ModeReport::Visual => Box::new(ViVisual::new()) as Box<dyn ViMode>,
ModeReport::Ex => Box::new(ViEx::new(self.ex_history.clone())) as Box<dyn ViMode>,
ModeReport::Replace => Box::new(ViReplace::new()) as Box<dyn ViMode>,
ModeReport::Verbatim => Box::new(ViVerbatim::new()) as Box<dyn ViMode>,
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<dyn ViMode> = 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<dyn ViMode> = 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<dyn ViMode> = 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<dyn ViMode> = 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<dyn ViMode> = 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<dyn ViMode> = 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(())
}
}
}

View File

@@ -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();

View File

@@ -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<Chars<'_>>) -> Result<Option<Verb>, Opt
}
_ if "help".starts_with(&cmd_name) => {
let cmd = "help ".to_string() + chars.collect::<String>().trim();
log::debug!("Parsed help command: {}", cmd);
Ok(Some(Verb::ShellCmd(cmd)))
}
"normal!" => parse_normal(chars),