diff --git a/assets/keymaps/default-macos.json b/assets/keymaps/default-macos.json index 5d52deea678cad..bd5fe59b60f7ce 100644 --- a/assets/keymaps/default-macos.json +++ b/assets/keymaps/default-macos.json @@ -97,8 +97,10 @@ "cmd-right": "editor::MoveToEndOfLine", "ctrl-e": "editor::MoveToEndOfLine", "end": "editor::MoveToEndOfLine", - "cmd-up": "editor::MoveToBeginning", - "cmd-down": "editor::MoveToEnd", + "cmd-up": "editor::MoveToStartOfExcerpt", + "cmd-down": "editor::MoveToEndOfExcerpt", + "cmd-home": "editor::MoveToBeginning", // Typed via `cmd-fn-left` + "cmd-end": "editor::MoveToEnd", // Typed via `cmd-fn-right` "shift-up": "editor::SelectUp", "ctrl-shift-p": "editor::SelectUp", "shift-down": "editor::SelectDown", @@ -111,8 +113,8 @@ "alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect "ctrl-shift-up": "editor::SelectToStartOfParagraph", "ctrl-shift-down": "editor::SelectToEndOfParagraph", - "cmd-shift-up": "editor::SelectToBeginning", - "cmd-shift-down": "editor::SelectToEnd", + "cmd-shift-up": "editor::SelectToStartOfExcerpt", + "cmd-shift-down": "editor::SelectToEndOfExcerpt", "cmd-a": "editor::SelectAll", "cmd-l": "editor::SelectLine", "cmd-shift-i": "editor::Format", diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index 3efa89d4690492..8ed3f7d29e8af8 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -329,6 +329,8 @@ gpui::actions!( MoveToPreviousSubwordStart, MoveToPreviousWordStart, MoveToStartOfParagraph, + MoveToStartOfExcerpt, + MoveToEndOfExcerpt, MoveUp, Newline, NewlineAbove, @@ -365,6 +367,8 @@ gpui::actions!( ScrollCursorTop, SelectAll, SelectAllMatches, + SelectToStartOfExcerpt, + SelectToEndOfExcerpt, SelectDown, SelectEnclosingSymbol, SelectLargerSyntaxNode, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1afd49fba20ff7..2111cbb21e704c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -9027,6 +9027,98 @@ impl Editor { }) } + pub fn move_to_start_of_excerpt( + &mut self, + _: &MoveToStartOfExcerpt, + window: &mut Window, + cx: &mut Context, + ) { + if matches!(self.mode, EditorMode::SingleLine { .. }) { + cx.propagate(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.move_with(|map, selection| { + selection.collapse_to( + movement::start_of_excerpt( + map, + selection.head(), + workspace::searchable::Direction::Prev, + ), + SelectionGoal::None, + ) + }); + }) + } + + pub fn move_to_end_of_excerpt( + &mut self, + _: &MoveToEndOfExcerpt, + window: &mut Window, + cx: &mut Context, + ) { + if matches!(self.mode, EditorMode::SingleLine { .. }) { + cx.propagate(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.move_with(|map, selection| { + selection.collapse_to( + movement::end_of_excerpt( + map, + selection.head(), + workspace::searchable::Direction::Next, + ), + SelectionGoal::None, + ) + }); + }) + } + + pub fn select_to_start_of_excerpt( + &mut self, + _: &SelectToStartOfExcerpt, + window: &mut Window, + cx: &mut Context, + ) { + if matches!(self.mode, EditorMode::SingleLine { .. }) { + cx.propagate(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev), + SelectionGoal::None, + ) + }); + }) + } + + pub fn select_to_end_of_excerpt( + &mut self, + _: &SelectToEndOfExcerpt, + window: &mut Window, + cx: &mut Context, + ) { + if matches!(self.mode, EditorMode::SingleLine { .. }) { + cx.propagate(); + return; + } + + self.change_selections(Some(Autoscroll::fit()), window, cx, |s| { + s.move_heads_with(|map, head, _| { + ( + movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next), + SelectionGoal::None, + ) + }); + }) + } + pub fn move_to_beginning( &mut self, _: &MoveToBeginning, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 93d60624e2d2d8..8d870c662949a6 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -291,6 +291,8 @@ impl EditorElement { register_action(editor, window, Editor::move_to_end_of_paragraph); register_action(editor, window, Editor::move_to_beginning); register_action(editor, window, Editor::move_to_end); + register_action(editor, window, Editor::move_to_start_of_excerpt); + register_action(editor, window, Editor::move_to_end_of_excerpt); register_action(editor, window, Editor::select_up); register_action(editor, window, Editor::select_down); register_action(editor, window, Editor::select_left); @@ -303,6 +305,8 @@ impl EditorElement { register_action(editor, window, Editor::select_to_end_of_line); register_action(editor, window, Editor::select_to_start_of_paragraph); register_action(editor, window, Editor::select_to_end_of_paragraph); + register_action(editor, window, Editor::select_to_start_of_excerpt); + register_action(editor, window, Editor::select_to_end_of_excerpt); register_action(editor, window, Editor::select_to_beginning); register_action(editor, window, Editor::select_to_end); register_action(editor, window, Editor::select_all); diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index d070feb262d5c5..fb4b6683c6b949 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -7,6 +7,7 @@ use gpui::{Pixels, WindowTextSystem}; use language::Point; use multi_buffer::{MultiBufferRow, MultiBufferSnapshot}; use serde::Deserialize; +use workspace::searchable::Direction; use std::{ops::Range, sync::Arc}; @@ -403,6 +404,69 @@ pub fn end_of_paragraph( map.max_point() } +pub fn start_of_excerpt( + map: &DisplaySnapshot, + display_point: DisplayPoint, + direction: Direction, +) -> DisplayPoint { + let point = map.display_point_to_point(display_point, Bias::Left); + let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else { + return display_point; + }; + match direction { + Direction::Prev => { + let mut start = excerpt.start_anchor().to_display_point(&map); + if start >= display_point && start.row() > DisplayRow(0) { + let Some(excerpt) = map.buffer_snapshot.excerpt_before(excerpt.id()) else { + return display_point; + }; + start = excerpt.start_anchor().to_display_point(&map); + } + start + } + Direction::Next => { + let mut end = excerpt.end_anchor().to_display_point(&map); + *end.row_mut() += 1; + map.clip_point(end, Bias::Right) + } + } +} + +pub fn end_of_excerpt( + map: &DisplaySnapshot, + display_point: DisplayPoint, + direction: Direction, +) -> DisplayPoint { + let point = map.display_point_to_point(display_point, Bias::Left); + let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else { + return display_point; + }; + match direction { + Direction::Prev => { + let mut start = excerpt.start_anchor().to_display_point(&map); + if start.row() > DisplayRow(0) { + *start.row_mut() -= 1; + } + map.clip_point(start, Bias::Left) + } + Direction::Next => { + let mut end = excerpt.end_anchor().to_display_point(&map); + *end.column_mut() = 0; + if end <= display_point { + *end.row_mut() += 1; + let point_end = map.display_point_to_point(end, Bias::Right); + let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point_end..point_end) + else { + return display_point; + }; + end = excerpt.end_anchor().to_display_point(&map); + *end.column_mut() = 0; + } + end + } + } +} + /// Scans for a boundary preceding the given start point `from` until a boundary is found, /// indicated by the given predicate returning true. /// The predicate is called with the character to the left and right of the candidate boundary location. diff --git a/crates/vim/src/motion.rs b/crates/vim/src/motion.rs index b8a375860a146b..89ad495fd78a3d 100644 --- a/crates/vim/src/motion.rs +++ b/crates/vim/src/motion.rs @@ -2699,49 +2699,10 @@ fn section_motion( }; for _ in 0..times { - let point = map.display_point_to_point(display_point, Bias::Left); - let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else { - return display_point; - }; - let next_point = match (direction, is_start) { - (Direction::Prev, true) => { - let mut start = excerpt.start_anchor().to_display_point(&map); - if start >= display_point && start.row() > DisplayRow(0) { - let Some(excerpt) = map.buffer_snapshot.excerpt_before(excerpt.id()) else { - return display_point; - }; - start = excerpt.start_anchor().to_display_point(&map); - } - start - } - (Direction::Prev, false) => { - let mut start = excerpt.start_anchor().to_display_point(&map); - if start.row() > DisplayRow(0) { - *start.row_mut() -= 1; - } - map.clip_point(start, Bias::Left) - } - (Direction::Next, true) => { - let mut end = excerpt.end_anchor().to_display_point(&map); - *end.row_mut() += 1; - map.clip_point(end, Bias::Right) - } - (Direction::Next, false) => { - let mut end = excerpt.end_anchor().to_display_point(&map); - *end.column_mut() = 0; - if end <= display_point { - *end.row_mut() += 1; - let point_end = map.display_point_to_point(end, Bias::Right); - let Some(excerpt) = - map.buffer_snapshot.excerpt_containing(point_end..point_end) - else { - return display_point; - }; - end = excerpt.end_anchor().to_display_point(&map); - *end.column_mut() = 0; - } - end - } + let next_point = if is_start { + movement::start_of_excerpt(map, display_point, direction) + } else { + movement::end_of_excerpt(map, display_point, direction) }; if next_point == display_point { break;