@@ -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<Buffer>,
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<u32>) -> 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<u32>) -> tab_map::HighlightedChunks {
+ self.tabs_snapshot.highlighted_chunks_for_rows(rows)
}
pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator<Item = char> + '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<Self::Item> {
- 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<Self::Item> {
- 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<Item = char>, 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<Item = char>,
- 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γ";
@@ -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<Snapshot>);
+
+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<InputEdit>,
+ ) -> (Snapshot, Vec<Edit>) {
+ 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<u32>) -> 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<Item = char>, 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<Item = char>,
+ 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<OutputOffset>,
+ pub new_bytes: Range<OutputOffset>,
+}
+
+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<Self::Item> {
+ 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<Self::Item> {
+ 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);
+ }
+}