Fixed autoscroll timing

Mikayla Maki created

Change summary

crates/editor/src/display_map.rs | 164 +++++++++++++++++-----------------
crates/editor/src/editor.rs      | 114 ++++++++++++++--------
crates/editor/src/element.rs     |  73 ++++++++------
crates/util/src/util.rs          |   4 
4 files changed, 198 insertions(+), 157 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -579,7 +579,7 @@ impl DisplaySnapshot {
     pub fn line_indent(&self, display_row: DisplayRow) -> (u32, bool) {
         let mut indent = 0;
         let mut is_blank = true;
-        for (c, _) in self.chars_at(display_row.start(self)) {
+        for (c, _) in self.chars_at(display_row.start()) {
             if c == ' ' {
                 indent += 1;
             } else {
@@ -626,7 +626,7 @@ impl DisplaySnapshot {
         return false;
     }
 
-    pub fn foldable_range(self: &Self, row: DisplayRow) -> Option<Range<Point>> {
+    pub fn foldable_range(self: &Self, row: DisplayRow) -> Option<Range<DisplayPoint>> {
         let start = row.end(&self);
 
         if self.is_foldable(row) && !self.is_line_folded(start.row()) {
@@ -637,13 +637,13 @@ impl DisplaySnapshot {
             for row in row.next_rows(self).unwrap() {
                 let (indent, is_blank) = self.line_indent(row);
                 if !is_blank && indent <= start_indent {
-                    end = row.previous_row(self);
+                    end = row.previous_row();
                     break;
                 }
             }
 
             let end = end.map(|end_row| end_row.end(self)).unwrap_or(max_point);
-            Some(start.to_point(self)..end.to_point(self))
+            Some(start..end)
         } else {
             None
         }
@@ -661,6 +661,81 @@ impl DisplaySnapshot {
 #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)]
 pub struct DisplayPoint(BlockPoint);
 
+impl Debug for DisplayPoint {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.write_fmt(format_args!(
+            "DisplayPoint({}, {})",
+            self.row(),
+            self.column()
+        ))
+    }
+}
+
+impl DisplayPoint {
+    pub fn new(row: u32, column: u32) -> Self {
+        Self(BlockPoint(Point::new(row, column)))
+    }
+
+    pub fn zero() -> Self {
+        Self::new(0, 0)
+    }
+
+    pub fn is_zero(&self) -> bool {
+        self.0.is_zero()
+    }
+
+    pub fn row(self) -> u32 {
+        self.0.row
+    }
+
+    pub fn column(self) -> u32 {
+        self.0.column
+    }
+
+    pub fn row_mut(&mut self) -> &mut u32 {
+        &mut self.0.row
+    }
+
+    pub fn column_mut(&mut self) -> &mut u32 {
+        &mut self.0.column
+    }
+
+    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
+        map.display_point_to_point(self, Bias::Left)
+    }
+
+    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
+        let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
+        let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
+        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
+        unexpanded_point.to_buffer_offset(&map.folds_snapshot)
+    }
+}
+
+impl ToDisplayPoint for usize {
+    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
+        map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
+    }
+}
+
+impl ToDisplayPoint for OffsetUtf16 {
+    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
+        self.to_offset(&map.buffer_snapshot).to_display_point(map)
+    }
+}
+
+impl ToDisplayPoint for Point {
+    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
+        map.point_to_display_point(*self, Bias::Left)
+    }
+}
+
+impl ToDisplayPoint for Anchor {
+    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
+        self.to_point(&map.buffer_snapshot).to_display_point(map)
+    }
+}
+
 #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)]
 #[repr(transparent)]
 pub struct DisplayRow(pub u32);
@@ -672,10 +747,10 @@ impl DisplayRow {
     }
 
     pub fn to_line_span(&self, display_map: &DisplaySnapshot) -> Range<DisplayPoint> {
-        self.start(display_map)..self.end(&display_map)
+        self.start()..self.end(&display_map)
     }
 
-    pub fn previous_row(&self, _display_map: &DisplaySnapshot) -> Option<DisplayRow> {
+    pub fn previous_row(&self) -> Option<DisplayRow> {
         self.0.checked_sub(1).map(|prev_row| DisplayRow(prev_row))
     }
 
@@ -727,7 +802,7 @@ impl DisplayRow {
     }
 
     // Force users to think about the display map when using this type
-    pub fn start(&self, _display_map: &DisplaySnapshot) -> DisplayPoint {
+    pub fn start(&self) -> DisplayPoint {
         DisplayPoint::new(self.0, 0)
     }
 
@@ -742,81 +817,6 @@ impl From<DisplayPoint> for DisplayRow {
     }
 }
 
-impl Debug for DisplayPoint {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.write_fmt(format_args!(
-            "DisplayPoint({}, {})",
-            self.row(),
-            self.column()
-        ))
-    }
-}
-
-impl DisplayPoint {
-    pub fn new(row: u32, column: u32) -> Self {
-        Self(BlockPoint(Point::new(row, column)))
-    }
-
-    pub fn zero() -> Self {
-        Self::new(0, 0)
-    }
-
-    pub fn is_zero(&self) -> bool {
-        self.0.is_zero()
-    }
-
-    pub fn row(self) -> u32 {
-        self.0.row
-    }
-
-    pub fn column(self) -> u32 {
-        self.0.column
-    }
-
-    pub fn row_mut(&mut self) -> &mut u32 {
-        &mut self.0.row
-    }
-
-    pub fn column_mut(&mut self) -> &mut u32 {
-        &mut self.0.column
-    }
-
-    pub fn to_point(self, map: &DisplaySnapshot) -> Point {
-        map.display_point_to_point(self, Bias::Left)
-    }
-
-    pub fn to_offset(self, map: &DisplaySnapshot, bias: Bias) -> usize {
-        let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0);
-        let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point);
-        let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0;
-        unexpanded_point.to_buffer_offset(&map.folds_snapshot)
-    }
-}
-
-impl ToDisplayPoint for usize {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        map.point_to_display_point(self.to_point(&map.buffer_snapshot), Bias::Left)
-    }
-}
-
-impl ToDisplayPoint for OffsetUtf16 {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        self.to_offset(&map.buffer_snapshot).to_display_point(map)
-    }
-}
-
-impl ToDisplayPoint for Point {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        map.point_to_display_point(*self, Bias::Left)
-    }
-}
-
-impl ToDisplayPoint for Anchor {
-    fn to_display_point(&self, map: &DisplaySnapshot) -> DisplayPoint {
-        self.to_point(&map.buffer_snapshot).to_display_point(map)
-    }
-}
-
 #[cfg(test)]
 pub mod tests {
     use super::*;

crates/editor/src/editor.rs 🔗

@@ -2686,47 +2686,51 @@ impl Editor {
 
     pub fn render_fold_indicators(
         &self,
-        fold_data: Vec<(u32, FoldStatus)>,
-        fold_indicators: &mut Vec<(u32, ElementBox)>,
+        fold_data: Option<Vec<(u32, FoldStatus)>>,
         style: &EditorStyle,
         cx: &mut RenderContext<Self>,
-    ) {
+    ) -> Option<Vec<(u32, ElementBox)>> {
         enum FoldIndicators {}
 
-        for (fold_location, fold_status) in fold_data.iter() {
-            fold_indicators.push((
-                *fold_location,
-                MouseEventHandler::<FoldIndicators>::new(
-                    *fold_location as usize,
-                    cx,
-                    |_, _| -> ElementBox {
-                        Svg::new(match *fold_status {
-                            FoldStatus::Folded => "icons/chevron_right_8.svg",
-                            FoldStatus::Foldable => "icons/chevron_down_8.svg",
+        fold_data.map(|fold_data| {
+            fold_data
+                .iter()
+                .map(|(fold_location, fold_status)| {
+                    (
+                        *fold_location,
+                        MouseEventHandler::<FoldIndicators>::new(
+                            *fold_location as usize,
+                            cx,
+                            |_, _| -> ElementBox {
+                                Svg::new(match *fold_status {
+                                    FoldStatus::Folded => "icons/chevron_right_8.svg",
+                                    FoldStatus::Foldable => "icons/chevron_down_8.svg",
+                                })
+                                .with_color(style.folds.indicator)
+                                .boxed()
+                            },
+                        )
+                        .with_cursor_style(CursorStyle::PointingHand)
+                        .with_padding(Padding::uniform(3.))
+                        .on_down(MouseButton::Left, {
+                            let fold_location = *fold_location;
+                            let fold_status = *fold_status;
+                            move |_, cx| {
+                                cx.dispatch_any_action(match fold_status {
+                                    FoldStatus::Folded => Box::new(UnfoldAt {
+                                        display_row: DisplayRow::new(fold_location),
+                                    }),
+                                    FoldStatus::Foldable => Box::new(FoldAt {
+                                        display_row: DisplayRow::new(fold_location),
+                                    }),
+                                });
+                            }
                         })
-                        .with_color(style.folds.indicator)
-                        .boxed()
-                    },
-                )
-                .with_cursor_style(CursorStyle::PointingHand)
-                .with_padding(Padding::uniform(3.))
-                .on_down(MouseButton::Left, {
-                    let fold_location = *fold_location;
-                    let fold_status = *fold_status;
-                    move |_, cx| {
-                        cx.dispatch_any_action(match fold_status {
-                            FoldStatus::Folded => Box::new(UnfoldAt {
-                                display_row: DisplayRow::new(fold_location),
-                            }),
-                            FoldStatus::Foldable => Box::new(FoldAt {
-                                display_row: DisplayRow::new(fold_location),
-                            }),
-                        });
-                    }
+                        .boxed(),
+                    )
                 })
-                .boxed(),
-            ))
-        }
+                .collect()
+        })
     }
 
     pub fn context_menu_visible(&self) -> bool {
@@ -5715,7 +5719,13 @@ impl Editor {
             let buffer_start_row = range.start.to_point(&display_map).row;
 
             for row in (0..=range.end.row()).rev() {
-                if let Some(fold_range) = display_map.foldable_range(DisplayRow::new(row)) {
+                let fold_range = display_map
+                    .foldable_range(DisplayRow::new(row))
+                    .map(|range| {
+                        range.map_range(|display_point| display_point.to_point(&display_map))
+                    });
+
+                if let Some(fold_range) = fold_range {
                     if fold_range.end.row >= buffer_start_row {
                         fold_ranges.push(fold_range);
                         if row <= range.start.row() {
@@ -5735,10 +5745,29 @@ impl Editor {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
 
         if let Some(fold_range) = display_map.foldable_range(display_row) {
-            self.fold_ranges(std::iter::once(fold_range), true, cx);
+            let autoscroll = self.selections_intersect(&fold_range, &display_map, cx);
+
+            let point_range =
+                fold_range.map_range(|display_point| display_point.to_point(&display_map));
+
+            self.fold_ranges(std::iter::once(point_range), autoscroll, cx);
         }
     }
 
+    fn selections_intersect(
+        &mut self,
+        range: &Range<DisplayPoint>,
+        display_map: &DisplaySnapshot,
+        cx: &mut ViewContext<Editor>,
+    ) -> bool {
+        let selections = self.selections.all::<Point>(cx);
+
+        selections.iter().any(|selection| {
+            let display_range = selection.display_range(display_map);
+            range.contains(&display_range.start) || range.contains(&display_range.end)
+        })
+    }
+
     pub fn unfold_lines(&mut self, _: &UnfoldLines, cx: &mut ViewContext<Self>) {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
         let buffer = &display_map.buffer_snapshot;
@@ -5760,12 +5789,13 @@ impl Editor {
     pub fn unfold_at(&mut self, fold_at: &UnfoldAt, cx: &mut ViewContext<Self>) {
         let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
 
-        let display_range = fold_at
-            .display_row
-            .to_line_span(&display_map)
-            .map_endpoints(|endpoint| endpoint.to_point(&display_map));
+        let unfold_range = fold_at.display_row.to_line_span(&display_map);
+
+        let autoscroll = self.selections_intersect(&unfold_range, &display_map, cx);
+
+        let unfold_range = unfold_range.map_range(|endpoint| endpoint.to_point(&display_map));
 
-        self.unfold_ranges(std::iter::once(display_range), true, true, cx)
+        self.unfold_ranges(std::iter::once(unfold_range), true, autoscroll, cx)
     }
 
     pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext<Self>) {

crates/editor/src/element.rs 🔗

@@ -48,6 +48,7 @@ use std::{
     ops::{DerefMut, Range},
     sync::Arc,
 };
+use workspace::item::Item;
 
 struct SelectionLayout {
     head: DisplayPoint,
@@ -576,15 +577,18 @@ impl EditorElement {
             indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
         }
 
-        for (line, fold_indicator) in layout.fold_indicators.iter_mut() {
-            let mut x = bounds.width() - layout.gutter_padding;
-            let mut y = *line as f32 * line_height - scroll_top;
+        layout.fold_indicators.as_mut().map(|fold_indicators| {
+            for (line, fold_indicator) in fold_indicators.iter_mut() {
+                let mut x = bounds.width() - layout.gutter_padding;
+                let mut y = *line as f32 * line_height - scroll_top;
 
-            x += ((layout.gutter_padding + layout.gutter_margin) - fold_indicator.size().x()) / 2.;
-            y += (line_height - fold_indicator.size().y()) / 2.;
+                x += ((layout.gutter_padding + layout.gutter_margin) - fold_indicator.size().x())
+                    / 2.;
+                y += (line_height - fold_indicator.size().y()) / 2.;
 
-            fold_indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
-        }
+                fold_indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx);
+            }
+        });
     }
 
     fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
@@ -1130,17 +1134,20 @@ impl EditorElement {
 
     fn get_fold_indicators(
         &self,
+        is_singleton: bool,
         display_rows: Range<u32>,
         snapshot: &EditorSnapshot,
-    ) -> Vec<(u32, FoldStatus)> {
-        display_rows
-            .into_iter()
-            .filter_map(|display_row| {
-                snapshot
-                    .fold_for_line(display_row)
-                    .map(|fold_status| (display_row, fold_status))
-            })
-            .collect()
+    ) -> Option<Vec<(u32, FoldStatus)>> {
+        is_singleton.then(|| {
+            display_rows
+                .into_iter()
+                .filter_map(|display_row| {
+                    snapshot
+                        .fold_for_line(display_row)
+                        .map(|fold_status| (display_row, fold_status))
+                })
+                .collect()
+        })
     }
 
     //Folds contained in a hunk are ignored apart from shrinking visual size
@@ -1633,7 +1640,10 @@ impl Element for EditorElement {
         let mut highlighted_ranges = Vec::new();
         let mut show_scrollbars = false;
         let mut include_root = false;
+        let mut is_singleton = false;
         self.update_view(cx.app, |view, cx| {
+            is_singleton = view.is_singleton(cx);
+
             let display_map = view.display_map.update(cx, |map, cx| map.snapshot(cx));
 
             highlighted_rows = view.highlighted_rows();
@@ -1714,7 +1724,7 @@ impl Element for EditorElement {
 
         let display_hunks = self.layout_git_gutters(start_row..end_row, &snapshot);
 
-        let folds = self.get_fold_indicators(start_row..end_row, &snapshot);
+        let folds = self.get_fold_indicators(is_singleton, start_row..end_row, &snapshot);
 
         let scrollbar_row_range = scroll_position.y()..(scroll_position.y() + height_in_lines);
 
@@ -1778,12 +1788,11 @@ impl Element for EditorElement {
             }
         });
 
-        let mut fold_indicators = Vec::with_capacity(folds.len());
         let mut context_menu = None;
         let mut code_actions_indicator = None;
         let mut hover = None;
         let mut mode = EditorMode::Full;
-        cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
+        let mut fold_indicators = cx.render(&self.view.upgrade(cx).unwrap(), |view, cx| {
             let newest_selection_head = view
                 .selections
                 .newest::<usize>(cx)
@@ -1802,11 +1811,11 @@ impl Element for EditorElement {
                     .map(|indicator| (newest_selection_head.row(), indicator));
             }
 
-            view.render_fold_indicators(folds, &mut fold_indicators, &style, cx);
-
             let visible_rows = start_row..start_row + line_layouts.len() as u32;
             hover = view.hover_state.render(&snapshot, &style, visible_rows, cx);
             mode = view.mode;
+
+            view.render_fold_indicators(folds, &style, cx)
         });
 
         if let Some((_, context_menu)) = context_menu.as_mut() {
@@ -1832,15 +1841,17 @@ impl Element for EditorElement {
             );
         }
 
-        for (_, indicator) in fold_indicators.iter_mut() {
-            indicator.layout(
-                SizeConstraint::strict_along(
-                    Axis::Vertical,
-                    line_height * style.code_actions.vertical_scale,
-                ),
-                cx,
-            );
-        }
+        fold_indicators.as_mut().map(|fold_indicators| {
+            for (_, indicator) in fold_indicators.iter_mut() {
+                indicator.layout(
+                    SizeConstraint::strict_along(
+                        Axis::Vertical,
+                        line_height * style.code_actions.vertical_scale,
+                    ),
+                    cx,
+                );
+            }
+        });
 
         if let Some((_, hover_popovers)) = hover.as_mut() {
             for hover_popover in hover_popovers.iter_mut() {
@@ -2020,7 +2031,7 @@ pub struct LayoutState {
     context_menu: Option<(DisplayPoint, ElementBox)>,
     code_actions_indicator: Option<(u32, ElementBox)>,
     hover_popovers: Option<(DisplayPoint, Vec<ElementBox>)>,
-    fold_indicators: Vec<(u32, ElementBox)>,
+    fold_indicators: Option<Vec<(u32, ElementBox)>>,
 }
 
 pub struct PositionMap {

crates/util/src/util.rs 🔗

@@ -146,14 +146,14 @@ where
 }
 
 pub trait MapRangeEndsExt<R> {
-    fn map_endpoints<T, F>(self, f: F) -> Range<T>
+    fn map_range<T, F>(self, f: F) -> Range<T>
     where
         Self: Sized,
         F: Fn(R) -> T;
 }
 
 impl<R> MapRangeEndsExt<R> for Range<R> {
-    fn map_endpoints<T, F>(self, f: F) -> Range<T>
+    fn map_range<T, F>(self, f: F) -> Range<T>
     where
         Self: Sized,
         F: Fn(R) -> T,