mod actions;pub(crate) mod autoscroll;pub(crate) mod scroll_amount; use crate::editor_settings::ScrollBeyondLastLine;use crate::{ Anchor, DisplayPoint, DisplayRow, Editor, EditorEvent, EditorMode, EditorSettings, InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, persistence::DB,};pub use autoscroll::{Autoscroll, AutoscrollStrategy};use core::fmt::Debug;use gpui::{Along, App, Axis, Context, Pixels, Task, Window, point, px};use language::language_settings::{AllLanguageSettings, SoftWrap};use language::{Bias, Point};pub use scroll_amount::ScrollAmount;use settings::Settings;use std::{ cmp::Ordering, time::{Duration, Instant},};use ui::scrollbars::ScrollbarAutoHide;use util::ResultExt;use workspace::{ItemId, WorkspaceId}; pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28);const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); pub struct WasScrolled(pub(crate) bool); pub type ScrollOffset = f64;pub type ScrollPixelOffset = f64;#[derive(Clone, Copy, Debug, PartialEq)]pub struct ScrollAnchor { pub offset: gpui::Point<ScrollOffset>, pub anchor: Anchor,} impl ScrollAnchor { pub(super) fn new() -> Self { Self { offset: gpui::Point::default(), anchor: Anchor::min(), } } pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> { self.offset.apply_along(Axis::Vertical, |offset| { if self.anchor == Anchor::min() { 0. } else { let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64(); (offset + scroll_top).max(0.) } }) } pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 { self.anchor.to_point(buffer).row }} #[derive(Clone, Copy, Debug)]pub struct OngoingScroll { last_event: Instant, axis: Option<Axis>,} impl OngoingScroll { fn new() -> Self { Self { last_event: Instant::now() - SCROLL_EVENT_SEPARATION, axis: None, } } pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> { const UNLOCK_PERCENT: f32 = 1.9; const UNLOCK_LOWER_BOUND: Pixels = px(6.); let mut axis = self.axis; let x = delta.x.abs(); let y = delta.y.abs(); let duration = Instant::now().duration_since(self.last_event); if duration > SCROLL_EVENT_SEPARATION { //New ongoing scroll will start, determine axis axis = if x <= y { Some(Axis::Vertical) } else { Some(Axis::Horizontal) }; } else if x.max(y) >= UNLOCK_LOWER_BOUND { //Check if the current ongoing will need to unlock match axis { Some(Axis::Vertical) => { if x > y && x >= y * UNLOCK_PERCENT { axis = None; } } Some(Axis::Horizontal) => { if y > x && y >= x * UNLOCK_PERCENT { axis = None; } } None => {} } } match axis { Some(Axis::Vertical) => { *delta = point(px(0.), delta.y); } Some(Axis::Horizontal) => { *delta = point(delta.x, px(0.)); } None => {} } axis }} #[derive(Copy, Clone, Default, PartialEq, Eq)]pub enum ScrollbarThumbState { #[default] Idle, Hovered, Dragging,} #[derive(PartialEq, Eq)]pub struct ActiveScrollbarState { axis: Axis, thumb_state: ScrollbarThumbState,} impl ActiveScrollbarState { pub fn new(axis: Axis, thumb_state: ScrollbarThumbState) -> Self { ActiveScrollbarState { axis, thumb_state } } pub fn thumb_state_for_axis(&self, axis: Axis) -> Option<ScrollbarThumbState> { (self.axis == axis).then_some(self.thumb_state) }} pub struct ScrollManager { pub(crate) vertical_scroll_margin: ScrollOffset, anchor: ScrollAnchor, ongoing: OngoingScroll, /// Number of sticky header lines currently being rendered for the current scroll position. sticky_header_line_count: usize, /// The second element indicates whether the autoscroll request is local /// (true) or remote (false). Local requests are initiated by user actions, /// while remote requests come from external sources. autoscroll_request: Option<(Autoscroll, bool)>, last_autoscroll: Option<( gpui::Point<ScrollOffset>, ScrollOffset, ScrollOffset, AutoscrollStrategy, )>, show_scrollbars: bool, hide_scrollbar_task: Option<Task<()>>, active_scrollbar: Option<ActiveScrollbarState>, visible_line_count: Option<f64>, visible_column_count: Option<f64>, forbid_vertical_scroll: bool, minimap_thumb_state: Option<ScrollbarThumbState>, _save_scroll_position_task: Task<()>,} impl ScrollManager { pub fn new(cx: &mut App) -> Self { ScrollManager { vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin, anchor: ScrollAnchor::new(), ongoing: OngoingScroll::new(), sticky_header_line_count: 0, autoscroll_request: None, show_scrollbars: true, hide_scrollbar_task: None, active_scrollbar: None, last_autoscroll: None, visible_line_count: None, visible_column_count: None, forbid_vertical_scroll: false, minimap_thumb_state: None, _save_scroll_position_task: Task::ready(()), } } pub fn clone_state(&mut self, other: &Self) { self.anchor = other.anchor; self.ongoing = other.ongoing; self.sticky_header_line_count = other.sticky_header_line_count; } pub fn anchor(&self) -> ScrollAnchor { self.anchor } pub fn ongoing_scroll(&self) -> OngoingScroll { self.ongoing } pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) { self.ongoing.last_event = Instant::now(); self.ongoing.axis = axis; } pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> { self.anchor.scroll_position(snapshot) } pub fn sticky_header_line_count(&self) -> usize { self.sticky_header_line_count } pub fn set_sticky_header_line_count(&mut self, count: usize) { self.sticky_header_line_count = count; } fn set_scroll_position( &mut self, scroll_position: gpui::Point<ScrollOffset>, map: &DisplaySnapshot, local: bool, autoscroll: bool, workspace_id: Option<WorkspaceId>, window: &mut Window, cx: &mut Context<Editor>, ) -> WasScrolled { let scroll_top = scroll_position.y.max(0.); let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line { ScrollBeyondLastLine::OnePage => scroll_top, ScrollBeyondLastLine::Off => { if let Some(height_in_lines) = self.visible_line_count { let max_row = map.max_point().row().as_f64(); scroll_top.min(max_row - height_in_lines + 1.).max(0.) } else { scroll_top } } ScrollBeyondLastLine::VerticalScrollMargin => { if let Some(height_in_lines) = self.visible_line_count { let max_row = map.max_point().row().as_f64(); scroll_top .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin) .max(0.) } else { scroll_top } } }; let scroll_top_row = DisplayRow(scroll_top as u32); let scroll_top_buffer_point = map .clip_point( DisplayPoint::new(scroll_top_row, scroll_position.x as u32), Bias::Left, ) .to_point(map); // Anchor the scroll position to the *left* of the first visible buffer point. // // This prevents the viewport from shifting down when blocks (e.g. expanded diff hunk // deletions) are inserted *above* the first buffer character in the file. let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point); self.set_anchor( ScrollAnchor { anchor: top_anchor, offset: point( scroll_position.x.max(0.), scroll_top - top_anchor.to_display_point(map).row().as_f64(), ), }, scroll_top_buffer_point.row, local, autoscroll, workspace_id, window, cx, ) } fn set_anchor( &mut self, anchor: ScrollAnchor, top_row: u32, local: bool, autoscroll: bool, workspace_id: Option<WorkspaceId>, window: &mut Window, cx: &mut Context<Editor>, ) -> WasScrolled { let adjusted_anchor = if self.forbid_vertical_scroll { ScrollAnchor { offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y), anchor: self.anchor.anchor, } } else { anchor }; self.autoscroll_request.take(); if self.anchor == adjusted_anchor { return WasScrolled(false); } self.anchor = adjusted_anchor; cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll }); self.show_scrollbars(window, cx); if let Some(workspace_id) = workspace_id { let item_id = cx.entity().entity_id().as_u64() as ItemId; let executor = cx.background_executor().clone(); self._save_scroll_position_task = cx.background_executor().spawn(async move { executor.timer(Duration::from_millis(10)).await; log::debug!( "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}" ); DB.save_scroll_position( item_id, workspace_id, top_row, anchor.offset.x, anchor.offset.y, ) .await .log_err(); }); } cx.notify(); WasScrolled(true) } pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) { if !self.show_scrollbars { self.show_scrollbars = true; cx.notify(); } if cx.default_global::<ScrollbarAutoHide>().should_hide() { self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| { cx.background_executor() .timer(SCROLLBAR_SHOW_INTERVAL) .await; editor .update(cx, |editor, cx| { editor.scroll_manager.show_scrollbars = false; cx.notify(); }) .log_err(); })); } else { self.hide_scrollbar_task = None; } } pub fn scrollbars_visible(&self) -> bool { self.show_scrollbars } pub fn has_autoscroll_request(&self) -> bool { self.autoscroll_request.is_some() } pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> { self.autoscroll_request.take() } pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> { self.active_scrollbar.as_ref() } pub fn dragging_scrollbar_axis(&self) -> Option<Axis> { self.active_scrollbar .as_ref() .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging) .map(|scrollbar| scrollbar.axis) } pub fn any_scrollbar_dragged(&self) -> bool { self.active_scrollbar .as_ref() .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging) } pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) { self.update_active_scrollbar_state( Some(ActiveScrollbarState::new( axis, ScrollbarThumbState::Hovered, )), cx, ); } pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) { self.update_active_scrollbar_state( Some(ActiveScrollbarState::new( axis, ScrollbarThumbState::Dragging, )), cx, ); } pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) { self.update_active_scrollbar_state(None, cx); } fn update_active_scrollbar_state( &mut self, new_state: Option<ActiveScrollbarState>, cx: &mut Context<Editor>, ) { if self.active_scrollbar != new_state { self.active_scrollbar = new_state; cx.notify(); } } pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) { self.update_minimap_thumb_state( Some(if hovered { ScrollbarThumbState::Hovered } else { ScrollbarThumbState::Idle }), cx, ); } pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) { self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx); } pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) { self.update_minimap_thumb_state(None, cx); } pub fn is_dragging_minimap(&self) -> bool { self.minimap_thumb_state .is_some_and(|state| state == ScrollbarThumbState::Dragging) } fn update_minimap_thumb_state( &mut self, thumb_state: Option<ScrollbarThumbState>, cx: &mut Context<Editor>, ) { if self.minimap_thumb_state != thumb_state { self.minimap_thumb_state = thumb_state; cx.notify(); } } pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> { self.minimap_thumb_state } pub fn clamp_scroll_left(&mut self, max: f64) -> bool { if max < self.anchor.offset.x { self.anchor.offset.x = max; true } else { false } } pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) { self.forbid_vertical_scroll = forbid; } pub fn forbid_vertical_scroll(&self) -> bool { self.forbid_vertical_scroll }} impl Editor { pub fn vertical_scroll_margin(&self) -> usize { self.scroll_manager.vertical_scroll_margin as usize } pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) { self.scroll_manager.vertical_scroll_margin = margin_rows as f64; cx.notify(); } pub fn visible_line_count(&self) -> Option<f64> { self.scroll_manager.visible_line_count } Lines 1–500 of 820. Load more
mod actions; pub(crate) mod autoscroll; pub(crate) mod scroll_amount; use crate::editor_settings::ScrollBeyondLastLine; use crate::{ Anchor, DisplayPoint, DisplayRow, Editor, EditorEvent, EditorMode, EditorSettings, InlayHintRefreshReason, MultiBufferSnapshot, RowExt, ToPoint, display_map::{DisplaySnapshot, ToDisplayPoint}, hover_popover::hide_hover, persistence::DB, }; pub use autoscroll::{Autoscroll, AutoscrollStrategy}; use core::fmt::Debug; use gpui::{Along, App, Axis, Context, Pixels, Task, Window, point, px}; use language::language_settings::{AllLanguageSettings, SoftWrap}; use language::{Bias, Point}; pub use scroll_amount::ScrollAmount; use settings::Settings; use std::{ cmp::Ordering, time::{Duration, Instant}, }; use ui::scrollbars::ScrollbarAutoHide; use util::ResultExt; use workspace::{ItemId, WorkspaceId}; pub const SCROLL_EVENT_SEPARATION: Duration = Duration::from_millis(28); const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); pub struct WasScrolled(pub(crate) bool); pub type ScrollOffset = f64; pub type ScrollPixelOffset = f64; #[derive(Clone, Copy, Debug, PartialEq)] pub struct ScrollAnchor { pub offset: gpui::Point<ScrollOffset>, pub anchor: Anchor, } impl ScrollAnchor { pub(super) fn new() -> Self { Self { offset: gpui::Point::default(), anchor: Anchor::min(), } } pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> { self.offset.apply_along(Axis::Vertical, |offset| { if self.anchor == Anchor::min() { 0. } else { let scroll_top = self.anchor.to_display_point(snapshot).row().as_f64(); (offset + scroll_top).max(0.) } }) } pub fn top_row(&self, buffer: &MultiBufferSnapshot) -> u32 { self.anchor.to_point(buffer).row } } #[derive(Clone, Copy, Debug)] pub struct OngoingScroll { last_event: Instant, axis: Option<Axis>, } impl OngoingScroll { fn new() -> Self { Self { last_event: Instant::now() - SCROLL_EVENT_SEPARATION, axis: None, } } pub fn filter(&self, delta: &mut gpui::Point<Pixels>) -> Option<Axis> { const UNLOCK_PERCENT: f32 = 1.9; const UNLOCK_LOWER_BOUND: Pixels = px(6.); let mut axis = self.axis; let x = delta.x.abs(); let y = delta.y.abs(); let duration = Instant::now().duration_since(self.last_event); if duration > SCROLL_EVENT_SEPARATION { //New ongoing scroll will start, determine axis axis = if x <= y { Some(Axis::Vertical) } else { Some(Axis::Horizontal) }; } else if x.max(y) >= UNLOCK_LOWER_BOUND { //Check if the current ongoing will need to unlock match axis { Some(Axis::Vertical) => { if x > y && x >= y * UNLOCK_PERCENT { axis = None; } } Some(Axis::Horizontal) => { if y > x && y >= x * UNLOCK_PERCENT { axis = None; } } None => {} } } match axis { Some(Axis::Vertical) => { *delta = point(px(0.), delta.y); } Some(Axis::Horizontal) => { *delta = point(delta.x, px(0.)); } None => {} } axis } } #[derive(Copy, Clone, Default, PartialEq, Eq)] pub enum ScrollbarThumbState { #[default] Idle, Hovered, Dragging, } #[derive(PartialEq, Eq)] pub struct ActiveScrollbarState { axis: Axis, thumb_state: ScrollbarThumbState, } impl ActiveScrollbarState { pub fn new(axis: Axis, thumb_state: ScrollbarThumbState) -> Self { ActiveScrollbarState { axis, thumb_state } } pub fn thumb_state_for_axis(&self, axis: Axis) -> Option<ScrollbarThumbState> { (self.axis == axis).then_some(self.thumb_state) } } pub struct ScrollManager { pub(crate) vertical_scroll_margin: ScrollOffset, anchor: ScrollAnchor, ongoing: OngoingScroll, /// Number of sticky header lines currently being rendered for the current scroll position. sticky_header_line_count: usize, /// The second element indicates whether the autoscroll request is local /// (true) or remote (false). Local requests are initiated by user actions, /// while remote requests come from external sources. autoscroll_request: Option<(Autoscroll, bool)>, last_autoscroll: Option<( gpui::Point<ScrollOffset>, ScrollOffset, ScrollOffset, AutoscrollStrategy, )>, show_scrollbars: bool, hide_scrollbar_task: Option<Task<()>>, active_scrollbar: Option<ActiveScrollbarState>, visible_line_count: Option<f64>, visible_column_count: Option<f64>, forbid_vertical_scroll: bool, minimap_thumb_state: Option<ScrollbarThumbState>, _save_scroll_position_task: Task<()>, } impl ScrollManager { pub fn new(cx: &mut App) -> Self { ScrollManager { vertical_scroll_margin: EditorSettings::get_global(cx).vertical_scroll_margin, anchor: ScrollAnchor::new(), ongoing: OngoingScroll::new(), sticky_header_line_count: 0, autoscroll_request: None, show_scrollbars: true, hide_scrollbar_task: None, active_scrollbar: None, last_autoscroll: None, visible_line_count: None, visible_column_count: None, forbid_vertical_scroll: false, minimap_thumb_state: None, _save_scroll_position_task: Task::ready(()), } } pub fn clone_state(&mut self, other: &Self) { self.anchor = other.anchor; self.ongoing = other.ongoing; self.sticky_header_line_count = other.sticky_header_line_count; } pub fn anchor(&self) -> ScrollAnchor { self.anchor } pub fn ongoing_scroll(&self) -> OngoingScroll { self.ongoing } pub fn update_ongoing_scroll(&mut self, axis: Option<Axis>) { self.ongoing.last_event = Instant::now(); self.ongoing.axis = axis; } pub fn scroll_position(&self, snapshot: &DisplaySnapshot) -> gpui::Point<ScrollOffset> { self.anchor.scroll_position(snapshot) } pub fn sticky_header_line_count(&self) -> usize { self.sticky_header_line_count } pub fn set_sticky_header_line_count(&mut self, count: usize) { self.sticky_header_line_count = count; } fn set_scroll_position( &mut self, scroll_position: gpui::Point<ScrollOffset>, map: &DisplaySnapshot, local: bool, autoscroll: bool, workspace_id: Option<WorkspaceId>, window: &mut Window, cx: &mut Context<Editor>, ) -> WasScrolled { let scroll_top = scroll_position.y.max(0.); let scroll_top = match EditorSettings::get_global(cx).scroll_beyond_last_line { ScrollBeyondLastLine::OnePage => scroll_top, ScrollBeyondLastLine::Off => { if let Some(height_in_lines) = self.visible_line_count { let max_row = map.max_point().row().as_f64(); scroll_top.min(max_row - height_in_lines + 1.).max(0.) } else { scroll_top } } ScrollBeyondLastLine::VerticalScrollMargin => { if let Some(height_in_lines) = self.visible_line_count { let max_row = map.max_point().row().as_f64(); scroll_top .min(max_row - height_in_lines + 1. + self.vertical_scroll_margin) .max(0.) } else { scroll_top } } }; let scroll_top_row = DisplayRow(scroll_top as u32); let scroll_top_buffer_point = map .clip_point( DisplayPoint::new(scroll_top_row, scroll_position.x as u32), Bias::Left, ) .to_point(map); // Anchor the scroll position to the *left* of the first visible buffer point. // // This prevents the viewport from shifting down when blocks (e.g. expanded diff hunk // deletions) are inserted *above* the first buffer character in the file. let top_anchor = map.buffer_snapshot().anchor_before(scroll_top_buffer_point); self.set_anchor( ScrollAnchor { anchor: top_anchor, offset: point( scroll_position.x.max(0.), scroll_top - top_anchor.to_display_point(map).row().as_f64(), ), }, scroll_top_buffer_point.row, local, autoscroll, workspace_id, window, cx, ) } fn set_anchor( &mut self, anchor: ScrollAnchor, top_row: u32, local: bool, autoscroll: bool, workspace_id: Option<WorkspaceId>, window: &mut Window, cx: &mut Context<Editor>, ) -> WasScrolled { let adjusted_anchor = if self.forbid_vertical_scroll { ScrollAnchor { offset: gpui::Point::new(anchor.offset.x, self.anchor.offset.y), anchor: self.anchor.anchor, } } else { anchor }; self.autoscroll_request.take(); if self.anchor == adjusted_anchor { return WasScrolled(false); } self.anchor = adjusted_anchor; cx.emit(EditorEvent::ScrollPositionChanged { local, autoscroll }); self.show_scrollbars(window, cx); if let Some(workspace_id) = workspace_id { let item_id = cx.entity().entity_id().as_u64() as ItemId; let executor = cx.background_executor().clone(); self._save_scroll_position_task = cx.background_executor().spawn(async move { executor.timer(Duration::from_millis(10)).await; log::debug!( "Saving scroll position for item {item_id:?} in workspace {workspace_id:?}" ); DB.save_scroll_position( item_id, workspace_id, top_row, anchor.offset.x, anchor.offset.y, ) .await .log_err(); }); } cx.notify(); WasScrolled(true) } pub fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context<Editor>) { if !self.show_scrollbars { self.show_scrollbars = true; cx.notify(); } if cx.default_global::<ScrollbarAutoHide>().should_hide() { self.hide_scrollbar_task = Some(cx.spawn_in(window, async move |editor, cx| { cx.background_executor() .timer(SCROLLBAR_SHOW_INTERVAL) .await; editor .update(cx, |editor, cx| { editor.scroll_manager.show_scrollbars = false; cx.notify(); }) .log_err(); })); } else { self.hide_scrollbar_task = None; } } pub fn scrollbars_visible(&self) -> bool { self.show_scrollbars } pub fn has_autoscroll_request(&self) -> bool { self.autoscroll_request.is_some() } pub fn take_autoscroll_request(&mut self) -> Option<(Autoscroll, bool)> { self.autoscroll_request.take() } pub fn active_scrollbar_state(&self) -> Option<&ActiveScrollbarState> { self.active_scrollbar.as_ref() } pub fn dragging_scrollbar_axis(&self) -> Option<Axis> { self.active_scrollbar .as_ref() .filter(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging) .map(|scrollbar| scrollbar.axis) } pub fn any_scrollbar_dragged(&self) -> bool { self.active_scrollbar .as_ref() .is_some_and(|scrollbar| scrollbar.thumb_state == ScrollbarThumbState::Dragging) } pub fn set_hovered_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) { self.update_active_scrollbar_state( Some(ActiveScrollbarState::new( axis, ScrollbarThumbState::Hovered, )), cx, ); } pub fn set_dragged_scroll_thumb_axis(&mut self, axis: Axis, cx: &mut Context<Editor>) { self.update_active_scrollbar_state( Some(ActiveScrollbarState::new( axis, ScrollbarThumbState::Dragging, )), cx, ); } pub fn reset_scrollbar_state(&mut self, cx: &mut Context<Editor>) { self.update_active_scrollbar_state(None, cx); } fn update_active_scrollbar_state( &mut self, new_state: Option<ActiveScrollbarState>, cx: &mut Context<Editor>, ) { if self.active_scrollbar != new_state { self.active_scrollbar = new_state; cx.notify(); } } pub fn set_is_hovering_minimap_thumb(&mut self, hovered: bool, cx: &mut Context<Editor>) { self.update_minimap_thumb_state( Some(if hovered { ScrollbarThumbState::Hovered } else { ScrollbarThumbState::Idle }), cx, ); } pub fn set_is_dragging_minimap(&mut self, cx: &mut Context<Editor>) { self.update_minimap_thumb_state(Some(ScrollbarThumbState::Dragging), cx); } pub fn hide_minimap_thumb(&mut self, cx: &mut Context<Editor>) { self.update_minimap_thumb_state(None, cx); } pub fn is_dragging_minimap(&self) -> bool { self.minimap_thumb_state .is_some_and(|state| state == ScrollbarThumbState::Dragging) } fn update_minimap_thumb_state( &mut self, thumb_state: Option<ScrollbarThumbState>, cx: &mut Context<Editor>, ) { if self.minimap_thumb_state != thumb_state { self.minimap_thumb_state = thumb_state; cx.notify(); } } pub fn minimap_thumb_state(&self) -> Option<ScrollbarThumbState> { self.minimap_thumb_state } pub fn clamp_scroll_left(&mut self, max: f64) -> bool { if max < self.anchor.offset.x { self.anchor.offset.x = max; true } else { false } } pub fn set_forbid_vertical_scroll(&mut self, forbid: bool) { self.forbid_vertical_scroll = forbid; } pub fn forbid_vertical_scroll(&self) -> bool { self.forbid_vertical_scroll } } impl Editor { pub fn vertical_scroll_margin(&self) -> usize { self.scroll_manager.vertical_scroll_margin as usize } pub fn set_vertical_scroll_margin(&mut self, margin_rows: usize, cx: &mut Context<Self>) { self.scroll_manager.vertical_scroll_margin = margin_rows as f64; cx.notify(); } pub fn visible_line_count(&self) -> Option<f64> { self.scroll_manager.visible_line_count }