Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions src/core_editor/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use super::{edit_stack::EditStack, Clipboard, ClipboardMode, LineBuffer};
use crate::core_editor::get_system_clipboard;
use crate::enums::{EditType, TextObject, TextObjectScope, TextObjectType, UndoBehavior};
use crate::{core_editor::get_local_clipboard, EditCommand};
use std::cmp::{max, min};
use std::ops::{DerefMut, Range};

/// Stateful editor executing changes to the underlying [`LineBuffer`]
Expand Down Expand Up @@ -50,6 +51,9 @@ impl Editor {
match command {
EditCommand::MoveToStart { select } => self.move_to_start(*select),
EditCommand::MoveToLineStart { select } => self.move_to_line_start(*select),
EditCommand::MoveToLineNonBlankStart { select } => {
self.move_to_line_non_blank_start(*select)
}
EditCommand::MoveToEnd { select } => self.move_to_end(*select),
EditCommand::MoveToLineEnd { select } => self.move_to_line_end(*select),
EditCommand::MoveToPosition { position, select } => {
Expand Down Expand Up @@ -82,6 +86,7 @@ impl Editor {
EditCommand::CutCurrentLine => self.cut_current_line(),
EditCommand::CutFromStart => self.cut_from_start(),
EditCommand::CutFromLineStart => self.cut_from_line_start(),
EditCommand::CutFromLineNonBlankStart => self.cut_from_line_non_blank_start(),
EditCommand::CutToEnd => self.cut_from_end(),
EditCommand::CutToLineEnd => self.cut_to_line_end(),
EditCommand::KillLine => self.kill_line(),
Expand Down Expand Up @@ -123,6 +128,7 @@ impl Editor {
EditCommand::Paste => self.paste_cut_buffer(),
EditCommand::CopyFromStart => self.copy_from_start(),
EditCommand::CopyFromLineStart => self.copy_from_line_start(),
EditCommand::CopyFromLineNonBlankStart => self.copy_from_line_non_blank_start(),
EditCommand::CopyToEnd => self.copy_from_end(),
EditCommand::CopyToLineEnd => self.copy_to_line_end(),
EditCommand::CopyWordLeft => self.copy_word_left(),
Expand Down Expand Up @@ -291,6 +297,11 @@ impl Editor {
self.line_buffer.move_to_line_start();
}

pub(crate) fn move_to_line_non_blank_start(&mut self, select: bool) {
self.update_selection_anchor(select);
self.line_buffer.move_to_line_non_blank_start();
}

pub(crate) fn move_to_line_end(&mut self, select: bool) {
self.update_selection_anchor(select);
self.line_buffer.move_to_line_end();
Expand Down Expand Up @@ -351,6 +362,14 @@ impl Editor {
}
}

fn cut_from_line_non_blank_start(&mut self) {
let offset_a = self.line_buffer.insertion_point();
self.line_buffer.move_to_line_non_blank_start();
let offset_b = self.line_buffer.insertion_point();
let deletion_range = min(offset_a, offset_b)..max(offset_a, offset_b);
self.cut_range(deletion_range);
}

fn cut_from_end(&mut self) {
let cut_slice = &self.line_buffer.get_buffer()[self.line_buffer.insertion_point()..];
if !cut_slice.is_empty() {
Expand Down Expand Up @@ -852,6 +871,15 @@ impl Editor {
self.copy_range(copy_range);
}

pub(crate) fn copy_from_line_non_blank_start(&mut self) {
let offset_a = self.line_buffer.insertion_point();
self.line_buffer.move_to_line_non_blank_start();
let offset_b = self.line_buffer.insertion_point();
self.line_buffer.set_insertion_point(offset_a);
let copy_range = min(offset_a, offset_b)..max(offset_a, offset_b);
self.copy_range(copy_range);
}

pub(crate) fn copy_from_end(&mut self) {
let copy_range = self.line_buffer.insertion_point()..self.line_buffer.len();
self.copy_range(copy_range);
Expand Down
13 changes: 13 additions & 0 deletions src/core_editor/line_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ impl LineBuffer {
// str is guaranteed to be utf8, thus \n is safe to assume 1 byte long
}

/// Move the cursor before the first non whitespace character of the line
pub fn move_to_line_non_blank_start(&mut self) {
let line_start = self.lines[..self.insertion_point]
.rfind('\n')
.map_or(0, |offset| offset + 1);
// str is guaranteed to be utf8, thus \n is safe to assume 1 byte long

self.insertion_point = self.lines[line_start..]
.find(|c: char| !c.is_whitespace() || c == '\n')
.map(|offset| line_start + offset)
.unwrap_or(self.lines.len());
}

/// Move cursor position to the end of the line
///
/// Insertion will append to the line.
Expand Down
40 changes: 40 additions & 0 deletions src/edit_mode/vi/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,25 @@ impl Command {
Some(vec![ReedlineOption::Edit(EditCommand::CutLeftBefore(*c))])
}
Motion::Start => Some(vec![ReedlineOption::Edit(EditCommand::CutFromLineStart)]),
Motion::NonBlankStart => Some(vec![ReedlineOption::Edit(
EditCommand::CutFromLineNonBlankStart,
)]),
Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::Backspace)]),
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
Motion::Up => None,
Motion::Down => None,
Motion::FirstLine => Some(vec![
ReedlineOption::Edit(EditCommand::MoveToLineEnd { select: false }),
// `MoveRight` to include the new line character
ReedlineOption::Edit(EditCommand::MoveRight { select: false }),
ReedlineOption::Edit(EditCommand::CutFromStart),
]),
Motion::LastLine => Some(vec![
ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }),
// `MoveLeft` to include the new line character
ReedlineOption::Edit(EditCommand::MoveLeft { select: false }),
ReedlineOption::Edit(EditCommand::CutToEnd),
]),
Motion::ReplayCharSearch => vi_state
.last_char_search
.as_ref()
Expand Down Expand Up @@ -399,10 +414,21 @@ impl Command {
Motion::Start => {
Some(vec![ReedlineOption::Edit(EditCommand::CutFromLineStart)])
}
Motion::NonBlankStart => Some(vec![ReedlineOption::Edit(
EditCommand::CutFromLineNonBlankStart,
)]),
Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::Backspace)]),
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::Delete)]),
Motion::Up => None,
Motion::Down => None,
Motion::FirstLine => Some(vec![
ReedlineOption::Edit(EditCommand::MoveToLineEnd { select: false }),
ReedlineOption::Edit(EditCommand::CutFromStart),
]),
Motion::LastLine => Some(vec![
ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }),
ReedlineOption::Edit(EditCommand::CutToEnd),
]),
Motion::ReplayCharSearch => vi_state
.last_char_search
.as_ref()
Expand Down Expand Up @@ -453,10 +479,24 @@ impl Command {
Some(vec![ReedlineOption::Edit(EditCommand::CopyLeftBefore(*c))])
}
Motion::Start => Some(vec![ReedlineOption::Edit(EditCommand::CopyFromLineStart)]),
Motion::NonBlankStart => Some(vec![ReedlineOption::Edit(
EditCommand::CopyFromLineNonBlankStart,
)]),
Motion::Left => Some(vec![ReedlineOption::Edit(EditCommand::CopyLeft)]),
Motion::Right => Some(vec![ReedlineOption::Edit(EditCommand::CopyRight)]),
Motion::Up => None,
Motion::Down => None,
Motion::FirstLine => Some(vec![
ReedlineOption::Edit(EditCommand::MoveToLineEnd { select: false }),
// `MoveRight` to include the new line character
ReedlineOption::Edit(EditCommand::MoveRight { select: false }),
ReedlineOption::Edit(EditCommand::CopyFromStart),
ReedlineOption::Edit(EditCommand::MoveToStart { select: false }),
]),
Motion::LastLine => Some(vec![
ReedlineOption::Edit(EditCommand::MoveToLineStart { select: false }),
ReedlineOption::Edit(EditCommand::CopyToEnd),
]),
Motion::ReplayCharSearch => vi_state
.last_char_search
.as_ref()
Expand Down
35 changes: 34 additions & 1 deletion src/edit_mode/vi/motion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,14 @@ where
let _ = input.next();
ParseResult::Valid(Motion::NextBigWordEnd)
}
Some('0' | '^') => {
Some('0') => {
let _ = input.next();
ParseResult::Valid(Motion::Start)
}
Some('^') => {
let _ = input.next();
ParseResult::Valid(Motion::NonBlankStart)
}
Some('$') => {
let _ = input.next();
ParseResult::Valid(Motion::End)
Expand Down Expand Up @@ -108,6 +112,21 @@ where
let _ = input.next();
ParseResult::Valid(Motion::ReverseCharSearch)
}
Some('g') => {
let _ = input.next();
match input.peek() {
Some('g') => {
input.next();
ParseResult::Valid(Motion::FirstLine)
}
Some(_) => ParseResult::Invalid,
None => ParseResult::Incomplete,
}
}
Some('G') => {
let _ = input.next();
ParseResult::Valid(Motion::LastLine)
}
ch if ch == command_char.as_ref().as_ref() && command_char.is_some() => {
let _ = input.next();
ParseResult::Valid(Motion::Line)
Expand All @@ -131,7 +150,10 @@ pub enum Motion {
PreviousBigWord,
Line,
Start,
NonBlankStart,
End,
FirstLine,
LastLine,
RightUntil(char),
RightBefore(char),
LeftUntil(char),
Expand Down Expand Up @@ -191,9 +213,20 @@ impl Motion {
Motion::Start => vec![ReedlineOption::Edit(EditCommand::MoveToLineStart {
select: select_mode,
})],
Motion::NonBlankStart => {
vec![ReedlineOption::Edit(EditCommand::MoveToLineNonBlankStart {
select: select_mode,
})]
}
Motion::End => vec![ReedlineOption::Edit(EditCommand::MoveToLineEnd {
select: select_mode,
})],
Motion::FirstLine => vec![ReedlineOption::Edit(EditCommand::MoveToStart {
select: select_mode,
})],
Motion::LastLine => vec![ReedlineOption::Edit(EditCommand::MoveToEnd {
select: select_mode,
})],
Motion::RightUntil(ch) => {
vi_state.last_char_search = Some(ViCharSearch::ToRight(*ch));
vec![ReedlineOption::Edit(EditCommand::MoveRightUntil {
Expand Down
24 changes: 22 additions & 2 deletions src/edit_mode/vi/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,20 +541,40 @@ mod tests {
// #[case(&['d', 'k'], ReedlineEvent::Multiple(vec![ReedlineEvent::Up, ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine]), ReedlineEvent::Edit(vec![EditCommand::CutCurrentLine])]))]
#[case(&['d', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRight])]))]
#[case(&['d', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart])]))]
#[case(&['d', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart])]))]
#[case(&['d', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart])]))]
#[case(&['d', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd])]))]
#[case(&['d', 'f', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutRightUntil('a')])]))]
#[case(&['d', 't', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutRightBefore('a')])]))]
#[case(&['d', 'F', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftUntil('a')])]))]
#[case(&['d', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')])]))]
#[case(&['d', 'g', 'g'], ReedlineEvent::Multiple(vec![
ReedlineEvent::Edit(vec![EditCommand::MoveToLineEnd { select: false }]),
ReedlineEvent::Edit(vec![EditCommand::MoveRight { select: false }]),
ReedlineEvent::Edit(vec![EditCommand::CutFromStart]),
]))]
#[case(&['d', 'G'], ReedlineEvent::Multiple(vec![
ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]),
ReedlineEvent::Edit(vec![EditCommand::MoveLeft { select: false }]),
ReedlineEvent::Edit(vec![EditCommand::CutToEnd]),
]))]
#[case(&['c', 'E'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutBigWordRight]), ReedlineEvent::Repaint]))]
#[case(&['c', '0'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))]
#[case(&['c', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineStart]), ReedlineEvent::Repaint]))]
#[case(&['c', '^'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutFromLineNonBlankStart]), ReedlineEvent::Repaint]))]
#[case(&['c', '$'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutToLineEnd]), ReedlineEvent::Repaint]))]
#[case(&['c', 'f', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutRightUntil('a')]), ReedlineEvent::Repaint]))]
#[case(&['c', 't', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutRightBefore('a')]), ReedlineEvent::Repaint]))]
#[case(&['c', 'F', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftUntil('a')]), ReedlineEvent::Repaint]))]
#[case(&['c', 'T', 'a'], ReedlineEvent::Multiple(vec![ReedlineEvent::Edit(vec![EditCommand::CutLeftBefore('a')]), ReedlineEvent::Repaint]))]
#[case(&['c', 'g', 'g'], ReedlineEvent::Multiple(vec![
ReedlineEvent::Edit(vec![EditCommand::MoveToLineEnd { select: false }]),
ReedlineEvent::Edit(vec![EditCommand::CutFromStart]),
ReedlineEvent::Repaint,
]))]
#[case(&['c', 'G'], ReedlineEvent::Multiple(vec![
ReedlineEvent::Edit(vec![EditCommand::MoveToLineStart { select: false }]),
ReedlineEvent::Edit(vec![EditCommand::CutToEnd]),
ReedlineEvent::Repaint,
]))]
fn test_reedline_move(#[case] input: &[char], #[case] expected: ReedlineEvent) {
let mut vi = Vi::default();
let res = vi_parse(input);
Expand Down
20 changes: 20 additions & 0 deletions src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ pub enum EditCommand {
select: bool,
},

/// Move to the start of the current line skipping any whitespace
MoveToLineNonBlankStart {
/// Select the text between the current cursor position and destination
select: bool,
},

/// Move to the end of the buffer
MoveToEnd {
/// Select the text between the current cursor position and destination
Expand Down Expand Up @@ -197,6 +203,9 @@ pub enum EditCommand {
/// Cut from the start of the current line to the insertion point
CutFromLineStart,

/// Cut from the first non whitespace character of the current line to the insertion point
CutFromLineNonBlankStart,

/// Cut from the insertion point to the end of the buffer
CutToEnd,

Expand Down Expand Up @@ -317,6 +326,9 @@ pub enum EditCommand {
/// Copy from the start of the current line to the insertion point
CopyFromLineStart,

/// Copy from the first non whitespace character of the current line to the insertion point
CopyFromLineNonBlankStart,

/// Copy from the insertion point to the end of the buffer
CopyToEnd,

Expand Down Expand Up @@ -423,6 +435,9 @@ impl Display for EditCommand {
EditCommand::MoveToLineStart { .. } => {
write!(f, "MoveToLineStart Optional[select: <bool>]")
}
EditCommand::MoveToLineNonBlankStart { .. } => {
write!(f, "MoveToLineNonBlankStart Optional[select: <bool>]")
}
EditCommand::MoveToEnd { .. } => write!(f, "MoveToEnd Optional[select: <bool>]"),
EditCommand::MoveToLineEnd { .. } => {
write!(f, "MoveToLineEnd Optional[select: <bool>]")
Expand Down Expand Up @@ -473,6 +488,7 @@ impl Display for EditCommand {
EditCommand::CutCurrentLine => write!(f, "CutCurrentLine"),
EditCommand::CutFromStart => write!(f, "CutFromStart"),
EditCommand::CutFromLineStart => write!(f, "CutFromLineStart"),
EditCommand::CutFromLineNonBlankStart => write!(f, "CutFromLineNonBlankStart"),
EditCommand::CutToEnd => write!(f, "CutToEnd"),
EditCommand::CutToLineEnd => write!(f, "CutToLineEnd"),
EditCommand::KillLine => write!(f, "KillLine"),
Expand Down Expand Up @@ -504,6 +520,7 @@ impl Display for EditCommand {
EditCommand::Paste => write!(f, "Paste"),
EditCommand::CopyFromStart => write!(f, "CopyFromStart"),
EditCommand::CopyFromLineStart => write!(f, "CopyFromLineStart"),
EditCommand::CopyFromLineNonBlankStart => write!(f, "CopyFromLineNonBlankStart"),
EditCommand::CopyToEnd => write!(f, "CopyToEnd"),
EditCommand::CopyToLineEnd => write!(f, "CopyToLineEnd"),
EditCommand::CopyCurrentLine => write!(f, "CopyCurrentLine"),
Expand Down Expand Up @@ -546,6 +563,7 @@ impl EditCommand {
| EditCommand::MoveToEnd { select, .. }
| EditCommand::MoveToLineStart { select, .. }
| EditCommand::MoveToLineEnd { select, .. }
| EditCommand::MoveToLineNonBlankStart { select, .. }
| EditCommand::MoveToPosition { select, .. }
| EditCommand::MoveLeft { select, .. }
| EditCommand::MoveRight { select, .. }
Expand Down Expand Up @@ -582,6 +600,7 @@ impl EditCommand {
| EditCommand::CutCurrentLine
| EditCommand::CutFromStart
| EditCommand::CutFromLineStart
| EditCommand::CutFromLineNonBlankStart
| EditCommand::CutToLineEnd
| EditCommand::KillLine
| EditCommand::CutToEnd
Expand Down Expand Up @@ -622,6 +641,7 @@ impl EditCommand {
EditCommand::CopyTextObject { .. } => EditType::NoOp,
EditCommand::CopyFromStart
| EditCommand::CopyFromLineStart
| EditCommand::CopyFromLineNonBlankStart
| EditCommand::CopyToEnd
| EditCommand::CopyToLineEnd
| EditCommand::CopyCurrentLine
Expand Down