diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 936f4697c5..4aed3e5f5c 100644 --- a/crates/editor/src/display_map.rs +++ b/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> { + pub fn foldable_range(self: &Self, row: DisplayRow) -> Option> { 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,87 +661,6 @@ impl DisplaySnapshot { #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DisplayPoint(BlockPoint); -#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)] -#[repr(transparent)] -pub struct DisplayRow(pub u32); - -// TODO: Move display_map check into new, and then remove it from everywhere else -impl DisplayRow { - pub fn new(display_row: u32) -> Self { - DisplayRow(display_row) - } - - pub fn to_line_span(&self, display_map: &DisplaySnapshot) -> Range { - self.start(display_map)..self.end(&display_map) - } - - pub fn previous_row(&self, _display_map: &DisplaySnapshot) -> Option { - self.0.checked_sub(1).map(|prev_row| DisplayRow(prev_row)) - } - - pub fn next_row(&self, display_map: &DisplaySnapshot) -> Option { - if self.0 + 1 > display_map.max_point().row() { - None - } else { - Some(DisplayRow(self.0 + 1)) - } - } - - // TODO: Remove and use next_row - fn next_unchecked(&self) -> Option { - Some(DisplayRow(self.0 + 1)) - } - - pub fn next_rows( - &self, - display_map: &DisplaySnapshot, - ) -> Option> { - self.next_row(display_map) - .and_then(|next_row| next_row.span_to(display_map.max_point())) - } - - pub fn span_to>( - &self, - end_row: I, - ) -> Option> { - let end_row = end_row.into(); - if self.0 <= end_row.0 { - let start = *self; - let mut current = None; - - Some(std::iter::from_fn(move || { - if current == None { - current = Some(start); - } else { - current = current.unwrap().next_unchecked(); - } - if current.unwrap().0 > end_row.0 { - None - } else { - current - } - })) - } else { - None - } - } - - // Force users to think about the display map when using this type - pub fn start(&self, _display_map: &DisplaySnapshot) -> DisplayPoint { - DisplayPoint::new(self.0, 0) - } - - pub fn end(&self, display_map: &DisplaySnapshot) -> DisplayPoint { - DisplayPoint::new(self.0, display_map.line_len(self.0)) - } -} - -impl From for DisplayRow { - fn from(value: DisplayPoint) -> Self { - DisplayRow(value.row()) - } -} - impl Debug for DisplayPoint { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( @@ -817,6 +736,87 @@ impl ToDisplayPoint for Anchor { } } +#[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)] +#[repr(transparent)] +pub struct DisplayRow(pub u32); + +// TODO: Move display_map check into new, and then remove it from everywhere else +impl DisplayRow { + pub fn new(display_row: u32) -> Self { + DisplayRow(display_row) + } + + pub fn to_line_span(&self, display_map: &DisplaySnapshot) -> Range { + self.start()..self.end(&display_map) + } + + pub fn previous_row(&self) -> Option { + self.0.checked_sub(1).map(|prev_row| DisplayRow(prev_row)) + } + + pub fn next_row(&self, display_map: &DisplaySnapshot) -> Option { + if self.0 + 1 > display_map.max_point().row() { + None + } else { + Some(DisplayRow(self.0 + 1)) + } + } + + // TODO: Remove and use next_row + fn next_unchecked(&self) -> Option { + Some(DisplayRow(self.0 + 1)) + } + + pub fn next_rows( + &self, + display_map: &DisplaySnapshot, + ) -> Option> { + self.next_row(display_map) + .and_then(|next_row| next_row.span_to(display_map.max_point())) + } + + pub fn span_to>( + &self, + end_row: I, + ) -> Option> { + let end_row = end_row.into(); + if self.0 <= end_row.0 { + let start = *self; + let mut current = None; + + Some(std::iter::from_fn(move || { + if current == None { + current = Some(start); + } else { + current = current.unwrap().next_unchecked(); + } + if current.unwrap().0 > end_row.0 { + None + } else { + current + } + })) + } else { + None + } + } + + // Force users to think about the display map when using this type + pub fn start(&self) -> DisplayPoint { + DisplayPoint::new(self.0, 0) + } + + pub fn end(&self, display_map: &DisplaySnapshot) -> DisplayPoint { + DisplayPoint::new(self.0, display_map.line_len(self.0)) + } +} + +impl From for DisplayRow { + fn from(value: DisplayPoint) -> Self { + DisplayRow(value.row()) + } +} + #[cfg(test)] pub mod tests { use super::*; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 05075636aa..0586669624 100644 --- a/crates/editor/src/editor.rs +++ b/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>, style: &EditorStyle, cx: &mut RenderContext, - ) { + ) -> Option> { enum FoldIndicators {} - for (fold_location, fold_status) in fold_data.iter() { - fold_indicators.push(( - *fold_location, - MouseEventHandler::::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::::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, + display_map: &DisplaySnapshot, + cx: &mut ViewContext, + ) -> bool { + let selections = self.selections.all::(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) { 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) { 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); - self.unfold_ranges(std::iter::once(display_range), true, true, cx) + 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(unfold_range), true, autoscroll, cx) } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 68f136bddb..5b1e1273c6 100644 --- a/crates/editor/src/element.rs +++ b/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, 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> { + 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::(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)>, - fold_indicators: Vec<(u32, ElementBox)>, + fold_indicators: Option>, } pub struct PositionMap { diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 339cc53bf8..c0f08ac32e 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -146,14 +146,14 @@ where } pub trait MapRangeEndsExt { - fn map_endpoints(self, f: F) -> Range + fn map_range(self, f: F) -> Range where Self: Sized, F: Fn(R) -> T; } impl MapRangeEndsExt for Range { - fn map_endpoints(self, f: F) -> Range + fn map_range(self, f: F) -> Range where Self: Sized, F: Fn(R) -> T,