Skip to content

Commit 01f08cd

Browse files
fantacellConradIrwin
authored andcommitted
helix: Add match operator (zed-industries#34060)
This is an implementation of matching like "m i (", as well as "] (" and "[ (" in `helix_mode` with a few supported objects and a basis for more. Release Notes: - Added helix operators for selecting text objects --------- Co-authored-by: Conrad Irwin <[email protected]>
1 parent cf7c06f commit 01f08cd

File tree

12 files changed

+1196
-69
lines changed

12 files changed

+1196
-69
lines changed

assets/keymaps/vim.json

Lines changed: 87 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -32,34 +32,6 @@
3232
"(": "vim::SentenceBackward",
3333
")": "vim::SentenceForward",
3434
"|": "vim::GoToColumn",
35-
"] ]": "vim::NextSectionStart",
36-
"] [": "vim::NextSectionEnd",
37-
"[ [": "vim::PreviousSectionStart",
38-
"[ ]": "vim::PreviousSectionEnd",
39-
"] m": "vim::NextMethodStart",
40-
"] shift-m": "vim::NextMethodEnd",
41-
"[ m": "vim::PreviousMethodStart",
42-
"[ shift-m": "vim::PreviousMethodEnd",
43-
"[ *": "vim::PreviousComment",
44-
"[ /": "vim::PreviousComment",
45-
"] *": "vim::NextComment",
46-
"] /": "vim::NextComment",
47-
"[ -": "vim::PreviousLesserIndent",
48-
"[ +": "vim::PreviousGreaterIndent",
49-
"[ =": "vim::PreviousSameIndent",
50-
"] -": "vim::NextLesserIndent",
51-
"] +": "vim::NextGreaterIndent",
52-
"] =": "vim::NextSameIndent",
53-
"] b": "pane::ActivateNextItem",
54-
"[ b": "pane::ActivatePreviousItem",
55-
"] shift-b": "pane::ActivateLastItem",
56-
"[ shift-b": ["pane::ActivateItem", 0],
57-
"] space": "vim::InsertEmptyLineBelow",
58-
"[ space": "vim::InsertEmptyLineAbove",
59-
"[ e": "editor::MoveLineUp",
60-
"] e": "editor::MoveLineDown",
61-
"[ f": "workspace::FollowNextCollaborator",
62-
"] f": "workspace::FollowNextCollaborator",
6335

6436
// Word motions
6537
"w": "vim::NextWordStart",
@@ -83,10 +55,6 @@
8355
"n": "vim::MoveToNextMatch",
8456
"shift-n": "vim::MoveToPreviousMatch",
8557
"%": "vim::Matching",
86-
"] }": ["vim::UnmatchedForward", { "char": "}" }],
87-
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
88-
"] )": ["vim::UnmatchedForward", { "char": ")" }],
89-
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
9058
"f": ["vim::PushFindForward", { "before": false, "multiline": false }],
9159
"t": ["vim::PushFindForward", { "before": true, "multiline": false }],
9260
"shift-f": ["vim::PushFindBackward", { "after": false, "multiline": false }],
@@ -219,6 +187,46 @@
219187
".": "vim::Repeat"
220188
}
221189
},
190+
{
191+
"context": "vim_mode == normal || vim_mode == visual || vim_mode == operator",
192+
"bindings": {
193+
"] ]": "vim::NextSectionStart",
194+
"] [": "vim::NextSectionEnd",
195+
"[ [": "vim::PreviousSectionStart",
196+
"[ ]": "vim::PreviousSectionEnd",
197+
"] m": "vim::NextMethodStart",
198+
"] shift-m": "vim::NextMethodEnd",
199+
"[ m": "vim::PreviousMethodStart",
200+
"[ shift-m": "vim::PreviousMethodEnd",
201+
"[ *": "vim::PreviousComment",
202+
"[ /": "vim::PreviousComment",
203+
"] *": "vim::NextComment",
204+
"] /": "vim::NextComment",
205+
"[ -": "vim::PreviousLesserIndent",
206+
"[ +": "vim::PreviousGreaterIndent",
207+
"[ =": "vim::PreviousSameIndent",
208+
"] -": "vim::NextLesserIndent",
209+
"] +": "vim::NextGreaterIndent",
210+
"] =": "vim::NextSameIndent",
211+
"] b": "pane::ActivateNextItem",
212+
"[ b": "pane::ActivatePreviousItem",
213+
"] shift-b": "pane::ActivateLastItem",
214+
"[ shift-b": ["pane::ActivateItem", 0],
215+
"] space": "vim::InsertEmptyLineBelow",
216+
"[ space": "vim::InsertEmptyLineAbove",
217+
"[ e": "editor::MoveLineUp",
218+
"] e": "editor::MoveLineDown",
219+
"[ f": "workspace::FollowNextCollaborator",
220+
"] f": "workspace::FollowNextCollaborator",
221+
"] }": ["vim::UnmatchedForward", { "char": "}" }],
222+
"[ {": ["vim::UnmatchedBackward", { "char": "{" }],
223+
"] )": ["vim::UnmatchedForward", { "char": ")" }],
224+
"[ (": ["vim::UnmatchedBackward", { "char": "(" }],
225+
// tree-sitter related commands
226+
"[ x": "vim::SelectLargerSyntaxNode",
227+
"] x": "vim::SelectSmallerSyntaxNode"
228+
}
229+
},
222230
{
223231
"context": "vim_mode == normal",
224232
"bindings": {
@@ -249,9 +257,6 @@
249257
"g w": "vim::PushRewrap",
250258
"g q": "vim::PushRewrap",
251259
"insert": "vim::InsertBefore",
252-
// tree-sitter related commands
253-
"[ x": "vim::SelectLargerSyntaxNode",
254-
"] x": "vim::SelectSmallerSyntaxNode",
255260
"] d": "editor::GoToDiagnostic",
256261
"[ d": "editor::GoToPreviousDiagnostic",
257262
"] c": "editor::GoToHunk",
@@ -317,10 +322,7 @@
317322
"g w": "vim::Rewrap",
318323
"g ?": "vim::ConvertToRot13",
319324
// "g ?": "vim::ConvertToRot47",
320-
"\"": "vim::PushRegister",
321-
// tree-sitter related commands
322-
"[ x": "editor::SelectLargerSyntaxNode",
323-
"] x": "editor::SelectSmallerSyntaxNode"
325+
"\"": "vim::PushRegister"
324326
}
325327
},
326328
{
@@ -397,6 +399,9 @@
397399
"ctrl-[": "editor::Cancel",
398400
";": "vim::HelixCollapseSelection",
399401
":": "command_palette::Toggle",
402+
"m": "vim::PushHelixMatch",
403+
"]": ["vim::PushHelixNext", { "around": true }],
404+
"[": ["vim::PushHelixPrevious", { "around": true }],
400405
"left": "vim::WrappingLeft",
401406
"right": "vim::WrappingRight",
402407
"h": "vim::WrappingLeft",
@@ -419,13 +424,6 @@
419424
"insert": "vim::InsertBefore",
420425
"alt-.": "vim::RepeatFind",
421426
"alt-s": ["editor::SplitSelectionIntoLines", { "keep_selections": true }],
422-
// tree-sitter related commands
423-
"[ x": "editor::SelectLargerSyntaxNode",
424-
"] x": "editor::SelectSmallerSyntaxNode",
425-
"] d": "editor::GoToDiagnostic",
426-
"[ d": "editor::GoToPreviousDiagnostic",
427-
"] c": "editor::GoToHunk",
428-
"[ c": "editor::GoToPreviousHunk",
429427
// Goto mode
430428
"g n": "pane::ActivateNextItem",
431429
"g p": "pane::ActivatePreviousItem",
@@ -469,9 +467,6 @@
469467
"space c": "editor::ToggleComments",
470468
"space y": "editor::Copy",
471469
"space p": "editor::Paste",
472-
// Match mode
473-
"m m": "vim::Matching",
474-
"m i w": ["workspace::SendKeystrokes", "v i w"],
475470
"shift-u": "editor::Redo",
476471
"ctrl-c": "editor::ToggleComments",
477472
"d": "vim::HelixDelete",
@@ -540,7 +535,7 @@
540535
}
541536
},
542537
{
543-
"context": "vim_operator == a || vim_operator == i || vim_operator == cs",
538+
"context": "vim_operator == a || vim_operator == i || vim_operator == cs || vim_operator == helix_next || vim_operator == helix_previous",
544539
"bindings": {
545540
"w": "vim::Word",
546541
"shift-w": ["vim::Word", { "ignore_punctuation": true }],
@@ -577,6 +572,48 @@
577572
"e": "vim::EntireFile"
578573
}
579574
},
575+
{
576+
"context": "vim_operator == helix_m",
577+
"bindings": {
578+
"m": "vim::Matching"
579+
}
580+
},
581+
{
582+
"context": "vim_operator == helix_next",
583+
"bindings": {
584+
"z": "vim::NextSectionStart",
585+
"shift-z": "vim::NextSectionEnd",
586+
"*": "vim::NextComment",
587+
"/": "vim::NextComment",
588+
"-": "vim::NextLesserIndent",
589+
"+": "vim::NextGreaterIndent",
590+
"=": "vim::NextSameIndent",
591+
"b": "pane::ActivateNextItem",
592+
"shift-b": "pane::ActivateLastItem",
593+
"x": "editor::SelectSmallerSyntaxNode",
594+
"d": "editor::GoToDiagnostic",
595+
"c": "editor::GoToHunk",
596+
"space": "vim::InsertEmptyLineBelow"
597+
}
598+
},
599+
{
600+
"context": "vim_operator == helix_previous",
601+
"bindings": {
602+
"z": "vim::PreviousSectionStart",
603+
"shift-z": "vim::PreviousSectionEnd",
604+
"*": "vim::PreviousComment",
605+
"/": "vim::PreviousComment",
606+
"-": "vim::PreviousLesserIndent",
607+
"+": "vim::PreviousGreaterIndent",
608+
"=": "vim::PreviousSameIndent",
609+
"b": "pane::ActivatePreviousItem",
610+
"shift-b": ["pane::ActivateItem", 0],
611+
"x": "editor::SelectLargerSyntaxNode",
612+
"d": "editor::GoToPreviousDiagnostic",
613+
"c": "editor::GoToPreviousHunk",
614+
"space": "vim::InsertEmptyLineAbove"
615+
}
616+
},
580617
{
581618
"context": "vim_operator == c",
582619
"bindings": {

crates/editor/src/movement.rs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use super::{Bias, DisplayPoint, DisplaySnapshot, SelectionGoal, ToDisplayPoint};
55
use crate::{DisplayRow, EditorStyle, ToOffset, ToPoint, scroll::ScrollAnchor};
66
use gpui::{Pixels, WindowTextSystem};
7-
use language::Point;
7+
use language::{CharClassifier, Point};
88
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
99
use serde::Deserialize;
1010
use workspace::searchable::Direction;
@@ -405,15 +405,18 @@ pub fn previous_subword_start(map: &DisplaySnapshot, point: DisplayPoint) -> Dis
405405
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
406406

407407
find_preceding_boundary_display_point(map, point, FindRange::MultiLine, |left, right| {
408-
let is_word_start =
409-
classifier.kind(left) != classifier.kind(right) && !right.is_whitespace();
410-
let is_subword_start = classifier.is_word('-') && left == '-' && right != '-'
411-
|| left == '_' && right != '_'
412-
|| left.is_lowercase() && right.is_uppercase();
413-
is_word_start || is_subword_start || left == '\n'
408+
is_subword_start(left, right, &classifier) || left == '\n'
414409
})
415410
}
416411

412+
pub fn is_subword_start(left: char, right: char, classifier: &CharClassifier) -> bool {
413+
let is_word_start = classifier.kind(left) != classifier.kind(right) && !right.is_whitespace();
414+
let is_subword_start = classifier.is_word('-') && left == '-' && right != '-'
415+
|| left == '_' && right != '_'
416+
|| left.is_lowercase() && right.is_uppercase();
417+
is_word_start || is_subword_start
418+
}
419+
417420
/// Returns a position of the next word boundary, where a word character is defined as either
418421
/// uppercase letter, lowercase letter, '_' character or language-specific word character (like '-' in CSS).
419422
pub fn next_word_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPoint {
@@ -463,15 +466,19 @@ pub fn next_subword_end(map: &DisplaySnapshot, point: DisplayPoint) -> DisplayPo
463466
let classifier = map.buffer_snapshot.char_classifier_at(raw_point);
464467

465468
find_boundary(map, point, FindRange::MultiLine, |left, right| {
466-
let is_word_end =
467-
(classifier.kind(left) != classifier.kind(right)) && !classifier.is_whitespace(left);
468-
let is_subword_end = classifier.is_word('-') && left != '-' && right == '-'
469-
|| left != '_' && right == '_'
470-
|| left.is_lowercase() && right.is_uppercase();
471-
is_word_end || is_subword_end || right == '\n'
469+
is_subword_end(left, right, &classifier) || right == '\n'
472470
})
473471
}
474472

473+
pub fn is_subword_end(left: char, right: char, classifier: &CharClassifier) -> bool {
474+
let is_word_end =
475+
(classifier.kind(left) != classifier.kind(right)) && !classifier.is_whitespace(left);
476+
let is_subword_end = classifier.is_word('-') && left != '-' && right == '-'
477+
|| left != '_' && right == '_'
478+
|| left.is_lowercase() && right.is_uppercase();
479+
is_word_end || is_subword_end
480+
}
481+
475482
/// Returns a position of the start of the current paragraph, where a paragraph
476483
/// is defined as a run of non-blank lines.
477484
pub fn start_of_paragraph(

crates/vim/src/helix.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
mod boundary;
2+
mod object;
3+
mod select;
4+
15
use editor::display_map::DisplaySnapshot;
26
use editor::{
37
DisplayPoint, Editor, HideMouseCursorOrigin, SelectionEffects, ToOffset, ToPoint, movement,

0 commit comments

Comments
 (0)