From 75bea91245442c4da3bf7a9fd17c60480d9aec27 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 14 Mar 2023 16:48:03 -0700 Subject: [PATCH] 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,