xi_core_lib/
editor.rs

1// Copyright 2016 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::borrow::{Borrow, Cow};
16use std::cmp::min;
17use std::collections::BTreeSet;
18
19use serde_json::Value;
20
21use xi_rope::diff::{Diff, LineHashDiff};
22use xi_rope::engine::{Engine, RevId, RevToken};
23use xi_rope::rope::count_newlines;
24use xi_rope::spans::SpansBuilder;
25use xi_rope::{Cursor, DeltaBuilder, Interval, LinesMetric, Rope, RopeDelta, Transformer};
26use xi_trace::{trace_block, trace_payload};
27
28use crate::annotations::{AnnotationType, Annotations};
29use crate::config::BufferItems;
30use crate::edit_types::BufferEvent;
31use crate::event_context::MAX_SIZE_LIMIT;
32use crate::layers::Layers;
33use crate::movement::{region_movement, Movement};
34use crate::plugins::rpc::{DataSpan, GetDataResponse, PluginEdit, ScopeSpan, TextUnit};
35use crate::plugins::PluginId;
36use crate::rpc::SelectionModifier;
37use crate::selection::{InsertDrift, SelRegion, Selection};
38use crate::styles::ThemeStyleMap;
39use crate::view::{Replace, View};
40use crate::word_boundaries::WordCursor;
41
42#[cfg(not(feature = "ledger"))]
43pub struct SyncStore;
44use crate::backspace::offset_for_delete_backwards;
45#[cfg(feature = "ledger")]
46use fuchsia::sync::SyncStore;
47
48// TODO This could go much higher without issue but while developing it is
49// better to keep it low to expose bugs in the GC during casual testing.
50const MAX_UNDOS: usize = 20;
51
52enum IndentDirection {
53    In,
54    Out,
55}
56
57pub struct Editor {
58    /// The contents of the buffer.
59    text: Rope,
60    /// The CRDT engine, which tracks edit history and manages concurrent edits.
61    engine: Engine,
62
63    /// The most recent revision.
64    last_rev_id: RevId,
65    /// The revision of the last save.
66    pristine_rev_id: RevId,
67    undo_group_id: usize,
68    /// Undo groups that may still be toggled
69    live_undos: Vec<usize>,
70    /// The index of the current undo; subsequent undos are currently 'undone'
71    /// (but may be redone)
72    cur_undo: usize,
73    /// undo groups that are undone
74    undos: BTreeSet<usize>,
75    /// undo groups that are no longer live and should be gc'ed
76    gc_undos: BTreeSet<usize>,
77    force_undo_group: bool,
78
79    this_edit_type: EditType,
80    last_edit_type: EditType,
81
82    revs_in_flight: usize,
83
84    /// Used only on Fuchsia for syncing
85    #[allow(dead_code)]
86    sync_store: Option<SyncStore>,
87    #[allow(dead_code)]
88    last_synced_rev: RevId,
89
90    layers: Layers,
91}
92
93impl Editor {
94    /// Creates a new `Editor` with a new empty buffer.
95    pub fn new() -> Editor {
96        Self::with_text("")
97    }
98
99    /// Creates a new `Editor`, loading text into a new buffer.
100    pub fn with_text<T: Into<Rope>>(text: T) -> Editor {
101        let engine = Engine::new(text.into());
102        let buffer = engine.get_head().clone();
103        let last_rev_id = engine.get_head_rev_id();
104
105        Editor {
106            text: buffer,
107            engine,
108            last_rev_id,
109            pristine_rev_id: last_rev_id,
110            undo_group_id: 1,
111            // GC only works on undone edits or prefixes of the visible edits,
112            // but initial file loading can create an edit with undo group 0,
113            // so we want to collect that as part of the prefix.
114            live_undos: vec![0],
115            cur_undo: 1,
116            undos: BTreeSet::new(),
117            gc_undos: BTreeSet::new(),
118            force_undo_group: false,
119            last_edit_type: EditType::Other,
120            this_edit_type: EditType::Other,
121            layers: Layers::default(),
122            revs_in_flight: 0,
123            sync_store: None,
124            last_synced_rev: last_rev_id,
125        }
126    }
127
128    pub(crate) fn get_buffer(&self) -> &Rope {
129        &self.text
130    }
131
132    pub(crate) fn get_layers(&self) -> &Layers {
133        &self.layers
134    }
135
136    pub(crate) fn get_layers_mut(&mut self) -> &mut Layers {
137        &mut self.layers
138    }
139
140    pub(crate) fn get_head_rev_token(&self) -> u64 {
141        self.engine.get_head_rev_id().token()
142    }
143
144    pub(crate) fn get_edit_type(&self) -> EditType {
145        self.this_edit_type
146    }
147
148    pub(crate) fn get_active_undo_group(&self) -> usize {
149        *self.live_undos.last().unwrap_or(&0)
150    }
151
152    pub(crate) fn update_edit_type(&mut self) {
153        self.last_edit_type = self.this_edit_type;
154        self.this_edit_type = EditType::Other
155    }
156
157    pub(crate) fn set_pristine(&mut self) {
158        self.pristine_rev_id = self.engine.get_head_rev_id();
159    }
160
161    pub(crate) fn is_pristine(&self) -> bool {
162        self.engine.is_equivalent_revision(self.pristine_rev_id, self.engine.get_head_rev_id())
163    }
164
165    /// Set whether or not edits are forced into the same undo group rather than being split by
166    /// their EditType.
167    ///
168    /// This is used for things such as recording playback, where you don't want the
169    /// individual events to be undoable, but instead the entire playback should be.
170    pub(crate) fn set_force_undo_group(&mut self, force_undo_group: bool) {
171        trace_payload("Editor::set_force_undo_group", &["core"], force_undo_group.to_string());
172        self.force_undo_group = force_undo_group;
173    }
174
175    /// Sets this Editor's contents to `text`, preserving undo state and cursor
176    /// position when possible.
177    pub fn reload(&mut self, text: Rope) {
178        let delta = LineHashDiff::compute_delta(self.get_buffer(), &text);
179        self.add_delta(delta);
180        self.set_pristine();
181    }
182
183    // each outstanding plugin edit represents a rev_in_flight.
184    pub fn increment_revs_in_flight(&mut self) {
185        self.revs_in_flight += 1;
186    }
187
188    // GC of CRDT engine is deferred until all plugins have acknowledged the new rev,
189    // so when the ack comes back, potentially trigger GC.
190    pub fn dec_revs_in_flight(&mut self) {
191        self.revs_in_flight -= 1;
192        self.gc_undos();
193    }
194
195    fn insert<T: Into<Rope>>(&mut self, view: &View, text: T) {
196        let rope = text.into();
197        let mut builder = DeltaBuilder::new(self.text.len());
198        for region in view.sel_regions() {
199            let iv = Interval::new(region.min(), region.max());
200            builder.replace(iv, rope.clone());
201        }
202        self.add_delta(builder.build());
203    }
204
205    /// Leaves the current selection untouched, but surrounds it with two insertions.
206    fn surround<BT, AT>(&mut self, view: &View, before_text: BT, after_text: AT)
207    where
208        BT: Into<Rope>,
209        AT: Into<Rope>,
210    {
211        let mut builder = DeltaBuilder::new(self.text.len());
212        let before_rope = before_text.into();
213        let after_rope = after_text.into();
214        for region in view.sel_regions() {
215            let before_iv = Interval::new(region.min(), region.min());
216            builder.replace(before_iv, before_rope.clone());
217            let after_iv = Interval::new(region.max(), region.max());
218            builder.replace(after_iv, after_rope.clone());
219        }
220        self.add_delta(builder.build());
221    }
222
223    /// Applies a delta to the text, and updates undo state.
224    ///
225    /// Records the delta into the CRDT engine so that it can be undone. Also
226    /// contains the logic for merging edits into the same undo group. At call
227    /// time, self.this_edit_type should be set appropriately.
228    ///
229    /// This method can be called multiple times, accumulating deltas that will
230    /// be committed at once with `commit_delta`. Note that it does not update
231    /// the views. Thus, view-associated state such as the selection and line
232    /// breaks are to be considered invalid after this method, until the
233    /// `commit_delta` call.
234    fn add_delta(&mut self, delta: RopeDelta) {
235        let head_rev_id = self.engine.get_head_rev_id();
236        let undo_group = self.calculate_undo_group();
237        self.last_edit_type = self.this_edit_type;
238        let priority = 0x10000;
239        self.engine.edit_rev(priority, undo_group, head_rev_id.token(), delta);
240        self.text = self.engine.get_head().clone();
241    }
242
243    pub(crate) fn calculate_undo_group(&mut self) -> usize {
244        let has_undos = !self.live_undos.is_empty();
245        let force_undo_group = self.force_undo_group;
246        let is_unbroken_group = !self.this_edit_type.breaks_undo_group(self.last_edit_type);
247
248        if has_undos && (force_undo_group || is_unbroken_group) {
249            *self.live_undos.last().unwrap()
250        } else {
251            let undo_group = self.undo_group_id;
252            self.gc_undos.extend(&self.live_undos[self.cur_undo..]);
253            self.live_undos.truncate(self.cur_undo);
254            self.live_undos.push(undo_group);
255            if self.live_undos.len() <= MAX_UNDOS {
256                self.cur_undo += 1;
257            } else {
258                self.gc_undos.insert(self.live_undos.remove(0));
259            }
260            self.undo_group_id += 1;
261            undo_group
262        }
263    }
264
265    /// generates a delta from a plugin's response and applies it to the buffer.
266    pub fn apply_plugin_edit(&mut self, edit: PluginEdit) {
267        let _t = trace_block("Editor::apply_plugin_edit", &["core"]);
268        //TODO: get priority working, so that plugin edits don't necessarily move cursor
269        let PluginEdit { rev, delta, priority, undo_group, .. } = edit;
270        let priority = priority as usize;
271        let undo_group = undo_group.unwrap_or_else(|| self.calculate_undo_group());
272        match self.engine.try_edit_rev(priority, undo_group, rev, delta) {
273            Err(e) => error!("Error applying plugin edit: {}", e),
274            Ok(_) => self.text = self.engine.get_head().clone(),
275        };
276    }
277
278    /// Commits the current delta. If the buffer has changed, returns
279    /// a 3-tuple containing the delta representing the changes, the previous
280    /// buffer, and an `InsertDrift` enum describing the correct selection update
281    /// behaviour.
282    pub(crate) fn commit_delta(&mut self) -> Option<(RopeDelta, Rope, InsertDrift)> {
283        let _t = trace_block("Editor::commit_delta", &["core"]);
284
285        if self.engine.get_head_rev_id() == self.last_rev_id {
286            return None;
287        }
288
289        let last_token = self.last_rev_id.token();
290        let delta = self.engine.try_delta_rev_head(last_token).expect("last_rev not found");
291        // TODO (performance): it's probably quicker to stash last_text
292        // rather than resynthesize it.
293        let last_text = self.engine.get_rev(last_token).expect("last_rev not found");
294
295        // Transpose can rotate characters inside of a selection; this is why it's an Inside edit.
296        // Surround adds characters on either side of a selection, that's why it's an Outside edit.
297        let drift = match self.this_edit_type {
298            EditType::Transpose => InsertDrift::Inside,
299            EditType::Surround => InsertDrift::Outside,
300            _ => InsertDrift::Default,
301        };
302        self.layers.update_all(&delta);
303
304        self.last_rev_id = self.engine.get_head_rev_id();
305        self.sync_state_changed();
306        Some((delta, last_text, drift))
307    }
308
309    /// Attempts to find the delta from head for the given `RevToken`. Returns
310    /// `None` if the revision is not found, so this result should be checked if
311    /// the revision is coming from a plugin.
312    pub(crate) fn delta_rev_head(&self, target_rev_id: RevToken) -> Option<RopeDelta> {
313        self.engine.try_delta_rev_head(target_rev_id).ok()
314    }
315
316    #[cfg(not(target_os = "fuchsia"))]
317    fn gc_undos(&mut self) {
318        if self.revs_in_flight == 0 && !self.gc_undos.is_empty() {
319            self.engine.gc(&self.gc_undos);
320            self.undos = &self.undos - &self.gc_undos;
321            self.gc_undos.clear();
322        }
323    }
324
325    #[cfg(target_os = "fuchsia")]
326    fn gc_undos(&mut self) {
327        // Never run GC on Fuchsia so that peers don't invalidate our
328        // last_rev_id and so that merge will work.
329    }
330
331    pub fn merge_new_state(&mut self, new_engine: Engine) {
332        self.engine.merge(&new_engine);
333        self.text = self.engine.get_head().clone();
334        // TODO: better undo semantics. This only implements separate undo
335        // histories for low concurrency.
336        self.undo_group_id = self.engine.max_undo_group_id() + 1;
337        self.last_synced_rev = self.engine.get_head_rev_id();
338        self.commit_delta();
339        //self.render();
340        //FIXME: render after fuchsia sync
341    }
342
343    /// See `Engine::set_session_id`. Only useful for Fuchsia sync.
344    pub fn set_session_id(&mut self, session: (u64, u32)) {
345        self.engine.set_session_id(session);
346    }
347
348    #[cfg(feature = "ledger")]
349    pub fn set_sync_store(&mut self, sync_store: SyncStore) {
350        self.sync_store = Some(sync_store);
351    }
352
353    #[cfg(not(feature = "ledger"))]
354    pub fn sync_state_changed(&mut self) {}
355
356    #[cfg(feature = "ledger")]
357    pub fn sync_state_changed(&mut self) {
358        if let Some(sync_store) = self.sync_store.as_mut() {
359            // we don't want to sync right after recieving a new merge
360            if self.last_synced_rev != self.engine.get_head_rev_id() {
361                self.last_synced_rev = self.engine.get_head_rev_id();
362                sync_store.state_changed();
363            }
364        }
365    }
366
367    #[cfg(feature = "ledger")]
368    pub fn transaction_ready(&mut self) {
369        if let Some(sync_store) = self.sync_store.as_mut() {
370            sync_store.commit_transaction(&self.engine);
371        }
372    }
373
374    fn delete_backward(&mut self, view: &View, config: &BufferItems) {
375        // TODO: this function is workable but probably overall code complexity
376        // could be improved by implementing a "backspace" movement instead.
377        let mut builder = DeltaBuilder::new(self.text.len());
378        for region in view.sel_regions() {
379            let start = offset_for_delete_backwards(&view, &region, &self.text, &config);
380            let iv = Interval::new(start, region.max());
381            if !iv.is_empty() {
382                builder.delete(iv);
383            }
384        }
385
386        if !builder.is_empty() {
387            self.this_edit_type = EditType::Delete;
388            self.add_delta(builder.build());
389        }
390    }
391
392    /// Common logic for a number of delete methods. For each region in the
393    /// selection, if the selection is a caret, delete the region between
394    /// the caret and the movement applied to the caret, otherwise delete
395    /// the region.
396    ///
397    /// If `save` is set, save the deleted text into the kill ring.
398    fn delete_by_movement(
399        &mut self,
400        view: &View,
401        movement: Movement,
402        save: bool,
403        kill_ring: &mut Rope,
404    ) {
405        // We compute deletions as a selection because the merge logic
406        // is convenient. Another possibility would be to make the delta
407        // builder able to handle overlapping deletions (with union semantics).
408        let mut deletions = Selection::new();
409        for &r in view.sel_regions() {
410            if r.is_caret() {
411                let new_region = region_movement(movement, r, view, &self.text, true);
412                deletions.add_region(new_region);
413            } else {
414                deletions.add_region(r);
415            }
416        }
417        if save {
418            let saved = self.extract_sel_regions(&deletions).unwrap_or_default();
419            *kill_ring = Rope::from(saved);
420        }
421        self.delete_sel_regions(&deletions);
422    }
423
424    /// Deletes the given regions.
425    fn delete_sel_regions(&mut self, sel_regions: &[SelRegion]) {
426        let mut builder = DeltaBuilder::new(self.text.len());
427        for region in sel_regions {
428            let iv = Interval::new(region.min(), region.max());
429            if !iv.is_empty() {
430                builder.delete(iv);
431            }
432        }
433        if !builder.is_empty() {
434            self.this_edit_type = EditType::Delete;
435            self.add_delta(builder.build());
436        }
437    }
438
439    /// Extracts non-caret selection regions into a string,
440    /// joining multiple regions with newlines.
441    fn extract_sel_regions(&self, sel_regions: &[SelRegion]) -> Option<Cow<str>> {
442        let mut saved = None;
443        for region in sel_regions {
444            if !region.is_caret() {
445                let val = self.text.slice_to_cow(region);
446                match saved {
447                    None => saved = Some(val),
448                    Some(ref mut s) => {
449                        s.to_mut().push('\n');
450                        s.to_mut().push_str(&val);
451                    }
452                }
453            }
454        }
455        saved
456    }
457
458    fn insert_newline(&mut self, view: &View, config: &BufferItems) {
459        self.this_edit_type = EditType::InsertNewline;
460        self.insert(view, &config.line_ending);
461    }
462
463    fn insert_tab(&mut self, view: &View, config: &BufferItems) {
464        self.this_edit_type = EditType::InsertChars;
465        let mut builder = DeltaBuilder::new(self.text.len());
466        let const_tab_text = self.get_tab_text(config, None);
467
468        if view.sel_regions().len() > 1 {
469            // if we indent multiple regions or multiple lines (below),
470            // we treat this as an indentation adjustment; otherwise it is
471            // just inserting text.
472            self.this_edit_type = EditType::Indent;
473        }
474
475        for region in view.sel_regions() {
476            let line_range = view.get_line_range(&self.text, region);
477
478            if line_range.len() > 1 {
479                self.this_edit_type = EditType::Indent;
480                for line in line_range {
481                    let offset = view.line_col_to_offset(&self.text, line, 0);
482                    let iv = Interval::new(offset, offset);
483                    builder.replace(iv, Rope::from(const_tab_text));
484                }
485            } else {
486                let (_, col) = view.offset_to_line_col(&self.text, region.start);
487                let mut tab_size = config.tab_size;
488                tab_size = tab_size - (col % tab_size);
489                let tab_text = self.get_tab_text(config, Some(tab_size));
490
491                let iv = Interval::new(region.min(), region.max());
492                builder.replace(iv, Rope::from(tab_text));
493            }
494        }
495        self.add_delta(builder.build());
496    }
497
498    /// Indents or outdents lines based on selection and user's tab settings.
499    /// Uses a BTreeSet to holds the collection of lines to modify.
500    /// Preserves cursor position and current selection as much as possible.
501    /// Tries to have behavior consistent with other editors like Atom,
502    /// Sublime and VSCode, with non-caret selections not being modified.
503    fn modify_indent(&mut self, view: &View, config: &BufferItems, direction: IndentDirection) {
504        self.this_edit_type = EditType::Indent;
505        let mut lines = BTreeSet::new();
506        let tab_text = self.get_tab_text(config, None);
507        for region in view.sel_regions() {
508            let line_range = view.get_line_range(&self.text, region);
509            for line in line_range {
510                lines.insert(line);
511            }
512        }
513        match direction {
514            IndentDirection::In => self.indent(view, lines, tab_text),
515            IndentDirection::Out => self.outdent(view, lines, tab_text),
516        };
517    }
518
519    fn indent(&mut self, view: &View, lines: BTreeSet<usize>, tab_text: &str) {
520        let mut builder = DeltaBuilder::new(self.text.len());
521        for line in lines {
522            let offset = view.line_col_to_offset(&self.text, line, 0);
523            let interval = Interval::new(offset, offset);
524            builder.replace(interval, Rope::from(tab_text));
525        }
526        self.this_edit_type = EditType::InsertChars;
527        self.add_delta(builder.build());
528    }
529
530    fn outdent(&mut self, view: &View, lines: BTreeSet<usize>, tab_text: &str) {
531        let mut builder = DeltaBuilder::new(self.text.len());
532        for line in lines {
533            let offset = view.line_col_to_offset(&self.text, line, 0);
534            let tab_offset = view.line_col_to_offset(&self.text, line, tab_text.len());
535            let interval = Interval::new(offset, tab_offset);
536            let leading_slice = self.text.slice_to_cow(interval.start()..interval.end());
537            if leading_slice == tab_text {
538                builder.delete(interval);
539            } else if let Some(first_char_col) = leading_slice.find(|c: char| !c.is_whitespace()) {
540                let first_char_offset = view.line_col_to_offset(&self.text, line, first_char_col);
541                let interval = Interval::new(offset, first_char_offset);
542                builder.delete(interval);
543            }
544        }
545        self.this_edit_type = EditType::Delete;
546        self.add_delta(builder.build());
547    }
548
549    fn get_tab_text(&self, config: &BufferItems, tab_size: Option<usize>) -> &'static str {
550        let tab_size = tab_size.unwrap_or(config.tab_size);
551        let tab_text = if config.translate_tabs_to_spaces { n_spaces(tab_size) } else { "\t" };
552
553        tab_text
554    }
555
556    fn do_insert(&mut self, view: &View, config: &BufferItems, chars: &str) {
557        let pair_search = config.surrounding_pairs.iter().find(|pair| pair.0 == chars);
558        let caret_exists = view.sel_regions().iter().any(|region| region.is_caret());
559        if let (Some(pair), false) = (pair_search, caret_exists) {
560            self.this_edit_type = EditType::Surround;
561            self.surround(view, pair.0.to_string(), pair.1.to_string());
562        } else {
563            self.this_edit_type = EditType::InsertChars;
564            self.insert(view, chars);
565        }
566    }
567
568    fn do_paste(&mut self, view: &View, chars: &str) {
569        if view.sel_regions().len() == 1 || view.sel_regions().len() != count_lines(chars) {
570            self.insert(view, chars);
571        } else {
572            let mut builder = DeltaBuilder::new(self.text.len());
573            for (sel, line) in view.sel_regions().iter().zip(chars.lines()) {
574                let iv = Interval::new(sel.min(), sel.max());
575                builder.replace(iv, line.into());
576            }
577            self.add_delta(builder.build());
578        }
579    }
580
581    pub(crate) fn do_cut(&mut self, view: &mut View) -> Value {
582        let result = self.do_copy(view);
583        self.delete_sel_regions(&view.sel_regions());
584        result
585    }
586
587    pub(crate) fn do_copy(&self, view: &View) -> Value {
588        if let Some(val) = self.extract_sel_regions(view.sel_regions()) {
589            Value::String(val.into_owned())
590        } else {
591            Value::Null
592        }
593    }
594
595    fn do_undo(&mut self) {
596        if self.cur_undo > 1 {
597            self.cur_undo -= 1;
598            assert!(self.undos.insert(self.live_undos[self.cur_undo]));
599            self.this_edit_type = EditType::Undo;
600            self.update_undos();
601        }
602    }
603
604    fn do_redo(&mut self) {
605        if self.cur_undo < self.live_undos.len() {
606            assert!(self.undos.remove(&self.live_undos[self.cur_undo]));
607            self.cur_undo += 1;
608            self.this_edit_type = EditType::Redo;
609            self.update_undos();
610        }
611    }
612
613    fn update_undos(&mut self) {
614        self.engine.undo(self.undos.clone());
615        self.text = self.engine.get_head().clone();
616    }
617
618    fn sel_region_to_interval_and_rope(&self, region: SelRegion) -> (Interval, Rope) {
619        let as_interval = Interval::new(region.min(), region.max());
620        let interval_rope = self.text.subseq(as_interval);
621        (as_interval, interval_rope)
622    }
623
624    fn do_transpose(&mut self, view: &View) {
625        let mut builder = DeltaBuilder::new(self.text.len());
626        let mut last = 0;
627        let mut optional_previous_selection: Option<(Interval, Rope)> =
628            last_selection_region(view.sel_regions())
629                .map(|&region| self.sel_region_to_interval_and_rope(region));
630
631        for &region in view.sel_regions() {
632            if region.is_caret() {
633                let mut middle = region.end;
634                let mut start = self.text.prev_grapheme_offset(middle).unwrap_or(0);
635                let mut end = self.text.next_grapheme_offset(middle).unwrap_or(middle);
636
637                // Note: this matches Emac's behavior. It swaps last
638                // two characters of line if at end of line.
639                if start >= last {
640                    let end_line_offset =
641                        view.offset_of_line(&self.text, view.line_of_offset(&self.text, end));
642                    // include end != self.text.len() because if the editor is entirely empty, we dont' want to pull from empty space
643                    if (end == middle || end == end_line_offset) && end != self.text.len() {
644                        middle = start;
645                        start = self.text.prev_grapheme_offset(middle).unwrap_or(0);
646                        end = middle.wrapping_add(1);
647                    }
648
649                    let interval = Interval::new(start, end);
650                    let before = self.text.slice_to_cow(start..middle);
651                    let after = self.text.slice_to_cow(middle..end);
652                    let swapped: String = [after, before].concat();
653                    builder.replace(interval, Rope::from(swapped));
654                    last = end;
655                }
656            } else if let Some(previous_selection) = optional_previous_selection {
657                let current_interval = self.sel_region_to_interval_and_rope(region);
658                builder.replace(current_interval.0, previous_selection.1);
659                optional_previous_selection = Some(current_interval);
660            }
661        }
662        if !builder.is_empty() {
663            self.this_edit_type = EditType::Transpose;
664            self.add_delta(builder.build());
665        }
666    }
667
668    fn yank(&mut self, view: &View, kill_ring: &mut Rope) {
669        // TODO: if there are multiple cursors and the number of newlines
670        // is one less than the number of cursors, split and distribute one
671        // line per cursor.
672        self.insert(view, kill_ring.clone());
673    }
674
675    fn replace(&mut self, view: &mut View, replace_all: bool) {
676        if let Some(Replace { chars, .. }) = view.get_replace() {
677            // todo: implement preserve case
678            // store old selection because in case nothing is found the selection will be preserved
679            let mut old_selection = Selection::new();
680            for &region in view.sel_regions() {
681                old_selection.add_region(region);
682            }
683            view.collapse_selections(&self.text);
684
685            if replace_all {
686                view.do_find_all(&self.text);
687            } else {
688                view.do_find_next(&self.text, false, true, true, &SelectionModifier::Set);
689            }
690
691            match last_selection_region(view.sel_regions()) {
692                Some(_) => self.insert(view, chars),
693                None => return,
694            };
695        }
696    }
697
698    fn transform_text<F: Fn(&str) -> String>(&mut self, view: &View, transform_function: F) {
699        let mut builder = DeltaBuilder::new(self.text.len());
700
701        for region in view.sel_regions() {
702            let selected_text = self.text.slice_to_cow(region);
703            let interval = Interval::new(region.min(), region.max());
704            builder.replace(interval, Rope::from(transform_function(&selected_text)));
705        }
706        if !builder.is_empty() {
707            self.this_edit_type = EditType::Other;
708            self.add_delta(builder.build());
709        }
710    }
711
712    /// Changes the number(s) under the cursor(s) with the `transform_function`.
713    /// If there is a number next to or on the beginning of the region, then
714    /// this number will be replaced with the result of `transform_function` and
715    /// the cursor will be placed at the end of the number.
716    /// Some Examples with a increment `transform_function`:
717    ///
718    /// "|1234" -> "1235|"
719    /// "12|34" -> "1235|"
720    /// "-|12" -> "-11|"
721    /// "another number is 123|]" -> "another number is 124"
722    ///
723    /// This function also works fine with multiple regions.
724    fn change_number<F: Fn(i128) -> Option<i128>>(&mut self, view: &View, transform_function: F) {
725        let mut builder = DeltaBuilder::new(self.text.len());
726        for region in view.sel_regions() {
727            let mut cursor = WordCursor::new(&self.text, region.end);
728            let (mut start, end) = cursor.select_word();
729
730            // if the word begins with '-', then it is a negative number
731            if start > 0 && self.text.byte_at(start - 1) == (b'-') {
732                start -= 1;
733            }
734
735            let word = self.text.slice_to_cow(start..end);
736            if let Some(number) = word.parse::<i128>().ok().and_then(&transform_function) {
737                let interval = Interval::new(start, end);
738                builder.replace(interval, Rope::from(number.to_string()));
739            }
740        }
741
742        if !builder.is_empty() {
743            self.this_edit_type = EditType::Other;
744            self.add_delta(builder.build());
745        }
746    }
747
748    // capitalization behaviour is similar to behaviour in XCode
749    fn capitalize_text(&mut self, view: &mut View) {
750        let mut builder = DeltaBuilder::new(self.text.len());
751        let mut final_selection = Selection::new();
752
753        for &region in view.sel_regions() {
754            final_selection.add_region(SelRegion::new(region.max(), region.max()));
755            let mut word_cursor = WordCursor::new(&self.text, region.min());
756
757            loop {
758                // capitalize each word in the current selection
759                let (start, end) = word_cursor.select_word();
760
761                if start < end {
762                    let interval = Interval::new(start, end);
763                    let word = self.text.slice_to_cow(start..end);
764
765                    // first letter is uppercase, remaining letters are lowercase
766                    let (first_char, rest) = word.split_at(1);
767                    let capitalized_text =
768                        [first_char.to_uppercase(), rest.to_lowercase()].concat();
769                    builder.replace(interval, Rope::from(capitalized_text));
770                }
771
772                if word_cursor.next_boundary().is_none() || end > region.max() {
773                    break;
774                }
775            }
776        }
777
778        if !builder.is_empty() {
779            self.this_edit_type = EditType::Other;
780            self.add_delta(builder.build());
781        }
782
783        // at the end of the transformation carets are located at the end of the words that were
784        // transformed last in the selections
785        view.collapse_selections(&self.text);
786        view.set_selection(&self.text, final_selection);
787    }
788
789    fn duplicate_line(&mut self, view: &View, config: &BufferItems) {
790        let mut builder = DeltaBuilder::new(self.text.len());
791        // get affected lines or regions
792        let mut to_duplicate = BTreeSet::new();
793
794        for region in view.sel_regions() {
795            let (first_line, _) = view.offset_to_line_col(&self.text, region.min());
796            let line_start = view.offset_of_line(&self.text, first_line);
797
798            let mut cursor = match region.is_caret() {
799                true => Cursor::new(&self.text, line_start),
800                false => {
801                    // duplicate all lines together that are part of the same selections
802                    let (last_line, _) = view.offset_to_line_col(&self.text, region.max());
803                    let line_end = view.offset_of_line(&self.text, last_line);
804                    Cursor::new(&self.text, line_end)
805                }
806            };
807
808            if let Some(line_end) = cursor.next::<LinesMetric>() {
809                to_duplicate.insert((line_start, line_end));
810            }
811        }
812
813        for (start, end) in to_duplicate {
814            // insert duplicates
815            let iv = Interval::new(start, start);
816            builder.replace(iv, self.text.slice(start..end));
817
818            // last line does not have new line character so it needs to be manually added
819            if end == self.text.len() {
820                builder.replace(iv, Rope::from(&config.line_ending))
821            }
822        }
823
824        self.this_edit_type = EditType::Other;
825        self.add_delta(builder.build());
826    }
827
828    pub(crate) fn do_edit(
829        &mut self,
830        view: &mut View,
831        kill_ring: &mut Rope,
832        config: &BufferItems,
833        cmd: BufferEvent,
834    ) {
835        use self::BufferEvent::*;
836        match cmd {
837            Delete { movement, kill } => self.delete_by_movement(view, movement, kill, kill_ring),
838            Backspace => self.delete_backward(view, config),
839            Transpose => self.do_transpose(view),
840            Undo => self.do_undo(),
841            Redo => self.do_redo(),
842            Uppercase => self.transform_text(view, |s| s.to_uppercase()),
843            Lowercase => self.transform_text(view, |s| s.to_lowercase()),
844            Capitalize => self.capitalize_text(view),
845            Indent => self.modify_indent(view, config, IndentDirection::In),
846            Outdent => self.modify_indent(view, config, IndentDirection::Out),
847            InsertNewline => self.insert_newline(view, config),
848            InsertTab => self.insert_tab(view, config),
849            Insert(chars) => self.do_insert(view, config, &chars),
850            Paste(chars) => self.do_paste(view, &chars),
851            Yank => self.yank(view, kill_ring),
852            ReplaceNext => self.replace(view, false),
853            ReplaceAll => self.replace(view, true),
854            DuplicateLine => self.duplicate_line(view, config),
855            IncreaseNumber => self.change_number(view, |s| s.checked_add(1)),
856            DecreaseNumber => self.change_number(view, |s| s.checked_sub(1)),
857        }
858    }
859
860    pub fn theme_changed(&mut self, style_map: &ThemeStyleMap) {
861        self.layers.theme_changed(style_map);
862    }
863
864    pub fn plugin_n_lines(&self) -> usize {
865        self.text.measure::<LinesMetric>() + 1
866    }
867
868    pub fn update_spans(
869        &mut self,
870        view: &mut View,
871        plugin: PluginId,
872        start: usize,
873        len: usize,
874        spans: Vec<ScopeSpan>,
875        rev: RevToken,
876    ) {
877        let _t = trace_block("Editor::update_spans", &["core"]);
878        // TODO: more protection against invalid input
879        let mut start = start;
880        let mut end_offset = start + len;
881        let mut sb = SpansBuilder::new(len);
882        for span in spans {
883            sb.add_span(Interval::new(span.start, span.end), span.scope_id);
884        }
885        let mut spans = sb.build();
886        if rev != self.engine.get_head_rev_id().token() {
887            if let Ok(delta) = self.engine.try_delta_rev_head(rev) {
888                let mut transformer = Transformer::new(&delta);
889                let new_start = transformer.transform(start, false);
890                if !transformer.interval_untouched(Interval::new(start, end_offset)) {
891                    spans = spans.transform(start, end_offset, &mut transformer);
892                }
893                start = new_start;
894                end_offset = transformer.transform(end_offset, true);
895            } else {
896                error!("Revision {} not found", rev);
897            }
898        }
899        let iv = Interval::new(start, end_offset);
900        self.layers.update_layer(plugin, iv, spans);
901        view.invalidate_styles(&self.text, start, end_offset);
902    }
903
904    pub fn update_annotations(
905        &mut self,
906        view: &mut View,
907        plugin: PluginId,
908        start: usize,
909        len: usize,
910        annotation_spans: Vec<DataSpan>,
911        annotation_type: AnnotationType,
912        rev: RevToken,
913    ) {
914        let _t = trace_block("Editor::update_annotations", &["core"]);
915
916        let mut start = start;
917        let mut end_offset = start + len;
918        let mut sb = SpansBuilder::new(len);
919        for span in annotation_spans {
920            sb.add_span(Interval::new(span.start, span.end), span.data);
921        }
922        let mut spans = sb.build();
923        if rev != self.engine.get_head_rev_id().token() {
924            if let Ok(delta) = self.engine.try_delta_rev_head(rev) {
925                let mut transformer = Transformer::new(&delta);
926                let new_start = transformer.transform(start, false);
927                if !transformer.interval_untouched(Interval::new(start, end_offset)) {
928                    spans = spans.transform(start, end_offset, &mut transformer);
929                }
930                start = new_start;
931                end_offset = transformer.transform(end_offset, true);
932            } else {
933                error!("Revision {} not found", rev);
934            }
935        }
936        let iv = Interval::new(start, end_offset);
937        view.update_annotations(plugin, iv, Annotations { items: spans, annotation_type });
938    }
939
940    pub(crate) fn get_rev(&self, rev: RevToken) -> Option<Cow<Rope>> {
941        let text_cow = if rev == self.engine.get_head_rev_id().token() {
942            Cow::Borrowed(&self.text)
943        } else {
944            match self.engine.get_rev(rev) {
945                None => return None,
946                Some(text) => Cow::Owned(text),
947            }
948        };
949
950        Some(text_cow)
951    }
952
953    pub fn plugin_get_data(
954        &self,
955        start: usize,
956        unit: TextUnit,
957        max_size: usize,
958        rev: RevToken,
959    ) -> Option<GetDataResponse> {
960        let _t = trace_block("Editor::plugin_get_data", &["core"]);
961        let text_cow = self.get_rev(rev)?;
962        let text = &text_cow;
963        // convert our offset into a valid byte offset
964        let offset = unit.resolve_offset(text.borrow(), start)?;
965
966        let max_size = min(max_size, MAX_SIZE_LIMIT);
967        let mut end_off = offset.saturating_add(max_size);
968        if end_off >= text.len() {
969            end_off = text.len();
970        } else {
971            // Snap end to codepoint boundary.
972            end_off = text.prev_codepoint_offset(end_off + 1).unwrap();
973        }
974
975        let chunk = text.slice_to_cow(offset..end_off).into_owned();
976        let first_line = text.line_of_offset(offset);
977        let first_line_offset = offset - text.offset_of_line(first_line);
978
979        Some(GetDataResponse { chunk, offset, first_line, first_line_offset })
980    }
981}
982
983#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
984#[serde(rename_all = "snake_case")]
985pub enum EditType {
986    /// A catchall for edits that don't fit elsewhere, and which should
987    /// always have their own undo groups; used for things like cut/copy/paste.
988    Other,
989    /// An insert from the keyboard/IME (not a paste or a yank).
990    #[serde(rename = "insert")]
991    InsertChars,
992    #[serde(rename = "newline")]
993    InsertNewline,
994    /// An indentation adjustment.
995    Indent,
996    Delete,
997    Undo,
998    Redo,
999    Transpose,
1000    Surround,
1001}
1002
1003impl EditType {
1004    /// Checks whether a new undo group should be created between two edits.
1005    fn breaks_undo_group(self, previous: EditType) -> bool {
1006        self == EditType::Other || self == EditType::Transpose || self != previous
1007    }
1008}
1009
1010fn last_selection_region(regions: &[SelRegion]) -> Option<&SelRegion> {
1011    for region in regions.iter().rev() {
1012        if !region.is_caret() {
1013            return Some(region);
1014        }
1015    }
1016    None
1017}
1018
1019fn n_spaces(n: usize) -> &'static str {
1020    let spaces = "                                ";
1021    assert!(n <= spaces.len());
1022    &spaces[..n]
1023}
1024
1025/// Counts the number of lines in the string, not including any trailing newline.
1026fn count_lines(s: &str) -> usize {
1027    let mut newlines = count_newlines(s);
1028    if s.as_bytes().last() == Some(&0xa) {
1029        newlines -= 1;
1030    }
1031    1 + newlines
1032}
1033
1034#[cfg(test)]
1035mod tests {
1036    use super::*;
1037
1038    #[test]
1039    fn plugin_edit() {
1040        let base_text = "hello";
1041        let mut editor = Editor::with_text(base_text);
1042        let mut builder = DeltaBuilder::new(base_text.len());
1043        builder.replace(0..0, "s".into());
1044        let delta = builder.build();
1045        let rev = editor.get_head_rev_token();
1046
1047        let edit_one = PluginEdit {
1048            rev,
1049            delta,
1050            priority: 55,
1051            after_cursor: false,
1052            undo_group: None,
1053            author: "plugin_one".into(),
1054        };
1055
1056        editor.apply_plugin_edit(edit_one.clone());
1057        editor.apply_plugin_edit(edit_one);
1058
1059        assert_eq!(editor.get_buffer().to_string(), "sshello");
1060    }
1061
1062}