Implement Helix Support (WIP) (#19175)
Closes #4642 - Added the ability to switch to helix normal mode, with an additional helix visual mode. - <kbd>ctrl</kbd><kbd>h</kbd> from Insert mode goes to Helix Normal mode. <kbd> i </kbd> and <kbd> a </kbd> to go back. - Need to find a way to perform the helix normal mode selection with <kbd> w </kbd>, <kbd>e </kbd>, <kbd> b </kbd> as a first step. Need to figure out how the mode will interoperate with the VIM mode as the new additions are in the same crate.
This commit is contained in:
parent
c5d15fd065
commit
8f08787cf0
11 changed files with 444 additions and 12 deletions
|
@ -326,6 +326,22 @@
|
||||||
"ctrl-o": "vim::TemporaryNormal"
|
"ctrl-o": "vim::TemporaryNormal"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"context": "vim_mode == helix_normal",
|
||||||
|
"bindings": {
|
||||||
|
"i": "vim::InsertBefore",
|
||||||
|
"a": "vim::InsertAfter",
|
||||||
|
"w": "vim::NextWordStart",
|
||||||
|
"e": "vim::NextWordEnd",
|
||||||
|
"b": "vim::PreviousWordStart",
|
||||||
|
|
||||||
|
"h": "vim::Left",
|
||||||
|
"j": "vim::Down",
|
||||||
|
"k": "vim::Up",
|
||||||
|
"l": "vim::Right"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
|
||||||
"use_layout_keys": true,
|
"use_layout_keys": true,
|
||||||
|
|
|
@ -488,6 +488,101 @@ pub fn find_boundary_point(
|
||||||
map.clip_point(offset.to_display_point(map), Bias::Right)
|
map.clip_point(offset.to_display_point(map), Bias::Right)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_preceding_boundary_trail(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
head: DisplayPoint,
|
||||||
|
mut is_boundary: impl FnMut(char, char) -> bool,
|
||||||
|
) -> (Option<DisplayPoint>, DisplayPoint) {
|
||||||
|
let mut offset = head.to_offset(map, Bias::Left);
|
||||||
|
let mut trail_offset = None;
|
||||||
|
|
||||||
|
let mut prev_ch = map.buffer_snapshot.chars_at(offset).next();
|
||||||
|
let mut forward = map.buffer_snapshot.reversed_chars_at(offset).peekable();
|
||||||
|
|
||||||
|
// Skip newlines
|
||||||
|
while let Some(&ch) = forward.peek() {
|
||||||
|
if ch == '\n' {
|
||||||
|
prev_ch = forward.next();
|
||||||
|
offset -= ch.len_utf8();
|
||||||
|
trail_offset = Some(offset);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the boundary
|
||||||
|
let start_offset = offset;
|
||||||
|
for ch in forward {
|
||||||
|
if let Some(prev_ch) = prev_ch {
|
||||||
|
if is_boundary(prev_ch, ch) {
|
||||||
|
if start_offset == offset {
|
||||||
|
trail_offset = Some(offset);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset -= ch.len_utf8();
|
||||||
|
prev_ch = Some(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let trail = trail_offset
|
||||||
|
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Left));
|
||||||
|
|
||||||
|
(
|
||||||
|
trail,
|
||||||
|
map.clip_point(offset.to_display_point(map), Bias::Left),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finds the location of a boundary
|
||||||
|
pub fn find_boundary_trail(
|
||||||
|
map: &DisplaySnapshot,
|
||||||
|
head: DisplayPoint,
|
||||||
|
mut is_boundary: impl FnMut(char, char) -> bool,
|
||||||
|
) -> (Option<DisplayPoint>, DisplayPoint) {
|
||||||
|
let mut offset = head.to_offset(map, Bias::Right);
|
||||||
|
let mut trail_offset = None;
|
||||||
|
|
||||||
|
let mut prev_ch = map.buffer_snapshot.reversed_chars_at(offset).next();
|
||||||
|
let mut forward = map.buffer_snapshot.chars_at(offset).peekable();
|
||||||
|
|
||||||
|
// Skip newlines
|
||||||
|
while let Some(&ch) = forward.peek() {
|
||||||
|
if ch == '\n' {
|
||||||
|
prev_ch = forward.next();
|
||||||
|
offset += ch.len_utf8();
|
||||||
|
trail_offset = Some(offset);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the boundary
|
||||||
|
let start_offset = offset;
|
||||||
|
for ch in forward {
|
||||||
|
if let Some(prev_ch) = prev_ch {
|
||||||
|
if is_boundary(prev_ch, ch) {
|
||||||
|
if start_offset == offset {
|
||||||
|
trail_offset = Some(offset);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset += ch.len_utf8();
|
||||||
|
prev_ch = Some(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
let trail = trail_offset
|
||||||
|
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Right));
|
||||||
|
|
||||||
|
(
|
||||||
|
trail,
|
||||||
|
map.clip_point(offset.to_display_point(map), Bias::Right),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn find_boundary(
|
pub fn find_boundary(
|
||||||
map: &DisplaySnapshot,
|
map: &DisplaySnapshot,
|
||||||
from: DisplayPoint,
|
from: DisplayPoint,
|
||||||
|
|
|
@ -4632,7 +4632,7 @@ impl CharClassifier {
|
||||||
self.kind(c) == CharKind::Punctuation
|
self.kind(c) == CharKind::Punctuation
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self, c: char) -> CharKind {
|
pub fn kind_with(&self, c: char, ignore_punctuation: bool) -> CharKind {
|
||||||
if c.is_whitespace() {
|
if c.is_whitespace() {
|
||||||
return CharKind::Whitespace;
|
return CharKind::Whitespace;
|
||||||
} else if c.is_alphanumeric() || c == '_' {
|
} else if c.is_alphanumeric() || c == '_' {
|
||||||
|
@ -4642,7 +4642,7 @@ impl CharClassifier {
|
||||||
if let Some(scope) = &self.scope {
|
if let Some(scope) = &self.scope {
|
||||||
if let Some(characters) = scope.word_characters() {
|
if let Some(characters) = scope.word_characters() {
|
||||||
if characters.contains(&c) {
|
if characters.contains(&c) {
|
||||||
if c == '-' && !self.for_completion && !self.ignore_punctuation {
|
if c == '-' && !self.for_completion && !ignore_punctuation {
|
||||||
return CharKind::Punctuation;
|
return CharKind::Punctuation;
|
||||||
}
|
}
|
||||||
return CharKind::Word;
|
return CharKind::Word;
|
||||||
|
@ -4650,12 +4650,16 @@ impl CharClassifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.ignore_punctuation {
|
if ignore_punctuation {
|
||||||
CharKind::Word
|
CharKind::Word
|
||||||
} else {
|
} else {
|
||||||
CharKind::Punctuation
|
CharKind::Punctuation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn kind(&self, c: char) -> CharKind {
|
||||||
|
self.kind_with(c, self.ignore_punctuation)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find all of the ranges of whitespace that occur at the ends of lines
|
/// Find all of the ranges of whitespace that occur at the ends of lines
|
||||||
|
|
|
@ -84,6 +84,31 @@ impl<T: Copy + Ord> Selection<T> {
|
||||||
}
|
}
|
||||||
self.goal = new_goal;
|
self.goal = new_goal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_tail(&mut self, tail: T, new_goal: SelectionGoal) {
|
||||||
|
if tail.cmp(&self.head()) <= Ordering::Equal {
|
||||||
|
if self.reversed {
|
||||||
|
self.end = self.start;
|
||||||
|
self.reversed = false;
|
||||||
|
}
|
||||||
|
self.start = tail;
|
||||||
|
} else {
|
||||||
|
if !self.reversed {
|
||||||
|
self.start = self.end;
|
||||||
|
self.reversed = true;
|
||||||
|
}
|
||||||
|
self.end = tail;
|
||||||
|
}
|
||||||
|
self.goal = new_goal;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn swap_head_tail(&mut self) {
|
||||||
|
if self.reversed {
|
||||||
|
self.reversed = false;
|
||||||
|
} else {
|
||||||
|
std::mem::swap(&mut self.start, &mut self.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Copy> Selection<T> {
|
impl<T: Copy> Selection<T> {
|
||||||
|
|
271
crates/vim/src/helix.rs
Normal file
271
crates/vim/src/helix.rs
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
use editor::{movement, scroll::Autoscroll, DisplayPoint, Editor};
|
||||||
|
use gpui::{actions, Action};
|
||||||
|
use language::{CharClassifier, CharKind};
|
||||||
|
use ui::ViewContext;
|
||||||
|
|
||||||
|
use crate::{motion::Motion, state::Mode, Vim};
|
||||||
|
|
||||||
|
actions!(vim, [HelixNormalAfter]);
|
||||||
|
|
||||||
|
pub fn register(editor: &mut Editor, cx: &mut ViewContext<Vim>) {
|
||||||
|
Vim::action(editor, cx, Vim::helix_normal_after);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vim {
|
||||||
|
pub fn helix_normal_after(&mut self, action: &HelixNormalAfter, cx: &mut ViewContext<Self>) {
|
||||||
|
if self.active_operator().is_some() {
|
||||||
|
self.operator_stack.clear();
|
||||||
|
self.sync_vim_settings(cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.stop_recording_immediately(action.boxed_clone(), cx);
|
||||||
|
self.switch_mode(Mode::HelixNormal, false, cx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn helix_normal_motion(
|
||||||
|
&mut self,
|
||||||
|
motion: Motion,
|
||||||
|
times: Option<usize>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.helix_move_cursor(motion, times, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn helix_find_range_forward(
|
||||||
|
&mut self,
|
||||||
|
times: Option<usize>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
||||||
|
) {
|
||||||
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
let times = times.unwrap_or(1);
|
||||||
|
|
||||||
|
if selection.head() == map.max_point() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collapse to block cursor
|
||||||
|
if selection.tail() < selection.head() {
|
||||||
|
selection.set_tail(movement::left(map, selection.head()), selection.goal);
|
||||||
|
} else {
|
||||||
|
selection.set_tail(selection.head(), selection.goal);
|
||||||
|
selection.set_head(movement::right(map, selection.head()), selection.goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a classifier
|
||||||
|
let classifier = map
|
||||||
|
.buffer_snapshot
|
||||||
|
.char_classifier_at(selection.head().to_point(map));
|
||||||
|
|
||||||
|
let mut last_selection = selection.clone();
|
||||||
|
for _ in 0..times {
|
||||||
|
let (new_tail, new_head) =
|
||||||
|
movement::find_boundary_trail(map, selection.head(), |left, right| {
|
||||||
|
is_boundary(left, right, &classifier)
|
||||||
|
});
|
||||||
|
|
||||||
|
selection.set_head(new_head, selection.goal);
|
||||||
|
if let Some(new_tail) = new_tail {
|
||||||
|
selection.set_tail(new_tail, selection.goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.head() == last_selection.head()
|
||||||
|
&& selection.tail() == last_selection.tail()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_selection = selection.clone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn helix_find_range_backward(
|
||||||
|
&mut self,
|
||||||
|
times: Option<usize>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
mut is_boundary: impl FnMut(char, char, &CharClassifier) -> bool,
|
||||||
|
) {
|
||||||
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
let times = times.unwrap_or(1);
|
||||||
|
|
||||||
|
if selection.head() == DisplayPoint::zero() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// collapse to block cursor
|
||||||
|
if selection.tail() < selection.head() {
|
||||||
|
selection.set_tail(movement::left(map, selection.head()), selection.goal);
|
||||||
|
} else {
|
||||||
|
selection.set_tail(selection.head(), selection.goal);
|
||||||
|
selection.set_head(movement::right(map, selection.head()), selection.goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// flip the selection
|
||||||
|
selection.swap_head_tail();
|
||||||
|
|
||||||
|
// create a classifier
|
||||||
|
let classifier = map
|
||||||
|
.buffer_snapshot
|
||||||
|
.char_classifier_at(selection.head().to_point(map));
|
||||||
|
|
||||||
|
let mut last_selection = selection.clone();
|
||||||
|
for _ in 0..times {
|
||||||
|
let (new_tail, new_head) = movement::find_preceding_boundary_trail(
|
||||||
|
map,
|
||||||
|
selection.head(),
|
||||||
|
|left, right| is_boundary(left, right, &classifier),
|
||||||
|
);
|
||||||
|
|
||||||
|
selection.set_head(new_head, selection.goal);
|
||||||
|
if let Some(new_tail) = new_tail {
|
||||||
|
selection.set_tail(new_tail, selection.goal);
|
||||||
|
}
|
||||||
|
|
||||||
|
if selection.head() == last_selection.head()
|
||||||
|
&& selection.tail() == last_selection.tail()
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_selection = selection.clone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn helix_move_and_collapse(
|
||||||
|
&mut self,
|
||||||
|
motion: Motion,
|
||||||
|
times: Option<usize>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.update_editor(cx, |_, editor, cx| {
|
||||||
|
let text_layout_details = editor.text_layout_details(cx);
|
||||||
|
editor.change_selections(Some(Autoscroll::fit()), cx, |s| {
|
||||||
|
s.move_with(|map, selection| {
|
||||||
|
let goal = selection.goal;
|
||||||
|
let cursor = if selection.is_empty() || selection.reversed {
|
||||||
|
selection.head()
|
||||||
|
} else {
|
||||||
|
movement::left(map, selection.head())
|
||||||
|
};
|
||||||
|
|
||||||
|
let (point, goal) = motion
|
||||||
|
.move_point(map, cursor, selection.goal, times, &text_layout_details)
|
||||||
|
.unwrap_or((cursor, goal));
|
||||||
|
|
||||||
|
selection.collapse_to(point, goal)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn helix_move_cursor(
|
||||||
|
&mut self,
|
||||||
|
motion: Motion,
|
||||||
|
times: Option<usize>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
match motion {
|
||||||
|
Motion::NextWordStart { ignore_punctuation } => {
|
||||||
|
self.helix_find_range_forward(times, cx, |left, right, classifier| {
|
||||||
|
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||||
|
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||||
|
let at_newline = right == '\n';
|
||||||
|
|
||||||
|
let found =
|
||||||
|
left_kind != right_kind && right_kind != CharKind::Whitespace || at_newline;
|
||||||
|
|
||||||
|
found
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Motion::NextWordEnd { ignore_punctuation } => {
|
||||||
|
self.helix_find_range_forward(times, cx, |left, right, classifier| {
|
||||||
|
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||||
|
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||||
|
let at_newline = right == '\n';
|
||||||
|
|
||||||
|
let found = left_kind != right_kind
|
||||||
|
&& (left_kind != CharKind::Whitespace || at_newline);
|
||||||
|
|
||||||
|
found
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Motion::PreviousWordStart { ignore_punctuation } => {
|
||||||
|
self.helix_find_range_backward(times, cx, |left, right, classifier| {
|
||||||
|
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||||
|
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||||
|
let at_newline = right == '\n';
|
||||||
|
|
||||||
|
let found = left_kind != right_kind
|
||||||
|
&& (left_kind != CharKind::Whitespace || at_newline);
|
||||||
|
|
||||||
|
found
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Motion::PreviousWordEnd { ignore_punctuation } => {
|
||||||
|
self.helix_find_range_backward(times, cx, |left, right, classifier| {
|
||||||
|
let left_kind = classifier.kind_with(left, ignore_punctuation);
|
||||||
|
let right_kind = classifier.kind_with(right, ignore_punctuation);
|
||||||
|
let at_newline = right == '\n';
|
||||||
|
|
||||||
|
let found = left_kind != right_kind
|
||||||
|
&& right_kind != CharKind::Whitespace
|
||||||
|
&& !at_newline;
|
||||||
|
|
||||||
|
found
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => self.helix_move_and_collapse(motion, times, cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
use crate::{state::Mode, test::VimTestContext};
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_next_word_start(cx: &mut gpui::TestAppContext) {
|
||||||
|
let mut cx = VimTestContext::new(cx, true).await;
|
||||||
|
// «
|
||||||
|
// ˇ
|
||||||
|
// »
|
||||||
|
cx.set_state(
|
||||||
|
indoc! {"
|
||||||
|
The quˇick brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog."},
|
||||||
|
Mode::HelixNormal,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.simulate_keystrokes("w");
|
||||||
|
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {"
|
||||||
|
The qu«ick ˇ»brown
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog."},
|
||||||
|
Mode::HelixNormal,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.simulate_keystrokes("w");
|
||||||
|
|
||||||
|
cx.assert_state(
|
||||||
|
indoc! {"
|
||||||
|
The quick «brownˇ»
|
||||||
|
fox jumps over
|
||||||
|
the lazy dog."},
|
||||||
|
Mode::HelixNormal,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -529,6 +529,8 @@ impl Vim {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mode::HelixNormal => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,6 +560,8 @@ impl Vim {
|
||||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||||
self.visual_motion(motion.clone(), count, cx)
|
self.visual_motion(motion.clone(), count, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mode::HelixNormal => self.helix_normal_motion(motion.clone(), count, cx),
|
||||||
}
|
}
|
||||||
self.clear_operator(cx);
|
self.clear_operator(cx);
|
||||||
if let Some(operator) = waiting_operator {
|
if let Some(operator) = waiting_operator {
|
||||||
|
|
|
@ -145,6 +145,8 @@ impl Vim {
|
||||||
cursor_positions.push(selection.start..selection.start);
|
cursor_positions.push(selection.start..selection.start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mode::HelixNormal => {}
|
||||||
Mode::Insert | Mode::Normal | Mode::Replace => {
|
Mode::Insert | Mode::Normal | Mode::Replace => {
|
||||||
let start = selection.start;
|
let start = selection.start;
|
||||||
let mut end = start;
|
let mut end = start;
|
||||||
|
|
|
@ -143,7 +143,7 @@ impl Vim {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Normal => self.normal_object(object, cx),
|
Mode::Normal => self.normal_object(object, cx),
|
||||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => self.visual_object(object, cx),
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => self.visual_object(object, cx),
|
||||||
Mode::Insert | Mode::Replace => {
|
Mode::Insert | Mode::Replace | Mode::HelixNormal => {
|
||||||
// Shouldn't execute a text object in insert mode. Ignoring
|
// Shouldn't execute a text object in insert mode. Ignoring
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ pub enum Mode {
|
||||||
Visual,
|
Visual,
|
||||||
VisualLine,
|
VisualLine,
|
||||||
VisualBlock,
|
VisualBlock,
|
||||||
|
HelixNormal,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Mode {
|
impl Display for Mode {
|
||||||
|
@ -37,6 +38,7 @@ impl Display for Mode {
|
||||||
Mode::Visual => write!(f, "VISUAL"),
|
Mode::Visual => write!(f, "VISUAL"),
|
||||||
Mode::VisualLine => write!(f, "VISUAL LINE"),
|
Mode::VisualLine => write!(f, "VISUAL LINE"),
|
||||||
Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
|
Mode::VisualBlock => write!(f, "VISUAL BLOCK"),
|
||||||
|
Mode::HelixNormal => write!(f, "HELIX NORMAL"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +48,7 @@ impl Mode {
|
||||||
match self {
|
match self {
|
||||||
Mode::Normal | Mode::Insert | Mode::Replace => false,
|
Mode::Normal | Mode::Insert | Mode::Replace => false,
|
||||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => true,
|
||||||
|
Mode::HelixNormal => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -442,6 +442,7 @@ impl NeovimConnection {
|
||||||
}
|
}
|
||||||
Mode::Insert | Mode::Normal | Mode::Replace => selections
|
Mode::Insert | Mode::Normal | Mode::Replace => selections
|
||||||
.push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
|
.push(Point::new(selection_row, selection_col)..Point::new(cursor_row, cursor_col)),
|
||||||
|
Mode::HelixNormal => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
let ranges = encode_ranges(&text, &selections);
|
let ranges = encode_ranges(&text, &selections);
|
||||||
|
|
|
@ -6,6 +6,7 @@ mod test;
|
||||||
mod change_list;
|
mod change_list;
|
||||||
mod command;
|
mod command;
|
||||||
mod digraph;
|
mod digraph;
|
||||||
|
mod helix;
|
||||||
mod indent;
|
mod indent;
|
||||||
mod insert;
|
mod insert;
|
||||||
mod mode_indicator;
|
mod mode_indicator;
|
||||||
|
@ -337,6 +338,7 @@ impl Vim {
|
||||||
|
|
||||||
normal::register(editor, cx);
|
normal::register(editor, cx);
|
||||||
insert::register(editor, cx);
|
insert::register(editor, cx);
|
||||||
|
helix::register(editor, cx);
|
||||||
motion::register(editor, cx);
|
motion::register(editor, cx);
|
||||||
command::register(editor, cx);
|
command::register(editor, cx);
|
||||||
replace::register(editor, cx);
|
replace::register(editor, cx);
|
||||||
|
@ -631,7 +633,9 @@ impl Vim {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::Replace => CursorShape::Underline,
|
Mode::Replace => CursorShape::Underline,
|
||||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => CursorShape::Block,
|
Mode::HelixNormal | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
||||||
|
CursorShape::Block
|
||||||
|
}
|
||||||
Mode::Insert => CursorShape::Bar,
|
Mode::Insert => CursorShape::Bar,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -645,9 +649,12 @@ impl Vim {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Mode::Normal | Mode::Replace | Mode::Visual | Mode::VisualLine | Mode::VisualBlock => {
|
Mode::Normal
|
||||||
false
|
| Mode::HelixNormal
|
||||||
}
|
| Mode::Replace
|
||||||
|
| Mode::Visual
|
||||||
|
| Mode::VisualLine
|
||||||
|
| Mode::VisualBlock => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,9 +664,12 @@ impl Vim {
|
||||||
|
|
||||||
pub fn clip_at_line_ends(&self) -> bool {
|
pub fn clip_at_line_ends(&self) -> bool {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
Mode::Insert | Mode::Visual | Mode::VisualLine | Mode::VisualBlock | Mode::Replace => {
|
Mode::Insert
|
||||||
false
|
| Mode::Visual
|
||||||
}
|
| Mode::VisualLine
|
||||||
|
| Mode::VisualBlock
|
||||||
|
| Mode::Replace
|
||||||
|
| Mode::HelixNormal => false,
|
||||||
Mode::Normal => true,
|
Mode::Normal => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -670,6 +680,7 @@ impl Vim {
|
||||||
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
|
Mode::Visual | Mode::VisualLine | Mode::VisualBlock => "visual",
|
||||||
Mode::Insert => "insert",
|
Mode::Insert => "insert",
|
||||||
Mode::Replace => "replace",
|
Mode::Replace => "replace",
|
||||||
|
Mode::HelixNormal => "helix_normal",
|
||||||
}
|
}
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
|
@ -998,7 +1009,7 @@ impl Vim {
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Mode::Insert | Mode::Replace => {}
|
Mode::Insert | Mode::Replace | Mode::HelixNormal => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue