Add editor actions for moving and selecting to next / previous excerpt (#25299)
Covers part of #5129 by adding `MoveToStartOfExcerpt`, `MoveToEndOfExcerpt`, `SelectToStartOfExcerpt`, and `SelectToEndOfExcerpt`. No default linux bindings yet as it's unclear what to use. Currently, `ctrl-up` / `ctrl-down` scroll up and down by one line (see #13269). Considering changing the meaning of those. Mac: * Previously `cmd-up` and `cmd-down` were `editor::MoveToBeginning` and `editor::MoveToEnd`. In singleton editors these will behave the same as before. In multibuffers, they will now step through excerpts instead of jumping to the beginning / end of the multibuffer. * `cmd-home` and `cmd-end`, often typed as `cmd-fn-left` and `cmd-fn-right` are now `editor::MoveToBeginning` and `editor::MoveToEnd`. This is useful in multibuffers. Release Notes: - Mac: `cmd-up` now moves to the previous multibuffer excerpt start, and `cmd-down` moves to the next multibuffer excerpt end. Within normal buffers these behave the same as before, moving to the beginning or end.
This commit is contained in:
parent
ec00fb97fd
commit
30850fe3bd
6 changed files with 174 additions and 47 deletions
|
@ -97,8 +97,10 @@
|
||||||
"cmd-right": "editor::MoveToEndOfLine",
|
"cmd-right": "editor::MoveToEndOfLine",
|
||||||
"ctrl-e": "editor::MoveToEndOfLine",
|
"ctrl-e": "editor::MoveToEndOfLine",
|
||||||
"end": "editor::MoveToEndOfLine",
|
"end": "editor::MoveToEndOfLine",
|
||||||
"cmd-up": "editor::MoveToBeginning",
|
"cmd-up": "editor::MoveToStartOfExcerpt",
|
||||||
"cmd-down": "editor::MoveToEnd",
|
"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",
|
"shift-up": "editor::SelectUp",
|
||||||
"ctrl-shift-p": "editor::SelectUp",
|
"ctrl-shift-p": "editor::SelectUp",
|
||||||
"shift-down": "editor::SelectDown",
|
"shift-down": "editor::SelectDown",
|
||||||
|
@ -111,8 +113,8 @@
|
||||||
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
"alt-shift-right": "editor::SelectToNextWordEnd", // cursorWordRightSelect
|
||||||
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
"ctrl-shift-up": "editor::SelectToStartOfParagraph",
|
||||||
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
"ctrl-shift-down": "editor::SelectToEndOfParagraph",
|
||||||
"cmd-shift-up": "editor::SelectToBeginning",
|
"cmd-shift-up": "editor::SelectToStartOfExcerpt",
|
||||||
"cmd-shift-down": "editor::SelectToEnd",
|
"cmd-shift-down": "editor::SelectToEndOfExcerpt",
|
||||||
"cmd-a": "editor::SelectAll",
|
"cmd-a": "editor::SelectAll",
|
||||||
"cmd-l": "editor::SelectLine",
|
"cmd-l": "editor::SelectLine",
|
||||||
"cmd-shift-i": "editor::Format",
|
"cmd-shift-i": "editor::Format",
|
||||||
|
|
|
@ -329,6 +329,8 @@ gpui::actions!(
|
||||||
MoveToPreviousSubwordStart,
|
MoveToPreviousSubwordStart,
|
||||||
MoveToPreviousWordStart,
|
MoveToPreviousWordStart,
|
||||||
MoveToStartOfParagraph,
|
MoveToStartOfParagraph,
|
||||||
|
MoveToStartOfExcerpt,
|
||||||
|
MoveToEndOfExcerpt,
|
||||||
MoveUp,
|
MoveUp,
|
||||||
Newline,
|
Newline,
|
||||||
NewlineAbove,
|
NewlineAbove,
|
||||||
|
@ -364,6 +366,8 @@ gpui::actions!(
|
||||||
ScrollCursorTop,
|
ScrollCursorTop,
|
||||||
SelectAll,
|
SelectAll,
|
||||||
SelectAllMatches,
|
SelectAllMatches,
|
||||||
|
SelectToStartOfExcerpt,
|
||||||
|
SelectToEndOfExcerpt,
|
||||||
SelectDown,
|
SelectDown,
|
||||||
SelectEnclosingSymbol,
|
SelectEnclosingSymbol,
|
||||||
SelectLargerSyntaxNode,
|
SelectLargerSyntaxNode,
|
||||||
|
|
|
@ -9033,6 +9033,98 @@ impl Editor {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn move_to_start_of_excerpt(
|
||||||
|
&mut self,
|
||||||
|
_: &MoveToStartOfExcerpt,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
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<Self>,
|
||||||
|
) {
|
||||||
|
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<Self>,
|
||||||
|
) {
|
||||||
|
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<Self>,
|
||||||
|
) {
|
||||||
|
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(
|
pub fn move_to_beginning(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &MoveToBeginning,
|
_: &MoveToBeginning,
|
||||||
|
|
|
@ -290,6 +290,8 @@ impl EditorElement {
|
||||||
register_action(editor, window, Editor::move_to_end_of_paragraph);
|
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_beginning);
|
||||||
register_action(editor, window, Editor::move_to_end);
|
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_up);
|
||||||
register_action(editor, window, Editor::select_down);
|
register_action(editor, window, Editor::select_down);
|
||||||
register_action(editor, window, Editor::select_left);
|
register_action(editor, window, Editor::select_left);
|
||||||
|
@ -302,6 +304,8 @@ impl EditorElement {
|
||||||
register_action(editor, window, Editor::select_to_end_of_line);
|
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_start_of_paragraph);
|
||||||
register_action(editor, window, Editor::select_to_end_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_beginning);
|
||||||
register_action(editor, window, Editor::select_to_end);
|
register_action(editor, window, Editor::select_to_end);
|
||||||
register_action(editor, window, Editor::select_all);
|
register_action(editor, window, Editor::select_all);
|
||||||
|
|
|
@ -7,6 +7,7 @@ use gpui::{Pixels, WindowTextSystem};
|
||||||
use language::Point;
|
use language::Point;
|
||||||
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
|
use multi_buffer::{MultiBufferRow, MultiBufferSnapshot};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use workspace::searchable::Direction;
|
||||||
|
|
||||||
use std::{ops::Range, sync::Arc};
|
use std::{ops::Range, sync::Arc};
|
||||||
|
|
||||||
|
@ -403,6 +404,69 @@ pub fn end_of_paragraph(
|
||||||
map.max_point()
|
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,
|
/// Scans for a boundary preceding the given start point `from` until a boundary is found,
|
||||||
/// indicated by the given predicate returning true.
|
/// indicated by the given predicate returning true.
|
||||||
/// The predicate is called with the character to the left and right of the candidate boundary location.
|
/// The predicate is called with the character to the left and right of the candidate boundary location.
|
||||||
|
|
|
@ -2699,49 +2699,10 @@ fn section_motion(
|
||||||
};
|
};
|
||||||
|
|
||||||
for _ in 0..times {
|
for _ in 0..times {
|
||||||
let point = map.display_point_to_point(display_point, Bias::Left);
|
let next_point = if is_start {
|
||||||
let Some(excerpt) = map.buffer_snapshot.excerpt_containing(point..point) else {
|
movement::start_of_excerpt(map, display_point, direction)
|
||||||
return display_point;
|
} else {
|
||||||
};
|
movement::end_of_excerpt(map, display_point, direction)
|
||||||
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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if next_point == display_point {
|
if next_point == display_point {
|
||||||
break;
|
break;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue