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 ®ion 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 ®ion 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