tab_map.rs

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