1use super::{
2 Highlights,
3 fold_map::{Chunk, FoldRows},
4 tab_map::{self, TabEdit, TabPoint, TabSnapshot},
5};
6use gpui::{App, AppContext as _, Context, Entity, Font, LineWrapper, Pixels, Task};
7use language::Point;
8use multi_buffer::{MultiBufferSnapshot, RowInfo};
9use smol::future::yield_now;
10use std::sync::LazyLock;
11use std::{cmp, collections::VecDeque, mem, ops::Range, time::Duration};
12use sum_tree::{Bias, Cursor, Dimensions, SumTree};
13use text::Patch;
14
15pub use super::tab_map::TextSummary;
16pub type WrapEdit = text::Edit<u32>;
17
18/// Handles soft wrapping of text.
19///
20/// See the [`display_map` module documentation](crate::display_map) for more information.
21pub struct WrapMap {
22 snapshot: WrapSnapshot,
23 pending_edits: VecDeque<(TabSnapshot, Vec<TabEdit>)>,
24 interpolated_edits: Patch<u32>,
25 edits_since_sync: Patch<u32>,
26 wrap_width: Option<Pixels>,
27 background_task: Option<Task<()>>,
28 font_with_size: (Font, Pixels),
29}
30
31#[derive(Clone)]
32pub struct WrapSnapshot {
33 tab_snapshot: TabSnapshot,
34 transforms: SumTree<Transform>,
35 interpolated: bool,
36}
37
38#[derive(Clone, Debug, Default, Eq, PartialEq)]
39struct Transform {
40 summary: TransformSummary,
41 display_text: Option<&'static str>,
42}
43
44#[derive(Clone, Debug, Default, Eq, PartialEq)]
45struct TransformSummary {
46 input: TextSummary,
47 output: TextSummary,
48}
49
50#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)]
51pub struct WrapPoint(pub Point);
52
53pub struct WrapChunks<'a> {
54 input_chunks: tab_map::TabChunks<'a>,
55 input_chunk: Chunk<'a>,
56 output_position: WrapPoint,
57 max_output_row: u32,
58 transforms: Cursor<'a, Transform, Dimensions<WrapPoint, TabPoint>>,
59 snapshot: &'a WrapSnapshot,
60}
61
62#[derive(Clone)]
63pub struct WrapRows<'a> {
64 input_buffer_rows: FoldRows<'a>,
65 input_buffer_row: RowInfo,
66 output_row: u32,
67 soft_wrapped: bool,
68 max_output_row: u32,
69 transforms: Cursor<'a, Transform, Dimensions<WrapPoint, TabPoint>>,
70}
71
72impl WrapRows<'_> {
73 pub(crate) fn seek(&mut self, start_row: u32) {
74 self.transforms
75 .seek(&WrapPoint::new(start_row, 0), Bias::Left);
76 let mut input_row = self.transforms.start().1.row();
77 if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
78 input_row += start_row - self.transforms.start().0.row();
79 }
80 self.soft_wrapped = self.transforms.item().map_or(false, |t| !t.is_isomorphic());
81 self.input_buffer_rows.seek(input_row);
82 self.input_buffer_row = self.input_buffer_rows.next().unwrap();
83 self.output_row = start_row;
84 }
85}
86
87impl WrapMap {
88 pub fn new(
89 tab_snapshot: TabSnapshot,
90 font: Font,
91 font_size: Pixels,
92 wrap_width: Option<Pixels>,
93 cx: &mut App,
94 ) -> (Entity<Self>, WrapSnapshot) {
95 let handle = cx.new(|cx| {
96 let mut this = Self {
97 font_with_size: (font, font_size),
98 wrap_width: None,
99 pending_edits: Default::default(),
100 interpolated_edits: Default::default(),
101 edits_since_sync: Default::default(),
102 snapshot: WrapSnapshot::new(tab_snapshot),
103 background_task: None,
104 };
105 this.set_wrap_width(wrap_width, cx);
106 mem::take(&mut this.edits_since_sync);
107 this
108 });
109 let snapshot = handle.read(cx).snapshot.clone();
110 (handle, snapshot)
111 }
112
113 #[cfg(test)]
114 pub fn is_rewrapping(&self) -> bool {
115 self.background_task.is_some()
116 }
117
118 pub fn sync(
119 &mut self,
120 tab_snapshot: TabSnapshot,
121 edits: Vec<TabEdit>,
122 cx: &mut Context<Self>,
123 ) -> (WrapSnapshot, Patch<u32>) {
124 if self.wrap_width.is_some() {
125 self.pending_edits.push_back((tab_snapshot, edits));
126 self.flush_edits(cx);
127 } else {
128 self.edits_since_sync = self
129 .edits_since_sync
130 .compose(self.snapshot.interpolate(tab_snapshot, &edits));
131 self.snapshot.interpolated = false;
132 }
133
134 (self.snapshot.clone(), mem::take(&mut self.edits_since_sync))
135 }
136
137 pub fn set_font_with_size(
138 &mut self,
139 font: Font,
140 font_size: Pixels,
141 cx: &mut Context<Self>,
142 ) -> bool {
143 let font_with_size = (font, font_size);
144
145 if font_with_size == self.font_with_size {
146 false
147 } else {
148 self.font_with_size = font_with_size;
149 self.rewrap(cx);
150 true
151 }
152 }
153
154 pub fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut Context<Self>) -> bool {
155 if wrap_width == self.wrap_width {
156 return false;
157 }
158
159 self.wrap_width = wrap_width;
160 self.rewrap(cx);
161 true
162 }
163
164 fn rewrap(&mut self, cx: &mut Context<Self>) {
165 self.background_task.take();
166 self.interpolated_edits.clear();
167 self.pending_edits.clear();
168
169 if let Some(wrap_width) = self.wrap_width {
170 let mut new_snapshot = self.snapshot.clone();
171
172 let text_system = cx.text_system().clone();
173 let (font, font_size) = self.font_with_size.clone();
174 let task = cx.background_spawn(async move {
175 let mut line_wrapper = text_system.line_wrapper(font, font_size);
176 let tab_snapshot = new_snapshot.tab_snapshot.clone();
177 let range = TabPoint::zero()..tab_snapshot.max_point();
178 let edits = new_snapshot
179 .update(
180 tab_snapshot,
181 &[TabEdit {
182 old: range.clone(),
183 new: range.clone(),
184 }],
185 wrap_width,
186 &mut line_wrapper,
187 )
188 .await;
189 (new_snapshot, edits)
190 });
191
192 match cx
193 .background_executor()
194 .block_with_timeout(Duration::from_millis(5), task)
195 {
196 Ok((snapshot, edits)) => {
197 self.snapshot = snapshot;
198 self.edits_since_sync = self.edits_since_sync.compose(&edits);
199 }
200 Err(wrap_task) => {
201 self.background_task = Some(cx.spawn(async move |this, cx| {
202 let (snapshot, edits) = wrap_task.await;
203 this.update(cx, |this, cx| {
204 this.snapshot = snapshot;
205 this.edits_since_sync = this
206 .edits_since_sync
207 .compose(mem::take(&mut this.interpolated_edits).invert())
208 .compose(&edits);
209 this.background_task = None;
210 this.flush_edits(cx);
211 cx.notify();
212 })
213 .ok();
214 }));
215 }
216 }
217 } else {
218 let old_rows = self.snapshot.transforms.summary().output.lines.row + 1;
219 self.snapshot.transforms = SumTree::default();
220 let summary = self.snapshot.tab_snapshot.text_summary();
221 if !summary.lines.is_zero() {
222 self.snapshot
223 .transforms
224 .push(Transform::isomorphic(summary), &());
225 }
226 let new_rows = self.snapshot.transforms.summary().output.lines.row + 1;
227 self.snapshot.interpolated = false;
228 self.edits_since_sync = self.edits_since_sync.compose(Patch::new(vec![WrapEdit {
229 old: 0..old_rows,
230 new: 0..new_rows,
231 }]));
232 }
233 }
234
235 fn flush_edits(&mut self, cx: &mut Context<Self>) {
236 if !self.snapshot.interpolated {
237 let mut to_remove_len = 0;
238 for (tab_snapshot, _) in &self.pending_edits {
239 if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
240 to_remove_len += 1;
241 } else {
242 break;
243 }
244 }
245 self.pending_edits.drain(..to_remove_len);
246 }
247
248 if self.pending_edits.is_empty() {
249 return;
250 }
251
252 if let Some(wrap_width) = self.wrap_width
253 && self.background_task.is_none() {
254 let pending_edits = self.pending_edits.clone();
255 let mut snapshot = self.snapshot.clone();
256 let text_system = cx.text_system().clone();
257 let (font, font_size) = self.font_with_size.clone();
258 let update_task = cx.background_spawn(async move {
259 let mut edits = Patch::default();
260 let mut line_wrapper = text_system.line_wrapper(font, font_size);
261 for (tab_snapshot, tab_edits) in pending_edits {
262 let wrap_edits = snapshot
263 .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper)
264 .await;
265 edits = edits.compose(&wrap_edits);
266 }
267 (snapshot, edits)
268 });
269
270 match cx
271 .background_executor()
272 .block_with_timeout(Duration::from_millis(1), update_task)
273 {
274 Ok((snapshot, output_edits)) => {
275 self.snapshot = snapshot;
276 self.edits_since_sync = self.edits_since_sync.compose(&output_edits);
277 }
278 Err(update_task) => {
279 self.background_task = Some(cx.spawn(async move |this, cx| {
280 let (snapshot, edits) = update_task.await;
281 this.update(cx, |this, cx| {
282 this.snapshot = snapshot;
283 this.edits_since_sync = this
284 .edits_since_sync
285 .compose(mem::take(&mut this.interpolated_edits).invert())
286 .compose(&edits);
287 this.background_task = None;
288 this.flush_edits(cx);
289 cx.notify();
290 })
291 .ok();
292 }));
293 }
294 }
295 }
296
297 let was_interpolated = self.snapshot.interpolated;
298 let mut to_remove_len = 0;
299 for (tab_snapshot, edits) in &self.pending_edits {
300 if tab_snapshot.version <= self.snapshot.tab_snapshot.version {
301 to_remove_len += 1;
302 } else {
303 let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), edits);
304 self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits);
305 self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits);
306 }
307 }
308
309 if !was_interpolated {
310 self.pending_edits.drain(..to_remove_len);
311 }
312 }
313}
314
315impl WrapSnapshot {
316 fn new(tab_snapshot: TabSnapshot) -> Self {
317 let mut transforms = SumTree::default();
318 let extent = tab_snapshot.text_summary();
319 if !extent.lines.is_zero() {
320 transforms.push(Transform::isomorphic(extent), &());
321 }
322 Self {
323 transforms,
324 tab_snapshot,
325 interpolated: true,
326 }
327 }
328
329 pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot {
330 self.tab_snapshot.buffer_snapshot()
331 }
332
333 fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch<u32> {
334 let mut new_transforms;
335 if tab_edits.is_empty() {
336 new_transforms = self.transforms.clone();
337 } else {
338 let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
339
340 let mut tab_edits_iter = tab_edits.iter().peekable();
341 new_transforms =
342 old_cursor.slice(&tab_edits_iter.peek().unwrap().old.start, Bias::Right);
343
344 while let Some(edit) = tab_edits_iter.next() {
345 if edit.new.start > TabPoint::from(new_transforms.summary().input.lines) {
346 let summary = new_tab_snapshot.text_summary_for_range(
347 TabPoint::from(new_transforms.summary().input.lines)..edit.new.start,
348 );
349 new_transforms.push_or_extend(Transform::isomorphic(summary));
350 }
351
352 if !edit.new.is_empty() {
353 new_transforms.push_or_extend(Transform::isomorphic(
354 new_tab_snapshot.text_summary_for_range(edit.new.clone()),
355 ));
356 }
357
358 old_cursor.seek_forward(&edit.old.end, Bias::Right);
359 if let Some(next_edit) = tab_edits_iter.peek() {
360 if next_edit.old.start > old_cursor.end() {
361 if old_cursor.end() > edit.old.end {
362 let summary = self
363 .tab_snapshot
364 .text_summary_for_range(edit.old.end..old_cursor.end());
365 new_transforms.push_or_extend(Transform::isomorphic(summary));
366 }
367
368 old_cursor.next();
369 new_transforms
370 .append(old_cursor.slice(&next_edit.old.start, Bias::Right), &());
371 }
372 } else {
373 if old_cursor.end() > edit.old.end {
374 let summary = self
375 .tab_snapshot
376 .text_summary_for_range(edit.old.end..old_cursor.end());
377 new_transforms.push_or_extend(Transform::isomorphic(summary));
378 }
379 old_cursor.next();
380 new_transforms.append(old_cursor.suffix(), &());
381 }
382 }
383 }
384
385 let old_snapshot = mem::replace(
386 self,
387 WrapSnapshot {
388 tab_snapshot: new_tab_snapshot,
389 transforms: new_transforms,
390 interpolated: true,
391 },
392 );
393 self.check_invariants();
394 old_snapshot.compute_edits(tab_edits, self)
395 }
396
397 async fn update(
398 &mut self,
399 new_tab_snapshot: TabSnapshot,
400 tab_edits: &[TabEdit],
401 wrap_width: Pixels,
402 line_wrapper: &mut LineWrapper,
403 ) -> Patch<u32> {
404 #[derive(Debug)]
405 struct RowEdit {
406 old_rows: Range<u32>,
407 new_rows: Range<u32>,
408 }
409
410 let mut tab_edits_iter = tab_edits.iter().peekable();
411 let mut row_edits = Vec::with_capacity(tab_edits.len());
412 while let Some(edit) = tab_edits_iter.next() {
413 let mut row_edit = RowEdit {
414 old_rows: edit.old.start.row()..edit.old.end.row() + 1,
415 new_rows: edit.new.start.row()..edit.new.end.row() + 1,
416 };
417
418 while let Some(next_edit) = tab_edits_iter.peek() {
419 if next_edit.old.start.row() <= row_edit.old_rows.end {
420 row_edit.old_rows.end = next_edit.old.end.row() + 1;
421 row_edit.new_rows.end = next_edit.new.end.row() + 1;
422 tab_edits_iter.next();
423 } else {
424 break;
425 }
426 }
427
428 row_edits.push(row_edit);
429 }
430
431 let mut new_transforms;
432 if row_edits.is_empty() {
433 new_transforms = self.transforms.clone();
434 } else {
435 let mut row_edits = row_edits.into_iter().peekable();
436 let mut old_cursor = self.transforms.cursor::<TabPoint>(&());
437
438 new_transforms = old_cursor.slice(
439 &TabPoint::new(row_edits.peek().unwrap().old_rows.start, 0),
440 Bias::Right,
441 );
442
443 while let Some(edit) = row_edits.next() {
444 if edit.new_rows.start > new_transforms.summary().input.lines.row {
445 let summary = new_tab_snapshot.text_summary_for_range(
446 TabPoint(new_transforms.summary().input.lines)
447 ..TabPoint::new(edit.new_rows.start, 0),
448 );
449 new_transforms.push_or_extend(Transform::isomorphic(summary));
450 }
451
452 let mut line = String::new();
453 let mut line_fragments = Vec::new();
454 let mut remaining = None;
455 let mut chunks = new_tab_snapshot.chunks(
456 TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(),
457 false,
458 Highlights::default(),
459 );
460 let mut edit_transforms = Vec::<Transform>::new();
461 for _ in edit.new_rows.start..edit.new_rows.end {
462 while let Some(chunk) = remaining.take().or_else(|| chunks.next()) {
463 if let Some(ix) = chunk.text.find('\n') {
464 let (prefix, suffix) = chunk.text.split_at(ix + 1);
465 line_fragments.push(gpui::LineFragment::text(prefix));
466 line.push_str(prefix);
467 remaining = Some(Chunk {
468 text: suffix,
469 ..chunk
470 });
471 break;
472 } else {
473 if let Some(width) =
474 chunk.renderer.as_ref().and_then(|r| r.measured_width)
475 {
476 line_fragments
477 .push(gpui::LineFragment::element(width, chunk.text.len()));
478 } else {
479 line_fragments.push(gpui::LineFragment::text(chunk.text));
480 }
481 line.push_str(chunk.text);
482 }
483 }
484
485 if line.is_empty() {
486 break;
487 }
488
489 let mut prev_boundary_ix = 0;
490 for boundary in line_wrapper.wrap_line(&line_fragments, wrap_width) {
491 let wrapped = &line[prev_boundary_ix..boundary.ix];
492 push_isomorphic(&mut edit_transforms, TextSummary::from(wrapped));
493 edit_transforms.push(Transform::wrap(boundary.next_indent));
494 prev_boundary_ix = boundary.ix;
495 }
496
497 if prev_boundary_ix < line.len() {
498 push_isomorphic(
499 &mut edit_transforms,
500 TextSummary::from(&line[prev_boundary_ix..]),
501 );
502 }
503
504 line.clear();
505 line_fragments.clear();
506 yield_now().await;
507 }
508
509 let mut edit_transforms = edit_transforms.into_iter();
510 if let Some(transform) = edit_transforms.next() {
511 new_transforms.push_or_extend(transform);
512 }
513 new_transforms.extend(edit_transforms, &());
514
515 old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right);
516 if let Some(next_edit) = row_edits.peek() {
517 if next_edit.old_rows.start > old_cursor.end().row() {
518 if old_cursor.end() > TabPoint::new(edit.old_rows.end, 0) {
519 let summary = self.tab_snapshot.text_summary_for_range(
520 TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(),
521 );
522 new_transforms.push_or_extend(Transform::isomorphic(summary));
523 }
524 old_cursor.next();
525 new_transforms.append(
526 old_cursor
527 .slice(&TabPoint::new(next_edit.old_rows.start, 0), Bias::Right),
528 &(),
529 );
530 }
531 } else {
532 if old_cursor.end() > TabPoint::new(edit.old_rows.end, 0) {
533 let summary = self.tab_snapshot.text_summary_for_range(
534 TabPoint::new(edit.old_rows.end, 0)..old_cursor.end(),
535 );
536 new_transforms.push_or_extend(Transform::isomorphic(summary));
537 }
538 old_cursor.next();
539 new_transforms.append(old_cursor.suffix(), &());
540 }
541 }
542 }
543
544 let old_snapshot = mem::replace(
545 self,
546 WrapSnapshot {
547 tab_snapshot: new_tab_snapshot,
548 transforms: new_transforms,
549 interpolated: false,
550 },
551 );
552 self.check_invariants();
553 old_snapshot.compute_edits(tab_edits, self)
554 }
555
556 fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &WrapSnapshot) -> Patch<u32> {
557 let mut wrap_edits = Vec::with_capacity(tab_edits.len());
558 let mut old_cursor = self.transforms.cursor::<TransformSummary>(&());
559 let mut new_cursor = new_snapshot.transforms.cursor::<TransformSummary>(&());
560 for mut tab_edit in tab_edits.iter().cloned() {
561 tab_edit.old.start.0.column = 0;
562 tab_edit.old.end.0 += Point::new(1, 0);
563 tab_edit.new.start.0.column = 0;
564 tab_edit.new.end.0 += Point::new(1, 0);
565
566 old_cursor.seek(&tab_edit.old.start, Bias::Right);
567 let mut old_start = old_cursor.start().output.lines;
568 old_start += tab_edit.old.start.0 - old_cursor.start().input.lines;
569
570 old_cursor.seek(&tab_edit.old.end, Bias::Right);
571 let mut old_end = old_cursor.start().output.lines;
572 old_end += tab_edit.old.end.0 - old_cursor.start().input.lines;
573
574 new_cursor.seek(&tab_edit.new.start, Bias::Right);
575 let mut new_start = new_cursor.start().output.lines;
576 new_start += tab_edit.new.start.0 - new_cursor.start().input.lines;
577
578 new_cursor.seek(&tab_edit.new.end, Bias::Right);
579 let mut new_end = new_cursor.start().output.lines;
580 new_end += tab_edit.new.end.0 - new_cursor.start().input.lines;
581
582 wrap_edits.push(WrapEdit {
583 old: old_start.row..old_end.row,
584 new: new_start.row..new_end.row,
585 });
586 }
587
588 wrap_edits = consolidate_wrap_edits(wrap_edits);
589 Patch::new(wrap_edits)
590 }
591
592 pub(crate) fn chunks<'a>(
593 &'a self,
594 rows: Range<u32>,
595 language_aware: bool,
596 highlights: Highlights<'a>,
597 ) -> WrapChunks<'a> {
598 let output_start = WrapPoint::new(rows.start, 0);
599 let output_end = WrapPoint::new(rows.end, 0);
600 let mut transforms = self
601 .transforms
602 .cursor::<Dimensions<WrapPoint, TabPoint>>(&());
603 transforms.seek(&output_start, Bias::Right);
604 let mut input_start = TabPoint(transforms.start().1.0);
605 if transforms.item().map_or(false, |t| t.is_isomorphic()) {
606 input_start.0 += output_start.0 - transforms.start().0.0;
607 }
608 let input_end = self
609 .to_tab_point(output_end)
610 .min(self.tab_snapshot.max_point());
611 WrapChunks {
612 input_chunks: self.tab_snapshot.chunks(
613 input_start..input_end,
614 language_aware,
615 highlights,
616 ),
617 input_chunk: Default::default(),
618 output_position: output_start,
619 max_output_row: rows.end,
620 transforms,
621 snapshot: self,
622 }
623 }
624
625 pub fn max_point(&self) -> WrapPoint {
626 WrapPoint(self.transforms.summary().output.lines)
627 }
628
629 pub fn line_len(&self, row: u32) -> u32 {
630 let mut cursor = self
631 .transforms
632 .cursor::<Dimensions<WrapPoint, TabPoint>>(&());
633 cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Left);
634 if cursor
635 .item()
636 .map_or(false, |transform| transform.is_isomorphic())
637 {
638 let overshoot = row - cursor.start().0.row();
639 let tab_row = cursor.start().1.row() + overshoot;
640 let tab_line_len = self.tab_snapshot.line_len(tab_row);
641 if overshoot == 0 {
642 cursor.start().0.column() + (tab_line_len - cursor.start().1.column())
643 } else {
644 tab_line_len
645 }
646 } else {
647 cursor.start().0.column()
648 }
649 }
650
651 pub fn text_summary_for_range(&self, rows: Range<u32>) -> TextSummary {
652 let mut summary = TextSummary::default();
653
654 let start = WrapPoint::new(rows.start, 0);
655 let end = WrapPoint::new(rows.end, 0);
656
657 let mut cursor = self
658 .transforms
659 .cursor::<Dimensions<WrapPoint, TabPoint>>(&());
660 cursor.seek(&start, Bias::Right);
661 if let Some(transform) = cursor.item() {
662 let start_in_transform = start.0 - cursor.start().0.0;
663 let end_in_transform = cmp::min(end, cursor.end().0).0 - cursor.start().0.0;
664 if transform.is_isomorphic() {
665 let tab_start = TabPoint(cursor.start().1.0 + start_in_transform);
666 let tab_end = TabPoint(cursor.start().1.0 + end_in_transform);
667 summary += &self.tab_snapshot.text_summary_for_range(tab_start..tab_end);
668 } else {
669 debug_assert_eq!(start_in_transform.row, end_in_transform.row);
670 let indent_len = end_in_transform.column - start_in_transform.column;
671 summary += &TextSummary {
672 lines: Point::new(0, indent_len),
673 first_line_chars: indent_len,
674 last_line_chars: indent_len,
675 longest_row: 0,
676 longest_row_chars: indent_len,
677 };
678 }
679
680 cursor.next();
681 }
682
683 if rows.end > cursor.start().0.row() {
684 summary += &cursor
685 .summary::<_, TransformSummary>(&WrapPoint::new(rows.end, 0), Bias::Right)
686 .output;
687
688 if let Some(transform) = cursor.item() {
689 let end_in_transform = end.0 - cursor.start().0.0;
690 if transform.is_isomorphic() {
691 let char_start = cursor.start().1;
692 let char_end = TabPoint(char_start.0 + end_in_transform);
693 summary += &self
694 .tab_snapshot
695 .text_summary_for_range(char_start..char_end);
696 } else {
697 debug_assert_eq!(end_in_transform, Point::new(1, 0));
698 summary += &TextSummary {
699 lines: Point::new(1, 0),
700 first_line_chars: 0,
701 last_line_chars: 0,
702 longest_row: 0,
703 longest_row_chars: 0,
704 };
705 }
706 }
707 }
708
709 summary
710 }
711
712 pub fn soft_wrap_indent(&self, row: u32) -> Option<u32> {
713 let mut cursor = self.transforms.cursor::<WrapPoint>(&());
714 cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right);
715 cursor.item().and_then(|transform| {
716 if transform.is_isomorphic() {
717 None
718 } else {
719 Some(transform.summary.output.lines.column)
720 }
721 })
722 }
723
724 pub fn longest_row(&self) -> u32 {
725 self.transforms.summary().output.longest_row
726 }
727
728 pub fn row_infos(&self, start_row: u32) -> WrapRows<'_> {
729 let mut transforms = self
730 .transforms
731 .cursor::<Dimensions<WrapPoint, TabPoint>>(&());
732 transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left);
733 let mut input_row = transforms.start().1.row();
734 if transforms.item().map_or(false, |t| t.is_isomorphic()) {
735 input_row += start_row - transforms.start().0.row();
736 }
737 let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic());
738 let mut input_buffer_rows = self.tab_snapshot.rows(input_row);
739 let input_buffer_row = input_buffer_rows.next().unwrap();
740 WrapRows {
741 transforms,
742 input_buffer_row,
743 input_buffer_rows,
744 output_row: start_row,
745 soft_wrapped,
746 max_output_row: self.max_point().row(),
747 }
748 }
749
750 pub fn to_tab_point(&self, point: WrapPoint) -> TabPoint {
751 let mut cursor = self
752 .transforms
753 .cursor::<Dimensions<WrapPoint, TabPoint>>(&());
754 cursor.seek(&point, Bias::Right);
755 let mut tab_point = cursor.start().1.0;
756 if cursor.item().map_or(false, |t| t.is_isomorphic()) {
757 tab_point += point.0 - cursor.start().0.0;
758 }
759 TabPoint(tab_point)
760 }
761
762 pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point {
763 self.tab_snapshot.to_point(self.to_tab_point(point), bias)
764 }
765
766 pub fn make_wrap_point(&self, point: Point, bias: Bias) -> WrapPoint {
767 self.tab_point_to_wrap_point(self.tab_snapshot.make_tab_point(point, bias))
768 }
769
770 pub fn tab_point_to_wrap_point(&self, point: TabPoint) -> WrapPoint {
771 let mut cursor = self
772 .transforms
773 .cursor::<Dimensions<TabPoint, WrapPoint>>(&());
774 cursor.seek(&point, Bias::Right);
775 WrapPoint(cursor.start().1.0 + (point.0 - cursor.start().0.0))
776 }
777
778 pub fn clip_point(&self, mut point: WrapPoint, bias: Bias) -> WrapPoint {
779 if bias == Bias::Left {
780 let mut cursor = self.transforms.cursor::<WrapPoint>(&());
781 cursor.seek(&point, Bias::Right);
782 if cursor.item().map_or(false, |t| !t.is_isomorphic()) {
783 point = *cursor.start();
784 *point.column_mut() -= 1;
785 }
786 }
787
788 self.tab_point_to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias))
789 }
790
791 pub fn prev_row_boundary(&self, mut point: WrapPoint) -> u32 {
792 if self.transforms.is_empty() {
793 return 0;
794 }
795
796 *point.column_mut() = 0;
797
798 let mut cursor = self
799 .transforms
800 .cursor::<Dimensions<WrapPoint, TabPoint>>(&());
801 cursor.seek(&point, Bias::Right);
802 if cursor.item().is_none() {
803 cursor.prev();
804 }
805
806 while let Some(transform) = cursor.item() {
807 if transform.is_isomorphic() && cursor.start().1.column() == 0 {
808 return cmp::min(cursor.end().0.row(), point.row());
809 } else {
810 cursor.prev();
811 }
812 }
813
814 unreachable!()
815 }
816
817 pub fn next_row_boundary(&self, mut point: WrapPoint) -> Option<u32> {
818 point.0 += Point::new(1, 0);
819
820 let mut cursor = self
821 .transforms
822 .cursor::<Dimensions<WrapPoint, TabPoint>>(&());
823 cursor.seek(&point, Bias::Right);
824 while let Some(transform) = cursor.item() {
825 if transform.is_isomorphic() && cursor.start().1.column() == 0 {
826 return Some(cmp::max(cursor.start().0.row(), point.row()));
827 } else {
828 cursor.next();
829 }
830 }
831
832 None
833 }
834
835 #[cfg(test)]
836 pub fn text(&self) -> String {
837 self.text_chunks(0).collect()
838 }
839
840 #[cfg(test)]
841 pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator<Item = &str> {
842 self.chunks(
843 wrap_row..self.max_point().row() + 1,
844 false,
845 Highlights::default(),
846 )
847 .map(|h| h.text)
848 }
849
850 fn check_invariants(&self) {
851 #[cfg(test)]
852 {
853 assert_eq!(
854 TabPoint::from(self.transforms.summary().input.lines),
855 self.tab_snapshot.max_point()
856 );
857
858 {
859 let mut transforms = self.transforms.cursor::<()>(&()).peekable();
860 while let Some(transform) = transforms.next() {
861 if let Some(next_transform) = transforms.peek() {
862 assert!(transform.is_isomorphic() != next_transform.is_isomorphic());
863 }
864 }
865 }
866
867 let text = language::Rope::from(self.text().as_str());
868 let mut input_buffer_rows = self.tab_snapshot.rows(0);
869 let mut expected_buffer_rows = Vec::new();
870 let mut prev_tab_row = 0;
871 for display_row in 0..=self.max_point().row() {
872 let tab_point = self.to_tab_point(WrapPoint::new(display_row, 0));
873 if tab_point.row() == prev_tab_row && display_row != 0 {
874 expected_buffer_rows.push(None);
875 } else {
876 expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row);
877 }
878
879 prev_tab_row = tab_point.row();
880 assert_eq!(self.line_len(display_row), text.line_len(display_row));
881 }
882
883 for start_display_row in 0..expected_buffer_rows.len() {
884 assert_eq!(
885 self.row_infos(start_display_row as u32)
886 .map(|row_info| row_info.buffer_row)
887 .collect::<Vec<_>>(),
888 &expected_buffer_rows[start_display_row..],
889 "invalid buffer_rows({}..)",
890 start_display_row
891 );
892 }
893 }
894 }
895}
896
897impl WrapChunks<'_> {
898 pub(crate) fn seek(&mut self, rows: Range<u32>) {
899 let output_start = WrapPoint::new(rows.start, 0);
900 let output_end = WrapPoint::new(rows.end, 0);
901 self.transforms.seek(&output_start, Bias::Right);
902 let mut input_start = TabPoint(self.transforms.start().1.0);
903 if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
904 input_start.0 += output_start.0 - self.transforms.start().0.0;
905 }
906 let input_end = self
907 .snapshot
908 .to_tab_point(output_end)
909 .min(self.snapshot.tab_snapshot.max_point());
910 self.input_chunks.seek(input_start..input_end);
911 self.input_chunk = Chunk::default();
912 self.output_position = output_start;
913 self.max_output_row = rows.end;
914 }
915}
916
917impl<'a> Iterator for WrapChunks<'a> {
918 type Item = Chunk<'a>;
919
920 fn next(&mut self) -> Option<Self::Item> {
921 if self.output_position.row() >= self.max_output_row {
922 return None;
923 }
924
925 let transform = self.transforms.item()?;
926 if let Some(display_text) = transform.display_text {
927 let mut start_ix = 0;
928 let mut end_ix = display_text.len();
929 let mut summary = transform.summary.output.lines;
930
931 if self.output_position > self.transforms.start().0 {
932 // Exclude newline starting prior to the desired row.
933 start_ix = 1;
934 summary.row = 0;
935 } else if self.output_position.row() + 1 >= self.max_output_row {
936 // Exclude soft indentation ending after the desired row.
937 end_ix = 1;
938 summary.column = 0;
939 }
940
941 self.output_position.0 += summary;
942 self.transforms.next();
943 return Some(Chunk {
944 text: &display_text[start_ix..end_ix],
945 ..Default::default()
946 });
947 }
948
949 if self.input_chunk.text.is_empty() {
950 self.input_chunk = self.input_chunks.next().unwrap();
951 }
952
953 let mut input_len = 0;
954 let transform_end = self.transforms.end().0;
955 for c in self.input_chunk.text.chars() {
956 let char_len = c.len_utf8();
957 input_len += char_len;
958 if c == '\n' {
959 *self.output_position.row_mut() += 1;
960 *self.output_position.column_mut() = 0;
961 } else {
962 *self.output_position.column_mut() += char_len as u32;
963 }
964
965 if self.output_position >= transform_end {
966 self.transforms.next();
967 break;
968 }
969 }
970
971 let (prefix, suffix) = self.input_chunk.text.split_at(input_len);
972 self.input_chunk.text = suffix;
973 Some(Chunk {
974 text: prefix,
975 ..self.input_chunk.clone()
976 })
977 }
978}
979
980impl Iterator for WrapRows<'_> {
981 type Item = RowInfo;
982
983 fn next(&mut self) -> Option<Self::Item> {
984 if self.output_row > self.max_output_row {
985 return None;
986 }
987
988 let buffer_row = self.input_buffer_row;
989 let soft_wrapped = self.soft_wrapped;
990 let diff_status = self.input_buffer_row.diff_status;
991
992 self.output_row += 1;
993 self.transforms
994 .seek_forward(&WrapPoint::new(self.output_row, 0), Bias::Left);
995 if self.transforms.item().map_or(false, |t| t.is_isomorphic()) {
996 self.input_buffer_row = self.input_buffer_rows.next().unwrap();
997 self.soft_wrapped = false;
998 } else {
999 self.soft_wrapped = true;
1000 }
1001
1002 Some(if soft_wrapped {
1003 RowInfo {
1004 buffer_id: None,
1005 buffer_row: None,
1006 multibuffer_row: None,
1007 diff_status,
1008 expand_info: None,
1009 }
1010 } else {
1011 buffer_row
1012 })
1013 }
1014}
1015
1016impl Transform {
1017 fn isomorphic(summary: TextSummary) -> Self {
1018 #[cfg(test)]
1019 assert!(!summary.lines.is_zero());
1020
1021 Self {
1022 summary: TransformSummary {
1023 input: summary.clone(),
1024 output: summary,
1025 },
1026 display_text: None,
1027 }
1028 }
1029
1030 fn wrap(indent: u32) -> Self {
1031 static WRAP_TEXT: LazyLock<String> = LazyLock::new(|| {
1032 let mut wrap_text = String::new();
1033 wrap_text.push('\n');
1034 wrap_text.extend((0..LineWrapper::MAX_INDENT as usize).map(|_| ' '));
1035 wrap_text
1036 });
1037
1038 Self {
1039 summary: TransformSummary {
1040 input: TextSummary::default(),
1041 output: TextSummary {
1042 lines: Point::new(1, indent),
1043 first_line_chars: 0,
1044 last_line_chars: indent,
1045 longest_row: 1,
1046 longest_row_chars: indent,
1047 },
1048 },
1049 display_text: Some(&WRAP_TEXT[..1 + indent as usize]),
1050 }
1051 }
1052
1053 fn is_isomorphic(&self) -> bool {
1054 self.display_text.is_none()
1055 }
1056}
1057
1058impl sum_tree::Item for Transform {
1059 type Summary = TransformSummary;
1060
1061 fn summary(&self, _cx: &()) -> Self::Summary {
1062 self.summary.clone()
1063 }
1064}
1065
1066fn push_isomorphic(transforms: &mut Vec<Transform>, summary: TextSummary) {
1067 if let Some(last_transform) = transforms.last_mut()
1068 && last_transform.is_isomorphic() {
1069 last_transform.summary.input += &summary;
1070 last_transform.summary.output += &summary;
1071 return;
1072 }
1073 transforms.push(Transform::isomorphic(summary));
1074}
1075
1076trait SumTreeExt {
1077 fn push_or_extend(&mut self, transform: Transform);
1078}
1079
1080impl SumTreeExt for SumTree<Transform> {
1081 fn push_or_extend(&mut self, transform: Transform) {
1082 let mut transform = Some(transform);
1083 self.update_last(
1084 |last_transform| {
1085 if last_transform.is_isomorphic() && transform.as_ref().unwrap().is_isomorphic() {
1086 let transform = transform.take().unwrap();
1087 last_transform.summary.input += &transform.summary.input;
1088 last_transform.summary.output += &transform.summary.output;
1089 }
1090 },
1091 &(),
1092 );
1093
1094 if let Some(transform) = transform {
1095 self.push(transform, &());
1096 }
1097 }
1098}
1099
1100impl WrapPoint {
1101 pub fn new(row: u32, column: u32) -> Self {
1102 Self(Point::new(row, column))
1103 }
1104
1105 pub fn row(self) -> u32 {
1106 self.0.row
1107 }
1108
1109 pub fn row_mut(&mut self) -> &mut u32 {
1110 &mut self.0.row
1111 }
1112
1113 pub fn column(self) -> u32 {
1114 self.0.column
1115 }
1116
1117 pub fn column_mut(&mut self) -> &mut u32 {
1118 &mut self.0.column
1119 }
1120}
1121
1122impl sum_tree::Summary for TransformSummary {
1123 type Context = ();
1124
1125 fn zero(_cx: &()) -> Self {
1126 Default::default()
1127 }
1128
1129 fn add_summary(&mut self, other: &Self, _: &()) {
1130 self.input += &other.input;
1131 self.output += &other.output;
1132 }
1133}
1134
1135impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint {
1136 fn zero(_cx: &()) -> Self {
1137 Default::default()
1138 }
1139
1140 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1141 self.0 += summary.input.lines;
1142 }
1143}
1144
1145impl sum_tree::SeekTarget<'_, TransformSummary, TransformSummary> for TabPoint {
1146 fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering {
1147 Ord::cmp(&self.0, &cursor_location.input.lines)
1148 }
1149}
1150
1151impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint {
1152 fn zero(_cx: &()) -> Self {
1153 Default::default()
1154 }
1155
1156 fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) {
1157 self.0 += summary.output.lines;
1158 }
1159}
1160
1161fn consolidate_wrap_edits(edits: Vec<WrapEdit>) -> Vec<WrapEdit> {
1162 let _old_alloc_ptr = edits.as_ptr();
1163 let mut wrap_edits = edits.into_iter();
1164
1165 if let Some(mut first_edit) = wrap_edits.next() {
1166 // This code relies on reusing allocations from the Vec<_> - at the time of writing .flatten() prevents them.
1167 #[allow(clippy::filter_map_identity)]
1168 let mut v: Vec<_> = wrap_edits
1169 .scan(&mut first_edit, |prev_edit, edit| {
1170 if prev_edit.old.end >= edit.old.start {
1171 prev_edit.old.end = edit.old.end;
1172 prev_edit.new.end = edit.new.end;
1173 Some(None) // Skip this edit, it's merged
1174 } else {
1175 let prev = std::mem::replace(*prev_edit, edit);
1176 Some(Some(prev)) // Yield the previous edit
1177 }
1178 })
1179 .filter_map(|x| x)
1180 .collect();
1181 v.push(first_edit.clone());
1182 debug_assert_eq!(v.as_ptr(), _old_alloc_ptr, "Wrap edits were reallocated");
1183 v
1184 } else {
1185 vec![]
1186 }
1187}
1188
1189#[cfg(test)]
1190mod tests {
1191 use super::*;
1192 use crate::{
1193 MultiBuffer,
1194 display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap},
1195 test::test_font,
1196 };
1197 use gpui::{LineFragment, px, test::observe};
1198 use rand::prelude::*;
1199 use settings::SettingsStore;
1200 use smol::stream::StreamExt;
1201 use std::{cmp, env, num::NonZeroU32};
1202 use text::Rope;
1203 use theme::LoadThemes;
1204
1205 #[gpui::test(iterations = 100)]
1206 async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
1207 // todo this test is flaky
1208 init_test(cx);
1209
1210 cx.background_executor.set_block_on_ticks(0..=50);
1211 let operations = env::var("OPERATIONS")
1212 .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
1213 .unwrap_or(10);
1214
1215 let text_system = cx.read(|cx| cx.text_system().clone());
1216 let mut wrap_width = if rng.gen_bool(0.1) {
1217 None
1218 } else {
1219 Some(px(rng.gen_range(0.0..=1000.0)))
1220 };
1221 let tab_size = NonZeroU32::new(rng.gen_range(1..=4)).unwrap();
1222
1223 let font = test_font();
1224 let _font_id = text_system.resolve_font(&font);
1225 let font_size = px(14.0);
1226
1227 log::info!("Tab size: {}", tab_size);
1228 log::info!("Wrap width: {:?}", wrap_width);
1229
1230 let buffer = cx.update(|cx| {
1231 if rng.r#gen() {
1232 MultiBuffer::build_random(&mut rng, cx)
1233 } else {
1234 let len = rng.gen_range(0..10);
1235 let text = util::RandomCharIter::new(&mut rng)
1236 .take(len)
1237 .collect::<String>();
1238 MultiBuffer::build_simple(&text, cx)
1239 }
1240 });
1241 let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx));
1242 log::info!("Buffer text: {:?}", buffer_snapshot.text());
1243 let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone());
1244 log::info!("InlayMap text: {:?}", inlay_snapshot.text());
1245 let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone());
1246 log::info!("FoldMap text: {:?}", fold_snapshot.text());
1247 let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size);
1248 let tabs_snapshot = tab_map.set_max_expansion_column(32);
1249 log::info!("TabMap text: {:?}", tabs_snapshot.text());
1250
1251 let mut line_wrapper = text_system.line_wrapper(font.clone(), font_size);
1252 let expected_text = wrap_text(&tabs_snapshot, wrap_width, &mut line_wrapper);
1253
1254 let (wrap_map, _) =
1255 cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font, font_size, wrap_width, cx));
1256 let mut notifications = observe(&wrap_map, cx);
1257
1258 if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
1259 notifications.next().await.unwrap();
1260 }
1261
1262 let (initial_snapshot, _) = wrap_map.update(cx, |map, cx| {
1263 assert!(!map.is_rewrapping());
1264 map.sync(tabs_snapshot.clone(), Vec::new(), cx)
1265 });
1266
1267 let actual_text = initial_snapshot.text();
1268 assert_eq!(
1269 actual_text,
1270 expected_text,
1271 "unwrapped text is: {:?}",
1272 tabs_snapshot.text()
1273 );
1274 log::info!("Wrapped text: {:?}", actual_text);
1275
1276 let mut next_inlay_id = 0;
1277 let mut edits = Vec::new();
1278 for _i in 0..operations {
1279 log::info!("{} ==============================================", _i);
1280
1281 let mut buffer_edits = Vec::new();
1282 match rng.gen_range(0..=100) {
1283 0..=19 => {
1284 wrap_width = if rng.gen_bool(0.2) {
1285 None
1286 } else {
1287 Some(px(rng.gen_range(0.0..=1000.0)))
1288 };
1289 log::info!("Setting wrap width to {:?}", wrap_width);
1290 wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx));
1291 }
1292 20..=39 => {
1293 for (fold_snapshot, fold_edits) in fold_map.randomly_mutate(&mut rng) {
1294 let (tabs_snapshot, tab_edits) =
1295 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1296 let (mut snapshot, wrap_edits) =
1297 wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
1298 snapshot.check_invariants();
1299 snapshot.verify_chunks(&mut rng);
1300 edits.push((snapshot, wrap_edits));
1301 }
1302 }
1303 40..=59 => {
1304 let (inlay_snapshot, inlay_edits) =
1305 inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng);
1306 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1307 let (tabs_snapshot, tab_edits) =
1308 tab_map.sync(fold_snapshot, fold_edits, tab_size);
1309 let (mut snapshot, wrap_edits) =
1310 wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx));
1311 snapshot.check_invariants();
1312 snapshot.verify_chunks(&mut rng);
1313 edits.push((snapshot, wrap_edits));
1314 }
1315 _ => {
1316 buffer.update(cx, |buffer, cx| {
1317 let subscription = buffer.subscribe();
1318 let edit_count = rng.gen_range(1..=5);
1319 buffer.randomly_mutate(&mut rng, edit_count, cx);
1320 buffer_snapshot = buffer.snapshot(cx);
1321 buffer_edits.extend(subscription.consume());
1322 });
1323 }
1324 }
1325
1326 log::info!("Buffer text: {:?}", buffer_snapshot.text());
1327 let (inlay_snapshot, inlay_edits) =
1328 inlay_map.sync(buffer_snapshot.clone(), buffer_edits);
1329 log::info!("InlayMap text: {:?}", inlay_snapshot.text());
1330 let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits);
1331 log::info!("FoldMap text: {:?}", fold_snapshot.text());
1332 let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size);
1333 log::info!("TabMap text: {:?}", tabs_snapshot.text());
1334
1335 let expected_text = wrap_text(&tabs_snapshot, wrap_width, &mut line_wrapper);
1336 let (mut snapshot, wrap_edits) =
1337 wrap_map.update(cx, |map, cx| map.sync(tabs_snapshot.clone(), tab_edits, cx));
1338 snapshot.check_invariants();
1339 snapshot.verify_chunks(&mut rng);
1340 edits.push((snapshot, wrap_edits));
1341
1342 if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) {
1343 log::info!("Waiting for wrapping to finish");
1344 while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
1345 notifications.next().await.unwrap();
1346 }
1347 wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
1348 }
1349
1350 if !wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
1351 let (mut wrapped_snapshot, wrap_edits) = wrap_map.update(cx, |map, cx| {
1352 map.sync(tabs_snapshot.clone(), Vec::new(), cx)
1353 });
1354 let actual_text = wrapped_snapshot.text();
1355 let actual_longest_row = wrapped_snapshot.longest_row();
1356 log::info!("Wrapping finished: {:?}", actual_text);
1357 wrapped_snapshot.check_invariants();
1358 wrapped_snapshot.verify_chunks(&mut rng);
1359 edits.push((wrapped_snapshot.clone(), wrap_edits));
1360 assert_eq!(
1361 actual_text,
1362 expected_text,
1363 "unwrapped text is: {:?}",
1364 tabs_snapshot.text()
1365 );
1366
1367 let mut summary = TextSummary::default();
1368 for (ix, item) in wrapped_snapshot
1369 .transforms
1370 .items(&())
1371 .into_iter()
1372 .enumerate()
1373 {
1374 summary += &item.summary.output;
1375 log::info!("{} summary: {:?}", ix, item.summary.output,);
1376 }
1377
1378 if tab_size.get() == 1
1379 || !wrapped_snapshot
1380 .tab_snapshot
1381 .fold_snapshot
1382 .text()
1383 .contains('\t')
1384 {
1385 let mut expected_longest_rows = Vec::new();
1386 let mut longest_line_len = -1;
1387 for (row, line) in expected_text.split('\n').enumerate() {
1388 let line_char_count = line.chars().count() as isize;
1389 if line_char_count > longest_line_len {
1390 expected_longest_rows.clear();
1391 longest_line_len = line_char_count;
1392 }
1393 if line_char_count >= longest_line_len {
1394 expected_longest_rows.push(row as u32);
1395 }
1396 }
1397
1398 assert!(
1399 expected_longest_rows.contains(&actual_longest_row),
1400 "incorrect longest row {}. expected {:?} with length {}",
1401 actual_longest_row,
1402 expected_longest_rows,
1403 longest_line_len,
1404 )
1405 }
1406 }
1407 }
1408
1409 let mut initial_text = Rope::from(initial_snapshot.text().as_str());
1410 for (snapshot, patch) in edits {
1411 let snapshot_text = Rope::from(snapshot.text().as_str());
1412 for edit in &patch {
1413 let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0));
1414 let old_end = initial_text.point_to_offset(cmp::min(
1415 Point::new(edit.new.start + edit.old.len() as u32, 0),
1416 initial_text.max_point(),
1417 ));
1418 let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0));
1419 let new_end = snapshot_text.point_to_offset(cmp::min(
1420 Point::new(edit.new.end, 0),
1421 snapshot_text.max_point(),
1422 ));
1423 let new_text = snapshot_text
1424 .chunks_in_range(new_start..new_end)
1425 .collect::<String>();
1426
1427 initial_text.replace(old_start..old_end, &new_text);
1428 }
1429 assert_eq!(initial_text.to_string(), snapshot_text.to_string());
1430 }
1431
1432 if wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
1433 log::info!("Waiting for wrapping to finish");
1434 while wrap_map.read_with(cx, |map, _| map.is_rewrapping()) {
1435 notifications.next().await.unwrap();
1436 }
1437 }
1438 wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
1439 }
1440
1441 fn init_test(cx: &mut gpui::TestAppContext) {
1442 cx.update(|cx| {
1443 let settings = SettingsStore::test(cx);
1444 cx.set_global(settings);
1445 theme::init(LoadThemes::JustBase, cx);
1446 });
1447 }
1448
1449 fn wrap_text(
1450 tab_snapshot: &TabSnapshot,
1451 wrap_width: Option<Pixels>,
1452 line_wrapper: &mut LineWrapper,
1453 ) -> String {
1454 if let Some(wrap_width) = wrap_width {
1455 let mut wrapped_text = String::new();
1456 for (row, line) in tab_snapshot.text().split('\n').enumerate() {
1457 if row > 0 {
1458 wrapped_text.push('\n');
1459 }
1460
1461 let mut prev_ix = 0;
1462 for boundary in line_wrapper.wrap_line(&[LineFragment::text(line)], wrap_width) {
1463 wrapped_text.push_str(&line[prev_ix..boundary.ix]);
1464 wrapped_text.push('\n');
1465 wrapped_text.push_str(&" ".repeat(boundary.next_indent as usize));
1466 prev_ix = boundary.ix;
1467 }
1468 wrapped_text.push_str(&line[prev_ix..]);
1469 }
1470
1471 wrapped_text
1472 } else {
1473 tab_snapshot.text()
1474 }
1475 }
1476
1477 impl WrapSnapshot {
1478 fn verify_chunks(&mut self, rng: &mut impl Rng) {
1479 for _ in 0..5 {
1480 let mut end_row = rng.gen_range(0..=self.max_point().row());
1481 let start_row = rng.gen_range(0..=end_row);
1482 end_row += 1;
1483
1484 let mut expected_text = self.text_chunks(start_row).collect::<String>();
1485 if expected_text.ends_with('\n') {
1486 expected_text.push('\n');
1487 }
1488 let mut expected_text = expected_text
1489 .lines()
1490 .take((end_row - start_row) as usize)
1491 .collect::<Vec<_>>()
1492 .join("\n");
1493 if end_row <= self.max_point().row() {
1494 expected_text.push('\n');
1495 }
1496
1497 let actual_text = self
1498 .chunks(start_row..end_row, true, Highlights::default())
1499 .map(|c| c.text)
1500 .collect::<String>();
1501 assert_eq!(
1502 expected_text,
1503 actual_text,
1504 "chunks != highlighted_chunks for rows {:?}",
1505 start_row..end_row
1506 );
1507 }
1508 }
1509 }
1510}