tab_map.rs

  1use parking_lot::Mutex;
  2
  3use super::fold_map::{
  4    Chunks as InputChunks, Edit as InputEdit, HighlightedChunks as InputHighlightedChunks,
  5    OutputOffset as InputOffset, OutputPoint as InputPoint, Snapshot as InputSnapshot,
  6};
  7use crate::{settings::StyleId, util::Bias};
  8use std::{
  9    mem,
 10    ops::{AddAssign, Range},
 11};
 12
 13pub struct TabMap(Mutex<Snapshot>);
 14
 15impl TabMap {
 16    pub fn new(input: InputSnapshot, tab_size: usize) -> Self {
 17        Self(Mutex::new(Snapshot { input, tab_size }))
 18    }
 19
 20    pub fn sync(
 21        &self,
 22        snapshot: InputSnapshot,
 23        input_edits: Vec<InputEdit>,
 24    ) -> (Snapshot, Vec<Edit>) {
 25        let mut old_snapshot = self.0.lock();
 26        let new_snapshot = Snapshot {
 27            input: snapshot,
 28            tab_size: old_snapshot.tab_size,
 29        };
 30
 31        let mut output_edits = Vec::with_capacity(input_edits.len());
 32        for input_edit in input_edits {
 33            let old_start = input_edit.old_bytes.start.to_point(&old_snapshot.input);
 34            let old_end = input_edit.old_bytes.end.to_point(&old_snapshot.input);
 35            let new_start = input_edit.new_bytes.start.to_point(&new_snapshot.input);
 36            let new_end = input_edit.new_bytes.end.to_point(&new_snapshot.input);
 37            output_edits.push(Edit {
 38                old_lines: old_snapshot.to_output_point(old_start)
 39                    ..old_snapshot.to_output_point(old_end),
 40                new_lines: new_snapshot.to_output_point(new_start)
 41                    ..new_snapshot.to_output_point(new_end),
 42            });
 43        }
 44
 45        *old_snapshot = new_snapshot;
 46        (old_snapshot.clone(), output_edits)
 47    }
 48}
 49
 50#[derive(Clone)]
 51pub struct Snapshot {
 52    input: InputSnapshot,
 53    tab_size: usize,
 54}
 55
 56impl Snapshot {
 57    pub fn text_summary(&self) -> TextSummary {
 58        // TODO: expand tabs on first and last line, ignoring the longest row.
 59        let summary = self.input.text_summary();
 60        TextSummary {
 61            lines: summary.lines,
 62            first_line_chars: summary.first_line_chars,
 63            last_line_chars: summary.last_line_chars,
 64            longest_row: summary.longest_row,
 65            longest_row_chars: summary.longest_row_chars,
 66        }
 67    }
 68
 69    pub fn text_summary_for_rows(&self, rows: Range<u32>) -> TextSummary {
 70        // TODO: expand tabs on first and last line, ignoring the longest row.
 71        let range = InputPoint::new(rows.start, 0)..InputPoint::new(rows.end, 0);
 72        let summary = self.input.text_summary_for_range(range);
 73        TextSummary {
 74            lines: summary.lines,
 75            first_line_chars: summary.first_line_chars,
 76            last_line_chars: summary.last_line_chars,
 77            longest_row: summary.longest_row,
 78            longest_row_chars: summary.longest_row_chars,
 79        }
 80    }
 81
 82    pub fn version(&self) -> usize {
 83        self.input.version
 84    }
 85
 86    pub fn chunks_at(&self, point: OutputPoint) -> Chunks {
 87        let (point, expanded_char_column, to_next_stop) = self.to_input_point(point, Bias::Left);
 88        let fold_chunks = self.input.chunks_at(self.input.to_output_offset(point));
 89        Chunks {
 90            fold_chunks,
 91            column: expanded_char_column,
 92            tab_size: self.tab_size,
 93            chunk: &SPACES[0..to_next_stop],
 94            skip_leading_tab: to_next_stop > 0,
 95        }
 96    }
 97
 98    pub fn highlighted_chunks_for_rows(&mut self, rows: Range<u32>) -> HighlightedChunks {
 99        let start = self.input.to_output_offset(InputPoint::new(rows.start, 0));
100        let end = self
101            .input
102            .to_output_offset(InputPoint::new(rows.end, 0).min(self.input.max_point()));
103        HighlightedChunks {
104            input_chunks: self.input.highlighted_chunks(start..end),
105            column: 0,
106            tab_size: self.tab_size,
107            chunk: "",
108            style_id: Default::default(),
109        }
110    }
111
112    #[cfg(test)]
113    pub fn text(&self) -> String {
114        self.chunks_at(Default::default()).collect()
115    }
116
117    pub fn len(&self) -> OutputOffset {
118        self.to_output_offset(self.input.len())
119    }
120
121    pub fn line_len(&self, row: u32) -> u32 {
122        self.to_output_point(InputPoint::new(row, self.input.line_len(row)))
123            .column()
124    }
125
126    pub fn longest_row(&self) -> u32 {
127        // TODO: Account for tab expansion.
128        self.input.longest_row()
129    }
130
131    pub fn max_point(&self) -> OutputPoint {
132        self.to_output_point(self.input.max_point())
133    }
134
135    pub fn clip_point(&self, point: OutputPoint, bias: Bias) -> OutputPoint {
136        self.to_output_point(
137            self.input
138                .clip_point(self.to_input_point(point, bias).0, bias),
139        )
140    }
141
142    pub fn to_output_offset(&self, input_offset: InputOffset) -> OutputOffset {
143        let input_point = input_offset.to_point(&self.input);
144        let input_row_start_offset = self
145            .input
146            .to_output_offset(InputPoint::new(input_point.row(), 0));
147        let output_point = self.to_output_point(input_point);
148        OutputOffset(input_row_start_offset.0 + output_point.column() as usize)
149    }
150
151    pub fn to_output_point(&self, input: InputPoint) -> OutputPoint {
152        let chars = self.input.chars_at(InputPoint::new(input.row(), 0));
153        let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size);
154        OutputPoint::new(input.row(), expanded as u32)
155    }
156
157    pub fn to_input_point(&self, output: OutputPoint, bias: Bias) -> (InputPoint, usize, usize) {
158        let chars = self.input.chars_at(InputPoint::new(output.row(), 0));
159        let expanded = output.column() as usize;
160        let (collapsed, expanded_char_column, to_next_stop) =
161            Self::collapse_tabs(chars, expanded, bias, self.tab_size);
162        (
163            InputPoint::new(output.row(), collapsed as u32),
164            expanded_char_column,
165            to_next_stop,
166        )
167    }
168
169    fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
170        let mut expanded_chars = 0;
171        let mut expanded_bytes = 0;
172        let mut collapsed_bytes = 0;
173        for c in chars {
174            if collapsed_bytes == column {
175                break;
176            }
177            if c == '\t' {
178                let tab_len = tab_size - expanded_chars % tab_size;
179                expanded_bytes += tab_len;
180                expanded_chars += tab_len;
181            } else {
182                expanded_bytes += c.len_utf8();
183                expanded_chars += 1;
184            }
185            collapsed_bytes += c.len_utf8();
186        }
187        expanded_bytes
188    }
189
190    fn collapse_tabs(
191        mut chars: impl Iterator<Item = char>,
192        column: usize,
193        bias: Bias,
194        tab_size: usize,
195    ) -> (usize, usize, usize) {
196        let mut expanded_bytes = 0;
197        let mut expanded_chars = 0;
198        let mut collapsed_bytes = 0;
199        while let Some(c) = chars.next() {
200            if expanded_bytes >= column {
201                break;
202            }
203
204            if c == '\t' {
205                let tab_len = tab_size - (expanded_chars % tab_size);
206                expanded_chars += tab_len;
207                expanded_bytes += tab_len;
208                if expanded_bytes > column {
209                    expanded_chars -= expanded_bytes - column;
210                    return match bias {
211                        Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
212                        Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
213                    };
214                }
215            } else {
216                expanded_chars += 1;
217                expanded_bytes += c.len_utf8();
218            }
219
220            if expanded_bytes > column && matches!(bias, Bias::Left) {
221                expanded_chars -= 1;
222                break;
223            }
224
225            collapsed_bytes += c.len_utf8();
226        }
227        (collapsed_bytes, expanded_chars, 0)
228    }
229}
230
231#[derive(Copy, Clone, Debug, PartialEq, Eq)]
232pub struct OutputOffset(pub usize);
233
234#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
235pub struct OutputPoint(super::Point);
236
237impl OutputPoint {
238    pub fn new(row: u32, column: u32) -> Self {
239        Self(super::Point::new(row, column))
240    }
241
242    pub fn zero() -> Self {
243        Self::new(0, 0)
244    }
245
246    pub fn row(self) -> u32 {
247        self.0.row
248    }
249
250    pub fn column(self) -> u32 {
251        self.0.column
252    }
253
254    pub fn row_mut(&mut self) -> &mut u32 {
255        &mut self.0.row
256    }
257
258    pub fn column_mut(&mut self) -> &mut u32 {
259        &mut self.0.column
260    }
261}
262
263impl AddAssign<Self> for OutputPoint {
264    fn add_assign(&mut self, rhs: Self) {
265        self.0 += rhs.0;
266    }
267}
268
269#[derive(Clone, Debug, PartialEq, Eq)]
270pub struct Edit {
271    pub old_lines: Range<OutputPoint>,
272    pub new_lines: Range<OutputPoint>,
273}
274
275#[derive(Clone, Debug, Default, Eq, PartialEq)]
276pub struct TextSummary {
277    pub lines: super::Point,
278    pub first_line_chars: u32,
279    pub last_line_chars: u32,
280    pub longest_row: u32,
281    pub longest_row_chars: u32,
282}
283
284impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
285    fn add_assign(&mut self, other: &'a Self) {
286        let joined_chars = self.last_line_chars + other.first_line_chars;
287        if joined_chars > self.longest_row_chars {
288            self.longest_row = self.lines.row;
289            self.longest_row_chars = joined_chars;
290        }
291        if other.longest_row_chars > self.longest_row_chars {
292            self.longest_row = self.lines.row + other.longest_row;
293            self.longest_row_chars = other.longest_row_chars;
294        }
295
296        if self.lines.row == 0 {
297            self.first_line_chars += other.first_line_chars;
298        }
299
300        if other.lines.row == 0 {
301            self.last_line_chars += other.first_line_chars;
302        } else {
303            self.last_line_chars = other.last_line_chars;
304        }
305
306        self.lines += &other.lines;
307    }
308}
309
310// Handles a tab width <= 16
311const SPACES: &'static str = "                ";
312
313pub struct Chunks<'a> {
314    fold_chunks: InputChunks<'a>,
315    chunk: &'a str,
316    column: usize,
317    tab_size: usize,
318    skip_leading_tab: bool,
319}
320
321impl<'a> Iterator for Chunks<'a> {
322    type Item = &'a str;
323
324    fn next(&mut self) -> Option<Self::Item> {
325        if self.chunk.is_empty() {
326            if let Some(chunk) = self.fold_chunks.next() {
327                self.chunk = chunk;
328                if self.skip_leading_tab {
329                    self.chunk = &self.chunk[1..];
330                    self.skip_leading_tab = false;
331                }
332            } else {
333                return None;
334            }
335        }
336
337        for (ix, c) in self.chunk.char_indices() {
338            match c {
339                '\t' => {
340                    if ix > 0 {
341                        let (prefix, suffix) = self.chunk.split_at(ix);
342                        self.chunk = suffix;
343                        return Some(prefix);
344                    } else {
345                        self.chunk = &self.chunk[1..];
346                        let len = self.tab_size - self.column % self.tab_size;
347                        self.column += len;
348                        return Some(&SPACES[0..len]);
349                    }
350                }
351                '\n' => self.column = 0,
352                _ => self.column += 1,
353            }
354        }
355
356        let result = Some(self.chunk);
357        self.chunk = "";
358        result
359    }
360}
361
362pub struct HighlightedChunks<'a> {
363    input_chunks: InputHighlightedChunks<'a>,
364    chunk: &'a str,
365    style_id: StyleId,
366    column: usize,
367    tab_size: usize,
368}
369
370impl<'a> Iterator for HighlightedChunks<'a> {
371    type Item = (&'a str, StyleId);
372
373    fn next(&mut self) -> Option<Self::Item> {
374        if self.chunk.is_empty() {
375            if let Some((chunk, style_id)) = self.input_chunks.next() {
376                self.chunk = chunk;
377                self.style_id = style_id;
378            } else {
379                return None;
380            }
381        }
382
383        for (ix, c) in self.chunk.char_indices() {
384            match c {
385                '\t' => {
386                    if ix > 0 {
387                        let (prefix, suffix) = self.chunk.split_at(ix);
388                        self.chunk = suffix;
389                        return Some((prefix, self.style_id));
390                    } else {
391                        self.chunk = &self.chunk[1..];
392                        let len = self.tab_size - self.column % self.tab_size;
393                        self.column += len;
394                        return Some((&SPACES[0..len], self.style_id));
395                    }
396                }
397                '\n' => self.column = 0,
398                _ => self.column += 1,
399            }
400        }
401
402        Some((mem::take(&mut self.chunk), mem::take(&mut self.style_id)))
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409
410    #[test]
411    fn test_expand_tabs() {
412        assert_eq!(Snapshot::expand_tabs("\t".chars(), 0, 4), 0);
413        assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4);
414        assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5);
415    }
416}