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