1use super::inline_line::InputLine;
2use gpui::Half;
3use std::borrow::Cow;
4use std::ops::Range;
5use std::rc::Rc;
6
7use gpui::{
8 App, Font, LineFragment, Pixels, Point, ShapedLine, Size, TextAlign, Window, point, px, size,
9};
10use ropey::Rope;
11use smallvec::SmallVec;
12use sum_tree::{Bias, Dimensions, SumTree};
13use unicode_segmentation::UnicodeSegmentation as _;
14
15use crate::input::{
16 Point as TreeSitterPoint, RopeExt,
17 layout::{LastLayout, WhitespaceIndicators},
18};
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum WrappingIndent {
23 None,
25 #[default]
27 Same,
28}
29
30fn measured_wrap_boundaries(
33 text: &str,
34 width: Pixels,
35 wrapping_indent: WrappingIndent,
36 mut measure: impl FnMut(&str) -> Pixels,
37) -> Vec<gpui::Boundary> {
38 let indent = if wrapping_indent == WrappingIndent::Same {
39 text.chars()
40 .take_while(|&c| c == ' ')
41 .count()
42 .min(gpui::LineWrapper::MAX_INDENT as usize)
43 } else {
44 0
45 };
46 let indent_width = measure(&text[..indent]);
47 let ends: Vec<usize> = text
48 .grapheme_indices(true)
49 .map(|(ix, grapheme)| ix + grapheme.len())
50 .collect();
51 let opportunities: Vec<usize> = unicode_linebreak::linebreaks(text)
52 .map(|(ix, _)| ix)
53 .filter(|ix| ends.binary_search(ix).is_ok())
54 .collect();
55 let mut result = Vec::new();
56 let mut first = 0;
57 let mut start = 0;
58 while first < ends.len() {
59 let available = if start == 0 {
60 width
61 } else {
62 width - indent_width
63 };
64 let remaining = ends.len() - first;
67 let mut low = 0;
68 let mut high = 1;
69 while measure(&text[start..ends[first + high - 1]]) <= available {
70 low = high;
71 if high == remaining {
72 break;
73 }
74 high = (high * 2).min(remaining);
75 }
76 while low + 1 < high {
77 let mid = (low + high) / 2;
78 if measure(&text[start..ends[first + mid - 1]]) <= available {
79 low = mid;
80 } else {
81 high = mid;
82 }
83 }
84 let fitting_end = ends[first + low.max(1) - 1];
86 if fitting_end == text.len() {
87 break;
88 }
89 let candidate = opportunities.partition_point(|&ix| ix <= fitting_end);
90 let end = candidate
91 .checked_sub(1)
92 .map(|ix| opportunities[ix])
93 .filter(|&ix| ix > start && (start != 0 || ix > indent))
94 .unwrap_or(fitting_end);
95 result.push(gpui::Boundary {
96 ix: end,
97 next_indent: indent as u32,
98 });
99 first = ends.partition_point(|&ix| ix <= end);
100 start = end;
101 }
102 result
103}
104
105#[derive(Debug, Clone)]
107pub(crate) struct LineItem {
108 len: usize,
110 pub(crate) indent: u32,
115 pub(crate) wrapped_lines: SmallVec<[Range<usize>; 1]>,
119}
120
121impl LineItem {
122 #[inline]
124 pub(crate) fn len(&self) -> usize {
125 self.len
126 }
127
128 #[inline]
130 pub(crate) fn lines_len(&self) -> usize {
131 self.wrapped_lines.len()
132 }
133}
134
135#[derive(Debug, Clone)]
137pub(crate) struct LineSummary {
138 buffer_rows: usize,
140 wrap_rows: usize,
142 bytes: usize,
144 max_line_len: usize,
146 longest_row: usize,
148}
149
150impl sum_tree::Summary for LineSummary {
151 type Context<'a> = &'a ();
152
153 fn zero(_: &()) -> Self {
154 LineSummary {
155 buffer_rows: 0,
156 wrap_rows: 0,
157 bytes: 0,
158 max_line_len: 0,
159 longest_row: 0,
160 }
161 }
162
163 fn add_summary(&mut self, other: &Self, _: &()) {
164 if other.max_line_len > self.max_line_len {
166 self.longest_row = self.buffer_rows + other.longest_row;
167 self.max_line_len = other.max_line_len;
168 }
169 self.buffer_rows += other.buffer_rows;
170 self.wrap_rows += other.wrap_rows;
171 self.bytes += other.bytes;
172 }
173}
174
175impl sum_tree::Item for LineItem {
176 type Summary = LineSummary;
177
178 fn summary(&self, _: &()) -> LineSummary {
179 LineSummary {
180 buffer_rows: 1,
181 wrap_rows: self.lines_len(),
182 bytes: self.len(),
183 max_line_len: self.len(),
184 longest_row: 0,
185 }
186 }
187}
188
189#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
191pub(crate) struct BufferRows(pub usize);
192
193impl<'a> sum_tree::Dimension<'a, LineSummary> for BufferRows {
194 fn zero(_: &()) -> Self {
195 BufferRows(0)
196 }
197
198 fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
199 self.0 += summary.buffer_rows;
200 }
201}
202
203#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
205pub(crate) struct WrapRows(pub usize);
206
207impl<'a> sum_tree::Dimension<'a, LineSummary> for WrapRows {
208 fn zero(_: &()) -> Self {
209 WrapRows(0)
210 }
211
212 fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
213 self.0 += summary.wrap_rows;
214 }
215}
216
217pub(crate) struct TextWrapper {
221 text: Rope,
222 font: Font,
223 font_size: Pixels,
224 wrap_width: Option<Pixels>,
226 wrapping_indent: WrappingIndent,
227 pub(crate) lines: SumTree<LineItem>,
229
230 inline_metrics: Rc<[(Range<usize>, Pixels)]>,
231 _initialized: bool,
232}
233
234#[allow(unused)]
235impl TextWrapper {
236 pub(crate) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
237 Self {
238 text: Rope::new(),
239 font,
240 font_size,
241 wrap_width,
242 wrapping_indent: WrappingIndent::default(),
243 lines: SumTree::new(&()),
244 inline_metrics: Rc::from([]),
245 _initialized: false,
246 }
247 }
248
249 #[inline]
250 pub(crate) fn set_default_text(&mut self, text: &Rope) {
251 self.text = text.clone();
252 }
253
254 #[inline]
256 pub(crate) fn text(&self) -> &Rope {
257 &self.text
258 }
259
260 #[inline]
262 pub(crate) fn len(&self) -> usize {
263 self.lines.summary().wrap_rows
264 }
265
266 #[inline]
268 pub(crate) fn lines_count(&self) -> usize {
269 self.lines.summary().buffer_rows
270 }
271
272 #[inline]
274 pub(crate) fn longest_row(&self) -> usize {
275 self.lines.summary().longest_row
276 }
277
278 #[inline]
280 pub(crate) fn line(&self, row: usize) -> Option<&LineItem> {
281 let mut cursor = self.lines.cursor::<BufferRows>(&());
282 cursor.seek(&BufferRows(row), Bias::Right);
283 cursor.item()
284 }
285
286 #[inline]
288 pub(crate) fn iter_lines(&self) -> impl Iterator<Item = &LineItem> {
289 self.lines.iter()
290 }
291
292 pub(crate) fn buffer_line_to_first_wrap_row(&self, row: usize) -> usize {
295 let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
296 cursor.seek(&BufferRows(row), Bias::Right);
297 cursor.start().1.0
298 }
299
300 pub(crate) fn buffer_line_to_wrap_row_range(&self, row: usize) -> Range<usize> {
302 let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
303 cursor.seek(&BufferRows(row), Bias::Right);
304 let start = cursor.start().1.0;
305 let len = cursor.item().map(|l| l.lines_len()).unwrap_or(0);
306 start..start + len
307 }
308
309 pub(crate) fn wrap_row_to_buffer_line(&self, wrap_row: usize) -> usize {
311 let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
312 cursor.seek(&WrapRows(wrap_row), Bias::Right);
313 match cursor.item() {
314 Some(_) => cursor.start().1.0,
315 None => self.lines_count().saturating_sub(1),
316 }
317 }
318
319 pub(crate) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
320 if wrap_width == self.wrap_width {
321 return;
322 }
323
324 self.wrap_width = wrap_width;
325 self.update_all(&self.text.clone(), cx);
326 }
327
328 pub(crate) fn set_wrapping_indent(&mut self, wrapping_indent: WrappingIndent, cx: &mut App) {
329 if wrapping_indent == self.wrapping_indent {
330 return;
331 }
332
333 self.wrapping_indent = wrapping_indent;
334 self.update_all(&self.text.clone(), cx);
335 }
336
337 pub(crate) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
338 if self.font.eq(&font) && self.font_size == font_size {
339 return;
340 }
341
342 self.font = font;
343 self.font_size = font_size;
344 self.update_all(&self.text.clone(), cx);
345 }
346
347 pub(crate) fn prepare_if_need(&mut self, text: &Rope, cx: &mut App) -> bool {
348 if self._initialized {
349 return false;
350 }
351 self._initialized = true;
352 self.update_all(text, cx);
353 true
354 }
355
356 pub(crate) fn update(
366 &mut self,
367 changed_text: &Rope,
368 range: &Range<usize>,
369 new_text: &Rope,
370 cx: &mut App,
371 ) {
372 let mut line_wrapper = cx
373 .text_system()
374 .line_wrapper(self.font.clone(), self.font_size);
375 let metrics = self.inline_metrics.clone();
376 let text_system = gpui::WindowTextSystem::new(cx.text_system().clone());
377 let font = self.font.clone();
378 let font_size = self.font_size;
379 let wrapping_indent = self.wrapping_indent;
380 self._update(
381 changed_text,
382 range,
383 new_text,
384 &mut |line_str, wrap_width, line_start| {
385 let mut fragments = Vec::new();
386 let mut offset = 0;
387 let first = metrics.partition_point(|(r, _)| r.end <= line_start);
388 for (range, width) in &metrics[first..] {
389 if range.start >= line_start + line_str.len() {
390 break;
391 }
392 if range.start < line_start || range.end > line_start + line_str.len() {
393 continue;
394 }
395 let range = range.start - line_start..range.end - line_start;
396 if !line_str.is_char_boundary(range.start)
397 || !line_str.is_char_boundary(range.end)
398 {
399 continue;
400 }
401 if offset < range.start {
402 fragments.push(LineFragment::text(&line_str[offset..range.start]));
403 }
404 fragments.push(LineFragment::element(*width, range.len()));
405 offset = range.end;
406 }
407 if fragments.is_empty() {
408 return measured_wrap_boundaries(
409 line_str,
410 wrap_width,
411 wrapping_indent,
412 |text| {
413 text_system
414 .layout_line(
415 text,
416 font_size,
417 &[gpui::TextRun {
418 len: text.len(),
419 font: font.clone(),
420 color: gpui::black(),
421 background_color: None,
422 underline: None,
423 strikethrough: None,
424 }],
425 None,
426 )
427 .width
428 },
429 );
430 }
431 if offset < line_str.len() {
432 fragments.push(LineFragment::text(&line_str[offset..]));
433 }
434 line_wrapper
435 .wrap_line(&fragments, wrap_width, gpui::IndentAdjustment::SameIndent)
436 .collect()
437 },
438 );
439 }
440
441 pub(crate) fn adjust_inline_metrics(&mut self, range: &Range<usize>, new_len: usize) {
442 if self.inline_metrics.is_empty() {
443 return;
444 }
445 let shift = new_len as isize - range.len() as isize;
446 self.inline_metrics = self
447 .inline_metrics
448 .iter()
449 .filter_map(|(token, width)| {
450 if token.start < range.end && range.start < token.end {
451 return None;
452 }
453 let token = if token.start >= range.end {
454 token.start.checked_add_signed(shift)?..token.end.checked_add_signed(shift)?
455 } else {
456 token.clone()
457 };
458 Some((token, *width))
459 })
460 .collect();
461 }
462
463 pub(crate) fn set_inline_metrics(
464 &mut self,
465 metrics: Rc<[(Range<usize>, Pixels)]>,
466 cx: &mut App,
467 ) {
468 if self.inline_metrics == metrics {
469 return;
470 }
471 let mut affected = Vec::new();
473 let (mut old, mut new) = (
474 self.inline_metrics.iter().peekable(),
475 metrics.iter().peekable(),
476 );
477 while old.peek().is_some() || new.peek().is_some() {
478 match (old.peek(), new.peek()) {
479 (Some(a), Some(b)) if a == b => {
480 old.next();
481 new.next();
482 }
483 (Some(a), Some(b)) if a.0.start <= b.0.start => {
484 affected.push(a.0.clone());
485 old.next();
486 }
487 (Some(_), Some(b)) | (None, Some(b)) => {
488 affected.push(b.0.clone());
489 new.next();
490 }
491 (Some(a), None) => {
492 affected.push(a.0.clone());
493 old.next();
494 }
495 (None, None) => break,
496 }
497 }
498 self.inline_metrics = metrics;
499 let text = self.text.clone();
500 let mut rows: Vec<usize> = affected
501 .iter()
502 .map(|r| text.offset_to_point(r.start.min(text.len())).row)
503 .collect();
504 rows.sort_unstable();
505 rows.dedup();
506 for row in rows {
507 let start = text.line_start_offset(row);
508 let end = text.line_end_offset(row);
509 self.update(
510 &text,
511 &(start..end),
512 &Rope::from(text.slice(start..end).to_string()),
513 cx,
514 );
515 }
516 }
517
518 fn _update<F>(
519 &mut self,
520 changed_text: &Rope,
521 range: &Range<usize>,
522 new_text: &Rope,
523 wrap_line: &mut F,
524 ) where
525 F: FnMut(&str, Pixels, usize) -> Vec<gpui::Boundary>,
526 {
527 let buffer_line_count = self.lines_count();
529 let start_row = self.text.offset_to_point(range.start).row;
530 let start_row = start_row.min(buffer_line_count.saturating_sub(1));
531 let end_row = self.text.offset_to_point(range.end).row;
532 let end_row = end_row.min(buffer_line_count.saturating_sub(1));
533
534 let new_start_row = changed_text.offset_to_point(range.start).row;
536 let new_end_row = changed_text
537 .offset_to_point(range.start + new_text.len())
538 .row;
539
540 let mut new_lines = Vec::with_capacity(new_end_row.saturating_sub(new_start_row) + 1);
541 let wrap_width = self.wrap_width;
542
543 for row in new_start_row..=new_end_row {
545 let line = changed_text.slice_line(row);
546 let mut wrapped_lines = SmallVec::<[Range<usize>; 1]>::new();
547 let mut prev_boundary_ix = 0;
548 let mut indent_chars = 0;
549
550 if let Some(wrap_width) = wrap_width {
552 let line_str: Cow<str> = line.into();
554 match self.wrapping_indent {
555 WrappingIndent::Same => {
556 for boundary in
559 wrap_line(&line_str, wrap_width, changed_text.line_start_offset(row))
560 {
561 wrapped_lines.push(prev_boundary_ix..boundary.ix);
562 prev_boundary_ix = boundary.ix;
563 indent_chars = boundary.next_indent;
564 }
565 }
566 WrappingIndent::None => {
567 let boundaries =
570 wrap_line(&line_str, wrap_width, changed_text.line_start_offset(row));
571 if let Some(first_ix) = boundaries.first().map(|b| b.ix) {
572 wrapped_lines.push(prev_boundary_ix..first_ix);
573 prev_boundary_ix = first_ix;
574
575 for boundary in wrap_line(
576 &line_str[first_ix..],
577 wrap_width,
578 changed_text.line_start_offset(row) + first_ix,
579 ) {
580 let ix = first_ix + boundary.ix;
581 wrapped_lines.push(prev_boundary_ix..ix);
582 prev_boundary_ix = ix;
583 }
584 }
585 }
586 }
587 }
588
589 if prev_boundary_ix < line.len() || prev_boundary_ix == 0 {
591 wrapped_lines.push(prev_boundary_ix..line.len());
592 }
593
594 new_lines.push(LineItem {
595 len: line.len(),
596 indent: indent_chars,
597 wrapped_lines,
598 });
599 }
600
601 if self.lines.is_empty() {
602 self.lines = SumTree::from_iter(new_lines, &());
603 } else {
604 let mut cursor = self.lines.cursor::<BufferRows>(&());
605 let mut new_tree = cursor.slice(&BufferRows(start_row), Bias::Right);
606 cursor.seek_forward(&BufferRows(end_row + 1), Bias::Right);
608 new_tree.extend(new_lines, &());
609 new_tree.append(cursor.suffix(), &());
611 drop(cursor);
612 self.lines = new_tree;
613 }
614
615 self.text = changed_text.clone();
616 }
617
618 fn update_all(&mut self, text: &Rope, cx: &mut App) {
622 self.update(text, &(0..text.len()), &text, cx);
623 }
624
625 pub(crate) fn offset_to_display_point(&self, offset: usize) -> WrapDisplayPoint {
629 self.offset_to_display_point_with_affinity(offset, false)
630 }
631
632 pub(crate) fn offset_to_display_point_with_affinity(
639 &self,
640 offset: usize,
641 line_end_affinity: bool,
642 ) -> WrapDisplayPoint {
643 let row = self.text.offset_to_point(offset).row;
644 let start = self.text.line_start_offset(row);
645
646 let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
648 cursor.seek(&BufferRows(row), Bias::Right);
649 let wrapped_row = cursor.start().1.0;
650 let Some(line) = cursor.item() else {
651 return WrapDisplayPoint::new(wrapped_row, 0, 0);
652 };
653
654 let local_offset = offset.saturating_sub(start);
655 for (ix, range) in line.wrapped_lines.iter().enumerate() {
656 let matches =
659 range.contains(&local_offset) || (line_end_affinity && local_offset == range.end);
660 if matches {
661 return WrapDisplayPoint::new(
662 wrapped_row + ix,
663 ix,
664 local_offset.saturating_sub(range.start),
665 );
666 }
667 }
668
669 let last_range = line.wrapped_lines.last().unwrap_or(&(0..0));
671 let ix = line.lines_len().saturating_sub(1);
672 return WrapDisplayPoint::new(wrapped_row + ix, ix, last_range.len());
673 }
674
675 pub(crate) fn display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {
679 let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
681 cursor.seek(&WrapRows(point.row), Bias::Right);
682 let Some(line) = cursor.item() else {
683 return self.text.len();
684 };
685 let wrapped_row = cursor.start().0.0;
686 let row = cursor.start().1.0;
687
688 let line_start = self.text.line_start_offset(row);
689 let local_row = point.row.saturating_sub(wrapped_row);
690 if let Some(range) = line.wrapped_lines.get(local_row) {
691 line_start + (range.start + point.column).min(range.end)
692 } else {
693 line_start + line.len()
695 }
696 }
697
698 pub(crate) fn display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint {
699 let offset = self.display_point_to_offset(point);
700 self.text.offset_to_point(offset)
701 }
702
703 pub(crate) fn point_to_display_point(&self, point: TreeSitterPoint) -> WrapDisplayPoint {
704 let offset = self.text.point_to_offset(point);
705 self.offset_to_display_point(offset)
706 }
707}
708
709#[derive(Debug, Clone, Copy, PartialEq, Eq)]
715pub(crate) struct WrapDisplayPoint {
716 pub row: usize,
718 pub local_row: usize,
722 pub column: usize,
724}
725
726impl WrapDisplayPoint {
727 pub(crate) fn new(row: usize, local_row: usize, column: usize) -> Self {
728 Self {
729 row,
730 local_row,
731 column,
732 }
733 }
734}
735
736pub(crate) struct LineLayout {
738 len: usize,
740 pub(crate) wrapped_lines: SmallVec<[InputLine; 1]>,
742 pub(crate) wrap_indent: Pixels,
745 pub(crate) longest_width: Pixels,
746 pub(crate) whitespace_indicators: Option<WhitespaceIndicators>,
747 pub(crate) whitespace_chars: Vec<(usize, Pixels, bool)>,
749 has_background: bool,
752}
753
754impl LineLayout {
755 pub(crate) fn new() -> Self {
756 Self {
757 len: 0,
758 longest_width: px(0.),
759 wrapped_lines: SmallVec::new(),
760 wrap_indent: px(0.),
761 whitespace_chars: Vec::new(),
762 whitespace_indicators: None,
763 has_background: false,
764 }
765 }
766
767 pub(crate) fn with_background(mut self, has_background: bool) -> Self {
769 self.has_background = has_background;
770 self
771 }
772
773 pub(crate) fn wrap_indent(mut self, wrap_indent: Pixels) -> Self {
775 self.wrap_indent = wrap_indent;
776 self
777 }
778
779 #[inline]
782 fn line_indent(&self, line_index: usize) -> Pixels {
783 if line_index == 0 {
784 px(0.)
785 } else {
786 self.wrap_indent
787 }
788 }
789
790 pub(crate) fn lines(mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) -> Self {
791 self.set_wrapped_lines(wrapped_lines);
792 self
793 }
794
795 pub(crate) fn set_wrapped_lines(&mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) {
796 self.len = wrapped_lines.iter().map(|l| l.len).sum();
797 let width = wrapped_lines
798 .iter()
799 .map(|l| l.width)
800 .max()
801 .unwrap_or_default();
802 self.longest_width = width;
803 self.wrapped_lines = wrapped_lines.into_iter().map(InputLine::from).collect();
804 }
805
806 pub(crate) fn inline_lines(mut self, lines: SmallVec<[InputLine; 1]>) -> Self {
807 self.len = lines.iter().map(|line| line.len).sum();
808 self.longest_width = lines
809 .iter()
810 .map(|line| line.width)
811 .max()
812 .unwrap_or_default();
813 self.wrapped_lines = lines;
814 self
815 }
816
817 pub(crate) fn with_whitespaces(mut self, indicators: Option<WhitespaceIndicators>) -> Self {
818 self.whitespace_indicators = indicators;
819 let Some(indicators) = self.whitespace_indicators.as_ref() else {
820 return self;
821 };
822
823 let space_indicator_offset = indicators.space.width.half();
824
825 for (line_index, wrapped_line) in self.wrapped_lines.iter().enumerate() {
826 for (relative_offset, c) in wrapped_line.text.char_indices() {
827 if matches!(c, ' ' | '\t') {
828 let is_tab = c == '\t';
829 let start_x = wrapped_line.x_for_index(relative_offset);
830 let end_x = wrapped_line.x_for_index(relative_offset + c.len_utf8());
831 let x_position = if c == ' ' {
833 (start_x + end_x).half() - space_indicator_offset
834 } else {
835 start_x
836 };
837
838 self.whitespace_chars.push((line_index, x_position, is_tab));
839 }
840 }
841 }
842 self
843 }
844
845 #[inline]
846 pub(crate) fn len(&self) -> usize {
847 self.len
848 }
849
850 pub(crate) fn position_for_index(
857 &self,
858 offset: usize,
859 last_layout: &LastLayout,
860 line_end_affinity: bool,
861 ) -> Option<Point<Pixels>> {
862 let mut acc_len = 0;
863 let mut offset_y = px(0.);
864
865 let x_offset = last_layout.alignment_offset(self.longest_width);
866
867 for (i, line) in self.wrapped_lines.iter().enumerate() {
868 let is_last = i + 1 == self.wrapped_lines.len();
869
870 let matches = if line.len == 0 {
871 offset == acc_len
873 } else if is_last || line_end_affinity {
874 offset >= acc_len && offset <= acc_len + line.len
876 } else {
877 offset >= acc_len && offset < acc_len + line.len
879 };
880
881 if matches {
882 let x = line.x_for_index(offset.saturating_sub(acc_len))
883 + x_offset
884 + self.line_indent(i);
885 return Some(point(x, offset_y));
886 }
887
888 acc_len += if is_last { line.len + 1 } else { line.len };
891 offset_y += last_layout.line_height;
892 }
893
894 None
895 }
896
897 pub(crate) fn closest_index_for_x(&self, x: Pixels, last_layout: &LastLayout) -> usize {
903 let mut acc_len = 0;
904 let x_offset = last_layout.alignment_offset(self.longest_width);
905 let x = x - x_offset;
906
907 for (i, line) in self.wrapped_lines.iter().enumerate() {
908 let line_indent = self.line_indent(i);
909 if x <= line_indent + line.width {
910 return acc_len + line.closest_index_for_x(x - line_indent);
911 }
912 acc_len += line.len;
913 }
914
915 acc_len
916 }
917
918 fn wrapped_line_at(
923 &self,
924 pos: Point<Pixels>,
925 last_layout: &LastLayout,
926 ) -> Option<(usize, usize, Pixels)> {
927 let mut offset = 0;
928 let mut line_top = px(0.);
929 let x_offset = last_layout.alignment_offset(self.longest_width);
930
931 for (i, line) in self.wrapped_lines.iter().enumerate() {
932 let line_bottom = line_top + last_layout.line_height;
933 if pos.y >= line_top && pos.y < line_bottom {
934 return Some((i, offset, pos.x - x_offset - self.line_indent(i)));
935 }
936
937 offset += line.len;
938 line_top = line_bottom;
939 }
940
941 None
942 }
943
944 pub(crate) fn closest_index_for_position(
955 &self,
956 pos: Point<Pixels>,
957 last_layout: &LastLayout,
958 ) -> Option<(usize, bool)> {
959 let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
960 let line = &self.wrapped_lines[i];
961 let ix = line.closest_index_for_x(x);
962 let line_end_affinity = i + 1 < self.wrapped_lines.len() && ix == line.len;
963
964 Some((offset + ix, line_end_affinity))
965 }
966
967 pub(crate) fn columns_past_line_end(
978 &self,
979 pos: Point<Pixels>,
980 last_layout: &LastLayout,
981 ) -> usize {
982 let Some((i, _, x)) = self.wrapped_line_at(pos, last_layout) else {
983 return 0;
984 };
985
986 if i + 1 < self.wrapped_lines.len() || last_layout.space_width <= px(0.) {
987 return 0;
988 }
989
990 let past_end = x - self.wrapped_lines[i].width;
991 if past_end <= px(0.) {
992 return 0;
993 }
994
995 (past_end / last_layout.space_width).round() as usize
996 }
997
998 pub(crate) fn index_for_position(
999 &self,
1000 pos: Point<Pixels>,
1001 last_layout: &LastLayout,
1002 ) -> Option<usize> {
1003 let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
1004
1005 Some(offset + self.wrapped_lines[i].index_for_x(x)?)
1006 }
1007
1008 pub(crate) fn size(&self, line_height: Pixels) -> Size<Pixels> {
1009 let width = self
1010 .wrapped_lines
1011 .iter()
1012 .enumerate()
1013 .map(|(ix, line)| line.width + self.line_indent(ix))
1014 .max()
1015 .unwrap_or(self.longest_width);
1016 size(width, self.wrapped_lines.len() * line_height)
1017 }
1018
1019 pub(crate) fn paint_background(
1024 &self,
1025 pos: Point<Pixels>,
1026 line_height: Pixels,
1027 text_align: TextAlign,
1028 align_width: Option<Pixels>,
1029 window: &mut Window,
1030 cx: &mut App,
1031 ) {
1032 if !self.has_background {
1035 return;
1036 }
1037
1038 for (ix, line) in self.wrapped_lines.iter().enumerate() {
1039 _ = line.paint_background(
1040 pos + point(self.line_indent(ix), ix * line_height),
1041 line_height,
1042 text_align,
1043 align_width,
1044 window,
1045 cx,
1046 );
1047 }
1048 }
1049
1050 pub(crate) fn paint(
1051 &self,
1052 pos: Point<Pixels>,
1053 line_height: Pixels,
1054 text_align: TextAlign,
1055 align_width: Option<Pixels>,
1056 window: &mut Window,
1057 cx: &mut App,
1058 ) {
1059 for (ix, line) in self.wrapped_lines.iter().enumerate() {
1060 _ = line.paint(
1061 pos + point(self.line_indent(ix), ix * line_height),
1062 line_height,
1063 text_align,
1064 align_width,
1065 window,
1066 cx,
1067 );
1068 }
1069
1070 if let Some(indicators) = self.whitespace_indicators.as_ref() {
1072 for (line_index, x_position, is_tab) in &self.whitespace_chars {
1073 let invisible = if *is_tab {
1074 indicators.tab.clone()
1075 } else {
1076 indicators.space.clone()
1077 };
1078
1079 let origin = point(
1080 pos.x + *x_position + self.line_indent(*line_index),
1081 pos.y + *line_index as f32 * line_height,
1082 );
1083
1084 _ = invisible.paint(origin, line_height, text_align, align_width, window, cx);
1085 }
1086 }
1087 }
1088}
1089
1090#[cfg(test)]
1091mod tests {
1092 use super::*;
1093 use std::rc::Rc;
1094
1095 #[cfg(target_os = "linux")]
1096 use gpui::TestAppContext;
1097 use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px};
1098
1099 #[cfg(target_os = "linux")]
1100 fn shaped_width(text: &str, font: &Font, font_size: Pixels, cx: &App) -> Pixels {
1101 gpui::WindowTextSystem::new(cx.text_system().clone())
1102 .layout_line(
1103 text,
1104 font_size,
1105 &[gpui::TextRun {
1106 len: text.len(),
1107 font: font.clone(),
1108 color: gpui::black(),
1109 background_color: None,
1110 underline: None,
1111 strikethrough: None,
1112 }],
1113 None,
1114 )
1115 .width
1116 }
1117
1118 #[cfg(target_os = "linux")]
1122 fn shaping_test_context() -> TestAppContext {
1123 let platform = gpui_platform::current_platform(true);
1124 TestAppContext::build_with_text_system(
1125 gpui::TestDispatcher::new(0),
1126 None,
1127 platform.text_system(),
1128 )
1129 }
1130
1131 #[cfg(target_os = "linux")]
1132 #[test]
1133 fn production_wrap_keeps_shaped_cjk_latin_boundary_during_edits() {
1134 let cx = shaping_test_context();
1135 cx.update(|cx| {
1136 let font = test_font();
1137 let font_size = px(14.);
1138 let prefix = "abcd的";
1139 let width = shaped_width(prefix, &font, font_size, cx);
1140 assert!(shaped_width("abcd的s", &font, font_size, cx) > width);
1141 let mut wrapper = TextWrapper::new(font.clone(), font_size, Some(width));
1142 let mut previous = Rope::new();
1143
1144 for value in ["abcd的", "abcd的s", "abcd的ss", "abcd的s", "abcd的"] {
1145 let text = Rope::from(value);
1146 let start = previous.len().min(text.len());
1147 let inserted = Rope::from(text.slice(start..).to_string());
1148 wrapper.update(&text, &(start..previous.len()), &inserted, cx);
1149 let expected = if value.ends_with('s') {
1150 vec![0..prefix.len(), prefix.len()..value.len()]
1151 } else {
1152 vec![0..prefix.len()]
1153 };
1154 assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), expected);
1155 for range in &wrapper.line(0).unwrap().wrapped_lines {
1156 assert!(shaped_width(&value[range.clone()], &font, font_size, cx) <= width);
1157 }
1158 assert_eq!(
1159 wrapper.offset_to_display_point(value.len()).row,
1160 usize::from(value.ends_with('s'))
1161 );
1162 previous = text;
1163 }
1164 });
1165 }
1166
1167 #[cfg(target_os = "linux")]
1168 #[test]
1169 fn production_wrap_preserves_words_and_complete_graphemes() {
1170 let cx = shaping_test_context();
1171 cx.update(|cx| {
1172 let font = test_font();
1173 let font_size = px(14.);
1174 for (value, prefix) in [("hello world", "hello "), ("a👩💻b", "a👩💻")] {
1175 let width = shaped_width(prefix, &font, font_size, cx).max(shaped_width(
1178 &value[prefix.len()..],
1179 &font,
1180 font_size,
1181 cx,
1182 ));
1183 assert!(shaped_width(value, &font, font_size, cx) > width);
1184 let text = Rope::from(value);
1185 let mut wrapper = TextWrapper::new(font.clone(), font_size, Some(width));
1186 wrapper.update(&text, &(0..0), &text, cx);
1187 assert_eq!(
1188 wrapper.line(0).unwrap().wrapped_lines.as_slice(),
1189 [0..prefix.len(), prefix.len()..value.len()]
1190 );
1191 for range in &wrapper.line(0).unwrap().wrapped_lines {
1192 assert!(shaped_width(&value[range.clone()], &font, font_size, cx) <= width);
1193 }
1194 }
1195 });
1196 }
1197
1198 #[test]
1199 fn measured_wrap_keeps_cjk_latin_boundary_stable_during_edits() {
1200 let measure = |text: &str| {
1201 px(text
1202 .chars()
1203 .map(|c| if c.is_ascii() { 1. } else { 2. })
1204 .sum())
1205 };
1206 let mut wrapper = TextWrapper::new(gpui::font("Arial"), px(14.), Some(px(6.)));
1207 let mut previous = Rope::new();
1208 for value in ["abcd的", "abcd的s", "abcd的ss", "abcd的s", "abcd的"] {
1209 let text = Rope::from(value);
1210 let start = previous.len().min(text.len());
1211 let inserted = Rope::from(text.slice(start..).to_string());
1212 wrapper._update(
1213 &text,
1214 &(start..previous.len()),
1215 &inserted,
1216 &mut |line, width, _| {
1217 measured_wrap_boundaries(line, width, WrappingIndent::None, measure)
1218 },
1219 );
1220 let expected = if value.ends_with('s') {
1221 vec![0..7, 7..value.len()]
1222 } else {
1223 vec![0..7]
1224 };
1225 assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), expected);
1226 let cursor = wrapper.offset_to_display_point(value.len());
1227 assert_eq!(cursor.row, usize::from(value.ends_with('s')));
1228 previous = text;
1229 }
1230 }
1231
1232 #[test]
1233 fn measured_wrap_preserves_words_graphemes_and_indentation() {
1234 let wrap = |text: &str, width, indent| {
1235 measured_wrap_boundaries(text, px(width), indent, |s| {
1236 px(s.graphemes(true).count() as f32)
1237 })
1238 .into_iter()
1239 .map(|b| b.ix)
1240 .collect::<Vec<_>>()
1241 };
1242 assert_eq!(wrap("hello world", 8., WrappingIndent::None), vec![6]);
1243 assert_eq!(wrap("a👩💻b", 2., WrappingIndent::None), vec!["a👩💻".len()]);
1244 assert_eq!(wrap(" abcdefgh", 5., WrappingIndent::Same), vec![5, 8]);
1245 assert_eq!(wrap(" abcdefgh", 5., WrappingIndent::None), vec![2, 7]);
1246 assert!(wrap("", 0., WrappingIndent::None).is_empty());
1247 assert_eq!(wrap("abc", 0., WrappingIndent::None), vec![1, 2]);
1248 assert_eq!(wrap("你好,世界", 2., WrappingIndent::None), vec![3, 9]);
1250 }
1251
1252 #[test]
1253 fn test_update() {
1254 let font = gpui::Font {
1255 family: "Arial".into(),
1256 weight: FontWeight::default(),
1257 style: FontStyle::Normal,
1258 features: FontFeatures::default(),
1259 fallbacks: None,
1260 };
1261
1262 let mut wrapper = TextWrapper::new(font, px(14.), None);
1263 let mut text = Rope::from(
1264 "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1265 );
1266
1267 fn fake_wrap_line(_line: &str, _wrap_width: Pixels, _: usize) -> Vec<Boundary> {
1268 vec![]
1269 }
1270
1271 #[track_caller]
1272 fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) {
1273 let mut actual_lines = vec![];
1274 let mut offset = 0;
1275 for line in wrapper.iter_lines() {
1276 actual_lines.push(
1277 line.wrapped_lines
1278 .iter()
1279 .map(|range| text.slice(offset + range.start..offset + range.end))
1280 .collect::<Vec<_>>(),
1281 );
1282 offset += line.len() + 1;
1284 }
1285 assert_eq!(actual_lines, expected_lines);
1286 }
1287
1288 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1289 assert_eq!(wrapper.lines_count(), 4);
1290 assert_wrapper_lines(
1291 &text,
1292 &wrapper,
1293 &[
1294 &["Hello, 世界!\r"],
1295 &["This is second line."],
1296 &["This is third line."],
1297 &["这里是第 4 行。"],
1298 ],
1299 );
1300
1301 let range = text.len()..text.len();
1303 let new_text = "New text";
1304 text.replace(range.clone(), new_text);
1305 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1306 assert_eq!(
1307 text.to_string(),
1308 "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
1309 );
1310 assert_eq!(wrapper.lines_count(), 4);
1311 assert_eq!(wrapper.lines_count(), 4);
1312 assert_wrapper_lines(
1313 &text,
1314 &wrapper,
1315 &[
1316 &["Hello, 世界!\r"],
1317 &["This is second line."],
1318 &["This is third line."],
1319 &["这里是第 4 行。New text"],
1320 ],
1321 );
1322
1323 let range = 0..5;
1325 let new_text = "AAA";
1326 text.replace(range.clone(), new_text);
1327 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1328 assert_eq!(
1329 text.to_string(),
1330 "AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
1331 );
1332 assert_eq!(wrapper.lines_count(), 4);
1333 assert_wrapper_lines(
1334 &text,
1335 &wrapper,
1336 &[
1337 &["AAA, 世界!\r"],
1338 &["This is second line."],
1339 &["This is third line."],
1340 &["这里是第 4 行。New text"],
1341 ],
1342 );
1343
1344 let start_offset = text.line_start_offset(1);
1346 let end_offset = text.line_end_offset(1);
1347 let range = start_offset..end_offset + 1;
1348 text.replace(range.clone(), "");
1349 wrapper._update(&text, &range, &Rope::from(""), &mut fake_wrap_line);
1350 assert_eq!(
1351 text.to_string(),
1352 "AAA, 世界!\r\nThis is third line.\n这里是第 4 行。New text"
1353 );
1354 assert_eq!(wrapper.lines_count(), 3);
1355 assert_wrapper_lines(
1356 &text,
1357 &wrapper,
1358 &[
1359 &["AAA, 世界!\r"],
1360 &["This is third line."],
1361 &["这里是第 4 行。New text"],
1362 ],
1363 );
1364
1365 let range = text.line_start_offset(0)..text.line_end_offset(1) + 1;
1367 let new_text = "This is a new line.\nThis is new line 2.\n";
1368 text.replace(range.clone(), new_text);
1369 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1370 assert_eq!(
1371 text.to_string(),
1372 "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text"
1373 );
1374 assert_eq!(wrapper.lines_count(), 3);
1375 assert_wrapper_lines(
1376 &text,
1377 &wrapper,
1378 &[
1379 &["This is a new line."],
1380 &["This is new line 2."],
1381 &["这里是第 4 行。New text"],
1382 ],
1383 );
1384
1385 let range = text.len()..text.len();
1387 let new_text = "\nThis is a new line at the end.";
1388 text.replace(range.clone(), new_text);
1389 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1390 assert_eq!(
1391 text.to_string(),
1392 "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end."
1393 );
1394 assert_eq!(wrapper.lines_count(), 4);
1395 assert_wrapper_lines(
1396 &text,
1397 &wrapper,
1398 &[
1399 &["This is a new line."],
1400 &["This is new line 2."],
1401 &["这里是第 4 行。New text"],
1402 &["This is a new line at the end."],
1403 ],
1404 );
1405
1406 let range = 0..0;
1408 let new_text = "This is a new line at the beginning.\n";
1409 text.replace(range.clone(), new_text);
1410 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1411 assert_eq!(
1412 text.to_string(),
1413 "This is a new line at the beginning.\nThis is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end."
1414 );
1415 assert_eq!(wrapper.lines_count(), 5);
1416 assert_wrapper_lines(
1417 &text,
1418 &wrapper,
1419 &[
1420 &["This is a new line at the beginning."],
1421 &["This is a new line."],
1422 &["This is new line 2."],
1423 &["这里是第 4 行。New text"],
1424 &["This is a new line at the end."],
1425 ],
1426 );
1427
1428 let range = 0..text.len();
1430 let new_text = "";
1431 text.replace(range.clone(), new_text);
1432 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1433 assert_eq!(text.to_string(), "");
1434 assert_eq!(wrapper.lines_count(), 1);
1435 assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1436
1437 let range = 0..text.len();
1439 let new_text = "This is a full text.\nThis is a second line.";
1440 text.replace(range.clone(), new_text);
1441 wrapper._update(&text, &range, &text, &mut fake_wrap_line);
1442 assert_eq!(
1443 text.to_string(),
1444 "This is a full text.\nThis is a second line."
1445 );
1446 assert_eq!(wrapper.lines_count(), 2);
1447 }
1448
1449 fn test_font() -> gpui::Font {
1450 gpui::Font {
1451 family: "Arial".into(),
1452 weight: FontWeight::default(),
1453 style: FontStyle::Normal,
1454 features: FontFeatures::default(),
1455 fallbacks: None,
1456 }
1457 }
1458
1459 #[test]
1461 fn test_longest_row_after_shrink() {
1462 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1463 let mut text = Rope::from("aa\nthis is the longest line\nbb");
1464 wrapper._update(&text, &(0..text.len()), &text, &mut |_, _, _| vec![]);
1465 assert_eq!(wrapper.longest_row(), 1);
1466
1467 let start = text.line_start_offset(0);
1470 let end = text.line_end_offset(0);
1471 let range = start..end;
1472 let new_text = "a very very long first line now";
1473 text.replace(range.clone(), new_text);
1474 wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _, _| vec![]);
1475 assert_eq!(wrapper.longest_row(), 0);
1476 }
1477
1478 #[test]
1480 fn test_edit_last_line_and_full_delete() {
1481 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1482 let mut text = Rope::from("one\ntwo\nthree");
1483 wrapper._update(&text, &(0..text.len()), &text, &mut |_, _, _| vec![]);
1484 assert_eq!(wrapper.lines_count(), 3);
1485
1486 let start = text.line_start_offset(2);
1488 let range = start..text.len();
1489 let new_text = "THREE EDITED";
1490 text.replace(range.clone(), new_text);
1491 wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _, _| vec![]);
1492 assert_eq!(wrapper.lines_count(), 3);
1493 assert_eq!(wrapper.line(2).unwrap().len(), "THREE EDITED".len());
1494
1495 let range = 0..text.len();
1497 text.replace(range.clone(), "");
1498 wrapper._update(&text, &range, &Rope::from(""), &mut |_, _, _| vec![]);
1499 assert_eq!(wrapper.lines_count(), 1);
1500 assert_eq!(wrapper.len(), 1);
1501 assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1502 }
1503
1504 #[test]
1505 fn test_wrap_row_buffer_line_boundaries() {
1506 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1507 wrapper.text = Rope::from("aa\nbbbb\nc");
1508 wrapper.lines = SumTree::from_iter(
1509 vec![
1510 LineItem {
1511 len: 2,
1512 indent: 0,
1513 wrapped_lines: smallvec::smallvec![0..2],
1514 },
1515 LineItem {
1516 len: 4,
1517 indent: 0,
1518 wrapped_lines: smallvec::smallvec![0..2, 2..4],
1519 },
1520 LineItem {
1521 len: 1,
1522 indent: 0,
1523 wrapped_lines: smallvec::smallvec![0..1],
1524 },
1525 ],
1526 &(),
1527 );
1528
1529 assert_eq!(wrapper.lines_count(), 3);
1530 assert_eq!(wrapper.len(), 4);
1531
1532 assert_eq!(wrapper.buffer_line_to_first_wrap_row(0), 0);
1533 assert_eq!(wrapper.buffer_line_to_first_wrap_row(1), 1);
1534 assert_eq!(wrapper.buffer_line_to_first_wrap_row(2), 3);
1535 assert_eq!(wrapper.buffer_line_to_first_wrap_row(3), 4);
1536
1537 assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1538 assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1539 assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1540 assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..4);
1541
1542 assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1543 assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1544 assert_eq!(wrapper.wrap_row_to_buffer_line(2), 1);
1545 assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1546 assert_eq!(wrapper.wrap_row_to_buffer_line(4), 2);
1547 }
1548
1549 #[test]
1550 fn test_wrap_row_queries_after_incremental_splice() {
1551 let mut wrapper = TextWrapper::new(test_font(), px(14.), Some(px(10.)));
1552 let mut text = Rope::from("aa\nbbbb\nc");
1553 let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1554 if line.len() > 2 {
1555 vec![Boundary {
1556 ix: 2,
1557 next_indent: 0,
1558 }]
1559 } else {
1560 vec![]
1561 }
1562 };
1563
1564 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1565 assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1566 assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1567 assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1568
1569 let range = text.line_start_offset(1)..text.line_end_offset(1);
1570 let new_text = "dd\neeee";
1571 text.replace(range.clone(), new_text);
1572 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1573
1574 assert_eq!(wrapper.lines_count(), 4);
1575 assert_eq!(wrapper.len(), 5);
1576 assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1577 assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..2);
1578 assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 2..4);
1579 assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..5);
1580 assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1581 assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1582 assert_eq!(wrapper.wrap_row_to_buffer_line(2), 2);
1583 assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1584 assert_eq!(wrapper.wrap_row_to_buffer_line(4), 3);
1585 }
1586
1587 #[test]
1588 fn test_line_layout() {
1589 let mut line_layout = LineLayout::new();
1590
1591 let line1 = ShapedLine::default().with_len(100);
1592 let line2 = ShapedLine::default().with_len(50);
1593 let wrapped_lines = smallvec::smallvec![line1, line2];
1594 line_layout.set_wrapped_lines(wrapped_lines);
1595 assert_eq!(line_layout.len(), 150);
1596 assert_eq!(line_layout.wrapped_lines.len(), 2);
1597 }
1598
1599 fn test_last_layout(line_height: Pixels) -> LastLayout {
1601 LastLayout {
1602 visible_range: 0..1,
1603 visible_buffer_lines: vec![0],
1604 visible_line_byte_offsets: vec![0],
1605 visible_top: px(0.),
1606 visible_range_offset: 0..0,
1607 lines: Rc::new(vec![]),
1608 line_height,
1609 wrap_width: None,
1610 wrapping_indent: WrappingIndent::default(),
1611 line_number_width: px(0.),
1612 space_width: px(0.),
1613 cursor_bounds: None,
1614 text_align: TextAlign::Left,
1615 content_width: px(0.),
1616 }
1617 }
1618
1619 #[test]
1620 fn test_position_for_index_prefers_first_leading_empty_visual_line() {
1621 let mut line_layout = LineLayout::new();
1622 line_layout.set_wrapped_lines(smallvec::smallvec![
1623 ShapedLine::default(),
1624 ShapedLine::default(),
1625 ShapedLine::default().with_len(3),
1626 ]);
1627
1628 assert_eq!(
1629 line_layout.position_for_index(0, &test_last_layout(px(20.)), false),
1630 Some(point(px(0.), px(0.)))
1631 );
1632 }
1633
1634 #[test]
1635 fn clicking_past_a_wrapped_row_keeps_the_caret_on_that_row() {
1636 let mut line_layout = LineLayout::new();
1638 line_layout.set_wrapped_lines(smallvec::smallvec![
1639 ShapedLine::default().with_len(10),
1640 ShapedLine::default().with_len(5),
1641 ]);
1642 let last_layout = test_last_layout(px(20.));
1643
1644 let (ix, line_end_affinity) = line_layout
1647 .closest_index_for_position(point(px(999.), px(5.)), &last_layout)
1648 .unwrap();
1649 assert_eq!(ix, 10);
1650 assert!(line_end_affinity);
1651
1652 assert_eq!(
1655 line_layout
1656 .position_for_index(ix, &last_layout, line_end_affinity)
1657 .map(|pos| pos.y),
1658 Some(px(0.))
1659 );
1660 assert_eq!(
1661 line_layout
1662 .position_for_index(ix, &last_layout, false)
1663 .map(|pos| pos.y),
1664 Some(px(20.))
1665 );
1666
1667 let (ix, line_end_affinity) = line_layout
1669 .closest_index_for_position(point(px(999.), px(25.)), &last_layout)
1670 .unwrap();
1671 assert_eq!(ix, 15);
1672 assert!(!line_end_affinity);
1673 }
1674
1675 #[test]
1676 fn a_wrap_boundary_offset_resolves_to_the_row_the_caret_is_drawn_on() {
1677 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1678 wrapper.text = Rope::from("first line\nthis one wraps");
1679 wrapper.lines = SumTree::from_iter(
1680 vec![
1681 LineItem {
1682 len: Rope::from("first line").len(),
1683 indent: 0,
1684 wrapped_lines: smallvec::smallvec![0..10],
1685 },
1686 LineItem {
1687 len: Rope::from("this one wraps").len(),
1688 indent: 0,
1689 wrapped_lines: smallvec::smallvec![0..9, 9..14],
1690 },
1691 ],
1692 &(),
1693 );
1694
1695 assert_eq!(
1699 wrapper.offset_to_display_point_with_affinity(20, true),
1700 WrapDisplayPoint::new(1, 0, 9)
1701 );
1702 assert_eq!(
1703 wrapper.offset_to_display_point_with_affinity(20, false),
1704 WrapDisplayPoint::new(2, 1, 0)
1705 );
1706 assert_eq!(
1707 wrapper.offset_to_display_point(20),
1708 wrapper.offset_to_display_point_with_affinity(20, false)
1709 );
1710
1711 for line_end_affinity in [false, true] {
1713 assert_eq!(
1714 wrapper.offset_to_display_point_with_affinity(23, line_end_affinity),
1715 WrapDisplayPoint::new(2, 1, 3)
1716 );
1717 }
1718 }
1719
1720 #[test]
1721 fn test_offset_to_display_point() {
1722 let font = gpui::Font {
1723 family: "Arial".into(),
1724 weight: FontWeight::default(),
1725 style: FontStyle::Normal,
1726 features: FontFeatures::default(),
1727 fallbacks: None,
1728 };
1729
1730 let mut wrapper = TextWrapper::new(font, px(14.), None);
1731 wrapper.text = Rope::from(
1732 "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1733 );
1734 wrapper.lines = SumTree::from_iter(
1735 vec![
1736 LineItem {
1738 len: Rope::from("Hello, 世界!\r").len(),
1739 indent: 0,
1740 wrapped_lines: smallvec::smallvec![0..15],
1741 },
1742 LineItem {
1744 len: Rope::from("This is second line.\n").len(),
1745 indent: 0,
1746 wrapped_lines: smallvec::smallvec![0..10, 10..20],
1747 },
1748 LineItem {
1750 len: Rope::from("This is third line.\n").len(),
1751 indent: 0,
1752 wrapped_lines: smallvec::smallvec![0..9, 9..15, 15..20],
1753 },
1754 LineItem {
1756 len: Rope::from("这里是第 4 行。").len(),
1757 indent: 0,
1758 wrapped_lines: smallvec::smallvec![0..22],
1759 },
1760 ],
1761 &(),
1762 );
1763
1764 assert_eq!(
1765 wrapper.offset_to_display_point(12),
1766 WrapDisplayPoint::new(0, 0, 12)
1767 );
1768 assert_eq!(
1769 wrapper.offset_to_display_point(15),
1770 WrapDisplayPoint::new(0, 0, 15)
1771 );
1772
1773 assert_eq!(
1774 wrapper.offset_to_display_point(16),
1775 WrapDisplayPoint::new(1, 0, 0)
1776 );
1777 assert_eq!(
1778 wrapper.offset_to_display_point(21),
1779 WrapDisplayPoint::new(1, 0, 5)
1780 );
1781 assert_eq!(
1782 wrapper.offset_to_display_point(27),
1783 WrapDisplayPoint::new(2, 1, 1)
1784 );
1785 assert_eq!(
1786 wrapper.offset_to_display_point(37),
1787 WrapDisplayPoint::new(3, 0, 0)
1788 );
1789 assert_eq!(
1790 wrapper.offset_to_display_point(54),
1791 WrapDisplayPoint::new(5, 2, 2)
1792 );
1793 assert_eq!(
1794 wrapper.offset_to_display_point(59),
1795 WrapDisplayPoint::new(6, 0, 2)
1796 );
1797
1798 assert_eq!(
1799 wrapper.display_point_to_offset(WrapDisplayPoint::new(6, 0, 2)),
1800 59
1801 );
1802 assert_eq!(
1803 wrapper.display_point_to_offset(WrapDisplayPoint::new(5, 2, 2)),
1804 54
1805 );
1806 assert_eq!(
1807 wrapper.display_point_to_offset(WrapDisplayPoint::new(3, 0, 0)),
1808 37
1809 );
1810 assert_eq!(
1811 wrapper.display_point_to_offset(WrapDisplayPoint::new(2, 1, 1)),
1812 27
1813 );
1814 assert_eq!(
1815 wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 5)),
1816 21
1817 );
1818 assert_eq!(
1819 wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 0)),
1820 16
1821 );
1822 assert_eq!(
1823 wrapper.display_point_to_offset(WrapDisplayPoint::new(0, 0, 15)),
1824 15
1825 );
1826 }
1827
1828 #[test]
1829 fn test_wrapping_indent_same_keeps_indent_reserved() {
1830 let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1831 wrapper.wrapping_indent = WrappingIndent::Same;
1832 let text = Rope::from(" abcdefghijklmnopqrstuv");
1833 let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1834 if line.starts_with(' ') {
1835 vec![Boundary {
1836 ix: 5,
1837 next_indent: 2,
1838 }]
1839 } else {
1840 let mut boundaries = vec![];
1841 let mut i = 8;
1842 while i < line.len() {
1843 boundaries.push(Boundary {
1844 ix: i,
1845 next_indent: 0,
1846 });
1847 i += 8;
1848 }
1849 boundaries
1850 }
1851 };
1852
1853 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1854
1855 let line = wrapper.line(0).unwrap();
1856 assert_eq!(line.indent, 2);
1857 assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..24]);
1858 }
1859
1860 #[test]
1861 fn test_wrapping_indent_none_continuation_lines_wrapped_at_full_width() {
1862 let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1863 wrapper.wrapping_indent = WrappingIndent::None;
1864 let text = Rope::from(" abcdefghijklmnopqrstuv");
1865 let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1866 if line.starts_with(' ') {
1867 vec![Boundary {
1868 ix: 5,
1869 next_indent: 2,
1870 }]
1871 } else {
1872 let mut boundaries = vec![];
1873 let mut i = 8;
1874 while i < line.len() {
1875 boundaries.push(Boundary {
1876 ix: i,
1877 next_indent: 0,
1878 });
1879 i += 8;
1880 }
1881 boundaries
1882 }
1883 };
1884
1885 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1886
1887 let line = wrapper.line(0).unwrap();
1888 assert_eq!(line.indent, 0);
1889 assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..13, 13..21, 21..24]);
1890 }
1891
1892 #[test]
1893 fn test_wrap_indent_offsets_continuation_lines() {
1894 let mut line_layout = LineLayout::new();
1895 line_layout.set_wrapped_lines(smallvec::smallvec![
1896 ShapedLine::default().with_len(5),
1897 ShapedLine::default().with_len(10),
1898 ]);
1899
1900 line_layout = line_layout.wrap_indent(px(20.0));
1901
1902 let last_layout = LastLayout {
1903 visible_range: 0..1,
1904 visible_buffer_lines: vec![0],
1905 visible_line_byte_offsets: vec![0],
1906 visible_top: px(0.),
1907 visible_range_offset: 0..0,
1908 lines: Rc::new(vec![]),
1909 line_height: px(20.0),
1910 wrap_width: Some(px(10.)),
1911 wrapping_indent: WrappingIndent::Same,
1912 line_number_width: px(0.),
1913 space_width: px(0.),
1914 cursor_bounds: None,
1915 text_align: TextAlign::Left,
1916 content_width: px(0.),
1917 };
1918
1919 assert_eq!(
1920 line_layout.position_for_index(0, &last_layout, false),
1921 Some(point(px(0.), px(0.))),
1922 );
1923
1924 assert_eq!(
1925 line_layout.position_for_index(6, &last_layout, false),
1926 Some(point(px(20.), px(20.))),
1927 )
1928 }
1929}