Skip to main content

gpui_base/input/editor/display_map/
text_wrapper.rs

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/// Controls how soft-wrapped continuation lines are indented.
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum WrappingIndent {
22    /// Continuation lines start flush-left at the full editor width.
23    None,
24    /// Continuation lines keep the same indentation as the first line.
25    #[default]
26    Same,
27}
28
29/// A line with soft wrapped lines info.
30#[derive(Debug, Clone)]
31pub(crate) struct LineItem {
32    /// The byte length of the line, without the end `\n`.
33    len: usize,
34    /// Number of leading characters of the line reserved as indentation for continuation wrapped
35    /// lines, when [`WrappingIndent::Same`] is used.
36    ///
37    /// Zero when [`WrappingIndent::None`] is used or the line is not wrapped.
38    pub(crate) indent: u32,
39    /// The soft wrapped lines relative byte range (0..len) of this line (Include first line).
40    ///
41    /// Not contains the line end `\n`.
42    pub(crate) wrapped_lines: SmallVec<[Range<usize>; 1]>,
43}
44
45impl LineItem {
46    /// Get the bytes length of this line.
47    #[inline]
48    pub(crate) fn len(&self) -> usize {
49        self.len
50    }
51
52    /// Get number of soft wrapped lines of this line (include the first line).
53    #[inline]
54    pub(crate) fn lines_len(&self) -> usize {
55        self.wrapped_lines.len()
56    }
57}
58
59/// Summary of a subtree of [`LineItem`]s, maintained incrementally by the [`SumTree`].
60#[derive(Debug, Clone)]
61pub(crate) struct LineSummary {
62    /// Number of buffer lines.
63    buffer_rows: usize,
64    /// Number of wrap rows (sum of each line's `lines_len()`).
65    wrap_rows: usize,
66    /// Sum of byte lengths of the buffer lines (without the trailing `\n`).
67    bytes: usize,
68    /// Byte length of the longest line in this subtree.
69    max_line_len: usize,
70    /// Buffer row (relative to this subtree) of the first line achieving `max_line_len`.
71    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        // Keep the leftmost row that achieves a strictly greater length
89        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/// Cursor dimension counting buffer rows.
114#[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/// Cursor dimension counting wrap rows.
128#[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
141/// Used to prepare the text with soft wrap to be get lines to displayed in the Editor.
142///
143/// After use lines to calculate the scroll size of the Editor.
144pub(crate) struct TextWrapper {
145    text: Rope,
146    font: Font,
147    font_size: Pixels,
148    /// If is none, it means the text is not wrapped
149    wrap_width: Option<Pixels>,
150    wrapping_indent: WrappingIndent,
151    /// The lines by split \n
152    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    /// Get reference to the rope text.
179    #[inline]
180    pub(crate) fn text(&self) -> &Rope {
181        &self.text
182    }
183
184    /// Get the total number of lines including wrapped lines.
185    #[inline]
186    pub(crate) fn len(&self) -> usize {
187        self.lines.summary().wrap_rows
188    }
189
190    /// Get the total number of buffer lines.
191    #[inline]
192    pub(crate) fn lines_count(&self) -> usize {
193        self.lines.summary().buffer_rows
194    }
195
196    /// Get the 0-based row index of the longest line (by byte length).
197    #[inline]
198    pub(crate) fn longest_row(&self) -> usize {
199        self.lines.summary().longest_row
200    }
201
202    /// Get the line item by buffer row index.
203    #[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    /// Iterate buffer lines in order.
211    #[inline]
212    pub(crate) fn iter_lines(&self) -> impl Iterator<Item = &LineItem> {
213        self.lines.iter()
214    }
215
216    /// First wrap row of buffer line `row`. Returns the total wrap row count if `row` is
217    /// out of range.
218    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    /// Wrap row range of buffer line `row`.
225    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    /// Buffer line containing wrap row `wrap_row`, clamped to the last line.
234    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    /// Update the text wrapper and recalculate the wrapped lines.
281    ///
282    /// If the `text` is the same as the current text, do nothing.
283    ///
284    /// - `changed_text`: The text [`Rope`] that has changed.
285    /// - `range`: The `selected_range` before change.
286    /// - `new_text`: The inserted text.
287    /// - `force`: Whether to force the update, if false, the update will be skipped if the text is the same.
288    /// - `cx`: The application context.
289    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        // Only rows whose element geometry changed need another wrap pass.
371        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        // Remove the old changed lines.
427        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        // To add the new lines.
434        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        // line not contains `\n`.
443        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 wrap_width is Pixels::MAX, skip wrapping to disable word wrap
450            if let Some(wrap_width) = wrap_width {
451                // Borrowed for lines within a single rope chunk.
452                let line_str: Cow<str> = line.into();
453                match self.wrapping_indent {
454                    WrappingIndent::Same => {
455                        // Here only have wrapped line, if there is no wrap meet, the `line_wraps`
456                        // result will empty.
457                        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                        // The first visual line keeps the line's leading indentation, so it is
467                        // wrapped as is.
468                        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            // Reset of the line
489            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            // Skip the replaced rows
506            cursor.seek_forward(&BufferRows(end_row + 1), Bias::Right);
507            new_tree.extend(new_lines, &());
508            // Untouched rows after the edit
509            new_tree.append(cursor.suffix(), &());
510            drop(cursor);
511            self.lines = new_tree;
512        }
513
514        self.text = changed_text.clone();
515    }
516
517    /// Update the text wrapper and recalculate the wrapped lines.
518    ///
519    /// If the `text` is the same as the current text, do nothing.
520    fn update_all(&mut self, text: &Rope, cx: &mut App) {
521        self.update(text, &(0..text.len()), &text, cx);
522    }
523
524    /// Return display point (with soft wrap) from the given byte offset in the text.
525    ///
526    /// Panics if the `offset` is out of bounds.
527    pub(crate) fn offset_to_display_point(&self, offset: usize) -> WrapDisplayPoint {
528        self.offset_to_display_point_with_affinity(offset, false)
529    }
530
531    /// Like [`Self::offset_to_display_point`], but honours the caret's line-end affinity.
532    ///
533    /// A soft wrap boundary is one offset shared by two visual rows. Without affinity it always
534    /// resolves to the start of the second row, which is wrong for a caret that is being drawn at
535    /// the end of the first one -- vertical movement would then step from the row below the one
536    /// the user can see.
537    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        // Seek to buffer row
546        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            // With affinity the boundary offset closes the current row instead of opening the
556            // next one, so the range is matched inclusively.
557            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        // Otherwise return the eof of the line.
569        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    /// Return byte offset in the text from the given display point (with soft wrap).
575    ///
576    /// Panics if the `point.row` is out of bounds.
577    pub(crate) fn display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {
578        // Seek to wrap row `point.row`
579        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            // If not found, return the end of the line.
593            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/// A display point within the soft-wrapped text.
609///
610/// This represents a position in the text after soft-wrapping,
611/// with an additional `local_row` field tracking the wrap line
612/// within the original buffer line.
613#[derive(Debug, Clone, Copy, PartialEq, Eq)]
614pub(crate) struct WrapDisplayPoint {
615    /// The 0-based soft wrapped row index in the text.
616    pub row: usize,
617    /// The 0-based row index in local line (include first line).
618    ///
619    /// This value only valid when return from [`TextWrapper::offset_to_display_point`], otherwise it will be ignored.
620    pub local_row: usize,
621    /// The 0-based column byte index in the display line (with soft wrap).
622    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
635/// The layout info of a line with soft wrapped lines.
636pub(crate) struct LineLayout {
637    /// Total bytes length of this line.
638    len: usize,
639    /// The soft wrapped lines of this line (Include the first line).
640    pub(crate) wrapped_lines: SmallVec<[InputLine; 1]>,
641    /// Extra left offset applied to continuation wrapped lines, used to reserve the first line's
642    /// indentation when [`WrappingIndent::Same`] is used.
643    pub(crate) wrap_indent: Pixels,
644    pub(crate) longest_width: Pixels,
645    pub(crate) whitespace_indicators: Option<WhitespaceIndicators>,
646    /// Whitespace indicators: (line_index, x_position, is_tab)
647    pub(crate) whitespace_chars: Vec<(usize, Pixels, bool)>,
648    /// Whether any run of this line carries a background color, so [`Self::paint_background`]
649    /// can skip the glyph walk for the common case of a line without highlights.
650    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    /// Record whether any run of this line carries a background color.
667    pub(crate) fn with_background(mut self, has_background: bool) -> Self {
668        self.has_background = has_background;
669        self
670    }
671
672    /// Set the left offset reserved for continuation wrapped lines.
673    pub(crate) fn wrap_indent(mut self, wrap_indent: Pixels) -> Self {
674        self.wrap_indent = wrap_indent;
675        self
676    }
677
678    /// The pixel indent applied to the given visual line, relative to the line's
679    /// leading text. Only continuation lines (index > 0) are indented.
680    #[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                    // Center the indicator in the actual character's space
731                    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    /// Get the position (x, y) for the given index in this line layout.
750    ///
751    /// - The `offset` is a local byte index in this line layout.
752    /// - When `line_end_affinity` is true, an offset at a soft wrap boundary is placed at
753    ///   the end of the current visual line rather than the start of the next one.
754    /// - The return value is relative to the top-left corner of this line layout, start from (0, 0)
755    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                // Empty visual lines still own their boundary offset.
771                offset == acc_len
772            } else if is_last || line_end_affinity {
773                // Inclusive: cursor can sit at end of this visual line.
774                offset >= acc_len && offset <= acc_len + line.len
775            } else {
776                // Exclusive: boundary offset belongs to the next visual line.
777                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            // Always advance by actual line length. The last line gets +1 so the
788            // cursor can be placed after the final character.
789            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    /// Get the closest index for the given x in this line layout.
797    ///
798    /// This ignores y, so it only makes sense for a layout that is known to occupy a single
799    /// visual line. Wrapped layouts must use [`Self::closest_index_for_position`], which also
800    /// reports the caret affinity that a wrap boundary needs.
801    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    /// Resolve `pos` to the wrapped sub-line under it.
818    ///
819    /// Returns the sub-line index, the byte offset that sub-line starts at within this line
820    /// layout, and `pos.x` translated into that sub-line's own coordinate space.
821    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    /// Get the index for the given position (x, y) in this line layout.
844    ///
845    /// The `pos` is relative to the top-left corner of this line layout, start from (0, 0).
846    ///
847    /// Returns a local byte index in this line layout (start from 0) together with the caret
848    /// affinity to use for it: `true` when the index landed on the wrap boundary of a non-final
849    /// sub-line. That boundary offset is shared by the end of one visual line and the start of
850    /// the next, so the affinity is what tells [`Self::position_for_index`] which of the two the
851    /// caret belongs to. Without it a click past the last glyph of a wrapped line would put a
852    /// visible caret on the following line.
853    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    /// How many columns the given position sits past the end of the line under it.
867    ///
868    /// Past the end of a line there is no glyph to hit-test against, so a position out
869    /// there resolves to the line end and loses how far right it really was. The extra
870    /// distance is reported here in whole spaces, letting a columnar selection keep its
871    /// width over a short row. Only the final visual line of a wrapped layout has that
872    /// trailing space; a continuation line ends at a wrap boundary, where the next glyph
873    /// merely lives on the following row.
874    ///
875    /// The `pos` is relative to the top-left corner of this line layout, start from (0, 0).
876    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    /// Paint only the glyph background quads of this line.
919    ///
920    /// gpui's [`ShapedLine::paint`] does not draw backgrounds, so every line painted with
921    /// [`Self::paint`] needs this called first, with the same origin and align width.
922    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        // Painting a background walks every glyph and pushes a scene layer, so skip the
932        // whole pass for lines that have no background color to paint.
933        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        // Paint whitespace indicators
970        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                // +1 \n
1027                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        // Add a new text to end
1046        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        // Replace first line `Hello` to `AAA`
1068        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        // Remove the second line
1089        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        // Replace the first 2 lines to "This is a new line."
1110        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        // Add a new line at the end
1130        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        // Add a new line at the beginning
1151        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        // Remove all to at least one line in `lines`.
1173        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        // Test update_all
1182        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    /// The longest-row summary stays exact when the previously-longest line is shrunk.
1204    #[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        // Shrink line 1 so line 2-equivalent isn't longest.
1212        // Make line 0 the longest now.
1213        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    /// Editing the last line and deleting everything must keep the tree consistent.
1223    #[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        // Replace the last line only.
1231        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        // Delete everything.
1240        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    /// A layout context whose only load-bearing field is the line height.
1344    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        // One buffer line wrapped into two visual rows, splitting at byte 10.
1381        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        // Clicking past the last glyph of the first row resolves to the wrap boundary, which is
1389        // also the first offset of the second row -- hence the affinity.
1390        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        // Carrying that affinity is what keeps the caret on the row that was clicked; dropping it
1397        // is the bug -- the caret shows up one row below the pointer.
1398        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        // The final row owns the end of the line outright, so there is nothing to disambiguate.
1412        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        // Offset 20 is the wrap boundary of the second buffer line: 11 (line start) + 9.
1440        // It is the last offset of wrap row 1 and the first of wrap row 2 at the same time, so
1441        // only the affinity can say which row a vertical move should step away from.
1442        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        // An offset that is not on a boundary is unaffected either way.
1456        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                // range: 0..15
1481                LineItem {
1482                    len: Rope::from("Hello, 世界!\r").len(),
1483                    indent: 0,
1484                    wrapped_lines: smallvec::smallvec![0..15],
1485                },
1486                // range: 16..36
1487                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                // range: 37..56
1493                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                // range: 57..79
1499                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}