Skip to main content

gpui_base/input/editor/display_map/
text_wrapper.rs

1use super::inline_line::InputLine;
2use gpui::Half;
3use std::borrow::Cow;
4use std::ops::Range;
5use std::rc::Rc;
6
7use gpui::{
8    App, Font, LineFragment, Pixels, Point, ShapedLine, Size, TextAlign, Window, point, px, size,
9};
10use ropey::Rope;
11use smallvec::SmallVec;
12use sum_tree::{Bias, Dimensions, SumTree};
13use unicode_segmentation::UnicodeSegmentation as _;
14
15use crate::input::{
16    Point as TreeSitterPoint, RopeExt, TabSize,
17    layout::{LastLayout, WhitespaceIndicators},
18    tab_stops::{expanded_width, tab_stop_width},
19};
20
21/// Controls how soft-wrapped continuation lines are indented.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
23pub enum WrappingIndent {
24    /// Continuation lines start flush-left at the full editor width.
25    None,
26    /// Continuation lines keep the same indentation as the first line.
27    #[default]
28    Same,
29}
30
31/// Choose Unicode line-break opportunities using the same shaped widths as
32/// painting. Oversized words fall back to complete graphemes, never UTF-8 bytes.
33fn measured_wrap_boundaries(
34    text: &str,
35    width: Pixels,
36    wrapping_indent: WrappingIndent,
37    mut measure: impl FnMut(&str) -> Pixels,
38) -> Vec<gpui::Boundary> {
39    let indent = if wrapping_indent == WrappingIndent::Same {
40        text.chars()
41            .take_while(|&c| c == ' ')
42            .count()
43            .min(gpui::LineWrapper::MAX_INDENT as usize)
44    } else {
45        0
46    };
47    let indent_width = measure(&text[..indent]);
48    let ends: Vec<usize> = text
49        .grapheme_indices(true)
50        .map(|(ix, grapheme)| ix + grapheme.len())
51        .collect();
52    let opportunities: Vec<usize> = unicode_linebreak::linebreaks(text)
53        .map(|(ix, _)| ix)
54        .filter(|ix| ends.binary_search(ix).is_ok())
55        .collect();
56    let mut result = Vec::new();
57    let mut first = 0;
58    let mut start = 0;
59    while first < ends.len() {
60        let available = if start == 0 {
61            width
62        } else {
63            width - indent_width
64        };
65        // Find the fitting prefix locally. Exponential probing avoids shaping
66        // the entire remaining logical line for every visual row of a long paste.
67        let remaining = ends.len() - first;
68        let mut low = 0;
69        let mut high = 1;
70        while measure(&text[start..ends[first + high - 1]]) <= available {
71            low = high;
72            if high == remaining {
73                break;
74            }
75            high = (high * 2).min(remaining);
76        }
77        while low + 1 < high {
78            let mid = (low + high) / 2;
79            if measure(&text[start..ends[first + mid - 1]]) <= available {
80                low = mid;
81            } else {
82                high = mid;
83            }
84        }
85        // An indivisible grapheme wider than the viewport still consumes a row.
86        let fitting_end = ends[first + low.max(1) - 1];
87        if fitting_end == text.len() {
88            break;
89        }
90        let candidate = opportunities.partition_point(|&ix| ix <= fitting_end);
91        let end = candidate
92            .checked_sub(1)
93            .map(|ix| opportunities[ix])
94            .filter(|&ix| ix > start && (start != 0 || ix > indent))
95            .unwrap_or(fitting_end);
96        result.push(gpui::Boundary {
97            ix: end,
98            next_indent: indent as u32,
99        });
100        first = ends.partition_point(|&ix| ix <= end);
101        start = end;
102    }
103    result
104}
105
106/// A line with soft wrapped lines info.
107#[derive(Debug, Clone)]
108pub(crate) struct LineItem {
109    /// The byte length of the line, without the end `\n`.
110    len: usize,
111    /// Number of leading characters of the line reserved as indentation for continuation wrapped
112    /// lines, when [`WrappingIndent::Same`] is used.
113    ///
114    /// Zero when [`WrappingIndent::None`] is used or the line is not wrapped.
115    pub(crate) indent: u32,
116    /// The soft wrapped lines relative byte range (0..len) of this line (Include first line).
117    ///
118    /// Not contains the line end `\n`.
119    pub(crate) wrapped_lines: SmallVec<[Range<usize>; 1]>,
120}
121
122impl LineItem {
123    /// Get the bytes length of this line.
124    #[inline]
125    pub(crate) fn len(&self) -> usize {
126        self.len
127    }
128
129    /// Get number of soft wrapped lines of this line (include the first line).
130    #[inline]
131    pub(crate) fn lines_len(&self) -> usize {
132        self.wrapped_lines.len()
133    }
134}
135
136/// Summary of a subtree of [`LineItem`]s, maintained incrementally by the [`SumTree`].
137#[derive(Debug, Clone)]
138pub(crate) struct LineSummary {
139    /// Number of buffer lines.
140    buffer_rows: usize,
141    /// Number of wrap rows (sum of each line's `lines_len()`).
142    wrap_rows: usize,
143    /// Sum of byte lengths of the buffer lines (without the trailing `\n`).
144    bytes: usize,
145    /// Byte length of the longest line in this subtree.
146    max_line_len: usize,
147    /// Buffer row (relative to this subtree) of the first line achieving `max_line_len`.
148    longest_row: usize,
149}
150
151impl sum_tree::Summary for LineSummary {
152    type Context<'a> = &'a ();
153
154    fn zero(_: &()) -> Self {
155        LineSummary {
156            buffer_rows: 0,
157            wrap_rows: 0,
158            bytes: 0,
159            max_line_len: 0,
160            longest_row: 0,
161        }
162    }
163
164    fn add_summary(&mut self, other: &Self, _: &()) {
165        // Keep the leftmost row that achieves a strictly greater length
166        if other.max_line_len > self.max_line_len {
167            self.longest_row = self.buffer_rows + other.longest_row;
168            self.max_line_len = other.max_line_len;
169        }
170        self.buffer_rows += other.buffer_rows;
171        self.wrap_rows += other.wrap_rows;
172        self.bytes += other.bytes;
173    }
174}
175
176impl sum_tree::Item for LineItem {
177    type Summary = LineSummary;
178
179    fn summary(&self, _: &()) -> LineSummary {
180        LineSummary {
181            buffer_rows: 1,
182            wrap_rows: self.lines_len(),
183            bytes: self.len(),
184            max_line_len: self.len(),
185            longest_row: 0,
186        }
187    }
188}
189
190/// Cursor dimension counting buffer rows.
191#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
192pub(crate) struct BufferRows(pub usize);
193
194impl<'a> sum_tree::Dimension<'a, LineSummary> for BufferRows {
195    fn zero(_: &()) -> Self {
196        BufferRows(0)
197    }
198
199    fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
200        self.0 += summary.buffer_rows;
201    }
202}
203
204/// Cursor dimension counting wrap rows.
205#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, PartialOrd, Ord)]
206pub(crate) struct WrapRows(pub usize);
207
208impl<'a> sum_tree::Dimension<'a, LineSummary> for WrapRows {
209    fn zero(_: &()) -> Self {
210        WrapRows(0)
211    }
212
213    fn add_summary(&mut self, summary: &'a LineSummary, _: &()) {
214        self.0 += summary.wrap_rows;
215    }
216}
217
218/// Used to prepare the text with soft wrap to be get lines to displayed in the Editor.
219///
220/// After use lines to calculate the scroll size of the Editor.
221pub(crate) struct TextWrapper {
222    text: Rope,
223    font: Font,
224    font_size: Pixels,
225    /// If is none, it means the text is not wrapped
226    wrap_width: Option<Pixels>,
227    wrapping_indent: WrappingIndent,
228    /// Spaces per tab stop, so tabs are measured as they are drawn.
229    tab_size: usize,
230    /// The lines by split \n
231    pub(crate) lines: SumTree<LineItem>,
232
233    inline_metrics: Rc<[(Range<usize>, Pixels)]>,
234    _initialized: bool,
235}
236
237#[allow(unused)]
238impl TextWrapper {
239    pub(crate) fn new(font: Font, font_size: Pixels, wrap_width: Option<Pixels>) -> Self {
240        Self {
241            text: Rope::new(),
242            font,
243            font_size,
244            wrap_width,
245            wrapping_indent: WrappingIndent::default(),
246            tab_size: TabSize::default().tab_size,
247            lines: SumTree::new(&()),
248            inline_metrics: Rc::from([]),
249            _initialized: false,
250        }
251    }
252
253    #[inline]
254    pub(crate) fn set_default_text(&mut self, text: &Rope) {
255        self.text = text.clone();
256    }
257
258    /// Get reference to the rope text.
259    #[inline]
260    pub(crate) fn text(&self) -> &Rope {
261        &self.text
262    }
263
264    /// Get the total number of lines including wrapped lines.
265    #[inline]
266    pub(crate) fn len(&self) -> usize {
267        self.lines.summary().wrap_rows
268    }
269
270    /// Get the total number of buffer lines.
271    #[inline]
272    pub(crate) fn lines_count(&self) -> usize {
273        self.lines.summary().buffer_rows
274    }
275
276    /// Get the 0-based row index of the longest line (by byte length).
277    #[inline]
278    pub(crate) fn longest_row(&self) -> usize {
279        self.lines.summary().longest_row
280    }
281
282    /// Get the line item by buffer row index.
283    #[inline]
284    pub(crate) fn line(&self, row: usize) -> Option<&LineItem> {
285        let mut cursor = self.lines.cursor::<BufferRows>(&());
286        cursor.seek(&BufferRows(row), Bias::Right);
287        cursor.item()
288    }
289
290    /// Iterate buffer lines in order.
291    #[inline]
292    pub(crate) fn iter_lines(&self) -> impl Iterator<Item = &LineItem> {
293        self.lines.iter()
294    }
295
296    /// First wrap row of buffer line `row`. Returns the total wrap row count if `row` is
297    /// out of range.
298    pub(crate) fn buffer_line_to_first_wrap_row(&self, row: usize) -> usize {
299        let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
300        cursor.seek(&BufferRows(row), Bias::Right);
301        cursor.start().1.0
302    }
303
304    /// Wrap row range of buffer line `row`.
305    pub(crate) fn buffer_line_to_wrap_row_range(&self, row: usize) -> Range<usize> {
306        let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
307        cursor.seek(&BufferRows(row), Bias::Right);
308        let start = cursor.start().1.0;
309        let len = cursor.item().map(|l| l.lines_len()).unwrap_or(0);
310        start..start + len
311    }
312
313    /// Buffer line containing wrap row `wrap_row`, clamped to the last line.
314    pub(crate) fn wrap_row_to_buffer_line(&self, wrap_row: usize) -> usize {
315        let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
316        cursor.seek(&WrapRows(wrap_row), Bias::Right);
317        match cursor.item() {
318            Some(_) => cursor.start().1.0,
319            None => self.lines_count().saturating_sub(1),
320        }
321    }
322
323    pub(crate) fn set_wrap_width(&mut self, wrap_width: Option<Pixels>, cx: &mut App) {
324        if wrap_width == self.wrap_width {
325            return;
326        }
327
328        self.wrap_width = wrap_width;
329        self.update_all(&self.text.clone(), cx);
330    }
331
332    pub(crate) fn set_wrapping_indent(&mut self, wrapping_indent: WrappingIndent, cx: &mut App) {
333        if wrapping_indent == self.wrapping_indent {
334            return;
335        }
336
337        self.wrapping_indent = wrapping_indent;
338        self.update_all(&self.text.clone(), cx);
339    }
340
341    /// Returns whether the tab size changed, and so the wrapping.
342    pub(crate) fn set_tab_size(&mut self, tab_size: usize, cx: &mut App) -> bool {
343        if tab_size == self.tab_size {
344            return false;
345        }
346
347        self.tab_size = tab_size;
348        self.update_all(&self.text.clone(), cx);
349        true
350    }
351
352    pub(crate) fn set_font(&mut self, font: Font, font_size: Pixels, cx: &mut App) {
353        if self.font.eq(&font) && self.font_size == font_size {
354            return;
355        }
356
357        self.font = font;
358        self.font_size = font_size;
359        self.update_all(&self.text.clone(), cx);
360    }
361
362    pub(crate) fn prepare_if_need(&mut self, text: &Rope, cx: &mut App) -> bool {
363        if self._initialized {
364            return false;
365        }
366        self._initialized = true;
367        self.update_all(text, cx);
368        true
369    }
370
371    /// Update the text wrapper and recalculate the wrapped lines.
372    ///
373    /// If the `text` is the same as the current text, do nothing.
374    ///
375    /// - `changed_text`: The text [`Rope`] that has changed.
376    /// - `range`: The `selected_range` before change.
377    /// - `new_text`: The inserted text.
378    /// - `force`: Whether to force the update, if false, the update will be skipped if the text is the same.
379    /// - `cx`: The application context.
380    pub(crate) fn update(
381        &mut self,
382        changed_text: &Rope,
383        range: &Range<usize>,
384        new_text: &Rope,
385        cx: &mut App,
386    ) {
387        let mut line_wrapper = cx
388            .text_system()
389            .line_wrapper(self.font.clone(), self.font_size);
390        let metrics = self.inline_metrics.clone();
391        let text_system = gpui::WindowTextSystem::new(cx.text_system().clone());
392        let font = self.font.clone();
393        let font_size = self.font_size;
394        let wrapping_indent = self.wrapping_indent;
395        let tab_stop = tab_stop_width(&text_system, &font, font_size, self.tab_size);
396        self._update(
397            changed_text,
398            range,
399            new_text,
400            &mut |line_str, wrap_width, line_start| {
401                let mut fragments = Vec::new();
402                let mut offset = 0;
403                let first = metrics.partition_point(|(r, _)| r.end <= line_start);
404                for (range, width) in &metrics[first..] {
405                    if range.start >= line_start + line_str.len() {
406                        break;
407                    }
408                    if range.start < line_start || range.end > line_start + line_str.len() {
409                        continue;
410                    }
411                    let range = range.start - line_start..range.end - line_start;
412                    if !line_str.is_char_boundary(range.start)
413                        || !line_str.is_char_boundary(range.end)
414                    {
415                        continue;
416                    }
417                    if offset < range.start {
418                        fragments.push(LineFragment::text(&line_str[offset..range.start]));
419                    }
420                    fragments.push(LineFragment::element(*width, range.len()));
421                    offset = range.end;
422                }
423                if fragments.is_empty() {
424                    return measured_wrap_boundaries(
425                        line_str,
426                        wrap_width,
427                        wrapping_indent,
428                        |text| {
429                            let layout = text_system.layout_line(
430                                text,
431                                font_size,
432                                &[gpui::TextRun {
433                                    len: text.len(),
434                                    font: font.clone(),
435                                    color: gpui::black(),
436                                    background_color: None,
437                                    underline: None,
438                                    strikethrough: None,
439                                }],
440                                None,
441                            );
442                            expanded_width(&layout, text, tab_stop)
443                        },
444                    );
445                }
446                if offset < line_str.len() {
447                    fragments.push(LineFragment::text(&line_str[offset..]));
448                }
449                line_wrapper
450                    .wrap_line(&fragments, wrap_width, gpui::IndentAdjustment::SameIndent)
451                    .collect()
452            },
453        );
454    }
455
456    pub(crate) fn adjust_inline_metrics(&mut self, range: &Range<usize>, new_len: usize) {
457        if self.inline_metrics.is_empty() {
458            return;
459        }
460        let shift = new_len as isize - range.len() as isize;
461        self.inline_metrics = self
462            .inline_metrics
463            .iter()
464            .filter_map(|(token, width)| {
465                if token.start < range.end && range.start < token.end {
466                    return None;
467                }
468                let token = if token.start >= range.end {
469                    token.start.checked_add_signed(shift)?..token.end.checked_add_signed(shift)?
470                } else {
471                    token.clone()
472                };
473                Some((token, *width))
474            })
475            .collect();
476    }
477
478    pub(crate) fn set_inline_metrics(
479        &mut self,
480        metrics: Rc<[(Range<usize>, Pixels)]>,
481        cx: &mut App,
482    ) {
483        if self.inline_metrics == metrics {
484            return;
485        }
486        // Only rows whose element geometry changed need another wrap pass.
487        let mut affected = Vec::new();
488        let (mut old, mut new) = (
489            self.inline_metrics.iter().peekable(),
490            metrics.iter().peekable(),
491        );
492        while old.peek().is_some() || new.peek().is_some() {
493            match (old.peek(), new.peek()) {
494                (Some(a), Some(b)) if a == b => {
495                    old.next();
496                    new.next();
497                }
498                (Some(a), Some(b)) if a.0.start <= b.0.start => {
499                    affected.push(a.0.clone());
500                    old.next();
501                }
502                (Some(_), Some(b)) | (None, Some(b)) => {
503                    affected.push(b.0.clone());
504                    new.next();
505                }
506                (Some(a), None) => {
507                    affected.push(a.0.clone());
508                    old.next();
509                }
510                (None, None) => break,
511            }
512        }
513        self.inline_metrics = metrics;
514        let text = self.text.clone();
515        let mut rows: Vec<usize> = affected
516            .iter()
517            .map(|r| text.offset_to_point(r.start.min(text.len())).row)
518            .collect();
519        rows.sort_unstable();
520        rows.dedup();
521        for row in rows {
522            let start = text.line_start_offset(row);
523            let end = text.line_end_offset(row);
524            self.update(
525                &text,
526                &(start..end),
527                &Rope::from(text.slice(start..end).to_string()),
528                cx,
529            );
530        }
531    }
532
533    fn _update<F>(
534        &mut self,
535        changed_text: &Rope,
536        range: &Range<usize>,
537        new_text: &Rope,
538        wrap_line: &mut F,
539    ) where
540        F: FnMut(&str, Pixels, usize) -> Vec<gpui::Boundary>,
541    {
542        // Remove the old changed lines.
543        let buffer_line_count = self.lines_count();
544        let start_row = self.text.offset_to_point(range.start).row;
545        let start_row = start_row.min(buffer_line_count.saturating_sub(1));
546        let end_row = self.text.offset_to_point(range.end).row;
547        let end_row = end_row.min(buffer_line_count.saturating_sub(1));
548
549        // To add the new lines.
550        let new_start_row = changed_text.offset_to_point(range.start).row;
551        let new_end_row = changed_text
552            .offset_to_point(range.start + new_text.len())
553            .row;
554
555        let mut new_lines = Vec::with_capacity(new_end_row.saturating_sub(new_start_row) + 1);
556        let wrap_width = self.wrap_width;
557
558        // line not contains `\n`.
559        for row in new_start_row..=new_end_row {
560            let line = changed_text.slice_line(row);
561            let mut wrapped_lines = SmallVec::<[Range<usize>; 1]>::new();
562            let mut prev_boundary_ix = 0;
563            let mut indent_chars = 0;
564
565            // If wrap_width is Pixels::MAX, skip wrapping to disable word wrap
566            if let Some(wrap_width) = wrap_width {
567                // Borrowed for lines within a single rope chunk.
568                let line_str: Cow<str> = line.into();
569                match self.wrapping_indent {
570                    WrappingIndent::Same => {
571                        // Here only have wrapped line, if there is no wrap meet, the `line_wraps`
572                        // result will empty.
573                        for boundary in
574                            wrap_line(&line_str, wrap_width, changed_text.line_start_offset(row))
575                        {
576                            wrapped_lines.push(prev_boundary_ix..boundary.ix);
577                            prev_boundary_ix = boundary.ix;
578                            indent_chars = boundary.next_indent;
579                        }
580                    }
581                    WrappingIndent::None => {
582                        // The first visual line keeps the line's leading indentation, so it is
583                        // wrapped as is.
584                        let boundaries =
585                            wrap_line(&line_str, wrap_width, changed_text.line_start_offset(row));
586                        if let Some(first_ix) = boundaries.first().map(|b| b.ix) {
587                            wrapped_lines.push(prev_boundary_ix..first_ix);
588                            prev_boundary_ix = first_ix;
589
590                            for boundary in wrap_line(
591                                &line_str[first_ix..],
592                                wrap_width,
593                                changed_text.line_start_offset(row) + first_ix,
594                            ) {
595                                let ix = first_ix + boundary.ix;
596                                wrapped_lines.push(prev_boundary_ix..ix);
597                                prev_boundary_ix = ix;
598                            }
599                        }
600                    }
601                }
602            }
603
604            // Reset of the line
605            if prev_boundary_ix < line.len() || prev_boundary_ix == 0 {
606                wrapped_lines.push(prev_boundary_ix..line.len());
607            }
608
609            new_lines.push(LineItem {
610                len: line.len(),
611                indent: indent_chars,
612                wrapped_lines,
613            });
614        }
615
616        if self.lines.is_empty() {
617            self.lines = SumTree::from_iter(new_lines, &());
618        } else {
619            let mut cursor = self.lines.cursor::<BufferRows>(&());
620            let mut new_tree = cursor.slice(&BufferRows(start_row), Bias::Right);
621            // Skip the replaced rows
622            cursor.seek_forward(&BufferRows(end_row + 1), Bias::Right);
623            new_tree.extend(new_lines, &());
624            // Untouched rows after the edit
625            new_tree.append(cursor.suffix(), &());
626            drop(cursor);
627            self.lines = new_tree;
628        }
629
630        self.text = changed_text.clone();
631    }
632
633    /// Update the text wrapper and recalculate the wrapped lines.
634    ///
635    /// If the `text` is the same as the current text, do nothing.
636    fn update_all(&mut self, text: &Rope, cx: &mut App) {
637        self.update(text, &(0..text.len()), &text, cx);
638    }
639
640    /// Return display point (with soft wrap) from the given byte offset in the text.
641    ///
642    /// Panics if the `offset` is out of bounds.
643    pub(crate) fn offset_to_display_point(&self, offset: usize) -> WrapDisplayPoint {
644        self.offset_to_display_point_with_affinity(offset, false)
645    }
646
647    /// Like [`Self::offset_to_display_point`], but honours the caret's line-end affinity.
648    ///
649    /// A soft wrap boundary is one offset shared by two visual rows. Without affinity it always
650    /// resolves to the start of the second row, which is wrong for a caret that is being drawn at
651    /// the end of the first one -- vertical movement would then step from the row below the one
652    /// the user can see.
653    pub(crate) fn offset_to_display_point_with_affinity(
654        &self,
655        offset: usize,
656        line_end_affinity: bool,
657    ) -> WrapDisplayPoint {
658        let row = self.text.offset_to_point(offset).row;
659        let start = self.text.line_start_offset(row);
660
661        // Seek to buffer row
662        let mut cursor = self.lines.cursor::<Dimensions<BufferRows, WrapRows>>(&());
663        cursor.seek(&BufferRows(row), Bias::Right);
664        let wrapped_row = cursor.start().1.0;
665        let Some(line) = cursor.item() else {
666            return WrapDisplayPoint::new(wrapped_row, 0, 0);
667        };
668
669        let local_offset = offset.saturating_sub(start);
670        for (ix, range) in line.wrapped_lines.iter().enumerate() {
671            // With affinity the boundary offset closes the current row instead of opening the
672            // next one, so the range is matched inclusively.
673            let matches =
674                range.contains(&local_offset) || (line_end_affinity && local_offset == range.end);
675            if matches {
676                return WrapDisplayPoint::new(
677                    wrapped_row + ix,
678                    ix,
679                    local_offset.saturating_sub(range.start),
680                );
681            }
682        }
683
684        // Otherwise return the eof of the line.
685        let last_range = line.wrapped_lines.last().unwrap_or(&(0..0));
686        let ix = line.lines_len().saturating_sub(1);
687        return WrapDisplayPoint::new(wrapped_row + ix, ix, last_range.len());
688    }
689
690    /// Return byte offset in the text from the given display point (with soft wrap).
691    ///
692    /// Panics if the `point.row` is out of bounds.
693    pub(crate) fn display_point_to_offset(&self, point: WrapDisplayPoint) -> usize {
694        // Seek to wrap row `point.row`
695        let mut cursor = self.lines.cursor::<Dimensions<WrapRows, BufferRows>>(&());
696        cursor.seek(&WrapRows(point.row), Bias::Right);
697        let Some(line) = cursor.item() else {
698            return self.text.len();
699        };
700        let wrapped_row = cursor.start().0.0;
701        let row = cursor.start().1.0;
702
703        let line_start = self.text.line_start_offset(row);
704        let local_row = point.row.saturating_sub(wrapped_row);
705        if let Some(range) = line.wrapped_lines.get(local_row) {
706            line_start + (range.start + point.column).min(range.end)
707        } else {
708            // If not found, return the end of the line.
709            line_start + line.len()
710        }
711    }
712
713    pub(crate) fn display_point_to_point(&self, point: WrapDisplayPoint) -> TreeSitterPoint {
714        let offset = self.display_point_to_offset(point);
715        self.text.offset_to_point(offset)
716    }
717
718    pub(crate) fn point_to_display_point(&self, point: TreeSitterPoint) -> WrapDisplayPoint {
719        let offset = self.text.point_to_offset(point);
720        self.offset_to_display_point(offset)
721    }
722}
723
724/// A display point within the soft-wrapped text.
725///
726/// This represents a position in the text after soft-wrapping,
727/// with an additional `local_row` field tracking the wrap line
728/// within the original buffer line.
729#[derive(Debug, Clone, Copy, PartialEq, Eq)]
730pub(crate) struct WrapDisplayPoint {
731    /// The 0-based soft wrapped row index in the text.
732    pub row: usize,
733    /// The 0-based row index in local line (include first line).
734    ///
735    /// This value only valid when return from [`TextWrapper::offset_to_display_point`], otherwise it will be ignored.
736    pub local_row: usize,
737    /// The 0-based column byte index in the display line (with soft wrap).
738    pub column: usize,
739}
740
741impl WrapDisplayPoint {
742    pub(crate) fn new(row: usize, local_row: usize, column: usize) -> Self {
743        Self {
744            row,
745            local_row,
746            column,
747        }
748    }
749}
750
751/// The layout info of a line with soft wrapped lines.
752pub(crate) struct LineLayout {
753    /// Total bytes length of this line.
754    len: usize,
755    /// The soft wrapped lines of this line (Include the first line).
756    pub(crate) wrapped_lines: SmallVec<[InputLine; 1]>,
757    /// Extra left offset applied to continuation wrapped lines, used to reserve the first line's
758    /// indentation when [`WrappingIndent::Same`] is used.
759    pub(crate) wrap_indent: Pixels,
760    pub(crate) longest_width: Pixels,
761    pub(crate) whitespace_indicators: Option<WhitespaceIndicators>,
762    /// Whitespace indicators: (line_index, x_position, is_tab)
763    pub(crate) whitespace_chars: Vec<(usize, Pixels, bool)>,
764    /// Whether any run of this line carries a background color, so [`Self::paint_background`]
765    /// can skip the glyph walk for the common case of a line without highlights.
766    has_background: bool,
767}
768
769impl LineLayout {
770    pub(crate) fn new() -> Self {
771        Self {
772            len: 0,
773            longest_width: px(0.),
774            wrapped_lines: SmallVec::new(),
775            wrap_indent: px(0.),
776            whitespace_chars: Vec::new(),
777            whitespace_indicators: None,
778            has_background: false,
779        }
780    }
781
782    /// Record whether any run of this line carries a background color.
783    pub(crate) fn with_background(mut self, has_background: bool) -> Self {
784        self.has_background = has_background;
785        self
786    }
787
788    /// Set the left offset reserved for continuation wrapped lines.
789    pub(crate) fn wrap_indent(mut self, wrap_indent: Pixels) -> Self {
790        self.wrap_indent = wrap_indent;
791        self
792    }
793
794    /// The pixel indent applied to the given visual line, relative to the line's
795    /// leading text. Only continuation lines (index > 0) are indented.
796    #[inline]
797    fn line_indent(&self, line_index: usize) -> Pixels {
798        if line_index == 0 {
799            px(0.)
800        } else {
801            self.wrap_indent
802        }
803    }
804
805    pub(crate) fn lines(mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) -> Self {
806        self.set_wrapped_lines(wrapped_lines);
807        self
808    }
809
810    pub(crate) fn set_wrapped_lines(&mut self, wrapped_lines: SmallVec<[ShapedLine; 1]>) {
811        self.len = wrapped_lines.iter().map(|l| l.len).sum();
812        let width = wrapped_lines
813            .iter()
814            .map(|l| l.width)
815            .max()
816            .unwrap_or_default();
817        self.longest_width = width;
818        self.wrapped_lines = wrapped_lines.into_iter().map(InputLine::from).collect();
819    }
820
821    pub(crate) fn inline_lines(mut self, lines: SmallVec<[InputLine; 1]>) -> Self {
822        self.len = lines.iter().map(|line| line.len).sum();
823        self.longest_width = lines
824            .iter()
825            .map(|line| line.width)
826            .max()
827            .unwrap_or_default();
828        self.wrapped_lines = lines;
829        self
830    }
831
832    pub(crate) fn with_whitespaces(mut self, indicators: Option<WhitespaceIndicators>) -> Self {
833        self.whitespace_indicators = indicators;
834        let Some(indicators) = self.whitespace_indicators.as_ref() else {
835            return self;
836        };
837
838        let space_indicator_offset = indicators.space.width.half();
839
840        for (line_index, wrapped_line) in self.wrapped_lines.iter().enumerate() {
841            for (relative_offset, c) in wrapped_line.text.char_indices() {
842                if matches!(c, ' ' | '\t') {
843                    let is_tab = c == '\t';
844                    let start_x = wrapped_line.x_for_index(relative_offset);
845                    let end_x = wrapped_line.x_for_index(relative_offset + c.len_utf8());
846                    // Center the indicator in the actual character's space
847                    let x_position = if c == ' ' {
848                        (start_x + end_x).half() - space_indicator_offset
849                    } else {
850                        start_x
851                    };
852
853                    self.whitespace_chars.push((line_index, x_position, is_tab));
854                }
855            }
856        }
857        self
858    }
859
860    #[inline]
861    pub(crate) fn len(&self) -> usize {
862        self.len
863    }
864
865    /// Get the position (x, y) for the given index in this line layout.
866    ///
867    /// - The `offset` is a local byte index in this line layout.
868    /// - When `line_end_affinity` is true, an offset at a soft wrap boundary is placed at
869    ///   the end of the current visual line rather than the start of the next one.
870    /// - The return value is relative to the top-left corner of this line layout, start from (0, 0)
871    pub(crate) fn position_for_index(
872        &self,
873        offset: usize,
874        last_layout: &LastLayout,
875        line_end_affinity: bool,
876    ) -> Option<Point<Pixels>> {
877        let mut acc_len = 0;
878        let mut offset_y = px(0.);
879
880        let x_offset = last_layout.alignment_offset(self.longest_width);
881
882        for (i, line) in self.wrapped_lines.iter().enumerate() {
883            let is_last = i + 1 == self.wrapped_lines.len();
884
885            let matches = if line.len == 0 {
886                // Empty visual lines still own their boundary offset.
887                offset == acc_len
888            } else if is_last || line_end_affinity {
889                // Inclusive: cursor can sit at end of this visual line.
890                offset >= acc_len && offset <= acc_len + line.len
891            } else {
892                // Exclusive: boundary offset belongs to the next visual line.
893                offset >= acc_len && offset < acc_len + line.len
894            };
895
896            if matches {
897                let x = line.x_for_index(offset.saturating_sub(acc_len))
898                    + x_offset
899                    + self.line_indent(i);
900                return Some(point(x, offset_y));
901            }
902
903            // Always advance by actual line length. The last line gets +1 so the
904            // cursor can be placed after the final character.
905            acc_len += if is_last { line.len + 1 } else { line.len };
906            offset_y += last_layout.line_height;
907        }
908
909        None
910    }
911
912    /// Get the closest index for the given x in this line layout.
913    ///
914    /// This ignores y, so it only makes sense for a layout that is known to occupy a single
915    /// visual line. Wrapped layouts must use [`Self::closest_index_for_position`], which also
916    /// reports the caret affinity that a wrap boundary needs.
917    pub(crate) fn closest_index_for_x(&self, x: Pixels, last_layout: &LastLayout) -> usize {
918        let mut acc_len = 0;
919        let x_offset = last_layout.alignment_offset(self.longest_width);
920        let x = x - x_offset;
921
922        for (i, line) in self.wrapped_lines.iter().enumerate() {
923            let line_indent = self.line_indent(i);
924            if x <= line_indent + line.width {
925                return acc_len + line.closest_index_for_x(x - line_indent);
926            }
927            acc_len += line.len;
928        }
929
930        acc_len
931    }
932
933    /// Resolve `pos` to the wrapped sub-line under it.
934    ///
935    /// Returns the sub-line index, the byte offset that sub-line starts at within this line
936    /// layout, and `pos.x` translated into that sub-line's own coordinate space.
937    fn wrapped_line_at(
938        &self,
939        pos: Point<Pixels>,
940        last_layout: &LastLayout,
941    ) -> Option<(usize, usize, Pixels)> {
942        let mut offset = 0;
943        let mut line_top = px(0.);
944        let x_offset = last_layout.alignment_offset(self.longest_width);
945
946        for (i, line) in self.wrapped_lines.iter().enumerate() {
947            let line_bottom = line_top + last_layout.line_height;
948            if pos.y >= line_top && pos.y < line_bottom {
949                return Some((i, offset, pos.x - x_offset - self.line_indent(i)));
950            }
951
952            offset += line.len;
953            line_top = line_bottom;
954        }
955
956        None
957    }
958
959    /// Get the index for the given position (x, y) in this line layout.
960    ///
961    /// The `pos` is relative to the top-left corner of this line layout, start from (0, 0).
962    ///
963    /// Returns a local byte index in this line layout (start from 0) together with the caret
964    /// affinity to use for it: `true` when the index landed on the wrap boundary of a non-final
965    /// sub-line. That boundary offset is shared by the end of one visual line and the start of
966    /// the next, so the affinity is what tells [`Self::position_for_index`] which of the two the
967    /// caret belongs to. Without it a click past the last glyph of a wrapped line would put a
968    /// visible caret on the following line.
969    pub(crate) fn closest_index_for_position(
970        &self,
971        pos: Point<Pixels>,
972        last_layout: &LastLayout,
973    ) -> Option<(usize, bool)> {
974        let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
975        let line = &self.wrapped_lines[i];
976        let ix = line.closest_index_for_x(x);
977        let line_end_affinity = i + 1 < self.wrapped_lines.len() && ix == line.len;
978
979        Some((offset + ix, line_end_affinity))
980    }
981
982    /// How many columns the given position sits past the end of the line under it.
983    ///
984    /// Past the end of a line there is no glyph to hit-test against, so a position out
985    /// there resolves to the line end and loses how far right it really was. The extra
986    /// distance is reported here in whole spaces, letting a columnar selection keep its
987    /// width over a short row. Only the final visual line of a wrapped layout has that
988    /// trailing space; a continuation line ends at a wrap boundary, where the next glyph
989    /// merely lives on the following row.
990    ///
991    /// The `pos` is relative to the top-left corner of this line layout, start from (0, 0).
992    pub(crate) fn columns_past_line_end(
993        &self,
994        pos: Point<Pixels>,
995        last_layout: &LastLayout,
996    ) -> usize {
997        let Some((i, _, x)) = self.wrapped_line_at(pos, last_layout) else {
998            return 0;
999        };
1000
1001        if i + 1 < self.wrapped_lines.len() || last_layout.space_width <= px(0.) {
1002            return 0;
1003        }
1004
1005        let past_end = x - self.wrapped_lines[i].width;
1006        if past_end <= px(0.) {
1007            return 0;
1008        }
1009
1010        (past_end / last_layout.space_width).round() as usize
1011    }
1012
1013    pub(crate) fn index_for_position(
1014        &self,
1015        pos: Point<Pixels>,
1016        last_layout: &LastLayout,
1017    ) -> Option<usize> {
1018        let (i, offset, x) = self.wrapped_line_at(pos, last_layout)?;
1019
1020        Some(offset + self.wrapped_lines[i].index_for_x(x)?)
1021    }
1022
1023    pub(crate) fn size(&self, line_height: Pixels) -> Size<Pixels> {
1024        let width = self
1025            .wrapped_lines
1026            .iter()
1027            .enumerate()
1028            .map(|(ix, line)| line.width + self.line_indent(ix))
1029            .max()
1030            .unwrap_or(self.longest_width);
1031        size(width, self.wrapped_lines.len() * line_height)
1032    }
1033
1034    /// Paint only the glyph background quads of this line.
1035    ///
1036    /// gpui's [`ShapedLine::paint`] does not draw backgrounds, so every line painted with
1037    /// [`Self::paint`] needs this called first, with the same origin and align width.
1038    pub(crate) fn paint_background(
1039        &self,
1040        pos: Point<Pixels>,
1041        line_height: Pixels,
1042        text_align: TextAlign,
1043        align_width: Option<Pixels>,
1044        window: &mut Window,
1045        cx: &mut App,
1046    ) {
1047        // Painting a background walks every glyph and pushes a scene layer, so skip the
1048        // whole pass for lines that have no background color to paint.
1049        if !self.has_background {
1050            return;
1051        }
1052
1053        for (ix, line) in self.wrapped_lines.iter().enumerate() {
1054            _ = line.paint_background(
1055                pos + point(self.line_indent(ix), ix * line_height),
1056                line_height,
1057                text_align,
1058                align_width,
1059                window,
1060                cx,
1061            );
1062        }
1063    }
1064
1065    pub(crate) fn paint(
1066        &self,
1067        pos: Point<Pixels>,
1068        line_height: Pixels,
1069        text_align: TextAlign,
1070        align_width: Option<Pixels>,
1071        window: &mut Window,
1072        cx: &mut App,
1073    ) {
1074        for (ix, line) in self.wrapped_lines.iter().enumerate() {
1075            _ = line.paint(
1076                pos + point(self.line_indent(ix), ix * line_height),
1077                line_height,
1078                text_align,
1079                align_width,
1080                window,
1081                cx,
1082            );
1083        }
1084
1085        // Paint whitespace indicators
1086        if let Some(indicators) = self.whitespace_indicators.as_ref() {
1087            for (line_index, x_position, is_tab) in &self.whitespace_chars {
1088                let invisible = if *is_tab {
1089                    indicators.tab.clone()
1090                } else {
1091                    indicators.space.clone()
1092                };
1093
1094                let origin = point(
1095                    pos.x + *x_position + self.line_indent(*line_index),
1096                    pos.y + *line_index as f32 * line_height,
1097                );
1098
1099                _ = invisible.paint(origin, line_height, text_align, align_width, window, cx);
1100            }
1101        }
1102    }
1103}
1104
1105#[cfg(test)]
1106mod tests {
1107    use super::*;
1108    use std::rc::Rc;
1109
1110    #[cfg(target_os = "linux")]
1111    use gpui::TestAppContext;
1112    use gpui::{Boundary, FontFeatures, FontStyle, FontWeight, px};
1113
1114    #[cfg(target_os = "linux")]
1115    fn shaped_width(text: &str, font: &Font, font_size: Pixels, cx: &App) -> Pixels {
1116        gpui::WindowTextSystem::new(cx.text_system().clone())
1117            .layout_line(
1118                text,
1119                font_size,
1120                &[gpui::TextRun {
1121                    len: text.len(),
1122                    font: font.clone(),
1123                    color: gpui::black(),
1124                    background_color: None,
1125                    underline: None,
1126                    strikethrough: None,
1127                }],
1128                None,
1129            )
1130            .width
1131    }
1132
1133    // Linux exposes its native text engine without creating a desktop window.
1134    // macOS platform creation requires the main thread, and Windows headless
1135    // mode uses NoopTextSystem rather than native shaping.
1136    #[cfg(target_os = "linux")]
1137    fn shaping_test_context() -> TestAppContext {
1138        let platform = gpui_platform::current_platform(true);
1139        TestAppContext::build_with_text_system(
1140            gpui::TestDispatcher::new(0),
1141            None,
1142            platform.text_system(),
1143        )
1144    }
1145
1146    #[cfg(target_os = "linux")]
1147    #[test]
1148    fn production_wrap_keeps_shaped_cjk_latin_boundary_during_edits() {
1149        let cx = shaping_test_context();
1150        cx.update(|cx| {
1151            let font = test_font();
1152            let font_size = px(14.);
1153            let prefix = "abcd的";
1154            let width = shaped_width(prefix, &font, font_size, cx);
1155            assert!(shaped_width("abcd的s", &font, font_size, cx) > width);
1156            let mut wrapper = TextWrapper::new(font.clone(), font_size, Some(width));
1157            let mut previous = Rope::new();
1158
1159            for value in ["abcd的", "abcd的s", "abcd的ss", "abcd的s", "abcd的"] {
1160                let text = Rope::from(value);
1161                let start = previous.len().min(text.len());
1162                let inserted = Rope::from(text.slice(start..).to_string());
1163                wrapper.update(&text, &(start..previous.len()), &inserted, cx);
1164                let expected = if value.ends_with('s') {
1165                    vec![0..prefix.len(), prefix.len()..value.len()]
1166                } else {
1167                    vec![0..prefix.len()]
1168                };
1169                assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), expected);
1170                for range in &wrapper.line(0).unwrap().wrapped_lines {
1171                    assert!(shaped_width(&value[range.clone()], &font, font_size, cx) <= width);
1172                }
1173                assert_eq!(
1174                    wrapper.offset_to_display_point(value.len()).row,
1175                    usize::from(value.ends_with('s'))
1176                );
1177                previous = text;
1178            }
1179        });
1180    }
1181
1182    #[cfg(target_os = "linux")]
1183    #[test]
1184    fn production_wrap_preserves_words_and_complete_graphemes() {
1185        let cx = shaping_test_context();
1186        cx.update(|cx| {
1187            let font = test_font();
1188            let font_size = px(14.);
1189            for (value, prefix) in [("hello world", "hello "), ("a👩‍💻b", "a👩‍💻")] {
1190                // Either complete row must fit even when the native font makes
1191                // the second word wider than the first word and its space.
1192                let width = shaped_width(prefix, &font, font_size, cx).max(shaped_width(
1193                    &value[prefix.len()..],
1194                    &font,
1195                    font_size,
1196                    cx,
1197                ));
1198                assert!(shaped_width(value, &font, font_size, cx) > width);
1199                let text = Rope::from(value);
1200                let mut wrapper = TextWrapper::new(font.clone(), font_size, Some(width));
1201                wrapper.update(&text, &(0..0), &text, cx);
1202                assert_eq!(
1203                    wrapper.line(0).unwrap().wrapped_lines.as_slice(),
1204                    [0..prefix.len(), prefix.len()..value.len()]
1205                );
1206                for range in &wrapper.line(0).unwrap().wrapped_lines {
1207                    assert!(shaped_width(&value[range.clone()], &font, font_size, cx) <= width);
1208                }
1209            }
1210        });
1211    }
1212
1213    #[test]
1214    fn measured_wrap_keeps_cjk_latin_boundary_stable_during_edits() {
1215        let measure = |text: &str| {
1216            px(text
1217                .chars()
1218                .map(|c| if c.is_ascii() { 1. } else { 2. })
1219                .sum())
1220        };
1221        let mut wrapper = TextWrapper::new(gpui::font("Arial"), px(14.), Some(px(6.)));
1222        let mut previous = Rope::new();
1223        for value in ["abcd的", "abcd的s", "abcd的ss", "abcd的s", "abcd的"] {
1224            let text = Rope::from(value);
1225            let start = previous.len().min(text.len());
1226            let inserted = Rope::from(text.slice(start..).to_string());
1227            wrapper._update(
1228                &text,
1229                &(start..previous.len()),
1230                &inserted,
1231                &mut |line, width, _| {
1232                    measured_wrap_boundaries(line, width, WrappingIndent::None, measure)
1233                },
1234            );
1235            let expected = if value.ends_with('s') {
1236                vec![0..7, 7..value.len()]
1237            } else {
1238                vec![0..7]
1239            };
1240            assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), expected);
1241            let cursor = wrapper.offset_to_display_point(value.len());
1242            assert_eq!(cursor.row, usize::from(value.ends_with('s')));
1243            previous = text;
1244        }
1245    }
1246
1247    #[test]
1248    fn measured_wrap_preserves_words_graphemes_and_indentation() {
1249        let wrap = |text: &str, width, indent| {
1250            measured_wrap_boundaries(text, px(width), indent, |s| {
1251                px(s.graphemes(true).count() as f32)
1252            })
1253            .into_iter()
1254            .map(|b| b.ix)
1255            .collect::<Vec<_>>()
1256        };
1257        assert_eq!(wrap("hello world", 8., WrappingIndent::None), vec![6]);
1258        assert_eq!(wrap("a👩‍💻b", 2., WrappingIndent::None), vec!["a👩‍💻".len()]);
1259        assert_eq!(wrap("  abcdefgh", 5., WrappingIndent::Same), vec![5, 8]);
1260        assert_eq!(wrap("  abcdefgh", 5., WrappingIndent::None), vec![2, 7]);
1261        assert!(wrap("", 0., WrappingIndent::None).is_empty());
1262        assert_eq!(wrap("abc", 0., WrappingIndent::None), vec![1, 2]);
1263        // Closing punctuation stays with the preceding Chinese character.
1264        assert_eq!(wrap("你好,世界", 2., WrappingIndent::None), vec![3, 9]);
1265    }
1266
1267    #[test]
1268    fn test_update() {
1269        let font = gpui::Font {
1270            family: "Arial".into(),
1271            weight: FontWeight::default(),
1272            style: FontStyle::Normal,
1273            features: FontFeatures::default(),
1274            fallbacks: None,
1275        };
1276
1277        let mut wrapper = TextWrapper::new(font, px(14.), None);
1278        let mut text = Rope::from(
1279            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1280        );
1281
1282        fn fake_wrap_line(_line: &str, _wrap_width: Pixels, _: usize) -> Vec<Boundary> {
1283            vec![]
1284        }
1285
1286        #[track_caller]
1287        fn assert_wrapper_lines(text: &Rope, wrapper: &TextWrapper, expected_lines: &[&[&str]]) {
1288            let mut actual_lines = vec![];
1289            let mut offset = 0;
1290            for line in wrapper.iter_lines() {
1291                actual_lines.push(
1292                    line.wrapped_lines
1293                        .iter()
1294                        .map(|range| text.slice(offset + range.start..offset + range.end))
1295                        .collect::<Vec<_>>(),
1296                );
1297                // +1 \n
1298                offset += line.len() + 1;
1299            }
1300            assert_eq!(actual_lines, expected_lines);
1301        }
1302
1303        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1304        assert_eq!(wrapper.lines_count(), 4);
1305        assert_wrapper_lines(
1306            &text,
1307            &wrapper,
1308            &[
1309                &["Hello, 世界!\r"],
1310                &["This is second line."],
1311                &["This is third line."],
1312                &["这里是第 4 行。"],
1313            ],
1314        );
1315
1316        // Add a new text to end
1317        let range = text.len()..text.len();
1318        let new_text = "New text";
1319        text.replace(range.clone(), new_text);
1320        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1321        assert_eq!(
1322            text.to_string(),
1323            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
1324        );
1325        assert_eq!(wrapper.lines_count(), 4);
1326        assert_eq!(wrapper.lines_count(), 4);
1327        assert_wrapper_lines(
1328            &text,
1329            &wrapper,
1330            &[
1331                &["Hello, 世界!\r"],
1332                &["This is second line."],
1333                &["This is third line."],
1334                &["这里是第 4 行。New text"],
1335            ],
1336        );
1337
1338        // Replace first line `Hello` to `AAA`
1339        let range = 0..5;
1340        let new_text = "AAA";
1341        text.replace(range.clone(), new_text);
1342        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1343        assert_eq!(
1344            text.to_string(),
1345            "AAA, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。New text"
1346        );
1347        assert_eq!(wrapper.lines_count(), 4);
1348        assert_wrapper_lines(
1349            &text,
1350            &wrapper,
1351            &[
1352                &["AAA, 世界!\r"],
1353                &["This is second line."],
1354                &["This is third line."],
1355                &["这里是第 4 行。New text"],
1356            ],
1357        );
1358
1359        // Remove the second line
1360        let start_offset = text.line_start_offset(1);
1361        let end_offset = text.line_end_offset(1);
1362        let range = start_offset..end_offset + 1;
1363        text.replace(range.clone(), "");
1364        wrapper._update(&text, &range, &Rope::from(""), &mut fake_wrap_line);
1365        assert_eq!(
1366            text.to_string(),
1367            "AAA, 世界!\r\nThis is third line.\n这里是第 4 行。New text"
1368        );
1369        assert_eq!(wrapper.lines_count(), 3);
1370        assert_wrapper_lines(
1371            &text,
1372            &wrapper,
1373            &[
1374                &["AAA, 世界!\r"],
1375                &["This is third line."],
1376                &["这里是第 4 行。New text"],
1377            ],
1378        );
1379
1380        // Replace the first 2 lines to "This is a new line."
1381        let range = text.line_start_offset(0)..text.line_end_offset(1) + 1;
1382        let new_text = "This is a new line.\nThis is new line 2.\n";
1383        text.replace(range.clone(), new_text);
1384        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1385        assert_eq!(
1386            text.to_string(),
1387            "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text"
1388        );
1389        assert_eq!(wrapper.lines_count(), 3);
1390        assert_wrapper_lines(
1391            &text,
1392            &wrapper,
1393            &[
1394                &["This is a new line."],
1395                &["This is new line 2."],
1396                &["这里是第 4 行。New text"],
1397            ],
1398        );
1399
1400        // Add a new line at the end
1401        let range = text.len()..text.len();
1402        let new_text = "\nThis is a new line at the end.";
1403        text.replace(range.clone(), new_text);
1404        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1405        assert_eq!(
1406            text.to_string(),
1407            "This is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end."
1408        );
1409        assert_eq!(wrapper.lines_count(), 4);
1410        assert_wrapper_lines(
1411            &text,
1412            &wrapper,
1413            &[
1414                &["This is a new line."],
1415                &["This is new line 2."],
1416                &["这里是第 4 行。New text"],
1417                &["This is a new line at the end."],
1418            ],
1419        );
1420
1421        // Add a new line at the beginning
1422        let range = 0..0;
1423        let new_text = "This is a new line at the beginning.\n";
1424        text.replace(range.clone(), new_text);
1425        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1426        assert_eq!(
1427            text.to_string(),
1428            "This is a new line at the beginning.\nThis is a new line.\nThis is new line 2.\n这里是第 4 行。New text\nThis is a new line at the end."
1429        );
1430        assert_eq!(wrapper.lines_count(), 5);
1431        assert_wrapper_lines(
1432            &text,
1433            &wrapper,
1434            &[
1435                &["This is a new line at the beginning."],
1436                &["This is a new line."],
1437                &["This is new line 2."],
1438                &["这里是第 4 行。New text"],
1439                &["This is a new line at the end."],
1440            ],
1441        );
1442
1443        // Remove all to at least one line in `lines`.
1444        let range = 0..text.len();
1445        let new_text = "";
1446        text.replace(range.clone(), new_text);
1447        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1448        assert_eq!(text.to_string(), "");
1449        assert_eq!(wrapper.lines_count(), 1);
1450        assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1451
1452        // Test update_all
1453        let range = 0..text.len();
1454        let new_text = "This is a full text.\nThis is a second line.";
1455        text.replace(range.clone(), new_text);
1456        wrapper._update(&text, &range, &text, &mut fake_wrap_line);
1457        assert_eq!(
1458            text.to_string(),
1459            "This is a full text.\nThis is a second line."
1460        );
1461        assert_eq!(wrapper.lines_count(), 2);
1462    }
1463
1464    fn test_font() -> gpui::Font {
1465        gpui::Font {
1466            family: "Arial".into(),
1467            weight: FontWeight::default(),
1468            style: FontStyle::Normal,
1469            features: FontFeatures::default(),
1470            fallbacks: None,
1471        }
1472    }
1473
1474    /// The longest-row summary stays exact when the previously-longest line is shrunk.
1475    #[test]
1476    fn test_longest_row_after_shrink() {
1477        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1478        let mut text = Rope::from("aa\nthis is the longest line\nbb");
1479        wrapper._update(&text, &(0..text.len()), &text, &mut |_, _, _| vec![]);
1480        assert_eq!(wrapper.longest_row(), 1);
1481
1482        // Shrink line 1 so line 2-equivalent isn't longest.
1483        // Make line 0 the longest now.
1484        let start = text.line_start_offset(0);
1485        let end = text.line_end_offset(0);
1486        let range = start..end;
1487        let new_text = "a very very long first line now";
1488        text.replace(range.clone(), new_text);
1489        wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _, _| vec![]);
1490        assert_eq!(wrapper.longest_row(), 0);
1491    }
1492
1493    /// Editing the last line and deleting everything must keep the tree consistent.
1494    #[test]
1495    fn test_edit_last_line_and_full_delete() {
1496        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1497        let mut text = Rope::from("one\ntwo\nthree");
1498        wrapper._update(&text, &(0..text.len()), &text, &mut |_, _, _| vec![]);
1499        assert_eq!(wrapper.lines_count(), 3);
1500
1501        // Replace the last line only.
1502        let start = text.line_start_offset(2);
1503        let range = start..text.len();
1504        let new_text = "THREE EDITED";
1505        text.replace(range.clone(), new_text);
1506        wrapper._update(&text, &range, &Rope::from(new_text), &mut |_, _, _| vec![]);
1507        assert_eq!(wrapper.lines_count(), 3);
1508        assert_eq!(wrapper.line(2).unwrap().len(), "THREE EDITED".len());
1509
1510        // Delete everything.
1511        let range = 0..text.len();
1512        text.replace(range.clone(), "");
1513        wrapper._update(&text, &range, &Rope::from(""), &mut |_, _, _| vec![]);
1514        assert_eq!(wrapper.lines_count(), 1);
1515        assert_eq!(wrapper.len(), 1);
1516        assert_eq!(wrapper.line(0).unwrap().wrapped_lines.as_slice(), [0..0]);
1517    }
1518
1519    #[test]
1520    fn test_wrap_row_buffer_line_boundaries() {
1521        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1522        wrapper.text = Rope::from("aa\nbbbb\nc");
1523        wrapper.lines = SumTree::from_iter(
1524            vec![
1525                LineItem {
1526                    len: 2,
1527                    indent: 0,
1528                    wrapped_lines: smallvec::smallvec![0..2],
1529                },
1530                LineItem {
1531                    len: 4,
1532                    indent: 0,
1533                    wrapped_lines: smallvec::smallvec![0..2, 2..4],
1534                },
1535                LineItem {
1536                    len: 1,
1537                    indent: 0,
1538                    wrapped_lines: smallvec::smallvec![0..1],
1539                },
1540            ],
1541            &(),
1542        );
1543
1544        assert_eq!(wrapper.lines_count(), 3);
1545        assert_eq!(wrapper.len(), 4);
1546
1547        assert_eq!(wrapper.buffer_line_to_first_wrap_row(0), 0);
1548        assert_eq!(wrapper.buffer_line_to_first_wrap_row(1), 1);
1549        assert_eq!(wrapper.buffer_line_to_first_wrap_row(2), 3);
1550        assert_eq!(wrapper.buffer_line_to_first_wrap_row(3), 4);
1551
1552        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1553        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1554        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1555        assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..4);
1556
1557        assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1558        assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1559        assert_eq!(wrapper.wrap_row_to_buffer_line(2), 1);
1560        assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1561        assert_eq!(wrapper.wrap_row_to_buffer_line(4), 2);
1562    }
1563
1564    #[test]
1565    fn test_wrap_row_queries_after_incremental_splice() {
1566        let mut wrapper = TextWrapper::new(test_font(), px(14.), Some(px(10.)));
1567        let mut text = Rope::from("aa\nbbbb\nc");
1568        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1569            if line.len() > 2 {
1570                vec![Boundary {
1571                    ix: 2,
1572                    next_indent: 0,
1573                }]
1574            } else {
1575                vec![]
1576            }
1577        };
1578
1579        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1580        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1581        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..3);
1582        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 3..4);
1583
1584        let range = text.line_start_offset(1)..text.line_end_offset(1);
1585        let new_text = "dd\neeee";
1586        text.replace(range.clone(), new_text);
1587        wrapper._update(&text, &range, &Rope::from(new_text), &mut fake_wrap_line);
1588
1589        assert_eq!(wrapper.lines_count(), 4);
1590        assert_eq!(wrapper.len(), 5);
1591        assert_eq!(wrapper.buffer_line_to_wrap_row_range(0), 0..1);
1592        assert_eq!(wrapper.buffer_line_to_wrap_row_range(1), 1..2);
1593        assert_eq!(wrapper.buffer_line_to_wrap_row_range(2), 2..4);
1594        assert_eq!(wrapper.buffer_line_to_wrap_row_range(3), 4..5);
1595        assert_eq!(wrapper.wrap_row_to_buffer_line(0), 0);
1596        assert_eq!(wrapper.wrap_row_to_buffer_line(1), 1);
1597        assert_eq!(wrapper.wrap_row_to_buffer_line(2), 2);
1598        assert_eq!(wrapper.wrap_row_to_buffer_line(3), 2);
1599        assert_eq!(wrapper.wrap_row_to_buffer_line(4), 3);
1600    }
1601
1602    #[test]
1603    fn test_line_layout() {
1604        let mut line_layout = LineLayout::new();
1605
1606        let line1 = ShapedLine::default().with_len(100);
1607        let line2 = ShapedLine::default().with_len(50);
1608        let wrapped_lines = smallvec::smallvec![line1, line2];
1609        line_layout.set_wrapped_lines(wrapped_lines);
1610        assert_eq!(line_layout.len(), 150);
1611        assert_eq!(line_layout.wrapped_lines.len(), 2);
1612    }
1613
1614    /// A layout context whose only load-bearing field is the line height.
1615    fn test_last_layout(line_height: Pixels) -> LastLayout {
1616        LastLayout {
1617            visible_range: 0..1,
1618            visible_buffer_lines: vec![0],
1619            visible_line_byte_offsets: vec![0],
1620            visible_top: px(0.),
1621            visible_range_offset: 0..0,
1622            lines: Rc::new(vec![]),
1623            line_height,
1624            wrap_width: None,
1625            wrapping_indent: WrappingIndent::default(),
1626            line_number_width: px(0.),
1627            space_width: px(0.),
1628            cursor_bounds: None,
1629            text_align: TextAlign::Left,
1630            content_width: px(0.),
1631        }
1632    }
1633
1634    #[test]
1635    fn test_position_for_index_prefers_first_leading_empty_visual_line() {
1636        let mut line_layout = LineLayout::new();
1637        line_layout.set_wrapped_lines(smallvec::smallvec![
1638            ShapedLine::default(),
1639            ShapedLine::default(),
1640            ShapedLine::default().with_len(3),
1641        ]);
1642
1643        assert_eq!(
1644            line_layout.position_for_index(0, &test_last_layout(px(20.)), false),
1645            Some(point(px(0.), px(0.)))
1646        );
1647    }
1648
1649    #[test]
1650    fn clicking_past_a_wrapped_row_keeps_the_caret_on_that_row() {
1651        // One buffer line wrapped into two visual rows, splitting at byte 10.
1652        let mut line_layout = LineLayout::new();
1653        line_layout.set_wrapped_lines(smallvec::smallvec![
1654            ShapedLine::default().with_len(10),
1655            ShapedLine::default().with_len(5),
1656        ]);
1657        let last_layout = test_last_layout(px(20.));
1658
1659        // Clicking past the last glyph of the first row resolves to the wrap boundary, which is
1660        // also the first offset of the second row -- hence the affinity.
1661        let (ix, line_end_affinity) = line_layout
1662            .closest_index_for_position(point(px(999.), px(5.)), &last_layout)
1663            .unwrap();
1664        assert_eq!(ix, 10);
1665        assert!(line_end_affinity);
1666
1667        // Carrying that affinity is what keeps the caret on the row that was clicked; dropping it
1668        // is the bug -- the caret shows up one row below the pointer.
1669        assert_eq!(
1670            line_layout
1671                .position_for_index(ix, &last_layout, line_end_affinity)
1672                .map(|pos| pos.y),
1673            Some(px(0.))
1674        );
1675        assert_eq!(
1676            line_layout
1677                .position_for_index(ix, &last_layout, false)
1678                .map(|pos| pos.y),
1679            Some(px(20.))
1680        );
1681
1682        // The final row owns the end of the line outright, so there is nothing to disambiguate.
1683        let (ix, line_end_affinity) = line_layout
1684            .closest_index_for_position(point(px(999.), px(25.)), &last_layout)
1685            .unwrap();
1686        assert_eq!(ix, 15);
1687        assert!(!line_end_affinity);
1688    }
1689
1690    #[test]
1691    fn a_wrap_boundary_offset_resolves_to_the_row_the_caret_is_drawn_on() {
1692        let mut wrapper = TextWrapper::new(test_font(), px(14.), None);
1693        wrapper.text = Rope::from("first line\nthis one wraps");
1694        wrapper.lines = SumTree::from_iter(
1695            vec![
1696                LineItem {
1697                    len: Rope::from("first line").len(),
1698                    indent: 0,
1699                    wrapped_lines: smallvec::smallvec![0..10],
1700                },
1701                LineItem {
1702                    len: Rope::from("this one wraps").len(),
1703                    indent: 0,
1704                    wrapped_lines: smallvec::smallvec![0..9, 9..14],
1705                },
1706            ],
1707            &(),
1708        );
1709
1710        // Offset 20 is the wrap boundary of the second buffer line: 11 (line start) + 9.
1711        // It is the last offset of wrap row 1 and the first of wrap row 2 at the same time, so
1712        // only the affinity can say which row a vertical move should step away from.
1713        assert_eq!(
1714            wrapper.offset_to_display_point_with_affinity(20, true),
1715            WrapDisplayPoint::new(1, 0, 9)
1716        );
1717        assert_eq!(
1718            wrapper.offset_to_display_point_with_affinity(20, false),
1719            WrapDisplayPoint::new(2, 1, 0)
1720        );
1721        assert_eq!(
1722            wrapper.offset_to_display_point(20),
1723            wrapper.offset_to_display_point_with_affinity(20, false)
1724        );
1725
1726        // An offset that is not on a boundary is unaffected either way.
1727        for line_end_affinity in [false, true] {
1728            assert_eq!(
1729                wrapper.offset_to_display_point_with_affinity(23, line_end_affinity),
1730                WrapDisplayPoint::new(2, 1, 3)
1731            );
1732        }
1733    }
1734
1735    #[test]
1736    fn test_offset_to_display_point() {
1737        let font = gpui::Font {
1738            family: "Arial".into(),
1739            weight: FontWeight::default(),
1740            style: FontStyle::Normal,
1741            features: FontFeatures::default(),
1742            fallbacks: None,
1743        };
1744
1745        let mut wrapper = TextWrapper::new(font, px(14.), None);
1746        wrapper.text = Rope::from(
1747            "Hello, 世界!\r\nThis is second line.\nThis is third line.\n这里是第 4 行。",
1748        );
1749        wrapper.lines = SumTree::from_iter(
1750            vec![
1751                // range: 0..15
1752                LineItem {
1753                    len: Rope::from("Hello, 世界!\r").len(),
1754                    indent: 0,
1755                    wrapped_lines: smallvec::smallvec![0..15],
1756                },
1757                // range: 16..36
1758                LineItem {
1759                    len: Rope::from("This is second line.\n").len(),
1760                    indent: 0,
1761                    wrapped_lines: smallvec::smallvec![0..10, 10..20],
1762                },
1763                // range: 37..56
1764                LineItem {
1765                    len: Rope::from("This is third line.\n").len(),
1766                    indent: 0,
1767                    wrapped_lines: smallvec::smallvec![0..9, 9..15, 15..20],
1768                },
1769                // range: 57..79
1770                LineItem {
1771                    len: Rope::from("这里是第 4 行。").len(),
1772                    indent: 0,
1773                    wrapped_lines: smallvec::smallvec![0..22],
1774                },
1775            ],
1776            &(),
1777        );
1778
1779        assert_eq!(
1780            wrapper.offset_to_display_point(12),
1781            WrapDisplayPoint::new(0, 0, 12)
1782        );
1783        assert_eq!(
1784            wrapper.offset_to_display_point(15),
1785            WrapDisplayPoint::new(0, 0, 15)
1786        );
1787
1788        assert_eq!(
1789            wrapper.offset_to_display_point(16),
1790            WrapDisplayPoint::new(1, 0, 0)
1791        );
1792        assert_eq!(
1793            wrapper.offset_to_display_point(21),
1794            WrapDisplayPoint::new(1, 0, 5)
1795        );
1796        assert_eq!(
1797            wrapper.offset_to_display_point(27),
1798            WrapDisplayPoint::new(2, 1, 1)
1799        );
1800        assert_eq!(
1801            wrapper.offset_to_display_point(37),
1802            WrapDisplayPoint::new(3, 0, 0)
1803        );
1804        assert_eq!(
1805            wrapper.offset_to_display_point(54),
1806            WrapDisplayPoint::new(5, 2, 2)
1807        );
1808        assert_eq!(
1809            wrapper.offset_to_display_point(59),
1810            WrapDisplayPoint::new(6, 0, 2)
1811        );
1812
1813        assert_eq!(
1814            wrapper.display_point_to_offset(WrapDisplayPoint::new(6, 0, 2)),
1815            59
1816        );
1817        assert_eq!(
1818            wrapper.display_point_to_offset(WrapDisplayPoint::new(5, 2, 2)),
1819            54
1820        );
1821        assert_eq!(
1822            wrapper.display_point_to_offset(WrapDisplayPoint::new(3, 0, 0)),
1823            37
1824        );
1825        assert_eq!(
1826            wrapper.display_point_to_offset(WrapDisplayPoint::new(2, 1, 1)),
1827            27
1828        );
1829        assert_eq!(
1830            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 5)),
1831            21
1832        );
1833        assert_eq!(
1834            wrapper.display_point_to_offset(WrapDisplayPoint::new(1, 0, 0)),
1835            16
1836        );
1837        assert_eq!(
1838            wrapper.display_point_to_offset(WrapDisplayPoint::new(0, 0, 15)),
1839            15
1840        );
1841    }
1842
1843    #[test]
1844    fn test_wrapping_indent_same_keeps_indent_reserved() {
1845        let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1846        wrapper.wrapping_indent = WrappingIndent::Same;
1847        let text = Rope::from("  abcdefghijklmnopqrstuv");
1848        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1849            if line.starts_with(' ') {
1850                vec![Boundary {
1851                    ix: 5,
1852                    next_indent: 2,
1853                }]
1854            } else {
1855                let mut boundaries = vec![];
1856                let mut i = 8;
1857                while i < line.len() {
1858                    boundaries.push(Boundary {
1859                        ix: i,
1860                        next_indent: 0,
1861                    });
1862                    i += 8;
1863                }
1864                boundaries
1865            }
1866        };
1867
1868        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1869
1870        let line = wrapper.line(0).unwrap();
1871        assert_eq!(line.indent, 2);
1872        assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..24]);
1873    }
1874
1875    #[test]
1876    fn test_wrapping_indent_none_continuation_lines_wrapped_at_full_width() {
1877        let mut wrapper = TextWrapper::new(test_font(), px(14.0), Some(px(10.)));
1878        wrapper.wrapping_indent = WrappingIndent::None;
1879        let text = Rope::from("  abcdefghijklmnopqrstuv");
1880        let mut fake_wrap_line = |line: &str, _wrap_width: Pixels, _: usize| {
1881            if line.starts_with(' ') {
1882                vec![Boundary {
1883                    ix: 5,
1884                    next_indent: 2,
1885                }]
1886            } else {
1887                let mut boundaries = vec![];
1888                let mut i = 8;
1889                while i < line.len() {
1890                    boundaries.push(Boundary {
1891                        ix: i,
1892                        next_indent: 0,
1893                    });
1894                    i += 8;
1895                }
1896                boundaries
1897            }
1898        };
1899
1900        wrapper._update(&text, &(0..text.len()), &text, &mut fake_wrap_line);
1901
1902        let line = wrapper.line(0).unwrap();
1903        assert_eq!(line.indent, 0);
1904        assert_eq!(line.wrapped_lines.as_slice(), [0..5, 5..13, 13..21, 21..24]);
1905    }
1906
1907    #[test]
1908    fn test_wrap_indent_offsets_continuation_lines() {
1909        let mut line_layout = LineLayout::new();
1910        line_layout.set_wrapped_lines(smallvec::smallvec![
1911            ShapedLine::default().with_len(5),
1912            ShapedLine::default().with_len(10),
1913        ]);
1914
1915        line_layout = line_layout.wrap_indent(px(20.0));
1916
1917        let last_layout = LastLayout {
1918            visible_range: 0..1,
1919            visible_buffer_lines: vec![0],
1920            visible_line_byte_offsets: vec![0],
1921            visible_top: px(0.),
1922            visible_range_offset: 0..0,
1923            lines: Rc::new(vec![]),
1924            line_height: px(20.0),
1925            wrap_width: Some(px(10.)),
1926            wrapping_indent: WrappingIndent::Same,
1927            line_number_width: px(0.),
1928            space_width: px(0.),
1929            cursor_bounds: None,
1930            text_align: TextAlign::Left,
1931            content_width: px(0.),
1932        };
1933
1934        assert_eq!(
1935            line_layout.position_for_index(0, &last_layout, false),
1936            Some(point(px(0.), px(0.))),
1937        );
1938
1939        assert_eq!(
1940            line_layout.position_for_index(6, &last_layout, false),
1941            Some(point(px(20.), px(20.))),
1942        )
1943    }
1944}