use crate::{ DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, LineWithInvisibles, RowExt, SelectionEffects, display_map::{DisplaySnapshot, ToDisplayPoint}, editor_settings::GoToDefinitionScrollStrategy, scroll::{ScrollOffset, WasScrolled},};use gpui::{App, Bounds, Context, Pixels, Window};use language::Point;use multi_buffer::Anchor;use settings::Settings;use std::cmp;use text::Bias; #[derive(Debug, PartialEq, Eq, Clone, Copy)]pub enum Autoscroll { Next, Strategy(AutoscrollStrategy, Option<Anchor>),} impl Autoscroll { /// scrolls the minimal amount to (try) and fit all cursors onscreen pub fn fit() -> Self { Self::Strategy(AutoscrollStrategy::Fit, None) } /// scrolls the minimal amount to fit the newest cursor pub fn newest() -> Self { Self::Strategy(AutoscrollStrategy::Newest, None) } /// scrolls so the newest cursor is vertically centered pub fn center() -> Self { Self::Strategy(AutoscrollStrategy::Center, None) } /// Returns the autoscroll strategy configured for navigation to definitions /// and references, based on `go_to_definition_scroll_strategy`. pub fn for_go_to_definition(offset: Option<ScrollOffset>, cx: &App) -> Self { match EditorSettings::get_global(cx).go_to_definition_scroll_strategy { GoToDefinitionScrollStrategy::Center => Self::center(), GoToDefinitionScrollStrategy::Minimum => Self::fit(), GoToDefinitionScrollStrategy::Top => Self::focused(), GoToDefinitionScrollStrategy::Preserve => { offset.map(Self::top_relative).unwrap_or_else(Self::center) } } } /// scrolls so the newest cursor is near the top /// (offset by vertical_scroll_margin) pub fn focused() -> Self { Self::Strategy(AutoscrollStrategy::Focused, None) } /// Scrolls so that the newest cursor is the given offset (in display rows) /// from the top of the viewport. pub fn top_relative(offset: ScrollOffset) -> Self { Self::Strategy(AutoscrollStrategy::TopRelative(offset), None) } /// Scrolls so that the newest cursor is at the top. pub fn top() -> Self { Self::Strategy(AutoscrollStrategy::Top, None) } /// Scrolls so that the newest cursor is the given offset (in display rows) /// from the bottom of the viewport. pub fn bottom_relative(offset: ScrollOffset) -> Self { Self::Strategy(AutoscrollStrategy::BottomRelative(offset), None) } /// Scrolls so that the newest cursor is at the bottom. pub fn bottom() -> Self { Self::Strategy(AutoscrollStrategy::Bottom, None) } /// Applies a given auto-scroll strategy to a given anchor instead of a cursor. /// E.G: Autoscroll::center().for_anchor(...) results in the anchor being at the center of the screen. pub fn for_anchor(self, anchor: Anchor) -> Self { match self { Autoscroll::Next => self, Autoscroll::Strategy(autoscroll_strategy, _) => { Self::Strategy(autoscroll_strategy, Some(anchor)) } } }} impl Into<SelectionEffects> for Option<Autoscroll> { fn into(self) -> SelectionEffects { match self { Some(autoscroll) => SelectionEffects::scroll(autoscroll), None => SelectionEffects::no_scroll(), } }} #[derive(Debug, PartialEq, Default, Clone, Copy)]pub enum AutoscrollStrategy { Fit, Newest, #[default] Center, Focused, Top, Bottom, TopRelative(ScrollOffset), BottomRelative(ScrollOffset),} impl Eq for AutoscrollStrategy {} impl AutoscrollStrategy { fn next(&self) -> Self { match self { AutoscrollStrategy::Center => AutoscrollStrategy::Top, AutoscrollStrategy::Top => AutoscrollStrategy::Bottom, _ => AutoscrollStrategy::Center, } }} pub(crate) struct NeedsHorizontalAutoscroll(pub(crate) bool); impl Editor { pub(crate) fn autoscroll_vertically( &mut self, bounds: Bounds<Pixels>, line_height: Pixels, max_scroll_top: ScrollOffset, autoscroll_request: Option<(Autoscroll, bool)>, window: &mut Window, cx: &mut Context<Editor>, ) -> (NeedsHorizontalAutoscroll, WasScrolled) { let viewport_height = bounds.size.height; let visible_lines = ScrollOffset::from(viewport_height / line_height); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut scroll_position = self.scroll_manager.scroll_position(&display_map, cx); let original_y = scroll_position.y; if let Some(last_bounds) = self.expect_bounds_change.take() && scroll_position.y != 0. { scroll_position.y += ScrollOffset::from((bounds.top() - last_bounds.top()) / line_height); if scroll_position.y < 0. { scroll_position.y = 0.; } } if scroll_position.y > max_scroll_top { scroll_position.y = max_scroll_top; } let editor_was_scrolled = if original_y != scroll_position.y { self.set_scroll_position(scroll_position, window, cx) } else { WasScrolled(false) }; let Some((autoscroll, local)) = autoscroll_request else { return (NeedsHorizontalAutoscroll(false), editor_was_scrolled); }; let mut target_point; let mut target_top; let mut target_bottom; if let Some(first_highlighted_row) = self.highlighted_display_row_for_autoscroll(&display_map) { target_point = DisplayPoint::new(first_highlighted_row, 0); target_top = target_point.row().as_f64(); target_bottom = target_top + 1.; } else { // Autoscroll only needs the first, last, and newest selections. target_point = self .selections .first::<Point>(&display_map) .head() .to_display_point(&display_map); target_top = target_point.row().as_f64(); target_bottom = self .selections .last::<Point>(&display_map) .head() .to_display_point(&display_map) .row() .next_row() .as_f64(); let selections_fit = target_bottom - target_top <= visible_lines; if matches!( autoscroll, Autoscroll::Strategy(AutoscrollStrategy::Newest, _) ) || (matches!(autoscroll, Autoscroll::Strategy(AutoscrollStrategy::Fit, _)) && !selections_fit) { target_point = self .selections .newest::<Point>(&display_map) .head() .to_display_point(&display_map); target_top = target_point.row().as_f64(); target_bottom = target_top + 1.; } } let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) { 0. } else { ((visible_lines - (target_bottom - target_top)) / 2.0).floor() }; let strategy = match autoscroll { Autoscroll::Strategy(strategy, _) => strategy, Autoscroll::Next => self .scroll_manager .last_autoscroll .as_ref() .filter(|(offset, last_target_top, last_target_bottom, _)| { self.scroll_manager.offset(cx) == *offset && target_top == *last_target_top && target_bottom == *last_target_bottom }) .map(|(_, _, _, strategy)| strategy.next()) .unwrap_or_default(), }; if let Autoscroll::Strategy(_, Some(anchor)) = autoscroll { target_point = anchor.to_display_point(&display_map); target_top = target_point.row().as_f64(); target_bottom = target_top + 1.; } let visible_sticky_headers = self.visible_sticky_header_count_for_point(&display_map, target_point, cx); let was_autoscrolled = match strategy { AutoscrollStrategy::Fit | AutoscrollStrategy::Newest => { let margin = margin.min(self.scroll_manager.vertical_scroll_margin); let target_top = (target_top - margin - visible_sticky_headers as f64).max(0.0); let target_bottom = target_bottom + margin; let start_row = scroll_position.y; let end_row = start_row + visible_lines; let needs_scroll_up = target_top < start_row; let needs_scroll_down = target_bottom >= end_row; if needs_scroll_up && !needs_scroll_down { scroll_position.y = target_top; } else if !needs_scroll_up && needs_scroll_down { scroll_position.y = target_bottom - visible_lines; } if needs_scroll_up ^ needs_scroll_down { self.set_scroll_position_internal(scroll_position, local, true, window, cx) } else { WasScrolled(false) } } AutoscrollStrategy::Center => { scroll_position.y = (target_top - margin).max(0.0); self.set_scroll_position_internal(scroll_position, local, true, window, cx) } AutoscrollStrategy::Focused => { let margin = margin.min(self.scroll_manager.vertical_scroll_margin); scroll_position.y = (target_top - margin).max(0.0); self.set_scroll_position_internal(scroll_position, local, true, window, cx) } AutoscrollStrategy::Top => { scroll_position.y = (target_top).max(0.0); self.set_scroll_position_internal(scroll_position, local, true, window, cx) } AutoscrollStrategy::Bottom => { scroll_position.y = (target_bottom - visible_lines).max(0.0); self.set_scroll_position_internal(scroll_position, local, true, window, cx) } AutoscrollStrategy::TopRelative(lines) => { scroll_position.y = target_top - lines as ScrollOffset; self.set_scroll_position_internal(scroll_position, local, true, window, cx) } AutoscrollStrategy::BottomRelative(lines) => { scroll_position.y = target_bottom + lines as ScrollOffset; self.set_scroll_position_internal(scroll_position, local, true, window, cx) } }; self.scroll_manager.last_autoscroll = Some(( self.scroll_manager.offset(cx), target_top, target_bottom, strategy, )); let was_scrolled = WasScrolled(editor_was_scrolled.0 || was_autoscrolled.0); (NeedsHorizontalAutoscroll(true), was_scrolled) } pub(crate) fn visible_sticky_header_count_for_point( &self, display_map: &DisplaySnapshot, target_point: DisplayPoint, cx: &App, ) -> usize { let sticky_scroll = EditorSettings::get_global(cx).sticky_scroll; if !sticky_scroll.enabled { return 0; } let Some(buffer_snapshot) = display_map.buffer_snapshot().as_singleton() else { return 0; }; let point = target_point.to_point(display_map); let mut item_ranges = buffer_snapshot .outline_ranges_containing(point..point) .collect::<Vec<_>>(); item_ranges.sort_by_key(|item_range| item_range.start); let mut previous_sticky_row = None; let mut num_visible_sticky_headers = 0; for item_range in item_ranges { let sticky_row = display_map .point_to_display_point(item_range.start, Bias::Left) .row(); if sticky_row >= target_point.row() { break; } if previous_sticky_row.replace(sticky_row) == Some(sticky_row) { continue; } let end_row = display_map .point_to_display_point(item_range.end, Bias::Left) .row(); if end_row <= target_point.row() { continue; } num_visible_sticky_headers += 1; } num_visible_sticky_headers } pub(crate) fn autoscroll_horizontally( &mut self, start_row: DisplayRow, viewport_width: Pixels, scroll_width: Pixels, em_advance: Pixels, layouts: &[LineWithInvisibles], autoscroll_request: Option<(Autoscroll, bool)>, window: &mut Window, cx: &mut Context<Self>, ) -> Option<gpui::Point<ScrollOffset>> { let (_, local) = autoscroll_request?; let em_advance = ScrollOffset::from(em_advance); let viewport_width = ScrollOffset::from(viewport_width); let scroll_width = ScrollOffset::from(scroll_width); let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); // Horizontal autoscroll only needs selections whose head is visible. let visible_end_row = DisplayRow(start_row.0 + layouts.len() as u32); let visible_range = display_map .buffer_snapshot() .anchor_before(DisplayPoint::new(start_row, 0).to_offset(&display_map, Bias::Left)) ..display_map.buffer_snapshot().anchor_after( DisplayPoint::new(visible_end_row, 0).to_offset(&display_map, Bias::Right), ); let mut selections = self .selections .disjoint_in_range::<Point>(visible_range, &display_map); selections.extend(self.selections.pending::<Point>(&display_map)); let mut scroll_position = self.scroll_manager.scroll_position(&display_map, cx); let mut target_left; let mut target_right: f64; if self .highlighted_display_row_for_autoscroll(&display_map) .is_none() { target_left = f64::INFINITY; target_right = 0.; for selection in selections { let head = selection.head().to_display_point(&display_map); if head.row() >= start_row && head.row() < DisplayRow(start_row.0 + layouts.len() as u32) { let row_line_len = display_map.line_len(head.row()); let start_dp = selection.start.to_display_point(&display_map); let end_dp = selection.end.to_display_point(&display_map); let start_column = if start_dp.row() == head.row() { start_dp.column() } else { 0 }; let end_column = cmp::min( row_line_len, if end_dp.row() == head.row() { end_dp.column() } else { row_line_len }, ); target_left = target_left.min(ScrollOffset::from( layouts[head.row().minus(start_row) as usize] .x_for_index(start_column as usize) + self.gutter_dimensions.margin, )); target_right = target_right.max( ScrollOffset::from( layouts[head.row().minus(start_row) as usize] .x_for_index(end_column as usize), ) + em_advance, ); } } } else { target_left = 0.; target_right = 0.; } target_right = target_right.min(scroll_width); if target_right - target_left > viewport_width { return None; } let scroll_left = self.scroll_manager.offset(cx).x * em_advance; let scroll_right = scroll_left + viewport_width; let was_scrolled = if target_left < scroll_left { scroll_position.x = target_left / em_advance; self.set_scroll_position_internal(scroll_position, local, true, window, cx) } else if target_right > scroll_right { scroll_position.x = (target_right - viewport_width) / em_advance; self.set_scroll_position_internal(scroll_position, local, true, window, cx) } else { WasScrolled(false) }; if was_scrolled.0 { Some(scroll_position) } else { None } } pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut Context<Self>) { self.scroll_manager.autoscroll_request = Some((autoscroll, true)); cx.notify(); } pub(crate) fn request_autoscroll_remotely( &mut self, autoscroll: Autoscroll, cx: &mut Context<Self>, ) { self.scroll_manager.autoscroll_request = Some((autoscroll, false)); cx.notify(); }}
use crate::{
DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, LineWithInvisibles, RowExt,
SelectionEffects,
display_map::{DisplaySnapshot, ToDisplayPoint},
editor_settings::GoToDefinitionScrollStrategy,
scroll::{ScrollOffset, WasScrolled},
};
use gpui::{App, Bounds, Context, Pixels, Window};
use language::Point;
use multi_buffer::Anchor;
use settings::Settings;
use std::cmp;
use text::Bias;
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Autoscroll {
Next,
Strategy(AutoscrollStrategy, Option<Anchor>),
}
impl Autoscroll {
/// scrolls the minimal amount to (try) and fit all cursors onscreen
pub fn fit() -> Self {
Self::Strategy(AutoscrollStrategy::Fit, None)
}
/// scrolls the minimal amount to fit the newest cursor
pub fn newest() -> Self {
Self::Strategy(AutoscrollStrategy::Newest, None)
}
/// scrolls so the newest cursor is vertically centered
pub fn center() -> Self {
Self::Strategy(AutoscrollStrategy::Center, None)
}
/// Returns the autoscroll strategy configured for navigation to definitions
/// and references, based on `go_to_definition_scroll_strategy`.
pub fn for_go_to_definition(offset: Option<ScrollOffset>, cx: &App) -> Self {
match EditorSettings::get_global(cx).go_to_definition_scroll_strategy {
GoToDefinitionScrollStrategy::Center => Self::center(),
GoToDefinitionScrollStrategy::Minimum => Self::fit(),
GoToDefinitionScrollStrategy::Top => Self::focused(),
GoToDefinitionScrollStrategy::Preserve => {
offset.map(Self::top_relative).unwrap_or_else(Self::center)
}
}
}
/// scrolls so the newest cursor is near the top
/// (offset by vertical_scroll_margin)
pub fn focused() -> Self {
Self::Strategy(AutoscrollStrategy::Focused, None)
}
/// Scrolls so that the newest cursor is the given offset (in display rows)
/// from the top of the viewport.
pub fn top_relative(offset: ScrollOffset) -> Self {
Self::Strategy(AutoscrollStrategy::TopRelative(offset), None)
}
/// Scrolls so that the newest cursor is at the top.
pub fn top() -> Self {
Self::Strategy(AutoscrollStrategy::Top, None)
}
/// Scrolls so that the newest cursor is the given offset (in display rows)
/// from the bottom of the viewport.
pub fn bottom_relative(offset: ScrollOffset) -> Self {
Self::Strategy(AutoscrollStrategy::BottomRelative(offset), None)
}
/// Scrolls so that the newest cursor is at the bottom.
pub fn bottom() -> Self {
Self::Strategy(AutoscrollStrategy::Bottom, None)
}
/// Applies a given auto-scroll strategy to a given anchor instead of a cursor.
/// E.G: Autoscroll::center().for_anchor(...) results in the anchor being at the center of the screen.
pub fn for_anchor(self, anchor: Anchor) -> Self {
match self {
Autoscroll::Next => self,
Autoscroll::Strategy(autoscroll_strategy, _) => {
Self::Strategy(autoscroll_strategy, Some(anchor))
}
}
}
}
impl Into<SelectionEffects> for Option<Autoscroll> {
fn into(self) -> SelectionEffects {
match self {
Some(autoscroll) => SelectionEffects::scroll(autoscroll),
None => SelectionEffects::no_scroll(),
}
}
}
#[derive(Debug, PartialEq, Default, Clone, Copy)]
pub enum AutoscrollStrategy {
Fit,
Newest,
#[default]
Center,
Focused,
Top,
Bottom,
TopRelative(ScrollOffset),
BottomRelative(ScrollOffset),
}
impl Eq for AutoscrollStrategy {}
impl AutoscrollStrategy {
fn next(&self) -> Self {
match self {
AutoscrollStrategy::Center => AutoscrollStrategy::Top,
AutoscrollStrategy::Top => AutoscrollStrategy::Bottom,
_ => AutoscrollStrategy::Center,
}
}
}
pub(crate) struct NeedsHorizontalAutoscroll(pub(crate) bool);
impl Editor {
pub(crate) fn autoscroll_vertically(
&mut self,
bounds: Bounds<Pixels>,
line_height: Pixels,
max_scroll_top: ScrollOffset,
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Editor>,
) -> (NeedsHorizontalAutoscroll, WasScrolled) {
let viewport_height = bounds.size.height;
let visible_lines = ScrollOffset::from(viewport_height / line_height);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut scroll_position = self.scroll_manager.scroll_position(&display_map, cx);
let original_y = scroll_position.y;
if let Some(last_bounds) = self.expect_bounds_change.take()
&& scroll_position.y != 0.
{
scroll_position.y +=
ScrollOffset::from((bounds.top() - last_bounds.top()) / line_height);
if scroll_position.y < 0. {
scroll_position.y = 0.;
}
}
if scroll_position.y > max_scroll_top {
scroll_position.y = max_scroll_top;
}
let editor_was_scrolled = if original_y != scroll_position.y {
self.set_scroll_position(scroll_position, window, cx)
} else {
WasScrolled(false)
};
let Some((autoscroll, local)) = autoscroll_request else {
return (NeedsHorizontalAutoscroll(false), editor_was_scrolled);
};
let mut target_point;
let mut target_top;
let mut target_bottom;
if let Some(first_highlighted_row) =
self.highlighted_display_row_for_autoscroll(&display_map)
{
target_point = DisplayPoint::new(first_highlighted_row, 0);
target_top = target_point.row().as_f64();
target_bottom = target_top + 1.;
} else {
// Autoscroll only needs the first, last, and newest selections.
target_point = self
.selections
.first::<Point>(&display_map)
.head()
.to_display_point(&display_map);
target_top = target_point.row().as_f64();
target_bottom = self
.selections
.last::<Point>(&display_map)
.head()
.to_display_point(&display_map)
.row()
.next_row()
.as_f64();
let selections_fit = target_bottom - target_top <= visible_lines;
if matches!(
autoscroll,
Autoscroll::Strategy(AutoscrollStrategy::Newest, _)
) || (matches!(autoscroll, Autoscroll::Strategy(AutoscrollStrategy::Fit, _))
&& !selections_fit)
{
target_point = self
.selections
.newest::<Point>(&display_map)
.head()
.to_display_point(&display_map);
target_top = target_point.row().as_f64();
target_bottom = target_top + 1.;
}
}
let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
0.
} else {
((visible_lines - (target_bottom - target_top)) / 2.0).floor()
};
let strategy = match autoscroll {
Autoscroll::Strategy(strategy, _) => strategy,
Autoscroll::Next => self
.scroll_manager
.last_autoscroll
.as_ref()
.filter(|(offset, last_target_top, last_target_bottom, _)| {
self.scroll_manager.offset(cx) == *offset
&& target_top == *last_target_top
&& target_bottom == *last_target_bottom
})
.map(|(_, _, _, strategy)| strategy.next())
.unwrap_or_default(),
};
if let Autoscroll::Strategy(_, Some(anchor)) = autoscroll {
target_point = anchor.to_display_point(&display_map);
target_top = target_point.row().as_f64();
target_bottom = target_top + 1.;
}
let visible_sticky_headers =
self.visible_sticky_header_count_for_point(&display_map, target_point, cx);
let was_autoscrolled = match strategy {
AutoscrollStrategy::Fit | AutoscrollStrategy::Newest => {
let margin = margin.min(self.scroll_manager.vertical_scroll_margin);
let target_top = (target_top - margin - visible_sticky_headers as f64).max(0.0);
let target_bottom = target_bottom + margin;
let start_row = scroll_position.y;
let end_row = start_row + visible_lines;
let needs_scroll_up = target_top < start_row;
let needs_scroll_down = target_bottom >= end_row;
if needs_scroll_up && !needs_scroll_down {
scroll_position.y = target_top;
} else if !needs_scroll_up && needs_scroll_down {
scroll_position.y = target_bottom - visible_lines;
}
if needs_scroll_up ^ needs_scroll_down {
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
} else {
WasScrolled(false)
}
}
AutoscrollStrategy::Center => {
scroll_position.y = (target_top - margin).max(0.0);
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::Focused => {
let margin = margin.min(self.scroll_manager.vertical_scroll_margin);
scroll_position.y = (target_top - margin).max(0.0);
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::Top => {
scroll_position.y = (target_top).max(0.0);
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::Bottom => {
scroll_position.y = (target_bottom - visible_lines).max(0.0);
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::TopRelative(lines) => {
scroll_position.y = target_top - lines as ScrollOffset;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
AutoscrollStrategy::BottomRelative(lines) => {
scroll_position.y = target_bottom + lines as ScrollOffset;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
}
};
self.scroll_manager.last_autoscroll = Some((
self.scroll_manager.offset(cx),
target_top,
target_bottom,
strategy,
));
let was_scrolled = WasScrolled(editor_was_scrolled.0 || was_autoscrolled.0);
(NeedsHorizontalAutoscroll(true), was_scrolled)
}
pub(crate) fn visible_sticky_header_count_for_point(
&self,
display_map: &DisplaySnapshot,
target_point: DisplayPoint,
cx: &App,
) -> usize {
let sticky_scroll = EditorSettings::get_global(cx).sticky_scroll;
if !sticky_scroll.enabled {
return 0;
}
let Some(buffer_snapshot) = display_map.buffer_snapshot().as_singleton() else {
return 0;
};
let point = target_point.to_point(display_map);
let mut item_ranges = buffer_snapshot
.outline_ranges_containing(point..point)
.collect::<Vec<_>>();
item_ranges.sort_by_key(|item_range| item_range.start);
let mut previous_sticky_row = None;
let mut num_visible_sticky_headers = 0;
for item_range in item_ranges {
let sticky_row = display_map
.point_to_display_point(item_range.start, Bias::Left)
.row();
if sticky_row >= target_point.row() {
break;
}
if previous_sticky_row.replace(sticky_row) == Some(sticky_row) {
continue;
}
let end_row = display_map
.point_to_display_point(item_range.end, Bias::Left)
.row();
if end_row <= target_point.row() {
continue;
}
num_visible_sticky_headers += 1;
}
num_visible_sticky_headers
}
pub(crate) fn autoscroll_horizontally(
&mut self,
start_row: DisplayRow,
viewport_width: Pixels,
scroll_width: Pixels,
em_advance: Pixels,
layouts: &[LineWithInvisibles],
autoscroll_request: Option<(Autoscroll, bool)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<gpui::Point<ScrollOffset>> {
let (_, local) = autoscroll_request?;
let em_advance = ScrollOffset::from(em_advance);
let viewport_width = ScrollOffset::from(viewport_width);
let scroll_width = ScrollOffset::from(scroll_width);
let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
// Horizontal autoscroll only needs selections whose head is visible.
let visible_end_row = DisplayRow(start_row.0 + layouts.len() as u32);
let visible_range = display_map
.buffer_snapshot()
.anchor_before(DisplayPoint::new(start_row, 0).to_offset(&display_map, Bias::Left))
..display_map.buffer_snapshot().anchor_after(
DisplayPoint::new(visible_end_row, 0).to_offset(&display_map, Bias::Right),
);
let mut selections = self
.selections
.disjoint_in_range::<Point>(visible_range, &display_map);
selections.extend(self.selections.pending::<Point>(&display_map));
let mut scroll_position = self.scroll_manager.scroll_position(&display_map, cx);
let mut target_left;
let mut target_right: f64;
if self
.highlighted_display_row_for_autoscroll(&display_map)
.is_none()
{
target_left = f64::INFINITY;
target_right = 0.;
for selection in selections {
let head = selection.head().to_display_point(&display_map);
if head.row() >= start_row
&& head.row() < DisplayRow(start_row.0 + layouts.len() as u32)
{
let row_line_len = display_map.line_len(head.row());
let start_dp = selection.start.to_display_point(&display_map);
let end_dp = selection.end.to_display_point(&display_map);
let start_column = if start_dp.row() == head.row() {
start_dp.column()
} else {
0
};
let end_column = cmp::min(
row_line_len,
if end_dp.row() == head.row() {
end_dp.column()
} else {
row_line_len
},
);
target_left = target_left.min(ScrollOffset::from(
layouts[head.row().minus(start_row) as usize]
.x_for_index(start_column as usize)
+ self.gutter_dimensions.margin,
));
target_right = target_right.max(
ScrollOffset::from(
layouts[head.row().minus(start_row) as usize]
.x_for_index(end_column as usize),
) + em_advance,
);
}
}
} else {
target_left = 0.;
target_right = 0.;
}
target_right = target_right.min(scroll_width);
if target_right - target_left > viewport_width {
return None;
}
let scroll_left = self.scroll_manager.offset(cx).x * em_advance;
let scroll_right = scroll_left + viewport_width;
let was_scrolled = if target_left < scroll_left {
scroll_position.x = target_left / em_advance;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
} else if target_right > scroll_right {
scroll_position.x = (target_right - viewport_width) / em_advance;
self.set_scroll_position_internal(scroll_position, local, true, window, cx)
} else {
WasScrolled(false)
};
if was_scrolled.0 {
Some(scroll_position)
} else {
None
}
}
pub fn request_autoscroll(&mut self, autoscroll: Autoscroll, cx: &mut Context<Self>) {
self.scroll_manager.autoscroll_request = Some((autoscroll, true));
cx.notify();
}
pub(crate) fn request_autoscroll_remotely(
&mut self,
autoscroll: Autoscroll,
cx: &mut Context<Self>,
) {
self.scroll_manager.autoscroll_request = Some((autoscroll, false));
cx.notify();
}
}