Fix ctrl-d/u issues with scroll_beyond_last_line off (#15395)

Closes #15356

Release Notes:

- vim: Fixed issues with `ctrl-d`/`ctrl-u` when
`scroll_beyond_last_line` is set to `off`
([#15356](https://github.com/zed-industries/zed/issues/15356)).


https://github.com/user-attachments/assets/d3166393-4a4e-4195-9db6-3ff1d4aeec78

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
This commit is contained in:
Kevin Sweet 2024-08-23 08:34:40 -06:00 committed by GitHub
parent 518dd3ed3a
commit 5e869dadf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 25 deletions

View File

@ -59,7 +59,7 @@ use convert_case::{Case, Casing};
use debounced_delay::DebouncedDelay; use debounced_delay::DebouncedDelay;
use display_map::*; use display_map::*;
pub use display_map::{DisplayPoint, FoldPlaceholder}; pub use display_map::{DisplayPoint, FoldPlaceholder};
pub use editor_settings::{CurrentLineHighlight, EditorSettings}; pub use editor_settings::{CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine};
pub use editor_settings_controls::*; pub use editor_settings_controls::*;
use element::LineWithInvisibles; use element::LineWithInvisibles;
pub use element::{ pub use element::{

View File

@ -505,7 +505,7 @@ impl Editor {
} }
if let Some(visible_lines) = self.visible_line_count() { if let Some(visible_lines) = self.visible_line_count() {
if newest_head.row() < DisplayRow(screen_top.row().0 + visible_lines as u32) { if newest_head.row() <= DisplayRow(screen_top.row().0 + visible_lines as u32) {
return Ordering::Equal; return Ordering::Equal;
} }
} }

View File

@ -91,6 +91,7 @@ fn scroll_editor(
s.move_with(|map, selection| { s.move_with(|map, selection| {
let mut head = selection.head(); let mut head = selection.head();
let top = top_anchor.to_display_point(map); let top = top_anchor.to_display_point(map);
let starting_column = head.column();
let vertical_scroll_margin = let vertical_scroll_margin =
(vertical_scroll_margin as u32).min(visible_line_count as u32 / 2); (vertical_scroll_margin as u32).min(visible_line_count as u32 / 2);
@ -99,7 +100,7 @@ fn scroll_editor(
let old_top = old_top_anchor.to_display_point(map); let old_top = old_top_anchor.to_display_point(map);
let new_row = if old_top.row() == top.row() { let new_row = if old_top.row() == top.row() {
DisplayRow( DisplayRow(
top.row() head.row()
.0 .0
.saturating_add_signed(amount.lines(visible_line_count) as i32), .saturating_add_signed(amount.lines(visible_line_count) as i32),
) )
@ -108,25 +109,25 @@ fn scroll_editor(
}; };
head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left) head = map.clip_point(DisplayPoint::new(new_row, head.column()), Bias::Left)
} }
let min_row = if top.row().0 == 0 { let min_row = if top.row().0 == 0 {
DisplayRow(0) DisplayRow(0)
} else { } else {
DisplayRow(top.row().0 + vertical_scroll_margin) DisplayRow(top.row().0 + vertical_scroll_margin)
}; };
let max_row = DisplayRow( let max_row = DisplayRow(map.max_point().row().0.max(top.row().0.saturating_add(
top.row().0 (visible_line_count as u32).saturating_sub(1 + vertical_scroll_margin),
+ (visible_line_count as u32) )));
.saturating_sub(vertical_scroll_margin)
.saturating_sub(1),
);
let new_head = if head.row() < min_row { let new_row = if head.row() < min_row {
map.clip_point(DisplayPoint::new(min_row, head.column()), Bias::Left) min_row
} else if head.row() > max_row { } else if head.row() > max_row {
map.clip_point(DisplayPoint::new(max_row, head.column()), Bias::Left) max_row
} else { } else {
head head.row()
}; };
let new_head = map.clip_point(DisplayPoint::new(new_row, starting_column), Bias::Left);
if selection.is_empty() { if selection.is_empty() {
selection.collapse_to(new_head, selection.goal) selection.collapse_to(new_head, selection.goal)
} else { } else {
@ -142,9 +143,24 @@ mod test {
state::Mode, state::Mode,
test::{NeovimBackedTestContext, VimTestContext}, test::{NeovimBackedTestContext, VimTestContext},
}; };
use editor::{EditorSettings, ScrollBeyondLastLine};
use gpui::{point, px, size, Context}; use gpui::{point, px, size, Context};
use indoc::indoc; use indoc::indoc;
use language::Point; use language::Point;
use settings::SettingsStore;
pub fn sample_text(rows: usize, cols: usize, start_char: char) -> String {
let mut text = String::new();
for row in 0..rows {
let c: char = (start_char as u32 + row as u32) as u8 as char;
let mut line = c.to_string().repeat(cols);
if row < rows - 1 {
line.push('\n');
}
text += &line;
}
text
}
#[gpui::test] #[gpui::test]
async fn test_scroll(cx: &mut gpui::TestAppContext) { async fn test_scroll(cx: &mut gpui::TestAppContext) {
@ -241,18 +257,6 @@ mod test {
cx.set_scroll_height(10).await; cx.set_scroll_height(10).await;
pub fn sample_text(rows: usize, cols: usize, start_char: char) -> String {
let mut text = String::new();
for row in 0..rows {
let c: char = (start_char as u32 + row as u32) as u8 as char;
let mut line = c.to_string().repeat(cols);
if row < rows - 1 {
line.push('\n');
}
text += &line;
}
text
}
let content = "ˇ".to_owned() + &sample_text(26, 2, 'a'); let content = "ˇ".to_owned() + &sample_text(26, 2, 'a');
cx.set_shared_state(&content).await; cx.set_shared_state(&content).await;
@ -277,4 +281,33 @@ mod test {
.await; .await;
cx.shared_state().await.assert_matches(); cx.shared_state().await.assert_matches();
} }
#[gpui::test]
async fn test_scroll_beyond_last_line(cx: &mut gpui::TestAppContext) {
let mut cx = NeovimBackedTestContext::new(cx).await;
cx.set_scroll_height(10).await;
cx.neovim.set_option(&format!("scrolloff={}", 0)).await;
let content = "ˇ".to_owned() + &sample_text(26, 2, 'a');
cx.set_shared_state(&content).await;
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<EditorSettings>(cx, |s| {
s.scroll_beyond_last_line = Some(ScrollBeyondLastLine::Off)
});
});
// ctrl-d can reach the end and the cursor stays in the first column
cx.simulate_shared_keystrokes("shift-g k").await;
cx.shared_state().await.assert_matches();
cx.simulate_shared_keystrokes("ctrl-d").await;
cx.shared_state().await.assert_matches();
// ctrl-u from the last line
cx.simulate_shared_keystrokes("shift-g").await;
cx.shared_state().await.assert_matches();
cx.simulate_shared_keystrokes("ctrl-u").await;
cx.shared_state().await.assert_matches();
}
} }

View File

@ -0,0 +1,13 @@
{"SetOption":{"value":"scrolloff=3"}}
{"SetOption":{"value":"lines=12"}}
{"SetOption":{"value":"scrolloff=0"}}
{"Put":{"state":"ˇaa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nzz"}}
{"Key":"shift-g"}
{"Key":"k"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nˇyy\nzz","mode":"Normal"}}
{"Key":"ctrl-d"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nˇzz","mode":"Normal"}}
{"Key":"shift-g"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nuu\nvv\nww\nxx\nyy\nˇzz","mode":"Normal"}}
{"Key":"ctrl-u"}
{"Get":{"state":"aa\nbb\ncc\ndd\nee\nff\ngg\nhh\nii\njj\nkk\nll\nmm\nnn\noo\npp\nqq\nrr\nss\ntt\nˇuu\nvv\nww\nxx\nyy\nzz","mode":"Normal"}}

View File

@ -258,6 +258,8 @@ There are also a few Zed settings that you may also enjoy if you use vim mode:
"relative_line_numbers": true, "relative_line_numbers": true,
// hide the scroll bar // hide the scroll bar
"scrollbar": { "show": "never" }, "scrollbar": { "show": "never" },
// prevent the buffer from scrolling beyond the last line
"scroll_beyond_last_line": "off",
// allow cursor to reach edges of screen // allow cursor to reach edges of screen
"vertical_scroll_margin": 0, "vertical_scroll_margin": 0,
"gutter": { "gutter": {