Skip to main content

gpui_base/input/editor/display_map/
text_wrapper.rs

1use gpui::Half;
2use std::borrow::Cow;
3use std::ops::Range;
4
5use gpui::{
6    App, Font, LineFragment, Pixels, Point, ShapedLine, Size, TextAlign, Window, point, px, size,
7};
8use ropey::Rope;
9use smallvec::SmallVec;
10use sum_tree::{Bias, Dimensions, SumTree};
11
12use crate::input::{
13    Point as TreeSitterPoint, RopeExt,
14    layout::{LastLayout, WhitespaceIndicators},
15};
16
17/// Controls how soft-wrapped continuation lines are indented.
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum WrappingIndent {
20    /// Continuation lines start flush-left at the full editor width.
21    None,
22    /// Continuation lines keep the same indentation as the first line.
23    #[default]
24    Same,
25}
26
27/// A line with soft wrapped lines info.
28#[derive(Debug, Clone)]
29pub(crate) struct LineItem {
30    /// The byte length of the line, without the end `\n`.
31    len: usize,
32    /// Number of leading characters of the line reserved as indentation for continuation wrapped
33    /// lines, when [`WrappingIndent::Same`] is used.
34    ///
35    /// Zero when [`WrappingIndent::None`] is used or the line is not wrapped.
36    pub(crate) indent: u32,
37    /// The soft wrapped lines relative byte range (0..len) of this line (Include first line).
38    ///
39    /// Not contains the line end `\n`.
40    pub(crate) wrapped_lines: SmallVec<[Range<usize>; 1]>,
41}
42
43impl LineItem {
44    /// Get the bytes length of this line.
45    #[inline]
46    pub(crate) fn len(&self) -> usize {
47        self.len
48    }
49
50    /// Get number of soft wrapped lines of this line (include the first line).
51    #[inline]
52    pub(crate) fn lines_len(&self) -> usize {
53        self.wrapped_lines.len()
54    }
55}
56
57/// Summary of a subtree of [`LineItem`]s, maintained incrementally by the [`SumTree`].
58#[derive(Debug, Clone)]
59pub(crate) struct LineSummary {
60    /// Number of buffer lines.
61    buffer_rows: usize,
62    /// Number of wrap rows (sum of each line's `lines_len()`).
63    wrap_rows: usize,
64    /// Sum of byte lengths of the buffer lines (without the trailing `\n`).
65    bytes: usize,
66    /// Byte length of the longest line in this subtree.
67    max_line_len: usize,
68    /// Buffer row (relative to this subtree) of the first line achieving `max_line_len`.
69    longest_row: usize,
70}
71
72impl sum_tree::Summary for LineSummary {
73    type Context<'a> = &'a ();
74
75    fn zero(_: &()) -> Self {
76        LineSummary {
77            buffer_rows: 0,
78            wrap_rows: 0,
79            bytes: 0,
80            max_line_len: 0,
81            longest_row: 0,
82        }
83    }
84
85    fn add_summary(&mut self, other: &Self, _: &()) {
86        // Keep the leftmost row that achieves a strictly greater length
87        if other.max_line_len > self.max_line_len {
88            self.longest_row = self.buffer_rows + other.longest_row;
89            self.max_line_len = other.max_line_len;
90        }
91        self.buffer_rows += other.buffer_rows;
92        self.wrap_rows += other.wrap_rows;
93        self.bytes += other.bytes;
94    }
95}
96
97impl sum_tree::Item for LineItem {
98    type Summary = LineSummary;
99
100    fn summary(&self, _: &()) -> LineSummary {
101        LineSummary {
102            buffer_rows: 1,
103            wrap_rows: self.lines_len(),
104            bytes: self.len(),
105            max_line_len: self.len(),
106            longest_row: 0,
107        }
108    }
109}
110
111/// Cursor dimension counting buffer rows.
112#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
113pub(crate) struct BufferRows(pub usize);
114
115impl<'a> sum_tree::Dimension<'a, LineSummary> for BufferRows {
116    fn zero(_: &()) -> Self {
117        BufferRows(0)
118    }
119
120    fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
121        self.0 += summary.buffer_rows;
122    }
123}
124
125/// Cursor dimension counting wrap rows.
126#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
127pub(crate) struct WrapRows(pub usize);
128
129impl<'a> sum_tree::Dimension<'a, LineSummary> for WrapRows {
130    fn zero(_: &()) -> Self {
131        WrapRows(0)
132    }
133
134    fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
135        self.0 += summary.wrap_rows;
136    }
137}
138
139/// Used to prepare the text with soft wrap to be get lines to displayed in the Editor.
140///
141/// After use lines to calculate the scroll size of the Editor.
142pub(crate) struct TextWrapper {
143    text: Rope,
144    font: Font,
145    font_size: Pixels,
146    /// If is none, it means the text is not wrapped
147    wrap_width: Option<Pixels>,
148    wrapping_indent: WrappingIndent,
149    /// The lines by split \n
150    pub(crate) lines: SumTree<LineItem>,
151
152    _initialized: bool,
153}
154
155#[allow(unused)]
156impl TextWrapper {
157    pub(crate) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
158        Self {
159            text: Rope::new(),
160            font,
161            font_size,
162            wrap_width,
163            wrapping_indent: WrappingIndent::default(),
164            lines: SumTree::new(&()),
165            _initialized: false,
166        }
167    }
168
169    #[inline]
170    pub(crate) fn set_default_text(&mut self, text: &Rope) {
171        self.text = text.clone();
172    }
173
174    /// Get reference to the rope text.
175    #[inline]
176    pub(crate) fn text(&self) -> &Rope {
177        &self.text
178    }
179
180    /// Get the total number of lines including wrapped lines.
181    #[inline]
182    pub(crate) fn len(&self) -> usize {
183        self.lines.summary().wrap_rows
184    }
185
186    /// Get the total number of buffer lines.
187    #[inline]
188    pub(crate) fn lines_count(&self) -> usize {
189        self.lines.summary().buffer_rows
190    }
191
192    /// Get the 0-based row index of the longest line (by byte length).
193    #[inline]
194    pub(crate) fn longest_row(&self) -> usize {
195        self.lines.summary().longest_row
196    }
197
198    /// Get the line item by buffer row index.
199    #[inline]
200    pub(crate) fn line(&self, row: usize) -> Option<&LineItem> {
201        let mut cursor = self.lines.cursor::<BufferRows>(&());
202        cursor.seek(&BufferRows(row), Bias::Right);
203        cursor.item()
204    }
205
206    /// Iterate buffer lines in order.
207    #[inline]
208    pub(crate) fn iter_lines(&self) -> impl Iterator<Item = &LineItem> {
209        self.lines.iter()
210    }
211
212    /// First wrap row of buffer line `row`. Returns the total wrap row count if `row` is
213    /// out of range.
214    pub(crate) fn buffer_line_to_first_wrap_row(&self, row: usize) -> usize {
215        let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
216        cursor.seek(&BufferRows(row), Bias::Right);
217        cursor.start().1.0
218    }
219
220    /// Wrap row range of buffer line `row`.
221    pub(crate) fn buffer_line_to_wrap_row_range(&self, row: usize) -> Range<usize> {
222        let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
223        cursor.seek(&BufferRows(row), Bias::Right);
224        let start = cursor.start().1.0;
225        let len = cursor.item().map(|l| l.lines_len()).unwrap_or(0);
226        start..start + len
227    }
228
229    /// Buffer line containing wrap row `wrap_row`, clamped to the last line.
230    pub(crate) fn wrap_row_to_buffer_line(&self, wrap_row: usize) -> usize {
231        let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
232        cursor.seek(&WrapRows(wrap_row), Bias::Right);
233        match cursor.item() {
234            Some(_) => cursor.start().1.0,
235            None => self.lines_count().saturating_sub(1),
236        }
237    }
238
239    pub(crate) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
240        if wrap_width == self.wrap_width {
241            return;
242        }
243
244        self.wrap_width = wrap_width;
245        self.update_all(&self.text.clone(), cx);
246    }
247
248    pub(crate) fn set_wrapping_indent(&mut self, wrapping_indent: WrappingIndent, cx: &mut App) {
249        if wrapping_indent == self.wrapping_indent {
250            return;
251        }
252
253        self.wrapping_indent = wrapping_indent;
254        self.update_all(&self.text.clone(), cx);
255    }
256
257    pub(crate) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
258        if self.font.eq(&font) && self.font_size == font_size {
259            return;
260        }
261
262        self.font = font;
263        self.font_size = font_size;
264        self.update_all(&self.text.clone(), cx);
265    }
266
267    pub(crate) fn prepare_if_need(&mut self, text: &Rope, cx: &mut App) -> bool {
268        if self._initialized {
269            return false;
270        }
271        self._initialized = true;
272        self.update_all(text, cx);
273        true
274    }
275
276    /// Update the text wrapper and recalculate the wrapped lines.
277    ///
278    /// If the `text` is the same as the current text, do nothing.
279    ///
280    /// - `changed_text`: The text [`Rope`] that has changed.
281    /// - `range`: The `selected_range` before change.
282    /// - `new_text`: The inserted text.
283    /// - `force`: Whether to force the update, if false, the update will be skipped if the text is the same.
284    /// - `cx`: The application context.
285    pub(crate) fn update(
286        &mut self,
287        changed_text: &Rope,
288        range: &Range<usize>,
289        new_text: &Rope,
290        cx: &mut App,
291    ) {
292        let mut line_wrapper = cx
293            .text_system()
294            .line_wrapper(self.font.clone(), self.font_size);
295        self._update(
296            changed_text,
297            range,
298            new_text,
299            &mut |line_str, wrap_width| {
300                line_wrapper
301                    .wrap_line(&[LineFragment::text(line_str)], wrap_width)
302                    .collect()
303            },
304        );
305    }
306
307    fn _update<F>(
308        &mut self,
309        changed_text: &Rope,
310        range: &Range<usize>,
311        new_text: &Rope,
312        wrap_line: &mut F,
313    ) where
314        F: FnMut(&str, Pixels) -> Vec<gpui::Boundary>,
315    {
316        // Remove the old changed lines.
317        let buffer_line_count = self.lines_count();
318        let start_row = self.text.offset_to_point(range.start).row;
319        let start_row = start_row.min(buffer_line_count.saturating_sub(1));
320        let end_row = self.text.offset_to_point(range.end).row;
321        let end_row = end_row.min(buffer_line_count.saturating_sub(1));
322
323        // To add the new lines.
324        let new_start_row = changed_text.offset_to_point(range.start).row;
325        let new_end_row = changed_text
326            .offset_to_point(range.start + new_text.len())
327            .row;
328
329        let mut new_lines = Vec::with_capacity(new_end_row.saturating_sub(new_start_row) + 1);
330        let wrap_width = self.wrap_width;
331
332        // line not contains `\n`.
333        for row in new_start_row..=new_end_row {
334            let line = changed_text.slice_line(row);
335            let mut wrapped_lines = SmallVec::<[Range<usize>; 1]>::new();
336            let mut prev_boundary_ix = 0;
337            let mut indent_chars = 0;
338
339            // If wrap_width is Pixels::MAX, skip wrapping to disable word wrap
340            if let Some(wrap_width) = wrap_width {
341                // Borrowed for lines within a single rope chunk.
342                let line_str: Cow<str> = line.into();
343                match self.wrapping_indent {
344                    WrappingIndent::Same => {
345                        // Here only have wrapped line, if there is no wrap meet, the `line_wraps`
346                        // result will empty.
347                        for boundary in wrap_line(&line_str, wrap_width) {
348                            wrapped_lines.push(prev_boundary_ix..boundary.ix);
349                            prev_boundary_ix = boundary.ix;
350                            indent_chars = boundary.next_indent;
351                        }
352                    }
353                    WrappingIndent::None => {
354                        // The first visual line keeps the line's leading indentation, so it is
355                        // wrapped as is.
356                        let boundaries = wrap_line(&line_str, wrap_width);
357                        if let Some(first_ix) = boundaries.first().map(|b| b.ix) {
358                            wrapped_lines.push(prev_boundary_ix..first_ix);
359                            prev_boundary_ix = first_ix;
360
361                            for boundary in wrap_line(&line_str[first_ix..], wrap_width) {
362                                let ix = first_ix + boundary.ix;
363                                wrapped_lines.push(prev_boundary_ix..ix);
364                                prev_boundary_ix = ix;
365                            }
366                        }
367                    }
368                }
369            }
370
371            // Reset of the line
372            if prev_boundary_ix < line.len() || prev_boundary_ix == 0 {
373                wrapped_lines.push(prev_boundary_ix..line.len());
374            }
375
376            new_lines.push(LineItem {
377                len: line.len(),
378                indent: indent_chars,
379                wrapped_lines,
380            });
381        }
382
383        if self.lines.is_empty() {
384            self.lines = SumTree::from_iter(new_lines, &());
385        } else {
386            let mut cursor = self.lines.cursor::<BufferRows>(&());
387            let mut new_tree = cursor.slice(&BufferRows(start_row), Bias::Right);
388            // Skip the replaced rows
389            cursor.seek_forward(&BufferRows(end_row + 1), Bias::Right);
390            new_tree.extend(new_lines, &());
391            // Untouched rows after the edit
392            new_tree.append(cursor.suffix(), &());
393            drop(cursor);
394            self.lines = new_tree;
395        }
396
397        self.text = changed_text.clone();
398    }
399
400    /// Update the text wrapper and recalculate the wrapped lines.
401    ///
402    /// If the `text` is the same as the current text, do nothing.
403    fn update_all(&mut self, text: &Rope, cx: &mut App) {
404        self.update(text, &(0..text.len()), &text, cx);
405    }
406
407    /// Return display point (with soft wrap) from the given byte offset in the text.
408    ///
409    /// Panics if the `offset` is out of bounds.
410    pub(crate) fn offset_to_display_point(&self, offset: usize) -> WrapDisplayPoint {
411        self.offset_to_display_point_with_affinity(offset, false)
412    }
413
414    /// Like [`Self::offset_to_display_point`], but honours the caret's line-end affinity.
415    ///
416    /// A soft wrap boundary is one offset shared by two visual rows. Without affinity it always
417    /// resolves to the start of the second row, which is wrong for a caret that is being drawn at
418    /// the end of the first one -- vertical movement would then step from the row below the one
419    /// the user can see.
420    pub(crate) fn offset_to_display_point_with_affinity(
421        &self,
422        offset: usize,
423        line_end_affinity: bool,
424    ) -> WrapDisplayPoint {
425        let row = self.text.offset_to_point(offset).row;
426        let start = self.text.line_start_offset(row);
427
428        // Seek to buffer row
429        let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
430        cursor.seek(&BufferRows(row), Bias::Right);
431        let wrapped_row = cursor.start().1.0;
432        let Some(line) = cursor.item() else {
433            return WrapDisplayPoint::new(wrapped_row, 0, 0);
434        };
435
436        let local_offset = offset.saturating_sub(start);
437        for (ix, range) in line.wrapped_lines.iter().enumerate() {
438            // With affinity the boundary offset closes the current row instead of opening the
439            // next one, so the range is matched inclusively.
440            let matches =
441                range.contains(&local_offset) || (line_end_affinity && local_offset == range.end);
442            if matches {
443                return WrapDisplayPoint::new(
444                    wrapped_row + ix,
445                    ix,
446                    local_offset.saturating_sub(range.start),
447                );
448            }
449        }
450
451        // Otherwise return the eof of the line.
452        let last_range = line.wrapped_lines.last().unwrap_or(&(0..0));
453        let ix = line.lines_len().saturating_sub(1);
454        return WrapDisplayPoint::new(wrapped_row + ix, ix, last_range.len());
455    }
456
457    /// Return byte offset in the text from the given display point (with soft wrap).
458    ///
459    /// Panics if the `point.row` is out of bounds.
460    pub(crate) fn display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {
461        // Seek to wrap row `point.row`
462        let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
463        cursor.seek(&WrapRows(point.row), Bias::Right);
464        let Some(line) = cursor.item() else {
465            return self.text.len();
466        };
467        let wrapped_row = cursor.start().0.0;
468        let row = cursor.start().1.0;
469
470        let line_start = self.text.line_start_offset(row);
471        let local_row = point.row.saturating_sub(wrapped_row);
472        if let Some(range) = line.wrapped_lines.get(local_row) {
473            line_start + (range.start + point.column).min(range.end)
474        } else {
475            // If not found, return the end of the line.
476            line_start + line.len()
477        }
478    }
479
480    pub(crate) fn display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint {
481        let offset = self.display_point_to_offset(point);
482        self.text.offset_to_point(offset)
483    }
484
485    pub(crate) fn point_to_display_point(&self, point: TreeSitterPoint) -> WrapDisplayPoint {
486        let offset = self.text.point_to_offset(point);
487        self.offset_to_display_point(offset)
488    }
489}
490
491/// A display point within the soft-wrapped text.
492///
493/// This represents a position in the text after soft-wrapping,
494/// with an additional `local_row` field tracking the wrap line
495/// within the original buffer line.
496#[derive(Debug, Clone, Copy, PartialEq, Eq)]
497pub(crate) struct WrapDisplayPoint {
498    /// The 0-based soft wrapped row index in the text.
499    pub row: usize,
500    /// The 0-based row index in local line (include first line).
501    ///
502    /// This value only valid when return from [`TextWrapper::offset_to_display_point`], otherwise it will be ignored.
503    pub local_row: usize,
504    /// The 0-based column byte index in the display line (with soft wrap).
505    pub column: usize,
506}
507
508impl WrapDisplayPoint {
509    pub(crate) fn new(row: usize, local_row: usize, column: usize) -> Self {
510        Self {
511            row,
512            local_row,
513            column,
514        }
515    }
516}
517
518/// The layout info of a line with soft wrapped lines.
519pub(crate) struct LineLayout {
520    /// Total bytes length of this line.
521    len: usize,
522    /// The soft wrapped lines of this line (Include the first line).
523    pub(crate) wrapped_lines: SmallVec<[ShapedLine; 1]>,
524    /// Extra left offset applied to continuation wrapped lines, used to reserve the first line's
525    /// indentation when [`WrappingIndent::Same`] is used.
526    pub(crate) wrap_indent: Pixels,
527    pub(crate) longest_width: Pixels,
528    pub(crate) whitespace_indicators: Option<WhitespaceIndicators>,
529    /// Whitespace indicators: (line_index, x_position, is_tab)
530    pub(crate) whitespace_chars: Vec<(usize, Pixels, bool)>,
531    /// Whether any run of this line carries a background color, so [`Self::paint_background`]
532    /// can skip the glyph walk for the common case of a line without highlights.
533    has_background: bool,
534}
535
536impl LineLayout {
537    pub(crate) fn new() -> Self {
538        Self {
539            len: 0,
540            longest_width: px(0.),
541            wrapped_lines: SmallVec::new(),
542            wrap_indent: px(0.),
543            whitespace_chars: Vec::new(),
544            whitespace_indicators: None,
545            has_background: false,
546        }
547    }
548
549    /// Record whether any run of this line carries a background color.
550    pub(crate) fn with_background(mut self, has_background: bool) -> Self {
551        self.has_background = has_background;
552        self
553    }
554
555    /// Set the left offset reserved for continuation wrapped lines.
556    pub(crate) fn wrap_indent(mut self, wrap_indent: Pixels) -> Self {
557        self.wrap_indent = wrap_indent;
558        self
559    }
560
561    /// The pixel indent applied to the given visual line, relative to the line's
562    /// leading text. Only continuation lines (index > 0) are indented.
563    #[inline]
564    fn line_indent(&self, line_index: usize) -> Pixels {
565        if line_index == 0 {
566            px(0.)
567        } else {
568            self.wrap_indent
569        }
570    }
571
572    pub(crate) fn lines(mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) -> Self {
573        self.set_wrapped_lines(wrapped_lines);
574        self
575    }
576
577    pub(crate) fn set_wrapped_lines(&mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) {
578        self.len = wrapped_lines.iter().map(|l| l.len).sum();
579        let width = wrapped_lines
580            .iter()
581            .map(|l| l.width)
582            .max()
583            .unwrap_or_default();
584        self.longest_width = width;
585        self.wrapped_lines = wrapped_lines;
586    }
587
588    pub(crate) fn with_whitespaces(mut self, indicators: Option<WhitespaceIndicators>) -> Self {
589        self.whitespace_indicators = indicators;
590        let Some(indicators) = self.whitespace_indicators.as_ref() else {
591            return self;
592        };
593
594        let space_indicator_offset = indicators.space.width.half();
595
596        for (line_index, wrapped_line) in self.wrapped_lines.iter().enumerate() {
597            for (relative_offset, c) in wrapped_line.text.char_indices() {
598                if matches!(c, ' ' | '\t') {
599                    let is_tab = c == '\t';
600                    let start_x = wrapped_line.x_for_index(relative_offset);
601                    let end_x = wrapped_line.x_for_index(relative_offset + c.len_utf8());
602                    // Center the indicator in the actual character's space
603                    let x_position = if c == ' ' {
604                        (start_x + end_x).half() - space_indicator_offset
605                    } else {
606                        start_x
607                    };
608
609                    self.whitespace_chars.push((line_index, x_position, is_tab));
610                }
611            }
612        }
613        self
614    }
615
616    #[inline]
617    pub(crate) fn len(&self) -> usize {
618        self.len
619    }
620
621    /// Get the position (x, y) for the given index in this line layout.
622    ///
623    /// - The `offset` is a local byte index in this line layout.
624    /// - When `line_end_affinity` is true, an offset at a soft wrap boundary is placed at
625    ///   the end of the current visual line rather than the start of the next one.
626    /// - The return value is relative to the top-left corner of this line layout, start from (0, 0)
627    pub(crate) fn position_for_index(
628        &self,
629        offset: usize,
630        last_layout: &LastLayout,
631        line_end_affinity: bool,
632    ) -> Option<Point<Pixels>> {
633        let mut acc_len = 0;
634        let mut offset_y = px(0.);
635
636        let x_offset = last_layout.alignment_offset(self.longest_width);
637
638        for (i, line) in self.wrapped_lines.iter().enumerate() {
639            let is_last = i + 1 == self.wrapped_lines.len();
640
641            let matches = if line.len == 0 {
642                // Empty visual lines still own their boundary offset.
643                offset == acc_len
644            } else if is_last || line_end_affinity {
645                // Inclusive: cursor can sit at end of this visual line.
646                offset >= acc_len && offset <= acc_len + line.len
647            } else {
648                // Exclusive: boundary offset belongs to the next visual line.
649                offset >= acc_len && offset < acc_len + line.len
650            };
651
652            if matches {
653                let x = line.x_for_index(offset.saturating_sub(acc_len))
654                    + x_offset
655                    + self.line_indent(i);
656                return Some(point(x, offset_y));
657            }
658
659            // Always advance by actual line length. The last line gets +1 so the
660            // cursor can be placed after the final character.
661            acc_len += if is_last { line.len + 1 } else { line.len };
662            offset_y += last_layout.line_height;
663        }
664
665        None
666    }
667
668    /// Get the closest index for the given x in this line layout.
669    ///
670    /// This ignores y, so it only makes sense for a layout that is known to occupy a single
671    /// visual line. Wrapped layouts must use [`Self::closest_index_for_position`], which also
672    /// reports the caret affinity that a wrap boundary needs.
673    pub(crate) fn closest_index_for_x(&self, x: Pixels, last_layout: &LastLayout) -> usize {
674        let mut acc_len = 0;
675        let x_offset = last_layout.alignment_offset(self.longest_width);
676        let x = x - x_offset;
677
678        for (i, line) in self.wrapped_lines.iter().enumerate() {
679            let line_indent = self.line_indent(i);
680            if x <= line_indent + line.width {
681                return acc_len + line.closest_index_for_x(x - line_indent);
682            }
683            acc_len += line.len;
684        }
685
686        acc_len
687    }
688
689    /// Resolve `pos` to the wrapped sub-line under it.
690    ///
691    /// Returns the sub-line index, the byte offset that sub-line starts at within this line
692    /// layout, and `pos.x` translated into that sub-line's own coordinate space.
693    fn wrapped_line_at(
694        &self,
695        pos: Point<Pixels>,
696        last_layout: &LastLayout,
697    ) -> Option<(usize, usize, Pixels)> {
698        let mut offset = 0;
699        let mut line_top = px(0.);
700        let x_offset = last_layout.alignment_offset(self.longest_width);
701
702        for (i, line) in self.wrapped_lines.iter().enumerate() {
703            let line_bottom = line_top + last_layout.line_height;
704            if pos.y >= line_top && pos.y < line_bottom {
705                return Some((i, offset, pos.x - x_offset - self.line_indent(i)));
706            }
707
708            offset += line.len;
709            line_top = line_bottom;
710        }
711
712        None
713    }
714
715    /// Get the index for the given position (x, y) in this line layout.
716    ///
717    /// The `pos` is relative to the top-left corner of this line layout, start from (0, 0).
718    ///
719    /// Returns a local byte index in this line layout (start from 0) together with the caret
720    /// affinity to use for it: `true` when the index landed on the wrap boundary of a non-final
721    /// sub-line. That boundary offset is shared by the end of one visual line and the start of
722    /// the next, so the affinity is what tells [`Self::position_for_index`] which of the two the
723    /// caret belongs to. Without it a click past the last glyph of a wrapped line would put a
724    /// visible caret on the following line.
725    pub(crate) fn closest_index_for_position(
726        &self,
727        pos: Point<Pixels>,
728        last_layout: &LastLayout,
729    ) -> Option<(usize, bool)> {
730        let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
731        let line = &self.wrapped_lines[i];
732        let ix = line.closest_index_for_x(x);
733        let line_end_affinity = i + 1 < self.wrapped_lines.len() && ix == line.len;
734
735        Some((offset + ix, line_end_affinity))
736    }
737
738    pub(crate) fn index_for_position(
739        &self,
740        pos: Point<Pixels>,
741        last_layout: &LastLayout,
742    ) -> Option<usize> {
743        let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
744
745        Some(offset + self.wrapped_lines[i].index_for_x(x)?)
746    }
747
748    pub(crate) fn size(&self, line_height: Pixels) -> Size<Pixels> {
749        let width = self
750            .wrapped_lines
751            .iter()
752            .enumerate()
753            .map(|(ix, line)| line.width + self.line_indent(ix))
754            .max()
755            .unwrap_or(self.longest_width);
756        size(width, self.wrapped_lines.len() * line_height)
757    }
758
759    /// Paint only the glyph background quads of this line.
760    ///
761    /// gpui's [`ShapedLine::paint`] does not draw backgrounds, so every line painted with
762    /// [`Self::paint`] needs this called first, with the same origin and align width.
763    pub(crate) fn paint_background(
764        &self,
765        pos: Point<Pixels>,
766        line_height: Pixels,
767        text_align: TextAlign,
768        align_width: Option<Pixels>,
769        window: &mut Window,
770        cx: &mut App,
771    ) {
772        // Painting a background walks every glyph and pushes a scene layer, so skip the
773        // whole pass for lines that have no background color to paint.
774        if !self.has_background {
775            return;
776        }
777
778        for (ix, line) in self.wrapped_lines.iter().enumerate() {
779            _ = line.paint_background(
780                pos + point(self.line_indent(ix), ix * line_height),
781                line_height,
782                text_align,
783                align_width,
784                window,
785                cx,
786            );
787        }
788    }
789
790    pub(crate) fn paint(
791        &self,
792        pos: Point<Pixels>,
793        line_height: Pixels,
794        text_align: TextAlign,
795        align_width: Option<Pixels>,
796        window: &mut Window,
797        cx: &mut App,
798    ) {
799        for (ix, line) in self.wrapped_lines.iter().enumerate() {
800            _ = line.paint(
801                pos + point(self.line_indent(ix), ix * line_height),
802                line_height,
803                text_align,
804                align_width,
805                window,
806                cx,
807            );
808        }
809
810        // Paint whitespace indicators
811        if let Some(indicators) = self.whitespace_indicators.as_ref() {
812            for (line_index, x_position, is_tab) in &self.whitespace_chars {
813                let invisible = if *is_tab {
814                    indicators.tab.clone()
815                } else {
816                    indicators.space.clone()
817                };
818
819                let origin = point(
820                    pos.x + *x_position + self.line_indent(*line_index),
821                    pos.y + *line_index as f32 * line_height,
822                );
823
824                _ = invisible.paint(origin, line_height, text_align, align_width, window, cx);
825            }
826        }
827    }
828}
829
830#[cfg(test)]
831mod tests {
832    use super::*;
833    use std::rc::Rc;
834
835    use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px};
836
837    #[test]
838    fn test_update() {
839        let font = gpui::Font {
840            family: "Arial".into(),
841            weight: FontWeight::default(),
842            style: FontStyle::Normal,
843            features: FontFeatures::default(),
844            fallbacks: None,
845        };
846
847        let mut wrapper = TextWrapper::new(font, px(14.), None);
848        let mut text = Rope::from(
849            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
850        );
851
852        fn fake_wrap_line(_line: &str, _wrap_width: Pixels) -> Vec<Boundary> {
853            vec![]
854        }
855
856        #[track_caller]
857        fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) {
858            let mut actual_lines = vec![];
859            let mut offset = 0;
860            for line in wrapper.iter_lines() {
861                actual_lines.push(
862                    line.wrapped_lines
863                        .iter()
864                        .map(|range| text.slice(offset + range.start..offset + range.end))
865                        .collect::<Vec<_>>(),
866                );
867                // +1 \n
868                offset += line.len() + 1;
869            }
870            assert_eq!(actual_lines, expected_lines);
871        }
872
873        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
874        assert_eq!(wrapper.lines_count(), 4);
875        assert_wrapper_lines(
876            &text,
877            &wrapper,
878            &[
879                &["Hello, 世界!\r"],
880                &["This is second line."],
881                &["This is third line."],
882                &["这里是第 4 行。"],
883            ],
884        );
885
886        // Add a new text to end
887        let range = text.len()..text.len();
888        let new_text = "New text";
889        text.replace(range.clone(), new_text);
890        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
891        assert_eq!(
892            text.to_string(),
893            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
894        );
895        assert_eq!(wrapper.lines_count(), 4);
896        assert_eq!(wrapper.lines_count(), 4);
897        assert_wrapper_lines(
898            &text,
899            &wrapper,
900            &[
901                &["Hello, 世界!\r"],
902                &["This is second line."],
903                &["This is third line."],
904                &["这里是第 4 行。New text"],
905            ],
906        );
907
908        // Replace first line `Hello` to `AAA`
909        let range = 0..5;
910        let new_text = "AAA";
911        text.replace(range.clone(), new_text);
912        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
913        assert_eq!(
914            text.to_string(),
915            "AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
916        );
917        assert_eq!(wrapper.lines_count(), 4);
918        assert_wrapper_lines(
919            &text,
920            &wrapper,
921            &[
922                &["AAA, 世界!\r"],
923                &["This is second line."],
924                &["This is third line."],
925                &["这里是第 4 行。New text"],
926            ],
927        );
928
929        // Remove the second line
930        let start_offset = text.line_start_offset(1);
931        let end_offset = text.line_end_offset(1);
932        let range = start_offset..end_offset + 1;
933        text.replace(range.clone(), "");
934        wrapper._update(&text, &range, &Rope::from(""), &mut fake_wrap_line);
935        assert_eq!(
936            text.to_string(),
937            "AAA, 世界!\r\nThis is third line.\n这里是第 4 行。New text"
938        );
939        assert_eq!(wrapper.lines_count(), 3);
940        assert_wrapper_lines(
941            &text,
942            &wrapper,
943            &[
944                &["AAA, 世界!\r"],
945                &["This is third line."],
946                &["这里是第 4 行。New text"],
947            ],
948        );
949
950        // Replace the first 2 lines to "This is a new line."
951        let range = text.line_start_offset(0)..text.line_end_offset(1) + 1;
952        let new_text = "This is a new line.\nThis is new line 2.\n";
953        text.replace(range.clone(), new_text);
954        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
955        assert_eq!(
956            text.to_string(),
957            "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text"
958        );
959        assert_eq!(wrapper.lines_count(), 3);
960        assert_wrapper_lines(
961            &text,
962            &wrapper,
963            &[
964                &["This is a new line."],
965                &["This is new line 2."],
966                &["这里是第 4 行。New text"],
967            ],
968        );
969
970        // Add a new line at the end
971        let range = text.len()..text.len();
972        let new_text = "\nThis is a new line at the end.";
973        text.replace(range.clone(), new_text);
974        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
975        assert_eq!(
976            text.to_string(),
977            "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end."
978        );
979        assert_eq!(wrapper.lines_count(), 4);
980        assert_wrapper_lines(
981            &text,
982            &wrapper,
983            &[
984                &["This is a new line."],
985                &["This is new line 2."],
986                &["这里是第 4 行。New text"],
987                &["This is a new line at the end."],
988            ],
989        );
990
991        // Add a new line at the beginning
992        let range = 0..0;
993        let new_text = "This is a new line at the beginning.\n";
994        text.replace(range.clone(), new_text);
995        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
996        assert_eq!(
997            text.to_string(),
998            "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."
999        );
1000        assert_eq!(wrapper.lines_count(), 5);
1001        assert_wrapper_lines(
1002            &text,
1003            &wrapper,
1004            &[
1005                &["This is a new line at the beginning."],
1006                &["This is a new line."],
1007                &["This is new line 2."],
1008                &["这里是第 4 行。New text"],
1009                &["This is a new line at the end."],
1010            ],
1011        );
1012
1013        // Remove all to at least one line in `lines`.
1014        let range = 0..text.len();
1015        let new_text = "";
1016        text.replace(range.clone(), new_text);
1017        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1018        assert_eq!(text.to_string(), "");
1019        assert_eq!(wrapper.lines_count(), 1);
1020        assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1021
1022        // Test update_all
1023        let range = 0..text.len();
1024        let new_text = "This is a full text.\nThis is a second line.";
1025        text.replace(range.clone(), new_text);
1026        wrapper._update(&text, &range, &text, &mut fake_wrap_line);
1027        assert_eq!(
1028            text.to_string(),
1029            "This is a full text.\nThis is a second line."
1030        );
1031        assert_eq!(wrapper.lines_count(), 2);
1032    }
1033
1034    fn test_font() -> gpui::Font {
1035        gpui::Font {
1036            family: "Arial".into(),
1037            weight: FontWeight::default(),
1038            style: FontStyle::Normal,
1039            features: FontFeatures::default(),
1040            fallbacks: None,
1041        }
1042    }
1043
1044    /// The longest-row summary stays exact when the previously-longest line is shrunk.
1045    #[test]
1046    fn test_longest_row_after_shrink() {
1047        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1048        let mut text = Rope::from("aa\nthis is the longest line\nbb");
1049        wrapper._update(&text, &(0..text.len()), &text, &mut |_, _| vec![]);
1050        assert_eq!(wrapper.longest_row(), 1);
1051
1052        // Shrink line 1 so line 2-equivalent isn't longest.
1053        // Make line 0 the longest now.
1054        let start = text.line_start_offset(0);
1055        let end = text.line_end_offset(0);
1056        let range = start..end;
1057        let new_text = "a very very long first line now";
1058        text.replace(range.clone(), new_text);
1059        wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _| vec![]);
1060        assert_eq!(wrapper.longest_row(), 0);
1061    }
1062
1063    /// Editing the last line and deleting everything must keep the tree consistent.
1064    #[test]
1065    fn test_edit_last_line_and_full_delete() {
1066        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1067        let mut text = Rope::from("one\ntwo\nthree");
1068        wrapper._update(&text, &(0..text.len()), &text, &mut |_, _| vec![]);
1069        assert_eq!(wrapper.lines_count(), 3);
1070
1071        // Replace the last line only.
1072        let start = text.line_start_offset(2);
1073        let range = start..text.len();
1074        let new_text = "THREE EDITED";
1075        text.replace(range.clone(), new_text);
1076        wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _| vec![]);
1077        assert_eq!(wrapper.lines_count(), 3);
1078        assert_eq!(wrapper.line(2).unwrap().len(), "THREE EDITED".len());
1079
1080        // Delete everything.
1081        let range = 0..text.len();
1082        text.replace(range.clone(), "");
1083        wrapper._update(&text, &range, &Rope::from(""), &mut |_, _| vec![]);
1084        assert_eq!(wrapper.lines_count(), 1);
1085        assert_eq!(wrapper.len(), 1);
1086        assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1087    }
1088
1089    #[test]
1090    fn test_wrap_row_buffer_line_boundaries() {
1091        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1092        wrapper.text = Rope::from("aa\nbbbb\nc");
1093        wrapper.lines = SumTree::from_iter(
1094            vec![
1095                LineItem {
1096                    len: 2,
1097                    indent: 0,
1098                    wrapped_lines: smallvec::smallvec![0..2],
1099                },
1100                LineItem {
1101                    len: 4,
1102                    indent: 0,
1103                    wrapped_lines: smallvec::smallvec![0..2, 2..4],
1104                },
1105                LineItem {
1106                    len: 1,
1107                    indent: 0,
1108                    wrapped_lines: smallvec::smallvec![0..1],
1109                },
1110            ],
1111            &(),
1112        );
1113
1114        assert_eq!(wrapper.lines_count(), 3);
1115        assert_eq!(wrapper.len(), 4);
1116
1117        assert_eq!(wrapper.buffer_line_to_first_wrap_row(0), 0);
1118        assert_eq!(wrapper.buffer_line_to_first_wrap_row(1), 1);
1119        assert_eq!(wrapper.buffer_line_to_first_wrap_row(2), 3);
1120        assert_eq!(wrapper.buffer_line_to_first_wrap_row(3), 4);
1121
1122        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1123        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1124        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1125        assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..4);
1126
1127        assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1128        assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1129        assert_eq!(wrapper.wrap_row_to_buffer_line(2), 1);
1130        assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1131        assert_eq!(wrapper.wrap_row_to_buffer_line(4), 2);
1132    }
1133
1134    #[test]
1135    fn test_wrap_row_queries_after_incremental_splice() {
1136        let mut wrapper = TextWrapper::new(test_font(), px(14.), Some(px(10.)));
1137        let mut text = Rope::from("aa\nbbbb\nc");
1138        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels| {
1139            if line.len() > 2 {
1140                vec![Boundary {
1141                    ix: 2,
1142                    next_indent: 0,
1143                }]
1144            } else {
1145                vec![]
1146            }
1147        };
1148
1149        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1150        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1151        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1152        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1153
1154        let range = text.line_start_offset(1)..text.line_end_offset(1);
1155        let new_text = "dd\neeee";
1156        text.replace(range.clone(), new_text);
1157        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1158
1159        assert_eq!(wrapper.lines_count(), 4);
1160        assert_eq!(wrapper.len(), 5);
1161        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1162        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..2);
1163        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 2..4);
1164        assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..5);
1165        assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1166        assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1167        assert_eq!(wrapper.wrap_row_to_buffer_line(2), 2);
1168        assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1169        assert_eq!(wrapper.wrap_row_to_buffer_line(4), 3);
1170    }
1171
1172    #[test]
1173    fn test_line_layout() {
1174        let mut line_layout = LineLayout::new();
1175
1176        let line1 = ShapedLine::default().with_len(100);
1177        let line2 = ShapedLine::default().with_len(50);
1178        let wrapped_lines = smallvec::smallvec![line1, line2];
1179        line_layout.set_wrapped_lines(wrapped_lines);
1180        assert_eq!(line_layout.len(), 150);
1181        assert_eq!(line_layout.wrapped_lines.len(), 2);
1182    }
1183
1184    /// A layout context whose only load-bearing field is the line height.
1185    fn test_last_layout(line_height: Pixels) -> LastLayout {
1186        LastLayout {
1187            visible_range: 0..1,
1188            visible_buffer_lines: vec![0],
1189            visible_line_byte_offsets: vec![0],
1190            visible_top: px(0.),
1191            visible_range_offset: 0..0,
1192            lines: Rc::new(vec![]),
1193            line_height,
1194            wrap_width: None,
1195            wrapping_indent: WrappingIndent::default(),
1196            line_number_width: px(0.),
1197            cursor_bounds: None,
1198            text_align: TextAlign::Left,
1199            content_width: px(0.),
1200        }
1201    }
1202
1203    #[test]
1204    fn test_position_for_index_prefers_first_leading_empty_visual_line() {
1205        let mut line_layout = LineLayout::new();
1206        line_layout.set_wrapped_lines(smallvec::smallvec![
1207            ShapedLine::default(),
1208            ShapedLine::default(),
1209            ShapedLine::default().with_len(3),
1210        ]);
1211
1212        assert_eq!(
1213            line_layout.position_for_index(0, &test_last_layout(px(20.)), false),
1214            Some(point(px(0.), px(0.)))
1215        );
1216    }
1217
1218    #[test]
1219    fn clicking_past_a_wrapped_row_keeps_the_caret_on_that_row() {
1220        // One buffer line wrapped into two visual rows, splitting at byte 10.
1221        let mut line_layout = LineLayout::new();
1222        line_layout.set_wrapped_lines(smallvec::smallvec![
1223            ShapedLine::default().with_len(10),
1224            ShapedLine::default().with_len(5),
1225        ]);
1226        let last_layout = test_last_layout(px(20.));
1227
1228        // Clicking past the last glyph of the first row resolves to the wrap boundary, which is
1229        // also the first offset of the second row -- hence the affinity.
1230        let (ix, line_end_affinity) = line_layout
1231            .closest_index_for_position(point(px(999.), px(5.)), &last_layout)
1232            .unwrap();
1233        assert_eq!(ix, 10);
1234        assert!(line_end_affinity);
1235
1236        // Carrying that affinity is what keeps the caret on the row that was clicked; dropping it
1237        // is the bug -- the caret shows up one row below the pointer.
1238        assert_eq!(
1239            line_layout
1240                .position_for_index(ix, &last_layout, line_end_affinity)
1241                .map(|pos| pos.y),
1242            Some(px(0.))
1243        );
1244        assert_eq!(
1245            line_layout
1246                .position_for_index(ix, &last_layout, false)
1247                .map(|pos| pos.y),
1248            Some(px(20.))
1249        );
1250
1251        // The final row owns the end of the line outright, so there is nothing to disambiguate.
1252        let (ix, line_end_affinity) = line_layout
1253            .closest_index_for_position(point(px(999.), px(25.)), &last_layout)
1254            .unwrap();
1255        assert_eq!(ix, 15);
1256        assert!(!line_end_affinity);
1257    }
1258
1259    #[test]
1260    fn a_wrap_boundary_offset_resolves_to_the_row_the_caret_is_drawn_on() {
1261        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1262        wrapper.text = Rope::from("first line\nthis one wraps");
1263        wrapper.lines = SumTree::from_iter(
1264            vec![
1265                LineItem {
1266                    len: Rope::from("first line").len(),
1267                    indent: 0,
1268                    wrapped_lines: smallvec::smallvec![0..10],
1269                },
1270                LineItem {
1271                    len: Rope::from("this one wraps").len(),
1272                    indent: 0,
1273                    wrapped_lines: smallvec::smallvec![0..9, 9..14],
1274                },
1275            ],
1276            &(),
1277        );
1278
1279        // Offset 20 is the wrap boundary of the second buffer line: 11 (line start) + 9.
1280        // It is the last offset of wrap row 1 and the first of wrap row 2 at the same time, so
1281        // only the affinity can say which row a vertical move should step away from.
1282        assert_eq!(
1283            wrapper.offset_to_display_point_with_affinity(20, true),
1284            WrapDisplayPoint::new(1, 0, 9)
1285        );
1286        assert_eq!(
1287            wrapper.offset_to_display_point_with_affinity(20, false),
1288            WrapDisplayPoint::new(2, 1, 0)
1289        );
1290        assert_eq!(
1291            wrapper.offset_to_display_point(20),
1292            wrapper.offset_to_display_point_with_affinity(20, false)
1293        );
1294
1295        // An offset that is not on a boundary is unaffected either way.
1296        for line_end_affinity in [false, true] {
1297            assert_eq!(
1298                wrapper.offset_to_display_point_with_affinity(23, line_end_affinity),
1299                WrapDisplayPoint::new(2, 1, 3)
1300            );
1301        }
1302    }
1303
1304    #[test]
1305    fn test_offset_to_display_point() {
1306        let font = gpui::Font {
1307            family: "Arial".into(),
1308            weight: FontWeight::default(),
1309            style: FontStyle::Normal,
1310            features: FontFeatures::default(),
1311            fallbacks: None,
1312        };
1313
1314        let mut wrapper = TextWrapper::new(font, px(14.), None);
1315        wrapper.text = Rope::from(
1316            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1317        );
1318        wrapper.lines = SumTree::from_iter(
1319            vec![
1320                // range: 0..15
1321                LineItem {
1322                    len: Rope::from("Hello, 世界!\r").len(),
1323                    indent: 0,
1324                    wrapped_lines: smallvec::smallvec![0..15],
1325                },
1326                // range: 16..36
1327                LineItem {
1328                    len: Rope::from("This is second line.\n").len(),
1329                    indent: 0,
1330                    wrapped_lines: smallvec::smallvec![0..10, 10..20],
1331                },
1332                // range: 37..56
1333                LineItem {
1334                    len: Rope::from("This is third line.\n").len(),
1335                    indent: 0,
1336                    wrapped_lines: smallvec::smallvec![0..9, 9..15, 15..20],
1337                },
1338                // range: 57..79
1339                LineItem {
1340                    len: Rope::from("这里是第 4 行。").len(),
1341                    indent: 0,
1342                    wrapped_lines: smallvec::smallvec![0..22],
1343                },
1344            ],
1345            &(),
1346        );
1347
1348        assert_eq!(
1349            wrapper.offset_to_display_point(12),
1350            WrapDisplayPoint::new(0, 0, 12)
1351        );
1352        assert_eq!(
1353            wrapper.offset_to_display_point(15),
1354            WrapDisplayPoint::new(0, 0, 15)
1355        );
1356
1357        assert_eq!(
1358            wrapper.offset_to_display_point(16),
1359            WrapDisplayPoint::new(1, 0, 0)
1360        );
1361        assert_eq!(
1362            wrapper.offset_to_display_point(21),
1363            WrapDisplayPoint::new(1, 0, 5)
1364        );
1365        assert_eq!(
1366            wrapper.offset_to_display_point(27),
1367            WrapDisplayPoint::new(2, 1, 1)
1368        );
1369        assert_eq!(
1370            wrapper.offset_to_display_point(37),
1371            WrapDisplayPoint::new(3, 0, 0)
1372        );
1373        assert_eq!(
1374            wrapper.offset_to_display_point(54),
1375            WrapDisplayPoint::new(5, 2, 2)
1376        );
1377        assert_eq!(
1378            wrapper.offset_to_display_point(59),
1379            WrapDisplayPoint::new(6, 0, 2)
1380        );
1381
1382        assert_eq!(
1383            wrapper.display_point_to_offset(WrapDisplayPoint::new(6, 0, 2)),
1384            59
1385        );
1386        assert_eq!(
1387            wrapper.display_point_to_offset(WrapDisplayPoint::new(5, 2, 2)),
1388            54
1389        );
1390        assert_eq!(
1391            wrapper.display_point_to_offset(WrapDisplayPoint::new(3, 0, 0)),
1392            37
1393        );
1394        assert_eq!(
1395            wrapper.display_point_to_offset(WrapDisplayPoint::new(2, 1, 1)),
1396            27
1397        );
1398        assert_eq!(
1399            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 5)),
1400            21
1401        );
1402        assert_eq!(
1403            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 0)),
1404            16
1405        );
1406        assert_eq!(
1407            wrapper.display_point_to_offset(WrapDisplayPoint::new(0, 0, 15)),
1408            15
1409        );
1410    }
1411
1412    #[test]
1413    fn test_wrapping_indent_same_keeps_indent_reserved() {
1414        let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1415        wrapper.wrapping_indent = WrappingIndent::Same;
1416        let text = Rope::from("  abcdefghijklmnopqrstuv");
1417        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels| {
1418            if line.starts_with(' ') {
1419                vec![Boundary {
1420                    ix: 5,
1421                    next_indent: 2,
1422                }]
1423            } else {
1424                let mut boundaries = vec![];
1425                let mut i = 8;
1426                while i < line.len() {
1427                    boundaries.push(Boundary {
1428                        ix: i,
1429                        next_indent: 0,
1430                    });
1431                    i += 8;
1432                }
1433                boundaries
1434            }
1435        };
1436
1437        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1438
1439        let line = wrapper.line(0).unwrap();
1440        assert_eq!(line.indent, 2);
1441        assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..24]);
1442    }
1443
1444    #[test]
1445    fn test_wrapping_indent_none_continuation_lines_wrapped_at_full_width() {
1446        let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1447        wrapper.wrapping_indent = WrappingIndent::None;
1448        let text = Rope::from("  abcdefghijklmnopqrstuv");
1449        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels| {
1450            if line.starts_with(' ') {
1451                vec![Boundary {
1452                    ix: 5,
1453                    next_indent: 2,
1454                }]
1455            } else {
1456                let mut boundaries = vec![];
1457                let mut i = 8;
1458                while i < line.len() {
1459                    boundaries.push(Boundary {
1460                        ix: i,
1461                        next_indent: 0,
1462                    });
1463                    i += 8;
1464                }
1465                boundaries
1466            }
1467        };
1468
1469        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1470
1471        let line = wrapper.line(0).unwrap();
1472        assert_eq!(line.indent, 0);
1473        assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..13, 13..21, 21..24]);
1474    }
1475
1476    #[test]
1477    fn test_wrap_indent_offsets_continuation_lines() {
1478        let mut line_layout = LineLayout::new();
1479        line_layout.set_wrapped_lines(smallvec::smallvec![
1480            ShapedLine::default().with_len(5),
1481            ShapedLine::default().with_len(10),
1482        ]);
1483
1484        line_layout = line_layout.wrap_indent(px(20.0));
1485
1486        let last_layout = LastLayout {
1487            visible_range: 0..1,
1488            visible_buffer_lines: vec![0],
1489            visible_line_byte_offsets: vec![0],
1490            visible_top: px(0.),
1491            visible_range_offset: 0..0,
1492            lines: Rc::new(vec![]),
1493            line_height: px(20.0),
1494            wrap_width: Some(px(10.)),
1495            wrapping_indent: WrappingIndent::Same,
1496            line_number_width: px(0.),
1497            cursor_bounds: None,
1498            text_align: TextAlign::Left,
1499            content_width: px(0.),
1500        };
1501
1502        assert_eq!(
1503            line_layout.position_for_index(0, &last_layout, false),
1504            Some(point(px(0.), px(0.))),
1505        );
1506
1507        assert_eq!(
1508            line_layout.position_for_index(6, &last_layout, false),
1509            Some(point(px(20.), px(20.))),
1510        )
1511    }
1512}