Detailed changes
@@ -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<Self>) {
+ 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<Self>) {
+ 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<Self>) {
let lines = match self.visible_line_count {
Some(lines) => lines,
@@ -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);
@@ -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 {
@@ -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<usize>) {
let mut handlers = BTreeMap::new();
{
@@ -34,11 +34,11 @@ pub struct ForegroundPlatform {
struct Dispatcher;
pub struct Window {
- size: Vector2F,
+ pub(crate) size: Vector2F,
scale_factor: f32,
current_scene: Option<crate::Scene>,
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()>>,
fullscreen_handlers: Vec<Box<dyn FnMut(bool)>>,
pub(crate) active_status_change_handlers: Vec<Box<dyn FnMut(bool)>>,