diff --git a/assets/settings/default.json b/assets/settings/default.json index 72e7ea6fea..557aebd457 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -255,6 +255,8 @@ /// 2. "indent_aware" "background_coloring": "disabled" }, + // Whether the editor will scroll beyond the last line. + "scroll_beyond_last_line": "one_page", // The number of lines to keep above/below the cursor when scrolling. "vertical_scroll_margin": 3, // Scroll sensitivity multiplier. This multiplier is applied diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 8a22264838..6cb45d2d45 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -15,6 +15,7 @@ pub struct EditorSettings { pub toolbar: Toolbar, pub scrollbar: Scrollbar, pub gutter: Gutter, + pub scroll_beyond_last_line: ScrollBeyondLastLine, pub vertical_scroll_margin: f32, pub scroll_sensitivity: f32, pub relative_line_numbers: bool, @@ -116,6 +117,22 @@ pub enum MultiCursorModifier { CmdOrCtrl, } +/// Whether the editor will scroll beyond the last line. +/// +/// Default: one_page +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ScrollBeyondLastLine { + /// The editor will not scroll beyond the last line. + Off, + + /// The editor will scroll beyond the last line by one page. + OnePage, + + /// The editor will scroll beyond the last line by the same number of lines as vertical_scroll_margin. + VerticalScrollMargin, +} + #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct EditorSettingsContent { /// Whether the cursor blinks in the editor. @@ -158,6 +175,10 @@ pub struct EditorSettingsContent { pub scrollbar: Option, /// Gutter related settings pub gutter: Option, + /// Whether the editor will scroll beyond the last line. + /// + /// Default: one_page + pub scroll_beyond_last_line: Option, /// The number of lines to keep above/below the cursor when auto-scrolling. /// /// Default: 3. diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 592f6a4374..a30fc1b2cc 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,3 +1,4 @@ +use crate::editor_settings::ScrollBeyondLastLine; use crate::{ blame_entry_tooltip::{blame_entry_relative_timestamp, BlameEntryTooltip}, display_map::{ @@ -1089,11 +1090,17 @@ impl EditorElement { point(bounds.lower_right().x, bounds.lower_left().y), ); + let settings = EditorSettings::get_global(cx); + let scroll_beyond_last_line: f32 = match settings.scroll_beyond_last_line { + ScrollBeyondLastLine::OnePage => rows_per_page, + ScrollBeyondLastLine::Off => 1.0, + ScrollBeyondLastLine::VerticalScrollMargin => 1.0 + settings.vertical_scroll_margin, + }; + let total_rows = snapshot.max_point().row().as_f32() + scroll_beyond_last_line; let height = bounds.size.height; - let total_rows = snapshot.max_point().row().as_f32() + rows_per_page; let px_per_row = height / total_rows; let thumb_height = (rows_per_page * px_per_row).max(ScrollbarLayout::MIN_THUMB_HEIGHT); - let row_height = (height - thumb_height) / snapshot.max_point().row().as_f32(); + let row_height = (height - thumb_height) / (total_rows - rows_per_page).max(0.0); Some(ScrollbarLayout { hitbox: cx.insert_hitbox(track_bounds, false), @@ -4805,9 +4812,22 @@ impl Element for EditorElement { cx, ); + let settings = EditorSettings::get_global(cx); + let scroll_max_row = max_row.as_f32(); + let scroll_max_row = match settings.scroll_beyond_last_line { + ScrollBeyondLastLine::OnePage => scroll_max_row, + ScrollBeyondLastLine::Off => { + (scroll_max_row - height_in_lines + 1.0).max(0.0) + } + ScrollBeyondLastLine::VerticalScrollMargin => (scroll_max_row + - height_in_lines + + 1.0 + + settings.vertical_scroll_margin) + .max(0.0), + }; let scroll_max = point( ((scroll_width - text_hitbox.size.width) / em_width).max(0.0), - max_row.as_f32(), + scroll_max_row, ); self.editor.update(cx, |editor, cx| { diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index 6205b53d89..1ee1b18fd1 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -2,6 +2,7 @@ mod actions; pub(crate) mod autoscroll; pub(crate) mod scroll_amount; +use crate::editor_settings::ScrollBeyondLastLine; use crate::{ display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, @@ -199,8 +200,20 @@ impl ScrollManager { 0, ) } else { + let scroll_top = scroll_position.y; + let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line { + ScrollBeyondLastLine::OnePage => scroll_top, + ScrollBeyondLastLine::Off => scroll_top + .min((map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap() + 1.0), + ScrollBeyondLastLine::VerticalScrollMargin => scroll_top.min( + (map.max_buffer_row().as_f32()) - self.visible_line_count.unwrap() + + 1.0 + + self.vertical_scroll_margin, + ), + }; + let scroll_top_buffer_point = - DisplayPoint::new(DisplayRow(scroll_position.y as u32), 0).to_point(&map); + DisplayPoint::new(DisplayRow(scroll_top as u32), 0).to_point(&map); let top_anchor = map .buffer_snapshot .anchor_at(scroll_top_buffer_point, Bias::Right); @@ -210,7 +223,7 @@ impl ScrollManager { anchor: top_anchor, offset: point( scroll_position.x.max(0.), - scroll_position.y - top_anchor.to_display_point(&map).row().as_f32(), + scroll_top - top_anchor.to_display_point(&map).row().as_f32(), ), }, scroll_top_buffer_point.row,