Merge pull request #1759 from zed-industries/move-page-up-down

Move page up / down
This commit is contained in:
Mikayla Maki
2022-10-14 14:02:27 -07:00
committed by GitHub
7 changed files with 404 additions and 40 deletions
+29 -2
View File
@@ -3,8 +3,12 @@
{ {
"bindings": { "bindings": {
"up": "menu::SelectPrev", "up": "menu::SelectPrev",
"pageup": "menu::SelectFirst",
"shift-pageup": "menu::SelectFirst",
"ctrl-p": "menu::SelectPrev", "ctrl-p": "menu::SelectPrev",
"down": "menu::SelectNext", "down": "menu::SelectNext",
"pagedown": "menu::SelectLast",
"shift-pagedown": "menu::SelectFirst",
"ctrl-n": "menu::SelectNext", "ctrl-n": "menu::SelectNext",
"cmd-up": "menu::SelectFirst", "cmd-up": "menu::SelectFirst",
"cmd-down": "menu::SelectLast", "cmd-down": "menu::SelectLast",
@@ -60,13 +64,18 @@
"cmd-z": "editor::Undo", "cmd-z": "editor::Undo",
"cmd-shift-z": "editor::Redo", "cmd-shift-z": "editor::Redo",
"up": "editor::MoveUp", "up": "editor::MoveUp",
"pageup": "editor::PageUp",
"shift-pageup": "editor::MovePageUp",
"down": "editor::MoveDown", "down": "editor::MoveDown",
"pagedown": "editor::PageDown",
"shift-pagedown": "editor::MovePageDown",
"left": "editor::MoveLeft", "left": "editor::MoveLeft",
"right": "editor::MoveRight", "right": "editor::MoveRight",
"ctrl-p": "editor::MoveUp", "ctrl-p": "editor::MoveUp",
"ctrl-n": "editor::MoveDown", "ctrl-n": "editor::MoveDown",
"ctrl-b": "editor::MoveLeft", "ctrl-b": "editor::MoveLeft",
"ctrl-f": "editor::MoveRight", "ctrl-f": "editor::MoveRight",
"ctrl-l": "editor::CenterScreen",
"alt-left": "editor::MoveToPreviousWordStart", "alt-left": "editor::MoveToPreviousWordStart",
"alt-b": "editor::MoveToPreviousWordStart", "alt-b": "editor::MoveToPreviousWordStart",
"alt-right": "editor::MoveToNextWordEnd", "alt-right": "editor::MoveToNextWordEnd",
@@ -118,8 +127,18 @@
"stop_at_soft_wraps": true "stop_at_soft_wraps": true
} }
], ],
"pageup": "editor::PageUp", "ctrl-v": [
"pagedown": "editor::PageDown", "editor::MovePageDown",
{
"center_cursor": true
}
],
"alt-v": [
"editor::MovePageUp",
{
"center_cursor": true
}
],
"ctrl-cmd-space": "editor::ShowCharacterPalette" "ctrl-cmd-space": "editor::ShowCharacterPalette"
} }
}, },
@@ -451,10 +470,18 @@
"terminal::SendKeystroke", "terminal::SendKeystroke",
"up" "up"
], ],
"pageup": [
"terminal::SendKeystroke",
"pageup"
],
"down": [ "down": [
"terminal::SendKeystroke", "terminal::SendKeystroke",
"down" "down"
], ],
"pagedown": [
"terminal::SendKeystroke",
"pagedown"
],
"escape": [ "escape": [
"terminal::SendKeystroke", "terminal::SendKeystroke",
"escape" "escape"
+217 -26
View File
@@ -108,6 +108,18 @@ pub struct SelectToBeginningOfLine {
stop_at_soft_wraps: bool, stop_at_soft_wraps: bool,
} }
#[derive(Clone, Default, Deserialize, PartialEq)]
pub struct MovePageUp {
#[serde(default)]
center_cursor: bool,
}
#[derive(Clone, Default, Deserialize, PartialEq)]
pub struct MovePageDown {
#[serde(default)]
center_cursor: bool,
}
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq)]
pub struct SelectToEndOfLine { pub struct SelectToEndOfLine {
#[serde(default)] #[serde(default)]
@@ -161,8 +173,11 @@ actions!(
Paste, Paste,
Undo, Undo,
Redo, Redo,
CenterScreen,
MoveUp, MoveUp,
PageUp,
MoveDown, MoveDown,
PageDown,
MoveLeft, MoveLeft,
MoveRight, MoveRight,
MoveToPreviousWordStart, MoveToPreviousWordStart,
@@ -202,8 +217,6 @@ actions!(
FindAllReferences, FindAllReferences,
Rename, Rename,
ConfirmRename, ConfirmRename,
PageUp,
PageDown,
Fold, Fold,
UnfoldLines, UnfoldLines,
FoldSelectedRanges, FoldSelectedRanges,
@@ -222,6 +235,8 @@ impl_actions!(
SelectToBeginningOfLine, SelectToBeginningOfLine,
SelectToEndOfLine, SelectToEndOfLine,
ToggleCodeActions, ToggleCodeActions,
MovePageUp,
MovePageDown,
ConfirmCompletion, ConfirmCompletion,
ConfirmCodeAction, ConfirmCodeAction,
] ]
@@ -273,7 +288,12 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::undo); cx.add_action(Editor::undo);
cx.add_action(Editor::redo); cx.add_action(Editor::redo);
cx.add_action(Editor::move_up); cx.add_action(Editor::move_up);
cx.add_action(Editor::move_page_up);
cx.add_action(Editor::page_up);
cx.add_action(Editor::move_down); cx.add_action(Editor::move_down);
cx.add_action(Editor::move_page_down);
cx.add_action(Editor::page_down);
cx.add_action(Editor::center_screen);
cx.add_action(Editor::move_left); cx.add_action(Editor::move_left);
cx.add_action(Editor::move_right); cx.add_action(Editor::move_right);
cx.add_action(Editor::move_to_previous_word_start); cx.add_action(Editor::move_to_previous_word_start);
@@ -312,8 +332,6 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Editor::go_to_prev_diagnostic); cx.add_action(Editor::go_to_prev_diagnostic);
cx.add_action(Editor::go_to_definition); cx.add_action(Editor::go_to_definition);
cx.add_action(Editor::go_to_type_definition); cx.add_action(Editor::go_to_type_definition);
cx.add_action(Editor::page_up);
cx.add_action(Editor::page_down);
cx.add_action(Editor::fold); cx.add_action(Editor::fold);
cx.add_action(Editor::unfold_lines); cx.add_action(Editor::unfold_lines);
cx.add_action(Editor::fold_selected_ranges); cx.add_action(Editor::fold_selected_ranges);
@@ -606,6 +624,18 @@ enum ContextMenu {
} }
impl ContextMenu { impl ContextMenu {
fn select_first(&mut self, cx: &mut ViewContext<Editor>) -> bool {
if self.visible() {
match self {
ContextMenu::Completions(menu) => menu.select_first(cx),
ContextMenu::CodeActions(menu) => menu.select_first(cx),
}
true
} else {
false
}
}
fn select_prev(&mut self, cx: &mut ViewContext<Editor>) -> bool { fn select_prev(&mut self, cx: &mut ViewContext<Editor>) -> bool {
if self.visible() { if self.visible() {
match self { match self {
@@ -630,6 +660,18 @@ impl ContextMenu {
} }
} }
fn select_last(&mut self, cx: &mut ViewContext<Editor>) -> bool {
if self.visible() {
match self {
ContextMenu::Completions(menu) => menu.select_last(cx),
ContextMenu::CodeActions(menu) => menu.select_last(cx),
}
true
} else {
false
}
}
fn visible(&self) -> bool { fn visible(&self) -> bool {
match self { match self {
ContextMenu::Completions(menu) => menu.visible(), ContextMenu::Completions(menu) => menu.visible(),
@@ -662,6 +704,12 @@ struct CompletionsMenu {
} }
impl CompletionsMenu { impl CompletionsMenu {
fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
self.selected_item = 0;
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
cx.notify();
}
fn select_prev(&mut self, cx: &mut ViewContext<Editor>) { fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
if self.selected_item > 0 { if self.selected_item > 0 {
self.selected_item -= 1; self.selected_item -= 1;
@@ -678,6 +726,12 @@ impl CompletionsMenu {
cx.notify(); cx.notify();
} }
fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
self.selected_item = self.matches.len() - 1;
self.list.scroll_to(ScrollTarget::Show(self.selected_item));
cx.notify();
}
fn visible(&self) -> bool { fn visible(&self) -> bool {
!self.matches.is_empty() !self.matches.is_empty()
} }
@@ -809,6 +863,11 @@ struct CodeActionsMenu {
} }
impl CodeActionsMenu { impl CodeActionsMenu {
fn select_first(&mut self, cx: &mut ViewContext<Editor>) {
self.selected_item = 0;
cx.notify()
}
fn select_prev(&mut self, cx: &mut ViewContext<Editor>) { fn select_prev(&mut self, cx: &mut ViewContext<Editor>) {
if self.selected_item > 0 { if self.selected_item > 0 {
self.selected_item -= 1; self.selected_item -= 1;
@@ -823,6 +882,11 @@ impl CodeActionsMenu {
} }
} }
fn select_last(&mut self, cx: &mut ViewContext<Editor>) {
self.selected_item = self.actions.len() - 1;
cx.notify()
}
fn visible(&self) -> bool { fn visible(&self) -> bool {
!self.actions.is_empty() !self.actions.is_empty()
} }
@@ -3849,6 +3913,23 @@ impl Editor {
}) })
} }
pub fn center_screen(&mut self, _: &CenterScreen, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() {
return;
}
if let Some(_) = self.context_menu.as_mut() {
return;
}
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
self.request_autoscroll(Autoscroll::Center, cx);
}
pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) { pub fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() { if self.take_rename(true, cx).is_some() {
return; return;
@@ -3877,6 +3958,72 @@ impl Editor {
}) })
} }
pub fn move_page_up(&mut self, action: &MovePageUp, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() {
return;
}
if let Some(context_menu) = self.context_menu.as_mut() {
if context_menu.select_first(cx) {
return;
}
}
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
let row_count = match self.visible_line_count {
Some(row_count) => row_count as u32 - 1,
None => return,
};
let autoscroll = if action.center_cursor {
Autoscroll::Center
} else {
Autoscroll::Fit
};
self.change_selections(Some(autoscroll), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if !selection.is_empty() && !line_mode {
selection.goal = SelectionGoal::None;
}
let (cursor, goal) =
movement::up_by_rows(map, selection.end, row_count, selection.goal, false);
selection.collapse_to(cursor, goal);
});
});
}
pub fn page_up(&mut self, _: &PageUp, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() {
return;
}
if let Some(context_menu) = self.context_menu.as_mut() {
if context_menu.select_first(cx) {
return;
}
}
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
let lines = match self.visible_line_count {
Some(lines) => lines,
None => return,
};
let cur_position = self.scroll_position(cx);
let new_pos = cur_position - vec2f(0., lines + 1.);
self.set_scroll_position(new_pos, cx);
}
pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) { pub fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
self.change_selections(Some(Autoscroll::Fit), cx, |s| { self.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.move_heads_with(|map, head, goal| movement::up(map, head, goal, false)) s.move_heads_with(|map, head, goal| movement::up(map, head, goal, false))
@@ -3909,6 +4056,72 @@ impl Editor {
}); });
} }
pub fn move_page_down(&mut self, action: &MovePageDown, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() {
return;
}
if let Some(context_menu) = self.context_menu.as_mut() {
if context_menu.select_last(cx) {
return;
}
}
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
let row_count = match self.visible_line_count {
Some(row_count) => row_count as u32 - 1,
None => return,
};
let autoscroll = if action.center_cursor {
Autoscroll::Center
} else {
Autoscroll::Fit
};
self.change_selections(Some(autoscroll), cx, |s| {
let line_mode = s.line_mode;
s.move_with(|map, selection| {
if !selection.is_empty() && !line_mode {
selection.goal = SelectionGoal::None;
}
let (cursor, goal) =
movement::down_by_rows(map, selection.end, row_count, selection.goal, false);
selection.collapse_to(cursor, goal);
});
});
}
pub fn page_down(&mut self, _: &PageDown, cx: &mut ViewContext<Self>) {
if self.take_rename(true, cx).is_some() {
return;
}
if let Some(context_menu) = self.context_menu.as_mut() {
if context_menu.select_last(cx) {
return;
}
}
if matches!(self.mode, EditorMode::SingleLine) {
cx.propagate_action();
return;
}
let lines = match self.visible_line_count {
Some(lines) => lines,
None => return,
};
let cur_position = self.scroll_position(cx);
let new_pos = cur_position + vec2f(0., lines - 1.);
self.set_scroll_position(new_pos, cx);
}
pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) { pub fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
self.change_selections(Some(Autoscroll::Fit), cx, |s| { self.change_selections(Some(Autoscroll::Fit), cx, |s| {
s.move_heads_with(|map, head, goal| movement::down(map, head, goal, false)) s.move_heads_with(|map, head, goal| movement::down(map, head, goal, false))
@@ -5536,28 +5749,6 @@ impl Editor {
} }
} }
pub fn page_up(&mut self, _: &PageUp, cx: &mut ViewContext<Self>) {
let lines = match self.visible_line_count {
Some(lines) => lines,
None => return,
};
let cur_position = self.scroll_position(cx);
let new_pos = cur_position - vec2f(0., lines + 1.);
self.set_scroll_position(new_pos, cx);
}
pub fn page_down(&mut self, _: &PageDown, cx: &mut ViewContext<Self>) {
let lines = match self.visible_line_count {
Some(lines) => lines,
None => return,
};
let cur_position = self.scroll_position(cx);
let new_pos = cur_position + vec2f(0., lines - 1.);
self.set_scroll_position(new_pos, cx);
}
pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) { pub fn fold(&mut self, _: &Fold, cx: &mut ViewContext<Self>) {
let mut fold_ranges = Vec::new(); let mut fold_ranges = Vec::new();
+114
View File
@@ -1194,6 +1194,120 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut gpui::MutableAppContext) {
}); });
} }
#[gpui::test]
async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx);
let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
cx.simulate_window_resize(cx.window_id, vec2f(100., 4. * line_height));
cx.set_state(
&r#"
ˇone
two
threeˇ
four
five
six
seven
eight
nine
ten
"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
cx.assert_editor_state(
&r#"
one
two
three
ˇfour
five
sixˇ
seven
eight
nine
ten
"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_page_down(&MovePageDown::default(), cx));
cx.assert_editor_state(
&r#"
one
two
three
four
five
six
ˇseven
eight
nineˇ
ten
"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
cx.assert_editor_state(
&r#"
one
two
three
ˇfour
five
sixˇ
seven
eight
nine
ten
"#
.unindent(),
);
cx.update_editor(|editor, cx| editor.move_page_up(&MovePageUp::default(), cx));
cx.assert_editor_state(
&r#"
ˇone
two
threeˇ
four
five
six
seven
eight
nine
ten
"#
.unindent(),
);
// Test select collapsing
cx.update_editor(|editor, cx| {
editor.move_page_down(&MovePageDown::default(), cx);
editor.move_page_down(&MovePageDown::default(), cx);
editor.move_page_down(&MovePageDown::default(), cx);
});
cx.assert_editor_state(
&r#"
one
two
three
four
five
six
seven
eight
nine
ˇten
ˇ"#
.unindent(),
);
}
#[gpui::test] #[gpui::test]
async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
let mut cx = EditorTestContext::new(cx); let mut cx = EditorTestContext::new(cx);
+24 -4
View File
@@ -29,6 +29,25 @@ pub fn up(
start: DisplayPoint, start: DisplayPoint,
goal: SelectionGoal, goal: SelectionGoal,
preserve_column_at_start: bool, preserve_column_at_start: bool,
) -> (DisplayPoint, SelectionGoal) {
up_by_rows(map, start, 1, goal, preserve_column_at_start)
}
pub fn down(
map: &DisplaySnapshot,
start: DisplayPoint,
goal: SelectionGoal,
preserve_column_at_end: bool,
) -> (DisplayPoint, SelectionGoal) {
down_by_rows(map, start, 1, goal, preserve_column_at_end)
}
pub fn up_by_rows(
map: &DisplaySnapshot,
start: DisplayPoint,
row_count: u32,
goal: SelectionGoal,
preserve_column_at_start: bool,
) -> (DisplayPoint, SelectionGoal) { ) -> (DisplayPoint, SelectionGoal) {
let mut goal_column = if let SelectionGoal::Column(column) = goal { let mut goal_column = if let SelectionGoal::Column(column) = goal {
column column
@@ -36,7 +55,7 @@ pub fn up(
map.column_to_chars(start.row(), start.column()) map.column_to_chars(start.row(), start.column())
}; };
let prev_row = start.row().saturating_sub(1); let prev_row = start.row().saturating_sub(row_count);
let mut point = map.clip_point( let mut point = map.clip_point(
DisplayPoint::new(prev_row, map.line_len(prev_row)), DisplayPoint::new(prev_row, map.line_len(prev_row)),
Bias::Left, Bias::Left,
@@ -62,9 +81,10 @@ pub fn up(
) )
} }
pub fn down( pub fn down_by_rows(
map: &DisplaySnapshot, map: &DisplaySnapshot,
start: DisplayPoint, start: DisplayPoint,
row_count: u32,
goal: SelectionGoal, goal: SelectionGoal,
preserve_column_at_end: bool, preserve_column_at_end: bool,
) -> (DisplayPoint, SelectionGoal) { ) -> (DisplayPoint, SelectionGoal) {
@@ -74,8 +94,8 @@ pub fn down(
map.column_to_chars(start.row(), start.column()) map.column_to_chars(start.row(), start.column())
}; };
let next_row = start.row() + 1; let new_row = start.row() + row_count;
let mut point = map.clip_point(DisplayPoint::new(next_row, 0), Bias::Right); let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right);
if point.row() > start.row() { if point.row() > start.row() {
*point.column_mut() = map.column_from_chars(point.row(), goal_column); *point.column_mut() = map.column_from_chars(point.row(), goal_column);
} else if preserve_column_at_end { } else if preserve_column_at_end {
+16 -4
View File
@@ -17,10 +17,11 @@ use parking_lot::{Mutex, RwLock};
use smol::stream::StreamExt; use smol::stream::StreamExt;
use crate::{ use crate::{
executor, keymap::Keystroke, platform, Action, AnyViewHandle, AppContext, Appearance, Entity, executor, geometry::vector::Vector2F, keymap::Keystroke, platform, Action, AnyViewHandle,
Event, FontCache, InputHandler, KeyDownEvent, LeakDetector, ModelContext, ModelHandle, AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent, LeakDetector,
MutableAppContext, Platform, ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, ReadViewWith,
UpdateView, View, ViewContext, ViewHandle, WeakHandle, WindowInputHandler, RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, WeakHandle,
WindowInputHandler,
}; };
use collections::BTreeMap; use collections::BTreeMap;
@@ -275,6 +276,17 @@ impl TestAppContext {
} }
} }
pub fn simulate_window_resize(&self, window_id: usize, size: Vector2F) {
let mut window = self.window_mut(window_id);
window.size = size;
let mut handlers = mem::take(&mut window.resize_handlers);
drop(window);
for handler in &mut handlers {
handler();
}
self.window_mut(window_id).resize_handlers = handlers;
}
pub fn simulate_window_activation(&self, to_activate: Option<usize>) { pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
let mut handlers = BTreeMap::new(); let mut handlers = BTreeMap::new();
{ {
+2 -2
View File
@@ -34,11 +34,11 @@ pub struct ForegroundPlatform {
struct Dispatcher; struct Dispatcher;
pub struct Window { pub struct Window {
size: Vector2F, pub(crate) size: Vector2F,
scale_factor: f32, scale_factor: f32,
current_scene: Option<crate::Scene>, current_scene: Option<crate::Scene>,
event_handlers: Vec<Box<dyn FnMut(super::Event) -> bool>>, event_handlers: Vec<Box<dyn FnMut(super::Event) -> bool>>,
resize_handlers: Vec<Box<dyn FnMut()>>, pub(crate) resize_handlers: Vec<Box<dyn FnMut()>>,
close_handlers: Vec<Box<dyn FnOnce()>>, close_handlers: Vec<Box<dyn FnOnce()>>,
fullscreen_handlers: Vec<Box<dyn FnMut(bool)>>, fullscreen_handlers: Vec<Box<dyn FnMut(bool)>>,
pub(crate) active_status_change_handlers: Vec<Box<dyn FnMut(bool)>>, pub(crate) active_status_change_handlers: Vec<Box<dyn FnMut(bool)>>,
+2 -2
View File
@@ -142,8 +142,8 @@ impl TerminalView {
pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) { pub fn deploy_context_menu(&mut self, action: &DeployContextMenu, cx: &mut ViewContext<Self>) {
let menu_entries = vec![ let menu_entries = vec![
ContextMenuItem::item("Clear Buffer", Clear), ContextMenuItem::item("Clear", Clear),
ContextMenuItem::item("Close Terminal", pane::CloseActiveItem), ContextMenuItem::item("Close", pane::CloseActiveItem),
]; ];
self.context_menu.update(cx, |menu, cx| { self.context_menu.update(cx, |menu, cx| {