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