tab_map.rs

  1use super::{
  2    fold_map::{self, FoldEdit, FoldPoint, FoldSnapshot},
  3    TextHighlights,
  4};
  5use crate::MultiBufferSnapshot;
  6use language::Chunk;
  7use parking_lot::Mutex;
  8use rope;
  9use rope::point::Point;
 10use std::{cmp, mem, num::NonZeroU32, ops::Range};
 11use sum_tree::Bias;
 12
 13pub struct TabMap(Mutex<TabSnapshot>);
 14
 15impl TabMap {
 16    pub fn new(input: FoldSnapshot, tab_size: NonZeroU32) -> (Self, TabSnapshot) {
 17        let snapshot = TabSnapshot {
 18            fold_snapshot: input,
 19            tab_size,
 20            version: 0,
 21        };
 22        (Self(Mutex::new(snapshot.clone())), snapshot)
 23    }
 24
 25    pub fn sync(
 26        &self,
 27        fold_snapshot: FoldSnapshot,
 28        mut fold_edits: Vec<FoldEdit>,
 29        tab_size: NonZeroU32,
 30    ) -> (TabSnapshot, Vec<TabEdit>) {
 31        let mut old_snapshot = self.0.lock();
 32        let mut new_snapshot = TabSnapshot {
 33            fold_snapshot,
 34            tab_size,
 35            version: old_snapshot.version,
 36        };
 37
 38        if old_snapshot.fold_snapshot.version != new_snapshot.fold_snapshot.version {
 39            new_snapshot.version += 1;
 40        }
 41
 42        let old_max_offset = old_snapshot.fold_snapshot.len();
 43        let mut tab_edits = Vec::with_capacity(fold_edits.len());
 44
 45        if old_snapshot.tab_size == new_snapshot.tab_size {
 46            for fold_edit in &mut fold_edits {
 47                let mut delta = 0;
 48                for chunk in old_snapshot.fold_snapshot.chunks(
 49                    fold_edit.old.end..old_max_offset,
 50                    false,
 51                    None,
 52                ) {
 53                    let patterns: &[_] = &['\t', '\n'];
 54                    if let Some(ix) = chunk.text.find(patterns) {
 55                        if &chunk.text[ix..ix + 1] == "\t" {
 56                            fold_edit.old.end.0 += delta + ix + 1;
 57                            fold_edit.new.end.0 += delta + ix + 1;
 58                        }
 59
 60                        break;
 61                    }
 62
 63                    delta += chunk.text.len();
 64                }
 65            }
 66
 67            let mut ix = 1;
 68            while ix < fold_edits.len() {
 69                let (prev_edits, next_edits) = fold_edits.split_at_mut(ix);
 70                let prev_edit = prev_edits.last_mut().unwrap();
 71                let edit = &next_edits[0];
 72                if prev_edit.old.end >= edit.old.start {
 73                    prev_edit.old.end = edit.old.end;
 74                    prev_edit.new.end = edit.new.end;
 75                    fold_edits.remove(ix);
 76                } else {
 77                    ix += 1;
 78                }
 79            }
 80
 81            for fold_edit in fold_edits {
 82                let old_start = fold_edit.old.start.to_point(&old_snapshot.fold_snapshot);
 83                let old_end = fold_edit.old.end.to_point(&old_snapshot.fold_snapshot);
 84                let new_start = fold_edit.new.start.to_point(&new_snapshot.fold_snapshot);
 85                let new_end = fold_edit.new.end.to_point(&new_snapshot.fold_snapshot);
 86                tab_edits.push(TabEdit {
 87                    old: old_snapshot.to_tab_point(old_start)..old_snapshot.to_tab_point(old_end),
 88                    new: new_snapshot.to_tab_point(new_start)..new_snapshot.to_tab_point(new_end),
 89                });
 90            }
 91        } else {
 92            new_snapshot.version += 1;
 93            tab_edits.push(TabEdit {
 94                old: TabPoint::zero()..old_snapshot.max_point(),
 95                new: TabPoint::zero()..new_snapshot.max_point(),
 96            });
 97        }
 98
 99        *old_snapshot = new_snapshot;
100        (old_snapshot.clone(), tab_edits)
101    }
102}
103
104#[derive(Clone)]
105pub struct TabSnapshot {
106    pub fold_snapshot: FoldSnapshot,
107    pub tab_size: NonZeroU32,
108    pub version: usize,
109}
110
111impl TabSnapshot {
112    pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
113        self.fold_snapshot.buffer_snapshot()
114    }
115
116    pub fn line_len(&self, row: u32) -> u32 {
117        let max_point = self.max_point();
118        if row < max_point.row() {
119            self.chunks(
120                TabPoint::new(row, 0)..TabPoint::new(row + 1, 0),
121                false,
122                None,
123            )
124            .map(|chunk| chunk.text.len() as u32)
125            .sum::<u32>()
126                - 1
127        } else {
128            max_point.column()
129        }
130    }
131
132    pub fn text_summary(&self) -> TextSummary {
133        self.text_summary_for_range(TabPoint::zero()..self.max_point())
134    }
135
136    pub fn text_summary_for_range(&self, range: Range<TabPoint>) -> TextSummary {
137        let input_start = self.to_fold_point(range.start, Bias::Left).0;
138        let input_end = self.to_fold_point(range.end, Bias::Right).0;
139        let input_summary = self
140            .fold_snapshot
141            .text_summary_for_range(input_start..input_end);
142
143        let mut first_line_chars = 0;
144        let line_end = if range.start.row() == range.end.row() {
145            range.end
146        } else {
147            self.max_point()
148        };
149        for c in self
150            .chunks(range.start..line_end, false, None)
151            .flat_map(|chunk| chunk.text.chars())
152        {
153            if c == '\n' {
154                break;
155            }
156            first_line_chars += 1;
157        }
158
159        let mut last_line_chars = 0;
160        if range.start.row() == range.end.row() {
161            last_line_chars = first_line_chars;
162        } else {
163            for _ in self
164                .chunks(TabPoint::new(range.end.row(), 0)..range.end, false, None)
165                .flat_map(|chunk| chunk.text.chars())
166            {
167                last_line_chars += 1;
168            }
169        }
170
171        TextSummary {
172            lines: range.end.0 - range.start.0,
173            first_line_chars,
174            last_line_chars,
175            longest_row: input_summary.longest_row,
176            longest_row_chars: input_summary.longest_row_chars,
177        }
178    }
179
180    pub fn chunks<'a>(
181        &'a self,
182        range: Range<TabPoint>,
183        language_aware: bool,
184        text_highlights: Option<&'a TextHighlights>,
185    ) -> TabChunks<'a> {
186        let (input_start, expanded_char_column, to_next_stop) =
187            self.to_fold_point(range.start, Bias::Left);
188        let input_start = input_start.to_offset(&self.fold_snapshot);
189        let input_end = self
190            .to_fold_point(range.end, Bias::Right)
191            .0
192            .to_offset(&self.fold_snapshot);
193        let to_next_stop = if range.start.0 + Point::new(0, to_next_stop as u32) > range.end.0 {
194            (range.end.column() - range.start.column()) as usize
195        } else {
196            to_next_stop
197        };
198
199        TabChunks {
200            fold_chunks: self.fold_snapshot.chunks(
201                input_start..input_end,
202                language_aware,
203                text_highlights,
204            ),
205            column: expanded_char_column,
206            output_position: range.start.0,
207            max_output_position: range.end.0,
208            tab_size: self.tab_size,
209            chunk: Chunk {
210                text: &SPACES[0..to_next_stop],
211                ..Default::default()
212            },
213            skip_leading_tab: to_next_stop > 0,
214        }
215    }
216
217    pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows {
218        self.fold_snapshot.buffer_rows(row)
219    }
220
221    #[cfg(test)]
222    pub fn text(&self) -> String {
223        self.chunks(TabPoint::zero()..self.max_point(), false, None)
224            .map(|chunk| chunk.text)
225            .collect()
226    }
227
228    pub fn max_point(&self) -> TabPoint {
229        self.to_tab_point(self.fold_snapshot.max_point())
230    }
231
232    pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
233        self.to_tab_point(
234            self.fold_snapshot
235                .clip_point(self.to_fold_point(point, bias).0, bias),
236        )
237    }
238
239    pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
240        let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0));
241        let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size);
242        TabPoint::new(input.row(), expanded as u32)
243    }
244
245    pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) {
246        let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0));
247        let expanded = output.column() as usize;
248        let (collapsed, expanded_char_column, to_next_stop) =
249            Self::collapse_tabs(chars, expanded, bias, self.tab_size);
250        (
251            FoldPoint::new(output.row(), collapsed as u32),
252            expanded_char_column,
253            to_next_stop,
254        )
255    }
256
257    pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint {
258        self.to_tab_point(self.fold_snapshot.to_fold_point(point, bias))
259    }
260
261    pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point {
262        self.to_fold_point(point, bias)
263            .0
264            .to_buffer_point(&self.fold_snapshot)
265    }
266
267    fn expand_tabs(
268        chars: impl Iterator<Item = char>,
269        column: usize,
270        tab_size: NonZeroU32,
271    ) -> usize {
272        let mut expanded_chars = 0;
273        let mut expanded_bytes = 0;
274        let mut collapsed_bytes = 0;
275        for c in chars {
276            if collapsed_bytes == column {
277                break;
278            }
279            if c == '\t' {
280                let tab_size = tab_size.get() as usize;
281                let tab_len = tab_size - expanded_chars % tab_size;
282                expanded_bytes += tab_len;
283                expanded_chars += tab_len;
284            } else {
285                expanded_bytes += c.len_utf8();
286                expanded_chars += 1;
287            }
288            collapsed_bytes += c.len_utf8();
289        }
290        expanded_bytes
291    }
292
293    fn collapse_tabs(
294        chars: impl Iterator<Item = char>,
295        column: usize,
296        bias: Bias,
297        tab_size: NonZeroU32,
298    ) -> (usize, usize, usize) {
299        let mut expanded_bytes = 0;
300        let mut expanded_chars = 0;
301        let mut collapsed_bytes = 0;
302        for c in chars {
303            if expanded_bytes >= column {
304                break;
305            }
306
307            if c == '\t' {
308                let tab_size = tab_size.get() as usize;
309                let tab_len = tab_size - (expanded_chars % tab_size);
310                expanded_chars += tab_len;
311                expanded_bytes += tab_len;
312                if expanded_bytes > column {
313                    expanded_chars -= expanded_bytes - column;
314                    return match bias {
315                        Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
316                        Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
317                    };
318                }
319            } else {
320                expanded_chars += 1;
321                expanded_bytes += c.len_utf8();
322            }
323
324            if expanded_bytes > column && matches!(bias, Bias::Left) {
325                expanded_chars -= 1;
326                break;
327            }
328
329            collapsed_bytes += c.len_utf8();
330        }
331        (collapsed_bytes, expanded_chars, 0)
332    }
333}
334
335#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
336pub struct TabPoint(pub Point);
337
338impl TabPoint {
339    pub fn new(row: u32, column: u32) -> Self {
340        Self(Point::new(row, column))
341    }
342
343    pub fn zero() -> Self {
344        Self::new(0, 0)
345    }
346
347    pub fn row(self) -> u32 {
348        self.0.row
349    }
350
351    pub fn column(self) -> u32 {
352        self.0.column
353    }
354}
355
356impl From<Point> for TabPoint {
357    fn from(point: Point) -> Self {
358        Self(point)
359    }
360}
361
362pub type TabEdit = text::Edit<TabPoint>;
363
364#[derive(Clone, Debug, Default, Eq, PartialEq)]
365pub struct TextSummary {
366    pub lines: Point,
367    pub first_line_chars: u32,
368    pub last_line_chars: u32,
369    pub longest_row: u32,
370    pub longest_row_chars: u32,
371}
372
373impl<'a> From<&'a str> for TextSummary {
374    fn from(text: &'a str) -> Self {
375        let sum = rope::TextSummary::from(text);
376
377        TextSummary {
378            lines: sum.lines,
379            first_line_chars: sum.first_line_chars,
380            last_line_chars: sum.last_line_chars,
381            longest_row: sum.longest_row,
382            longest_row_chars: sum.longest_row_chars,
383        }
384    }
385}
386
387impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
388    fn add_assign(&mut self, other: &'a Self) {
389        let joined_chars = self.last_line_chars + other.first_line_chars;
390        if joined_chars > self.longest_row_chars {
391            self.longest_row = self.lines.row;
392            self.longest_row_chars = joined_chars;
393        }
394        if other.longest_row_chars > self.longest_row_chars {
395            self.longest_row = self.lines.row + other.longest_row;
396            self.longest_row_chars = other.longest_row_chars;
397        }
398
399        if self.lines.row == 0 {
400            self.first_line_chars += other.first_line_chars;
401        }
402
403        if other.lines.row == 0 {
404            self.last_line_chars += other.first_line_chars;
405        } else {
406            self.last_line_chars = other.last_line_chars;
407        }
408
409        self.lines += &other.lines;
410    }
411}
412
413// Handles a tab width <= 16
414const SPACES: &str = "                ";
415
416pub struct TabChunks<'a> {
417    fold_chunks: fold_map::FoldChunks<'a>,
418    chunk: Chunk<'a>,
419    column: usize,
420    output_position: Point,
421    max_output_position: Point,
422    tab_size: NonZeroU32,
423    skip_leading_tab: bool,
424}
425
426impl<'a> Iterator for TabChunks<'a> {
427    type Item = Chunk<'a>;
428
429    fn next(&mut self) -> Option<Self::Item> {
430        if self.chunk.text.is_empty() {
431            if let Some(chunk) = self.fold_chunks.next() {
432                self.chunk = chunk;
433                if self.skip_leading_tab {
434                    self.chunk.text = &self.chunk.text[1..];
435                    self.skip_leading_tab = false;
436                }
437            } else {
438                return None;
439            }
440        }
441
442        for (ix, c) in self.chunk.text.char_indices() {
443            match c {
444                '\t' => {
445                    if ix > 0 {
446                        let (prefix, suffix) = self.chunk.text.split_at(ix);
447                        self.chunk.text = suffix;
448                        return Some(Chunk {
449                            text: prefix,
450                            ..self.chunk
451                        });
452                    } else {
453                        self.chunk.text = &self.chunk.text[1..];
454                        let tab_size = self.tab_size.get() as u32;
455                        let mut len = tab_size - self.column as u32 % tab_size;
456                        let next_output_position = cmp::min(
457                            self.output_position + Point::new(0, len),
458                            self.max_output_position,
459                        );
460                        len = next_output_position.column - self.output_position.column;
461                        self.column += len as usize;
462                        self.output_position = next_output_position;
463                        return Some(Chunk {
464                            text: &SPACES[0..len as usize],
465                            ..self.chunk
466                        });
467                    }
468                }
469                '\n' => {
470                    self.column = 0;
471                    self.output_position += Point::new(1, 0);
472                }
473                _ => {
474                    self.column += 1;
475                    self.output_position.column += c.len_utf8() as u32;
476                }
477            }
478        }
479
480        Some(mem::take(&mut self.chunk))
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487    use crate::{display_map::fold_map::FoldMap, MultiBuffer};
488    use rand::{prelude::StdRng, Rng};
489
490    #[test]
491    fn test_expand_tabs() {
492        assert_eq!(
493            TabSnapshot::expand_tabs("\t".chars(), 0, 4.try_into().unwrap()),
494            0
495        );
496        assert_eq!(
497            TabSnapshot::expand_tabs("\t".chars(), 1, 4.try_into().unwrap()),
498            4
499        );
500        assert_eq!(
501            TabSnapshot::expand_tabs("\ta".chars(), 2, 4.try_into().unwrap()),
502            5
503        );
504    }
505
506    #[gpui::test(iterations = 100)]
507    fn test_random_tabs(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
508        let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
509        let len = rng.gen_range(0..30);
510        let buffer = if rng.gen() {
511            let text = util::RandomCharIter::new(&mut rng)
512                .take(len)
513                .collect::<String>();
514            MultiBuffer::build_simple(&text, cx)
515        } else {
516            MultiBuffer::build_random(&mut rng, cx)
517        };
518        let buffer_snapshot = buffer.read(cx).snapshot(cx);
519        log::info!("Buffer text: {:?}", buffer_snapshot.text());
520
521        let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone());
522        fold_map.randomly_mutate(&mut rng);
523        let (folds_snapshot, _) = fold_map.read(buffer_snapshot, vec![]);
524        log::info!("FoldMap text: {:?}", folds_snapshot.text());
525
526        let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
527        let text = rope::Rope::from(tabs_snapshot.text().as_str());
528        log::info!(
529            "TabMap text (tab size: {}): {:?}",
530            tab_size,
531            tabs_snapshot.text(),
532        );
533
534        for _ in 0..5 {
535            let end_row = rng.gen_range(0..=text.max_point().row);
536            let end_column = rng.gen_range(0..=text.line_len(end_row));
537            let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
538            let start_row = rng.gen_range(0..=text.max_point().row);
539            let start_column = rng.gen_range(0..=text.line_len(start_row));
540            let mut start =
541                TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
542            if start > end {
543                mem::swap(&mut start, &mut end);
544            }
545
546            let expected_text = text
547                .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0))
548                .collect::<String>();
549            let expected_summary = TextSummary::from(expected_text.as_str());
550            assert_eq!(
551                expected_text,
552                tabs_snapshot
553                    .chunks(start..end, false, None)
554                    .map(|c| c.text)
555                    .collect::<String>(),
556                "chunks({:?}..{:?})",
557                start,
558                end
559            );
560
561            let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);
562            if tab_size.get() > 1 && folds_snapshot.text().contains('\t') {
563                actual_summary.longest_row = expected_summary.longest_row;
564                actual_summary.longest_row_chars = expected_summary.longest_row_chars;
565            }
566            assert_eq!(actual_summary, expected_summary);
567        }
568
569        for row in 0..=text.max_point().row {
570            assert_eq!(tabs_snapshot.line_len(row), text.line_len(row));
571        }
572    }
573}