@@ -1,8 +1,10 @@
use editor::display_map::DisplaySnapshot;
-use editor::{DisplayPoint, Editor, SelectionEffects, ToOffset, ToPoint, movement};
+use editor::{
+ DisplayPoint, Editor, HideMouseCursorOrigin, SelectionEffects, ToOffset, ToPoint, movement,
+};
use gpui::{Action, actions};
use gpui::{Context, Window};
-use language::{CharClassifier, CharKind};
+use language::{CharClassifier, CharKind, Point};
use text::{Bias, SelectionGoal};
use crate::motion;
@@ -25,11 +27,14 @@ actions!(
HelixAppend,
/// Goes to the location of the last modification.
HelixGotoLastModification,
+ /// Select entire line or multiple lines, extending downwards.
+ HelixSelectLine,
]
);
pub fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, Vim::helix_normal_after);
+ Vim::action(editor, cx, Vim::helix_select_lines);
Vim::action(editor, cx, Vim::helix_insert);
Vim::action(editor, cx, Vim::helix_append);
Vim::action(editor, cx, Vim::helix_yank);
@@ -442,6 +447,47 @@ impl Vim {
) {
self.jump(".".into(), false, false, window, cx);
}
+
+ pub fn helix_select_lines(
+ &mut self,
+ _: &HelixSelectLine,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let count = Vim::take_count(cx).unwrap_or(1);
+ self.update_editor(cx, |_, editor, cx| {
+ editor.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
+ let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
+ let mut selections = editor.selections.all::<Point>(cx);
+ let max_point = display_map.buffer_snapshot.max_point();
+ let buffer_snapshot = &display_map.buffer_snapshot;
+
+ for selection in &mut selections {
+ // Start always goes to column 0 of the first selected line
+ let start_row = selection.start.row;
+ let current_end_row = selection.end.row;
+
+ // Check if cursor is on empty line by checking first character
+ let line_start_offset = buffer_snapshot.point_to_offset(Point::new(start_row, 0));
+ let first_char = buffer_snapshot.chars_at(line_start_offset).next();
+ let extra_line = if first_char == Some('\n') { 1 } else { 0 };
+
+ let end_row = current_end_row + count as u32 + extra_line;
+
+ selection.start = Point::new(start_row, 0);
+ selection.end = if end_row > max_point.row {
+ max_point
+ } else {
+ Point::new(end_row, 0)
+ };
+ selection.reversed = false;
+ }
+
+ editor.change_selections(Default::default(), window, cx, |s| {
+ s.select(selections);
+ });
+ });
+ }
}
#[cfg(test)]
@@ -850,4 +896,142 @@ mod test {
Mode::HelixNormal,
);
}
+
+ #[gpui::test]
+ async fn test_helix_select_lines(cx: &mut gpui::TestAppContext) {
+ let mut cx = VimTestContext::new(cx, true).await;
+ cx.set_state(
+ "line one\nline ˇtwo\nline three\nline four",
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("2 x");
+ cx.assert_state(
+ "line one\n«line two\nline three\nˇ»line four",
+ Mode::HelixNormal,
+ );
+
+ // Test extending existing line selection
+ cx.set_state(
+ indoc! {"
+ li«ˇne one
+ li»ne two
+ line three
+ line four"},
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("x");
+ cx.assert_state(
+ indoc! {"
+ «line one
+ line two
+ ˇ»line three
+ line four"},
+ Mode::HelixNormal,
+ );
+
+ // Pressing x in empty line, select next line (because helix considers cursor a selection)
+ cx.set_state(
+ indoc! {"
+ line one
+ ˇ
+ line three
+ line four"},
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("x");
+ cx.assert_state(
+ indoc! {"
+ line one
+ «
+ line three
+ ˇ»line four"},
+ Mode::HelixNormal,
+ );
+
+ // Empty line with count selects extra + count lines
+ cx.set_state(
+ indoc! {"
+ line one
+ ˇ
+ line three
+ line four
+ line five"},
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("2 x");
+ cx.assert_state(
+ indoc! {"
+ line one
+ «
+ line three
+ line four
+ ˇ»line five"},
+ Mode::HelixNormal,
+ );
+
+ // Compare empty vs non-empty line behavior
+ cx.set_state(
+ indoc! {"
+ ˇnon-empty line
+ line two
+ line three"},
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("x");
+ cx.assert_state(
+ indoc! {"
+ «non-empty line
+ ˇ»line two
+ line three"},
+ Mode::HelixNormal,
+ );
+
+ // Same test but with empty line - should select one extra
+ cx.set_state(
+ indoc! {"
+ ˇ
+ line two
+ line three"},
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("x");
+ cx.assert_state(
+ indoc! {"
+ «
+ line two
+ ˇ»line three"},
+ Mode::HelixNormal,
+ );
+
+ // Test selecting multiple lines with count
+ cx.set_state(
+ indoc! {"
+ ˇline one
+ line two
+ line threeˇ
+ line four
+ line five"},
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("x");
+ cx.assert_state(
+ indoc! {"
+ «line one
+ ˇ»line two
+ «line three
+ ˇ»line four
+ line five"},
+ Mode::HelixNormal,
+ );
+ cx.simulate_keystrokes("x");
+ cx.assert_state(
+ indoc! {"
+ «line one
+ line two
+ line three
+ line four
+ ˇ»line five"},
+ Mode::HelixNormal,
+ );
+ }
}