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, num::NonZeroU32, 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: NonZeroU32) -> (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: NonZeroU32,
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: NonZeroU32,
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(
255 chars: impl Iterator<Item = char>,
256 column: usize,
257 tab_size: NonZeroU32,
258 ) -> usize {
259 let mut expanded_chars = 0;
260 let mut expanded_bytes = 0;
261 let mut collapsed_bytes = 0;
262 for c in chars {
263 if collapsed_bytes == column {
264 break;
265 }
266 if c == '\t' {
267 let tab_size = tab_size.get() as usize;
268 let tab_len = tab_size - expanded_chars % tab_size;
269 expanded_bytes += tab_len;
270 expanded_chars += tab_len;
271 } else {
272 expanded_bytes += c.len_utf8();
273 expanded_chars += 1;
274 }
275 collapsed_bytes += c.len_utf8();
276 }
277 expanded_bytes
278 }
279
280 fn collapse_tabs(
281 mut chars: impl Iterator<Item = char>,
282 column: usize,
283 bias: Bias,
284 tab_size: NonZeroU32,
285 ) -> (usize, usize, usize) {
286 let mut expanded_bytes = 0;
287 let mut expanded_chars = 0;
288 let mut collapsed_bytes = 0;
289 while let Some(c) = chars.next() {
290 if expanded_bytes >= column {
291 break;
292 }
293
294 if c == '\t' {
295 let tab_size = tab_size.get() as usize;
296 let tab_len = tab_size - (expanded_chars % tab_size);
297 expanded_chars += tab_len;
298 expanded_bytes += tab_len;
299 if expanded_bytes > column {
300 expanded_chars -= expanded_bytes - column;
301 return match bias {
302 Bias::Left => (collapsed_bytes, expanded_chars, expanded_bytes - column),
303 Bias::Right => (collapsed_bytes + 1, expanded_chars, 0),
304 };
305 }
306 } else {
307 expanded_chars += 1;
308 expanded_bytes += c.len_utf8();
309 }
310
311 if expanded_bytes > column && matches!(bias, Bias::Left) {
312 expanded_chars -= 1;
313 break;
314 }
315
316 collapsed_bytes += c.len_utf8();
317 }
318 (collapsed_bytes, expanded_chars, 0)
319 }
320}
321
322#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
323pub struct TabPoint(pub super::Point);
324
325impl TabPoint {
326 pub fn new(row: u32, column: u32) -> Self {
327 Self(super::Point::new(row, column))
328 }
329
330 pub fn zero() -> Self {
331 Self::new(0, 0)
332 }
333
334 pub fn row(self) -> u32 {
335 self.0.row
336 }
337
338 pub fn column(self) -> u32 {
339 self.0.column
340 }
341}
342
343impl From<super::Point> for TabPoint {
344 fn from(point: super::Point) -> Self {
345 Self(point)
346 }
347}
348
349pub type TabEdit = text::Edit<TabPoint>;
350
351#[derive(Clone, Debug, Default, Eq, PartialEq)]
352pub struct TextSummary {
353 pub lines: super::Point,
354 pub first_line_chars: u32,
355 pub last_line_chars: u32,
356 pub longest_row: u32,
357 pub longest_row_chars: u32,
358}
359
360impl<'a> From<&'a str> for TextSummary {
361 fn from(text: &'a str) -> Self {
362 let sum = rope::TextSummary::from(text);
363
364 TextSummary {
365 lines: sum.lines,
366 first_line_chars: sum.first_line_chars,
367 last_line_chars: sum.last_line_chars,
368 longest_row: sum.longest_row,
369 longest_row_chars: sum.longest_row_chars,
370 }
371 }
372}
373
374impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
375 fn add_assign(&mut self, other: &'a Self) {
376 let joined_chars = self.last_line_chars + other.first_line_chars;
377 if joined_chars > self.longest_row_chars {
378 self.longest_row = self.lines.row;
379 self.longest_row_chars = joined_chars;
380 }
381 if other.longest_row_chars > self.longest_row_chars {
382 self.longest_row = self.lines.row + other.longest_row;
383 self.longest_row_chars = other.longest_row_chars;
384 }
385
386 if self.lines.row == 0 {
387 self.first_line_chars += other.first_line_chars;
388 }
389
390 if other.lines.row == 0 {
391 self.last_line_chars += other.first_line_chars;
392 } else {
393 self.last_line_chars = other.last_line_chars;
394 }
395
396 self.lines += &other.lines;
397 }
398}
399
400// Handles a tab width <= 16
401const SPACES: &'static str = " ";
402
403pub struct TabChunks<'a> {
404 fold_chunks: fold_map::FoldChunks<'a>,
405 chunk: Chunk<'a>,
406 column: usize,
407 output_position: Point,
408 max_output_position: Point,
409 tab_size: NonZeroU32,
410 skip_leading_tab: bool,
411}
412
413impl<'a> Iterator for TabChunks<'a> {
414 type Item = Chunk<'a>;
415
416 fn next(&mut self) -> Option<Self::Item> {
417 if self.chunk.text.is_empty() {
418 if let Some(chunk) = self.fold_chunks.next() {
419 self.chunk = chunk;
420 if self.skip_leading_tab {
421 self.chunk.text = &self.chunk.text[1..];
422 self.skip_leading_tab = false;
423 }
424 } else {
425 return None;
426 }
427 }
428
429 for (ix, c) in self.chunk.text.char_indices() {
430 match c {
431 '\t' => {
432 if ix > 0 {
433 let (prefix, suffix) = self.chunk.text.split_at(ix);
434 self.chunk.text = suffix;
435 return Some(Chunk {
436 text: prefix,
437 ..self.chunk
438 });
439 } else {
440 self.chunk.text = &self.chunk.text[1..];
441 let tab_size = self.tab_size.get() as u32;
442 let mut len = tab_size - self.column as u32 % tab_size;
443 let next_output_position = cmp::min(
444 self.output_position + Point::new(0, len),
445 self.max_output_position,
446 );
447 len = next_output_position.column - self.output_position.column;
448 self.column += len as usize;
449 self.output_position = next_output_position;
450 return Some(Chunk {
451 text: &SPACES[0..len as usize],
452 ..self.chunk
453 });
454 }
455 }
456 '\n' => {
457 self.column = 0;
458 self.output_position += Point::new(1, 0);
459 }
460 _ => {
461 self.column += 1;
462 self.output_position.column += c.len_utf8() as u32;
463 }
464 }
465 }
466
467 Some(mem::take(&mut self.chunk))
468 }
469}
470
471#[cfg(test)]
472mod tests {
473 use super::*;
474 use crate::{display_map::fold_map::FoldMap, MultiBuffer};
475 use rand::{prelude::StdRng, Rng};
476 use text::{RandomCharIter, Rope};
477
478 #[test]
479 fn test_expand_tabs() {
480 assert_eq!(
481 TabSnapshot::expand_tabs("\t".chars(), 0, 4.try_into().unwrap()),
482 0
483 );
484 assert_eq!(
485 TabSnapshot::expand_tabs("\t".chars(), 1, 4.try_into().unwrap()),
486 4
487 );
488 assert_eq!(
489 TabSnapshot::expand_tabs("\ta".chars(), 2, 4.try_into().unwrap()),
490 5
491 );
492 }
493
494 #[gpui::test(iterations = 100)]
495 fn test_random_tabs(cx: &mut gpui::MutableAppContext, mut rng: StdRng) {
496 let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
497 let len = rng.gen_range(0..30);
498 let buffer = if rng.gen() {
499 let text = RandomCharIter::new(&mut rng).take(len).collect::<String>();
500 MultiBuffer::build_simple(&text, cx)
501 } else {
502 MultiBuffer::build_random(&mut rng, cx)
503 };
504 let buffer_snapshot = buffer.read(cx).snapshot(cx);
505 log::info!("Buffer text: {:?}", buffer_snapshot.text());
506
507 let (mut fold_map, _) = FoldMap::new(buffer_snapshot.clone());
508 fold_map.randomly_mutate(&mut rng);
509 let (folds_snapshot, _) = fold_map.read(buffer_snapshot.clone(), vec![]);
510 log::info!("FoldMap text: {:?}", folds_snapshot.text());
511
512 let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size);
513 let text = Rope::from(tabs_snapshot.text().as_str());
514 log::info!(
515 "TabMap text (tab size: {}): {:?}",
516 tab_size,
517 tabs_snapshot.text(),
518 );
519
520 for _ in 0..5 {
521 let end_row = rng.gen_range(0..=text.max_point().row);
522 let end_column = rng.gen_range(0..=text.line_len(end_row));
523 let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right));
524 let start_row = rng.gen_range(0..=text.max_point().row);
525 let start_column = rng.gen_range(0..=text.line_len(start_row));
526 let mut start =
527 TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left));
528 if start > end {
529 mem::swap(&mut start, &mut end);
530 }
531
532 let expected_text = text
533 .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0))
534 .collect::<String>();
535 let expected_summary = TextSummary::from(expected_text.as_str());
536 assert_eq!(
537 expected_text,
538 tabs_snapshot
539 .chunks(start..end, false, None)
540 .map(|c| c.text)
541 .collect::<String>(),
542 "chunks({:?}..{:?})",
543 start,
544 end
545 );
546
547 let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end);
548 if tab_size.get() > 1 && folds_snapshot.text().contains('\t') {
549 actual_summary.longest_row = expected_summary.longest_row;
550 actual_summary.longest_row_chars = expected_summary.longest_row_chars;
551 }
552 assert_eq!(actual_summary, expected_summary);
553 }
554
555 for row in 0..=text.max_point().row {
556 assert_eq!(tabs_snapshot.line_len(row), text.line_len(row));
557 }
558 }
559}