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