Convert code folding to be in terms of buffer points instead of display points
Co-authored-by: max <max@zed.dev>
This commit is contained in:
parent
828e9c1bb8
commit
75bea91245
5 changed files with 77 additions and 49 deletions
|
@ -4,7 +4,7 @@ mod tab_map;
|
||||||
mod wrap_map;
|
mod wrap_map;
|
||||||
|
|
||||||
use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
|
||||||
use block_map::{BlockMap, BlockPoint};
|
pub use block_map::{BlockMap, BlockPoint};
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{HashMap, HashSet};
|
||||||
use fold_map::FoldMap;
|
use fold_map::FoldMap;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
|
@ -249,7 +249,7 @@ pub struct DisplaySnapshot {
|
||||||
folds_snapshot: fold_map::FoldSnapshot,
|
folds_snapshot: fold_map::FoldSnapshot,
|
||||||
tabs_snapshot: tab_map::TabSnapshot,
|
tabs_snapshot: tab_map::TabSnapshot,
|
||||||
wraps_snapshot: wrap_map::WrapSnapshot,
|
wraps_snapshot: wrap_map::WrapSnapshot,
|
||||||
blocks_snapshot: block_map::BlockSnapshot,
|
pub blocks_snapshot: block_map::BlockSnapshot,
|
||||||
text_highlights: TextHighlights,
|
text_highlights: TextHighlights,
|
||||||
clip_at_line_ends: bool,
|
clip_at_line_ends: bool,
|
||||||
}
|
}
|
||||||
|
@ -544,11 +544,8 @@ impl DisplaySnapshot {
|
||||||
self.folds_snapshot.intersects_fold(offset)
|
self.folds_snapshot.intersects_fold(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_line_folded(&self, display_row: u32) -> bool {
|
pub fn is_line_folded(&self, buffer_row: u32) -> bool {
|
||||||
let block_point = BlockPoint(Point::new(display_row, 0));
|
self.folds_snapshot.is_line_folded(buffer_row)
|
||||||
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_block_line(&self, display_row: u32) -> bool {
|
pub fn is_block_line(&self, display_row: u32) -> bool {
|
||||||
|
@ -594,6 +591,27 @@ impl DisplaySnapshot {
|
||||||
(indent, is_blank)
|
(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 {
|
pub fn line_len(&self, row: u32) -> u32 {
|
||||||
self.blocks_snapshot.line_len(row)
|
self.blocks_snapshot.line_len(row)
|
||||||
}
|
}
|
||||||
|
@ -602,49 +620,55 @@ impl DisplaySnapshot {
|
||||||
self.blocks_snapshot.longest_row()
|
self.blocks_snapshot.longest_row()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fold_for_line(self: &Self, display_row: u32) -> Option<FoldStatus> {
|
pub fn fold_for_line(self: &Self, buffer_row: u32) -> Option<FoldStatus> {
|
||||||
if self.is_foldable(display_row) {
|
if self.is_foldable(buffer_row) {
|
||||||
Some(FoldStatus::Foldable)
|
Some(FoldStatus::Foldable)
|
||||||
} else if self.is_line_folded(display_row) {
|
} else if self.is_line_folded(buffer_row) {
|
||||||
Some(FoldStatus::Folded)
|
Some(FoldStatus::Folded)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_foldable(self: &Self, row: u32) -> bool {
|
pub fn is_foldable(self: &Self, buffer_row: u32) -> bool {
|
||||||
let max_point = self.max_point();
|
let max_row = self.buffer_snapshot.max_buffer_row();
|
||||||
if row >= max_point.row() {
|
if buffer_row >= max_row {
|
||||||
return false;
|
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 {
|
if is_blank {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for display_row in next_rows(row, self) {
|
for next_row in (buffer_row + 1)..max_row {
|
||||||
let (indent, is_blank) = self.line_indent(display_row);
|
let (next_indent_size, next_line_is_blank) = self.line_indent_for_buffer_row(next_row);
|
||||||
if !is_blank {
|
if next_indent_size > indent_size {
|
||||||
return indent > start_indent;
|
return true;
|
||||||
|
} else if !next_line_is_blank {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn foldable_range(self: &Self, row: u32) -> Option<Range<DisplayPoint>> {
|
pub fn foldable_range(self: &Self, buffer_row: u32) -> Option<Range<Point>> {
|
||||||
let start = DisplayPoint::new(row, self.line_len(row));
|
let start = Point::new(buffer_row, self.buffer_snapshot.line_len(buffer_row));
|
||||||
|
|
||||||
if self.is_foldable(row) && !self.is_line_folded(start.row()) {
|
if self.is_foldable(start.row) && !self.is_line_folded(start.row) {
|
||||||
let (start_indent, _) = self.line_indent(row);
|
let (start_indent, _) = self.line_indent_for_buffer_row(buffer_row);
|
||||||
let max_point = self.max_point();
|
let max_point = self.buffer_snapshot.max_point();
|
||||||
let mut end = None;
|
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);
|
let (indent, is_blank) = self.line_indent(row);
|
||||||
if !is_blank && indent <= start_indent {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -639,14 +639,14 @@ impl FoldSnapshot {
|
||||||
cursor.item().map_or(false, |t| t.output_text.is_some())
|
cursor.item().map_or(false, |t| t.output_text.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_line_folded(&self, output_row: u32) -> bool {
|
pub fn is_line_folded(&self, buffer_row: u32) -> bool {
|
||||||
let mut cursor = self.transforms.cursor::<FoldPoint>();
|
let mut cursor = self.transforms.cursor::<Point>();
|
||||||
cursor.seek(&FoldPoint::new(output_row, 0), Bias::Right, &());
|
cursor.seek(&Point::new(buffer_row, 0), Bias::Right, &());
|
||||||
while let Some(transform) = cursor.item() {
|
while let Some(transform) = cursor.item() {
|
||||||
if transform.output_text.is_some() {
|
if transform.output_text.is_some() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if cursor.end(&()).row() == output_row {
|
if cursor.end(&()).row == buffer_row {
|
||||||
cursor.next(&())
|
cursor.next(&())
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
|
@ -1214,6 +1214,7 @@ pub type FoldEdit = Edit<FoldOffset>;
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{MultiBuffer, ToPoint};
|
use crate::{MultiBuffer, ToPoint};
|
||||||
|
use collections::HashSet;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{cmp::Reverse, env, mem, sync::Arc};
|
use std::{cmp::Reverse, env, mem, sync::Arc};
|
||||||
|
@ -1593,10 +1594,13 @@ mod tests {
|
||||||
fold_row += 1;
|
fold_row += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for fold_range in map.merged_fold_ranges() {
|
let fold_start_rows = map
|
||||||
let fold_point =
|
.merged_fold_ranges()
|
||||||
snapshot.to_fold_point(fold_range.start.to_point(&buffer_snapshot), Right);
|
.iter()
|
||||||
assert!(snapshot.is_line_folded(fold_point.row()));
|
.map(|range| range.start.to_point(&buffer_snapshot).row)
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
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 {
|
for _ in 0..5 {
|
||||||
|
|
|
@ -262,7 +262,7 @@ impl TabSnapshot {
|
||||||
.to_buffer_point(&self.fold_snapshot)
|
.to_buffer_point(&self.fold_snapshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_tabs(
|
pub fn expand_tabs(
|
||||||
chars: impl Iterator<Item = char>,
|
chars: impl Iterator<Item = char>,
|
||||||
column: usize,
|
column: usize,
|
||||||
tab_size: NonZeroU32,
|
tab_size: NonZeroU32,
|
||||||
|
|
|
@ -40,6 +40,7 @@ use gpui::{
|
||||||
keymap_matcher::KeymapContext,
|
keymap_matcher::KeymapContext,
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
serde_json::json,
|
serde_json::json,
|
||||||
|
text_layout::Line,
|
||||||
AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
|
AnyViewHandle, AppContext, AsyncAppContext, ClipboardItem, Element, ElementBox, Entity,
|
||||||
ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
|
ModelHandle, MouseButton, MutableAppContext, RenderContext, Subscription, Task, View,
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
ViewContext, ViewHandle, WeakViewHandle,
|
||||||
|
@ -2707,6 +2708,7 @@ impl Editor {
|
||||||
&self,
|
&self,
|
||||||
fold_data: Option<Vec<(u32, FoldStatus)>>,
|
fold_data: Option<Vec<(u32, FoldStatus)>>,
|
||||||
active_rows: &BTreeMap<u32, bool>,
|
active_rows: &BTreeMap<u32, bool>,
|
||||||
|
line_layouts: &Vec<Option<Line>>,
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
gutter_hovered: bool,
|
gutter_hovered: bool,
|
||||||
line_height: f32,
|
line_height: f32,
|
||||||
|
@ -2722,9 +2724,11 @@ impl Editor {
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
.filter_map(|(fold_location, fold_status)| {
|
.filter_map(|(fold_location, fold_status)| {
|
||||||
(gutter_hovered
|
let has_line_number = line_layouts[fold_location as usize].is_some();
|
||||||
|| fold_status == FoldStatus::Folded
|
(has_line_number
|
||||||
|| !*active_rows.get(&fold_location).unwrap_or(&true))
|
&& (gutter_hovered
|
||||||
|
|| fold_status == FoldStatus::Folded
|
||||||
|
|| !*active_rows.get(&fold_location).unwrap_or(&true)))
|
||||||
.then(|| {
|
.then(|| {
|
||||||
(
|
(
|
||||||
fold_location,
|
fold_location,
|
||||||
|
@ -5759,18 +5763,16 @@ impl Editor {
|
||||||
|
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all::<Point>(cx);
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
let range = selection.display_range(&display_map).sorted();
|
let range = selection.range().sorted();
|
||||||
let buffer_start_row = range.start.to_point(&display_map).row;
|
let buffer_start_row = range.start.row;
|
||||||
|
|
||||||
for row in (0..=range.end.row()).rev() {
|
for row in (0..=range.end.row).rev() {
|
||||||
let fold_range = display_map.foldable_range(row).map(|range| {
|
let fold_range = display_map.foldable_range(row);
|
||||||
range.start.to_point(&display_map)..range.end.to_point(&display_map)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(fold_range) = fold_range {
|
if let Some(fold_range) = fold_range {
|
||||||
if fold_range.end.row >= buffer_start_row {
|
if fold_range.end.row >= buffer_start_row {
|
||||||
fold_ranges.push(fold_range);
|
fold_ranges.push(fold_range);
|
||||||
if row <= range.start.row() {
|
if row <= range.start.row {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5791,10 +5793,7 @@ impl Editor {
|
||||||
.selections
|
.selections
|
||||||
.all::<Point>(cx)
|
.all::<Point>(cx)
|
||||||
.iter()
|
.iter()
|
||||||
.any(|selection| fold_range.overlaps(&selection.display_range(&display_map)));
|
.any(|selection| fold_range.overlaps(&selection.range()));
|
||||||
|
|
||||||
let fold_range =
|
|
||||||
fold_range.start.to_point(&display_map)..fold_range.end.to_point(&display_map);
|
|
||||||
|
|
||||||
self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
|
self.fold_ranges(std::iter::once(fold_range), autoscroll, cx);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1898,6 +1898,7 @@ impl Element for EditorElement {
|
||||||
view.render_fold_indicators(
|
view.render_fold_indicators(
|
||||||
folds,
|
folds,
|
||||||
&active_rows,
|
&active_rows,
|
||||||
|
&line_number_layouts,
|
||||||
&style,
|
&style,
|
||||||
view.gutter_hovered,
|
view.gutter_hovered,
|
||||||
line_height,
|
line_height,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue