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    /// How many columns the given position sits past the end of the line under it.
739    ///
740    /// Past the end of a line there is no glyph to hit-test against, so a position out
741    /// there resolves to the line end and loses how far right it really was. The extra
742    /// distance is reported here in whole spaces, letting a columnar selection keep its
743    /// width over a short row. Only the final visual line of a wrapped layout has that
744    /// trailing space; a continuation line ends at a wrap boundary, where the next glyph
745    /// merely lives on the following row.
746    ///
747    /// The `pos` is relative to the top-left corner of this line layout, start from (0, 0).
748    pub(crate) fn columns_past_line_end(
749        &self,
750        pos: Point<Pixels>,
751        last_layout: &LastLayout,
752    ) -> usize {
753        let Some((i, _, x)) = self.wrapped_line_at(pos, last_layout) else {
754            return 0;
755        };
756
757        if i + 1 < self.wrapped_lines.len() || last_layout.space_width <= px(0.) {
758            return 0;
759        }
760
761        let past_end = x - self.wrapped_lines[i].width;
762        if past_end <= px(0.) {
763            return 0;
764        }
765
766        (past_end / last_layout.space_width).round() as usize
767    }
768
769    pub(crate) fn index_for_position(
770        &self,
771        pos: Point<Pixels>,
772        last_layout: &LastLayout,
773    ) -> Option<usize> {
774        let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
775
776        Some(offset + self.wrapped_lines[i].index_for_x(x)?)
777    }
778
779    pub(crate) fn size(&self, line_height: Pixels) -> Size<Pixels> {
780        let width = self
781            .wrapped_lines
782            .iter()
783            .enumerate()
784            .map(|(ix, line)| line.width + self.line_indent(ix))
785            .max()
786            .unwrap_or(self.longest_width);
787        size(width, self.wrapped_lines.len() * line_height)
788    }
789
790    /// Paint only the glyph background quads of this line.
791    ///
792    /// gpui's [`ShapedLine::paint`] does not draw backgrounds, so every line painted with
793    /// [`Self::paint`] needs this called first, with the same origin and align width.
794    pub(crate) fn paint_background(
795        &self,
796        pos: Point<Pixels>,
797        line_height: Pixels,
798        text_align: TextAlign,
799        align_width: Option<Pixels>,
800        window: &mut Window,
801        cx: &mut App,
802    ) {
803        // Painting a background walks every glyph and pushes a scene layer, so skip the
804        // whole pass for lines that have no background color to paint.
805        if !self.has_background {
806            return;
807        }
808
809        for (ix, line) in self.wrapped_lines.iter().enumerate() {
810            _ = line.paint_background(
811                pos + point(self.line_indent(ix), ix * line_height),
812                line_height,
813                text_align,
814                align_width,
815                window,
816                cx,
817            );
818        }
819    }
820
821    pub(crate) fn paint(
822        &self,
823        pos: Point<Pixels>,
824        line_height: Pixels,
825        text_align: TextAlign,
826        align_width: Option<Pixels>,
827        window: &mut Window,
828        cx: &mut App,
829    ) {
830        for (ix, line) in self.wrapped_lines.iter().enumerate() {
831            _ = line.paint(
832                pos + point(self.line_indent(ix), ix * line_height),
833                line_height,
834                text_align,
835                align_width,
836                window,
837                cx,
838            );
839        }
840
841        // Paint whitespace indicators
842        if let Some(indicators) = self.whitespace_indicators.as_ref() {
843            for (line_index, x_position, is_tab) in &self.whitespace_chars {
844                let invisible = if *is_tab {
845                    indicators.tab.clone()
846                } else {
847                    indicators.space.clone()
848                };
849
850                let origin = point(
851                    pos.x + *x_position + self.line_indent(*line_index),
852                    pos.y + *line_index as f32 * line_height,
853                );
854
855                _ = invisible.paint(origin, line_height, text_align, align_width, window, cx);
856            }
857        }
858    }
859}
860
861#[cfg(test)]
862mod tests {
863    use super::*;
864    use std::rc::Rc;
865
866    use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px};
867
868    #[test]
869    fn test_update() {
870        let font = gpui::Font {
871            family: "Arial".into(),
872            weight: FontWeight::default(),
873            style: FontStyle::Normal,
874            features: FontFeatures::default(),
875            fallbacks: None,
876        };
877
878        let mut wrapper = TextWrapper::new(font, px(14.), None);
879        let mut text = Rope::from(
880            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
881        );
882
883        fn fake_wrap_line(_line: &str, _wrap_width: Pixels) -> Vec<Boundary> {
884            vec![]
885        }
886
887        #[track_caller]
888        fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) {
889            let mut actual_lines = vec![];
890            let mut offset = 0;
891            for line in wrapper.iter_lines() {
892                actual_lines.push(
893                    line.wrapped_lines
894                        .iter()
895                        .map(|range| text.slice(offset + range.start..offset + range.end))
896                        .collect::<Vec<_>>(),
897                );
898                // +1 \n
899                offset += line.len() + 1;
900            }
901            assert_eq!(actual_lines, expected_lines);
902        }
903
904        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
905        assert_eq!(wrapper.lines_count(), 4);
906        assert_wrapper_lines(
907            &text,
908            &wrapper,
909            &[
910                &["Hello, 世界!\r"],
911                &["This is second line."],
912                &["This is third line."],
913                &["这里是第 4 行。"],
914            ],
915        );
916
917        // Add a new text to end
918        let range = text.len()..text.len();
919        let new_text = "New text";
920        text.replace(range.clone(), new_text);
921        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
922        assert_eq!(
923            text.to_string(),
924            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
925        );
926        assert_eq!(wrapper.lines_count(), 4);
927        assert_eq!(wrapper.lines_count(), 4);
928        assert_wrapper_lines(
929            &text,
930            &wrapper,
931            &[
932                &["Hello, 世界!\r"],
933                &["This is second line."],
934                &["This is third line."],
935                &["这里是第 4 行。New text"],
936            ],
937        );
938
939        // Replace first line `Hello` to `AAA`
940        let range = 0..5;
941        let new_text = "AAA";
942        text.replace(range.clone(), new_text);
943        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
944        assert_eq!(
945            text.to_string(),
946            "AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
947        );
948        assert_eq!(wrapper.lines_count(), 4);
949        assert_wrapper_lines(
950            &text,
951            &wrapper,
952            &[
953                &["AAA, 世界!\r"],
954                &["This is second line."],
955                &["This is third line."],
956                &["这里是第 4 行。New text"],
957            ],
958        );
959
960        // Remove the second line
961        let start_offset = text.line_start_offset(1);
962        let end_offset = text.line_end_offset(1);
963        let range = start_offset..end_offset + 1;
964        text.replace(range.clone(), "");
965        wrapper._update(&text, &range, &Rope::from(""), &mut fake_wrap_line);
966        assert_eq!(
967            text.to_string(),
968            "AAA, 世界!\r\nThis is third line.\n这里是第 4 行。New text"
969        );
970        assert_eq!(wrapper.lines_count(), 3);
971        assert_wrapper_lines(
972            &text,
973            &wrapper,
974            &[
975                &["AAA, 世界!\r"],
976                &["This is third line."],
977                &["这里是第 4 行。New text"],
978            ],
979        );
980
981        // Replace the first 2 lines to "This is a new line."
982        let range = text.line_start_offset(0)..text.line_end_offset(1) + 1;
983        let new_text = "This is a new line.\nThis is new line 2.\n";
984        text.replace(range.clone(), new_text);
985        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
986        assert_eq!(
987            text.to_string(),
988            "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text"
989        );
990        assert_eq!(wrapper.lines_count(), 3);
991        assert_wrapper_lines(
992            &text,
993            &wrapper,
994            &[
995                &["This is a new line."],
996                &["This is new line 2."],
997                &["这里是第 4 行。New text"],
998            ],
999        );
1000
1001        // Add a new line at the end
1002        let range = text.len()..text.len();
1003        let new_text = "\nThis is a new line at the end.";
1004        text.replace(range.clone(), new_text);
1005        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1006        assert_eq!(
1007            text.to_string(),
1008            "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end."
1009        );
1010        assert_eq!(wrapper.lines_count(), 4);
1011        assert_wrapper_lines(
1012            &text,
1013            &wrapper,
1014            &[
1015                &["This is a new line."],
1016                &["This is new line 2."],
1017                &["这里是第 4 行。New text"],
1018                &["This is a new line at the end."],
1019            ],
1020        );
1021
1022        // Add a new line at the beginning
1023        let range = 0..0;
1024        let new_text = "This is a new line at the beginning.\n";
1025        text.replace(range.clone(), new_text);
1026        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1027        assert_eq!(
1028            text.to_string(),
1029            "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."
1030        );
1031        assert_eq!(wrapper.lines_count(), 5);
1032        assert_wrapper_lines(
1033            &text,
1034            &wrapper,
1035            &[
1036                &["This is a new line at the beginning."],
1037                &["This is a new line."],
1038                &["This is new line 2."],
1039                &["这里是第 4 行。New text"],
1040                &["This is a new line at the end."],
1041            ],
1042        );
1043
1044        // Remove all to at least one line in `lines`.
1045        let range = 0..text.len();
1046        let new_text = "";
1047        text.replace(range.clone(), new_text);
1048        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1049        assert_eq!(text.to_string(), "");
1050        assert_eq!(wrapper.lines_count(), 1);
1051        assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1052
1053        // Test update_all
1054        let range = 0..text.len();
1055        let new_text = "This is a full text.\nThis is a second line.";
1056        text.replace(range.clone(), new_text);
1057        wrapper._update(&text, &range, &text, &mut fake_wrap_line);
1058        assert_eq!(
1059            text.to_string(),
1060            "This is a full text.\nThis is a second line."
1061        );
1062        assert_eq!(wrapper.lines_count(), 2);
1063    }
1064
1065    fn test_font() -> gpui::Font {
1066        gpui::Font {
1067            family: "Arial".into(),
1068            weight: FontWeight::default(),
1069            style: FontStyle::Normal,
1070            features: FontFeatures::default(),
1071            fallbacks: None,
1072        }
1073    }
1074
1075    /// The longest-row summary stays exact when the previously-longest line is shrunk.
1076    #[test]
1077    fn test_longest_row_after_shrink() {
1078        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1079        let mut text = Rope::from("aa\nthis is the longest line\nbb");
1080        wrapper._update(&text, &(0..text.len()), &text, &mut |_, _| vec![]);
1081        assert_eq!(wrapper.longest_row(), 1);
1082
1083        // Shrink line 1 so line 2-equivalent isn't longest.
1084        // Make line 0 the longest now.
1085        let start = text.line_start_offset(0);
1086        let end = text.line_end_offset(0);
1087        let range = start..end;
1088        let new_text = "a very very long first line now";
1089        text.replace(range.clone(), new_text);
1090        wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _| vec![]);
1091        assert_eq!(wrapper.longest_row(), 0);
1092    }
1093
1094    /// Editing the last line and deleting everything must keep the tree consistent.
1095    #[test]
1096    fn test_edit_last_line_and_full_delete() {
1097        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1098        let mut text = Rope::from("one\ntwo\nthree");
1099        wrapper._update(&text, &(0..text.len()), &text, &mut |_, _| vec![]);
1100        assert_eq!(wrapper.lines_count(), 3);
1101
1102        // Replace the last line only.
1103        let start = text.line_start_offset(2);
1104        let range = start..text.len();
1105        let new_text = "THREE EDITED";
1106        text.replace(range.clone(), new_text);
1107        wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _| vec![]);
1108        assert_eq!(wrapper.lines_count(), 3);
1109        assert_eq!(wrapper.line(2).unwrap().len(), "THREE EDITED".len());
1110
1111        // Delete everything.
1112        let range = 0..text.len();
1113        text.replace(range.clone(), "");
1114        wrapper._update(&text, &range, &Rope::from(""), &mut |_, _| vec![]);
1115        assert_eq!(wrapper.lines_count(), 1);
1116        assert_eq!(wrapper.len(), 1);
1117        assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1118    }
1119
1120    #[test]
1121    fn test_wrap_row_buffer_line_boundaries() {
1122        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1123        wrapper.text = Rope::from("aa\nbbbb\nc");
1124        wrapper.lines = SumTree::from_iter(
1125            vec![
1126                LineItem {
1127                    len: 2,
1128                    indent: 0,
1129                    wrapped_lines: smallvec::smallvec![0..2],
1130                },
1131                LineItem {
1132                    len: 4,
1133                    indent: 0,
1134                    wrapped_lines: smallvec::smallvec![0..2, 2..4],
1135                },
1136                LineItem {
1137                    len: 1,
1138                    indent: 0,
1139                    wrapped_lines: smallvec::smallvec![0..1],
1140                },
1141            ],
1142            &(),
1143        );
1144
1145        assert_eq!(wrapper.lines_count(), 3);
1146        assert_eq!(wrapper.len(), 4);
1147
1148        assert_eq!(wrapper.buffer_line_to_first_wrap_row(0), 0);
1149        assert_eq!(wrapper.buffer_line_to_first_wrap_row(1), 1);
1150        assert_eq!(wrapper.buffer_line_to_first_wrap_row(2), 3);
1151        assert_eq!(wrapper.buffer_line_to_first_wrap_row(3), 4);
1152
1153        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1154        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1155        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1156        assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..4);
1157
1158        assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1159        assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1160        assert_eq!(wrapper.wrap_row_to_buffer_line(2), 1);
1161        assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1162        assert_eq!(wrapper.wrap_row_to_buffer_line(4), 2);
1163    }
1164
1165    #[test]
1166    fn test_wrap_row_queries_after_incremental_splice() {
1167        let mut wrapper = TextWrapper::new(test_font(), px(14.), Some(px(10.)));
1168        let mut text = Rope::from("aa\nbbbb\nc");
1169        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels| {
1170            if line.len() > 2 {
1171                vec![Boundary {
1172                    ix: 2,
1173                    next_indent: 0,
1174                }]
1175            } else {
1176                vec![]
1177            }
1178        };
1179
1180        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1181        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1182        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1183        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1184
1185        let range = text.line_start_offset(1)..text.line_end_offset(1);
1186        let new_text = "dd\neeee";
1187        text.replace(range.clone(), new_text);
1188        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1189
1190        assert_eq!(wrapper.lines_count(), 4);
1191        assert_eq!(wrapper.len(), 5);
1192        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1193        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..2);
1194        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 2..4);
1195        assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..5);
1196        assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1197        assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1198        assert_eq!(wrapper.wrap_row_to_buffer_line(2), 2);
1199        assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1200        assert_eq!(wrapper.wrap_row_to_buffer_line(4), 3);
1201    }
1202
1203    #[test]
1204    fn test_line_layout() {
1205        let mut line_layout = LineLayout::new();
1206
1207        let line1 = ShapedLine::default().with_len(100);
1208        let line2 = ShapedLine::default().with_len(50);
1209        let wrapped_lines = smallvec::smallvec![line1, line2];
1210        line_layout.set_wrapped_lines(wrapped_lines);
1211        assert_eq!(line_layout.len(), 150);
1212        assert_eq!(line_layout.wrapped_lines.len(), 2);
1213    }
1214
1215    /// A layout context whose only load-bearing field is the line height.
1216    fn test_last_layout(line_height: Pixels) -> LastLayout {
1217        LastLayout {
1218            visible_range: 0..1,
1219            visible_buffer_lines: vec![0],
1220            visible_line_byte_offsets: vec![0],
1221            visible_top: px(0.),
1222            visible_range_offset: 0..0,
1223            lines: Rc::new(vec![]),
1224            line_height,
1225            wrap_width: None,
1226            wrapping_indent: WrappingIndent::default(),
1227            line_number_width: px(0.),
1228            space_width: px(0.),
1229            cursor_bounds: None,
1230            text_align: TextAlign::Left,
1231            content_width: px(0.),
1232        }
1233    }
1234
1235    #[test]
1236    fn test_position_for_index_prefers_first_leading_empty_visual_line() {
1237        let mut line_layout = LineLayout::new();
1238        line_layout.set_wrapped_lines(smallvec::smallvec![
1239            ShapedLine::default(),
1240            ShapedLine::default(),
1241            ShapedLine::default().with_len(3),
1242        ]);
1243
1244        assert_eq!(
1245            line_layout.position_for_index(0, &test_last_layout(px(20.)), false),
1246            Some(point(px(0.), px(0.)))
1247        );
1248    }
1249
1250    #[test]
1251    fn clicking_past_a_wrapped_row_keeps_the_caret_on_that_row() {
1252        // One buffer line wrapped into two visual rows, splitting at byte 10.
1253        let mut line_layout = LineLayout::new();
1254        line_layout.set_wrapped_lines(smallvec::smallvec![
1255            ShapedLine::default().with_len(10),
1256            ShapedLine::default().with_len(5),
1257        ]);
1258        let last_layout = test_last_layout(px(20.));
1259
1260        // Clicking past the last glyph of the first row resolves to the wrap boundary, which is
1261        // also the first offset of the second row -- hence the affinity.
1262        let (ix, line_end_affinity) = line_layout
1263            .closest_index_for_position(point(px(999.), px(5.)), &last_layout)
1264            .unwrap();
1265        assert_eq!(ix, 10);
1266        assert!(line_end_affinity);
1267
1268        // Carrying that affinity is what keeps the caret on the row that was clicked; dropping it
1269        // is the bug -- the caret shows up one row below the pointer.
1270        assert_eq!(
1271            line_layout
1272                .position_for_index(ix, &last_layout, line_end_affinity)
1273                .map(|pos| pos.y),
1274            Some(px(0.))
1275        );
1276        assert_eq!(
1277            line_layout
1278                .position_for_index(ix, &last_layout, false)
1279                .map(|pos| pos.y),
1280            Some(px(20.))
1281        );
1282
1283        // The final row owns the end of the line outright, so there is nothing to disambiguate.
1284        let (ix, line_end_affinity) = line_layout
1285            .closest_index_for_position(point(px(999.), px(25.)), &last_layout)
1286            .unwrap();
1287        assert_eq!(ix, 15);
1288        assert!(!line_end_affinity);
1289    }
1290
1291    #[test]
1292    fn a_wrap_boundary_offset_resolves_to_the_row_the_caret_is_drawn_on() {
1293        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1294        wrapper.text = Rope::from("first line\nthis one wraps");
1295        wrapper.lines = SumTree::from_iter(
1296            vec![
1297                LineItem {
1298                    len: Rope::from("first line").len(),
1299                    indent: 0,
1300                    wrapped_lines: smallvec::smallvec![0..10],
1301                },
1302                LineItem {
1303                    len: Rope::from("this one wraps").len(),
1304                    indent: 0,
1305                    wrapped_lines: smallvec::smallvec![0..9, 9..14],
1306                },
1307            ],
1308            &(),
1309        );
1310
1311        // Offset 20 is the wrap boundary of the second buffer line: 11 (line start) + 9.
1312        // It is the last offset of wrap row 1 and the first of wrap row 2 at the same time, so
1313        // only the affinity can say which row a vertical move should step away from.
1314        assert_eq!(
1315            wrapper.offset_to_display_point_with_affinity(20, true),
1316            WrapDisplayPoint::new(1, 0, 9)
1317        );
1318        assert_eq!(
1319            wrapper.offset_to_display_point_with_affinity(20, false),
1320            WrapDisplayPoint::new(2, 1, 0)
1321        );
1322        assert_eq!(
1323            wrapper.offset_to_display_point(20),
1324            wrapper.offset_to_display_point_with_affinity(20, false)
1325        );
1326
1327        // An offset that is not on a boundary is unaffected either way.
1328        for line_end_affinity in [false, true] {
1329            assert_eq!(
1330                wrapper.offset_to_display_point_with_affinity(23, line_end_affinity),
1331                WrapDisplayPoint::new(2, 1, 3)
1332            );
1333        }
1334    }
1335
1336    #[test]
1337    fn test_offset_to_display_point() {
1338        let font = gpui::Font {
1339            family: "Arial".into(),
1340            weight: FontWeight::default(),
1341            style: FontStyle::Normal,
1342            features: FontFeatures::default(),
1343            fallbacks: None,
1344        };
1345
1346        let mut wrapper = TextWrapper::new(font, px(14.), None);
1347        wrapper.text = Rope::from(
1348            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1349        );
1350        wrapper.lines = SumTree::from_iter(
1351            vec![
1352                // range: 0..15
1353                LineItem {
1354                    len: Rope::from("Hello, 世界!\r").len(),
1355                    indent: 0,
1356                    wrapped_lines: smallvec::smallvec![0..15],
1357                },
1358                // range: 16..36
1359                LineItem {
1360                    len: Rope::from("This is second line.\n").len(),
1361                    indent: 0,
1362                    wrapped_lines: smallvec::smallvec![0..10, 10..20],
1363                },
1364                // range: 37..56
1365                LineItem {
1366                    len: Rope::from("This is third line.\n").len(),
1367                    indent: 0,
1368                    wrapped_lines: smallvec::smallvec![0..9, 9..15, 15..20],
1369                },
1370                // range: 57..79
1371                LineItem {
1372                    len: Rope::from("这里是第 4 行。").len(),
1373                    indent: 0,
1374                    wrapped_lines: smallvec::smallvec![0..22],
1375                },
1376            ],
1377            &(),
1378        );
1379
1380        assert_eq!(
1381            wrapper.offset_to_display_point(12),
1382            WrapDisplayPoint::new(0, 0, 12)
1383        );
1384        assert_eq!(
1385            wrapper.offset_to_display_point(15),
1386            WrapDisplayPoint::new(0, 0, 15)
1387        );
1388
1389        assert_eq!(
1390            wrapper.offset_to_display_point(16),
1391            WrapDisplayPoint::new(1, 0, 0)
1392        );
1393        assert_eq!(
1394            wrapper.offset_to_display_point(21),
1395            WrapDisplayPoint::new(1, 0, 5)
1396        );
1397        assert_eq!(
1398            wrapper.offset_to_display_point(27),
1399            WrapDisplayPoint::new(2, 1, 1)
1400        );
1401        assert_eq!(
1402            wrapper.offset_to_display_point(37),
1403            WrapDisplayPoint::new(3, 0, 0)
1404        );
1405        assert_eq!(
1406            wrapper.offset_to_display_point(54),
1407            WrapDisplayPoint::new(5, 2, 2)
1408        );
1409        assert_eq!(
1410            wrapper.offset_to_display_point(59),
1411            WrapDisplayPoint::new(6, 0, 2)
1412        );
1413
1414        assert_eq!(
1415            wrapper.display_point_to_offset(WrapDisplayPoint::new(6, 0, 2)),
1416            59
1417        );
1418        assert_eq!(
1419            wrapper.display_point_to_offset(WrapDisplayPoint::new(5, 2, 2)),
1420            54
1421        );
1422        assert_eq!(
1423            wrapper.display_point_to_offset(WrapDisplayPoint::new(3, 0, 0)),
1424            37
1425        );
1426        assert_eq!(
1427            wrapper.display_point_to_offset(WrapDisplayPoint::new(2, 1, 1)),
1428            27
1429        );
1430        assert_eq!(
1431            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 5)),
1432            21
1433        );
1434        assert_eq!(
1435            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 0)),
1436            16
1437        );
1438        assert_eq!(
1439            wrapper.display_point_to_offset(WrapDisplayPoint::new(0, 0, 15)),
1440            15
1441        );
1442    }
1443
1444    #[test]
1445    fn test_wrapping_indent_same_keeps_indent_reserved() {
1446        let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1447        wrapper.wrapping_indent = WrappingIndent::Same;
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, 2);
1473        assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..24]);
1474    }
1475
1476    #[test]
1477    fn test_wrapping_indent_none_continuation_lines_wrapped_at_full_width() {
1478        let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1479        wrapper.wrapping_indent = WrappingIndent::None;
1480        let text = Rope::from("  abcdefghijklmnopqrstuv");
1481        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels| {
1482            if line.starts_with(' ') {
1483                vec![Boundary {
1484                    ix: 5,
1485                    next_indent: 2,
1486                }]
1487            } else {
1488                let mut boundaries = vec![];
1489                let mut i = 8;
1490                while i < line.len() {
1491                    boundaries.push(Boundary {
1492                        ix: i,
1493                        next_indent: 0,
1494                    });
1495                    i += 8;
1496                }
1497                boundaries
1498            }
1499        };
1500
1501        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1502
1503        let line = wrapper.line(0).unwrap();
1504        assert_eq!(line.indent, 0);
1505        assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..13, 13..21, 21..24]);
1506    }
1507
1508    #[test]
1509    fn test_wrap_indent_offsets_continuation_lines() {
1510        let mut line_layout = LineLayout::new();
1511        line_layout.set_wrapped_lines(smallvec::smallvec![
1512            ShapedLine::default().with_len(5),
1513            ShapedLine::default().with_len(10),
1514        ]);
1515
1516        line_layout = line_layout.wrap_indent(px(20.0));
1517
1518        let last_layout = LastLayout {
1519            visible_range: 0..1,
1520            visible_buffer_lines: vec![0],
1521            visible_line_byte_offsets: vec![0],
1522            visible_top: px(0.),
1523            visible_range_offset: 0..0,
1524            lines: Rc::new(vec![]),
1525            line_height: px(20.0),
1526            wrap_width: Some(px(10.)),
1527            wrapping_indent: WrappingIndent::Same,
1528            line_number_width: px(0.),
1529            space_width: px(0.),
1530            cursor_bounds: None,
1531            text_align: TextAlign::Left,
1532            content_width: px(0.),
1533        };
1534
1535        assert_eq!(
1536            line_layout.position_for_index(0, &last_layout, false),
1537            Some(point(px(0.), px(0.))),
1538        );
1539
1540        assert_eq!(
1541            line_layout.position_for_index(6, &last_layout, false),
1542            Some(point(px(20.), px(20.))),
1543        )
1544    }
1545}