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};
13
14use crate::input::{
15 Point as TreeSitterPoint, RopeExt,
16 layout::{LastLayout, WhitespaceIndicators},
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum WrappingIndent {
22 None,
24 #[default]
26 Same,
27}
28
29#[derive(Debug, Clone)]
31pub(crate) struct LineItem {
32 len: usize,
34 pub(crate) indent: u32,
39 pub(crate) wrapped_lines: SmallVec<[Range<usize>; 1]>,
43}
44
45impl LineItem {
46 #[inline]
48 pub(crate) fn len(&self) -> usize {
49 self.len
50 }
51
52 #[inline]
54 pub(crate) fn lines_len(&self) -> usize {
55 self.wrapped_lines.len()
56 }
57}
58
59#[derive(Debug, Clone)]
61pub(crate) struct LineSummary {
62 buffer_rows: usize,
64 wrap_rows: usize,
66 bytes: usize,
68 max_line_len: usize,
70 longest_row: usize,
72}
73
74impl sum_tree::Summary for LineSummary {
75 type Context<'a> = &'a ();
76
77 fn zero(_: &()) -> Self {
78 LineSummary {
79 buffer_rows: 0,
80 wrap_rows: 0,
81 bytes: 0,
82 max_line_len: 0,
83 longest_row: 0,
84 }
85 }
86
87 fn add_summary(&mut self, other: &Self, _: &()) {
88 if other.max_line_len > self.max_line_len {
90 self.longest_row = self.buffer_rows + other.longest_row;
91 self.max_line_len = other.max_line_len;
92 }
93 self.buffer_rows += other.buffer_rows;
94 self.wrap_rows += other.wrap_rows;
95 self.bytes += other.bytes;
96 }
97}
98
99impl sum_tree::Item for LineItem {
100 type Summary = LineSummary;
101
102 fn summary(&self, _: &()) -> LineSummary {
103 LineSummary {
104 buffer_rows: 1,
105 wrap_rows: self.lines_len(),
106 bytes: self.len(),
107 max_line_len: self.len(),
108 longest_row: 0,
109 }
110 }
111}
112
113#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
115pub(crate) struct BufferRows(pub usize);
116
117impl<'a> sum_tree::Dimension<'a, LineSummary> for BufferRows {
118 fn zero(_: &()) -> Self {
119 BufferRows(0)
120 }
121
122 fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
123 self.0 += summary.buffer_rows;
124 }
125}
126
127#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
129pub(crate) struct WrapRows(pub usize);
130
131impl<'a> sum_tree::Dimension<'a, LineSummary> for WrapRows {
132 fn zero(_: &()) -> Self {
133 WrapRows(0)
134 }
135
136 fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
137 self.0 += summary.wrap_rows;
138 }
139}
140
141pub(crate) struct TextWrapper {
145 text: Rope,
146 font: Font,
147 font_size: Pixels,
148 wrap_width: Option<Pixels>,
150 wrapping_indent: WrappingIndent,
151 pub(crate) lines: SumTree<LineItem>,
153
154 inline_metrics: Rc<[(Range<usize>, Pixels)]>,
155 _initialized: bool,
156}
157
158#[allow(unused)]
159impl TextWrapper {
160 pub(crate) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
161 Self {
162 text: Rope::new(),
163 font,
164 font_size,
165 wrap_width,
166 wrapping_indent: WrappingIndent::default(),
167 lines: SumTree::new(&()),
168 inline_metrics: Rc::from([]),
169 _initialized: false,
170 }
171 }
172
173 #[inline]
174 pub(crate) fn set_default_text(&mut self, text: &Rope) {
175 self.text = text.clone();
176 }
177
178 #[inline]
180 pub(crate) fn text(&self) -> &Rope {
181 &self.text
182 }
183
184 #[inline]
186 pub(crate) fn len(&self) -> usize {
187 self.lines.summary().wrap_rows
188 }
189
190 #[inline]
192 pub(crate) fn lines_count(&self) -> usize {
193 self.lines.summary().buffer_rows
194 }
195
196 #[inline]
198 pub(crate) fn longest_row(&self) -> usize {
199 self.lines.summary().longest_row
200 }
201
202 #[inline]
204 pub(crate) fn line(&self, row: usize) -> Option<&LineItem> {
205 let mut cursor = self.lines.cursor::<BufferRows>(&());
206 cursor.seek(&BufferRows(row), Bias::Right);
207 cursor.item()
208 }
209
210 #[inline]
212 pub(crate) fn iter_lines(&self) -> impl Iterator<Item = &LineItem> {
213 self.lines.iter()
214 }
215
216 pub(crate) fn buffer_line_to_first_wrap_row(&self, row: usize) -> usize {
219 let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
220 cursor.seek(&BufferRows(row), Bias::Right);
221 cursor.start().1.0
222 }
223
224 pub(crate) fn buffer_line_to_wrap_row_range(&self, row: usize) -> Range<usize> {
226 let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
227 cursor.seek(&BufferRows(row), Bias::Right);
228 let start = cursor.start().1.0;
229 let len = cursor.item().map(|l| l.lines_len()).unwrap_or(0);
230 start..start + len
231 }
232
233 pub(crate) fn wrap_row_to_buffer_line(&self, wrap_row: usize) -> usize {
235 let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
236 cursor.seek(&WrapRows(wrap_row), Bias::Right);
237 match cursor.item() {
238 Some(_) => cursor.start().1.0,
239 None => self.lines_count().saturating_sub(1),
240 }
241 }
242
243 pub(crate) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
244 if wrap_width == self.wrap_width {
245 return;
246 }
247
248 self.wrap_width = wrap_width;
249 self.update_all(&self.text.clone(), cx);
250 }
251
252 pub(crate) fn set_wrapping_indent(&mut self, wrapping_indent: WrappingIndent, cx: &mut App) {
253 if wrapping_indent == self.wrapping_indent {
254 return;
255 }
256
257 self.wrapping_indent = wrapping_indent;
258 self.update_all(&self.text.clone(), cx);
259 }
260
261 pub(crate) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
262 if self.font.eq(&font) && self.font_size == font_size {
263 return;
264 }
265
266 self.font = font;
267 self.font_size = font_size;
268 self.update_all(&self.text.clone(), cx);
269 }
270
271 pub(crate) fn prepare_if_need(&mut self, text: &Rope, cx: &mut App) -> bool {
272 if self._initialized {
273 return false;
274 }
275 self._initialized = true;
276 self.update_all(text, cx);
277 true
278 }
279
280 pub(crate) fn update(
290 &mut self,
291 changed_text: &Rope,
292 range: &Range<usize>,
293 new_text: &Rope,
294 cx: &mut App,
295 ) {
296 let mut line_wrapper = cx
297 .text_system()
298 .line_wrapper(self.font.clone(), self.font_size);
299 let metrics = self.inline_metrics.clone();
300 self._update(
301 changed_text,
302 range,
303 new_text,
304 &mut |line_str, wrap_width, line_start| {
305 let mut fragments = Vec::new();
306 let mut offset = 0;
307 let first = metrics.partition_point(|(r, _)| r.end <= line_start);
308 for (range, width) in &metrics[first..] {
309 if range.start >= line_start + line_str.len() {
310 break;
311 }
312 if range.start < line_start || range.end > line_start + line_str.len() {
313 continue;
314 }
315 let range = range.start - line_start..range.end - line_start;
316 if !line_str.is_char_boundary(range.start)
317 || !line_str.is_char_boundary(range.end)
318 {
319 continue;
320 }
321 if offset < range.start {
322 fragments.push(LineFragment::text(&line_str[offset..range.start]));
323 }
324 fragments.push(LineFragment::element(*width, range.len()));
325 offset = range.end;
326 }
327 if fragments.is_empty() {
328 return line_wrapper
329 .wrap_line(&[LineFragment::text(line_str)], wrap_width)
330 .collect();
331 }
332 if offset < line_str.len() {
333 fragments.push(LineFragment::text(&line_str[offset..]));
334 }
335 line_wrapper.wrap_line(&fragments, wrap_width).collect()
336 },
337 );
338 }
339
340 pub(crate) fn adjust_inline_metrics(&mut self, range: &Range<usize>, new_len: usize) {
341 if self.inline_metrics.is_empty() {
342 return;
343 }
344 let shift = new_len as isize - range.len() as isize;
345 self.inline_metrics = self
346 .inline_metrics
347 .iter()
348 .filter_map(|(token, width)| {
349 if token.start < range.end && range.start < token.end {
350 return None;
351 }
352 let token = if token.start >= range.end {
353 token.start.checked_add_signed(shift)?..token.end.checked_add_signed(shift)?
354 } else {
355 token.clone()
356 };
357 Some((token, *width))
358 })
359 .collect();
360 }
361
362 pub(crate) fn set_inline_metrics(
363 &mut self,
364 metrics: Rc<[(Range<usize>, Pixels)]>,
365 cx: &mut App,
366 ) {
367 if self.inline_metrics == metrics {
368 return;
369 }
370 let mut affected = Vec::new();
372 let (mut old, mut new) = (
373 self.inline_metrics.iter().peekable(),
374 metrics.iter().peekable(),
375 );
376 while old.peek().is_some() || new.peek().is_some() {
377 match (old.peek(), new.peek()) {
378 (Some(a), Some(b)) if a == b => {
379 old.next();
380 new.next();
381 }
382 (Some(a), Some(b)) if a.0.start <= b.0.start => {
383 affected.push(a.0.clone());
384 old.next();
385 }
386 (Some(_), Some(b)) | (None, Some(b)) => {
387 affected.push(b.0.clone());
388 new.next();
389 }
390 (Some(a), None) => {
391 affected.push(a.0.clone());
392 old.next();
393 }
394 (None, None) => break,
395 }
396 }
397 self.inline_metrics = metrics;
398 let text = self.text.clone();
399 let mut rows: Vec<usize> = affected
400 .iter()
401 .map(|r| text.offset_to_point(r.start.min(text.len())).row)
402 .collect();
403 rows.sort_unstable();
404 rows.dedup();
405 for row in rows {
406 let start = text.line_start_offset(row);
407 let end = text.line_end_offset(row);
408 self.update(
409 &text,
410 &(start..end),
411 &Rope::from(text.slice(start..end).to_string()),
412 cx,
413 );
414 }
415 }
416
417 fn _update<F>(
418 &mut self,
419 changed_text: &Rope,
420 range: &Range<usize>,
421 new_text: &Rope,
422 wrap_line: &mut F,
423 ) where
424 F: FnMut(&str, Pixels, usize) -> Vec<gpui::Boundary>,
425 {
426 let buffer_line_count = self.lines_count();
428 let start_row = self.text.offset_to_point(range.start).row;
429 let start_row = start_row.min(buffer_line_count.saturating_sub(1));
430 let end_row = self.text.offset_to_point(range.end).row;
431 let end_row = end_row.min(buffer_line_count.saturating_sub(1));
432
433 let new_start_row = changed_text.offset_to_point(range.start).row;
435 let new_end_row = changed_text
436 .offset_to_point(range.start + new_text.len())
437 .row;
438
439 let mut new_lines = Vec::with_capacity(new_end_row.saturating_sub(new_start_row) + 1);
440 let wrap_width = self.wrap_width;
441
442 for row in new_start_row..=new_end_row {
444 let line = changed_text.slice_line(row);
445 let mut wrapped_lines = SmallVec::<[Range<usize>; 1]>::new();
446 let mut prev_boundary_ix = 0;
447 let mut indent_chars = 0;
448
449 if let Some(wrap_width) = wrap_width {
451 let line_str: Cow<str> = line.into();
453 match self.wrapping_indent {
454 WrappingIndent::Same => {
455 for boundary in
458 wrap_line(&line_str, wrap_width, changed_text.line_start_offset(row))
459 {
460 wrapped_lines.push(prev_boundary_ix..boundary.ix);
461 prev_boundary_ix = boundary.ix;
462 indent_chars = boundary.next_indent;
463 }
464 }
465 WrappingIndent::None => {
466 let boundaries =
469 wrap_line(&line_str, wrap_width, changed_text.line_start_offset(row));
470 if let Some(first_ix) = boundaries.first().map(|b| b.ix) {
471 wrapped_lines.push(prev_boundary_ix..first_ix);
472 prev_boundary_ix = first_ix;
473
474 for boundary in wrap_line(
475 &line_str[first_ix..],
476 wrap_width,
477 changed_text.line_start_offset(row) + first_ix,
478 ) {
479 let ix = first_ix + boundary.ix;
480 wrapped_lines.push(prev_boundary_ix..ix);
481 prev_boundary_ix = ix;
482 }
483 }
484 }
485 }
486 }
487
488 if prev_boundary_ix < line.len() || prev_boundary_ix == 0 {
490 wrapped_lines.push(prev_boundary_ix..line.len());
491 }
492
493 new_lines.push(LineItem {
494 len: line.len(),
495 indent: indent_chars,
496 wrapped_lines,
497 });
498 }
499
500 if self.lines.is_empty() {
501 self.lines = SumTree::from_iter(new_lines, &());
502 } else {
503 let mut cursor = self.lines.cursor::<BufferRows>(&());
504 let mut new_tree = cursor.slice(&BufferRows(start_row), Bias::Right);
505 cursor.seek_forward(&BufferRows(end_row + 1), Bias::Right);
507 new_tree.extend(new_lines, &());
508 new_tree.append(cursor.suffix(), &());
510 drop(cursor);
511 self.lines = new_tree;
512 }
513
514 self.text = changed_text.clone();
515 }
516
517 fn update_all(&mut self, text: &Rope, cx: &mut App) {
521 self.update(text, &(0..text.len()), &text, cx);
522 }
523
524 pub(crate) fn offset_to_display_point(&self, offset: usize) -> WrapDisplayPoint {
528 self.offset_to_display_point_with_affinity(offset, false)
529 }
530
531 pub(crate) fn offset_to_display_point_with_affinity(
538 &self,
539 offset: usize,
540 line_end_affinity: bool,
541 ) -> WrapDisplayPoint {
542 let row = self.text.offset_to_point(offset).row;
543 let start = self.text.line_start_offset(row);
544
545 let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
547 cursor.seek(&BufferRows(row), Bias::Right);
548 let wrapped_row = cursor.start().1.0;
549 let Some(line) = cursor.item() else {
550 return WrapDisplayPoint::new(wrapped_row, 0, 0);
551 };
552
553 let local_offset = offset.saturating_sub(start);
554 for (ix, range) in line.wrapped_lines.iter().enumerate() {
555 let matches =
558 range.contains(&local_offset) || (line_end_affinity && local_offset == range.end);
559 if matches {
560 return WrapDisplayPoint::new(
561 wrapped_row + ix,
562 ix,
563 local_offset.saturating_sub(range.start),
564 );
565 }
566 }
567
568 let last_range = line.wrapped_lines.last().unwrap_or(&(0..0));
570 let ix = line.lines_len().saturating_sub(1);
571 return WrapDisplayPoint::new(wrapped_row + ix, ix, last_range.len());
572 }
573
574 pub(crate) fn display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {
578 let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
580 cursor.seek(&WrapRows(point.row), Bias::Right);
581 let Some(line) = cursor.item() else {
582 return self.text.len();
583 };
584 let wrapped_row = cursor.start().0.0;
585 let row = cursor.start().1.0;
586
587 let line_start = self.text.line_start_offset(row);
588 let local_row = point.row.saturating_sub(wrapped_row);
589 if let Some(range) = line.wrapped_lines.get(local_row) {
590 line_start + (range.start + point.column).min(range.end)
591 } else {
592 line_start + line.len()
594 }
595 }
596
597 pub(crate) fn display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint {
598 let offset = self.display_point_to_offset(point);
599 self.text.offset_to_point(offset)
600 }
601
602 pub(crate) fn point_to_display_point(&self, point: TreeSitterPoint) -> WrapDisplayPoint {
603 let offset = self.text.point_to_offset(point);
604 self.offset_to_display_point(offset)
605 }
606}
607
608#[derive(Debug, Clone, Copy, PartialEq, Eq)]
614pub(crate) struct WrapDisplayPoint {
615 pub row: usize,
617 pub local_row: usize,
621 pub column: usize,
623}
624
625impl WrapDisplayPoint {
626 pub(crate) fn new(row: usize, local_row: usize, column: usize) -> Self {
627 Self {
628 row,
629 local_row,
630 column,
631 }
632 }
633}
634
635pub(crate) struct LineLayout {
637 len: usize,
639 pub(crate) wrapped_lines: SmallVec<[InputLine; 1]>,
641 pub(crate) wrap_indent: Pixels,
644 pub(crate) longest_width: Pixels,
645 pub(crate) whitespace_indicators: Option<WhitespaceIndicators>,
646 pub(crate) whitespace_chars: Vec<(usize, Pixels, bool)>,
648 has_background: bool,
651}
652
653impl LineLayout {
654 pub(crate) fn new() -> Self {
655 Self {
656 len: 0,
657 longest_width: px(0.),
658 wrapped_lines: SmallVec::new(),
659 wrap_indent: px(0.),
660 whitespace_chars: Vec::new(),
661 whitespace_indicators: None,
662 has_background: false,
663 }
664 }
665
666 pub(crate) fn with_background(mut self, has_background: bool) -> Self {
668 self.has_background = has_background;
669 self
670 }
671
672 pub(crate) fn wrap_indent(mut self, wrap_indent: Pixels) -> Self {
674 self.wrap_indent = wrap_indent;
675 self
676 }
677
678 #[inline]
681 fn line_indent(&self, line_index: usize) -> Pixels {
682 if line_index == 0 {
683 px(0.)
684 } else {
685 self.wrap_indent
686 }
687 }
688
689 pub(crate) fn lines(mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) -> Self {
690 self.set_wrapped_lines(wrapped_lines);
691 self
692 }
693
694 pub(crate) fn set_wrapped_lines(&mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) {
695 self.len = wrapped_lines.iter().map(|l| l.len).sum();
696 let width = wrapped_lines
697 .iter()
698 .map(|l| l.width)
699 .max()
700 .unwrap_or_default();
701 self.longest_width = width;
702 self.wrapped_lines = wrapped_lines.into_iter().map(InputLine::from).collect();
703 }
704
705 pub(crate) fn inline_lines(mut self, lines: SmallVec<[InputLine; 1]>) -> Self {
706 self.len = lines.iter().map(|line| line.len).sum();
707 self.longest_width = lines
708 .iter()
709 .map(|line| line.width)
710 .max()
711 .unwrap_or_default();
712 self.wrapped_lines = lines;
713 self
714 }
715
716 pub(crate) fn with_whitespaces(mut self, indicators: Option<WhitespaceIndicators>) -> Self {
717 self.whitespace_indicators = indicators;
718 let Some(indicators) = self.whitespace_indicators.as_ref() else {
719 return self;
720 };
721
722 let space_indicator_offset = indicators.space.width.half();
723
724 for (line_index, wrapped_line) in self.wrapped_lines.iter().enumerate() {
725 for (relative_offset, c) in wrapped_line.text.char_indices() {
726 if matches!(c, ' ' | '\t') {
727 let is_tab = c == '\t';
728 let start_x = wrapped_line.x_for_index(relative_offset);
729 let end_x = wrapped_line.x_for_index(relative_offset + c.len_utf8());
730 let x_position = if c == ' ' {
732 (start_x + end_x).half() - space_indicator_offset
733 } else {
734 start_x
735 };
736
737 self.whitespace_chars.push((line_index, x_position, is_tab));
738 }
739 }
740 }
741 self
742 }
743
744 #[inline]
745 pub(crate) fn len(&self) -> usize {
746 self.len
747 }
748
749 pub(crate) fn position_for_index(
756 &self,
757 offset: usize,
758 last_layout: &LastLayout,
759 line_end_affinity: bool,
760 ) -> Option<Point<Pixels>> {
761 let mut acc_len = 0;
762 let mut offset_y = px(0.);
763
764 let x_offset = last_layout.alignment_offset(self.longest_width);
765
766 for (i, line) in self.wrapped_lines.iter().enumerate() {
767 let is_last = i + 1 == self.wrapped_lines.len();
768
769 let matches = if line.len == 0 {
770 offset == acc_len
772 } else if is_last || line_end_affinity {
773 offset >= acc_len && offset <= acc_len + line.len
775 } else {
776 offset >= acc_len && offset < acc_len + line.len
778 };
779
780 if matches {
781 let x = line.x_for_index(offset.saturating_sub(acc_len))
782 + x_offset
783 + self.line_indent(i);
784 return Some(point(x, offset_y));
785 }
786
787 acc_len += if is_last { line.len + 1 } else { line.len };
790 offset_y += last_layout.line_height;
791 }
792
793 None
794 }
795
796 pub(crate) fn closest_index_for_x(&self, x: Pixels, last_layout: &LastLayout) -> usize {
802 let mut acc_len = 0;
803 let x_offset = last_layout.alignment_offset(self.longest_width);
804 let x = x - x_offset;
805
806 for (i, line) in self.wrapped_lines.iter().enumerate() {
807 let line_indent = self.line_indent(i);
808 if x <= line_indent + line.width {
809 return acc_len + line.closest_index_for_x(x - line_indent);
810 }
811 acc_len += line.len;
812 }
813
814 acc_len
815 }
816
817 fn wrapped_line_at(
822 &self,
823 pos: Point<Pixels>,
824 last_layout: &LastLayout,
825 ) -> Option<(usize, usize, Pixels)> {
826 let mut offset = 0;
827 let mut line_top = px(0.);
828 let x_offset = last_layout.alignment_offset(self.longest_width);
829
830 for (i, line) in self.wrapped_lines.iter().enumerate() {
831 let line_bottom = line_top + last_layout.line_height;
832 if pos.y >= line_top && pos.y < line_bottom {
833 return Some((i, offset, pos.x - x_offset - self.line_indent(i)));
834 }
835
836 offset += line.len;
837 line_top = line_bottom;
838 }
839
840 None
841 }
842
843 pub(crate) fn closest_index_for_position(
854 &self,
855 pos: Point<Pixels>,
856 last_layout: &LastLayout,
857 ) -> Option<(usize, bool)> {
858 let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
859 let line = &self.wrapped_lines[i];
860 let ix = line.closest_index_for_x(x);
861 let line_end_affinity = i + 1 < self.wrapped_lines.len() && ix == line.len;
862
863 Some((offset + ix, line_end_affinity))
864 }
865
866 pub(crate) fn columns_past_line_end(
877 &self,
878 pos: Point<Pixels>,
879 last_layout: &LastLayout,
880 ) -> usize {
881 let Some((i, _, x)) = self.wrapped_line_at(pos, last_layout) else {
882 return 0;
883 };
884
885 if i + 1 < self.wrapped_lines.len() || last_layout.space_width <= px(0.) {
886 return 0;
887 }
888
889 let past_end = x - self.wrapped_lines[i].width;
890 if past_end <= px(0.) {
891 return 0;
892 }
893
894 (past_end / last_layout.space_width).round() as usize
895 }
896
897 pub(crate) fn index_for_position(
898 &self,
899 pos: Point<Pixels>,
900 last_layout: &LastLayout,
901 ) -> Option<usize> {
902 let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
903
904 Some(offset + self.wrapped_lines[i].index_for_x(x)?)
905 }
906
907 pub(crate) fn size(&self, line_height: Pixels) -> Size<Pixels> {
908 let width = self
909 .wrapped_lines
910 .iter()
911 .enumerate()
912 .map(|(ix, line)| line.width + self.line_indent(ix))
913 .max()
914 .unwrap_or(self.longest_width);
915 size(width, self.wrapped_lines.len() * line_height)
916 }
917
918 pub(crate) fn paint_background(
923 &self,
924 pos: Point<Pixels>,
925 line_height: Pixels,
926 text_align: TextAlign,
927 align_width: Option<Pixels>,
928 window: &mut Window,
929 cx: &mut App,
930 ) {
931 if !self.has_background {
934 return;
935 }
936
937 for (ix, line) in self.wrapped_lines.iter().enumerate() {
938 _ = line.paint_background(
939 pos + point(self.line_indent(ix), ix * line_height),
940 line_height,
941 text_align,
942 align_width,
943 window,
944 cx,
945 );
946 }
947 }
948
949 pub(crate) fn paint(
950 &self,
951 pos: Point<Pixels>,
952 line_height: Pixels,
953 text_align: TextAlign,
954 align_width: Option<Pixels>,
955 window: &mut Window,
956 cx: &mut App,
957 ) {
958 for (ix, line) in self.wrapped_lines.iter().enumerate() {
959 _ = line.paint(
960 pos + point(self.line_indent(ix), ix * line_height),
961 line_height,
962 text_align,
963 align_width,
964 window,
965 cx,
966 );
967 }
968
969 if let Some(indicators) = self.whitespace_indicators.as_ref() {
971 for (line_index, x_position, is_tab) in &self.whitespace_chars {
972 let invisible = if *is_tab {
973 indicators.tab.clone()
974 } else {
975 indicators.space.clone()
976 };
977
978 let origin = point(
979 pos.x + *x_position + self.line_indent(*line_index),
980 pos.y + *line_index as f32 * line_height,
981 );
982
983 _ = invisible.paint(origin, line_height, text_align, align_width, window, cx);
984 }
985 }
986 }
987}
988
989#[cfg(test)]
990mod tests {
991 use super::*;
992 use std::rc::Rc;
993
994 use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px};
995
996 #[test]
997 fn test_update() {
998 let font = gpui::Font {
999 family: "Arial".into(),
1000 weight: FontWeight::default(),
1001 style: FontStyle::Normal,
1002 features: FontFeatures::default(),
1003 fallbacks: None,
1004 };
1005
1006 let mut wrapper = TextWrapper::new(font, px(14.), None);
1007 let mut text = Rope::from(
1008 "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1009 );
1010
1011 fn fake_wrap_line(_line: &str, _wrap_width: Pixels, _: usize) -> Vec<Boundary> {
1012 vec![]
1013 }
1014
1015 #[track_caller]
1016 fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) {
1017 let mut actual_lines = vec![];
1018 let mut offset = 0;
1019 for line in wrapper.iter_lines() {
1020 actual_lines.push(
1021 line.wrapped_lines
1022 .iter()
1023 .map(|range| text.slice(offset + range.start..offset + range.end))
1024 .collect::<Vec<_>>(),
1025 );
1026 offset += line.len() + 1;
1028 }
1029 assert_eq!(actual_lines, expected_lines);
1030 }
1031
1032 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1033 assert_eq!(wrapper.lines_count(), 4);
1034 assert_wrapper_lines(
1035 &text,
1036 &wrapper,
1037 &[
1038 &["Hello, 世界!\r"],
1039 &["This is second line."],
1040 &["This is third line."],
1041 &["这里是第 4 行。"],
1042 ],
1043 );
1044
1045 let range = text.len()..text.len();
1047 let new_text = "New text";
1048 text.replace(range.clone(), new_text);
1049 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1050 assert_eq!(
1051 text.to_string(),
1052 "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
1053 );
1054 assert_eq!(wrapper.lines_count(), 4);
1055 assert_eq!(wrapper.lines_count(), 4);
1056 assert_wrapper_lines(
1057 &text,
1058 &wrapper,
1059 &[
1060 &["Hello, 世界!\r"],
1061 &["This is second line."],
1062 &["This is third line."],
1063 &["这里是第 4 行。New text"],
1064 ],
1065 );
1066
1067 let range = 0..5;
1069 let new_text = "AAA";
1070 text.replace(range.clone(), new_text);
1071 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1072 assert_eq!(
1073 text.to_string(),
1074 "AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
1075 );
1076 assert_eq!(wrapper.lines_count(), 4);
1077 assert_wrapper_lines(
1078 &text,
1079 &wrapper,
1080 &[
1081 &["AAA, 世界!\r"],
1082 &["This is second line."],
1083 &["This is third line."],
1084 &["这里是第 4 行。New text"],
1085 ],
1086 );
1087
1088 let start_offset = text.line_start_offset(1);
1090 let end_offset = text.line_end_offset(1);
1091 let range = start_offset..end_offset + 1;
1092 text.replace(range.clone(), "");
1093 wrapper._update(&text, &range, &Rope::from(""), &mut fake_wrap_line);
1094 assert_eq!(
1095 text.to_string(),
1096 "AAA, 世界!\r\nThis is third line.\n这里是第 4 行。New text"
1097 );
1098 assert_eq!(wrapper.lines_count(), 3);
1099 assert_wrapper_lines(
1100 &text,
1101 &wrapper,
1102 &[
1103 &["AAA, 世界!\r"],
1104 &["This is third line."],
1105 &["这里是第 4 行。New text"],
1106 ],
1107 );
1108
1109 let range = text.line_start_offset(0)..text.line_end_offset(1) + 1;
1111 let new_text = "This is a new line.\nThis is new line 2.\n";
1112 text.replace(range.clone(), new_text);
1113 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1114 assert_eq!(
1115 text.to_string(),
1116 "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text"
1117 );
1118 assert_eq!(wrapper.lines_count(), 3);
1119 assert_wrapper_lines(
1120 &text,
1121 &wrapper,
1122 &[
1123 &["This is a new line."],
1124 &["This is new line 2."],
1125 &["这里是第 4 行。New text"],
1126 ],
1127 );
1128
1129 let range = text.len()..text.len();
1131 let new_text = "\nThis is a new line at the end.";
1132 text.replace(range.clone(), new_text);
1133 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1134 assert_eq!(
1135 text.to_string(),
1136 "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end."
1137 );
1138 assert_eq!(wrapper.lines_count(), 4);
1139 assert_wrapper_lines(
1140 &text,
1141 &wrapper,
1142 &[
1143 &["This is a new line."],
1144 &["This is new line 2."],
1145 &["这里是第 4 行。New text"],
1146 &["This is a new line at the end."],
1147 ],
1148 );
1149
1150 let range = 0..0;
1152 let new_text = "This is a new line at the beginning.\n";
1153 text.replace(range.clone(), new_text);
1154 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1155 assert_eq!(
1156 text.to_string(),
1157 "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."
1158 );
1159 assert_eq!(wrapper.lines_count(), 5);
1160 assert_wrapper_lines(
1161 &text,
1162 &wrapper,
1163 &[
1164 &["This is a new line at the beginning."],
1165 &["This is a new line."],
1166 &["This is new line 2."],
1167 &["这里是第 4 行。New text"],
1168 &["This is a new line at the end."],
1169 ],
1170 );
1171
1172 let range = 0..text.len();
1174 let new_text = "";
1175 text.replace(range.clone(), new_text);
1176 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1177 assert_eq!(text.to_string(), "");
1178 assert_eq!(wrapper.lines_count(), 1);
1179 assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1180
1181 let range = 0..text.len();
1183 let new_text = "This is a full text.\nThis is a second line.";
1184 text.replace(range.clone(), new_text);
1185 wrapper._update(&text, &range, &text, &mut fake_wrap_line);
1186 assert_eq!(
1187 text.to_string(),
1188 "This is a full text.\nThis is a second line."
1189 );
1190 assert_eq!(wrapper.lines_count(), 2);
1191 }
1192
1193 fn test_font() -> gpui::Font {
1194 gpui::Font {
1195 family: "Arial".into(),
1196 weight: FontWeight::default(),
1197 style: FontStyle::Normal,
1198 features: FontFeatures::default(),
1199 fallbacks: None,
1200 }
1201 }
1202
1203 #[test]
1205 fn test_longest_row_after_shrink() {
1206 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1207 let mut text = Rope::from("aa\nthis is the longest line\nbb");
1208 wrapper._update(&text, &(0..text.len()), &text, &mut |_, _, _| vec![]);
1209 assert_eq!(wrapper.longest_row(), 1);
1210
1211 let start = text.line_start_offset(0);
1214 let end = text.line_end_offset(0);
1215 let range = start..end;
1216 let new_text = "a very very long first line now";
1217 text.replace(range.clone(), new_text);
1218 wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _, _| vec![]);
1219 assert_eq!(wrapper.longest_row(), 0);
1220 }
1221
1222 #[test]
1224 fn test_edit_last_line_and_full_delete() {
1225 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1226 let mut text = Rope::from("one\ntwo\nthree");
1227 wrapper._update(&text, &(0..text.len()), &text, &mut |_, _, _| vec![]);
1228 assert_eq!(wrapper.lines_count(), 3);
1229
1230 let start = text.line_start_offset(2);
1232 let range = start..text.len();
1233 let new_text = "THREE EDITED";
1234 text.replace(range.clone(), new_text);
1235 wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _, _| vec![]);
1236 assert_eq!(wrapper.lines_count(), 3);
1237 assert_eq!(wrapper.line(2).unwrap().len(), "THREE EDITED".len());
1238
1239 let range = 0..text.len();
1241 text.replace(range.clone(), "");
1242 wrapper._update(&text, &range, &Rope::from(""), &mut |_, _, _| vec![]);
1243 assert_eq!(wrapper.lines_count(), 1);
1244 assert_eq!(wrapper.len(), 1);
1245 assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1246 }
1247
1248 #[test]
1249 fn test_wrap_row_buffer_line_boundaries() {
1250 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1251 wrapper.text = Rope::from("aa\nbbbb\nc");
1252 wrapper.lines = SumTree::from_iter(
1253 vec![
1254 LineItem {
1255 len: 2,
1256 indent: 0,
1257 wrapped_lines: smallvec::smallvec![0..2],
1258 },
1259 LineItem {
1260 len: 4,
1261 indent: 0,
1262 wrapped_lines: smallvec::smallvec![0..2, 2..4],
1263 },
1264 LineItem {
1265 len: 1,
1266 indent: 0,
1267 wrapped_lines: smallvec::smallvec![0..1],
1268 },
1269 ],
1270 &(),
1271 );
1272
1273 assert_eq!(wrapper.lines_count(), 3);
1274 assert_eq!(wrapper.len(), 4);
1275
1276 assert_eq!(wrapper.buffer_line_to_first_wrap_row(0), 0);
1277 assert_eq!(wrapper.buffer_line_to_first_wrap_row(1), 1);
1278 assert_eq!(wrapper.buffer_line_to_first_wrap_row(2), 3);
1279 assert_eq!(wrapper.buffer_line_to_first_wrap_row(3), 4);
1280
1281 assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1282 assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1283 assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1284 assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..4);
1285
1286 assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1287 assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1288 assert_eq!(wrapper.wrap_row_to_buffer_line(2), 1);
1289 assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1290 assert_eq!(wrapper.wrap_row_to_buffer_line(4), 2);
1291 }
1292
1293 #[test]
1294 fn test_wrap_row_queries_after_incremental_splice() {
1295 let mut wrapper = TextWrapper::new(test_font(), px(14.), Some(px(10.)));
1296 let mut text = Rope::from("aa\nbbbb\nc");
1297 let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1298 if line.len() > 2 {
1299 vec![Boundary {
1300 ix: 2,
1301 next_indent: 0,
1302 }]
1303 } else {
1304 vec![]
1305 }
1306 };
1307
1308 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1309 assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1310 assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1311 assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1312
1313 let range = text.line_start_offset(1)..text.line_end_offset(1);
1314 let new_text = "dd\neeee";
1315 text.replace(range.clone(), new_text);
1316 wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1317
1318 assert_eq!(wrapper.lines_count(), 4);
1319 assert_eq!(wrapper.len(), 5);
1320 assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1321 assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..2);
1322 assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 2..4);
1323 assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..5);
1324 assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1325 assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1326 assert_eq!(wrapper.wrap_row_to_buffer_line(2), 2);
1327 assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1328 assert_eq!(wrapper.wrap_row_to_buffer_line(4), 3);
1329 }
1330
1331 #[test]
1332 fn test_line_layout() {
1333 let mut line_layout = LineLayout::new();
1334
1335 let line1 = ShapedLine::default().with_len(100);
1336 let line2 = ShapedLine::default().with_len(50);
1337 let wrapped_lines = smallvec::smallvec![line1, line2];
1338 line_layout.set_wrapped_lines(wrapped_lines);
1339 assert_eq!(line_layout.len(), 150);
1340 assert_eq!(line_layout.wrapped_lines.len(), 2);
1341 }
1342
1343 fn test_last_layout(line_height: Pixels) -> LastLayout {
1345 LastLayout {
1346 visible_range: 0..1,
1347 visible_buffer_lines: vec![0],
1348 visible_line_byte_offsets: vec![0],
1349 visible_top: px(0.),
1350 visible_range_offset: 0..0,
1351 lines: Rc::new(vec![]),
1352 line_height,
1353 wrap_width: None,
1354 wrapping_indent: WrappingIndent::default(),
1355 line_number_width: px(0.),
1356 space_width: px(0.),
1357 cursor_bounds: None,
1358 text_align: TextAlign::Left,
1359 content_width: px(0.),
1360 }
1361 }
1362
1363 #[test]
1364 fn test_position_for_index_prefers_first_leading_empty_visual_line() {
1365 let mut line_layout = LineLayout::new();
1366 line_layout.set_wrapped_lines(smallvec::smallvec![
1367 ShapedLine::default(),
1368 ShapedLine::default(),
1369 ShapedLine::default().with_len(3),
1370 ]);
1371
1372 assert_eq!(
1373 line_layout.position_for_index(0, &test_last_layout(px(20.)), false),
1374 Some(point(px(0.), px(0.)))
1375 );
1376 }
1377
1378 #[test]
1379 fn clicking_past_a_wrapped_row_keeps_the_caret_on_that_row() {
1380 let mut line_layout = LineLayout::new();
1382 line_layout.set_wrapped_lines(smallvec::smallvec![
1383 ShapedLine::default().with_len(10),
1384 ShapedLine::default().with_len(5),
1385 ]);
1386 let last_layout = test_last_layout(px(20.));
1387
1388 let (ix, line_end_affinity) = line_layout
1391 .closest_index_for_position(point(px(999.), px(5.)), &last_layout)
1392 .unwrap();
1393 assert_eq!(ix, 10);
1394 assert!(line_end_affinity);
1395
1396 assert_eq!(
1399 line_layout
1400 .position_for_index(ix, &last_layout, line_end_affinity)
1401 .map(|pos| pos.y),
1402 Some(px(0.))
1403 );
1404 assert_eq!(
1405 line_layout
1406 .position_for_index(ix, &last_layout, false)
1407 .map(|pos| pos.y),
1408 Some(px(20.))
1409 );
1410
1411 let (ix, line_end_affinity) = line_layout
1413 .closest_index_for_position(point(px(999.), px(25.)), &last_layout)
1414 .unwrap();
1415 assert_eq!(ix, 15);
1416 assert!(!line_end_affinity);
1417 }
1418
1419 #[test]
1420 fn a_wrap_boundary_offset_resolves_to_the_row_the_caret_is_drawn_on() {
1421 let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1422 wrapper.text = Rope::from("first line\nthis one wraps");
1423 wrapper.lines = SumTree::from_iter(
1424 vec![
1425 LineItem {
1426 len: Rope::from("first line").len(),
1427 indent: 0,
1428 wrapped_lines: smallvec::smallvec![0..10],
1429 },
1430 LineItem {
1431 len: Rope::from("this one wraps").len(),
1432 indent: 0,
1433 wrapped_lines: smallvec::smallvec![0..9, 9..14],
1434 },
1435 ],
1436 &(),
1437 );
1438
1439 assert_eq!(
1443 wrapper.offset_to_display_point_with_affinity(20, true),
1444 WrapDisplayPoint::new(1, 0, 9)
1445 );
1446 assert_eq!(
1447 wrapper.offset_to_display_point_with_affinity(20, false),
1448 WrapDisplayPoint::new(2, 1, 0)
1449 );
1450 assert_eq!(
1451 wrapper.offset_to_display_point(20),
1452 wrapper.offset_to_display_point_with_affinity(20, false)
1453 );
1454
1455 for line_end_affinity in [false, true] {
1457 assert_eq!(
1458 wrapper.offset_to_display_point_with_affinity(23, line_end_affinity),
1459 WrapDisplayPoint::new(2, 1, 3)
1460 );
1461 }
1462 }
1463
1464 #[test]
1465 fn test_offset_to_display_point() {
1466 let font = gpui::Font {
1467 family: "Arial".into(),
1468 weight: FontWeight::default(),
1469 style: FontStyle::Normal,
1470 features: FontFeatures::default(),
1471 fallbacks: None,
1472 };
1473
1474 let mut wrapper = TextWrapper::new(font, px(14.), None);
1475 wrapper.text = Rope::from(
1476 "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1477 );
1478 wrapper.lines = SumTree::from_iter(
1479 vec![
1480 LineItem {
1482 len: Rope::from("Hello, 世界!\r").len(),
1483 indent: 0,
1484 wrapped_lines: smallvec::smallvec![0..15],
1485 },
1486 LineItem {
1488 len: Rope::from("This is second line.\n").len(),
1489 indent: 0,
1490 wrapped_lines: smallvec::smallvec![0..10, 10..20],
1491 },
1492 LineItem {
1494 len: Rope::from("This is third line.\n").len(),
1495 indent: 0,
1496 wrapped_lines: smallvec::smallvec![0..9, 9..15, 15..20],
1497 },
1498 LineItem {
1500 len: Rope::from("这里是第 4 行。").len(),
1501 indent: 0,
1502 wrapped_lines: smallvec::smallvec![0..22],
1503 },
1504 ],
1505 &(),
1506 );
1507
1508 assert_eq!(
1509 wrapper.offset_to_display_point(12),
1510 WrapDisplayPoint::new(0, 0, 12)
1511 );
1512 assert_eq!(
1513 wrapper.offset_to_display_point(15),
1514 WrapDisplayPoint::new(0, 0, 15)
1515 );
1516
1517 assert_eq!(
1518 wrapper.offset_to_display_point(16),
1519 WrapDisplayPoint::new(1, 0, 0)
1520 );
1521 assert_eq!(
1522 wrapper.offset_to_display_point(21),
1523 WrapDisplayPoint::new(1, 0, 5)
1524 );
1525 assert_eq!(
1526 wrapper.offset_to_display_point(27),
1527 WrapDisplayPoint::new(2, 1, 1)
1528 );
1529 assert_eq!(
1530 wrapper.offset_to_display_point(37),
1531 WrapDisplayPoint::new(3, 0, 0)
1532 );
1533 assert_eq!(
1534 wrapper.offset_to_display_point(54),
1535 WrapDisplayPoint::new(5, 2, 2)
1536 );
1537 assert_eq!(
1538 wrapper.offset_to_display_point(59),
1539 WrapDisplayPoint::new(6, 0, 2)
1540 );
1541
1542 assert_eq!(
1543 wrapper.display_point_to_offset(WrapDisplayPoint::new(6, 0, 2)),
1544 59
1545 );
1546 assert_eq!(
1547 wrapper.display_point_to_offset(WrapDisplayPoint::new(5, 2, 2)),
1548 54
1549 );
1550 assert_eq!(
1551 wrapper.display_point_to_offset(WrapDisplayPoint::new(3, 0, 0)),
1552 37
1553 );
1554 assert_eq!(
1555 wrapper.display_point_to_offset(WrapDisplayPoint::new(2, 1, 1)),
1556 27
1557 );
1558 assert_eq!(
1559 wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 5)),
1560 21
1561 );
1562 assert_eq!(
1563 wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 0)),
1564 16
1565 );
1566 assert_eq!(
1567 wrapper.display_point_to_offset(WrapDisplayPoint::new(0, 0, 15)),
1568 15
1569 );
1570 }
1571
1572 #[test]
1573 fn test_wrapping_indent_same_keeps_indent_reserved() {
1574 let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1575 wrapper.wrapping_indent = WrappingIndent::Same;
1576 let text = Rope::from(" abcdefghijklmnopqrstuv");
1577 let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1578 if line.starts_with(' ') {
1579 vec![Boundary {
1580 ix: 5,
1581 next_indent: 2,
1582 }]
1583 } else {
1584 let mut boundaries = vec![];
1585 let mut i = 8;
1586 while i < line.len() {
1587 boundaries.push(Boundary {
1588 ix: i,
1589 next_indent: 0,
1590 });
1591 i += 8;
1592 }
1593 boundaries
1594 }
1595 };
1596
1597 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1598
1599 let line = wrapper.line(0).unwrap();
1600 assert_eq!(line.indent, 2);
1601 assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..24]);
1602 }
1603
1604 #[test]
1605 fn test_wrapping_indent_none_continuation_lines_wrapped_at_full_width() {
1606 let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1607 wrapper.wrapping_indent = WrappingIndent::None;
1608 let text = Rope::from(" abcdefghijklmnopqrstuv");
1609 let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1610 if line.starts_with(' ') {
1611 vec![Boundary {
1612 ix: 5,
1613 next_indent: 2,
1614 }]
1615 } else {
1616 let mut boundaries = vec![];
1617 let mut i = 8;
1618 while i < line.len() {
1619 boundaries.push(Boundary {
1620 ix: i,
1621 next_indent: 0,
1622 });
1623 i += 8;
1624 }
1625 boundaries
1626 }
1627 };
1628
1629 wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1630
1631 let line = wrapper.line(0).unwrap();
1632 assert_eq!(line.indent, 0);
1633 assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..13, 13..21, 21..24]);
1634 }
1635
1636 #[test]
1637 fn test_wrap_indent_offsets_continuation_lines() {
1638 let mut line_layout = LineLayout::new();
1639 line_layout.set_wrapped_lines(smallvec::smallvec![
1640 ShapedLine::default().with_len(5),
1641 ShapedLine::default().with_len(10),
1642 ]);
1643
1644 line_layout = line_layout.wrap_indent(px(20.0));
1645
1646 let last_layout = LastLayout {
1647 visible_range: 0..1,
1648 visible_buffer_lines: vec![0],
1649 visible_line_byte_offsets: vec![0],
1650 visible_top: px(0.),
1651 visible_range_offset: 0..0,
1652 lines: Rc::new(vec![]),
1653 line_height: px(20.0),
1654 wrap_width: Some(px(10.)),
1655 wrapping_indent: WrappingIndent::Same,
1656 line_number_width: px(0.),
1657 space_width: px(0.),
1658 cursor_bounds: None,
1659 text_align: TextAlign::Left,
1660 content_width: px(0.),
1661 };
1662
1663 assert_eq!(
1664 line_layout.position_for_index(0, &last_layout, false),
1665 Some(point(px(0.), px(0.))),
1666 );
1667
1668 assert_eq!(
1669 line_layout.position_for_index(6, &last_layout, false),
1670 Some(point(px(20.), px(20.))),
1671 )
1672 }
1673}