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 
16 use std::cell::RefCell;
17 use std::cmp::{max, min};
18 use std::iter;
19 use std::ops::Range;
20 
21 use serde_json::Value;
22 
23 use crate::annotations::{AnnotationStore, Annotations, ToAnnotation};
24 use crate::client::{Client, Update, UpdateOp};
25 use crate::edit_types::ViewEvent;
26 use crate::find::{Find, FindStatus};
27 use crate::line_cache_shadow::{self, LineCacheShadow, RenderPlan, RenderTactic};
28 use crate::linewrap::{InvalLines, Lines, VisualLine, WrapWidth};
29 use crate::movement::{region_movement, selection_movement, Movement};
30 use crate::plugins::PluginId;
31 use crate::rpc::{FindQuery, GestureType, MouseAction, SelectionGranularity, SelectionModifier};
32 use crate::selection::{Affinity, InsertDrift, SelRegion, Selection};
33 use crate::styles::{Style, ThemeStyleMap};
34 use crate::tabs::{BufferId, Counter, ViewId};
35 use crate::width_cache::WidthCache;
36 use crate::word_boundaries::WordCursor;
37 use xi_rope::spans::Spans;
38 use xi_rope::{Cursor, Interval, LinesMetric, Rope, RopeDelta};
39 use xi_trace::trace_block;
40 
41 type StyleMap = RefCell<ThemeStyleMap>;
42 
43 /// A flag used to indicate when legacy actions should modify selections
44 const FLAG_SELECT: u64 = 2;
45 
46 /// Size of batches as number of bytes used during incremental find.
47 const FIND_BATCH_SIZE: usize = 500000;
48 
49 pub 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)]
106 enum 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)]
119 enum 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)]
132 pub 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)]
140 pub struct Size {
141     pub width: f64,
142     pub height: f64,
143 }
144 
145 /// State required to resolve a drag gesture into a selection.
146 struct 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 
160 impl View {
new(view_id: ViewId, buffer_id: BufferId) -> View161     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 
get_buffer_id(&self) -> BufferId185     pub(crate) fn get_buffer_id(&self) -> BufferId {
186         self.buffer_id
187     }
188 
get_view_id(&self) -> ViewId189     pub(crate) fn get_view_id(&self) -> ViewId {
190         self.view_id
191     }
192 
get_replace(&self) -> Option<Replace>193     pub(crate) fn get_replace(&self) -> Option<Replace> {
194         self.replace.clone()
195     }
196 
set_has_pending_render(&mut self, pending: bool)197     pub(crate) fn set_has_pending_render(&mut self, pending: bool) {
198         self.pending_render = pending
199     }
200 
has_pending_render(&self) -> bool201     pub(crate) fn has_pending_render(&self) -> bool {
202         self.pending_render
203     }
204 
update_wrap_settings(&mut self, text: &Rope, wrap_cols: usize, word_wrap: bool)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 
needs_more_wrap(&self) -> bool214     pub(crate) fn needs_more_wrap(&self) -> bool {
215         !self.lines.is_converged()
216     }
217 
needs_wrap_in_visible_region(&self, text: &Rope) -> bool218     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 
find_in_progress(&self) -> bool227     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 
do_edit(&mut self, text: &Rope, cmd: ViewEvent)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 
do_gesture(&mut self, text: &Rope, line: u64, col: u64, ty: GestureType)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 
goto_line(&mut self, text: &Rope, line: u64)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 
set_size(&mut self, size: Size)319     pub fn set_size(&mut self, size: Size) {
320         self.size = size;
321     }
322 
set_scroll(&mut self, first: i64, last: i64)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 
scroll_height(&self) -> usize330     pub fn scroll_height(&self) -> usize {
331         self.height
332     }
333 
scroll_to_cursor(&mut self, text: &Rope)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.
deselect_at_offset(&mut self, text: &Rope, offset: usize) -> bool350     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.
do_move(&mut self, text: &Rope, movement: Movement, modify: bool)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.
set_selection<S: Into<Selection>>(&mut self, text: &Rope, sel: S)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.
set_selection_for_edit(&mut self, text: &Rope, sel: Selection)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.
set_selection_raw(&mut self, text: &Rope, sel: Selection)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.
invalidate_selection(&mut self, text: &Rope)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 
add_selection_by_movement(&mut self, text: &Rope, movement: Movement)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).
invalidate_styles(&mut self, text: &Rope, start: usize, end: usize)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 
update_annotations( &mut self, plugin: PluginId, interval: Interval, annotations: Annotations, )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.
select_all(&mut self, text: &Rope)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.
unit(&self, text: &Rope, offset: usize, granularity: SelectionGranularity) -> Interval448     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
select( &mut self, text: &Rope, offset: usize, granularity: SelectionGranularity, multi: bool, )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).
extend_selection( &mut self, text: &Rope, offset: usize, granularity: SelectionGranularity, )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.
do_split_selection_into_lines(&mut self, text: &Rope)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.
do_drag(&mut self, text: &Rope, offset: usize, affinity: Affinity)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.
range_region( &self, text: &Rope, start: (usize, usize), offset: usize, granularity: SelectionGranularity, ) -> SelRegion572     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.
sel_regions(&self) -> &[SelRegion]590     pub fn sel_regions(&self) -> &[SelRegion] {
591         &self.selection
592     }
593 
594     /// Collapse all selections in this view into a single caret
collapse_selections(&mut self, text: &Rope)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).
is_point_in_selection(&self, offset: usize) -> bool603     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.
render_line( &self, client: &Client, styles: &StyleMap, text: &Rope, line: VisualLine, style_spans: &Spans<Style>, line_num: usize, ) -> Value608     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 
render_styles( &self, client: &Client, styles: &StyleMap, start: usize, end: usize, sel: &[(usize, usize)], hls: &Vec<Vec<(usize, usize)>>, style_spans: &Spans<Style>, ) -> Vec<isize>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 
get_or_def_style_id(&self, client: &Client, style_map: &StyleMap, style: &Style) -> usize716     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 
send_update_for_plan( &mut self, text: &Rope, client: &Client, styles: &StyleMap, style_spans: &Spans<Style>, plan: &RenderPlan, pristine: bool, )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.
find_status(&self, text: &Rope, matches_only: bool) -> Vec<FindStatus>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.
render_if_dirty( &mut self, text: &Rope, client: &Client, styles: &StyleMap, style_spans: &Spans<Style>, pristine: bool, )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.
request_lines( &mut self, text: &Rope, client: &Client, styles: &StyleMap, style_spans: &Spans<Style>, first_line: usize, last_line: usize, pristine: bool, )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.
set_dirty(&mut self, text: &Rope)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 
offset_to_line_col(&self, text: &Rope, offset: usize) -> (usize, usize)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 
line_col_to_offset(&self, text: &Rope, line: usize, col: usize) -> usize916     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.
interval_of_visible_region(&self, text: &Rope) -> Interval939     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.
line_of_offset(&self, text: &Rope, offset: usize) -> usize948     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.
offset_of_line(&self, text: &Rope, line: usize) -> usize953     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.
rewrap( &mut self, text: &Rope, width_cache: &mut WidthCache, client: &Client, spans: &Spans<Style>, )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.
after_edit( &mut self, text: &Rope, last_text: &Rope, delta: &RopeDelta, client: &Client, width_cache: &mut WidthCache, drift: InsertDrift, )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 
do_selection_for_find(&mut self, text: &Rope, case_sensitive: bool)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 
add_find(&mut self)1042     fn add_find(&mut self) {
1043         let id = self.find_id_counter.next();
1044         self.find.push(Find::new(id));
1045     }
1046 
set_find(&mut self, text: &Rope, queries: Vec<FindQuery>)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 
do_find(&mut self, text: &Rope)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.
do_find_next( &mut self, text: &Rope, reverse: bool, wrap: bool, allow_same: bool, modify_selection: &SelectionModifier, )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.
do_find_all(&mut self, text: &Rope)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.
select_next_occurrence( &mut self, text: &Rope, reverse: bool, wrapped: bool, _allow_same: bool, modify_selection: &SelectionModifier, )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 
do_set_replace(&mut self, chars: String, preserve_case: bool)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 
do_selection_for_replace(&mut self, text: &Rope)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.
get_line_range(&self, text: &Rope, region: &SelRegion) -> Range<usize>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 
get_caret_offset(&self) -> Option<usize>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 
1285 impl View {
1286     /// Exposed for benchmarking
1287     #[doc(hidden)]
debug_force_rewrap_cols(&mut self, text: &Rope, cols: usize)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
clamp(x: usize, min: usize, max: usize) -> usize1300 fn 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)]
1311 mod tests {
1312     use super::*;
1313     use crate::rpc::FindQuery;
1314 
1315     #[test]
incremental_find_update()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]
incremental_find_codepoint_boundary()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]
selection_for_find()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]
find_next()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]
find_all()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]
multi_queries_find_next()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]
multi_queries_find_all()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 }
1532