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}