Solve an inconsistency between vim and helix

This commit is contained in:
fantacell 2025-07-08 11:18:47 +02:00
parent 05bc741eaf
commit 309831ea9a
2 changed files with 70 additions and 1 deletions

View file

@ -16,7 +16,7 @@ impl Vim {
self.update_editor(window, cx, |_, editor, window, cx| {
editor.change_selections(Default::default(), window, cx, |s| {
s.move_with(|map, selection| {
let Some(range) = object.range(map, selection.clone(), around, None) else {
let Some(range) = object.helix_range(map, selection.clone(), around) else {
return;
};

View file

@ -714,6 +714,27 @@ impl Object {
}
}
/// Returns the range the object spans if the cursor is over it.
/// Follows helix convention.
pub fn helix_range(
self,
map: &DisplaySnapshot,
selection: Selection<DisplayPoint>,
around: bool,
) -> Option<Range<DisplayPoint>> {
let relative_to = selection.head();
match self {
Object::Word { ignore_punctuation } => {
if around {
helix_around_word(map, relative_to, ignore_punctuation)
} else {
helix_in_word(map, relative_to, ignore_punctuation)
}
}
_ => self.range(map, selection, around, None),
}
}
pub fn expand_selection(
self,
map: &DisplaySnapshot,
@ -759,6 +780,42 @@ fn in_word(
Some(start..end)
}
/// Returns a range that surrounds the word `relative_to` is in.
///
/// If `relative_to` is between words, return `None`.
fn helix_in_word(
map: &DisplaySnapshot,
relative_to: DisplayPoint,
ignore_punctuation: bool,
) -> Option<Range<DisplayPoint>> {
// Use motion::right so that we consider the character under the cursor when looking for the start
let classifier = map
.buffer_snapshot
.char_classifier_at(relative_to.to_point(map))
.ignore_punctuation(ignore_punctuation);
let char = map
.buffer_chars_at(relative_to.to_offset(map, Bias::Left))
.next()?
.0;
if classifier.kind(char) == CharKind::Whitespace {
return None;
}
let start = movement::find_preceding_boundary_display_point(
map,
right(map, relative_to, 1),
movement::FindRange::SingleLine,
|left, right| classifier.kind(left) != classifier.kind(right),
);
let end = movement::find_boundary(map, relative_to, FindRange::SingleLine, |left, right| {
classifier.kind(left) != classifier.kind(right)
});
Some(start..end)
}
fn in_subword(
map: &DisplaySnapshot,
relative_to: DisplayPoint,
@ -927,6 +984,18 @@ fn around_word(
}
}
/// Returns the range of the word the cursor is over and all the whitespace on one side.
/// If there is whitespace after that is included, otherwise it's whitespace before the word if any.
fn helix_around_word(
map: &DisplaySnapshot,
relative_to: DisplayPoint,
ignore_punctuation: bool,
) -> Option<Range<DisplayPoint>> {
let word_range = helix_in_word(map, relative_to, ignore_punctuation)?;
Some(expand_to_include_whitespace(map, word_range, true))
}
fn around_subword(
map: &DisplaySnapshot,
relative_to: DisplayPoint,