tab_map.rs

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