diff --git a/zed/src/editor/display_map.rs b/zed/src/editor/display_map.rs index c12fae47738ed80bc471f380593d7bdc23adbb51..44f8af34034ebeca37bca4d4e133ceb7fe8cbd4b 100644 --- a/zed/src/editor/display_map.rs +++ b/zed/src/editor/display_map.rs @@ -1,19 +1,20 @@ mod fold_map; +mod tab_map; // mod wrap_map; use super::{buffer, Anchor, Bias, Buffer, Point, ToOffset, ToPoint}; -use crate::settings::StyleId; use fold_map::FoldMap; pub use fold_map::InputRows; use gpui::{AppContext, ModelHandle}; -use std::{mem, ops::Range}; +use std::ops::Range; +use tab_map::TabMap; // use wrap_map::WrapMap; pub struct DisplayMap { buffer: ModelHandle, fold_map: FoldMap, + tab_map: TabMap, // wrap_map: WrapMap, - tab_size: usize, } impl DisplayMap { @@ -21,23 +22,25 @@ impl DisplayMap { let fold_map = FoldMap::new(buffer.clone(), cx); let (snapshot, edits) = fold_map.read(cx); assert_eq!(edits.len(), 0); + let tab_map = TabMap::new(snapshot, tab_size); // TODO: take `wrap_width` as a parameter. // let config = { todo!() }; // let wrap_map = WrapMap::new(snapshot, config, cx); DisplayMap { buffer, fold_map, + tab_map, // wrap_map, - tab_size, } } pub fn snapshot(&self, cx: &AppContext) -> DisplayMapSnapshot { let (folds_snapshot, edits) = self.fold_map.read(cx); + let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits); DisplayMapSnapshot { buffer_snapshot: self.buffer.read(cx).snapshot(), folds_snapshot, - tab_size: self.tab_size, + tabs_snapshot, } } @@ -63,7 +66,7 @@ impl DisplayMap { pub struct DisplayMapSnapshot { buffer_snapshot: buffer::Snapshot, folds_snapshot: fold_map::Snapshot, - tab_size: usize, + tabs_snapshot: tab_map::Snapshot, } impl DisplayMapSnapshot { @@ -72,35 +75,15 @@ impl DisplayMapSnapshot { } pub fn max_point(&self) -> DisplayPoint { - self.expand_tabs(self.folds_snapshot.max_point()) - } - - pub fn chunks_at(&self, point: DisplayPoint) -> Chunks { - let (point, expanded_char_column, to_next_stop) = self.collapse_tabs(point, Bias::Left); - let fold_chunks = self - .folds_snapshot - .chunks_at(self.folds_snapshot.to_output_offset(point)); - Chunks { - fold_chunks, - column: expanded_char_column, - tab_size: self.tab_size, - chunk: &SPACES[0..to_next_stop], - skip_leading_tab: to_next_stop > 0, - } + DisplayPoint(self.tabs_snapshot.max_point()) } - pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { - let start = DisplayPoint::new(rows.start, 0); - let start = self.folds_snapshot.to_output_offset(start.0); - let end = DisplayPoint::new(rows.end, 0).min(self.max_point()); - let end = self.folds_snapshot.to_output_offset(end.0); - HighlightedChunks { - fold_chunks: self.folds_snapshot.highlighted_chunks(start..end), - column: 0, - tab_size: self.tab_size, - chunk: "", - style_id: Default::default(), - } + pub fn chunks_at(&self, point: DisplayPoint) -> tab_map::Chunks { + self.tabs_snapshot.chunks_at(point.0) + } + + pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> tab_map::HighlightedChunks { + self.tabs_snapshot.highlighted_chunks_for_rows(rows) } pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { @@ -134,10 +117,7 @@ impl DisplayMapSnapshot { } pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { - self.expand_tabs( - self.folds_snapshot - .clip_point(self.collapse_tabs(point, bias).0, bias), - ) + DisplayPoint(self.tabs_snapshot.clip_point(point.0, bias)) } pub fn folds_in_range<'a, T>( @@ -190,15 +170,11 @@ impl DisplayMapSnapshot { } pub fn line_len(&self, row: u32) -> u32 { - self.expand_tabs(fold_map::OutputPoint::new( - row, - self.folds_snapshot.line_len(row), - )) - .column() + self.tabs_snapshot.line_len(row) } pub fn longest_row(&self) -> u32 { - self.folds_snapshot.longest_row() + self.tabs_snapshot.longest_row() } pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor { @@ -210,40 +186,14 @@ impl DisplayMapSnapshot { self.buffer_snapshot .anchor_after(point.to_buffer_point(self, bias)) } - - fn expand_tabs(&self, point: fold_map::OutputPoint) -> DisplayPoint { - let chars = self - .folds_snapshot - .chars_at(fold_map::OutputPoint::new(point.row(), 0)); - let expanded = expand_tabs(chars, point.column() as usize, self.tab_size); - DisplayPoint::new(point.row(), expanded as u32) - } - - fn collapse_tabs( - &self, - point: DisplayPoint, - bias: Bias, - ) -> (fold_map::OutputPoint, usize, usize) { - let chars = self - .folds_snapshot - .chars_at(fold_map::OutputPoint::new(point.row(), 0)); - let expanded = point.column() as usize; - let (collapsed, expanded_char_column, to_next_stop) = - collapse_tabs(chars, expanded, bias, self.tab_size); - ( - fold_map::OutputPoint::new(point.row(), collapsed as u32), - expanded_char_column, - to_next_stop, - ) - } } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct DisplayPoint(fold_map::OutputPoint); +pub struct DisplayPoint(tab_map::OutputPoint); impl DisplayPoint { pub fn new(row: u32, column: u32) -> Self { - Self(fold_map::OutputPoint::new(row, column)) + Self(tab_map::OutputPoint::new(row, column)) } pub fn zero() -> Self { @@ -268,24 +218,20 @@ impl DisplayPoint { pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point { map.folds_snapshot - .to_input_point(map.collapse_tabs(self, bias).0) + .to_input_point(map.tabs_snapshot.to_input_point(self.0, bias).0) } pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize { map.folds_snapshot - .to_input_offset(map.collapse_tabs(self, bias).0) + .to_input_offset(map.tabs_snapshot.to_input_point(self.0, bias).0) } } impl Point { pub fn to_display_point(self, map: &DisplayMapSnapshot) -> DisplayPoint { - let folded_point = map.folds_snapshot.to_output_point(self); - let chars = map - .folds_snapshot - .chars_at(fold_map::OutputPoint::new(folded_point.row(), 0)); - DisplayPoint::new( - folded_point.row(), - expand_tabs(chars, folded_point.column() as usize, map.tab_size) as u32, + DisplayPoint( + map.tabs_snapshot + .to_output_point(map.folds_snapshot.to_output_point(self)), ) } } @@ -296,163 +242,6 @@ impl Anchor { } } -// Handles a tab width <= 16 -const SPACES: &'static str = " "; - -pub struct Chunks<'a> { - fold_chunks: fold_map::Chunks<'a>, - chunk: &'a str, - column: usize, - tab_size: usize, - skip_leading_tab: bool, -} - -impl<'a> Iterator for Chunks<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - if self.chunk.is_empty() { - if let Some(chunk) = self.fold_chunks.next() { - self.chunk = chunk; - if self.skip_leading_tab { - self.chunk = &self.chunk[1..]; - self.skip_leading_tab = false; - } - } else { - return None; - } - } - - for (ix, c) in self.chunk.char_indices() { - match c { - '\t' => { - if ix > 0 { - let (prefix, suffix) = self.chunk.split_at(ix); - self.chunk = suffix; - return Some(prefix); - } else { - self.chunk = &self.chunk[1..]; - let len = self.tab_size - self.column % self.tab_size; - self.column += len; - return Some(&SPACES[0..len]); - } - } - '\n' => self.column = 0, - _ => self.column += 1, - } - } - - let result = Some(self.chunk); - self.chunk = ""; - result - } -} - -pub struct HighlightedChunks<'a> { - fold_chunks: fold_map::HighlightedChunks<'a>, - chunk: &'a str, - style_id: StyleId, - column: usize, - tab_size: usize, -} - -impl<'a> Iterator for HighlightedChunks<'a> { - type Item = (&'a str, StyleId); - - fn next(&mut self) -> Option { - if self.chunk.is_empty() { - if let Some((chunk, style_id)) = self.fold_chunks.next() { - self.chunk = chunk; - self.style_id = style_id; - } else { - return None; - } - } - - for (ix, c) in self.chunk.char_indices() { - match c { - '\t' => { - if ix > 0 { - let (prefix, suffix) = self.chunk.split_at(ix); - self.chunk = suffix; - return Some((prefix, self.style_id)); - } else { - self.chunk = &self.chunk[1..]; - let len = self.tab_size - self.column % self.tab_size; - self.column += len; - return Some((&SPACES[0..len], self.style_id)); - } - } - '\n' => self.column = 0, - _ => self.column += 1, - } - } - - Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id))) - } -} - -pub fn expand_tabs(chars: impl Iterator, column: usize, tab_size: usize) -> usize { - let mut expanded_chars = 0; - let mut expanded_bytes = 0; - let mut collapsed_bytes = 0; - for c in chars { - if collapsed_bytes == column { - break; - } - if c == '\t' { - let tab_len = tab_size - expanded_chars % tab_size; - expanded_bytes += tab_len; - expanded_chars += tab_len; - } else { - expanded_bytes += c.len_utf8(); - expanded_chars += 1; - } - collapsed_bytes += c.len_utf8(); - } - expanded_bytes -} - -pub fn collapse_tabs( - mut chars: impl Iterator, - column: usize, - bias: Bias, - tab_size: usize, -) -> (usize, usize, usize) { - let mut expanded_bytes = 0; - let mut expanded_chars = 0; - let mut collapsed_bytes = 0; - while let Some(c) = chars.next() { - if expanded_bytes >= column { - break; - } - - if c == '\t' { - let tab_len = tab_size - (expanded_chars % tab_size); - expanded_chars += tab_len; - expanded_bytes += tab_len; - if expanded_bytes > column { - expanded_chars -= expanded_bytes - column; - return match bias { - Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column), - Bias::Right => (collapsed_bytes + 1, expanded_chars, 0), - }; - } - } else { - expanded_chars += 1; - expanded_bytes += c.len_utf8(); - } - - if expanded_bytes > column && matches!(bias, Bias::Left) { - expanded_chars -= 1; - break; - } - - collapsed_bytes += c.len_utf8(); - } - (collapsed_bytes, expanded_chars, 0) -} - #[cfg(test)] mod tests { use super::*; @@ -640,13 +429,6 @@ mod tests { } } - #[test] - fn test_expand_tabs() { - assert_eq!(expand_tabs("\t".chars(), 0, 4), 0); - assert_eq!(expand_tabs("\t".chars(), 1, 4), 4); - assert_eq!(expand_tabs("\ta".chars(), 2, 4), 5); - } - #[gpui::test] fn test_tabs_with_multibyte_chars(cx: &mut gpui::MutableAppContext) { let text = "✅\t\tα\nβ\t\n🏀β\t\tγ"; diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index 77b99618b2bc14bca83712c9fe96d82ab5700b40..f3a6d9aa5cbb2a299932abe8518f88a03c545e41 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -1041,7 +1041,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputPoint { pub struct OutputOffset(pub usize); impl OutputOffset { - pub fn to_output_point(&self, snapshot: &Snapshot) -> OutputPoint { + pub fn to_point(&self, snapshot: &Snapshot) -> OutputPoint { let mut cursor = snapshot .transforms .cursor::(); diff --git a/zed/src/editor/display_map/tab_map.rs b/zed/src/editor/display_map/tab_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..2628af0a32d29829582e88325080ef115c6c3979 --- /dev/null +++ b/zed/src/editor/display_map/tab_map.rs @@ -0,0 +1,344 @@ +use parking_lot::Mutex; + +use super::fold_map::{ + Chunks as InputChunks, Edit as InputEdit, HighlightedChunks as InputHighlightedChunks, + OutputOffset as InputOffset, OutputPoint as InputPoint, Snapshot as InputSnapshot, +}; +use crate::{settings::StyleId, util::Bias}; +use std::{mem, ops::Range}; + +pub struct TabMap(Mutex); + +impl TabMap { + pub fn new(input: InputSnapshot, tab_size: usize) -> Self { + Self(Mutex::new(Snapshot { input, tab_size })) + } + + pub fn sync( + &self, + snapshot: InputSnapshot, + input_edits: Vec, + ) -> (Snapshot, Vec) { + let mut old_snapshot = self.0.lock(); + let new_snapshot = Snapshot { + input: snapshot, + tab_size: old_snapshot.tab_size, + }; + + let mut output_edits = Vec::with_capacity(input_edits.len()); + for input_edit in input_edits { + output_edits.push(Edit { + old_bytes: old_snapshot.to_output_offset(input_edit.old_bytes.start) + ..old_snapshot.to_output_offset(input_edit.old_bytes.end), + new_bytes: new_snapshot.to_output_offset(input_edit.new_bytes.start) + ..new_snapshot.to_output_offset(input_edit.new_bytes.end), + }); + } + + *old_snapshot = new_snapshot; + (old_snapshot.clone(), output_edits) + } +} + +#[derive(Clone)] +pub struct Snapshot { + input: InputSnapshot, + tab_size: usize, +} + +impl Snapshot { + pub fn chunks_at(&self, point: OutputPoint) -> Chunks { + let (point, expanded_char_column, to_next_stop) = self.to_input_point(point, Bias::Left); + let fold_chunks = self.input.chunks_at(self.input.to_output_offset(point)); + Chunks { + fold_chunks, + column: expanded_char_column, + tab_size: self.tab_size, + chunk: &SPACES[0..to_next_stop], + skip_leading_tab: to_next_stop > 0, + } + } + + pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { + let start = self.input.to_output_offset(InputPoint::new(rows.start, 0)); + let end = self + .input + .to_output_offset(InputPoint::new(rows.end, 0).min(self.input.max_point())); + HighlightedChunks { + input_chunks: self.input.highlighted_chunks(start..end), + column: 0, + tab_size: self.tab_size, + chunk: "", + style_id: Default::default(), + } + } + + pub fn line_len(&self, row: u32) -> u32 { + self.to_output_point(InputPoint::new(row, self.input.line_len(row))) + .column() + } + + pub fn longest_row(&self) -> u32 { + // TODO: Account for tab expansion. + self.input.longest_row() + } + + pub fn max_point(&self) -> OutputPoint { + self.to_output_point(self.input.max_point()) + } + + pub fn clip_point(&self, point: OutputPoint, bias: Bias) -> OutputPoint { + self.to_output_point( + self.input + .clip_point(self.to_input_point(point, bias).0, bias), + ) + } + + pub fn to_output_offset(&self, input_offset: InputOffset) -> OutputOffset { + let input_point = input_offset.to_point(&self.input); + let input_row_start_offset = self + .input + .to_output_offset(InputPoint::new(input_point.row(), 0)); + let output_point = self.to_output_point(input_point); + OutputOffset(input_row_start_offset.0 + output_point.column() as usize) + } + + pub fn to_output_point(&self, input: InputPoint) -> OutputPoint { + let chars = self.input.chars_at(InputPoint::new(input.row(), 0)); + let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size); + OutputPoint::new(input.row(), expanded as u32) + } + + pub fn to_input_point(&self, output: OutputPoint, bias: Bias) -> (InputPoint, usize, usize) { + let chars = self.input.chars_at(InputPoint::new(output.row(), 0)); + let expanded = output.column() as usize; + let (collapsed, expanded_char_column, to_next_stop) = + Self::collapse_tabs(chars, expanded, bias, self.tab_size); + ( + InputPoint::new(output.row(), collapsed as u32), + expanded_char_column, + to_next_stop, + ) + } + + fn expand_tabs(chars: impl Iterator, column: usize, tab_size: usize) -> usize { + let mut expanded_chars = 0; + let mut expanded_bytes = 0; + let mut collapsed_bytes = 0; + for c in chars { + if collapsed_bytes == column { + break; + } + if c == '\t' { + let tab_len = tab_size - expanded_chars % tab_size; + expanded_bytes += tab_len; + expanded_chars += tab_len; + } else { + expanded_bytes += c.len_utf8(); + expanded_chars += 1; + } + collapsed_bytes += c.len_utf8(); + } + expanded_bytes + } + + fn collapse_tabs( + mut chars: impl Iterator, + column: usize, + bias: Bias, + tab_size: usize, + ) -> (usize, usize, usize) { + let mut expanded_bytes = 0; + let mut expanded_chars = 0; + let mut collapsed_bytes = 0; + while let Some(c) = chars.next() { + if expanded_bytes >= column { + break; + } + + if c == '\t' { + let tab_len = tab_size - (expanded_chars % tab_size); + expanded_chars += tab_len; + expanded_bytes += tab_len; + if expanded_bytes > column { + expanded_chars -= expanded_bytes - column; + return match bias { + Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column), + Bias::Right => (collapsed_bytes + 1, expanded_chars, 0), + }; + } + } else { + expanded_chars += 1; + expanded_bytes += c.len_utf8(); + } + + if expanded_bytes > column && matches!(bias, Bias::Left) { + expanded_chars -= 1; + break; + } + + collapsed_bytes += c.len_utf8(); + } + (collapsed_bytes, expanded_chars, 0) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct OutputOffset(usize); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Edit { + pub old_bytes: Range, + pub new_bytes: Range, +} + +impl Edit { + pub fn delta(&self) -> isize { + self.inserted_bytes() as isize - self.deleted_bytes() as isize + } + + pub fn deleted_bytes(&self) -> usize { + self.old_bytes.end.0 - self.old_bytes.start.0 + } + + pub fn inserted_bytes(&self) -> usize { + self.new_bytes.end.0 - self.new_bytes.start.0 + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct OutputPoint(super::Point); + +impl OutputPoint { + pub fn new(row: u32, column: u32) -> Self { + Self(super::Point::new(row, column)) + } + + pub fn zero() -> Self { + Self::new(0, 0) + } + + 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 + } +} + +// Handles a tab width <= 16 +const SPACES: &'static str = " "; + +pub struct Chunks<'a> { + fold_chunks: InputChunks<'a>, + chunk: &'a str, + column: usize, + tab_size: usize, + skip_leading_tab: bool, +} + +impl<'a> Iterator for Chunks<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option { + if self.chunk.is_empty() { + if let Some(chunk) = self.fold_chunks.next() { + self.chunk = chunk; + if self.skip_leading_tab { + self.chunk = &self.chunk[1..]; + self.skip_leading_tab = false; + } + } else { + return None; + } + } + + for (ix, c) in self.chunk.char_indices() { + match c { + '\t' => { + if ix > 0 { + let (prefix, suffix) = self.chunk.split_at(ix); + self.chunk = suffix; + return Some(prefix); + } else { + self.chunk = &self.chunk[1..]; + let len = self.tab_size - self.column % self.tab_size; + self.column += len; + return Some(&SPACES[0..len]); + } + } + '\n' => self.column = 0, + _ => self.column += 1, + } + } + + let result = Some(self.chunk); + self.chunk = ""; + result + } +} + +pub struct HighlightedChunks<'a> { + input_chunks: InputHighlightedChunks<'a>, + chunk: &'a str, + style_id: StyleId, + column: usize, + tab_size: usize, +} + +impl<'a> Iterator for HighlightedChunks<'a> { + type Item = (&'a str, StyleId); + + fn next(&mut self) -> Option { + if self.chunk.is_empty() { + if let Some((chunk, style_id)) = self.input_chunks.next() { + self.chunk = chunk; + self.style_id = style_id; + } else { + return None; + } + } + + for (ix, c) in self.chunk.char_indices() { + match c { + '\t' => { + if ix > 0 { + let (prefix, suffix) = self.chunk.split_at(ix); + self.chunk = suffix; + return Some((prefix, self.style_id)); + } else { + self.chunk = &self.chunk[1..]; + let len = self.tab_size - self.column % self.tab_size; + self.column += len; + return Some((&SPACES[0..len], self.style_id)); + } + } + '\n' => self.column = 0, + _ => self.column += 1, + } + } + + Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_expand_tabs() { + assert_eq!(Snapshot::expand_tabs("\t".chars(), 0, 4), 0); + assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4); + assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5); + } +}