1use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot};
2use language::{rope, HighlightedChunk};
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: HighlightedChunk {
177 text: &SPACES[0..to_next_stop],
178 ..Default::default()
179 },
180 skip_leading_tab: to_next_stop > 0,
181 }
182 }
183
184 pub fn buffer_rows(&self, row: u32) -> fold_map::BufferRows {
185 self.fold_snapshot.buffer_rows(row)
186 }
187
188 #[cfg(test)]
189 pub fn text(&self) -> String {
190 self.chunks_at(Default::default()).collect()
191 }
192
193 pub fn max_point(&self) -> TabPoint {
194 self.to_tab_point(self.fold_snapshot.max_point())
195 }
196
197 pub fn clip_point(&self, point: TabPoint, bias: Bias) -> TabPoint {
198 self.to_tab_point(
199 self.fold_snapshot
200 .clip_point(self.to_fold_point(point, bias).0, bias),
201 )
202 }
203
204 pub fn to_tab_point(&self, input: FoldPoint) -> TabPoint {
205 let chars = self.fold_snapshot.chars_at(FoldPoint::new(input.row(), 0));
206 let expanded = Self::expand_tabs(chars, input.column() as usize, self.tab_size);
207 TabPoint::new(input.row(), expanded as u32)
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 fn expand_tabs(chars: impl Iterator<Item = char>, column: usize, tab_size: usize) -> usize {
223 let mut expanded_chars = 0;
224 let mut expanded_bytes = 0;
225 let mut collapsed_bytes = 0;
226 for c in chars {
227 if collapsed_bytes == column {
228 break;
229 }
230 if c == '\t' {
231 let tab_len = tab_size - expanded_chars % tab_size;
232 expanded_bytes += tab_len;
233 expanded_chars += tab_len;
234 } else {
235 expanded_bytes += c.len_utf8();
236 expanded_chars += 1;
237 }
238 collapsed_bytes += c.len_utf8();
239 }
240 expanded_bytes
241 }
242
243 fn collapse_tabs(
244 mut chars: impl Iterator<Item = char>,
245 column: usize,
246 bias: Bias,
247 tab_size: usize,
248 ) -> (usize, usize, usize) {
249 let mut expanded_bytes = 0;
250 let mut expanded_chars = 0;
251 let mut collapsed_bytes = 0;
252 while let Some(c) = chars.next() {
253 if expanded_bytes >= column {
254 break;
255 }
256
257 if c == '\t' {
258 let tab_len = tab_size - (expanded_chars % tab_size);
259 expanded_chars += tab_len;
260 expanded_bytes += tab_len;
261 if expanded_bytes > column {
262 expanded_chars -= expanded_bytes - column;
263 return match bias {
264 Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
265 Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
266 };
267 }
268 } else {
269 expanded_chars += 1;
270 expanded_bytes += c.len_utf8();
271 }
272
273 if expanded_bytes > column && matches!(bias, Bias::Left) {
274 expanded_chars -= 1;
275 break;
276 }
277
278 collapsed_bytes += c.len_utf8();
279 }
280 (collapsed_bytes, expanded_chars, 0)
281 }
282}
283
284#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
285pub struct TabPoint(pub super::Point);
286
287impl TabPoint {
288 pub fn new(row: u32, column: u32) -> Self {
289 Self(super::Point::new(row, column))
290 }
291
292 pub fn zero() -> Self {
293 Self::new(0, 0)
294 }
295
296 pub fn row(self) -> u32 {
297 self.0.row
298 }
299
300 pub fn column(self) -> u32 {
301 self.0.column
302 }
303}
304
305impl From<super::Point> for TabPoint {
306 fn from(point: super::Point) -> Self {
307 Self(point)
308 }
309}
310
311#[derive(Clone, Debug, PartialEq, Eq)]
312pub struct Edit {
313 pub old_lines: Range<TabPoint>,
314 pub new_lines: Range<TabPoint>,
315}
316
317#[derive(Clone, Debug, Default, Eq, PartialEq)]
318pub struct TextSummary {
319 pub lines: super::Point,
320 pub first_line_chars: u32,
321 pub last_line_chars: u32,
322 pub longest_row: u32,
323 pub longest_row_chars: u32,
324}
325
326impl<'a> From<&'a str> for TextSummary {
327 fn from(text: &'a str) -> Self {
328 let sum = rope::TextSummary::from(text);
329
330 TextSummary {
331 lines: sum.lines,
332 first_line_chars: sum.first_line_chars,
333 last_line_chars: sum.last_line_chars,
334 longest_row: sum.longest_row,
335 longest_row_chars: sum.longest_row_chars,
336 }
337 }
338}
339
340impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
341 fn add_assign(&mut self, other: &'a Self) {
342 let joined_chars = self.last_line_chars + other.first_line_chars;
343 if joined_chars > self.longest_row_chars {
344 self.longest_row = self.lines.row;
345 self.longest_row_chars = joined_chars;
346 }
347 if other.longest_row_chars > self.longest_row_chars {
348 self.longest_row = self.lines.row + other.longest_row;
349 self.longest_row_chars = other.longest_row_chars;
350 }
351
352 if self.lines.row == 0 {
353 self.first_line_chars += other.first_line_chars;
354 }
355
356 if other.lines.row == 0 {
357 self.last_line_chars += other.first_line_chars;
358 } else {
359 self.last_line_chars = other.last_line_chars;
360 }
361
362 self.lines += &other.lines;
363 }
364}
365
366// Handles a tab width <= 16
367const SPACES: &'static str = " ";
368
369pub struct Chunks<'a> {
370 fold_chunks: fold_map::Chunks<'a>,
371 chunk: &'a str,
372 column: usize,
373 tab_size: usize,
374 skip_leading_tab: bool,
375}
376
377impl<'a> Iterator for Chunks<'a> {
378 type Item = &'a str;
379
380 fn next(&mut self) -> Option<Self::Item> {
381 if self.chunk.is_empty() {
382 if let Some(chunk) = self.fold_chunks.next() {
383 self.chunk = chunk;
384 if self.skip_leading_tab {
385 self.chunk = &self.chunk[1..];
386 self.skip_leading_tab = false;
387 }
388 } else {
389 return None;
390 }
391 }
392
393 for (ix, c) in self.chunk.char_indices() {
394 match c {
395 '\t' => {
396 if ix > 0 {
397 let (prefix, suffix) = self.chunk.split_at(ix);
398 self.chunk = suffix;
399 return Some(prefix);
400 } else {
401 self.chunk = &self.chunk[1..];
402 let len = self.tab_size - self.column % self.tab_size;
403 self.column += len;
404 return Some(&SPACES[0..len]);
405 }
406 }
407 '\n' => self.column = 0,
408 _ => self.column += 1,
409 }
410 }
411
412 let result = Some(self.chunk);
413 self.chunk = "";
414 result
415 }
416}
417
418pub struct HighlightedChunks<'a> {
419 fold_chunks: fold_map::HighlightedChunks<'a>,
420 chunk: HighlightedChunk<'a>,
421 column: usize,
422 tab_size: usize,
423 skip_leading_tab: bool,
424}
425
426impl<'a> Iterator for HighlightedChunks<'a> {
427 type Item = HighlightedChunk<'a>;
428
429 fn next(&mut self) -> Option<Self::Item> {
430 if self.chunk.text.is_empty() {
431 if let Some(chunk) = self.fold_chunks.next() {
432 self.chunk = chunk;
433 if self.skip_leading_tab {
434 self.chunk.text = &self.chunk.text[1..];
435 self.skip_leading_tab = false;
436 }
437 } else {
438 return None;
439 }
440 }
441
442 for (ix, c) in self.chunk.text.char_indices() {
443 match c {
444 '\t' => {
445 if ix > 0 {
446 let (prefix, suffix) = self.chunk.text.split_at(ix);
447 self.chunk.text = suffix;
448 return Some(HighlightedChunk {
449 text: prefix,
450 ..self.chunk
451 });
452 } else {
453 self.chunk.text = &self.chunk.text[1..];
454 let len = self.tab_size - self.column % self.tab_size;
455 self.column += len;
456 return Some(HighlightedChunk {
457 text: &SPACES[0..len],
458 ..self.chunk
459 });
460 }
461 }
462 '\n' => self.column = 0,
463 _ => self.column += 1,
464 }
465 }
466
467 Some(mem::take(&mut self.chunk))
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474
475 #[test]
476 fn test_expand_tabs() {
477 assert_eq!(Snapshot::expand_tabs("\t".chars(), 0, 4), 0);
478 assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4);
479 assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5);
480 }
481}