Skip to content

Commit 8f08787

Browse files
authored
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.
1 parent c5d15fd commit 8f08787

File tree

11 files changed

+444
-12
lines changed

11 files changed

+444
-12
lines changed

assets/keymaps/vim.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,22 @@
326326
"ctrl-o": "vim::TemporaryNormal"
327327
}
328328
},
329+
{
330+
"context": "vim_mode == helix_normal",
331+
"bindings": {
332+
"i": "vim::InsertBefore",
333+
"a": "vim::InsertAfter",
334+
"w": "vim::NextWordStart",
335+
"e": "vim::NextWordEnd",
336+
"b": "vim::PreviousWordStart",
337+
338+
"h": "vim::Left",
339+
"j": "vim::Down",
340+
"k": "vim::Up",
341+
"l": "vim::Right"
342+
}
343+
},
344+
329345
{
330346
"context": "vim_mode == insert && !(showing_code_actions || showing_completions)",
331347
"use_layout_keys": true,

crates/editor/src/movement.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,101 @@ pub fn find_boundary_point(
488488
map.clip_point(offset.to_display_point(map), Bias::Right)
489489
}
490490

491+
pub fn find_preceding_boundary_trail(
492+
map: &DisplaySnapshot,
493+
head: DisplayPoint,
494+
mut is_boundary: impl FnMut(char, char) -> bool,
495+
) -> (Option<DisplayPoint>, DisplayPoint) {
496+
let mut offset = head.to_offset(map, Bias::Left);
497+
let mut trail_offset = None;
498+
499+
let mut prev_ch = map.buffer_snapshot.chars_at(offset).next();
500+
let mut forward = map.buffer_snapshot.reversed_chars_at(offset).peekable();
501+
502+
// Skip newlines
503+
while let Some(&ch) = forward.peek() {
504+
if ch == '\n' {
505+
prev_ch = forward.next();
506+
offset -= ch.len_utf8();
507+
trail_offset = Some(offset);
508+
} else {
509+
break;
510+
}
511+
}
512+
513+
// Find the boundary
514+
let start_offset = offset;
515+
for ch in forward {
516+
if let Some(prev_ch) = prev_ch {
517+
if is_boundary(prev_ch, ch) {
518+
if start_offset == offset {
519+
trail_offset = Some(offset);
520+
} else {
521+
break;
522+
}
523+
}
524+
}
525+
offset -= ch.len_utf8();
526+
prev_ch = Some(ch);
527+
}
528+
529+
let trail = trail_offset
530+
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Left));
531+
532+
(
533+
trail,
534+
map.clip_point(offset.to_display_point(map), Bias::Left),
535+
)
536+
}
537+
538+
/// Finds the location of a boundary
539+
pub fn find_boundary_trail(
540+
map: &DisplaySnapshot,
541+
head: DisplayPoint,
542+
mut is_boundary: impl FnMut(char, char) -> bool,
543+
) -> (Option<DisplayPoint>, DisplayPoint) {
544+
let mut offset = head.to_offset(map, Bias::Right);
545+
let mut trail_offset = None;
546+
547+
let mut prev_ch = map.buffer_snapshot.reversed_chars_at(offset).next();
548+
let mut forward = map.buffer_snapshot.chars_at(offset).peekable();
549+
550+
// Skip newlines
551+
while let Some(&ch) = forward.peek() {
552+
if ch == '\n' {
553+
prev_ch = forward.next();
554+
offset += ch.len_utf8();
555+
trail_offset = Some(offset);
556+
} else {
557+
break;
558+
}
559+
}
560+
561+
// Find the boundary
562+
let start_offset = offset;
563+
for ch in forward {
564+
if let Some(prev_ch) = prev_ch {
565+
if is_boundary(prev_ch, ch) {
566+
if start_offset == offset {
567+
trail_offset = Some(offset);
568+
} else {
569+
break;
570+
}
571+
}
572+
}
573+
offset += ch.len_utf8();
574+
prev_ch = Some(ch);
575+
}
576+
577+
let trail = trail_offset
578+
.map(|trail_offset: usize| map.clip_point(trail_offset.to_display_point(map), Bias::Right));
579+
580+
(
581+
trail,
582+
map.clip_point(offset.to_display_point(map), Bias::Right),
583+
)
584+
}
585+
491586
pub fn find_boundary(
492587
map: &DisplaySnapshot,
493588
from: DisplayPoint,

crates/language/src/buffer.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4632,7 +4632,7 @@ impl CharClassifier {
46324632
self.kind(c) == CharKind::Punctuation
46334633
}
46344634

4635-
pub fn kind(&self, c: char) -> CharKind {
4635+
pub fn kind_with(&self, c: char, ignore_punctuation: bool) -> CharKind {
46364636
if c.is_whitespace() {
46374637
return CharKind::Whitespace;
46384638
} else if c.is_alphanumeric() || c == '_' {
@@ -4642,20 +4642,24 @@ impl CharClassifier {
46424642
if let Some(scope) = &self.scope {
46434643
if let Some(characters) = scope.word_characters() {
46444644
if characters.contains(&c) {
4645-
if c == '-' && !self.for_completion && !self.ignore_punctuation {
4645+
if c == '-' && !self.for_completion && !ignore_punctuation {
46464646
return CharKind::Punctuation;
46474647
}
46484648
return CharKind::Word;
46494649
}
46504650
}
46514651
}
46524652

4653-
if self.ignore_punctuation {
4653+
if ignore_punctuation {
46544654
CharKind::Word
46554655
} else {
46564656
CharKind::Punctuation
46574657
}
46584658
}
4659+
4660+
pub fn kind(&self, c: char) -> CharKind {
4661+
self.kind_with(c, self.ignore_punctuation)
4662+
}
46594663
}
46604664

46614665
/// Find all of the ranges of whitespace that occur at the ends of lines

crates/text/src/selection.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,31 @@ impl<T: Copy + Ord> Selection<T> {
8484
}
8585
self.goal = new_goal;
8686
}
87+
88+
pub fn set_tail(&mut self, tail: T, new_goal: SelectionGoal) {
89+
if tail.cmp(&self.head()) <= Ordering::Equal {
90+
if self.reversed {
91+
self.end = self.start;
92+
self.reversed = false;
93+
}
94+
self.start = tail;
95+
} else {
96+
if !self.reversed {
97+
self.start = self.end;
98+
self.reversed = true;
99+
}
100+
self.end = tail;
101+
}
102+
self.goal = new_goal;
103+
}
104+
105+
pub fn swap_head_tail(&mut self) {
106+
if self.reversed {
107+
self.reversed = false;
108+
} else {
109+
std::mem::swap(&mut self.start, &mut self.end);
110+
}
111+
}
87112
}
88113

89114
impl<T: Copy> Selection<T> {

0 commit comments

Comments
 (0)