diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 014787b503..d453d26d0e 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -108,6 +108,18 @@ pub struct SelectToBeginningOfLine { 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)] pub struct SelectToEndOfLine { #[serde(default)] @@ -222,6 +234,8 @@ impl_actions!( SelectToBeginningOfLine, SelectToEndOfLine, ToggleCodeActions, + MovePageUp, + MovePageDown, ConfirmCompletion, ConfirmCodeAction, ] @@ -5536,6 +5550,49 @@ impl Editor { } } + // Vscode style + emacs style + pub fn move_page_down(&mut self, _: &MovePageDown, cx: &mut ViewContext) { + let row_count = match self.visible_line_count { + Some(row_count) => row_count as u32 - 1, + None => return, + }; + + self.change_selections(Some(Autoscroll::Fit), 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); + eprintln!( + "{:?} down by rows {} = {:?}", + selection.end, row_count, cursor + ); + selection.collapse_to(cursor, goal); + }); + }); + } + + pub fn move_page_up(&mut self, _: &MovePageUp, cx: &mut ViewContext) { + let row_count = match self.visible_line_count { + Some(row_count) => row_count as u32 - 1, + None => return, + }; + + self.change_selections(Some(Autoscroll::Fit), 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) { let lines = match self.visible_line_count { Some(lines) => lines, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 5ad39d2bbd..46fbfa4fb3 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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 + tenx + "# + .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] async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) { let mut cx = EditorTestContext::new(cx); diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 40908d2bb3..96b2065823 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -29,6 +29,25 @@ pub fn up( start: DisplayPoint, goal: SelectionGoal, 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) { let mut goal_column = if let SelectionGoal::Column(column) = goal { column @@ -36,7 +55,7 @@ pub fn up( 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( DisplayPoint::new(prev_row, map.line_len(prev_row)), Bias::Left, @@ -62,9 +81,10 @@ pub fn up( ) } -pub fn down( +pub fn down_by_rows( map: &DisplaySnapshot, start: DisplayPoint, + row_count: u32, goal: SelectionGoal, preserve_column_at_end: bool, ) -> (DisplayPoint, SelectionGoal) { @@ -74,8 +94,8 @@ pub fn down( map.column_to_chars(start.row(), start.column()) }; - let next_row = start.row() + 1; - let mut point = map.clip_point(DisplayPoint::new(next_row, 0), Bias::Right); + let new_row = start.row() + row_count; + let mut point = map.clip_point(DisplayPoint::new(new_row, 0), Bias::Right); if point.row() > start.row() { *point.column_mut() = map.column_from_chars(point.row(), goal_column); } else if preserve_column_at_end { diff --git a/crates/gpui/src/app/test_app_context.rs b/crates/gpui/src/app/test_app_context.rs index 477c316f71..72f1f546fb 100644 --- a/crates/gpui/src/app/test_app_context.rs +++ b/crates/gpui/src/app/test_app_context.rs @@ -17,10 +17,11 @@ use parking_lot::{Mutex, RwLock}; use smol::stream::StreamExt; use crate::{ - executor, keymap::Keystroke, platform, Action, AnyViewHandle, AppContext, Appearance, Entity, - Event, FontCache, InputHandler, KeyDownEvent, LeakDetector, ModelContext, ModelHandle, - MutableAppContext, Platform, ReadModelWith, ReadViewWith, RenderContext, Task, UpdateModel, - UpdateView, View, ViewContext, ViewHandle, WeakHandle, WindowInputHandler, + executor, geometry::vector::Vector2F, keymap::Keystroke, platform, Action, AnyViewHandle, + AppContext, Appearance, Entity, Event, FontCache, InputHandler, KeyDownEvent, LeakDetector, + ModelContext, ModelHandle, MutableAppContext, Platform, ReadModelWith, ReadViewWith, + RenderContext, Task, UpdateModel, UpdateView, View, ViewContext, ViewHandle, WeakHandle, + WindowInputHandler, }; 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) { let mut handlers = BTreeMap::new(); { diff --git a/crates/gpui/src/platform/test.rs b/crates/gpui/src/platform/test.rs index 3c2e23bbd3..2a44616cdd 100644 --- a/crates/gpui/src/platform/test.rs +++ b/crates/gpui/src/platform/test.rs @@ -34,11 +34,11 @@ pub struct ForegroundPlatform { struct Dispatcher; pub struct Window { - size: Vector2F, + pub(crate) size: Vector2F, scale_factor: f32, current_scene: Option, event_handlers: Vec bool>>, - resize_handlers: Vec>, + pub(crate) resize_handlers: Vec>, close_handlers: Vec>, fullscreen_handlers: Vec>, pub(crate) active_status_change_handlers: Vec>,