xi_core_lib/
view.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#![allow(clippy::range_plus_one)]
15
16use std::cell::RefCell;
17use std::cmp::{max, min};
18use std::iter;
19use std::ops::Range;
20
21use serde_json::Value;
22
23use crate::annotations::{AnnotationStore, Annotations, ToAnnotation};
24use crate::client::{Client, Update, UpdateOp};
25use crate::edit_types::ViewEvent;
26use crate::find::{Find, FindStatus};
27use crate::line_cache_shadow::{self, LineCacheShadow, RenderPlan, RenderTactic};
28use crate::linewrap::{InvalLines, Lines, VisualLine, WrapWidth};
29use crate::movement::{region_movement, selection_movement, Movement};
30use crate::plugins::PluginId;
31use crate::rpc::{FindQuery, GestureType, MouseAction, SelectionGranularity, SelectionModifier};
32use crate::selection::{Affinity, InsertDrift, SelRegion, Selection};
33use crate::styles::{Style, ThemeStyleMap};
34use crate::tabs::{BufferId, Counter, ViewId};
35use crate::width_cache::WidthCache;
36use crate::word_boundaries::WordCursor;
37use xi_rope::spans::Spans;
38use xi_rope::{Cursor, Interval, LinesMetric, Rope, RopeDelta};
39use xi_trace::trace_block;
40
41type StyleMap = RefCell<ThemeStyleMap>;
42
43/// A flag used to indicate when legacy actions should modify selections
44const FLAG_SELECT: u64 = 2;
45
46/// Size of batches as number of bytes used during incremental find.
47const FIND_BATCH_SIZE: usize = 500000;
48
49pub struct View {
50    view_id: ViewId,
51    buffer_id: BufferId,
52
53    /// Tracks whether this view has been scheduled to render.
54    /// We attempt to reduce duplicate renders by setting a small timeout
55    /// after an edit is applied, to allow batching with any plugin updates.
56    pending_render: bool,
57    size: Size,
58    /// The selection state for this view. Invariant: non-empty.
59    selection: Selection,
60
61    drag_state: Option<DragState>,
62
63    /// vertical scroll position
64    first_line: usize,
65    /// height of visible portion
66    height: usize,
67    lines: Lines,
68
69    /// Front end's line cache state for this view. See the `LineCacheShadow`
70    /// description for the invariant.
71    lc_shadow: LineCacheShadow,
72
73    /// New offset to be scrolled into position after an edit.
74    scroll_to: Option<usize>,
75
76    /// The state for finding text for this view.
77    /// Each instance represents a separate search query.
78    find: Vec<Find>,
79
80    /// Tracks the IDs for additional search queries in find.
81    find_id_counter: Counter,
82
83    /// Tracks whether there has been changes in find results or find parameters.
84    /// This is used to determined whether FindStatus should be sent to the frontend.
85    find_changed: FindStatusChange,
86
87    /// Tracks the progress of incremental find.
88    find_progress: FindProgress,
89
90    /// Tracks whether find highlights should be rendered.
91    /// Highlights are only rendered when search dialog is open.
92    highlight_find: bool,
93
94    /// The state for replacing matches for this view.
95    replace: Option<Replace>,
96
97    /// Tracks whether the replacement string or replace parameters changed.
98    replace_changed: bool,
99
100    /// Annotations provided by plugins.
101    annotations: AnnotationStore,
102}
103
104/// Indicates what changed in the find state.
105#[derive(PartialEq, Debug)]
106enum FindStatusChange {
107    /// None of the find parameters or number of matches changed.
108    None,
109
110    /// Find parameters and number of matches changed.
111    All,
112
113    /// Only number of matches changed
114    Matches,
115}
116
117/// Indicates what changed in the find state.
118#[derive(PartialEq, Debug, Clone)]
119enum FindProgress {
120    /// Incremental find is done/not running.
121    Ready,
122
123    /// The find process just started.
124    Started,
125
126    /// Incremental find is in progress. Keeps tracked of already searched range.
127    InProgress(Range<usize>),
128}
129
130/// Contains replacement string and replace options.
131#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
132pub struct Replace {
133    /// Replacement string.
134    pub chars: String,
135    pub preserve_case: bool,
136}
137
138/// A size, in pixel units (not display pixels).
139#[derive(Debug, Default, PartialEq, Serialize, Deserialize, Clone)]
140pub struct Size {
141    pub width: f64,
142    pub height: f64,
143}
144
145/// State required to resolve a drag gesture into a selection.
146struct DragState {
147    /// All the selection regions other than the one being dragged.
148    base_sel: Selection,
149
150    /// Start of the region selected when drag was started (region is
151    /// assumed to be forward).
152    min: usize,
153
154    /// End of the region selected when drag was started.
155    max: usize,
156
157    granularity: SelectionGranularity,
158}
159
160impl View {
161    pub fn new(view_id: ViewId, buffer_id: BufferId) -> View {
162        View {
163            view_id,
164            buffer_id,
165            pending_render: false,
166            selection: SelRegion::caret(0).into(),
167            scroll_to: Some(0),
168            size: Size::default(),
169            drag_state: None,
170            first_line: 0,
171            height: 10,
172            lines: Lines::default(),
173            lc_shadow: LineCacheShadow::default(),
174            find: Vec::new(),
175            find_id_counter: Counter::default(),
176            find_changed: FindStatusChange::None,
177            find_progress: FindProgress::Ready,
178            highlight_find: false,
179            replace: None,
180            replace_changed: false,
181            annotations: AnnotationStore::new(),
182        }
183    }
184
185    pub(crate) fn get_buffer_id(&self) -> BufferId {
186        self.buffer_id
187    }
188
189    pub(crate) fn get_view_id(&self) -> ViewId {
190        self.view_id
191    }
192
193    pub(crate) fn get_replace(&self) -> Option<Replace> {
194        self.replace.clone()
195    }
196
197    pub(crate) fn set_has_pending_render(&mut self, pending: bool) {
198        self.pending_render = pending
199    }
200
201    pub(crate) fn has_pending_render(&self) -> bool {
202        self.pending_render
203    }
204
205    pub(crate) fn update_wrap_settings(&mut self, text: &Rope, wrap_cols: usize, word_wrap: bool) {
206        let wrap_width = match (word_wrap, wrap_cols) {
207            (true, _) => WrapWidth::Width(self.size.width),
208            (false, 0) => WrapWidth::None,
209            (false, cols) => WrapWidth::Bytes(cols),
210        };
211        self.lines.set_wrap_width(text, wrap_width);
212    }
213
214    pub(crate) fn needs_more_wrap(&self) -> bool {
215        !self.lines.is_converged()
216    }
217
218    pub(crate) fn needs_wrap_in_visible_region(&self, text: &Rope) -> bool {
219        if self.lines.is_converged() {
220            false
221        } else {
222            let visible_region = self.interval_of_visible_region(text);
223            self.lines.interval_needs_wrap(visible_region)
224        }
225    }
226
227    pub(crate) fn find_in_progress(&self) -> bool {
228        match self.find_progress {
229            FindProgress::InProgress(_) => true,
230            FindProgress::Started => true,
231            _ => false,
232        }
233    }
234
235    pub(crate) fn do_edit(&mut self, text: &Rope, cmd: ViewEvent) {
236        use self::ViewEvent::*;
237        match cmd {
238            Move(movement) => self.do_move(text, movement, false),
239            ModifySelection(movement) => self.do_move(text, movement, true),
240            SelectAll => self.select_all(text),
241            Scroll(range) => self.set_scroll(range.first, range.last),
242            AddSelectionAbove => self.add_selection_by_movement(text, Movement::UpExactPosition),
243            AddSelectionBelow => self.add_selection_by_movement(text, Movement::DownExactPosition),
244            Gesture { line, col, ty } => self.do_gesture(text, line, col, ty),
245            GotoLine { line } => self.goto_line(text, line),
246            Find { chars, case_sensitive, regex, whole_words } => {
247                let id = self.find.first().and_then(|q| Some(q.id()));
248                let query_changes = FindQuery { id, chars, case_sensitive, regex, whole_words };
249                self.set_find(text, [query_changes].to_vec())
250            }
251            MultiFind { queries } => self.set_find(text, queries),
252            FindNext { wrap_around, allow_same, modify_selection } => {
253                self.do_find_next(text, false, wrap_around, allow_same, &modify_selection)
254            }
255            FindPrevious { wrap_around, allow_same, modify_selection } => {
256                self.do_find_next(text, true, wrap_around, allow_same, &modify_selection)
257            }
258            FindAll => self.do_find_all(text),
259            Click(MouseAction { line, column, flags, click_count }) => {
260                // Deprecated (kept for client compatibility):
261                // should be removed in favor of do_gesture
262                warn!("Usage of click is deprecated; use do_gesture");
263                if (flags & FLAG_SELECT) != 0 {
264                    self.do_gesture(
265                        text,
266                        line,
267                        column,
268                        GestureType::SelectExtend { granularity: SelectionGranularity::Point },
269                    )
270                } else if click_count == Some(2) {
271                    self.do_gesture(text, line, column, GestureType::WordSelect)
272                } else if click_count == Some(3) {
273                    self.do_gesture(text, line, column, GestureType::LineSelect)
274                } else {
275                    self.do_gesture(text, line, column, GestureType::PointSelect)
276                }
277            }
278            Drag(MouseAction { line, column, .. }) => {
279                warn!("Usage of drag is deprecated; use gesture instead");
280                self.do_gesture(text, line, column, GestureType::Drag)
281            }
282            CollapseSelections => self.collapse_selections(text),
283            HighlightFind { visible } => {
284                self.highlight_find = visible;
285                self.find_changed = FindStatusChange::All;
286                self.set_dirty(text);
287            }
288            SelectionForFind { case_sensitive } => self.do_selection_for_find(text, case_sensitive),
289            Replace { chars, preserve_case } => self.do_set_replace(chars, preserve_case),
290            SelectionForReplace => self.do_selection_for_replace(text),
291            SelectionIntoLines => self.do_split_selection_into_lines(text),
292        }
293    }
294
295    fn do_gesture(&mut self, text: &Rope, line: u64, col: u64, ty: GestureType) {
296        let line = line as usize;
297        let col = col as usize;
298        let offset = self.line_col_to_offset(text, line, col);
299        match ty {
300            GestureType::Select { granularity, multi } => {
301                self.select(text, offset, granularity, multi)
302            }
303            GestureType::SelectExtend { granularity } => {
304                self.extend_selection(text, offset, granularity)
305            }
306            GestureType::Drag => self.do_drag(text, offset, Affinity::default()),
307
308            _ => {
309                warn!("Deprecated gesture type sent to do_gesture method");
310            }
311        }
312    }
313
314    fn goto_line(&mut self, text: &Rope, line: u64) {
315        let offset = self.line_col_to_offset(text, line as usize, 0);
316        self.set_selection(text, SelRegion::caret(offset));
317    }
318
319    pub fn set_size(&mut self, size: Size) {
320        self.size = size;
321    }
322
323    pub fn set_scroll(&mut self, first: i64, last: i64) {
324        let first = max(first, 0) as usize;
325        let last = max(last, 0) as usize;
326        self.first_line = first;
327        self.height = last - first;
328    }
329
330    pub fn scroll_height(&self) -> usize {
331        self.height
332    }
333
334    fn scroll_to_cursor(&mut self, text: &Rope) {
335        let end = self.sel_regions().last().unwrap().end;
336        let line = self.line_of_offset(text, end);
337        if line < self.first_line {
338            self.first_line = line;
339        } else if self.first_line + self.height <= line {
340            self.first_line = line - (self.height - 1);
341        }
342        // We somewhat arbitrarily choose the last region for setting the old-style
343        // selection state, and for scrolling it into view if needed. This choice can
344        // likely be improved.
345        self.scroll_to = Some(end);
346    }
347
348    /// Removes any selection present at the given offset.
349    /// Returns true if a selection was removed, false otherwise.
350    pub fn deselect_at_offset(&mut self, text: &Rope, offset: usize) -> bool {
351        if !self.selection.regions_in_range(offset, offset).is_empty() {
352            let mut sel = self.selection.clone();
353            sel.delete_range(offset, offset, true);
354            if !sel.is_empty() {
355                self.drag_state = None;
356                self.set_selection_raw(text, sel);
357                return true;
358            }
359        }
360        false
361    }
362
363    /// Move the selection by the given movement. Return value is the offset of
364    /// a point that should be scrolled into view.
365    ///
366    /// If `modify` is `true`, the selections are modified, otherwise the results
367    /// of individual region movements become carets.
368    pub fn do_move(&mut self, text: &Rope, movement: Movement, modify: bool) {
369        self.drag_state = None;
370        let new_sel = selection_movement(movement, &self.selection, self, text, modify);
371        self.set_selection(text, new_sel);
372    }
373
374    /// Set the selection to a new value.
375    pub fn set_selection<S: Into<Selection>>(&mut self, text: &Rope, sel: S) {
376        self.set_selection_raw(text, sel.into());
377        self.scroll_to_cursor(text);
378    }
379
380    /// Sets the selection to a new value, without invalidating.
381    fn set_selection_for_edit(&mut self, text: &Rope, sel: Selection) {
382        self.selection = sel;
383        self.scroll_to_cursor(text);
384    }
385
386    /// Sets the selection to a new value, invalidating the line cache as needed.
387    /// This function does not perform any scrolling.
388    fn set_selection_raw(&mut self, text: &Rope, sel: Selection) {
389        self.invalidate_selection(text);
390        self.selection = sel;
391        self.invalidate_selection(text);
392    }
393
394    /// Invalidate the current selection. Note that we could be even more
395    /// fine-grained in the case of multiple cursors, but we also want this
396    /// method to be fast even when the selection is large.
397    fn invalidate_selection(&mut self, text: &Rope) {
398        // TODO: refine for upstream (caret appears on prev line)
399        let first_line = self.line_of_offset(text, self.selection.first().unwrap().min());
400        let last_line = self.line_of_offset(text, self.selection.last().unwrap().max()) + 1;
401        let all_caret = self.selection.iter().all(|region| region.is_caret());
402        let invalid = if all_caret {
403            line_cache_shadow::CURSOR_VALID
404        } else {
405            line_cache_shadow::CURSOR_VALID | line_cache_shadow::STYLES_VALID
406        };
407        self.lc_shadow.partial_invalidate(first_line, last_line, invalid);
408    }
409
410    fn add_selection_by_movement(&mut self, text: &Rope, movement: Movement) {
411        let mut sel = Selection::new();
412        for &region in self.sel_regions() {
413            sel.add_region(region);
414            let new_region = region_movement(movement, region, self, &text, false);
415            sel.add_region(new_region);
416        }
417        self.set_selection(text, sel);
418    }
419
420    // TODO: insert from keyboard or input method shouldn't break undo group,
421    /// Invalidates the styles of the given range (start and end are offsets within
422    /// the text).
423    pub fn invalidate_styles(&mut self, text: &Rope, start: usize, end: usize) {
424        let first_line = self.line_of_offset(text, start);
425        let (mut last_line, last_col) = self.offset_to_line_col(text, end);
426        last_line += if last_col > 0 { 1 } else { 0 };
427        self.lc_shadow.partial_invalidate(first_line, last_line, line_cache_shadow::STYLES_VALID);
428    }
429
430    pub fn update_annotations(
431        &mut self,
432        plugin: PluginId,
433        interval: Interval,
434        annotations: Annotations,
435    ) {
436        self.annotations.update(plugin, interval, annotations)
437    }
438
439    /// Select entire buffer.
440    ///
441    /// Note: unlike movement based selection, this does not scroll.
442    pub fn select_all(&mut self, text: &Rope) {
443        let selection = SelRegion::new(0, text.len()).into();
444        self.set_selection_raw(text, selection);
445    }
446
447    /// Finds the unit of text containing the given offset.
448    fn unit(&self, text: &Rope, offset: usize, granularity: SelectionGranularity) -> Interval {
449        match granularity {
450            SelectionGranularity::Point => Interval::new(offset, offset),
451            SelectionGranularity::Word => {
452                let mut word_cursor = WordCursor::new(text, offset);
453                let (start, end) = word_cursor.select_word();
454                Interval::new(start, end)
455            }
456            SelectionGranularity::Line => {
457                let (line, _) = self.offset_to_line_col(text, offset);
458                let (start, end) = self.lines.logical_line_range(text, line);
459                Interval::new(start, end)
460            }
461        }
462    }
463
464    /// Selects text with a certain granularity and supports multi_selection
465    fn select(
466        &mut self,
467        text: &Rope,
468        offset: usize,
469        granularity: SelectionGranularity,
470        multi: bool,
471    ) {
472        // If multi-select is enabled, toggle existing regions
473        if multi
474            && granularity == SelectionGranularity::Point
475            && self.deselect_at_offset(text, offset)
476        {
477            return;
478        }
479
480        let region = self.unit(text, offset, granularity).into();
481
482        let base_sel = match multi {
483            true => self.selection.clone(),
484            false => Selection::new(),
485        };
486        let mut selection = base_sel.clone();
487        selection.add_region(region);
488        self.set_selection(text, selection);
489
490        self.drag_state =
491            Some(DragState { base_sel, min: region.start, max: region.end, granularity });
492    }
493
494    /// Extends an existing selection (eg. when the user performs SHIFT + click).
495    pub fn extend_selection(
496        &mut self,
497        text: &Rope,
498        offset: usize,
499        granularity: SelectionGranularity,
500    ) {
501        if self.sel_regions().is_empty() {
502            return;
503        }
504
505        let (base_sel, last) = {
506            let mut base = Selection::new();
507            let (last, rest) = self.sel_regions().split_last().unwrap();
508            for &region in rest {
509                base.add_region(region);
510            }
511            (base, *last)
512        };
513
514        let mut sel = base_sel.clone();
515        self.drag_state =
516            Some(DragState { base_sel, min: last.start, max: last.start, granularity });
517
518        let start = (last.start, last.start);
519        let new_region = self.range_region(text, start, offset, granularity);
520
521        // TODO: small nit, merged region should be backward if end < start.
522        // This could be done by explicitly overriding, or by tweaking the
523        // merge logic.
524        sel.add_region(new_region);
525        self.set_selection(text, sel);
526    }
527
528    /// Splits current selections into lines.
529    fn do_split_selection_into_lines(&mut self, text: &Rope) {
530        let mut selection = Selection::new();
531
532        for region in self.selection.iter() {
533            if region.is_caret() {
534                selection.add_region(SelRegion::caret(region.max()));
535            } else {
536                let mut cursor = Cursor::new(&text, region.min());
537
538                while cursor.pos() < region.max() {
539                    let sel_start = cursor.pos();
540                    let end_of_line = match cursor.next::<LinesMetric>() {
541                        Some(end) if end >= region.max() => max(0, region.max() - 1),
542                        Some(end) => max(0, end - 1),
543                        None if cursor.pos() == text.len() => cursor.pos(),
544                        _ => break,
545                    };
546
547                    selection.add_region(SelRegion::new(sel_start, end_of_line));
548                }
549            }
550        }
551
552        self.set_selection_raw(text, selection);
553    }
554
555    /// Does a drag gesture, setting the selection from a combination of the drag
556    /// state and new offset.
557    fn do_drag(&mut self, text: &Rope, offset: usize, affinity: Affinity) {
558        let new_sel = self.drag_state.as_ref().map(|drag_state| {
559            let mut sel = drag_state.base_sel.clone();
560            let start = (drag_state.min, drag_state.max);
561            let new_region = self.range_region(text, start, offset, drag_state.granularity);
562            sel.add_region(new_region.with_horiz(None).with_affinity(affinity));
563            sel
564        });
565
566        if let Some(sel) = new_sel {
567            self.set_selection(text, sel);
568        }
569    }
570
571    /// Creates a `SelRegion` for range select or drag operations.
572    pub fn range_region(
573        &self,
574        text: &Rope,
575        start: (usize, usize),
576        offset: usize,
577        granularity: SelectionGranularity,
578    ) -> SelRegion {
579        let (min_start, max_start) = start;
580        let end = self.unit(text, offset, granularity);
581        let (min_end, max_end) = (end.start, end.end);
582        if offset >= min_start {
583            SelRegion::new(min_start, max_end)
584        } else {
585            SelRegion::new(max_start, min_end)
586        }
587    }
588
589    /// Returns the regions of the current selection.
590    pub fn sel_regions(&self) -> &[SelRegion] {
591        &self.selection
592    }
593
594    /// Collapse all selections in this view into a single caret
595    pub fn collapse_selections(&mut self, text: &Rope) {
596        let mut sel = self.selection.clone();
597        sel.collapse();
598        self.set_selection(text, sel);
599    }
600
601    /// Determines whether the offset is in any selection (counting carets and
602    /// selection edges).
603    pub fn is_point_in_selection(&self, offset: usize) -> bool {
604        !self.selection.regions_in_range(offset, offset).is_empty()
605    }
606
607    // Render a single line, and advance cursors to next line.
608    fn render_line(
609        &self,
610        client: &Client,
611        styles: &StyleMap,
612        text: &Rope,
613        line: VisualLine,
614        style_spans: &Spans<Style>,
615        line_num: usize,
616    ) -> Value {
617        let start_pos = line.interval.start;
618        let pos = line.interval.end;
619        let l_str = text.slice_to_cow(start_pos..pos);
620        let mut cursors = Vec::new();
621        let mut selections = Vec::new();
622        for region in self.selection.regions_in_range(start_pos, pos) {
623            // cursor
624            let c = region.end;
625            if (c > start_pos && c < pos)
626                || (!region.is_upstream() && c == start_pos)
627                || (region.is_upstream() && c == pos)
628                || (c == pos && c == text.len() && self.line_of_offset(text, c) == line_num)
629            {
630                cursors.push(c - start_pos);
631            }
632
633            // selection with interior
634            let sel_start_ix = clamp(region.min(), start_pos, pos) - start_pos;
635            let sel_end_ix = clamp(region.max(), start_pos, pos) - start_pos;
636            if sel_end_ix > sel_start_ix {
637                selections.push((sel_start_ix, sel_end_ix));
638            }
639        }
640
641        let mut hls = Vec::new();
642
643        if self.highlight_find {
644            for find in &self.find {
645                let mut cur_hls = Vec::new();
646                for region in find.occurrences().regions_in_range(start_pos, pos) {
647                    let sel_start_ix = clamp(region.min(), start_pos, pos) - start_pos;
648                    let sel_end_ix = clamp(region.max(), start_pos, pos) - start_pos;
649                    if sel_end_ix > sel_start_ix {
650                        cur_hls.push((sel_start_ix, sel_end_ix));
651                    }
652                }
653                hls.push(cur_hls);
654            }
655        }
656
657        let styles =
658            self.render_styles(client, styles, start_pos, pos, &selections, &hls, style_spans);
659
660        let mut result = json!({
661            "text": &l_str,
662            "styles": styles,
663        });
664
665        if !cursors.is_empty() {
666            result["cursor"] = json!(cursors);
667        }
668        if let Some(line_num) = line.line_num {
669            result["ln"] = json!(line_num);
670        }
671        result
672    }
673
674    pub fn render_styles(
675        &self,
676        client: &Client,
677        styles: &StyleMap,
678        start: usize,
679        end: usize,
680        sel: &[(usize, usize)],
681        hls: &Vec<Vec<(usize, usize)>>,
682        style_spans: &Spans<Style>,
683    ) -> Vec<isize> {
684        let mut rendered_styles = Vec::new();
685        assert!(start <= end, "{} {}", start, end);
686        let style_spans = style_spans.subseq(Interval::new(start, end));
687
688        let mut ix = 0;
689        // we add the special find highlights (1 to N) and selection (0) styles first.
690        // We add selection after find because we want it to be preferred if the
691        // same span exists in both sets (as when there is an active selection)
692        for (index, cur_find_hls) in hls.iter().enumerate() {
693            for &(sel_start, sel_end) in cur_find_hls {
694                rendered_styles.push((sel_start as isize) - ix);
695                rendered_styles.push(sel_end as isize - sel_start as isize);
696                rendered_styles.push(index as isize + 1);
697                ix = sel_end as isize;
698            }
699        }
700        for &(sel_start, sel_end) in sel {
701            rendered_styles.push((sel_start as isize) - ix);
702            rendered_styles.push(sel_end as isize - sel_start as isize);
703            rendered_styles.push(0);
704            ix = sel_end as isize;
705        }
706        for (iv, style) in style_spans.iter() {
707            let style_id = self.get_or_def_style_id(client, styles, &style);
708            rendered_styles.push((iv.start() as isize) - ix);
709            rendered_styles.push(iv.end() as isize - iv.start() as isize);
710            rendered_styles.push(style_id as isize);
711            ix = iv.end() as isize;
712        }
713        rendered_styles
714    }
715
716    fn get_or_def_style_id(&self, client: &Client, style_map: &StyleMap, style: &Style) -> usize {
717        let mut style_map = style_map.borrow_mut();
718        if let Some(ix) = style_map.lookup(style) {
719            return ix;
720        }
721        let ix = style_map.add(style);
722        let style = style_map.merge_with_default(style);
723        client.def_style(&style.to_json(ix));
724        ix
725    }
726
727    fn send_update_for_plan(
728        &mut self,
729        text: &Rope,
730        client: &Client,
731        styles: &StyleMap,
732        style_spans: &Spans<Style>,
733        plan: &RenderPlan,
734        pristine: bool,
735    ) {
736        if !self.lc_shadow.needs_render(plan) {
737            return;
738        }
739
740        // send updated find status only if there have been changes
741        if self.find_changed != FindStatusChange::None {
742            let matches_only = self.find_changed == FindStatusChange::Matches;
743            client.find_status(self.view_id, &json!(self.find_status(text, matches_only)));
744            self.find_changed = FindStatusChange::None;
745        }
746
747        // send updated replace status if changed
748        if self.replace_changed {
749            if let Some(replace) = self.get_replace() {
750                client.replace_status(self.view_id, &json!(replace))
751            }
752        }
753
754        let mut b = line_cache_shadow::Builder::new();
755        let mut ops = Vec::new();
756        let mut line_num = 0; // tracks old line cache
757
758        for seg in self.lc_shadow.iter_with_plan(plan) {
759            match seg.tactic {
760                RenderTactic::Discard => {
761                    ops.push(UpdateOp::invalidate(seg.n));
762                    b.add_span(seg.n, 0, 0);
763                }
764                RenderTactic::Preserve => {
765                    // TODO: in the case where it's ALL_VALID & !CURSOR_VALID, and cursors
766                    // are empty, could send update removing the cursor.
767                    if seg.validity == line_cache_shadow::ALL_VALID {
768                        let n_skip = seg.their_line_num - line_num;
769                        if n_skip > 0 {
770                            ops.push(UpdateOp::skip(n_skip));
771                        }
772                        let line_offset = self.offset_of_line(text, seg.our_line_num);
773                        let logical_line = text.line_of_offset(line_offset) + 1;
774                        ops.push(UpdateOp::copy(seg.n, logical_line));
775                        b.add_span(seg.n, seg.our_line_num, line_cache_shadow::ALL_VALID);
776                        line_num = seg.their_line_num + seg.n;
777                    } else {
778                        ops.push(UpdateOp::invalidate(seg.n));
779                        b.add_span(seg.n, 0, 0);
780                    }
781                }
782                RenderTactic::Render => {
783                    // TODO: update (rather than re-render) in cases of text valid
784                    if seg.validity == line_cache_shadow::ALL_VALID {
785                        let n_skip = seg.their_line_num - line_num;
786                        if n_skip > 0 {
787                            ops.push(UpdateOp::skip(n_skip));
788                        }
789                        let line_offset = self.offset_of_line(text, seg.our_line_num);
790                        let logical_line = text.line_of_offset(line_offset) + 1;
791                        ops.push(UpdateOp::copy(seg.n, logical_line));
792                        b.add_span(seg.n, seg.our_line_num, line_cache_shadow::ALL_VALID);
793                        line_num = seg.their_line_num + seg.n;
794                    } else {
795                        let start_line = seg.our_line_num;
796                        let rendered_lines = self
797                            .lines
798                            .iter_lines(text, start_line)
799                            .take(seg.n)
800                            .enumerate()
801                            .map(|(i, l)| {
802                                self.render_line(
803                                    client,
804                                    styles,
805                                    text,
806                                    l,
807                                    style_spans,
808                                    start_line + i,
809                                )
810                            })
811                            .collect::<Vec<_>>();
812                        debug_assert_eq!(rendered_lines.len(), seg.n);
813                        ops.push(UpdateOp::insert(rendered_lines));
814                        b.add_span(seg.n, seg.our_line_num, line_cache_shadow::ALL_VALID);
815                    }
816                }
817            }
818        }
819
820        self.lc_shadow = b.build();
821        for find in &mut self.find {
822            find.set_hls_dirty(false)
823        }
824
825        let start_off = self.offset_of_line(text, self.first_line);
826        let end_off = self.offset_of_line(text, self.first_line + self.height + 2);
827        let visible_range = Interval::new(start_off, end_off);
828        let selection_annotations =
829            self.selection.get_annotations(visible_range, &self, text).to_json();
830        let find_annotations =
831            self.find.iter().map(|ref f| f.get_annotations(visible_range, &self, text).to_json());
832        let plugin_annotations =
833            self.annotations.iter_range(&self, text, visible_range).map(|a| a.to_json());
834
835        let annotations = iter::once(selection_annotations)
836            .chain(find_annotations)
837            .chain(plugin_annotations)
838            .collect::<Vec<_>>();
839
840        let update = Update { ops, pristine, annotations };
841        client.update_view(self.view_id, &update);
842    }
843
844    /// Determines the current number of find results and search parameters to send them to
845    /// the frontend.
846    pub fn find_status(&self, text: &Rope, matches_only: bool) -> Vec<FindStatus> {
847        self.find
848            .iter()
849            .map(|find| find.find_status(&self, text, matches_only))
850            .collect::<Vec<FindStatus>>()
851    }
852
853    /// Update front-end with any changes to view since the last time sent.
854    /// The `pristine` argument indicates whether or not the buffer has
855    /// unsaved changes.
856    pub fn render_if_dirty(
857        &mut self,
858        text: &Rope,
859        client: &Client,
860        styles: &StyleMap,
861        style_spans: &Spans<Style>,
862        pristine: bool,
863    ) {
864        let height = self.line_of_offset(text, text.len()) + 1;
865        let plan = RenderPlan::create(height, self.first_line, self.height);
866        self.send_update_for_plan(text, client, styles, style_spans, &plan, pristine);
867        if let Some(new_scroll_pos) = self.scroll_to.take() {
868            let (line, col) = self.offset_to_line_col(text, new_scroll_pos);
869            client.scroll_to(self.view_id, line, col);
870        }
871    }
872
873    // Send the requested lines even if they're outside the current scroll region.
874    pub fn request_lines(
875        &mut self,
876        text: &Rope,
877        client: &Client,
878        styles: &StyleMap,
879        style_spans: &Spans<Style>,
880        first_line: usize,
881        last_line: usize,
882        pristine: bool,
883    ) {
884        let height = self.line_of_offset(text, text.len()) + 1;
885        let mut plan = RenderPlan::create(height, self.first_line, self.height);
886        plan.request_lines(first_line, last_line);
887        self.send_update_for_plan(text, client, styles, style_spans, &plan, pristine);
888    }
889
890    /// Invalidates front-end's entire line cache, forcing a full render at the next
891    /// update cycle. This should be a last resort, updates should generally cause
892    /// finer grain invalidation.
893    pub fn set_dirty(&mut self, text: &Rope) {
894        let height = self.line_of_offset(text, text.len()) + 1;
895        let mut b = line_cache_shadow::Builder::new();
896        b.add_span(height, 0, 0);
897        b.set_dirty(true);
898        self.lc_shadow = b.build();
899    }
900
901    // How should we count "column"? Valid choices include:
902    // * Unicode codepoints
903    // * grapheme clusters
904    // * Unicode width (so CJK counts as 2)
905    // * Actual measurement in text layout
906    // * Code units in some encoding
907    //
908    // Of course, all these are identical for ASCII. For now we use UTF-8 code units
909    // for simplicity.
910
911    pub(crate) fn offset_to_line_col(&self, text: &Rope, offset: usize) -> (usize, usize) {
912        let line = self.line_of_offset(text, offset);
913        (line, offset - self.offset_of_line(text, line))
914    }
915
916    pub(crate) fn line_col_to_offset(&self, text: &Rope, line: usize, col: usize) -> usize {
917        let mut offset = self.offset_of_line(text, line).saturating_add(col);
918        if offset >= text.len() {
919            offset = text.len();
920            if self.line_of_offset(text, offset) <= line {
921                return offset;
922            }
923        } else {
924            // Snap to grapheme cluster boundary
925            offset = text.prev_grapheme_offset(offset + 1).unwrap();
926        }
927
928        // clamp to end of line
929        let next_line_offset = self.offset_of_line(text, line + 1);
930        if offset >= next_line_offset {
931            if let Some(prev) = text.prev_grapheme_offset(next_line_offset) {
932                offset = prev;
933            }
934        }
935        offset
936    }
937
938    /// Returns the byte range of the currently visible lines.
939    fn interval_of_visible_region(&self, text: &Rope) -> Interval {
940        let start = self.offset_of_line(text, self.first_line);
941        let end = self.offset_of_line(text, self.first_line + self.height + 1);
942        Interval::new(start, end)
943    }
944
945    // use own breaks if present, or text if not (no line wrapping)
946
947    /// Returns the visible line number containing the given offset.
948    pub fn line_of_offset(&self, text: &Rope, offset: usize) -> usize {
949        self.lines.visual_line_of_offset(text, offset)
950    }
951
952    /// Returns the byte offset corresponding to the given visual line.
953    pub fn offset_of_line(&self, text: &Rope, line: usize) -> usize {
954        self.lines.offset_of_visual_line(text, line)
955    }
956
957    /// Generate line breaks, based on current settings. Currently batch-mode,
958    /// and currently in a debugging state.
959    pub(crate) fn rewrap(
960        &mut self,
961        text: &Rope,
962        width_cache: &mut WidthCache,
963        client: &Client,
964        spans: &Spans<Style>,
965    ) {
966        let _t = trace_block("View::rewrap", &["core"]);
967        let visible = self.first_line..self.first_line + self.height;
968        let inval = self.lines.rewrap_chunk(text, width_cache, client, spans, visible);
969        if let Some(InvalLines { start_line, inval_count, new_count }) = inval {
970            self.lc_shadow.edit(start_line, start_line + inval_count, new_count);
971        }
972    }
973
974    /// Updates the view after the text has been modified by the given `delta`.
975    /// This method is responsible for updating the cursors, and also for
976    /// recomputing line wraps.
977    pub fn after_edit(
978        &mut self,
979        text: &Rope,
980        last_text: &Rope,
981        delta: &RopeDelta,
982        client: &Client,
983        width_cache: &mut WidthCache,
984        drift: InsertDrift,
985    ) {
986        let visible = self.first_line..self.first_line + self.height;
987        match self.lines.after_edit(text, last_text, delta, width_cache, client, visible) {
988            Some(InvalLines { start_line, inval_count, new_count }) => {
989                self.lc_shadow.edit(start_line, start_line + inval_count, new_count);
990            }
991            None => self.set_dirty(text),
992        }
993
994        // Any edit cancels a drag. This is good behavior for edits initiated through
995        // the front-end, but perhaps not for async edits.
996        self.drag_state = None;
997
998        let (iv, _) = delta.summary();
999        self.annotations.invalidate(iv);
1000
1001        // update only find highlights affected by change
1002        for find in &mut self.find {
1003            find.update_highlights(text, delta);
1004            self.find_changed = FindStatusChange::All;
1005        }
1006
1007        // Note: for committing plugin edits, we probably want to know the priority
1008        // of the delta so we can set the cursor before or after the edit, as needed.
1009        let new_sel = self.selection.apply_delta(delta, true, drift);
1010        self.set_selection_for_edit(text, new_sel);
1011    }
1012
1013    fn do_selection_for_find(&mut self, text: &Rope, case_sensitive: bool) {
1014        // set last selection or word under current cursor as search query
1015        let search_query = match self.selection.last() {
1016            Some(region) => {
1017                if !region.is_caret() {
1018                    text.slice_to_cow(region)
1019                } else {
1020                    let (start, end) = {
1021                        let mut word_cursor = WordCursor::new(text, region.max());
1022                        word_cursor.select_word()
1023                    };
1024                    text.slice_to_cow(start..end)
1025                }
1026            }
1027            _ => return,
1028        };
1029
1030        self.set_dirty(text);
1031
1032        // set selection as search query for first find if no additional search queries are used
1033        // otherwise add new find with selection as search query
1034        if self.find.len() != 1 {
1035            self.add_find();
1036        }
1037
1038        self.find.last_mut().unwrap().set_find(&search_query, case_sensitive, false, true);
1039        self.find_progress = FindProgress::Started;
1040    }
1041
1042    fn add_find(&mut self) {
1043        let id = self.find_id_counter.next();
1044        self.find.push(Find::new(id));
1045    }
1046
1047    fn set_find(&mut self, text: &Rope, queries: Vec<FindQuery>) {
1048        // checks if at least query has been changed, otherwise we don't need to rerun find
1049        let mut find_changed = queries.len() != self.find.len();
1050
1051        // remove deleted queries
1052        self.find.retain(|f| queries.iter().any(|q| q.id == Some(f.id())));
1053
1054        for query in &queries {
1055            let pos = match query.id {
1056                Some(id) => {
1057                    // update existing query
1058                    match self.find.iter().position(|f| f.id() == id) {
1059                        Some(p) => p,
1060                        None => return,
1061                    }
1062                }
1063                None => {
1064                    // add new query
1065                    self.add_find();
1066                    self.find.len() - 1
1067                }
1068            };
1069
1070            if self.find[pos].set_find(
1071                &query.chars.clone(),
1072                query.case_sensitive,
1073                query.regex,
1074                query.whole_words,
1075            ) {
1076                find_changed = true;
1077            }
1078        }
1079
1080        if find_changed {
1081            self.set_dirty(text);
1082            self.find_progress = FindProgress::Started;
1083        }
1084    }
1085
1086    pub fn do_find(&mut self, text: &Rope) {
1087        let search_range = match &self.find_progress.clone() {
1088            FindProgress::Started => {
1089                // start incremental find on visible region
1090                let start = self.offset_of_line(text, self.first_line);
1091                let end = min(text.len(), start + FIND_BATCH_SIZE);
1092                self.find_changed = FindStatusChange::Matches;
1093                self.find_progress = FindProgress::InProgress(Range { start, end });
1094                Some((start, end))
1095            }
1096            FindProgress::InProgress(searched_range) => {
1097                if searched_range.start == 0 && searched_range.end >= text.len() {
1098                    // the entire text has been searched
1099                    // end find by executing multi-line regex queries on entire text
1100                    // stop incremental find
1101                    self.find_progress = FindProgress::Ready;
1102                    self.find_changed = FindStatusChange::All;
1103                    Some((0, text.len()))
1104                } else {
1105                    self.find_changed = FindStatusChange::Matches;
1106                    // expand find to un-searched regions
1107                    let start_off = self.offset_of_line(text, self.first_line);
1108
1109                    // If there is unsearched text before the visible region, we want to include it in this search operation
1110                    let search_preceding_range = start_off.saturating_sub(searched_range.start)
1111                        < searched_range.end.saturating_sub(start_off)
1112                        && searched_range.start > 0;
1113
1114                    if search_preceding_range || searched_range.end >= text.len() {
1115                        let start = searched_range.start.saturating_sub(FIND_BATCH_SIZE);
1116                        self.find_progress =
1117                            FindProgress::InProgress(Range { start, end: searched_range.end });
1118                        Some((start, searched_range.start))
1119                    } else if searched_range.end < text.len() {
1120                        let end = min(text.len(), searched_range.end + FIND_BATCH_SIZE);
1121                        self.find_progress =
1122                            FindProgress::InProgress(Range { start: searched_range.start, end });
1123                        Some((searched_range.end, end))
1124                    } else {
1125                        self.find_changed = FindStatusChange::All;
1126                        None
1127                    }
1128                }
1129            }
1130            _ => {
1131                self.find_changed = FindStatusChange::None;
1132                None
1133            }
1134        };
1135
1136        if let Some((search_range_start, search_range_end)) = search_range {
1137            for query in &mut self.find {
1138                if !query.is_multiline_regex() {
1139                    query.update_find(text, search_range_start, search_range_end, true);
1140                } else {
1141                    // only execute multi-line regex queries if we are searching the entire text (last step)
1142                    if search_range_start == 0 && search_range_end == text.len() {
1143                        query.update_find(text, search_range_start, search_range_end, true);
1144                    }
1145                }
1146            }
1147        }
1148    }
1149
1150    /// Selects the next find match.
1151    pub fn do_find_next(
1152        &mut self,
1153        text: &Rope,
1154        reverse: bool,
1155        wrap: bool,
1156        allow_same: bool,
1157        modify_selection: &SelectionModifier,
1158    ) {
1159        self.select_next_occurrence(text, reverse, false, allow_same, modify_selection);
1160        if self.scroll_to.is_none() && wrap {
1161            self.select_next_occurrence(text, reverse, true, allow_same, modify_selection);
1162        }
1163    }
1164
1165    /// Selects all find matches.
1166    pub fn do_find_all(&mut self, text: &Rope) {
1167        let mut selection = Selection::new();
1168        for find in &self.find {
1169            for &occurrence in find.occurrences().iter() {
1170                selection.add_region(occurrence);
1171            }
1172        }
1173
1174        if !selection.is_empty() {
1175            // todo: invalidate so that nothing selected accidentally replaced
1176            self.set_selection(text, selection);
1177        }
1178    }
1179
1180    /// Select the next occurrence relative to the last cursor. `reverse` determines whether the
1181    /// next occurrence before (`true`) or after (`false`) the last cursor is selected. `wrapped`
1182    /// indicates a search for the next occurrence past the end of the file.
1183    pub fn select_next_occurrence(
1184        &mut self,
1185        text: &Rope,
1186        reverse: bool,
1187        wrapped: bool,
1188        _allow_same: bool,
1189        modify_selection: &SelectionModifier,
1190    ) {
1191        let (cur_start, cur_end) = match self.selection.last() {
1192            Some(sel) => (sel.min(), sel.max()),
1193            _ => (0, 0),
1194        };
1195
1196        // multiple queries; select closest occurrence
1197        let closest_occurrence = self
1198            .find
1199            .iter()
1200            .flat_map(|x| x.next_occurrence(text, reverse, wrapped, &self.selection))
1201            .min_by_key(|x| match reverse {
1202                true if x.end > cur_end => 2 * text.len() - x.end,
1203                true => cur_end - x.end,
1204                false if x.start < cur_start => x.start + text.len(),
1205                false => x.start - cur_start,
1206            });
1207
1208        if let Some(occ) = closest_occurrence {
1209            match modify_selection {
1210                SelectionModifier::Set => self.set_selection(text, occ),
1211                SelectionModifier::Add => {
1212                    let mut selection = self.selection.clone();
1213                    selection.add_region(occ);
1214                    self.set_selection(text, selection);
1215                }
1216                SelectionModifier::AddRemovingCurrent => {
1217                    let mut selection = self.selection.clone();
1218
1219                    if let Some(last_selection) = self.selection.last() {
1220                        if !last_selection.is_caret() {
1221                            selection.delete_range(
1222                                last_selection.min(),
1223                                last_selection.max(),
1224                                false,
1225                            );
1226                        }
1227                    }
1228
1229                    selection.add_region(occ);
1230                    self.set_selection(text, selection);
1231                }
1232                _ => {}
1233            }
1234        }
1235    }
1236
1237    fn do_set_replace(&mut self, chars: String, preserve_case: bool) {
1238        self.replace = Some(Replace { chars, preserve_case });
1239        self.replace_changed = true;
1240    }
1241
1242    fn do_selection_for_replace(&mut self, text: &Rope) {
1243        // set last selection or word under current cursor as replacement string
1244        let replacement = match self.selection.last() {
1245            Some(region) => {
1246                if !region.is_caret() {
1247                    text.slice_to_cow(region)
1248                } else {
1249                    let (start, end) = {
1250                        let mut word_cursor = WordCursor::new(text, region.max());
1251                        word_cursor.select_word()
1252                    };
1253                    text.slice_to_cow(start..end)
1254                }
1255            }
1256            _ => return,
1257        };
1258
1259        self.set_dirty(text);
1260        self.do_set_replace(replacement.into_owned(), false);
1261    }
1262
1263    /// Get the line range of a selected region.
1264    pub fn get_line_range(&self, text: &Rope, region: &SelRegion) -> Range<usize> {
1265        let (first_line, _) = self.offset_to_line_col(text, region.min());
1266        let (mut last_line, last_col) = self.offset_to_line_col(text, region.max());
1267        if last_col == 0 && last_line > first_line {
1268            last_line -= 1;
1269        }
1270
1271        first_line..(last_line + 1)
1272    }
1273
1274    pub fn get_caret_offset(&self) -> Option<usize> {
1275        match self.selection.len() {
1276            1 if self.selection[0].is_caret() => {
1277                let offset = self.selection[0].start;
1278                Some(offset)
1279            }
1280            _ => None,
1281        }
1282    }
1283}
1284
1285impl View {
1286    /// Exposed for benchmarking
1287    #[doc(hidden)]
1288    pub fn debug_force_rewrap_cols(&mut self, text: &Rope, cols: usize) {
1289        use xi_rpc::test_utils::DummyPeer;
1290
1291        let spans: Spans<Style> = Spans::default();
1292        let mut width_cache = WidthCache::new();
1293        let client = Client::new(Box::new(DummyPeer));
1294        self.update_wrap_settings(text, cols, false);
1295        self.rewrap(text, &mut width_cache, &client, &spans);
1296    }
1297}
1298
1299// utility function to clamp a value within the given range
1300fn clamp(x: usize, min: usize, max: usize) -> usize {
1301    if x < min {
1302        min
1303    } else if x < max {
1304        x
1305    } else {
1306        max
1307    }
1308}
1309
1310#[cfg(test)]
1311mod tests {
1312    use super::*;
1313    use crate::rpc::FindQuery;
1314
1315    #[test]
1316    fn incremental_find_update() {
1317        let mut view = View::new(1.into(), BufferId::new(2));
1318        let mut s = String::new();
1319        for _ in 0..(FIND_BATCH_SIZE - 2) {
1320            s += "x";
1321        }
1322        s += "aaaaaa";
1323        for _ in 0..(FIND_BATCH_SIZE) {
1324            s += "x";
1325        }
1326        s += "aaaaaa";
1327        assert_eq!(view.find_in_progress(), false);
1328
1329        let text = Rope::from(&s);
1330        view.do_edit(
1331            &text,
1332            ViewEvent::Find {
1333                chars: "aaaaaa".to_string(),
1334                case_sensitive: false,
1335                regex: false,
1336                whole_words: false,
1337            },
1338        );
1339        view.do_find(&text);
1340        assert_eq!(view.find_in_progress(), true);
1341        view.do_find_all(&text);
1342        assert_eq!(view.sel_regions().len(), 1);
1343        assert_eq!(
1344            view.sel_regions().first(),
1345            Some(&SelRegion::new(FIND_BATCH_SIZE - 2, FIND_BATCH_SIZE + 6 - 2))
1346        );
1347        view.do_find(&text);
1348        assert_eq!(view.find_in_progress(), true);
1349        view.do_find_all(&text);
1350        assert_eq!(view.sel_regions().len(), 2);
1351    }
1352
1353    #[test]
1354    fn incremental_find_codepoint_boundary() {
1355        let mut view = View::new(1.into(), BufferId::new(2));
1356        let mut s = String::new();
1357        for _ in 0..(FIND_BATCH_SIZE + 2) {
1358            s += "£€äßß";
1359        }
1360
1361        assert_eq!(view.find_in_progress(), false);
1362
1363        let text = Rope::from(&s);
1364        view.do_edit(
1365            &text,
1366            ViewEvent::Find {
1367                chars: "a".to_string(),
1368                case_sensitive: false,
1369                regex: false,
1370                whole_words: false,
1371            },
1372        );
1373        view.do_find(&text);
1374        assert_eq!(view.find_in_progress(), true);
1375        view.do_find_all(&text);
1376        assert_eq!(view.sel_regions().len(), 1); // cursor
1377    }
1378
1379    #[test]
1380    fn selection_for_find() {
1381        let mut view = View::new(1.into(), BufferId::new(2));
1382        let text = Rope::from("hello hello world\n");
1383        view.set_selection(&text, SelRegion::new(6, 11));
1384        view.do_edit(&text, ViewEvent::SelectionForFind { case_sensitive: false });
1385        view.do_find(&text);
1386        view.do_find_all(&text);
1387        assert_eq!(view.sel_regions().len(), 2);
1388    }
1389
1390    #[test]
1391    fn find_next() {
1392        let mut view = View::new(1.into(), BufferId::new(2));
1393        let text = Rope::from("hello hello world\n");
1394        view.do_edit(
1395            &text,
1396            ViewEvent::Find {
1397                chars: "foo".to_string(),
1398                case_sensitive: false,
1399                regex: false,
1400                whole_words: false,
1401            },
1402        );
1403        view.do_find(&text);
1404        view.do_find_next(&text, false, true, false, &SelectionModifier::Set);
1405        assert_eq!(view.sel_regions().len(), 1);
1406        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(0, 0))); // caret
1407
1408        view.do_edit(
1409            &text,
1410            ViewEvent::Find {
1411                chars: "hello".to_string(),
1412                case_sensitive: false,
1413                regex: false,
1414                whole_words: false,
1415            },
1416        );
1417        view.do_find(&text);
1418        assert_eq!(view.sel_regions().len(), 1);
1419        view.do_find_next(&text, false, true, false, &SelectionModifier::Set);
1420        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(0, 5)));
1421        view.do_find_next(&text, false, true, false, &SelectionModifier::Set);
1422        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(6, 11)));
1423        view.do_find_next(&text, false, true, false, &SelectionModifier::Set);
1424        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(0, 5)));
1425        view.do_find_next(&text, true, true, false, &SelectionModifier::Set);
1426        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(6, 11)));
1427
1428        view.do_find_next(&text, true, true, false, &SelectionModifier::Add);
1429        assert_eq!(view.sel_regions().len(), 2);
1430        view.do_find_next(&text, true, true, false, &SelectionModifier::AddRemovingCurrent);
1431        assert_eq!(view.sel_regions().len(), 1);
1432        view.do_find_next(&text, true, true, false, &SelectionModifier::None);
1433        assert_eq!(view.sel_regions().len(), 1);
1434    }
1435
1436    #[test]
1437    fn find_all() {
1438        let mut view = View::new(1.into(), BufferId::new(2));
1439        let text = Rope::from("hello hello world\n hello!");
1440        view.do_edit(
1441            &text,
1442            ViewEvent::Find {
1443                chars: "foo".to_string(),
1444                case_sensitive: false,
1445                regex: false,
1446                whole_words: false,
1447            },
1448        );
1449        view.do_find(&text);
1450        view.do_find_all(&text);
1451        assert_eq!(view.sel_regions().len(), 1); // caret
1452
1453        view.do_edit(
1454            &text,
1455            ViewEvent::Find {
1456                chars: "hello".to_string(),
1457                case_sensitive: false,
1458                regex: false,
1459                whole_words: false,
1460            },
1461        );
1462        view.do_find(&text);
1463        view.do_find_all(&text);
1464        assert_eq!(view.sel_regions().len(), 3);
1465
1466        view.do_edit(
1467            &text,
1468            ViewEvent::Find {
1469                chars: "foo".to_string(),
1470                case_sensitive: false,
1471                regex: false,
1472                whole_words: false,
1473            },
1474        );
1475        view.do_find(&text);
1476        view.do_find_all(&text);
1477        assert_eq!(view.sel_regions().len(), 3);
1478    }
1479
1480    #[test]
1481    fn multi_queries_find_next() {
1482        let mut view = View::new(1.into(), BufferId::new(2));
1483        let text = Rope::from("hello hello world\n hello!");
1484        let query1 = FindQuery {
1485            id: None,
1486            chars: "hello".to_string(),
1487            case_sensitive: false,
1488            regex: false,
1489            whole_words: false,
1490        };
1491        let query2 = FindQuery {
1492            id: None,
1493            chars: "o world".to_string(),
1494            case_sensitive: false,
1495            regex: false,
1496            whole_words: false,
1497        };
1498        view.do_edit(&text, ViewEvent::MultiFind { queries: vec![query1, query2] });
1499        view.do_find(&text);
1500        view.do_find_next(&text, false, true, false, &SelectionModifier::Set);
1501        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(0, 5)));
1502        view.do_find_next(&text, false, true, false, &SelectionModifier::Set);
1503        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(6, 11)));
1504        view.do_find_next(&text, false, true, false, &SelectionModifier::Set);
1505        assert_eq!(view.sel_regions().first(), Some(&SelRegion::new(10, 17)));
1506    }
1507
1508    #[test]
1509    fn multi_queries_find_all() {
1510        let mut view = View::new(1.into(), BufferId::new(2));
1511        let text = Rope::from("hello hello world\n hello!");
1512        let query1 = FindQuery {
1513            id: None,
1514            chars: "hello".to_string(),
1515            case_sensitive: false,
1516            regex: false,
1517            whole_words: false,
1518        };
1519        let query2 = FindQuery {
1520            id: None,
1521            chars: "world".to_string(),
1522            case_sensitive: false,
1523            regex: false,
1524            whole_words: false,
1525        };
1526        view.do_edit(&text, ViewEvent::MultiFind { queries: vec![query1, query2] });
1527        view.do_find(&text);
1528        view.do_find_all(&text);
1529        assert_eq!(view.sel_regions().len(), 4);
1530    }
1531}