Skip to main content

gpui_base/input/editor/display_map/
text_wrapper.rs

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