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