xi_core_lib/
linewrap.rs

1// Copyright 2016 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Compute line wrapping breaks for text.
16
17use std::cmp::Ordering;
18use std::ops::Range;
19
20use xi_rope::breaks::{BreakBuilder, Breaks, BreaksInfo, BreaksMetric};
21use xi_rope::spans::Spans;
22use xi_rope::{Cursor, Interval, LinesMetric, Rope, RopeDelta, RopeInfo};
23use xi_trace::trace_block;
24use xi_unicode::LineBreakLeafIter;
25
26use crate::client::Client;
27use crate::styles::{Style, N_RESERVED_STYLES};
28use crate::width_cache::{CodepointMono, Token, WidthCache, WidthMeasure};
29
30/// The visual width of the buffer for the purpose of word wrapping.
31#[derive(Clone, Copy, Debug, PartialEq)]
32pub(crate) enum WrapWidth {
33    /// No wrapping in effect.
34    None,
35
36    /// Width in bytes (utf-8 code units).
37    ///
38    /// Only works well for ASCII, will probably not be maintained long-term.
39    Bytes(usize),
40
41    /// Width in px units, requiring measurement by the front-end.
42    Width(f64),
43}
44
45impl Default for WrapWidth {
46    fn default() -> Self {
47        WrapWidth::None
48    }
49}
50
51impl WrapWidth {
52    fn differs_in_kind(self, other: WrapWidth) -> bool {
53        use self::WrapWidth::*;
54        match (self, other) {
55            (None, None) | (Bytes(_), Bytes(_)) | (Width(_), Width(_)) => false,
56            _else => true,
57        }
58    }
59}
60
61/// A range to be rewrapped.
62type Task = Interval;
63
64/// Tracks state related to visual lines.
65#[derive(Default)]
66pub(crate) struct Lines {
67    breaks: Breaks,
68    wrap: WrapWidth,
69    /// Aka the 'frontier'; ranges of lines that still need to be wrapped.
70    work: Vec<Task>,
71}
72
73pub(crate) struct VisualLine {
74    pub(crate) interval: Interval,
75    /// The logical line number for this line. Only present when this is the
76    /// first visual line in a logical line.
77    pub(crate) line_num: Option<usize>,
78}
79
80impl VisualLine {
81    fn new<I: Into<Interval>, L: Into<Option<usize>>>(iv: I, line: L) -> Self {
82        VisualLine { interval: iv.into(), line_num: line.into() }
83    }
84}
85
86/// Describes what has changed after a batch of word wrapping; this is used
87/// for minimal invalidation.
88pub(crate) struct InvalLines {
89    pub(crate) start_line: usize,
90    pub(crate) inval_count: usize,
91    pub(crate) new_count: usize,
92}
93
94/// Detailed information about changes to linebreaks, used to generate
95/// invalidation information. The logic behind invalidation is different
96/// depending on whether we're updating breaks after an edit, or continuing
97/// a bulk rewrapping task. This is shared between the two cases, and contains
98/// the information relevant to both of them.
99struct WrapSummary {
100    start_line: usize,
101    /// Total number of invalidated lines; this is meaningless in the after_edit case.
102    inval_count: usize,
103    /// The total number of new (hard + soft) breaks in the wrapped region.
104    new_count: usize,
105    /// The number of new soft breaks.
106    new_soft: usize,
107}
108
109impl Lines {
110    pub(crate) fn set_wrap_width(&mut self, text: &Rope, wrap: WrapWidth) {
111        self.work.clear();
112        self.add_task(0..text.len());
113        if self.breaks.is_empty() || self.wrap.differs_in_kind(wrap) {
114            // we keep breaks while resizing, for more efficient invalidation
115            self.breaks = Breaks::new_no_break(text.len());
116        }
117        self.wrap = wrap;
118    }
119
120    fn add_task<T: Into<Interval>>(&mut self, iv: T) {
121        let iv = iv.into();
122        if iv.is_empty() {
123            return;
124        }
125
126        // keep everything that doesn't intersect. merge things that do.
127        let split_idx = match self.work.iter().position(|&t| !t.intersect(iv).is_empty()) {
128            Some(idx) => idx,
129            None => {
130                self.work.push(iv);
131                return;
132            }
133        };
134
135        let to_update = self.work.split_off(split_idx);
136        let mut new_task = Some(iv);
137
138        for t in &to_update {
139            match new_task.take() {
140                Some(new) if !t.intersect(new).is_empty() => new_task = Some(t.union(new)),
141                Some(new) => {
142                    self.work.push(new);
143                    self.work.push(*t);
144                }
145                None => self.work.push(*t),
146            }
147        }
148        if let Some(end) = new_task.take() {
149            self.work.push(end);
150        }
151    }
152
153    pub(crate) fn is_converged(&self) -> bool {
154        self.wrap == WrapWidth::None || self.work.is_empty()
155    }
156
157    /// Returns `true` if this interval is part of an incomplete task.
158    pub(crate) fn interval_needs_wrap(&self, iv: Interval) -> bool {
159        self.work.iter().any(|t| !t.intersect(iv).is_empty())
160    }
161
162    pub(crate) fn visual_line_of_offset(&self, text: &Rope, offset: usize) -> usize {
163        let mut line = text.line_of_offset(offset);
164        if self.wrap != WrapWidth::None {
165            line += self.breaks.count::<BreaksMetric>(offset)
166        }
167        line
168    }
169
170    /// Returns the byte offset corresponding to the line `line`.
171    pub(crate) fn offset_of_visual_line(&self, text: &Rope, line: usize) -> usize {
172        match self.wrap {
173            WrapWidth::None => {
174                // sanitize input
175                let line = line.min(text.measure::<LinesMetric>() + 1);
176                text.offset_of_line(line)
177            }
178            _ => {
179                let mut cursor = MergedBreaks::new(text, &self.breaks);
180                cursor.offset_of_line(line)
181            }
182        }
183    }
184
185    /// Returns an iterator over [`VisualLine`]s, starting at (and including)
186    /// `start_line`.
187    pub(crate) fn iter_lines<'a>(
188        &'a self,
189        text: &'a Rope,
190        start_line: usize,
191    ) -> impl Iterator<Item = VisualLine> + 'a {
192        let mut cursor = MergedBreaks::new(text, &self.breaks);
193        let offset = cursor.offset_of_line(start_line);
194        let logical_line = text.line_of_offset(offset) + 1;
195        cursor.set_offset(offset);
196        VisualLines { offset, cursor, len: text.len(), logical_line, eof: false }
197    }
198
199    /// Returns the next task, prioritizing the currently visible region.
200    /// Does not modify the task list; this is done after the task runs.
201    fn get_next_task(&self, visible_offset: usize) -> Option<Task> {
202        // the first task t where t.end > visible_offset is the only task
203        // that might contain the visible region.
204        self.work
205            .iter()
206            .find(|t| t.end > visible_offset)
207            .map(|t| Task::new(t.start.max(visible_offset), t.end))
208            .or(self.work.last().cloned())
209    }
210
211    fn update_tasks_after_wrap<T: Into<Interval>>(&mut self, wrapped_iv: T) {
212        if self.work.is_empty() {
213            return;
214        }
215        let wrapped_iv = wrapped_iv.into();
216
217        let mut work = Vec::new();
218        for task in &self.work {
219            if task.is_before(wrapped_iv.start) || task.is_after(wrapped_iv.end) {
220                work.push(*task);
221                continue;
222            }
223            if wrapped_iv.start > task.start {
224                work.push(task.prefix(wrapped_iv));
225            }
226            if wrapped_iv.end < task.end {
227                work.push(task.suffix(wrapped_iv));
228            }
229        }
230        self.work = work;
231    }
232
233    /// Adjust offsets for any tasks after an edit.
234    fn patchup_tasks<T: Into<Interval>>(&mut self, iv: T, new_len: usize) {
235        let iv = iv.into();
236        let mut new_work = Vec::new();
237
238        for task in &self.work {
239            if task.is_before(iv.start) {
240                new_work.push(*task);
241            } else if task.contains(iv.start) {
242                let head = task.prefix(iv);
243                let tail_end = iv.start.max((task.end + new_len).saturating_sub(iv.size()));
244                let tail = Interval::new(iv.start, tail_end);
245                new_work.push(head);
246                new_work.push(tail);
247            } else {
248                // take task - our edit interval, then translate it (- old_size, + new_size)
249                let tail = task.suffix(iv).translate(new_len).translate_neg(iv.size());
250                new_work.push(tail);
251            }
252        }
253        new_work.retain(|iv| !iv.is_empty());
254        self.work.clear();
255        for task in new_work {
256            if let Some(prev) = self.work.last_mut() {
257                if prev.end >= task.start {
258                    *prev = prev.union(task);
259                    continue;
260                }
261            }
262            self.work.push(task);
263        }
264    }
265
266    /// Do a chunk of wrap work, if any exists.
267    pub(crate) fn rewrap_chunk(
268        &mut self,
269        text: &Rope,
270        width_cache: &mut WidthCache,
271        client: &Client,
272        _spans: &Spans<Style>,
273        visible_lines: Range<usize>,
274    ) -> Option<InvalLines> {
275        if self.is_converged() {
276            None
277        } else {
278            let summary = self.do_wrap_task(text, width_cache, client, visible_lines, None);
279            let WrapSummary { start_line, inval_count, new_count, .. } = summary;
280            Some(InvalLines { start_line, inval_count, new_count })
281        }
282    }
283
284    /// Updates breaks after an edit. Returns `InvalLines`, for minimal invalidation,
285    /// when possible.
286    pub(crate) fn after_edit(
287        &mut self,
288        text: &Rope,
289        old_text: &Rope,
290        delta: &RopeDelta,
291        width_cache: &mut WidthCache,
292        client: &Client,
293        visible_lines: Range<usize>,
294    ) -> Option<InvalLines> {
295        let (iv, newlen) = delta.summary();
296
297        let logical_start_line = text.line_of_offset(iv.start);
298        let old_logical_end_line = old_text.line_of_offset(iv.end) + 1;
299        let new_logical_end_line = text.line_of_offset(iv.start + newlen) + 1;
300        let old_logical_end_offset = old_text.offset_of_line(old_logical_end_line);
301        let old_hard_count = old_logical_end_line - logical_start_line;
302        let new_hard_count = new_logical_end_line - logical_start_line;
303
304        //TODO: we should be able to avoid wrapping the whole para in most cases,
305        // but the logic is trickier.
306        let prev_break = text.offset_of_line(logical_start_line);
307        let next_hard_break = text.offset_of_line(new_logical_end_line);
308
309        // count the soft breaks in the region we will rewrap, before we update them.
310        let inval_soft = self.breaks.count::<BreaksMetric>(old_logical_end_offset)
311            - self.breaks.count::<BreaksMetric>(prev_break);
312
313        // update soft breaks, adding empty spans in the edited region
314        let mut builder = BreakBuilder::new();
315        builder.add_no_break(newlen);
316        self.breaks.edit(iv, builder.build());
317        self.patchup_tasks(iv, newlen);
318
319        if self.wrap == WrapWidth::None {
320            return Some(InvalLines {
321                start_line: logical_start_line,
322                inval_count: old_hard_count,
323                new_count: new_hard_count,
324            });
325        }
326
327        let new_task = prev_break..next_hard_break;
328        self.add_task(new_task);
329
330        // possible if the whole buffer is deleted, e.g
331        if !self.work.is_empty() {
332            let summary = self.do_wrap_task(text, width_cache, client, visible_lines, None);
333            let WrapSummary { start_line, new_soft, .. } = summary;
334            // if we haven't converged after this update we can't do minimal invalidation
335            // because we don't have complete knowledge of the new breaks state.
336            if self.is_converged() {
337                let inval_count = old_hard_count + inval_soft;
338                let new_count = new_hard_count + new_soft;
339                Some(InvalLines { start_line, new_count, inval_count })
340            } else {
341                None
342            }
343        } else {
344            None
345        }
346    }
347
348    fn do_wrap_task(
349        &mut self,
350        text: &Rope,
351        width_cache: &mut WidthCache,
352        client: &Client,
353        visible_lines: Range<usize>,
354        max_lines: Option<usize>,
355    ) -> WrapSummary {
356        use self::WrapWidth::*;
357        let _t = trace_block("Lines::do_wrap_task", &["core"]);
358        // 'line' is a poor unit here; could do some fancy Duration thing?
359        const MAX_LINES_PER_BATCH: usize = 500;
360
361        let mut cursor = MergedBreaks::new(text, &self.breaks);
362        let visible_off = cursor.offset_of_line(visible_lines.start);
363        let logical_off = text.offset_of_line(text.line_of_offset(visible_off));
364
365        // task.start is a hard break; task.end is a boundary or EOF.
366        let task = self.get_next_task(logical_off).unwrap();
367        cursor.set_offset(task.start);
368        debug_assert_eq!(cursor.offset, task.start, "task_start must be valid offset");
369
370        let mut ctx = match self.wrap {
371            Bytes(b) => RewrapCtx::new(text, &CodepointMono, b as f64, width_cache, task.start),
372            Width(w) => RewrapCtx::new(text, client, w, width_cache, task.start),
373            None => unreachable!(),
374        };
375
376        let start_line = cursor.cur_line;
377        let max_lines = max_lines.unwrap_or(MAX_LINES_PER_BATCH);
378        // always wrap at least a screen worth of lines (unless we converge earlier)
379        let batch_size = max_lines.max(visible_lines.end - visible_lines.start);
380
381        let mut builder = BreakBuilder::new();
382        let mut lines_wrapped = 0;
383        let mut pos = task.start;
384        let mut old_next_maybe = cursor.next();
385
386        loop {
387            if let Some(new_next) = ctx.wrap_one_line(pos) {
388                while let Some(old_next) = old_next_maybe {
389                    if old_next >= new_next {
390                        break; // just advance old cursor and continue
391                    }
392                    old_next_maybe = cursor.next();
393                }
394
395                let is_hard = cursor.offset == new_next && cursor.is_hard_break();
396                if is_hard {
397                    builder.add_no_break(new_next - pos);
398                } else {
399                    builder.add_break(new_next - pos);
400                }
401                lines_wrapped += 1;
402                pos = new_next;
403                if pos == task.end || (lines_wrapped > batch_size && is_hard) {
404                    break;
405                }
406            } else {
407                // EOF
408                builder.add_no_break(text.len() - pos);
409                break;
410            }
411        }
412
413        let breaks = builder.build();
414        let end = task.start + breaks.len();
415
416        // this is correct *only* when an edit has not occured.
417        let inval_soft =
418            self.breaks.count::<BreaksMetric>(end) - self.breaks.count::<BreaksMetric>(task.start);
419
420        let hard_count = 1 + text.line_of_offset(end) - text.line_of_offset(task.start);
421
422        let inval_count = inval_soft + hard_count;
423        let new_soft = breaks.measure::<BreaksMetric>();
424        let new_count = new_soft + hard_count;
425
426        let iv = Interval::new(task.start, end);
427        self.breaks.edit(iv, breaks);
428        self.update_tasks_after_wrap(iv);
429
430        WrapSummary { start_line, inval_count, new_count, new_soft }
431    }
432
433    pub fn logical_line_range(&self, text: &Rope, line: usize) -> (usize, usize) {
434        let mut cursor = MergedBreaks::new(text, &self.breaks);
435        let offset = cursor.offset_of_line(line);
436        let logical_line = text.line_of_offset(offset);
437        let start_logical_line_offset = text.offset_of_line(logical_line);
438        let end_logical_line_offset = text.offset_of_line(logical_line + 1);
439        (start_logical_line_offset, end_logical_line_offset)
440    }
441
442    #[cfg(test)]
443    fn for_testing(text: &Rope, wrap: WrapWidth) -> Lines {
444        let mut lines = Lines::default();
445        lines.set_wrap_width(text, wrap);
446        lines
447    }
448
449    #[cfg(test)]
450    fn rewrap_all(&mut self, text: &Rope, client: &Client, width_cache: &mut WidthCache) {
451        if !self.is_converged() {
452            self.do_wrap_task(text, width_cache, client, 0..10, Some(usize::max_value()));
453        }
454    }
455}
456
457/// A potential opportunity to insert a break. In this representation, the widths
458/// have been requested (in a batch request) but are not necessarily known until
459/// the request is issued.
460struct PotentialBreak {
461    /// The offset within the text of the end of the word.
462    pos: usize,
463    /// A token referencing the width of the word, to be resolved in the width cache.
464    tok: Token,
465    /// Whether the break is a hard break or a soft break.
466    hard: bool,
467}
468
469/// State for a rewrap in progress
470struct RewrapCtx<'a> {
471    text: &'a Rope,
472    lb_cursor: LineBreakCursor<'a>,
473    lb_cursor_pos: usize,
474    width_cache: &'a mut WidthCache,
475    client: &'a dyn WidthMeasure,
476    pot_breaks: Vec<PotentialBreak>,
477    /// Index within `pot_breaks`
478    pot_break_ix: usize,
479    max_width: f64,
480}
481
482// This constant should be tuned so that the RPC takes about 1ms. Less than that,
483// RPC overhead becomes significant. More than that, interactivity suffers.
484const MAX_POT_BREAKS: usize = 10_000;
485
486impl<'a> RewrapCtx<'a> {
487    fn new(
488        text: &'a Rope,
489        //_style_spans: &Spans<Style>,  client: &'a T,
490        client: &'a dyn WidthMeasure,
491        max_width: f64,
492        width_cache: &'a mut WidthCache,
493        start: usize,
494    ) -> RewrapCtx<'a> {
495        let lb_cursor_pos = start;
496        let lb_cursor = LineBreakCursor::new(text, start);
497        RewrapCtx {
498            text,
499            lb_cursor,
500            lb_cursor_pos,
501            width_cache,
502            client,
503            pot_breaks: Vec::new(),
504            pot_break_ix: 0,
505            max_width,
506        }
507    }
508
509    fn refill_pot_breaks(&mut self) {
510        let mut req = self.width_cache.batch_req();
511
512        self.pot_breaks.clear();
513        self.pot_break_ix = 0;
514        let mut pos = self.lb_cursor_pos;
515        while pos < self.text.len() && self.pot_breaks.len() < MAX_POT_BREAKS {
516            let (next, hard) = self.lb_cursor.next();
517            let word = self.text.slice_to_cow(pos..next);
518            let tok = req.request(N_RESERVED_STYLES, &word);
519            pos = next;
520            self.pot_breaks.push(PotentialBreak { pos, tok, hard });
521        }
522        req.resolve_pending(self.client).unwrap();
523        self.lb_cursor_pos = pos;
524    }
525
526    /// Compute the next break, assuming `start` is a valid break.
527    ///
528    /// Invariant: `start` corresponds to the start of the word referenced by `pot_break_ix`.
529    fn wrap_one_line(&mut self, start: usize) -> Option<usize> {
530        let mut line_width = 0.0;
531        let mut pos = start;
532        while pos < self.text.len() {
533            if self.pot_break_ix >= self.pot_breaks.len() {
534                self.refill_pot_breaks();
535            }
536            let pot_break = &self.pot_breaks[self.pot_break_ix];
537            let width = self.width_cache.resolve(pot_break.tok);
538            if !pot_break.hard {
539                if line_width == 0.0 && width >= self.max_width {
540                    // we don't care about soft breaks at EOF
541                    if pot_break.pos == self.text.len() {
542                        return None;
543                    }
544                    self.pot_break_ix += 1;
545                    return Some(pot_break.pos);
546                }
547                line_width += width;
548                if line_width > self.max_width {
549                    return Some(pos);
550                }
551                self.pot_break_ix += 1;
552                pos = pot_break.pos;
553            } else if line_width != 0. && width + line_width > self.max_width {
554                // if this is a hard break but we would have broken at the previous
555                // pos otherwise, we still break at the previous pos.
556                return Some(pos);
557            } else {
558                self.pot_break_ix += 1;
559                return Some(pot_break.pos);
560            }
561        }
562        None
563    }
564}
565
566struct LineBreakCursor<'a> {
567    inner: Cursor<'a, RopeInfo>,
568    lb_iter: LineBreakLeafIter,
569    last_byte: u8,
570}
571
572impl<'a> LineBreakCursor<'a> {
573    fn new(text: &'a Rope, pos: usize) -> LineBreakCursor<'a> {
574        let inner = Cursor::new(text, pos);
575        let lb_iter = match inner.get_leaf() {
576            Some((s, offset)) => LineBreakLeafIter::new(s.as_str(), offset),
577            _ => LineBreakLeafIter::default(),
578        };
579        LineBreakCursor { inner, lb_iter, last_byte: 0 }
580    }
581
582    // position and whether break is hard; up to caller to stop calling after EOT
583    fn next(&mut self) -> (usize, bool) {
584        let mut leaf = self.inner.get_leaf();
585        loop {
586            match leaf {
587                Some((s, offset)) => {
588                    let (next, hard) = self.lb_iter.next(s.as_str());
589                    if next < s.len() {
590                        return (self.inner.pos() - offset + next, hard);
591                    }
592                    if !s.is_empty() {
593                        self.last_byte = s.as_bytes()[s.len() - 1];
594                    }
595                    leaf = self.inner.next_leaf();
596                }
597                // A little hacky but only reports last break as hard if final newline
598                None => return (self.inner.pos(), self.last_byte == b'\n'),
599            }
600        }
601    }
602}
603
604struct VisualLines<'a> {
605    cursor: MergedBreaks<'a>,
606    offset: usize,
607    /// The current logical line number.
608    logical_line: usize,
609    len: usize,
610    eof: bool,
611}
612
613impl<'a> Iterator for VisualLines<'a> {
614    type Item = VisualLine;
615
616    fn next(&mut self) -> Option<VisualLine> {
617        let line_num = if self.cursor.is_hard_break() { Some(self.logical_line) } else { None };
618        let next_end_bound = match self.cursor.next() {
619            Some(b) => b,
620            None if self.eof => return None,
621            _else => {
622                self.eof = true;
623                self.len
624            }
625        };
626        let result = VisualLine::new(self.offset..next_end_bound, line_num);
627        if self.cursor.is_hard_break() {
628            self.logical_line += 1;
629        }
630        self.offset = next_end_bound;
631        Some(result)
632    }
633}
634
635/// A cursor over both hard and soft breaks. Hard breaks are retrieved from
636/// the rope; the soft breaks are stored independently; this interleaves them.
637///
638/// # Invariants:
639///
640/// `self.offset` is always a valid break in one of the cursors, unless
641/// at 0 or EOF.
642///
643/// `self.offset == self.text.pos().min(self.soft.pos())`.
644struct MergedBreaks<'a> {
645    text: Cursor<'a, RopeInfo>,
646    soft: Cursor<'a, BreaksInfo>,
647    offset: usize,
648    /// Starting from zero, how many calls to `next` to get to `self.offset`?
649    cur_line: usize,
650    total_lines: usize,
651    /// Total length, in base units
652    len: usize,
653}
654
655impl<'a> Iterator for MergedBreaks<'a> {
656    type Item = usize;
657
658    fn next(&mut self) -> Option<usize> {
659        if self.text.pos() == self.offset && !self.at_eof() {
660            // don't iterate past EOF, or we can't get the leaf and check for \n
661            self.text.next::<LinesMetric>();
662        }
663        if self.soft.pos() == self.offset {
664            self.soft.next::<BreaksMetric>();
665        }
666        let prev_off = self.offset;
667        self.offset = self.text.pos().min(self.soft.pos());
668
669        let eof_without_newline = self.offset > 0 && self.at_eof() && self.eof_without_newline();
670        if self.offset == prev_off || eof_without_newline {
671            None
672        } else {
673            self.cur_line += 1;
674            Some(self.offset)
675        }
676    }
677}
678
679// arrived at this by just trying out a bunch of values ¯\_(ツ)_/¯
680/// how far away a line can be before we switch to a binary search
681const MAX_LINEAR_DIST: usize = 20;
682
683impl<'a> MergedBreaks<'a> {
684    fn new(text: &'a Rope, breaks: &'a Breaks) -> Self {
685        debug_assert_eq!(text.len(), breaks.len());
686        let text = Cursor::new(text, 0);
687        let soft = Cursor::new(breaks, 0);
688        let total_lines =
689            text.root().measure::<LinesMetric>() + soft.root().measure::<BreaksMetric>() + 1;
690        let len = text.total_len();
691        MergedBreaks { text, soft, offset: 0, cur_line: 0, total_lines, len }
692    }
693
694    /// Sets the `self.offset` to the first valid break immediately at or preceding `offset`,
695    /// and restores invariants.
696    fn set_offset(&mut self, offset: usize) {
697        self.text.set(offset);
698        self.soft.set(offset);
699        if offset > 0 {
700            if self.text.at_or_prev::<LinesMetric>().is_none() {
701                self.text.set(0);
702            }
703            if self.soft.at_or_prev::<BreaksMetric>().is_none() {
704                self.soft.set(0);
705            }
706        }
707
708        // self.offset should be at the first valid break immediately preceding `offset`, or 0.
709        // the position of the non-break cursor should be > than that of the break cursor, or EOF.
710        match self.text.pos().cmp(&self.soft.pos()) {
711            Ordering::Less => {
712                self.text.next::<LinesMetric>();
713            }
714            Ordering::Greater => {
715                self.soft.next::<BreaksMetric>();
716            }
717            Ordering::Equal => assert!(self.text.pos() == 0),
718        }
719
720        self.offset = self.text.pos().min(self.soft.pos());
721        self.cur_line = merged_line_of_offset(self.text.root(), self.soft.root(), self.offset);
722    }
723
724    fn offset_of_line(&mut self, line: usize) -> usize {
725        match line {
726            0 => 0,
727            l if l >= self.total_lines => self.text.total_len(),
728            l if l == self.cur_line => self.offset,
729            l if l > self.cur_line && l - self.cur_line < MAX_LINEAR_DIST => {
730                self.offset_of_line_linear(l)
731            }
732            other => self.offset_of_line_bsearch(other),
733        }
734    }
735
736    fn offset_of_line_linear(&mut self, line: usize) -> usize {
737        assert!(line > self.cur_line);
738        let dist = line - self.cur_line;
739        self.nth(dist - 1).unwrap_or(self.len)
740    }
741
742    fn offset_of_line_bsearch(&mut self, line: usize) -> usize {
743        let mut range = 0..self.len;
744        loop {
745            let pivot = range.start + (range.end - range.start) / 2;
746            self.set_offset(pivot);
747
748            match self.cur_line {
749                l if l == line => break self.offset,
750                l if l > line => range = range.start..pivot,
751                l if line - l > MAX_LINEAR_DIST => range = pivot..range.end,
752                _else => break self.offset_of_line_linear(line),
753            }
754        }
755    }
756
757    fn is_hard_break(&self) -> bool {
758        self.offset == self.text.pos()
759    }
760
761    fn at_eof(&self) -> bool {
762        self.offset == self.len
763    }
764
765    fn eof_without_newline(&mut self) -> bool {
766        debug_assert!(self.at_eof());
767        self.text.set(self.len);
768        self.text.get_leaf().map(|(l, _)| l.as_bytes().last() != Some(&b'\n')).unwrap()
769    }
770}
771
772fn merged_line_of_offset(text: &Rope, soft: &Breaks, offset: usize) -> usize {
773    text.count::<LinesMetric>(offset) + soft.count::<BreaksMetric>(offset)
774}
775
776#[cfg(test)]
777mod tests {
778    use super::*;
779    use std::borrow::Cow;
780    use std::iter;
781    use xi_rpc::test_utils::DummyPeer;
782
783    fn make_lines(text: &Rope, width: f64) -> Lines {
784        let client = Client::new(Box::new(DummyPeer));
785        let mut width_cache = WidthCache::new();
786        let wrap = WrapWidth::Bytes(width as usize);
787        let mut lines = Lines::for_testing(text, wrap);
788        lines.rewrap_all(text, &client, &mut width_cache);
789        lines
790    }
791
792    fn render_breaks<'a>(text: &'a Rope, lines: &Lines) -> Vec<Cow<'a, str>> {
793        let result = lines.iter_lines(text, 0).map(|l| text.slice_to_cow(l.interval)).collect();
794        result
795    }
796
797    fn debug_breaks<'a>(text: &'a Rope, width: f64) -> Vec<Cow<'a, str>> {
798        let lines = make_lines(text, width);
799        render_breaks(text, &lines)
800    }
801
802    #[test]
803    fn column_breaks_basic() {
804        let text: Rope = "every wordthing should getits own".into();
805        let result = debug_breaks(&text, 8.0);
806        assert_eq!(result, vec!["every ", "wordthing ", "should ", "getits ", "own",]);
807    }
808
809    #[test]
810    fn column_breaks_trailing_newline() {
811        let text: Rope = "every wordthing should getits ow\n".into();
812        let result = debug_breaks(&text, 8.0);
813        assert_eq!(result, vec!["every ", "wordthing ", "should ", "getits ", "ow\n", "",]);
814    }
815
816    #[test]
817    fn soft_before_hard() {
818        let text: Rope = "create abreak between THESE TWO\nwords andbreakcorrectlyhere\nplz".into();
819        let result = debug_breaks(&text, 4.0);
820        assert_eq!(
821            result,
822            vec![
823                "create ",
824                "abreak ",
825                "between ",
826                "THESE ",
827                "TWO\n",
828                "words ",
829                "andbreakcorrectlyhere\n",
830                "plz",
831            ]
832        );
833    }
834
835    #[test]
836    fn column_breaks_hard_soft() {
837        let text: Rope = "so\nevery wordthing should getits own".into();
838        let result = debug_breaks(&text, 4.0);
839        assert_eq!(result, vec!["so\n", "every ", "wordthing ", "should ", "getits ", "own",]);
840    }
841
842    #[test]
843    fn empty_file() {
844        let text: Rope = "".into();
845        let result = debug_breaks(&text, 4.0);
846        assert_eq!(result, vec![""]);
847    }
848
849    #[test]
850    fn dont_break_til_i_tell_you() {
851        let text: Rope = "thisis_longerthan_our_break_width".into();
852        let result = debug_breaks(&text, 12.0);
853        assert_eq!(result, vec!["thisis_longerthan_our_break_width"]);
854    }
855
856    #[test]
857    fn break_now_though() {
858        let text: Rope = "thisis_longerthan_our_break_width hi".into();
859        let result = debug_breaks(&text, 12.0);
860        assert_eq!(result, vec!["thisis_longerthan_our_break_width ", "hi"]);
861    }
862
863    #[test]
864    fn newlines() {
865        let text: Rope = "\n\n".into();
866        let result = debug_breaks(&text, 4.0);
867        assert_eq!(result, vec!["\n", "\n", ""]);
868    }
869
870    #[test]
871    fn newline_eof() {
872        let text: Rope = "hello\n".into();
873        let result = debug_breaks(&text, 4.0);
874        assert_eq!(result, vec!["hello\n", ""]);
875    }
876
877    #[test]
878    fn no_newline_eof() {
879        let text: Rope = "hello".into();
880        let result = debug_breaks(&text, 4.0);
881        assert_eq!(result, vec!["hello"]);
882    }
883
884    #[test]
885    fn merged_offset() {
886        let text: Rope = "a quite\nshort text".into();
887        let mut builder = BreakBuilder::new();
888        builder.add_break(2);
889        builder.add_no_break(text.len() - 2);
890        let breaks = builder.build();
891        assert_eq!(merged_line_of_offset(&text, &breaks, 0), 0);
892        assert_eq!(merged_line_of_offset(&text, &breaks, 1), 0);
893        assert_eq!(merged_line_of_offset(&text, &breaks, 2), 1);
894        assert_eq!(merged_line_of_offset(&text, &breaks, 5), 1);
895        assert_eq!(merged_line_of_offset(&text, &breaks, 5), 1);
896        assert_eq!(merged_line_of_offset(&text, &breaks, 9), 2);
897        assert_eq!(merged_line_of_offset(&text, &breaks, text.len()), 2);
898
899        let text: Rope = "a quite\nshort tex\n".into();
900        // trailing newline increases total count
901        assert_eq!(merged_line_of_offset(&text, &breaks, text.len()), 3);
902    }
903
904    #[test]
905    fn bsearch_equivalence() {
906        let text: Rope =
907            iter::repeat("this is a line with some text in it, which is not unusual\n")
908                .take(1000)
909                .collect::<String>()
910                .into();
911        let lines = make_lines(&text, 30.);
912
913        let mut linear = MergedBreaks::new(&text, &lines.breaks);
914        let mut binary = MergedBreaks::new(&text, &lines.breaks);
915
916        // skip zero because these two impls don't handle edge cases
917        for i in 1..1000 {
918            linear.set_offset(0);
919            binary.set_offset(0);
920            assert_eq!(
921                linear.offset_of_line_linear(i),
922                binary.offset_of_line_bsearch(i),
923                "line {}",
924                i
925            );
926        }
927    }
928
929    #[test]
930    fn merge_cursor_no_breaks() {
931        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
932        // first with no breaks
933        let breaks = Breaks::new_no_break(text.len());
934        let mut cursor = MergedBreaks::new(&text, &breaks);
935        assert_eq!(cursor.offset, 0);
936        assert_eq!(cursor.cur_line, 0);
937        assert_eq!(cursor.len, text.len());
938        assert_eq!(cursor.total_lines, 4);
939        assert!(cursor.is_hard_break());
940
941        assert_eq!(cursor.next(), Some(5));
942        assert_eq!(cursor.cur_line, 1);
943        assert_eq!(cursor.offset, 5);
944        assert_eq!(cursor.text.pos(), 5);
945        assert_eq!(cursor.soft.pos(), text.len());
946        assert!(cursor.is_hard_break());
947
948        assert_eq!(cursor.next(), Some(14));
949        assert_eq!(cursor.cur_line, 2);
950        assert_eq!(cursor.offset, 14);
951        assert_eq!(cursor.text.pos(), 14);
952        assert_eq!(cursor.soft.pos(), text.len());
953        assert!(cursor.is_hard_break());
954
955        assert_eq!(cursor.next(), Some(30));
956        assert_eq!(cursor.next(), None);
957    }
958
959    #[test]
960    fn merge_cursor_breaks() {
961        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
962
963        let mut builder = BreakBuilder::new();
964        builder.add_break(8);
965        builder.add_break(3);
966        builder.add_no_break(text.len() - (8 + 3));
967        let breaks = builder.build();
968
969        let mut cursor = MergedBreaks::new(&text, &breaks);
970        assert_eq!(cursor.offset, 0);
971        assert_eq!(cursor.cur_line, 0);
972        assert_eq!(cursor.len, text.len());
973        assert_eq!(cursor.total_lines, 6);
974
975        assert_eq!(cursor.next(), Some(5));
976        assert_eq!(cursor.cur_line, 1);
977        assert_eq!(cursor.offset, 5);
978        assert_eq!(cursor.text.pos(), 5);
979        assert_eq!(cursor.soft.pos(), 8);
980        assert!(cursor.is_hard_break());
981
982        assert_eq!(cursor.next(), Some(8));
983        assert_eq!(cursor.cur_line, 2);
984        assert_eq!(cursor.offset, 8);
985        assert_eq!(cursor.text.pos(), 14);
986        assert_eq!(cursor.soft.pos(), 8);
987        assert!(!cursor.is_hard_break());
988
989        assert_eq!(cursor.next(), Some(11));
990        assert_eq!(cursor.cur_line, 3);
991        assert_eq!(cursor.offset, 11);
992        assert_eq!(cursor.text.pos(), 14);
993        assert_eq!(cursor.soft.pos(), 11);
994        assert!(!cursor.is_hard_break());
995
996        assert_eq!(cursor.next(), Some(14));
997        assert_eq!(cursor.cur_line, 4);
998        assert_eq!(cursor.offset, 14);
999        assert_eq!(cursor.text.pos(), 14);
1000        assert_eq!(cursor.soft.pos(), text.len());
1001        assert!(cursor.is_hard_break());
1002    }
1003
1004    #[test]
1005    fn set_offset() {
1006        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
1007        let lines = make_lines(&text, 2.);
1008        let mut merged = MergedBreaks::new(&text, &lines.breaks);
1009        assert_eq!(merged.total_lines, 10);
1010
1011        let check_props = |m: &MergedBreaks, line, off, softpos, hardpos| {
1012            assert_eq!(m.cur_line, line);
1013            assert_eq!(m.offset, off);
1014            assert_eq!(m.soft.pos(), softpos);
1015            assert_eq!(m.text.pos(), hardpos);
1016        };
1017        merged.next();
1018        check_props(&merged, 1, 5, 8, 5);
1019        merged.set_offset(0);
1020        check_props(&merged, 0, 0, 0, 0);
1021        merged.set_offset(5);
1022        check_props(&merged, 1, 5, 8, 5);
1023        merged.set_offset(0);
1024        merged.set_offset(6);
1025        check_props(&merged, 1, 5, 8, 5);
1026        merged.set_offset(9);
1027        check_props(&merged, 2, 8, 8, 14);
1028        merged.set_offset(text.len());
1029        check_props(&merged, 9, 33, 33, 37);
1030        merged.set_offset(text.len() - 1);
1031        check_props(&merged, 9, 33, 33, 37);
1032
1033        // but a trailing newline adds a line at EOF
1034        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff ggg\n".into();
1035        let lines = make_lines(&text, 2.);
1036        let mut merged = MergedBreaks::new(&text, &lines.breaks);
1037        assert_eq!(merged.total_lines, 11);
1038        merged.set_offset(text.len());
1039        check_props(&merged, 10, 37, 37, 37);
1040        merged.set_offset(text.len() - 1);
1041        check_props(&merged, 9, 33, 33, 37);
1042    }
1043
1044    #[test]
1045    fn test_break_at_linear_transition() {
1046        // do we handle the break at MAX_LINEAR_DIST correctly?
1047        let text = "a b c d e f g h i j k l m n o p q r s t u v w x ".into();
1048        let lines = make_lines(&text, 1.);
1049
1050        for offset in 0..text.len() {
1051            let line = lines.visual_line_of_offset(&text, offset);
1052            let line_offset = lines.offset_of_visual_line(&text, line);
1053            assert!(line_offset <= offset, "{} <= {} L{} O{}", line_offset, offset, line, offset);
1054        }
1055    }
1056
1057    #[test]
1058    fn expected_soft_breaks() {
1059        let text = "a b c d ".into();
1060        let mut text_cursor = Cursor::new(&text, text.len());
1061        assert!(!text_cursor.is_boundary::<LinesMetric>());
1062
1063        let Lines { breaks, .. } = make_lines(&text, 1.);
1064        let mut cursor = Cursor::new(&breaks, 0);
1065
1066        cursor.set(2);
1067        assert!(cursor.is_boundary::<BreaksMetric>());
1068        cursor.set(4);
1069        assert!(cursor.is_boundary::<BreaksMetric>());
1070        cursor.set(6);
1071        assert!(cursor.is_boundary::<BreaksMetric>());
1072        cursor.set(8);
1073        assert!(!cursor.is_boundary::<BreaksMetric>());
1074
1075        cursor.set(0);
1076        let breaks = cursor.iter::<BreaksMetric>().collect::<Vec<_>>();
1077        assert_eq!(breaks, vec![2, 4, 6]);
1078    }
1079
1080    #[test]
1081    fn expected_soft_with_hard() {
1082        let text: Rope = "aa\nbb cc\ncc dd ee ff\ngggg".into();
1083        let Lines { breaks, .. } = make_lines(&text, 2.);
1084        let mut cursor = Cursor::new(&breaks, 0);
1085        let breaks = cursor.iter::<BreaksMetric>().collect::<Vec<_>>();
1086        assert_eq!(breaks, vec![6, 12, 15, 18]);
1087    }
1088
1089    #[test]
1090    fn offset_to_line() {
1091        let text = "a b c d ".into();
1092        let lines = make_lines(&text, 1.);
1093        let cursor = MergedBreaks::new(&text, &lines.breaks);
1094        assert_eq!(cursor.total_lines, 4);
1095
1096        assert_eq!(lines.visual_line_of_offset(&text, 0), 0);
1097        assert_eq!(lines.visual_line_of_offset(&text, 1), 0);
1098        assert_eq!(lines.visual_line_of_offset(&text, 2), 1);
1099        assert_eq!(lines.visual_line_of_offset(&text, 3), 1);
1100        assert_eq!(lines.visual_line_of_offset(&text, 4), 2);
1101        assert_eq!(lines.visual_line_of_offset(&text, 5), 2);
1102        assert_eq!(lines.visual_line_of_offset(&text, 6), 3);
1103        assert_eq!(lines.visual_line_of_offset(&text, 7), 3);
1104
1105        assert_eq!(lines.offset_of_visual_line(&text, 0), 0);
1106        assert_eq!(lines.offset_of_visual_line(&text, 1), 2);
1107        assert_eq!(lines.offset_of_visual_line(&text, 2), 4);
1108        assert_eq!(lines.offset_of_visual_line(&text, 3), 6);
1109        assert_eq!(lines.offset_of_visual_line(&text, 10), 8);
1110
1111        for offset in 0..text.len() {
1112            let line = lines.visual_line_of_offset(&text, offset);
1113            let line_offset = lines.offset_of_visual_line(&text, line);
1114            assert!(line_offset <= offset, "{} <= {} L{} O{}", line_offset, offset, line, offset);
1115        }
1116    }
1117
1118    #[test]
1119    fn iter_lines() {
1120        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
1121        let lines = make_lines(&text, 2.);
1122        let r: Vec<_> =
1123            lines.iter_lines(&text, 0).take(2).map(|l| text.slice_to_cow(l.interval)).collect();
1124        assert_eq!(r, vec!["aaaa\n", "bb "]);
1125
1126        let r: Vec<_> =
1127            lines.iter_lines(&text, 1).take(2).map(|l| text.slice_to_cow(l.interval)).collect();
1128        assert_eq!(r, vec!["bb ", "bb "]);
1129
1130        let r: Vec<_> =
1131            lines.iter_lines(&text, 3).take(3).map(|l| text.slice_to_cow(l.interval)).collect();
1132        assert_eq!(r, vec!["cc\n", "cc ", "dddd "]);
1133    }
1134
1135    #[test]
1136    fn line_numbers() {
1137        let text: Rope = "aaaa\nbb bb cc\ncc dddd eeee ff\nff gggg".into();
1138        let lines = make_lines(&text, 2.);
1139        let nums: Vec<_> = lines.iter_lines(&text, 0).map(|l| l.line_num).collect();
1140        assert_eq!(
1141            nums,
1142            vec![Some(1), Some(2), None, None, Some(3), None, None, None, Some(4), None]
1143        );
1144    }
1145
1146    fn make_ranges(ivs: &[Interval]) -> Vec<Range<usize>> {
1147        ivs.iter().map(|iv| iv.start..iv.end).collect()
1148    }
1149
1150    #[test]
1151    fn update_frontier() {
1152        let mut lines = Lines::default();
1153        lines.add_task(0..1000);
1154        lines.update_tasks_after_wrap(0..10);
1155        lines.update_tasks_after_wrap(50..100);
1156        assert_eq!(make_ranges(&lines.work), vec![10..50, 100..1000]);
1157        lines.update_tasks_after_wrap(200..1000);
1158        assert_eq!(make_ranges(&lines.work), vec![10..50, 100..200]);
1159        lines.add_task(300..400);
1160        assert_eq!(make_ranges(&lines.work), vec![10..50, 100..200, 300..400]);
1161        lines.add_task(250..350);
1162        assert_eq!(make_ranges(&lines.work), vec![10..50, 100..200, 250..400]);
1163        lines.add_task(60..450);
1164        assert_eq!(make_ranges(&lines.work), vec![10..50, 60..450]);
1165    }
1166
1167    #[test]
1168    fn patchup_frontier() {
1169        let mut lines = Lines::default();
1170        lines.add_task(0..100);
1171        assert_eq!(make_ranges(&lines.work), vec![0..100]);
1172        lines.patchup_tasks(20..30, 20);
1173        assert_eq!(make_ranges(&lines.work), vec![0..110]);
1174
1175        // delete everything?
1176        lines.patchup_tasks(0..200, 0);
1177        assert_eq!(make_ranges(&lines.work), vec![]);
1178
1179        lines.add_task(0..110);
1180        lines.patchup_tasks(0..30, 0);
1181        assert_eq!(make_ranges(&lines.work), vec![0..80]);
1182        lines.update_tasks_after_wrap(20..30);
1183        assert_eq!(make_ranges(&lines.work), vec![0..20, 30..80]);
1184        lines.patchup_tasks(10..40, 0);
1185        assert_eq!(make_ranges(&lines.work), vec![0..50]);
1186        lines.update_tasks_after_wrap(20..30);
1187        assert_eq!(make_ranges(&lines.work), vec![0..20, 30..50]);
1188        lines.patchup_tasks(10..10, 10);
1189        assert_eq!(make_ranges(&lines.work), vec![0..30, 40..60]);
1190    }
1191
1192    /// https://github.com/xi-editor/xi-editor/issues/1112
1193    #[test]
1194    fn patchup_for_edit_before_task() {
1195        let mut lines = Lines::default();
1196        lines.add_task(0..100);
1197        lines.update_tasks_after_wrap(0..30);
1198        assert_eq!(make_ranges(&lines.work), vec![30..100]);
1199        lines.patchup_tasks(5..90, 80);
1200        assert_eq!(make_ranges(&lines.work), vec![85..95]);
1201    }
1202}