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