From 75bea91245442c4da3bf7a9fd17c60480d9aec27 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 14 Mar 2023 16:48:03 -0700 Subject: [PATCH 1/5] Convert code folding to be in terms of buffer points instead of display points Co-authored-by: max --- crates/editor/src/display_map.rs | 76 +++++++++++++++-------- crates/editor/src/display_map/fold_map.rs | 20 +++--- crates/editor/src/display_map/tab_map.rs | 2 +- crates/editor/src/editor.rs | 27 ++++---- crates/editor/src/element.rs | 1 + 5 files changed, 77 insertions(+), 49 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 500f3626e348aeaf94cfa0efeff71ce5454ad01d..afa9980cb7fb760707c8c7ae6ed26feae0baa784 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -4,7 +4,7 @@ mod tab_map; mod wrap_map; use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; -use block_map::{BlockMap, BlockPoint}; +pub use block_map::{BlockMap, BlockPoint}; use collections::{HashMap, HashSet}; use fold_map::FoldMap; use gpui::{ @@ -249,7 +249,7 @@ pub struct DisplaySnapshot { folds_snapshot: fold_map::FoldSnapshot, tabs_snapshot: tab_map::TabSnapshot, wraps_snapshot: wrap_map::WrapSnapshot, - blocks_snapshot: block_map::BlockSnapshot, + pub blocks_snapshot: block_map::BlockSnapshot, text_highlights: TextHighlights, clip_at_line_ends: bool, } @@ -544,11 +544,8 @@ impl DisplaySnapshot { self.folds_snapshot.intersects_fold(offset) } - pub fn is_line_folded(&self, display_row: u32) -> bool { - let block_point = BlockPoint(Point::new(display_row, 0)); - let wrap_point = self.blocks_snapshot.to_wrap_point(block_point); - let tab_point = self.wraps_snapshot.to_tab_point(wrap_point); - self.folds_snapshot.is_line_folded(tab_point.row()) + pub fn is_line_folded(&self, buffer_row: u32) -> bool { + self.folds_snapshot.is_line_folded(buffer_row) } pub fn is_block_line(&self, display_row: u32) -> bool { @@ -594,6 +591,27 @@ impl DisplaySnapshot { (indent, is_blank) } + pub fn line_indent_for_buffer_row(&self, buffer_row: u32) -> (u32, bool) { + let (buffer, range) = self + .buffer_snapshot + .buffer_line_for_row(buffer_row) + .unwrap(); + let chars = buffer.chars_at(Point::new(range.start.row, 0)); + + let mut is_blank = false; + let mut indent_size = 0; + for c in chars { + // TODO: Handle tab expansion here + if c == ' ' { + indent_size += 1; + } else { + is_blank = c == '\n'; + break; + } + } + (indent_size, is_blank) + } + pub fn line_len(&self, row: u32) -> u32 { self.blocks_snapshot.line_len(row) } @@ -602,49 +620,55 @@ impl DisplaySnapshot { self.blocks_snapshot.longest_row() } - pub fn fold_for_line(self: &Self, display_row: u32) -> Option { - if self.is_foldable(display_row) { + pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option { + if self.is_foldable(buffer_row) { Some(FoldStatus::Foldable) - } else if self.is_line_folded(display_row) { + } else if self.is_line_folded(buffer_row) { Some(FoldStatus::Folded) } else { None } } - pub fn is_foldable(self: &Self, row: u32) -> bool { - let max_point = self.max_point(); - if row >= max_point.row() { + pub fn is_foldable(self: &Self, buffer_row: u32) -> bool { + let max_row = self.buffer_snapshot.max_buffer_row(); + if buffer_row >= max_row { return false; } - let (start_indent, is_blank) = self.line_indent(row); + let (indent_size, is_blank) = self.line_indent_for_buffer_row(buffer_row); if is_blank { return false; } - for display_row in next_rows(row, self) { - let (indent, is_blank) = self.line_indent(display_row); - if !is_blank { - return indent > start_indent; + for next_row in (buffer_row + 1)..max_row { + let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row); + if next_indent_size > indent_size { + return true; + } else if !next_line_is_blank { + break; } } - return false; + false } - pub fn foldable_range(self: &Self, row: u32) -> Option> { - let start = DisplayPoint::new(row, self.line_len(row)); + pub fn foldable_range(self: &Self, buffer_row: u32) -> Option> { + let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row)); - if self.is_foldable(row) && !self.is_line_folded(start.row()) { - let (start_indent, _) = self.line_indent(row); - let max_point = self.max_point(); + if self.is_foldable(start.row) && !self.is_line_folded(start.row) { + let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row); + let max_point = self.buffer_snapshot.max_point(); let mut end = None; - for row in next_rows(row, self) { + for row in (buffer_row + 1)..=max_point.row { let (indent, is_blank) = self.line_indent(row); if !is_blank && indent <= start_indent { - end = Some(DisplayPoint::new(row - 1, self.line_len(row - 1))); + let prev_row = row - 1; + end = Some(Point::new( + prev_row, + self.buffer_snapshot.line_len(prev_row), + )); break; } } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 6323461987aa58c1e280ee4fb1e869c34ddab4b4..d0663c736fc3b2f05e0c7fe78cca0b00dd44831a 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -639,14 +639,14 @@ impl FoldSnapshot { cursor.item().map_or(false, |t| t.output_text.is_some()) } - pub fn is_line_folded(&self, output_row: u32) -> bool { - let mut cursor = self.transforms.cursor::(); - cursor.seek(&FoldPoint::new(output_row, 0), Bias::Right, &()); + pub fn is_line_folded(&self, buffer_row: u32) -> bool { + let mut cursor = self.transforms.cursor::(); + cursor.seek(&Point::new(buffer_row, 0), Bias::Right, &()); while let Some(transform) = cursor.item() { if transform.output_text.is_some() { return true; } - if cursor.end(&()).row() == output_row { + if cursor.end(&()).row == buffer_row { cursor.next(&()) } else { break; @@ -1214,6 +1214,7 @@ pub type FoldEdit = Edit; mod tests { use super::*; use crate::{MultiBuffer, ToPoint}; + use collections::HashSet; use rand::prelude::*; use settings::Settings; use std::{cmp::Reverse, env, mem, sync::Arc}; @@ -1593,10 +1594,13 @@ mod tests { fold_row += 1; } - for fold_range in map.merged_fold_ranges() { - let fold_point = - snapshot.to_fold_point(fold_range.start.to_point(&buffer_snapshot), Right); - assert!(snapshot.is_line_folded(fold_point.row())); + let fold_start_rows = map + .merged_fold_ranges() + .iter() + .map(|range| range.start.to_point(&buffer_snapshot).row) + .collect::>(); + for row in 0..=buffer_snapshot.max_buffer_row() { + assert_eq!(snapshot.is_line_folded(row), fold_start_rows.contains(&row)); } for _ in 0..5 { diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 39bcdc4d9f1f3a71899e3cf274542789a64c61a3..5a5aae6135055e3d54041ae07c2774439f51fa80 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -262,7 +262,7 @@ impl TabSnapshot { .to_buffer_point(&self.fold_snapshot) } - fn expand_tabs( + pub fn expand_tabs( chars: impl Iterator, column: usize, tab_size: NonZeroU32, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 66169be652ee1402c2a6359095e709a7c89f1911..8963d9a1410f0109d61c4fb178a0afd09242dc95 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -40,6 +40,7 @@ use gpui::{ keymap_matcher::KeymapContext, platform::CursorStyle, serde_json::json, + text_layout::Line, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -2707,6 +2708,7 @@ impl Editor { &self, fold_data: Option>, active_rows: &BTreeMap, + line_layouts: &Vec>, style: &EditorStyle, gutter_hovered: bool, line_height: f32, @@ -2722,9 +2724,11 @@ impl Editor { .iter() .copied() .filter_map(|(fold_location, fold_status)| { - (gutter_hovered - || fold_status == FoldStatus::Folded - || !*active_rows.get(&fold_location).unwrap_or(&true)) + let has_line_number = line_layouts[fold_location as usize].is_some(); + (has_line_number + && (gutter_hovered + || fold_status == FoldStatus::Folded + || !*active_rows.get(&fold_location).unwrap_or(&true))) .then(|| { ( fold_location, @@ -5759,18 +5763,16 @@ impl Editor { let selections = self.selections.all::(cx); for selection in selections { - let range = selection.display_range(&display_map).sorted(); - let buffer_start_row = range.start.to_point(&display_map).row; + let range = selection.range().sorted(); + let buffer_start_row = range.start.row; - for row in (0..=range.end.row()).rev() { - let fold_range = display_map.foldable_range(row).map(|range| { - range.start.to_point(&display_map)..range.end.to_point(&display_map) - }); + for row in (0..=range.end.row).rev() { + let fold_range = display_map.foldable_range(row); 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() { + if row <= range.start.row { break; } } @@ -5791,10 +5793,7 @@ impl Editor { .selections .all::(cx) .iter() - .any(|selection| fold_range.overlaps(&selection.display_range(&display_map))); - - let fold_range = - fold_range.start.to_point(&display_map)..fold_range.end.to_point(&display_map); + .any(|selection| fold_range.overlaps(&selection.range())); self.fold_ranges(std::iter::once(fold_range), autoscroll, cx); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index f69902e53d993fbe60470a2b42f3d553c93a6c1f..fb9f44ab3622de902fdd1c4c4604bdff6b547c26 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1898,6 +1898,7 @@ impl Element for EditorElement { view.render_fold_indicators( folds, &active_rows, + &line_number_layouts, &style, view.gutter_hovered, line_height, From c427a8c58438b4455f84007b8423205141b69387 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Thu, 16 Mar 2023 08:41:19 -0700 Subject: [PATCH 2/5] WIP - DEBUGGING --- crates/editor/src/editor.rs | 12 +++++++++++- crates/editor/src/editor_tests.rs | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8963d9a1410f0109d61c4fb178a0afd09242dc95..3523413db49542a84b07cab9f8c484add2db409f 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5762,14 +5762,24 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self.selections.all::(cx); + dbg!(&selections); for selection in selections { let range = selection.range().sorted(); let buffer_start_row = range.start.row; for row in (0..=range.end.row).rev() { - let fold_range = display_map.foldable_range(row); + dbg!(row); + let fold_range = dbg!(display_map.foldable_range(row)); if let Some(fold_range) = fold_range { + let display_point = fold_range.end.to_display_point(&display_map); + let line = display_map + .chars_at(DisplayPoint::new(display_point.row(), 0)) + .map(|(ccharr, _)| ccharr) + .take_while(|charr| charr != &'\n') + .collect::(); + dbg!(line); + if fold_range.end.row >= buffer_start_row { fold_ranges.push(fold_range); if row <= range.start.row { diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 4f61bde39877bb0fb7ba40c0000077cf75c9fe82..92ec518c88fa156d989012ac87fb03df8bb417cc 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -679,7 +679,7 @@ fn test_fold(cx: &mut gpui::MutableAppContext) { " .unindent(), ); - + dbg!("SECOND FOLD"); view.fold(&Fold, cx); assert_eq!( view.display_text(cx), From eba119b91467996435d930572304a09ae0e77050 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 17 Mar 2023 16:00:22 -0700 Subject: [PATCH 3/5] Fix fold tests with new representation Switch UI code from using display rows to using buffer rows Make folds only show up on lines with line layouts co-authored-by: Max --- crates/editor/src/display_map.rs | 9 +- crates/editor/src/display_map/fold_map.rs | 4 +- crates/editor/src/editor.rs | 93 +++++++------------ crates/editor/src/element.rs | 104 ++++++++++++---------- 4 files changed, 93 insertions(+), 117 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index afa9980cb7fb760707c8c7ae6ed26feae0baa784..6748d0c1b001c575292029e4b393f9ceb624c763 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -621,10 +621,10 @@ impl DisplaySnapshot { } pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option { - if self.is_foldable(buffer_row) { - Some(FoldStatus::Foldable) - } else if self.is_line_folded(buffer_row) { + if self.is_line_folded(buffer_row) { Some(FoldStatus::Folded) + } else if self.is_foldable(buffer_row) { + Some(FoldStatus::Foldable) } else { None } @@ -655,14 +655,13 @@ impl DisplaySnapshot { pub fn foldable_range(self: &Self, buffer_row: u32) -> Option> { let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row)); - if self.is_foldable(start.row) && !self.is_line_folded(start.row) { let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row); let max_point = self.buffer_snapshot.max_point(); let mut end = None; for row in (buffer_row + 1)..=max_point.row { - let (indent, is_blank) = self.line_indent(row); + let (indent, is_blank) = self.line_indent_for_buffer_row(row); if !is_blank && indent <= start_indent { let prev_row = row - 1; end = Some(Point::new( diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index d0663c736fc3b2f05e0c7fe78cca0b00dd44831a..4e624c824ce2b3cbe66720313419ddade6f010da 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1599,8 +1599,8 @@ mod tests { .iter() .map(|range| range.start.to_point(&buffer_snapshot).row) .collect::>(); - for row in 0..=buffer_snapshot.max_buffer_row() { - assert_eq!(snapshot.is_line_folded(row), fold_start_rows.contains(&row)); + for row in fold_start_rows { + assert!(snapshot.is_line_folded(row)); } for _ in 0..5 { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3523413db49542a84b07cab9f8c484add2db409f..5f13a22128bc2e5c64f20b9a3b35e4034ddfadc7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -40,7 +40,6 @@ use gpui::{ keymap_matcher::KeymapContext, platform::CursorStyle, serde_json::json, - text_layout::Line, AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -164,12 +163,12 @@ pub struct ToggleComments { #[derive(Clone, Default, Deserialize, PartialEq)] pub struct FoldAt { - pub display_row: u32, + pub buffer_row: u32, } #[derive(Clone, Default, Deserialize, PartialEq)] pub struct UnfoldAt { - pub display_row: u32, + pub buffer_row: u32, } #[derive(Clone, Default, Deserialize, PartialEq)] @@ -2706,34 +2705,26 @@ impl Editor { pub fn render_fold_indicators( &self, - fold_data: Option>, - active_rows: &BTreeMap, - line_layouts: &Vec>, + fold_data: Vec>, style: &EditorStyle, gutter_hovered: bool, line_height: f32, gutter_margin: f32, cx: &mut RenderContext, - ) -> Option> { + ) -> Vec> { enum FoldIndicators {} let style = style.folds.clone(); - fold_data.map(|fold_data| { - fold_data - .iter() - .copied() - .filter_map(|(fold_location, fold_status)| { - let has_line_number = line_layouts[fold_location as usize].is_some(); - (has_line_number - && (gutter_hovered - || fold_status == FoldStatus::Folded - || !*active_rows.get(&fold_location).unwrap_or(&true))) - .then(|| { - ( - fold_location, + fold_data + .iter() + .enumerate() + .map(|(ix, fold_data)| { + fold_data + .map(|(fold_status, buffer_row, active)| { + (active || gutter_hovered || fold_status == FoldStatus::Folded).then(|| { MouseEventHandler::::new( - fold_location as usize, + ix as usize, cx, |mouse_state, _| -> ElementBox { Svg::new(match fold_status { @@ -2764,21 +2755,17 @@ impl Editor { .on_click(MouseButton::Left, { move |_, cx| { cx.dispatch_any_action(match fold_status { - FoldStatus::Folded => Box::new(UnfoldAt { - display_row: fold_location, - }), - FoldStatus::Foldable => Box::new(FoldAt { - display_row: fold_location, - }), + FoldStatus::Folded => Box::new(UnfoldAt { buffer_row }), + FoldStatus::Foldable => Box::new(FoldAt { buffer_row }), }); } }) - .boxed(), - ) + .boxed() + }) }) - }) - .collect() - }) + .flatten() + }) + .collect() } pub fn context_menu_visible(&self) -> bool { @@ -5762,24 +5749,14 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let selections = self.selections.all::(cx); - dbg!(&selections); for selection in selections { let range = selection.range().sorted(); let buffer_start_row = range.start.row; for row in (0..=range.end.row).rev() { - dbg!(row); - let fold_range = dbg!(display_map.foldable_range(row)); + let fold_range = display_map.foldable_range(row); if let Some(fold_range) = fold_range { - let display_point = fold_range.end.to_display_point(&display_map); - let line = display_map - .chars_at(DisplayPoint::new(display_point.row(), 0)) - .map(|(ccharr, _)| ccharr) - .take_while(|charr| charr != &'\n') - .collect::(); - dbg!(line); - if fold_range.end.row >= buffer_start_row { fold_ranges.push(fold_range); if row <= range.start.row { @@ -5794,11 +5771,11 @@ impl Editor { } pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext) { - let display_row = fold_at.display_row; + let buffer_row = fold_at.buffer_row; let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - if let Some(fold_range) = display_map.foldable_range(display_row) { + if let Some(fold_range) = display_map.foldable_range(buffer_row) { let autoscroll = self .selections .all::(cx) @@ -5831,25 +5808,19 @@ impl Editor { pub fn unfold_at(&mut self, unfold_at: &UnfoldAt, cx: &mut ViewContext) { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); - let intersection_range = DisplayPoint::new(unfold_at.display_row, 0) - ..DisplayPoint::new( - unfold_at.display_row, - display_map.line_len(unfold_at.display_row), + let intersection_range = Point::new(unfold_at.buffer_row, 0) + ..Point::new( + unfold_at.buffer_row, + display_map.buffer_snapshot.line_len(unfold_at.buffer_row), ); - let autoscroll = - self.selections.all::(cx).iter().any(|selection| { - intersection_range.overlaps(&selection.display_range(&display_map)) - }); - - let display_point = DisplayPoint::new(unfold_at.display_row, 0).to_point(&display_map); - - let mut point_range = display_point..display_point; - - point_range.start.column = 0; - point_range.end.column = display_map.buffer_snapshot.line_len(point_range.end.row); + let autoscroll = self + .selections + .all::(cx) + .iter() + .any(|selection| selection.range().overlaps(&intersection_range)); - self.unfold_ranges(std::iter::once(point_range), true, autoscroll, cx) + self.unfold_ranges(std::iter::once(intersection_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 fb9f44ab3622de902fdd1c4c4604bdff6b547c26..69e39b65d3825066b48f4a742ac28314214a9e0d 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -574,6 +574,20 @@ impl EditorElement { } } + for (ix, fold_indicator) in layout.fold_indicators.iter_mut().enumerate() { + if let Some(indicator) = fold_indicator.as_mut() { + let mut x = bounds.width() - layout.gutter_padding; + let mut y = ix as f32 * line_height - scroll_top; + + x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.; + y += (line_height - indicator.size().y()) / 2.; + + let indicator_origin = bounds.origin() + vec2f(x, y); + + indicator.paint(indicator_origin, visible_bounds, cx); + } + } + if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { let mut x = 0.; let mut y = *row as f32 * line_height - scroll_top; @@ -581,19 +595,6 @@ impl EditorElement { y += (line_height - indicator.size().y()) / 2.; indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); } - - 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.; - - fold_indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); - } - }); } fn paint_diff_hunks(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) { @@ -739,10 +740,15 @@ impl EditorElement { }); let display_row = range.start.row(); + + let buffer_row = DisplayPoint::new(display_row, 0) + .to_point(&layout.position_map.snapshot.display_snapshot) + .row; + cx.scene.push_mouse_region( MouseRegion::new::(self.view.id(), *id as usize, bound) .on_click(MouseButton::Left, move |_, cx| { - cx.dispatch_action(UnfoldAt { display_row }) + cx.dispatch_action(UnfoldAt { buffer_row }) }) .with_notify_on_hover(true) .with_notify_on_click(true), @@ -1181,24 +1187,6 @@ impl EditorElement { .width() } - fn get_fold_indicators( - &self, - is_singleton: bool, - display_rows: Range, - snapshot: &EditorSnapshot, - ) -> 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 //If a fold contains any hunks then that fold line is marked as modified fn layout_git_gutters( @@ -1226,12 +1214,17 @@ impl EditorElement { &self, rows: Range, active_rows: &BTreeMap, + is_singleton: bool, snapshot: &EditorSnapshot, cx: &LayoutContext, - ) -> Vec> { + ) -> ( + Vec>, + Vec>, + ) { let style = &self.style; let include_line_numbers = snapshot.mode == EditorMode::Full; let mut line_number_layouts = Vec::with_capacity(rows.len()); + let mut fold_statuses = Vec::with_capacity(rows.len()); let mut line_number = String::new(); for (ix, row) in snapshot .buffer_rows(rows.start) @@ -1239,10 +1232,10 @@ impl EditorElement { .enumerate() { let display_row = rows.start + ix as u32; - let color = if active_rows.contains_key(&display_row) { - style.line_number_active + let (active, color) = if active_rows.contains_key(&display_row) { + (true, style.line_number_active) } else { - style.line_number + (false, style.line_number) }; if let Some(buffer_row) = row { if include_line_numbers { @@ -1260,13 +1253,23 @@ impl EditorElement { }, )], ))); + fold_statuses.push( + is_singleton + .then(|| { + snapshot + .fold_for_line(buffer_row) + .map(|fold_status| (fold_status, buffer_row, active)) + }) + .flatten(), + ) } } else { + fold_statuses.push(None); line_number_layouts.push(None); } } - line_number_layouts + (line_number_layouts, fold_statuses) } fn layout_lines( @@ -1797,13 +1800,16 @@ impl Element for EditorElement { }) .collect(); - let line_number_layouts = - self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx); + let (line_number_layouts, fold_statuses) = self.layout_line_numbers( + start_row..end_row, + &active_rows, + is_singleton, + &snapshot, + cx, + ); let display_hunks = self.layout_git_gutters(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); let mut max_visible_line_width = 0.0; @@ -1896,9 +1902,7 @@ impl Element for EditorElement { mode = view.mode; view.render_fold_indicators( - folds, - &active_rows, - &line_number_layouts, + fold_statuses, &style, view.gutter_hovered, line_height, @@ -1930,8 +1934,8 @@ impl Element for EditorElement { ); } - fold_indicators.as_mut().map(|fold_indicators| { - for (_, indicator) in fold_indicators.iter_mut() { + for fold_indicator in fold_indicators.iter_mut() { + if let Some(indicator) = fold_indicator.as_mut() { indicator.layout( SizeConstraint::strict_along( Axis::Vertical, @@ -1940,7 +1944,7 @@ impl Element for EditorElement { cx, ); } - }); + } if let Some((_, hover_popovers)) = hover.as_mut() { for hover_popover in hover_popovers.iter_mut() { @@ -2124,7 +2128,7 @@ pub struct LayoutState { context_menu: Option<(DisplayPoint, ElementBox)>, code_actions_indicator: Option<(u32, ElementBox)>, hover_popovers: Option<(DisplayPoint, Vec)>, - fold_indicators: Option>, + fold_indicators: Vec>, } pub struct PositionMap { @@ -2525,7 +2529,9 @@ mod tests { let snapshot = editor.snapshot(cx); let mut presenter = cx.build_presenter(window_id, 30., Default::default()); let layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx); - element.layout_line_numbers(0..6, &Default::default(), &snapshot, &layout_cx) + element + .layout_line_numbers(0..6, &Default::default(), false, &snapshot, &layout_cx) + .0 }); assert_eq!(layouts.len(), 6); } From c39b4ac229eb45a7e43a1ade8d86db91c1a8e788 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 17 Mar 2023 16:56:44 -0700 Subject: [PATCH 4/5] Fix boundary condition in buffer_line_len when at the end of a file co-authored-by: max --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/editor.rs | 1 - crates/editor/src/multi_buffer.rs | 6 +++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 6748d0c1b001c575292029e4b393f9ceb624c763..8abc706dd7ba0b11f60743bc7dd33f30fe54d100 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -641,7 +641,7 @@ impl DisplaySnapshot { return false; } - for next_row in (buffer_row + 1)..max_row { + for next_row in (buffer_row + 1)..=max_row { let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row); if next_indent_size > indent_size { return true; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5f13a22128bc2e5c64f20b9a3b35e4034ddfadc7..c9c448b09f940227021f75c7e5f6a0f475d937c5 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5772,7 +5772,6 @@ impl Editor { pub fn fold_at(&mut self, fold_at: &FoldAt, cx: &mut ViewContext) { let buffer_row = fold_at.buffer_row; - let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); if let Some(fold_range) = display_map.foldable_range(buffer_row) { diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index e1e82859317929404182b7766d34b143ff5c01dc..0b85f94f087ab8da62a070d2c036103b53dcdd3d 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -2194,7 +2194,11 @@ impl MultiBufferSnapshot { pub fn buffer_line_for_row(&self, row: u32) -> Option<(&BufferSnapshot, Range)> { let mut cursor = self.excerpts.cursor::(); - cursor.seek(&Point::new(row, 0), Bias::Right, &()); + let point = Point::new(row, 0); + cursor.seek(&point, Bias::Right, &()); + if cursor.item().is_none() && *cursor.start() == point { + cursor.prev(&()); + } if let Some(excerpt) = cursor.item() { let overshoot = row - cursor.start().row; let excerpt_start = excerpt.range.context.start.to_point(&excerpt.buffer); From 5a3d5dff42bf1d9d74b572cab776ae6362b238fa Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Fri, 17 Mar 2023 17:12:24 -0700 Subject: [PATCH 5/5] Make folds tab aware --- crates/editor/src/display_map.rs | 31 +++++++++++++++++++------------ crates/editor/src/editor_tests.rs | 2 +- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 8abc706dd7ba0b11f60743bc7dd33f30fe54d100..fe960b42d32849adb3db10bddc5b7065d44dd8a0 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -24,6 +24,8 @@ pub use block_map::{ BlockDisposition, BlockId, BlockProperties, BlockStyle, RenderBlock, TransformBlock, }; +use self::tab_map::TabSnapshot; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum FoldStatus { Folded, @@ -249,7 +251,7 @@ pub struct DisplaySnapshot { folds_snapshot: fold_map::FoldSnapshot, tabs_snapshot: tab_map::TabSnapshot, wraps_snapshot: wrap_map::WrapSnapshot, - pub blocks_snapshot: block_map::BlockSnapshot, + blocks_snapshot: block_map::BlockSnapshot, text_highlights: TextHighlights, clip_at_line_ends: bool, } @@ -599,17 +601,22 @@ impl DisplaySnapshot { let chars = buffer.chars_at(Point::new(range.start.row, 0)); let mut is_blank = false; - let mut indent_size = 0; - for c in chars { - // TODO: Handle tab expansion here - if c == ' ' { - indent_size += 1; - } else { - is_blank = c == '\n'; - break; - } - } - (indent_size, is_blank) + let indent_size = TabSnapshot::expand_tabs( + chars.take_while(|c| { + if *c == ' ' || *c == '\t' { + true + } else { + if *c == '\n' { + is_blank = true; + } + false + } + }), + buffer.line_len(buffer_row) as usize, // Never collapse + self.tabs_snapshot.tab_size, + ); + + (indent_size as u32, is_blank) } pub fn line_len(&self, row: u32) -> u32 { diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 92ec518c88fa156d989012ac87fb03df8bb417cc..4f61bde39877bb0fb7ba40c0000077cf75c9fe82 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -679,7 +679,7 @@ fn test_fold(cx: &mut gpui::MutableAppContext) { " .unindent(), ); - dbg!("SECOND FOLD"); + view.fold(&Fold, cx); assert_eq!( view.display_text(cx),