Various line editor fixes and optimizations
This commit is contained in:
@@ -7,7 +7,7 @@ use std::{
|
||||
use crate::{
|
||||
libsh::term::{Style, StyleSet, Styled},
|
||||
prompt::readline::{annotate_input, markers::{self, is_marker}},
|
||||
state::{read_logic, read_shopts},
|
||||
state::{read_logic, read_meta, read_shopts},
|
||||
};
|
||||
|
||||
/// Syntax highlighter for shell input using Unicode marker-based annotation
|
||||
@@ -173,7 +173,6 @@ impl Highlighter {
|
||||
}
|
||||
cmd_name.push(ch);
|
||||
}
|
||||
log::debug!("Command name: '{}'", Self::strip_markers(&cmd_name));
|
||||
let style = if matches!(Self::strip_markers(&cmd_name).as_str(), "break" | "continue" | "return") {
|
||||
Style::Magenta.into()
|
||||
} else if Self::is_valid(&Self::strip_markers(&cmd_name)) {
|
||||
@@ -291,54 +290,35 @@ impl Highlighter {
|
||||
/// 2. All directories in PATH environment variable
|
||||
/// 3. Shell functions and aliases in the current shell state
|
||||
fn is_valid(command: &str) -> bool {
|
||||
let path = env::var("PATH").unwrap_or_default();
|
||||
let paths = path.split(':');
|
||||
let cmd_path = PathBuf::from(&command);
|
||||
let cmd_path = Path::new(&command);
|
||||
|
||||
if cmd_path.exists() {
|
||||
if cmd_path.is_absolute() {
|
||||
// the user has given us an absolute path
|
||||
if cmd_path.is_dir() && read_shopts(|o| o.core.autocd) {
|
||||
// this is a directory and autocd is enabled
|
||||
return true;
|
||||
true
|
||||
} else {
|
||||
let Ok(meta) = cmd_path.metadata() else {
|
||||
return false;
|
||||
};
|
||||
// this is a file that is executable by someone
|
||||
return meta.permissions().mode() & 0o111 == 0;
|
||||
meta.permissions().mode() & 0o111 != 0
|
||||
}
|
||||
} else {
|
||||
// they gave us a command name
|
||||
// now we must traverse the PATH env var
|
||||
// and see if we find any matches
|
||||
for path in paths {
|
||||
let path = PathBuf::from(path).join(command);
|
||||
if path.exists() {
|
||||
let Ok(meta) = path.metadata() else { continue };
|
||||
return meta.permissions().mode() & 0o111 != 0;
|
||||
}
|
||||
}
|
||||
|
||||
// also check shell functions and aliases for any matches
|
||||
let found = read_logic(|l| l.get_func(command).is_some() || l.get_alias(command).is_some());
|
||||
if found {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
read_meta(|m| m.cached_cmds().get(command).is_some())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_filename(arg: &str) -> bool {
|
||||
let path = PathBuf::from(arg);
|
||||
let path = Path::new(arg);
|
||||
|
||||
if path.exists() {
|
||||
if path.is_absolute() && path.exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(parent_dir) = path.parent()
|
||||
&& let Ok(entries) = parent_dir.read_dir()
|
||||
{
|
||||
if path.is_absolute()
|
||||
&& let Some(parent_dir) = path.parent()
|
||||
&& let Ok(entries) = parent_dir.read_dir() {
|
||||
let files = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.file_name().to_string_lossy().to_string())
|
||||
@@ -354,22 +334,17 @@ impl Highlighter {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Ok(this_dir) = env::current_dir()
|
||||
&& let Ok(entries) = this_dir.read_dir()
|
||||
{
|
||||
let this_dir_files = entries
|
||||
.filter_map(|e| e.ok())
|
||||
.map(|e| e.file_name().to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
for file in this_dir_files {
|
||||
if file.starts_with(arg) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
false
|
||||
read_meta(|m| {
|
||||
let files = m.cwd_cache();
|
||||
for file in files {
|
||||
if file.starts_with(arg) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
/// Emits a reset ANSI code to the output, with deduplication
|
||||
|
||||
@@ -496,6 +496,12 @@ impl LineBuf {
|
||||
pub fn grapheme_at_cursor(&mut self) -> Option<&str> {
|
||||
self.grapheme_at(self.cursor.get())
|
||||
}
|
||||
pub fn grapheme_before_cursor(&mut self) -> Option<&str> {
|
||||
if self.cursor.get() == 0 {
|
||||
return None;
|
||||
}
|
||||
self.grapheme_at(self.cursor.ret_sub(1))
|
||||
}
|
||||
pub fn mark_insert_mode_start_pos(&mut self) {
|
||||
self.insert_mode_start_pos = Some(self.cursor.get())
|
||||
}
|
||||
@@ -1884,7 +1890,11 @@ impl LineBuf {
|
||||
self.buffer.replace_range(start..end, new);
|
||||
}
|
||||
pub fn calc_indent_level(&mut self) {
|
||||
let input = Arc::new(self.buffer.clone());
|
||||
let to_cursor = self
|
||||
.slice_to_cursor()
|
||||
.map(|s| s.to_string())
|
||||
.unwrap_or(self.buffer.clone());
|
||||
let input = Arc::new(to_cursor);
|
||||
let Ok(tokens) = LexStream::new(input, LexFlags::LEX_UNFINISHED).collect::<ShResult<Vec<Tk>>>() else {
|
||||
log::error!("Failed to lex buffer for indent calculation");
|
||||
return;
|
||||
@@ -1914,8 +1924,10 @@ impl LineBuf {
|
||||
}
|
||||
|
||||
let eval = match motion {
|
||||
MotionCmd(count, Motion::WholeLine) => {
|
||||
let Some((start, end)) = (if count == 1 {
|
||||
MotionCmd(count, motion @ (Motion::WholeLineInclusive | Motion::WholeLineExclusive)) => {
|
||||
let exclusive = matches!(motion, Motion::WholeLineExclusive);
|
||||
|
||||
let Some((start, mut end)) = (if count == 1 {
|
||||
Some(self.this_line())
|
||||
} else {
|
||||
self.select_lines_down(count)
|
||||
@@ -1923,6 +1935,10 @@ impl LineBuf {
|
||||
return MotionKind::Null;
|
||||
};
|
||||
|
||||
if exclusive && self.grapheme_before(end).is_some_and(|gr| gr == "\n") {
|
||||
end = end.saturating_sub(1);
|
||||
}
|
||||
|
||||
let target_col = if let Some(col) = self.saved_col {
|
||||
col
|
||||
} else {
|
||||
@@ -1938,6 +1954,7 @@ impl LineBuf {
|
||||
if self.cursor.exclusive
|
||||
&& line.ends_with("\n")
|
||||
&& self.grapheme_at(target_pos) == Some("\n")
|
||||
&& line != "\n" // Allow landing on newline for empty lines
|
||||
{
|
||||
target_pos = target_pos.saturating_sub(1); // Don't land on the
|
||||
// newline
|
||||
@@ -2098,7 +2115,7 @@ impl LineBuf {
|
||||
MotionCmd(_, Motion::BeginningOfLine) => MotionKind::On(self.start_of_line()),
|
||||
MotionCmd(count, Motion::EndOfLine) => {
|
||||
let pos = if count == 1 {
|
||||
self.end_of_line()
|
||||
self.end_of_line()
|
||||
} else if let Some((_, end)) = self.select_lines_down(count) {
|
||||
end
|
||||
} else {
|
||||
@@ -2171,12 +2188,14 @@ impl LineBuf {
|
||||
};
|
||||
|
||||
let Some(line) = self.slice(start..end).map(|s| s.to_string()) else {
|
||||
log::warn!("Failed to get line slice for motion, start: {start}, end: {end}");
|
||||
return MotionKind::Null;
|
||||
};
|
||||
let mut target_pos = self.grapheme_index_for_display_col(&line, target_col);
|
||||
if self.cursor.exclusive
|
||||
&& line.ends_with("\n")
|
||||
&& self.grapheme_at(target_pos) == Some("\n")
|
||||
&& line != "\n" // Allow landing on newline for empty lines
|
||||
{
|
||||
target_pos = target_pos.saturating_sub(1); // Don't land on the
|
||||
// newline
|
||||
@@ -2188,6 +2207,7 @@ impl LineBuf {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
|
||||
MotionKind::InclusiveWithTargetCol((start, end), target_pos)
|
||||
}
|
||||
MotionCmd(count, Motion::LineDownCharwise) | MotionCmd(count, Motion::LineUpCharwise) => {
|
||||
@@ -2412,9 +2432,15 @@ impl LineBuf {
|
||||
) -> ShResult<()> {
|
||||
match verb {
|
||||
Verb::Delete | Verb::Yank | Verb::Change => {
|
||||
let Some((start, end)) = self.range_from_motion(&motion) else {
|
||||
let Some((mut start, mut end)) = self.range_from_motion(&motion) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let mut do_indent = false;
|
||||
if verb == Verb::Change && (start,end) == self.this_line() {
|
||||
do_indent = read_shopts(|o| o.prompt.auto_indent);
|
||||
}
|
||||
|
||||
let register_text = if verb == Verb::Yank {
|
||||
self
|
||||
.slice(start..end)
|
||||
@@ -2426,15 +2452,18 @@ impl LineBuf {
|
||||
drained
|
||||
};
|
||||
register.write_to_register(register_text);
|
||||
match motion {
|
||||
MotionKind::ExclusiveWithTargetCol((_, _), pos)
|
||||
| MotionKind::InclusiveWithTargetCol((_, _), pos) => {
|
||||
let (start, end) = self.this_line();
|
||||
self.cursor.set(start);
|
||||
self.cursor.add(end.min(pos));
|
||||
}
|
||||
_ => self.cursor.set(start),
|
||||
}
|
||||
self.cursor.set(start);
|
||||
if do_indent {
|
||||
self.calc_indent_level();
|
||||
let tabs = (0..self.auto_indent_level).map(|_| '\t');
|
||||
for tab in tabs {
|
||||
self.insert_at_cursor(tab);
|
||||
self.cursor.add(1);
|
||||
}
|
||||
} else if verb != Verb::Change
|
||||
&& let MotionKind::InclusiveWithTargetCol((_,_), col) = motion {
|
||||
self.cursor.add(col);
|
||||
}
|
||||
}
|
||||
Verb::Rot13 => {
|
||||
let Some((start, end)) = self.range_from_motion(&motion) else {
|
||||
@@ -2667,7 +2696,11 @@ impl LineBuf {
|
||||
let Some((start, end)) = self.range_from_motion(&motion) else {
|
||||
return Ok(());
|
||||
};
|
||||
let move_cursor = self.cursor.get() == start;
|
||||
self.insert_at(start, '\t');
|
||||
if move_cursor {
|
||||
self.cursor.add(1);
|
||||
}
|
||||
let mut range_indices = self.grapheme_indices()[start..end].to_vec().into_iter();
|
||||
while let Some(idx) = range_indices.next() {
|
||||
let gr = self.grapheme_at(idx).unwrap();
|
||||
@@ -2733,12 +2766,9 @@ impl LineBuf {
|
||||
Anchor::After => {
|
||||
self.push('\n');
|
||||
if auto_indent {
|
||||
log::debug!("Calculating indent level for new line");
|
||||
self.calc_indent_level();
|
||||
log::debug!("Auto-indent level: {}", self.auto_indent_level);
|
||||
let tabs = (0..self.auto_indent_level).map(|_| '\t');
|
||||
for tab in tabs {
|
||||
log::debug!("Pushing tab for auto-indent");
|
||||
self.push(tab);
|
||||
}
|
||||
}
|
||||
@@ -2793,8 +2823,24 @@ impl LineBuf {
|
||||
Verb::AcceptLineOrNewline => {
|
||||
// If this verb has reached this function, it means we have incomplete input
|
||||
// and therefore must insert a newline instead of accepting the input
|
||||
self.push('\n');
|
||||
if self.cursor.exclusive {
|
||||
// in this case we are in normal/visual mode, so we don't insert anything
|
||||
// and just move down a line
|
||||
let motion = self.eval_motion(None, MotionCmd(1, Motion::LineDownCharwise));
|
||||
self.apply_motion(motion);
|
||||
return Ok(());
|
||||
}
|
||||
let auto_indent = read_shopts(|o| o.prompt.auto_indent);
|
||||
self.insert_at_cursor('\n');
|
||||
self.cursor.add(1);
|
||||
if auto_indent {
|
||||
self.calc_indent_level();
|
||||
let tabs = (0..self.auto_indent_level).map(|_| '\t');
|
||||
for tab in tabs {
|
||||
self.insert_at_cursor(tab);
|
||||
self.cursor.add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Verb::Complete
|
||||
@@ -2813,7 +2859,7 @@ impl LineBuf {
|
||||
pub fn exec_cmd(&mut self, cmd: ViCmd) -> ShResult<()> {
|
||||
let clear_redos = !cmd.is_undo_op() || cmd.verb.as_ref().is_some_and(|v| v.1.is_edit());
|
||||
let is_char_insert = cmd.verb.as_ref().is_some_and(|v| v.1.is_char_insert());
|
||||
let is_line_motion = cmd.is_line_motion();
|
||||
let is_line_motion = cmd.is_line_motion() || cmd.verb.as_ref().is_some_and(|v| v.1 == Verb::AcceptLineOrNewline);
|
||||
let is_undo_op = cmd.is_undo_op();
|
||||
let edit_is_merging = self.undo_stack.last().is_some_and(|edit| edit.merging);
|
||||
|
||||
@@ -2886,6 +2932,14 @@ impl LineBuf {
|
||||
self.apply_motion(motion_eval);
|
||||
}
|
||||
|
||||
if self.cursor.exclusive
|
||||
&& self.grapheme_at_cursor().is_some_and(|gr| gr == "\n")
|
||||
&& self.grapheme_before_cursor().is_some_and(|gr| gr != "\n") {
|
||||
// we landed on a newline, and we aren't inbetween two newlines.
|
||||
self.cursor.sub(1);
|
||||
self.update_select_range();
|
||||
}
|
||||
|
||||
/* Done executing, do some cleanup */
|
||||
|
||||
let after = self.buffer.clone();
|
||||
|
||||
@@ -129,11 +129,78 @@ pub enum ReadlineEvent {
|
||||
Pending,
|
||||
}
|
||||
|
||||
pub struct Prompt {
|
||||
ps1_expanded: String,
|
||||
ps1_raw: String,
|
||||
psr_expanded: Option<String>,
|
||||
psr_raw: Option<String>,
|
||||
}
|
||||
|
||||
impl Prompt {
|
||||
const DEFAULT_PS1: &str = "\\e[0m\\n\\e[1;0m\\u\\e[1;36m@\\e[1;31m\\h\\n\\e[1;36m\\W\\e[1;32m/\\n\\e[1;32m\\$\\e[0m ";
|
||||
pub fn new() -> Self {
|
||||
let Ok(ps1_raw) = env::var("PS1") else {
|
||||
return Self::default();
|
||||
};
|
||||
let Ok(ps1_expanded) = expand_prompt(&ps1_raw) else {
|
||||
return Self::default();
|
||||
};
|
||||
Self {
|
||||
ps1_expanded,
|
||||
ps1_raw,
|
||||
psr_expanded: None,
|
||||
psr_raw: None,
|
||||
}
|
||||
}
|
||||
pub fn with_psr(mut self, psr_raw: String) -> ShResult<Self> {
|
||||
let psr_expanded = expand_prompt(&psr_raw)?;
|
||||
self.psr_expanded = Some(psr_expanded);
|
||||
self.psr_raw = Some(psr_raw);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub fn get_ps1(&self) -> &str {
|
||||
&self.ps1_expanded
|
||||
}
|
||||
pub fn set_ps1(&mut self, ps1_raw: String) -> ShResult<()> {
|
||||
self.ps1_expanded = expand_prompt(&ps1_raw)?;
|
||||
self.ps1_raw = ps1_raw;
|
||||
Ok(())
|
||||
}
|
||||
pub fn set_psr(&mut self, psr_raw: String) -> ShResult<()> {
|
||||
self.psr_expanded = Some(expand_prompt(&psr_raw)?);
|
||||
self.psr_raw = Some(psr_raw);
|
||||
Ok(())
|
||||
}
|
||||
pub fn get_psr(&self) -> Option<&str> {
|
||||
self.psr_expanded.as_deref()
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self) -> ShResult<()> {
|
||||
self.ps1_expanded = expand_prompt(&self.ps1_raw)?;
|
||||
if let Some(psr_raw) = &self.psr_raw {
|
||||
self.psr_expanded = Some(expand_prompt(psr_raw)?);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Prompt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ps1_expanded: expand_prompt(Self::DEFAULT_PS1).unwrap_or_else(|_| Self::DEFAULT_PS1.to_string()),
|
||||
ps1_raw: Self::DEFAULT_PS1.to_string(),
|
||||
psr_expanded: None,
|
||||
psr_raw: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShedVi {
|
||||
pub reader: PollReader,
|
||||
pub writer: TermWriter,
|
||||
|
||||
pub prompt: String,
|
||||
pub prompt: Prompt,
|
||||
pub highlighter: Highlighter,
|
||||
pub completer: Completer,
|
||||
|
||||
@@ -149,11 +216,11 @@ pub struct ShedVi {
|
||||
}
|
||||
|
||||
impl ShedVi {
|
||||
pub fn new(prompt: Option<String>, tty: RawFd) -> ShResult<Self> {
|
||||
pub fn new(prompt: Prompt, tty: RawFd) -> ShResult<Self> {
|
||||
let mut new = Self {
|
||||
reader: PollReader::new(),
|
||||
writer: TermWriter::new(tty),
|
||||
prompt: prompt.unwrap_or("$ ".styled(Style::Green)),
|
||||
prompt,
|
||||
completer: Completer::new(),
|
||||
highlighter: Highlighter::new(),
|
||||
mode: Box::new(ViInsert::new()),
|
||||
@@ -187,11 +254,10 @@ impl ShedVi {
|
||||
self.needs_redraw = true;
|
||||
}
|
||||
|
||||
|
||||
/// Reset readline state for a new prompt
|
||||
pub fn reset(&mut self, prompt: Option<String>) {
|
||||
if let Some(p) = prompt {
|
||||
self.prompt = p;
|
||||
}
|
||||
pub fn reset(&mut self, prompt: Prompt) {
|
||||
self.prompt = prompt;
|
||||
self.editor = Default::default();
|
||||
self.mode = Box::new(ViInsert::new());
|
||||
self.old_layout = None;
|
||||
@@ -200,9 +266,12 @@ impl ShedVi {
|
||||
self.history.reset();
|
||||
}
|
||||
|
||||
pub fn update_prompt(&mut self, prompt: String) {
|
||||
self.prompt = prompt;
|
||||
self.needs_redraw = true;
|
||||
pub fn prompt(&self) -> &Prompt {
|
||||
&self.prompt
|
||||
}
|
||||
|
||||
pub fn prompt_mut(&mut self) -> &mut Prompt {
|
||||
&mut self.prompt
|
||||
}
|
||||
|
||||
fn should_submit(&mut self) -> ShResult<bool> {
|
||||
@@ -366,7 +435,7 @@ impl ShedVi {
|
||||
pub fn get_layout(&mut self, line: &str) -> Layout {
|
||||
let to_cursor = self.editor.slice_to_cursor().unwrap_or_default();
|
||||
let (cols, _) = get_win_size(*TTY_FILENO);
|
||||
Layout::from_parts(cols, &self.prompt, to_cursor, line)
|
||||
Layout::from_parts(cols, self.prompt.get_ps1(), to_cursor, line)
|
||||
}
|
||||
pub fn scroll_history(&mut self, cmd: ViCmd) {
|
||||
/*
|
||||
@@ -457,6 +526,7 @@ impl ShedVi {
|
||||
}
|
||||
|
||||
let row0_used = self.prompt
|
||||
.get_ps1()
|
||||
.lines()
|
||||
.next()
|
||||
.map(|l| Layout::calc_pos(self.writer.t_cols, l, Pos { col: 0, row: 0 }))
|
||||
@@ -469,7 +539,7 @@ impl ShedVi {
|
||||
self.writer.clear_rows(layout)?;
|
||||
}
|
||||
|
||||
self.writer.redraw(&self.prompt, &line, &new_layout)?;
|
||||
self.writer.redraw(self.prompt.get_ps1(), &line, &new_layout)?;
|
||||
|
||||
let seq_fits = pending_seq.as_ref().is_some_and(|seq| row0_used + 1 < self.writer.t_cols as usize - seq.width());
|
||||
let psr_fits = prompt_string_right.as_ref().is_some_and(|psr| new_layout.end.col as usize + 1 < self.writer.t_cols as usize - psr.width());
|
||||
|
||||
@@ -299,7 +299,8 @@ impl Verb {
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum Motion {
|
||||
WholeLine,
|
||||
WholeLineInclusive, // whole line including the linebreak
|
||||
WholeLineExclusive, // whole line excluding the linebreak
|
||||
TextObj(TextObj),
|
||||
EndOfLastWord,
|
||||
BeginningOfFirstWord,
|
||||
@@ -381,7 +382,7 @@ impl Motion {
|
||||
pub fn is_linewise(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::WholeLine | Self::LineUp | Self::LineDown | Self::ScreenLineDown | Self::ScreenLineUp
|
||||
Self::WholeLineInclusive | Self::WholeLineExclusive | Self::LineUp | Self::LineDown | Self::ScreenLineDown | Self::ScreenLineUp
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,7 +451,7 @@ impl ViNormal {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::ForwardChar)),
|
||||
motion: Some(MotionCmd(1, Motion::ForwardCharForced)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags(),
|
||||
});
|
||||
@@ -478,7 +478,7 @@ impl ViNormal {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(count, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineExclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: self.flags(),
|
||||
});
|
||||
@@ -684,7 +684,7 @@ impl ViNormal {
|
||||
| ('~', Some(VerbCmd(_, Verb::ToggleCaseRange)))
|
||||
| ('>', Some(VerbCmd(_, Verb::Indent)))
|
||||
| ('<', Some(VerbCmd(_, Verb::Dedent))) => {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLine));
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineInclusive));
|
||||
}
|
||||
('W', Some(VerbCmd(_, Verb::Change))) => {
|
||||
// Same with 'W'
|
||||
@@ -1218,7 +1218,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -1227,7 +1227,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Yank)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -1236,7 +1236,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Delete)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -1245,7 +1245,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Change)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineExclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -1254,7 +1254,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Indent)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -1263,7 +1263,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Dedent)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -1272,7 +1272,7 @@ impl ViVisual {
|
||||
return Some(ViCmd {
|
||||
register,
|
||||
verb: Some(VerbCmd(1, Verb::Equalize)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLine)),
|
||||
motion: Some(MotionCmd(1, Motion::WholeLineInclusive)),
|
||||
raw_seq: self.take_cmd(),
|
||||
flags: CmdFlags::empty(),
|
||||
});
|
||||
@@ -1389,13 +1389,15 @@ impl ViVisual {
|
||||
};
|
||||
match (ch, &verb) {
|
||||
('d', Some(VerbCmd(_, Verb::Delete)))
|
||||
| ('c', Some(VerbCmd(_, Verb::Change)))
|
||||
| ('y', Some(VerbCmd(_, Verb::Yank)))
|
||||
| ('=', Some(VerbCmd(_, Verb::Equalize)))
|
||||
| ('>', Some(VerbCmd(_, Verb::Indent)))
|
||||
| ('<', Some(VerbCmd(_, Verb::Dedent))) => {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLine));
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineInclusive));
|
||||
}
|
||||
('c', Some(VerbCmd(_, Verb::Change))) => {
|
||||
break 'motion_parse Some(MotionCmd(count, Motion::WholeLineExclusive));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match ch {
|
||||
|
||||
Reference in New Issue
Block a user