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