Start a helix directory

This commit is contained in:
fantacell 2025-07-09 12:35:24 +02:00
parent 459f7e3403
commit b93869c564
3 changed files with 132 additions and 109 deletions

View file

@ -1,3 +1,5 @@
mod object;
use editor::{DisplayPoint, Editor, movement};
use gpui::{Action, actions};
use gpui::{Context, Window};

View file

@ -0,0 +1,129 @@
use std::ops::Range;
use editor::{
DisplayPoint,
display_map::DisplaySnapshot,
movement::{self, FindRange},
};
use language::CharKind;
use text::{Bias, Selection};
use crate::{
motion::right,
object::{Object, expand_to_include_whitespace},
};
impl Object {
/// Returns
/// 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),
}
}
}
/// 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)
}
/// 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))
}
#[cfg(test)]
mod test {
use crate::{state::Mode, test::VimTestContext};
#[gpui::test]
async fn test_select_word_object(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let start = indoc! {"
The quick brˇowˇnˇ
fox «ˇjumps» ov«er
the laˇ»zy dogˇ
"
};
cx.set_state(start, Mode::HelixNormal);
cx.simulate_keystrokes("m i w");
cx.assert_state(
indoc! {"
The quick «brownˇ»
fox «jumpsˇ» over
the «lazyˇ» dogˇ
"
},
Mode::HelixNormal,
);
cx.set_state(start, Mode::HelixNormal);
cx.simulate_keystrokes("m a w");
cx.assert_state(
indoc! {"
The quick« brownˇ»
fox «jumps ˇ»over
the «lazy ˇ»dogˇ
"
},
Mode::HelixNormal,
);
}
}

View file

@ -714,27 +714,6 @@ 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,
@ -780,42 +759,6 @@ 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,
@ -984,18 +927,6 @@ 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,
@ -1435,7 +1366,7 @@ fn is_sentence_end(map: &DisplaySnapshot, offset: usize) -> bool {
/// Expands the passed range to include whitespace on one side or the other in a line. Attempts to add the
/// whitespace to the end first and falls back to the start if there was none.
fn expand_to_include_whitespace(
pub fn expand_to_include_whitespace(
map: &DisplaySnapshot,
range: Range<DisplayPoint>,
stop_at_newline: bool,
@ -1822,45 +1753,6 @@ mod test {
.assert_matches();
}
#[gpui::test]
async fn test_select_word_object(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let start = indoc! {"
The quick brˇowˇnˇ
fox «ˇjumps» ov«er
the laˇ»zy dogˇ
"
};
cx.set_state(start, Mode::HelixNormal);
cx.simulate_keystrokes("m i w");
cx.assert_state(
indoc! {"
The quick «brownˇ»
fox «jumpsˇ» over
the «lazyˇ» dogˇ
"
},
Mode::HelixNormal,
);
cx.set_state(start, Mode::HelixNormal);
cx.simulate_keystrokes("m a w");
cx.assert_state(
indoc! {"
The quick« brownˇ»
fox «jumps ˇ»over
the «lazy ˇ»dogˇ
"
},
Mode::HelixNormal,
);
}
#[gpui::test]
async fn test_visual_word_object(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;