xi_core_lib/
event_context.rs

1// Copyright 2018 The xi-editor Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A container for the state relevant to a single event.
16
17use std::cell::RefCell;
18use std::iter;
19use std::ops::Range;
20use std::path::Path;
21use std::time::{Duration, Instant};
22
23use serde_json::{self, Value};
24
25use xi_rope::{Cursor, Interval, LinesMetric, Rope, RopeDelta};
26use xi_rpc::{Error as RpcError, RemoteError};
27use xi_trace::trace_block;
28
29use crate::plugins::rpc::{
30    ClientPluginInfo, Hover, PluginBufferInfo, PluginNotification, PluginRequest, PluginUpdate,
31};
32use crate::rpc::{EditNotification, EditRequest, LineRange, Position as ClientPosition};
33
34use crate::client::Client;
35use crate::config::{BufferItems, Table};
36use crate::edit_types::{EventDomain, SpecialEvent};
37use crate::editor::Editor;
38use crate::file::FileInfo;
39use crate::plugins::Plugin;
40use crate::recorder::Recorder;
41use crate::selection::InsertDrift;
42use crate::styles::ThemeStyleMap;
43use crate::syntax::LanguageId;
44use crate::tabs::{
45    BufferId, PluginId, ViewId, FIND_VIEW_IDLE_MASK, RENDER_VIEW_IDLE_MASK, REWRAP_VIEW_IDLE_MASK,
46};
47use crate::view::View;
48use crate::width_cache::WidthCache;
49use crate::WeakXiCore;
50
51// Maximum returned result from plugin get_data RPC.
52pub const MAX_SIZE_LIMIT: usize = 1024 * 1024;
53
54//TODO: tune this. a few ms can make a big difference. We may in the future
55//want to make this tuneable at runtime, or to be configured by the client.
56/// The render delay after an edit occurs; plugin updates received in this
57/// window will be sent to the view along with the edit.
58const RENDER_DELAY: Duration = Duration::from_millis(2);
59
60/// A collection of all the state relevant for handling a particular event.
61///
62/// This is created dynamically for each event that arrives to the core,
63/// such as a user-initiated edit or style updates from a plugin.
64pub struct EventContext<'a> {
65    pub(crate) view_id: ViewId,
66    pub(crate) buffer_id: BufferId,
67    pub(crate) editor: &'a RefCell<Editor>,
68    pub(crate) info: Option<&'a FileInfo>,
69    pub(crate) config: &'a BufferItems,
70    pub(crate) recorder: &'a RefCell<Recorder>,
71    pub(crate) language: LanguageId,
72    pub(crate) view: &'a RefCell<View>,
73    pub(crate) siblings: Vec<&'a RefCell<View>>,
74    pub(crate) plugins: Vec<&'a Plugin>,
75    pub(crate) client: &'a Client,
76    pub(crate) style_map: &'a RefCell<ThemeStyleMap>,
77    pub(crate) width_cache: &'a RefCell<WidthCache>,
78    pub(crate) kill_ring: &'a RefCell<Rope>,
79    pub(crate) weak_core: &'a WeakXiCore,
80}
81
82impl<'a> EventContext<'a> {
83    /// Executes a closure with mutable references to the editor and the view,
84    /// common in edit actions that modify the text.
85    pub(crate) fn with_editor<R, F>(&mut self, f: F) -> R
86    where
87        F: FnOnce(&mut Editor, &mut View, &mut Rope, &BufferItems) -> R,
88    {
89        let mut editor = self.editor.borrow_mut();
90        let mut view = self.view.borrow_mut();
91        let mut kill_ring = self.kill_ring.borrow_mut();
92        f(&mut editor, &mut view, &mut kill_ring, &self.config)
93    }
94
95    /// Executes a closure with a mutable reference to the view and a reference
96    /// to the current text. This is common to most edits that just modify
97    /// selection or viewport state.
98    fn with_view<R, F>(&mut self, f: F) -> R
99    where
100        F: FnOnce(&mut View, &Rope) -> R,
101    {
102        let editor = self.editor.borrow();
103        let mut view = self.view.borrow_mut();
104        f(&mut view, editor.get_buffer())
105    }
106
107    fn with_each_plugin<F: FnMut(&&Plugin)>(&self, f: F) {
108        self.plugins.iter().for_each(f)
109    }
110
111    pub(crate) fn do_edit(&mut self, cmd: EditNotification) {
112        let event: EventDomain = cmd.into();
113
114        {
115            // Handle recording-- clone every non-toggle and play event into the recording buffer
116            let mut recorder = self.recorder.borrow_mut();
117            match (recorder.is_recording(), &event) {
118                (_, EventDomain::Special(SpecialEvent::ToggleRecording(recording_name))) => {
119                    recorder.toggle_recording(recording_name.clone());
120                }
121                // Don't save special events
122                (true, EventDomain::Special(_)) => {
123                    warn!("Special events cannot be recorded-- ignoring event {:?}", event)
124                }
125                (true, event) => recorder.record(event.clone()),
126                _ => {}
127            }
128        }
129
130        self.dispatch_event(event);
131        self.after_edit("core");
132        self.render_if_needed();
133    }
134
135    fn dispatch_event(&mut self, event: EventDomain) {
136        use self::EventDomain as E;
137        match event {
138            E::View(cmd) => {
139                self.with_view(|view, text| view.do_edit(text, cmd));
140                self.editor.borrow_mut().update_edit_type();
141                if self.with_view(|v, t| v.needs_wrap_in_visible_region(t)) {
142                    self.rewrap();
143                }
144                if self.with_view(|v, _| v.find_in_progress()) {
145                    self.do_incremental_find();
146                }
147            }
148            E::Buffer(cmd) => {
149                self.with_editor(|ed, view, k_ring, conf| ed.do_edit(view, k_ring, conf, cmd))
150            }
151            E::Special(cmd) => self.do_special(cmd),
152        }
153    }
154
155    fn do_special(&mut self, cmd: SpecialEvent) {
156        match cmd {
157            SpecialEvent::Resize(size) => {
158                self.with_view(|view, _| view.set_size(size));
159                if self.config.word_wrap {
160                    self.update_wrap_settings(false);
161                }
162            }
163            SpecialEvent::DebugRewrap | SpecialEvent::DebugWrapWidth => {
164                warn!("debug wrapping methods are removed, use the config system")
165            }
166            SpecialEvent::DebugPrintSpans => self.with_editor(|ed, view, _, _| {
167                let sel = view.sel_regions().last().unwrap();
168                let iv = Interval::new(sel.min(), sel.max());
169                ed.get_layers().debug_print_spans(iv);
170            }),
171            SpecialEvent::RequestLines(LineRange { first, last }) => {
172                self.do_request_lines(first as usize, last as usize)
173            }
174            SpecialEvent::RequestHover { request_id, position } => {
175                self.do_request_hover(request_id, position)
176            }
177            SpecialEvent::DebugToggleComment => self.do_debug_toggle_comment(),
178            SpecialEvent::Reindent => self.do_reindent(),
179            SpecialEvent::ToggleRecording(_) => {}
180            SpecialEvent::PlayRecording(recording_name) => {
181                let recorder = self.recorder.borrow();
182
183                let starting_revision = self.editor.borrow_mut().get_head_rev_token();
184
185                // Don't group with the previous action
186                self.editor.borrow_mut().update_edit_type();
187                self.editor.borrow_mut().calculate_undo_group();
188
189                // No matter what, our entire block must belong to the same undo group
190                self.editor.borrow_mut().set_force_undo_group(true);
191                recorder.play(&recording_name, |event| {
192                    self.dispatch_event(event.clone());
193
194                    let mut editor = self.editor.borrow_mut();
195                    let (delta, last_text, drift) = match editor.commit_delta() {
196                        Some(edit_info) => edit_info,
197                        None => return,
198                    };
199                    self.update_views(&editor, &delta, &last_text, drift);
200                });
201                self.editor.borrow_mut().set_force_undo_group(false);
202
203                // The action that follows the block must belong to a separate undo group
204                self.editor.borrow_mut().update_edit_type();
205
206                let delta = self.editor.borrow_mut().delta_rev_head(starting_revision).unwrap();
207                self.update_plugins(&mut self.editor.borrow_mut(), delta, "core");
208            }
209            SpecialEvent::ClearRecording(recording_name) => {
210                let mut recorder = self.recorder.borrow_mut();
211                recorder.clear(&recording_name);
212            }
213        }
214    }
215
216    pub(crate) fn do_edit_sync(&mut self, cmd: EditRequest) -> Result<Value, RemoteError> {
217        use self::EditRequest::*;
218        let result = match cmd {
219            Cut => Ok(self.with_editor(|ed, view, _, _| ed.do_cut(view))),
220            Copy => Ok(self.with_editor(|ed, view, _, _| ed.do_copy(view))),
221        };
222        self.after_edit("core");
223        self.render_if_needed();
224        result
225    }
226
227    pub(crate) fn do_plugin_cmd(&mut self, plugin: PluginId, cmd: PluginNotification) {
228        use self::PluginNotification::*;
229        match cmd {
230            AddScopes { scopes } => {
231                let mut ed = self.editor.borrow_mut();
232                let style_map = self.style_map.borrow();
233                ed.get_layers_mut().add_scopes(plugin, scopes, &style_map);
234            }
235            UpdateSpans { start, len, spans, rev } => self.with_editor(|ed, view, _, _| {
236                ed.update_spans(view, plugin, start, len, spans, rev)
237            }),
238            Edit { edit } => self.with_editor(|ed, _, _, _| ed.apply_plugin_edit(edit)),
239            Alert { msg } => self.client.alert(&msg),
240            AddStatusItem { key, value, alignment } => {
241                let plugin_name = &self.plugins.iter().find(|p| p.id == plugin).unwrap().name;
242                self.client.add_status_item(self.view_id, plugin_name, &key, &value, &alignment);
243            }
244            UpdateStatusItem { key, value } => {
245                self.client.update_status_item(self.view_id, &key, &value)
246            }
247            UpdateAnnotations { start, len, spans, annotation_type, rev } => {
248                self.with_editor(|ed, view, _, _| {
249                    ed.update_annotations(view, plugin, start, len, spans, annotation_type, rev)
250                })
251            }
252            RemoveStatusItem { key } => self.client.remove_status_item(self.view_id, &key),
253            ShowHover { request_id, result } => self.do_show_hover(request_id, result),
254        };
255        self.after_edit(&plugin.to_string());
256        self.render_if_needed();
257    }
258
259    pub(crate) fn do_plugin_cmd_sync(&mut self, _plugin: PluginId, cmd: PluginRequest) -> Value {
260        use self::PluginRequest::*;
261        match cmd {
262            LineCount => json!(self.editor.borrow().plugin_n_lines()),
263            GetData { start, unit, max_size, rev } => {
264                json!(self.editor.borrow().plugin_get_data(start, unit, max_size, rev))
265            }
266            GetSelections => json!("not implemented"),
267        }
268    }
269
270    /// Commits any changes to the buffer, updating views and plugins as needed.
271    /// This only updates internal state; it does not update the client.
272    fn after_edit(&mut self, author: &str) {
273        let _t = trace_block("EventContext::after_edit", &["core"]);
274
275        let edit_info = self.editor.borrow_mut().commit_delta();
276        let (delta, last_text, drift) = match edit_info {
277            Some(edit_info) => edit_info,
278            None => return,
279        };
280
281        self.update_views(&self.editor.borrow(), &delta, &last_text, drift);
282        self.update_plugins(&mut self.editor.borrow_mut(), delta, author);
283
284        //if we have no plugins we always render immediately.
285        if !self.plugins.is_empty() {
286            let mut view = self.view.borrow_mut();
287            if !view.has_pending_render() {
288                let timeout = Instant::now() + RENDER_DELAY;
289                let view_id: usize = self.view_id.into();
290                let token = RENDER_VIEW_IDLE_MASK | view_id;
291                self.client.schedule_timer(timeout, token);
292                view.set_has_pending_render(true);
293            }
294        }
295    }
296
297    fn update_views(&self, ed: &Editor, delta: &RopeDelta, last_text: &Rope, drift: InsertDrift) {
298        let mut width_cache = self.width_cache.borrow_mut();
299        let iter_views = iter::once(&self.view).chain(self.siblings.iter());
300        iter_views.for_each(|view| {
301            view.borrow_mut().after_edit(
302                ed.get_buffer(),
303                last_text,
304                delta,
305                self.client,
306                &mut width_cache,
307                drift,
308            )
309        });
310    }
311
312    fn update_plugins(&self, ed: &mut Editor, delta: RopeDelta, author: &str) {
313        let new_len = delta.new_document_len();
314        let nb_lines = ed.get_buffer().measure::<LinesMetric>() + 1;
315        // don't send the actual delta if it is too large, by some heuristic
316        let approx_size = delta.inserts_len() + (delta.els.len() * 10);
317        let delta = if approx_size > MAX_SIZE_LIMIT { None } else { Some(delta) };
318
319        let undo_group = ed.get_active_undo_group();
320        //TODO: we want to just put EditType on the wire, but don't want
321        //to update the plugin lib quite yet.
322        let v: Value = serde_json::to_value(&ed.get_edit_type()).unwrap();
323        let edit_type_str = v.as_str().unwrap().to_string();
324
325        let update = PluginUpdate::new(
326            self.view_id,
327            ed.get_head_rev_token(),
328            delta,
329            new_len,
330            nb_lines,
331            Some(undo_group),
332            edit_type_str,
333            author.into(),
334        );
335
336        // we always increment and decrement regardless of whether we're
337        // sending plugins, to ensure that GC runs.
338        ed.increment_revs_in_flight();
339
340        self.plugins.iter().for_each(|plugin| {
341            ed.increment_revs_in_flight();
342            let weak_core = self.weak_core.clone();
343            let id = plugin.id;
344            let view_id = self.view_id;
345            plugin.update(&update, move |resp| {
346                weak_core.handle_plugin_update(id, view_id, resp);
347            });
348        });
349        ed.dec_revs_in_flight();
350        ed.update_edit_type();
351    }
352
353    /// Renders the view, if a render has not already been scheduled.
354    pub(crate) fn render_if_needed(&mut self) {
355        let needed = !self.view.borrow().has_pending_render();
356        if needed {
357            self.render()
358        }
359    }
360
361    pub(crate) fn _finish_delayed_render(&mut self) {
362        self.render();
363        self.view.borrow_mut().set_has_pending_render(false);
364    }
365
366    /// Flushes any changes in the views out to the frontend.
367    fn render(&mut self) {
368        let _t = trace_block("EventContext::render", &["core"]);
369        let ed = self.editor.borrow();
370        //TODO: render other views
371        self.view.borrow_mut().render_if_dirty(
372            ed.get_buffer(),
373            self.client,
374            self.style_map,
375            ed.get_layers().get_merged(),
376            ed.is_pristine(),
377        )
378    }
379}
380
381/// Helpers related to specific commands.
382///
383/// Certain events and actions don't generalize well; handling these
384/// requires access to particular combinations of state. We isolate such
385/// special cases here.
386impl<'a> EventContext<'a> {
387    pub(crate) fn finish_init(&mut self, config: &Table) {
388        if !self.plugins.is_empty() {
389            let info = self.plugin_info();
390            self.plugins.iter().for_each(|plugin| plugin.new_buffer(&info));
391        }
392
393        let available_plugins = self
394            .plugins
395            .iter()
396            .map(|plugin| ClientPluginInfo { name: plugin.name.clone(), running: true })
397            .collect::<Vec<_>>();
398        self.client.available_plugins(self.view_id, &available_plugins);
399
400        self.client.config_changed(self.view_id, config);
401        self.client.language_changed(self.view_id, &self.language);
402        self.update_wrap_settings(true);
403        self.with_view(|view, text| view.set_dirty(text));
404        self.render()
405    }
406
407    pub(crate) fn after_save(&mut self, path: &Path) {
408        // notify plugins
409        self.plugins.iter().for_each(|plugin| plugin.did_save(self.view_id, path));
410
411        self.editor.borrow_mut().set_pristine();
412        self.with_view(|view, text| view.set_dirty(text));
413        self.render()
414    }
415
416    /// Returns `true` if this was the last view
417    pub(crate) fn close_view(&self) -> bool {
418        // we probably want to notify plugins _before_ we close the view
419        // TODO: determine what plugins we're stopping
420        self.plugins.iter().for_each(|plug| plug.close_view(self.view_id));
421        self.siblings.is_empty()
422    }
423
424    pub(crate) fn config_changed(&mut self, changes: &Table) {
425        if changes.contains_key("wrap_width") || changes.contains_key("word_wrap") {
426            // FIXME: if switching from measurement-based widths to columnar widths,
427            // we need to reset the cache, since we're using different coordinate spaces
428            // for the same IDs. The long-term solution would be to include font
429            // information in the width cache, and then use real width even in the column
430            // case, getting the unit width for a typeface and multiplying that by
431            // a string's unicode width.
432            if changes.contains_key("word_wrap") {
433                debug!("clearing {} items from width cache", self.width_cache.borrow().len());
434                self.width_cache.replace(WidthCache::new());
435            }
436            self.update_wrap_settings(true);
437        }
438
439        self.client.config_changed(self.view_id, &changes);
440        self.plugins.iter().for_each(|plug| plug.config_changed(self.view_id, &changes));
441        self.render()
442    }
443
444    pub(crate) fn language_changed(&mut self, new_language_id: &LanguageId) {
445        self.language = new_language_id.clone();
446        self.client.language_changed(self.view_id, new_language_id);
447        self.plugins.iter().for_each(|plug| plug.language_changed(self.view_id, new_language_id));
448    }
449
450    pub(crate) fn reload(&mut self, text: Rope) {
451        self.with_editor(|ed, _, _, _| ed.reload(text));
452        self.after_edit("core");
453        self.render();
454    }
455
456    pub(crate) fn plugin_info(&mut self) -> PluginBufferInfo {
457        let ed = self.editor.borrow();
458        let nb_lines = ed.get_buffer().measure::<LinesMetric>() + 1;
459        let views: Vec<ViewId> = iter::once(&self.view)
460            .chain(self.siblings.iter())
461            .map(|v| v.borrow().get_view_id())
462            .collect();
463
464        let changes = serde_json::to_value(self.config).unwrap();
465        let path = self.info.map(|info| info.path.to_owned());
466        PluginBufferInfo::new(
467            self.buffer_id,
468            &views,
469            ed.get_head_rev_token(),
470            ed.get_buffer().len(),
471            nb_lines,
472            path,
473            self.language.clone(),
474            changes.as_object().unwrap().to_owned(),
475        )
476    }
477
478    pub(crate) fn plugin_started(&mut self, plugin: &Plugin) {
479        self.client.plugin_started(self.view_id, &plugin.name)
480    }
481
482    pub(crate) fn plugin_stopped(&mut self, plugin: &Plugin) {
483        self.client.plugin_stopped(self.view_id, &plugin.name, 0);
484        let needs_render = self.with_editor(|ed, view, _, _| {
485            if ed.get_layers_mut().remove_layer(plugin.id).is_some() {
486                view.set_dirty(ed.get_buffer());
487                true
488            } else {
489                false
490            }
491        });
492        if needs_render {
493            self.render();
494        }
495    }
496
497    pub(crate) fn do_plugin_update(&mut self, update: Result<Value, RpcError>) {
498        match update.map(serde_json::from_value::<u64>) {
499            Ok(Ok(_)) => (),
500            Ok(Err(err)) => error!("plugin response json err: {:?}", err),
501            Err(err) => error!("plugin shutdown, do something {:?}", err),
502        }
503        self.editor.borrow_mut().dec_revs_in_flight();
504    }
505
506    /// Returns the text to be saved, appending a newline if necessary.
507    pub(crate) fn text_for_save(&mut self) -> Rope {
508        let editor = self.editor.borrow();
509        let mut rope = editor.get_buffer().clone();
510        let rope_len = rope.len();
511
512        if rope_len < 1 || !self.config.save_with_newline {
513            return rope;
514        }
515
516        let cursor = Cursor::new(&rope, rope.len());
517        let has_newline_at_eof = match cursor.get_leaf() {
518            Some((last_chunk, _)) => last_chunk.ends_with(&self.config.line_ending),
519            // The rope can't be empty, since we would have returned earlier if it was
520            None => unreachable!(),
521        };
522
523        if !has_newline_at_eof {
524            let line_ending = &self.config.line_ending;
525            rope.edit(rope_len.., line_ending);
526            rope
527        } else {
528            rope
529        }
530    }
531
532    /// Called after anything changes that effects word wrap, such as the size of
533    /// the window or the user's wrap settings. `rewrap_immediately` should be `true`
534    /// except in the resize case; during live resize we want to delay recalculation
535    /// to avoid unnecessary work.
536    fn update_wrap_settings(&mut self, rewrap_immediately: bool) {
537        let wrap_width = self.config.wrap_width;
538        let word_wrap = self.config.word_wrap;
539        self.with_view(|view, text| view.update_wrap_settings(text, wrap_width, word_wrap));
540        if rewrap_immediately {
541            self.rewrap();
542            self.with_view(|view, text| view.set_dirty(text));
543        }
544        if self.view.borrow().needs_more_wrap() {
545            self.schedule_rewrap();
546        }
547    }
548
549    /// Tells the view to rewrap a batch of lines, if needed. This guarantees that
550    /// the currently visible region will be correctly wrapped; the caller should
551    /// check if additional wrapping is necessary and schedule that if so.
552    fn rewrap(&mut self) {
553        let mut view = self.view.borrow_mut();
554        let ed = self.editor.borrow();
555        let mut width_cache = self.width_cache.borrow_mut();
556        view.rewrap(ed.get_buffer(), &mut width_cache, self.client, ed.get_layers().get_merged());
557    }
558
559    /// Does incremental find.
560    pub(crate) fn do_incremental_find(&mut self) {
561        let _t = trace_block("EventContext::do_incremental_find", &["find"]);
562
563        self.find();
564        if self.view.borrow().find_in_progress() {
565            let ed = self.editor.borrow();
566            self.client.find_status(
567                self.view_id,
568                &json!(self.view.borrow().find_status(ed.get_buffer(), true)),
569            );
570            self.schedule_find();
571        }
572        self.render_if_needed();
573    }
574
575    fn schedule_find(&self) {
576        let view_id: usize = self.view_id.into();
577        let token = FIND_VIEW_IDLE_MASK | view_id;
578        self.client.schedule_idle(token);
579    }
580
581    /// Tells the view to execute find on a batch of lines, if needed.
582    fn find(&mut self) {
583        let mut view = self.view.borrow_mut();
584        let ed = self.editor.borrow();
585        view.do_find(ed.get_buffer());
586    }
587
588    /// Does a rewrap batch, and schedules follow-up work if needed.
589    pub(crate) fn do_rewrap_batch(&mut self) {
590        self.rewrap();
591        if self.view.borrow().needs_more_wrap() {
592            self.schedule_rewrap();
593        }
594        self.render_if_needed();
595    }
596
597    fn schedule_rewrap(&self) {
598        let view_id: usize = self.view_id.into();
599        let token = REWRAP_VIEW_IDLE_MASK | view_id;
600        self.client.schedule_idle(token);
601    }
602
603    fn do_request_lines(&mut self, first: usize, last: usize) {
604        let mut view = self.view.borrow_mut();
605        let ed = self.editor.borrow();
606        view.request_lines(
607            ed.get_buffer(),
608            self.client,
609            self.style_map,
610            ed.get_layers().get_merged(),
611            first,
612            last,
613            ed.is_pristine(),
614        )
615    }
616
617    fn selected_line_ranges(&mut self) -> Vec<(usize, usize)> {
618        let ed = self.editor.borrow();
619        let mut prev_range: Option<Range<usize>> = None;
620        let mut line_ranges = Vec::new();
621        // we send selection state to syntect in the form of a vec of line ranges,
622        // so we combine overlapping selections to get the minimum set of ranges.
623        for region in self.view.borrow().sel_regions().iter() {
624            let start = ed.get_buffer().line_of_offset(region.min());
625            let end = ed.get_buffer().line_of_offset(region.max()) + 1;
626            let line_range = start..end;
627            let prev = prev_range.take();
628            match (prev, line_range) {
629                (None, range) => prev_range = Some(range),
630                (Some(ref prev), ref range) if range.start <= prev.end => {
631                    let combined =
632                        Range { start: prev.start.min(range.start), end: prev.end.max(range.end) };
633                    prev_range = Some(combined);
634                }
635                (Some(prev), range) => {
636                    line_ranges.push((prev.start, prev.end));
637                    prev_range = Some(range);
638                }
639            }
640        }
641
642        if let Some(prev) = prev_range {
643            line_ranges.push((prev.start, prev.end));
644        }
645
646        line_ranges
647    }
648
649    fn do_reindent(&mut self) {
650        let line_ranges = self.selected_line_ranges();
651        // this is handled by syntect only; this is definitely not the long-term solution.
652        if let Some(plug) = self.plugins.iter().find(|p| p.name == "xi-syntect-plugin") {
653            plug.dispatch_command(self.view_id, "reindent", &json!(line_ranges));
654        }
655    }
656
657    fn do_debug_toggle_comment(&mut self) {
658        let line_ranges = self.selected_line_ranges();
659
660        // this is handled by syntect only; this is definitely not the long-term solution.
661        if let Some(plug) = self.plugins.iter().find(|p| p.name == "xi-syntect-plugin") {
662            plug.dispatch_command(self.view_id, "toggle_comment", &json!(line_ranges));
663        }
664    }
665
666    fn do_request_hover(&mut self, request_id: usize, position: Option<ClientPosition>) {
667        if let Some(position) = self.get_resolved_position(position) {
668            self.with_each_plugin(|p| p.get_hover(self.view_id, request_id, position))
669        }
670    }
671
672    fn do_show_hover(&mut self, request_id: usize, hover: Result<Hover, RemoteError>) {
673        match hover {
674            Ok(hover) => {
675                // TODO: Get Range from hover here and use it to highlight text
676                self.client.show_hover(self.view_id, request_id, hover.content)
677            }
678            Err(err) => warn!("Hover Response from Client Error {:?}", err),
679        }
680    }
681
682    /// Gives the requested position in UTF-8 offset format to be sent to plugin
683    /// If position is `None`, it tries to get the current Caret Position and use
684    /// that instead
685    fn get_resolved_position(&mut self, position: Option<ClientPosition>) -> Option<usize> {
686        position
687            .map(|p| self.with_view(|view, text| view.line_col_to_offset(text, p.line, p.column)))
688            .or_else(|| self.view.borrow().get_caret_offset())
689    }
690}
691
692#[cfg(test)]
693#[rustfmt::skip]
694mod tests {
695    use super::*;
696    use crate::config::ConfigManager;
697    use crate::core::dummy_weak_core;
698    use crate::tabs::BufferId;
699    use xi_rpc::test_utils::DummyPeer;
700
701    struct ContextHarness {
702        view: RefCell<View>,
703        editor: RefCell<Editor>,
704        client: Client,
705        core_ref: WeakXiCore,
706        kill_ring: RefCell<Rope>,
707        style_map: RefCell<ThemeStyleMap>,
708        width_cache: RefCell<WidthCache>,
709        config_manager: ConfigManager,
710        recorder: RefCell<Recorder>,
711    }
712
713    impl ContextHarness {
714        fn new<S: AsRef<str>>(s: S) -> Self {
715            // we could make this take a config, which would let us test
716            // behaviour with different config settings?
717            let view_id = ViewId(1);
718            let buffer_id = BufferId(2);
719            let mut config_manager = ConfigManager::new(None, None);
720            let config = config_manager.add_buffer(buffer_id, None);
721            let view = RefCell::new(View::new(view_id, buffer_id));
722            let editor = RefCell::new(Editor::with_text(s));
723            let client = Client::new(Box::new(DummyPeer));
724            let core_ref = dummy_weak_core();
725            let kill_ring = RefCell::new(Rope::from(""));
726            let style_map = RefCell::new(ThemeStyleMap::new(None));
727            let width_cache = RefCell::new(WidthCache::new());
728            let recorder = RefCell::new(Recorder::new());
729            let harness = ContextHarness { view, editor, client, core_ref, kill_ring,
730                             style_map, width_cache, config_manager, recorder };
731            harness.make_context().finish_init(&config);
732            harness
733
734        }
735
736        /// Renders the text and selections. cursors are represented with
737        /// the pipe '|', and non-caret regions are represented by \[braces\].
738        fn debug_render(&self) -> String {
739            let b = self.editor.borrow();
740            let mut text: String = b.get_buffer().into();
741            let v = self.view.borrow();
742            for sel in v.sel_regions().iter().rev() {
743                if sel.end == sel.start {
744                    text.insert(sel.end, '|');
745                } else if sel.end > sel.start {
746                    text.insert_str(sel.end, "|]");
747                    text.insert(sel.start, '[');
748                } else {
749                    text.insert(sel.start, ']');
750                    text.insert_str(sel.end, "[|");
751                }
752            }
753            text
754        }
755
756        fn make_context<'a>(&'a self) -> EventContext<'a> {
757            let view_id = ViewId(1);
758            let buffer_id = self.view.borrow().get_buffer_id();
759            let config = self.config_manager.get_buffer_config(buffer_id);
760            let language = self.config_manager.get_buffer_language(buffer_id);
761            EventContext {
762                view_id,
763                buffer_id,
764                view: &self.view,
765                editor: &self.editor,
766                config: &config.items,
767                language,
768                info: None,
769                siblings: Vec::new(),
770                plugins: Vec::new(),
771                recorder: &self.recorder,
772                client: &self.client,
773                kill_ring: &self.kill_ring,
774                style_map: &self.style_map,
775                width_cache: &self.width_cache,
776                weak_core: &self.core_ref,
777            }
778        }
779    }
780
781    #[test]
782    fn smoke_test() {
783        let harness = ContextHarness::new("");
784        let mut ctx = harness.make_context();
785        ctx.do_edit(EditNotification::Insert { chars: "hello".into() });
786        ctx.do_edit(EditNotification::Insert { chars: " ".into() });
787        ctx.do_edit(EditNotification::Insert { chars: "world".into() });
788        ctx.do_edit(EditNotification::Insert { chars: "!".into() });
789        assert_eq!(harness.debug_render(),"hello world!|");
790        ctx.do_edit(EditNotification::MoveWordLeft);
791        ctx.do_edit(EditNotification::InsertNewline);
792        assert_eq!(harness.debug_render(),"hello \n|world!");
793        ctx.do_edit(EditNotification::MoveWordRightAndModifySelection);
794        assert_eq!(harness.debug_render(), "hello \n[world|]!");
795        ctx.do_edit(EditNotification::Insert { chars: "friends".into() });
796        assert_eq!(harness.debug_render(), "hello \nfriends|!");
797    }
798
799    #[test]
800    fn test_gestures() {
801        use crate::rpc::GestureType::*;
802        let initial_text = "\
803        this is a string\n\
804        that has three\n\
805        lines.";
806        let harness = ContextHarness::new(initial_text);
807        let mut ctx = harness.make_context();
808
809        ctx.do_edit(EditNotification::MoveDown);
810        ctx.do_edit(EditNotification::MoveDown);
811        ctx.do_edit(EditNotification::MoveToEndOfParagraph);
812        assert_eq!(harness.debug_render(),"\
813        this is a string\n\
814        that has three\n\
815        lines.|" );
816
817        ctx.do_edit(EditNotification::Gesture { line: 0, col: 0, ty: PointSelect });
818        ctx.do_edit(EditNotification::MoveToEndOfParagraphAndModifySelection);
819        assert_eq!(harness.debug_render(),"\
820        [this is a string|]\n\
821        that has three\n\
822        lines." );
823
824        ctx.do_edit(EditNotification::MoveToEndOfParagraph);
825        ctx.do_edit(EditNotification::MoveToBeginningOfParagraphAndModifySelection);
826        assert_eq!(harness.debug_render(),"\
827        [|this is a string]\n\
828        that has three\n\
829        lines." );
830
831        ctx.do_edit(EditNotification::Gesture { line: 0, col: 0, ty: PointSelect });
832        assert_eq!(harness.debug_render(),"\
833        |this is a string\n\
834        that has three\n\
835        lines." );
836
837        ctx.do_edit(EditNotification::Gesture { line: 0, col: 5, ty: PointSelect });
838        assert_eq!(harness.debug_render(),"\
839        this |is a string\n\
840        that has three\n\
841        lines." );
842
843        ctx.do_edit(EditNotification::Gesture { line: 1, col: 5, ty: ToggleSel });
844        assert_eq!(harness.debug_render(),"\
845        this |is a string\n\
846        that |has three\n\
847        lines." );
848
849        ctx.do_edit(EditNotification::MoveToRightEndOfLineAndModifySelection);
850        assert_eq!(harness.debug_render(),"\
851        this [is a string|]\n\
852        that [has three|]\n\
853        lines." );
854
855        ctx.do_edit(EditNotification::Gesture { line: 2, col: 2, ty: MultiWordSelect });
856        assert_eq!(harness.debug_render(),"\
857        this [is a string|]\n\
858        that [has three|]\n\
859        [lines|]." );
860
861        ctx.do_edit(EditNotification::Gesture { line: 2, col: 2, ty: ToggleSel });
862        assert_eq!(harness.debug_render(),"\
863        this [is a string|]\n\
864        that [has three|]\n\
865        lines." );
866
867        ctx.do_edit(EditNotification::Gesture { line: 2, col: 2, ty: ToggleSel });
868        assert_eq!(harness.debug_render(),"\
869        this [is a string|]\n\
870        that [has three|]\n\
871        li|nes." );
872
873        ctx.do_edit(EditNotification::MoveToLeftEndOfLine);
874        assert_eq!(harness.debug_render(),"\
875        |this is a string\n\
876        |that has three\n\
877        |lines." );
878
879        ctx.do_edit(EditNotification::MoveWordRight);
880        assert_eq!(harness.debug_render(),"\
881        this| is a string\n\
882        that| has three\n\
883        lines|." );
884
885        ctx.do_edit(EditNotification::MoveToLeftEndOfLineAndModifySelection);
886        assert_eq!(harness.debug_render(),"\
887        [|this] is a string\n\
888        [|that] has three\n\
889        [|lines]." );
890
891        ctx.do_edit(EditNotification::CollapseSelections);
892        ctx.do_edit(EditNotification::MoveToRightEndOfLine);
893        assert_eq!(harness.debug_render(),"\
894        this is a string|\n\
895        that has three\n\
896        lines." );
897
898        ctx.do_edit(EditNotification::Gesture { line: 2, col: 2, ty: MultiLineSelect });
899        assert_eq!(harness.debug_render(),"\
900        this is a string|\n\
901        that has three\n\
902        [lines.|]" );
903
904        ctx.do_edit(EditNotification::SelectAll);
905        assert_eq!(harness.debug_render(),"\
906        [this is a string\n\
907        that has three\n\
908        lines.|]" );
909
910        ctx.do_edit(EditNotification::CollapseSelections);
911        ctx.do_edit(EditNotification::AddSelectionAbove);
912        assert_eq!(harness.debug_render(),"\
913        this is a string\n\
914        that h|as three\n\
915        lines.|" );
916
917        ctx.do_edit(EditNotification::MoveRight);
918        assert_eq!(harness.debug_render(),"\
919        this is a string\n\
920        that ha|s three\n\
921        lines.|" );
922
923        ctx.do_edit(EditNotification::MoveLeft);
924        assert_eq!(harness.debug_render(),"\
925        this is a string\n\
926        that h|as three\n\
927        lines|." );
928    }
929
930    #[test]
931    fn delete_combining_enclosing_keycaps_tests() {
932        use crate::rpc::GestureType::*;
933
934        let initial_text = "1\u{E0101}\u{20E3}";
935        let harness = ContextHarness::new(initial_text);
936        let mut ctx = harness.make_context();
937        ctx.do_edit(EditNotification::Gesture { line: 0, col: 8, ty: PointSelect });
938
939        assert_eq!(harness.debug_render(), "1\u{E0101}\u{20E3}|");
940
941        ctx.do_edit(EditNotification::DeleteBackward);
942        assert_eq!(harness.debug_render(), "|");
943
944        // multiple COMBINING ENCLOSING KEYCAP
945        ctx.do_edit(EditNotification::Insert { chars: "1\u{20E3}\u{20E3}".into() });
946        assert_eq!(harness.debug_render(), "1\u{20E3}\u{20E3}|");
947        ctx.do_edit(EditNotification::DeleteBackward);
948        assert_eq!(harness.debug_render(), "1\u{20E3}|");
949        ctx.do_edit(EditNotification::DeleteBackward);
950        assert_eq!(harness.debug_render(), "|");
951
952        // Isolated COMBINING ENCLOSING KEYCAP
953        ctx.do_edit(EditNotification::Insert { chars: "\u{20E3}".into() });
954        assert_eq!(harness.debug_render(), "\u{20E3}|");
955        ctx.do_edit(EditNotification::DeleteBackward);
956        assert_eq!(harness.debug_render(), "|");
957
958        // Isolated multiple COMBINING ENCLOSING KEYCAP
959        ctx.do_edit(EditNotification::Insert { chars: "\u{20E3}\u{20E3}".into() });
960        assert_eq!(harness.debug_render(), "\u{20E3}\u{20E3}|");
961        ctx.do_edit(EditNotification::DeleteBackward);
962        assert_eq!(harness.debug_render(), "\u{20E3}|");
963        ctx.do_edit(EditNotification::DeleteBackward);
964        assert_eq!(harness.debug_render(), "|");
965    }
966
967    #[test]
968    fn delete_variation_selector_tests() {
969        use crate::rpc::GestureType::*;
970
971        let initial_text = "\u{FE0F}";
972        let harness = ContextHarness::new(initial_text);
973        let mut ctx = harness.make_context();
974        ctx.do_edit(EditNotification::Gesture { line: 0, col: 3, ty: PointSelect });
975
976        assert_eq!(harness.debug_render(), "\u{FE0F}|");
977
978        // Isolated variation selector
979        ctx.do_edit(EditNotification::DeleteBackward);
980        assert_eq!(harness.debug_render(), "|");
981
982        ctx.do_edit(EditNotification::Insert { chars: "\u{E0100}".into() });
983        assert_eq!(harness.debug_render(), "\u{E0100}|");
984        ctx.do_edit(EditNotification::DeleteBackward);
985        assert_eq!(harness.debug_render(), "|");
986
987        // Isolated multiple variation selectors
988        ctx.do_edit(EditNotification::Insert { chars: "\u{FE0F}\u{FE0F}".into() });
989        assert_eq!(harness.debug_render(), "\u{FE0F}\u{FE0F}|");
990        ctx.do_edit(EditNotification::DeleteBackward);
991        assert_eq!(harness.debug_render(), "\u{FE0F}|");
992        ctx.do_edit(EditNotification::DeleteBackward);
993        assert_eq!(harness.debug_render(), "|");
994
995        ctx.do_edit(EditNotification::Insert { chars: "\u{FE0F}\u{E0100}".into() });
996        assert_eq!(harness.debug_render(), "\u{FE0F}\u{E0100}|");
997        ctx.do_edit(EditNotification::DeleteBackward);
998        assert_eq!(harness.debug_render(), "\u{FE0F}|");
999        ctx.do_edit(EditNotification::DeleteBackward);
1000        assert_eq!(harness.debug_render(), "|");
1001
1002        ctx.do_edit(EditNotification::Insert { chars: "\u{E0100}\u{FE0F}".into() });
1003        assert_eq!(harness.debug_render(), "\u{E0100}\u{FE0F}|");
1004        ctx.do_edit(EditNotification::DeleteBackward);
1005        assert_eq!(harness.debug_render(), "\u{E0100}|");
1006        ctx.do_edit(EditNotification::DeleteBackward);
1007        assert_eq!(harness.debug_render(), "|");
1008
1009        ctx.do_edit(EditNotification::Insert { chars: "\u{E0100}\u{E0100}".into() });
1010        assert_eq!(harness.debug_render(), "\u{E0100}\u{E0100}|");
1011        ctx.do_edit(EditNotification::DeleteBackward);
1012        assert_eq!(harness.debug_render(), "\u{E0100}|");
1013        ctx.do_edit(EditNotification::DeleteBackward);
1014        assert_eq!(harness.debug_render(), "|");
1015
1016        // Multiple variation selectors
1017        ctx.do_edit(EditNotification::Insert { chars: "#\u{FE0F}\u{FE0F}".into() });
1018        assert_eq!(harness.debug_render(), "#\u{FE0F}\u{FE0F}|");
1019        ctx.do_edit(EditNotification::DeleteBackward);
1020        assert_eq!(harness.debug_render(), "#\u{FE0F}|");
1021        ctx.do_edit(EditNotification::DeleteBackward);
1022        assert_eq!(harness.debug_render(), "|");
1023
1024        ctx.do_edit(EditNotification::Insert { chars: "#\u{FE0F}\u{E0100}".into() });
1025        assert_eq!(harness.debug_render(), "#\u{FE0F}\u{E0100}|");
1026        ctx.do_edit(EditNotification::DeleteBackward);
1027        assert_eq!(harness.debug_render(), "#\u{FE0F}|");
1028        ctx.do_edit(EditNotification::DeleteBackward);
1029        assert_eq!(harness.debug_render(), "|");
1030
1031        ctx.do_edit(EditNotification::Insert { chars: "#\u{E0100}\u{FE0F}".into() });
1032        assert_eq!(harness.debug_render(), "#\u{E0100}\u{FE0F}|");
1033        ctx.do_edit(EditNotification::DeleteBackward);
1034        assert_eq!(harness.debug_render(), "#\u{E0100}|");
1035        ctx.do_edit(EditNotification::DeleteBackward);
1036        assert_eq!(harness.debug_render(), "|");
1037
1038        ctx.do_edit(EditNotification::Insert { chars: "#\u{E0100}\u{E0100}".into() });
1039        assert_eq!(harness.debug_render(), "#\u{E0100}\u{E0100}|");
1040        ctx.do_edit(EditNotification::DeleteBackward);
1041        assert_eq!(harness.debug_render(), "#\u{E0100}|");
1042        ctx.do_edit(EditNotification::DeleteBackward);
1043        assert_eq!(harness.debug_render(), "|");
1044    }
1045
1046    #[test]
1047    fn delete_emoji_zwj_sequence_tests() {
1048        use crate::rpc::GestureType::*;
1049        let initial_text = "\u{1F441}\u{200D}\u{1F5E8}";
1050        let harness = ContextHarness::new(initial_text);
1051        let mut ctx = harness.make_context();
1052        ctx.do_edit(EditNotification::Gesture { line: 0, col: 11, ty: PointSelect });
1053        assert_eq!(harness.debug_render(), "\u{1F441}\u{200D}\u{1F5E8}|");
1054
1055        // U+200D is ZERO WIDTH JOINER.
1056        ctx.do_edit(EditNotification::DeleteBackward);
1057        assert_eq!(harness.debug_render(), "|");
1058
1059        ctx.do_edit(EditNotification::Insert { chars: "\u{1F441}\u{200D}\u{1F5E8}\u{FE0E}".into() });
1060        assert_eq!(harness.debug_render(), "\u{1F441}\u{200D}\u{1F5E8}\u{FE0E}|");
1061        ctx.do_edit(EditNotification::DeleteBackward);
1062        assert_eq!(harness.debug_render(), "|");
1063
1064        ctx.do_edit(EditNotification::Insert { chars: "\u{1F469}\u{200D}\u{1F373}".into() });
1065        assert_eq!(harness.debug_render(), "\u{1F469}\u{200D}\u{1F373}|");
1066        ctx.do_edit(EditNotification::DeleteBackward);
1067        assert_eq!(harness.debug_render(), "|");
1068
1069        ctx.do_edit(EditNotification::Insert { chars: "\u{1F487}\u{200D}\u{2640}".into() });
1070        assert_eq!(harness.debug_render(), "\u{1F487}\u{200D}\u{2640}|");
1071        ctx.do_edit(EditNotification::DeleteBackward);
1072        assert_eq!(harness.debug_render(), "|");
1073
1074        ctx.do_edit(EditNotification::Insert { chars: "\u{1F487}\u{200D}\u{2640}\u{FE0F}".into() });
1075        assert_eq!(harness.debug_render(), "\u{1F487}\u{200D}\u{2640}\u{FE0F}|");
1076        ctx.do_edit(EditNotification::DeleteBackward);
1077        assert_eq!(harness.debug_render(), "|");
1078
1079        ctx.do_edit(EditNotification::Insert { chars: "\u{1F468}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F48B}\u{200D}\u{1F468}".into() });
1080        assert_eq!(harness.debug_render(), "\u{1F468}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F48B}\u{200D}\u{1F468}|");
1081        ctx.do_edit(EditNotification::DeleteBackward);
1082        assert_eq!(harness.debug_render(), "|");
1083
1084        // Emoji modifier can be appended to the first emoji.
1085        ctx.do_edit(EditNotification::Insert { chars: "\u{1F469}\u{1F3FB}\u{200D}\u{1F4BC}".into() });
1086        assert_eq!(harness.debug_render(), "\u{1F469}\u{1F3FB}\u{200D}\u{1F4BC}|");
1087        ctx.do_edit(EditNotification::DeleteBackward);
1088        assert_eq!(harness.debug_render(), "|");
1089
1090        // End with ZERO WIDTH JOINER
1091        ctx.do_edit(EditNotification::Insert { chars: "\u{1F441}\u{200D}".into() });
1092        assert_eq!(harness.debug_render(), "\u{1F441}\u{200D}|");
1093        ctx.do_edit(EditNotification::DeleteBackward);
1094        assert_eq!(harness.debug_render(), "\u{1F441}|");
1095        ctx.do_edit(EditNotification::DeleteBackward);
1096        assert_eq!(harness.debug_render(), "|");
1097
1098        // Start with ZERO WIDTH JOINER
1099        ctx.do_edit(EditNotification::Insert { chars: "\u{200D}\u{1F5E8}".into() });
1100        assert_eq!(harness.debug_render(), "\u{200D}\u{1F5E8}|");
1101        ctx.do_edit(EditNotification::DeleteBackward);
1102        assert_eq!(harness.debug_render(), "\u{200D}|");
1103        ctx.do_edit(EditNotification::DeleteBackward);
1104        assert_eq!(harness.debug_render(), "|");
1105
1106        ctx.do_edit(EditNotification::Insert { chars: "\u{FE0E}\u{200D}\u{1F5E8}".into() });
1107        assert_eq!(harness.debug_render(), "\u{FE0E}\u{200D}\u{1F5E8}|");
1108        ctx.do_edit(EditNotification::DeleteBackward);
1109        assert_eq!(harness.debug_render(), "\u{FE0E}\u{200D}|");
1110        ctx.do_edit(EditNotification::DeleteBackward);
1111        assert_eq!(harness.debug_render(), "\u{FE0E}|");
1112        ctx.do_edit(EditNotification::DeleteBackward);
1113        assert_eq!(harness.debug_render(), "|");
1114
1115        // Multiple ZERO WIDTH JOINER
1116        ctx.do_edit(EditNotification::Insert { chars: "\u{1F441}\u{200D}\u{200D}\u{1F5E8}".into() });
1117        assert_eq!(harness.debug_render(), "\u{1F441}\u{200D}\u{200D}\u{1F5E8}|");
1118        ctx.do_edit(EditNotification::DeleteBackward);
1119        assert_eq!(harness.debug_render(), "\u{1F441}\u{200D}\u{200D}|");
1120        ctx.do_edit(EditNotification::DeleteBackward);
1121        assert_eq!(harness.debug_render(), "\u{1F441}\u{200D}|");
1122        ctx.do_edit(EditNotification::DeleteBackward);
1123        assert_eq!(harness.debug_render(), "\u{1F441}|");
1124        ctx.do_edit(EditNotification::DeleteBackward);
1125        assert_eq!(harness.debug_render(), "|");
1126
1127        // Isolated ZERO WIDTH JOINER
1128        ctx.do_edit(EditNotification::Insert { chars: "\u{200D}".into() });
1129        assert_eq!(harness.debug_render(), "\u{200D}|");
1130        ctx.do_edit(EditNotification::DeleteBackward);
1131        assert_eq!(harness.debug_render(), "|");
1132
1133        // Isolated multiple ZERO WIDTH JOINER
1134        ctx.do_edit(EditNotification::Insert { chars: "\u{200D}\u{200D}".into() });
1135        assert_eq!(harness.debug_render(), "\u{200D}\u{200D}|");
1136        ctx.do_edit(EditNotification::DeleteBackward);
1137        assert_eq!(harness.debug_render(), "\u{200D}|");
1138        ctx.do_edit(EditNotification::DeleteBackward);
1139        assert_eq!(harness.debug_render(), "|");
1140    }
1141
1142    #[test]
1143    fn delete_flags_tests() {
1144        use crate::rpc::GestureType::*;
1145        let initial_text = "\u{1F1FA}";
1146        let harness = ContextHarness::new(initial_text);
1147        let mut ctx = harness.make_context();
1148        ctx.do_edit(EditNotification::Gesture { line: 0, col: 4, ty: PointSelect });
1149
1150        // Isolated regional indicator symbol
1151        assert_eq!(harness.debug_render(), "\u{1F1FA}|");
1152        ctx.do_edit(EditNotification::DeleteBackward);
1153        assert_eq!(harness.debug_render(), "|");
1154
1155        // Odd numbered regional indicator symbols
1156        ctx.do_edit(EditNotification::Insert { chars: "\u{1F1FA}\u{1F1F8}\u{1F1FA}".into() });
1157        assert_eq!(harness.debug_render(), "\u{1F1FA}\u{1F1F8}\u{1F1FA}|");
1158        ctx.do_edit(EditNotification::DeleteBackward);
1159        assert_eq!(harness.debug_render(), "\u{1F1FA}\u{1F1F8}|");
1160        ctx.do_edit(EditNotification::DeleteBackward);
1161        assert_eq!(harness.debug_render(), "|");
1162
1163        // Incomplete sequence. (no tag_term: U+E007E)
1164        ctx.do_edit(EditNotification::Insert { chars: "a\u{1F3F4}\u{E0067}b".into() });
1165        assert_eq!(harness.debug_render(), "a\u{1F3F4}\u{E0067}b|");
1166        ctx.do_edit(EditNotification::DeleteBackward);
1167        assert_eq!(harness.debug_render(), "a\u{1F3F4}\u{E0067}|");
1168        ctx.do_edit(EditNotification::DeleteBackward);
1169        assert_eq!(harness.debug_render(), "a\u{1F3F4}|");
1170        ctx.do_edit(EditNotification::DeleteBackward);
1171        assert_eq!(harness.debug_render(), "a|");
1172
1173        // No tag_base
1174        ctx.do_edit(EditNotification::Insert { chars: "\u{E0067}\u{E007F}b".into() });
1175        assert_eq!(harness.debug_render(), "a\u{E0067}\u{E007F}b|");
1176        ctx.do_edit(EditNotification::DeleteBackward);
1177        assert_eq!(harness.debug_render(), "a\u{E0067}\u{E007F}|");
1178        ctx.do_edit(EditNotification::DeleteBackward);
1179        assert_eq!(harness.debug_render(), "a\u{E0067}|");
1180        ctx.do_edit(EditNotification::DeleteBackward);
1181        assert_eq!(harness.debug_render(), "a|");
1182
1183        // Isolated tag chars
1184        ctx.do_edit(EditNotification::Insert { chars: "\u{E0067}\u{E0067}b".into() });
1185        assert_eq!(harness.debug_render(), "a\u{E0067}\u{E0067}b|");
1186        ctx.do_edit(EditNotification::DeleteBackward);
1187        assert_eq!(harness.debug_render(), "a\u{E0067}\u{E0067}|");
1188        ctx.do_edit(EditNotification::DeleteBackward);
1189        assert_eq!(harness.debug_render(), "a\u{E0067}|");
1190        ctx.do_edit(EditNotification::DeleteBackward);
1191        assert_eq!(harness.debug_render(), "a|");
1192
1193        // Isolated tab term.
1194        ctx.do_edit(EditNotification::Insert { chars: "\u{E007F}\u{E007F}b".into() });
1195        assert_eq!(harness.debug_render(), "a\u{E007F}\u{E007F}b|");
1196        ctx.do_edit(EditNotification::DeleteBackward);
1197        assert_eq!(harness.debug_render(), "a\u{E007F}\u{E007F}|");
1198        ctx.do_edit(EditNotification::DeleteBackward);
1199        assert_eq!(harness.debug_render(), "a\u{E007F}|");
1200        ctx.do_edit(EditNotification::DeleteBackward);
1201        assert_eq!(harness.debug_render(), "a|");
1202
1203        // Immediate tag_term after tag_base
1204        ctx.do_edit(EditNotification::Insert { chars: "\u{1F3F4}\u{E007F}\u{1F3F4}\u{E007F}b".into() });
1205        assert_eq!(harness.debug_render(), "a\u{1F3F4}\u{E007F}\u{1F3F4}\u{E007F}b|");
1206        ctx.do_edit(EditNotification::DeleteBackward);
1207        assert_eq!(harness.debug_render(), "a\u{1F3F4}\u{E007F}\u{1F3F4}\u{E007F}|");
1208        ctx.do_edit(EditNotification::DeleteBackward);
1209        assert_eq!(harness.debug_render(), "a\u{1F3F4}\u{E007F}|");
1210        ctx.do_edit(EditNotification::DeleteBackward);
1211        assert_eq!(harness.debug_render(), "a|");
1212    }
1213
1214    #[test]
1215    fn delete_emoji_modifier_tests() {
1216        use crate::rpc::GestureType::*;
1217        let initial_text = "\u{1F466}\u{1F3FB}";
1218        let harness = ContextHarness::new(initial_text);
1219        let mut ctx = harness.make_context();
1220        ctx.do_edit(EditNotification::Gesture { line: 0, col: 8, ty: PointSelect });
1221
1222        // U+1F3FB is EMOJI MODIFIER FITZPATRICK TYPE-1-2.
1223        assert_eq!(harness.debug_render(), "\u{1F466}\u{1F3FB}|");
1224        ctx.do_edit(EditNotification::DeleteBackward);
1225        assert_eq!(harness.debug_render(), "|");
1226
1227        // Isolated emoji modifier
1228        ctx.do_edit(EditNotification::Insert { chars: "\u{1F3FB}".into() });
1229        assert_eq!(harness.debug_render(), "\u{1F3FB}|");
1230        ctx.do_edit(EditNotification::DeleteBackward);
1231        assert_eq!(harness.debug_render(), "|");
1232
1233        // Isolated multiple emoji modifier
1234        ctx.do_edit(EditNotification::Insert { chars: "\u{1F3FB}\u{1F3FB}".into() });
1235        assert_eq!(harness.debug_render(), "\u{1F3FB}\u{1F3FB}|");
1236        ctx.do_edit(EditNotification::DeleteBackward);
1237        assert_eq!(harness.debug_render(), "\u{1F3FB}|");
1238        ctx.do_edit(EditNotification::DeleteBackward);
1239        assert_eq!(harness.debug_render(), "|");
1240
1241        // Multiple emoji modifiers
1242        ctx.do_edit(EditNotification::Insert { chars: "\u{1F466}\u{1F3FB}\u{1F3FB}".into() });
1243        ctx.do_edit(EditNotification::DeleteBackward);
1244        assert_eq!(harness.debug_render(), "\u{1F466}\u{1F3FB}|");
1245        ctx.do_edit(EditNotification::DeleteBackward);
1246        assert_eq!(harness.debug_render(), "|");
1247    }
1248
1249    #[test]
1250    fn delete_mixed_edge_cases_tests() {
1251        use crate::rpc::GestureType::*;
1252        let initial_text = "";
1253        let harness = ContextHarness::new(initial_text);
1254        let mut ctx = harness.make_context();
1255        ctx.do_edit(EditNotification::Gesture { line: 0, col: 7, ty: PointSelect });
1256
1257        // COMBINING ENCLOSING KEYCAP + variation selector
1258        ctx.do_edit(EditNotification::Insert { chars: "1\u{20E3}\u{FE0F}".into() });
1259        ctx.do_edit(EditNotification::DeleteBackward);
1260        assert_eq!(harness.debug_render(), "1|");
1261        ctx.do_edit(EditNotification::DeleteBackward);
1262        assert_eq!(harness.debug_render(), "|");
1263
1264        // Variation selector + COMBINING ENCLOSING KEYCAP
1265        ctx.do_edit(EditNotification::Insert { chars: "\u{2665}\u{FE0F}\u{20E3}".into() });
1266        ctx.do_edit(EditNotification::DeleteBackward);
1267        assert_eq!(harness.debug_render(), "\u{2665}\u{FE0F}|");
1268        ctx.do_edit(EditNotification::DeleteBackward);
1269        assert_eq!(harness.debug_render(), "|");
1270
1271        // COMBINING ENCLOSING KEYCAP + ending with ZERO WIDTH JOINER
1272        ctx.do_edit(EditNotification::Insert { chars: "1\u{20E3}\u{200D}".into() });
1273        ctx.do_edit(EditNotification::DeleteBackward);
1274        assert_eq!(harness.debug_render(), "1\u{20E3}|");
1275        ctx.do_edit(EditNotification::DeleteBackward);
1276        assert_eq!(harness.debug_render(), "|");
1277
1278        // COMBINING ENCLOSING KEYCAP + ZERO WIDTH JOINER
1279        ctx.do_edit(EditNotification::Insert { chars: "1\u{20E3}\u{200D}\u{1F5E8}".into() });
1280        ctx.do_edit(EditNotification::DeleteBackward);
1281        assert_eq!(harness.debug_render(), "1\u{20E3}\u{200D}|");
1282        ctx.do_edit(EditNotification::DeleteBackward);
1283        assert_eq!(harness.debug_render(), "1\u{20E3}|");
1284        ctx.do_edit(EditNotification::DeleteBackward);
1285        assert_eq!(harness.debug_render(), "|");
1286
1287        // Start with ZERO WIDTH JOINER + COMBINING ENCLOSING KEYCAP
1288        ctx.do_edit(EditNotification::Insert { chars: "\u{200D}\u{20E3}".into() });
1289        ctx.do_edit(EditNotification::DeleteBackward);
1290        assert_eq!(harness.debug_render(), "\u{200D}|");
1291        ctx.do_edit(EditNotification::DeleteBackward);
1292        assert_eq!(harness.debug_render(), "|");
1293
1294        // ZERO WIDTH JOINER + COMBINING ENCLOSING KEYCAP
1295        ctx.do_edit(EditNotification::Insert { chars: "\u{1F441}\u{200D}\u{20E3}".into() });
1296        ctx.do_edit(EditNotification::DeleteBackward);
1297        assert_eq!(harness.debug_render(), "\u{1F441}\u{200D}|");
1298        ctx.do_edit(EditNotification::DeleteBackward);
1299        assert_eq!(harness.debug_render(), "\u{1F441}|");
1300        ctx.do_edit(EditNotification::DeleteBackward);
1301        assert_eq!(harness.debug_render(), "|");
1302
1303        // COMBINING ENCLOSING KEYCAP + regional indicator symbol
1304        ctx.do_edit(EditNotification::Insert { chars: "1\u{20E3}\u{1F1FA}".into() });
1305        ctx.do_edit(EditNotification::DeleteBackward);
1306        assert_eq!(harness.debug_render(), "1\u{20E3}|");
1307        ctx.do_edit(EditNotification::DeleteBackward);
1308        assert_eq!(harness.debug_render(), "|");
1309
1310        // Regional indicator symbol + COMBINING ENCLOSING KEYCAP
1311        ctx.do_edit(EditNotification::Insert { chars: "\u{1F1FA}\u{20E3}".into() });
1312        ctx.do_edit(EditNotification::DeleteBackward);
1313        assert_eq!(harness.debug_render(), "\u{1F1FA}|");
1314        ctx.do_edit(EditNotification::DeleteBackward);
1315        assert_eq!(harness.debug_render(), "|");
1316
1317        // COMBINING ENCLOSING KEYCAP + emoji modifier
1318        ctx.do_edit(EditNotification::Insert { chars: "1\u{20E3}\u{1F3FB}".into() });
1319        ctx.do_edit(EditNotification::DeleteBackward);
1320        assert_eq!(harness.debug_render(), "1\u{20E3}|");
1321        ctx.do_edit(EditNotification::DeleteBackward);
1322        assert_eq!(harness.debug_render(), "|");
1323
1324        // Emoji modifier + COMBINING ENCLOSING KEYCAP
1325        ctx.do_edit(EditNotification::Insert { chars: "\u{1F466}\u{1F3FB}\u{20E3}".into() });
1326        ctx.do_edit(EditNotification::DeleteBackward);
1327        assert_eq!(harness.debug_render(), "\u{1f466}\u{1F3FB}|");
1328        ctx.do_edit(EditNotification::DeleteBackward);
1329        assert_eq!(harness.debug_render(), "|");
1330
1331        // Variation selector + end with ZERO WIDTH JOINER
1332        ctx.do_edit(EditNotification::Insert { chars: "\u{2665}\u{FE0F}\u{200D}".into() });
1333        ctx.do_edit(EditNotification::DeleteBackward);
1334        assert_eq!(harness.debug_render(), "\u{2665}\u{FE0F}|");
1335        ctx.do_edit(EditNotification::DeleteBackward);
1336        assert_eq!(harness.debug_render(), "|");
1337
1338        // Variation selector + ZERO WIDTH JOINER
1339        ctx.do_edit(EditNotification::Insert { chars: "\u{1F469}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F469}".into() });
1340        ctx.do_edit(EditNotification::DeleteBackward);
1341        assert_eq!(harness.debug_render(), "|");
1342
1343        // Start with ZERO WIDTH JOINER + variation selector
1344        ctx.do_edit(EditNotification::Insert { chars: "\u{200D}\u{FE0F}".into() });
1345        ctx.do_edit(EditNotification::DeleteBackward);
1346        assert_eq!(harness.debug_render(), "|");
1347
1348        // ZERO WIDTH JOINER + variation selector
1349        ctx.do_edit(EditNotification::Insert { chars: "\u{1F469}\u{200D}\u{FE0F}".into() });
1350        ctx.do_edit(EditNotification::DeleteBackward);
1351        assert_eq!(harness.debug_render(), "\u{1F469}|");
1352        ctx.do_edit(EditNotification::DeleteBackward);
1353        assert_eq!(harness.debug_render(), "|");
1354
1355        // Variation selector + regional indicator symbol
1356        ctx.do_edit(EditNotification::Insert { chars: "\u{2665}\u{FE0F}\u{1F1FA}".into() });
1357        ctx.do_edit(EditNotification::DeleteBackward);
1358        assert_eq!(harness.debug_render(), "\u{2665}\u{FE0F}|");
1359        ctx.do_edit(EditNotification::DeleteBackward);
1360        assert_eq!(harness.debug_render(), "|");
1361
1362        // Regional indicator symbol + variation selector
1363        ctx.do_edit(EditNotification::Insert { chars: "\u{1F1FA}\u{FE0F}".into() });
1364        ctx.do_edit(EditNotification::DeleteBackward);
1365        assert_eq!(harness.debug_render(), "|");
1366
1367        // Variation selector + emoji modifier
1368        ctx.do_edit(EditNotification::Insert { chars: "\u{2665}\u{FE0F}\u{1F3FB}".into() });
1369        ctx.do_edit(EditNotification::DeleteBackward);
1370        assert_eq!(harness.debug_render(), "\u{2665}\u{FE0F}|");
1371        ctx.do_edit(EditNotification::DeleteBackward);
1372        assert_eq!(harness.debug_render(), "|");
1373
1374        // Emoji modifier + variation selector
1375        ctx.do_edit(EditNotification::Insert { chars: "\u{1F466}\u{1F3FB}\u{FE0F}".into() });
1376        ctx.do_edit(EditNotification::DeleteBackward);
1377        assert_eq!(harness.debug_render(), "\u{1F466}|");
1378        ctx.do_edit(EditNotification::DeleteBackward);
1379        assert_eq!(harness.debug_render(), "|");
1380
1381        // Start withj ZERO WIDTH JOINER + regional indicator symbol
1382        ctx.do_edit(EditNotification::Insert { chars: "\u{200D}\u{1F1FA}".into() });
1383        ctx.do_edit(EditNotification::DeleteBackward);
1384        assert_eq!(harness.debug_render(), "\u{200D}|");
1385        ctx.do_edit(EditNotification::DeleteBackward);
1386        assert_eq!(harness.debug_render(), "|");
1387
1388        // ZERO WIDTH JOINER + Regional indicator symbol
1389        ctx.do_edit(EditNotification::Insert { chars: "\u{1F469}\u{200D}\u{1F1FA}".into() });
1390        ctx.do_edit(EditNotification::DeleteBackward);
1391        assert_eq!(harness.debug_render(), "\u{1F469}\u{200D}|");
1392        ctx.do_edit(EditNotification::DeleteBackward);
1393        assert_eq!(harness.debug_render(), "\u{1F469}|");
1394        ctx.do_edit(EditNotification::DeleteBackward);
1395        assert_eq!(harness.debug_render(), "|");
1396
1397        // Regional indicator symbol + end with ZERO WIDTH JOINER
1398        ctx.do_edit(EditNotification::Insert { chars: "\u{1F1FA}\u{200D}".into() });
1399        ctx.do_edit(EditNotification::DeleteBackward);
1400        assert_eq!(harness.debug_render(), "\u{1F1FA}|");
1401        ctx.do_edit(EditNotification::DeleteBackward);
1402        assert_eq!(harness.debug_render(), "|");
1403
1404        // Regional indicator symbol + ZERO WIDTH JOINER
1405        ctx.do_edit(EditNotification::Insert { chars: "\u{1F1FA}\u{200D}\u{1F469}".into() });
1406        ctx.do_edit(EditNotification::DeleteBackward);
1407        assert_eq!(harness.debug_render(), "|");
1408
1409        // Start with ZERO WIDTH JOINER + emoji modifier
1410        ctx.do_edit(EditNotification::Insert { chars: "\u{200D}\u{1F3FB}".into() });
1411        ctx.do_edit(EditNotification::DeleteBackward);
1412        assert_eq!(harness.debug_render(), "\u{200D}|");
1413        ctx.do_edit(EditNotification::DeleteBackward);
1414        assert_eq!(harness.debug_render(), "|");
1415
1416        // ZERO WIDTH JOINER + emoji modifier
1417        ctx.do_edit(EditNotification::Insert { chars: "\u{1F469}\u{200D}\u{1F3FB}".into() });
1418        ctx.do_edit(EditNotification::DeleteBackward);
1419        assert_eq!(harness.debug_render(), "\u{1F469}\u{200D}|");
1420        ctx.do_edit(EditNotification::DeleteBackward);
1421        assert_eq!(harness.debug_render(), "\u{1F469}|");
1422        ctx.do_edit(EditNotification::DeleteBackward);
1423        assert_eq!(harness.debug_render(), "|");
1424
1425        // Emoji modifier + end with ZERO WIDTH JOINER
1426        ctx.do_edit(EditNotification::Insert { chars: "\u{1F466}\u{1F3FB}\u{200D}".into() });
1427        ctx.do_edit(EditNotification::DeleteBackward);
1428        assert_eq!(harness.debug_render(), "\u{1F466}\u{1F3FB}|");
1429        ctx.do_edit(EditNotification::DeleteBackward);
1430        assert_eq!(harness.debug_render(), "|");
1431
1432        // Regional indicator symbol + Emoji modifier
1433        ctx.do_edit(EditNotification::Insert { chars: "\u{1F1FA}\u{1F3FB}".into() });
1434        ctx.do_edit(EditNotification::DeleteBackward);
1435        assert_eq!(harness.debug_render(), "\u{1F1FA}|");
1436        ctx.do_edit(EditNotification::DeleteBackward);
1437        assert_eq!(harness.debug_render(), "|");
1438
1439        // Emoji modifier + regional indicator symbol
1440        ctx.do_edit(EditNotification::Insert { chars: "\u{1F466}\u{1F3FB}\u{1F1FA}".into() });
1441        ctx.do_edit(EditNotification::DeleteBackward);
1442        assert_eq!(harness.debug_render(), "\u{1F466}\u{1F3FB}|");
1443        ctx.do_edit(EditNotification::DeleteBackward);
1444        assert_eq!(harness.debug_render(), "|");
1445
1446        // RIS + LF
1447        ctx.do_edit(EditNotification::Insert { chars: "\u{1F1E6}\u{000A}".into() });
1448        ctx.do_edit(EditNotification::DeleteBackward);
1449        assert_eq!(harness.debug_render(), "\u{1F1E6}|");
1450        ctx.do_edit(EditNotification::DeleteBackward);
1451        assert_eq!(harness.debug_render(), "|");
1452    }
1453
1454    #[test]
1455    fn delete_tests() {
1456        use crate::rpc::GestureType::*;
1457        let initial_text = "\
1458        this is a string\n\
1459        that has three\n\
1460        lines.";
1461        let harness = ContextHarness::new(initial_text);
1462        let mut ctx = harness.make_context();
1463        ctx.do_edit(EditNotification::Gesture { line: 0, col: 0, ty: PointSelect });
1464
1465        ctx.do_edit(EditNotification::MoveRight);
1466        assert_eq!(harness.debug_render(),"\
1467        t|his is a string\n\
1468        that has three\n\
1469        lines." );
1470
1471        ctx.do_edit(EditNotification::DeleteBackward);
1472        assert_eq!(harness.debug_render(),"\
1473        |his is a string\n\
1474        that has three\n\
1475        lines." );
1476
1477        ctx.do_edit(EditNotification::DeleteForward);
1478        assert_eq!(harness.debug_render(),"\
1479        |is is a string\n\
1480        that has three\n\
1481        lines." );
1482
1483        ctx.do_edit(EditNotification::MoveWordRight);
1484        ctx.do_edit(EditNotification::DeleteWordForward);
1485        assert_eq!(harness.debug_render(),"\
1486        is| a string\n\
1487        that has three\n\
1488        lines." );
1489
1490        ctx.do_edit(EditNotification::DeleteWordBackward);
1491        assert_eq!(harness.debug_render(),"| \
1492        a string\n\
1493        that has three\n\
1494        lines." );
1495
1496        ctx.do_edit(EditNotification::MoveToRightEndOfLine);
1497        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1498        assert_eq!(harness.debug_render(),"\
1499        |\nthat has three\n\
1500        lines." );
1501
1502        ctx.do_edit(EditNotification::DeleteToEndOfParagraph);
1503        ctx.do_edit(EditNotification::DeleteToEndOfParagraph);
1504        assert_eq!(harness.debug_render(),"\
1505        |\nlines." );
1506    }
1507
1508    #[test]
1509    fn simple_indentation_test() {
1510        use crate::rpc::GestureType::*;
1511        let harness = ContextHarness::new("");
1512        let mut ctx = harness.make_context();
1513        // Single indent and outdent test
1514        ctx.do_edit(EditNotification::Insert { chars: "hello".into() });
1515        ctx.do_edit(EditNotification::Indent);
1516        assert_eq!(harness.debug_render(),"    hello|");
1517        ctx.do_edit(EditNotification::Outdent);
1518        assert_eq!(harness.debug_render(),"hello|");
1519
1520        // Test when outdenting with less than 4 spaces
1521        ctx.do_edit(EditNotification::Gesture { line: 0, col: 0, ty: PointSelect });
1522        ctx.do_edit(EditNotification::Insert { chars: "  ".into() });
1523        assert_eq!(harness.debug_render(),"  |hello");
1524        ctx.do_edit(EditNotification::Outdent);
1525        assert_eq!(harness.debug_render(),"|hello");
1526
1527        // Non-selection one line indent and outdent test
1528        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1529        ctx.do_edit(EditNotification::Indent);
1530        ctx.do_edit(EditNotification::InsertNewline);
1531        ctx.do_edit(EditNotification::Insert { chars: "world".into() });
1532        assert_eq!(harness.debug_render(),"    hello\nworld|");
1533
1534        ctx.do_edit(EditNotification::MoveWordLeft);
1535        ctx.do_edit(EditNotification::MoveToBeginningOfDocumentAndModifySelection);
1536        ctx.do_edit(EditNotification::Indent);
1537        assert_eq!(harness.debug_render(),"    [|    hello\n]world");
1538
1539        ctx.do_edit(EditNotification::Outdent);
1540        assert_eq!(harness.debug_render(),"[|    hello\n]world");
1541    }
1542
1543    #[test]
1544    fn multiline_indentation_test() {
1545        use crate::rpc::GestureType::*;
1546        let initial_text = "\
1547        this is a string\n\
1548        that has three\n\
1549        lines.";
1550        let harness = ContextHarness::new(initial_text);
1551        let mut ctx = harness.make_context();
1552
1553        ctx.do_edit(EditNotification::Gesture { line: 0, col: 5, ty: PointSelect });
1554        assert_eq!(harness.debug_render(),"\
1555        this |is a string\n\
1556        that has three\n\
1557        lines." );
1558
1559        ctx.do_edit(EditNotification::Gesture { line: 1, col: 5, ty: ToggleSel });
1560        assert_eq!(harness.debug_render(),"\
1561        this |is a string\n\
1562        that |has three\n\
1563        lines." );
1564
1565        // Simple multi line indent/outdent test
1566        ctx.do_edit(EditNotification::Indent);
1567        assert_eq!(harness.debug_render(),"    \
1568        this |is a string\n    \
1569        that |has three\n\
1570        lines." );
1571
1572        ctx.do_edit(EditNotification::Outdent);
1573        ctx.do_edit(EditNotification::Outdent);
1574        assert_eq!(harness.debug_render(),"\
1575        this |is a string\n\
1576        that |has three\n\
1577        lines." );
1578
1579        // Different position indent/outdent test
1580        // Shouldn't change cursor position
1581        ctx.do_edit(EditNotification::Gesture { line: 1, col: 5, ty: ToggleSel });
1582        ctx.do_edit(EditNotification::Gesture { line: 1, col: 10, ty: ToggleSel });
1583        assert_eq!(harness.debug_render(),"\
1584        this |is a string\n\
1585        that has t|hree\n\
1586        lines." );
1587
1588        ctx.do_edit(EditNotification::Indent);
1589        assert_eq!(harness.debug_render(),"    \
1590        this |is a string\n    \
1591        that has t|hree\n\
1592        lines." );
1593
1594        ctx.do_edit(EditNotification::Outdent);
1595        assert_eq!(harness.debug_render(),"\
1596        this |is a string\n\
1597        that has t|hree\n\
1598        lines." );
1599
1600        // Multi line selection test
1601        ctx.do_edit(EditNotification::Gesture { line: 1, col: 10, ty: ToggleSel });
1602        ctx.do_edit(EditNotification::MoveToEndOfDocumentAndModifySelection);
1603        ctx.do_edit(EditNotification::Indent);
1604        assert_eq!(harness.debug_render(),"    \
1605        this [is a string\n    \
1606        that has three\n    \
1607        lines.|]" );
1608
1609        ctx.do_edit(EditNotification::Outdent);
1610        assert_eq!(harness.debug_render(),"\
1611        this [is a string\n\
1612        that has three\n\
1613        lines.|]" );
1614
1615        // Multi cursor different line indent test
1616        ctx.do_edit(EditNotification::Gesture { line: 0, col: 0, ty: PointSelect });
1617        ctx.do_edit(EditNotification::Gesture { line: 2, col: 0, ty: ToggleSel });
1618        assert_eq!(harness.debug_render(),"\
1619        |this is a string\n\
1620        that has three\n\
1621        |lines." );
1622
1623        ctx.do_edit(EditNotification::Indent);
1624        assert_eq!(harness.debug_render(),"    \
1625        |this is a string\n\
1626        that has three\n    \
1627        |lines." );
1628
1629        ctx.do_edit(EditNotification::Outdent);
1630        assert_eq!(harness.debug_render(),"\
1631        |this is a string\n\
1632        that has three\n\
1633        |lines." );
1634    }
1635
1636    #[test]
1637    fn number_change_tests() {
1638        use crate::rpc::GestureType::*;
1639        let harness = ContextHarness::new("");
1640        let mut ctx = harness.make_context();
1641        // Single indent and outdent test
1642        ctx.do_edit(EditNotification::Insert { chars: "1234".into() });
1643        ctx.do_edit(EditNotification::IncreaseNumber);
1644        assert_eq!(harness.debug_render(), "1235|");
1645
1646        ctx.do_edit(EditNotification::Gesture { line: 0, col: 2, ty: PointSelect });
1647        ctx.do_edit(EditNotification::IncreaseNumber);
1648        assert_eq!(harness.debug_render(), "1236|");
1649
1650        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1651        ctx.do_edit(EditNotification::Insert { chars: "-42".into() });
1652        ctx.do_edit(EditNotification::IncreaseNumber);
1653        assert_eq!(harness.debug_render(), "-41|");
1654
1655        // Cursor is on the 3
1656        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1657        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1658        ctx.do_edit(EditNotification::Insert { chars: "this is a 336 text example".into() });
1659        ctx.do_edit(EditNotification::Gesture { line: 0, col: 11, ty: PointSelect });
1660        ctx.do_edit(EditNotification::DecreaseNumber);
1661        assert_eq!(harness.debug_render(), "this is a 335| text example");
1662
1663        // Cursor is on of the 3
1664        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1665        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1666        ctx.do_edit(EditNotification::Insert { chars: "this is a -336 text example".into() });
1667        ctx.do_edit(EditNotification::Gesture { line: 0, col: 11, ty: PointSelect });
1668        ctx.do_edit(EditNotification::DecreaseNumber);
1669        assert_eq!(harness.debug_render(), "this is a -337| text example");
1670
1671        // Cursor is on the 't' of text
1672        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1673        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1674        ctx.do_edit(EditNotification::Insert { chars: "this is a -336 text example".into() });
1675        ctx.do_edit(EditNotification::Gesture { line: 0, col: 15, ty: PointSelect });
1676        ctx.do_edit(EditNotification::DecreaseNumber);
1677        assert_eq!(harness.debug_render(), "this is a -336 |text example");
1678
1679        // test multiple iterations
1680        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1681        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1682        ctx.do_edit(EditNotification::Insert { chars: "this is a 336 text example".into() });
1683        ctx.do_edit(EditNotification::Gesture { line: 0, col: 11, ty: PointSelect });
1684        ctx.do_edit(EditNotification::IncreaseNumber);
1685        ctx.do_edit(EditNotification::IncreaseNumber);
1686        ctx.do_edit(EditNotification::IncreaseNumber);
1687        assert_eq!(harness.debug_render(), "this is a 339| text example");
1688
1689        // test changing number of chars
1690        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1691        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1692        ctx.do_edit(EditNotification::Insert { chars: "this is a 10 text example".into() });
1693        ctx.do_edit(EditNotification::Gesture { line: 0, col: 11, ty: PointSelect });
1694        ctx.do_edit(EditNotification::DecreaseNumber);
1695        assert_eq!(harness.debug_render(), "this is a 9| text example");
1696
1697        // test going negative
1698        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1699        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1700        ctx.do_edit(EditNotification::Insert { chars: "this is a 0 text example".into() });
1701        ctx.do_edit(EditNotification::Gesture { line: 0, col: 11, ty: PointSelect });
1702        ctx.do_edit(EditNotification::DecreaseNumber);
1703        assert_eq!(harness.debug_render(), "this is a -1| text example");
1704
1705        // test going positive
1706        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1707        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1708        ctx.do_edit(EditNotification::Insert { chars: "this is a -1 text example".into() });
1709        ctx.do_edit(EditNotification::Gesture { line: 0, col: 12, ty: PointSelect });
1710        ctx.do_edit(EditNotification::IncreaseNumber);
1711        assert_eq!(harness.debug_render(), "this is a 0| text example");
1712
1713        // if it begins in a region, nothing will happen
1714        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1715        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1716        ctx.do_edit(EditNotification::Insert { chars: "this is a 10 text example".into() });
1717        ctx.do_edit(EditNotification::Gesture { line: 0, col: 10, ty: PointSelect });
1718        ctx.do_edit(EditNotification::MoveToEndOfDocumentAndModifySelection);
1719        ctx.do_edit(EditNotification::DecreaseNumber);
1720        assert_eq!(harness.debug_render(), "this is a [10 text example|]");
1721
1722        // If a number just happens to be in a region, nothing will happen
1723        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1724        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1725        ctx.do_edit(EditNotification::Insert { chars: "this is a 10 text example".into() });
1726        ctx.do_edit(EditNotification::Gesture { line: 0, col: 5, ty: PointSelect });
1727        ctx.do_edit(EditNotification::MoveToEndOfDocumentAndModifySelection);
1728        ctx.do_edit(EditNotification::DecreaseNumber);
1729        assert_eq!(harness.debug_render(), "this [is a 10 text example|]");
1730
1731        // if it ends on a region, the number will be changed
1732        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1733        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1734        ctx.do_edit(EditNotification::Insert { chars: "this is a 10".into() });
1735        ctx.do_edit(EditNotification::Gesture { line: 0, col: 0, ty: PointSelect });
1736        ctx.do_edit(EditNotification::MoveToEndOfDocumentAndModifySelection);
1737        ctx.do_edit(EditNotification::IncreaseNumber);
1738        assert_eq!(harness.debug_render(), "[this is a 11|]");
1739
1740        // if only a part of a number is in a region, the whole number will be changed
1741        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1742        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1743        ctx.do_edit(EditNotification::Insert { chars: "this is a 1000 text example".into() });
1744        ctx.do_edit(EditNotification::Gesture { line: 0, col: 11, ty: PointSelect });
1745        ctx.do_edit(EditNotification::MoveRightAndModifySelection);
1746        ctx.do_edit(EditNotification::DecreaseNumber);
1747        assert_eq!(harness.debug_render(), "this is a 999| text example");
1748
1749        // invalid numbers
1750        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1751        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1752        ctx.do_edit(EditNotification::Insert { chars: "10_000".into() });
1753        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1754        ctx.do_edit(EditNotification::IncreaseNumber);
1755        assert_eq!(harness.debug_render(), "10_000|");
1756
1757        // decimals are kinda accounted for (i.e. 4.55 becomes 4.56 (good), but 4.99 becomes 4.100 (bad)
1758        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1759        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1760        ctx.do_edit(EditNotification::Insert { chars: "4.55".into() });
1761        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1762        ctx.do_edit(EditNotification::IncreaseNumber);
1763        assert_eq!(harness.debug_render(), "4.56|");
1764
1765        // invalid numbers
1766        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1767        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1768        ctx.do_edit(EditNotification::Insert { chars: "0xFF03".into() });
1769        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1770        ctx.do_edit(EditNotification::IncreaseNumber);
1771        assert_eq!(harness.debug_render(), "0xFF03|");
1772
1773        // Test multiple selections
1774        ctx.do_edit(EditNotification::MoveToEndOfDocument);
1775        ctx.do_edit(EditNotification::DeleteToBeginningOfLine);
1776        let multi_text = "\
1777        example 42 number\n\
1778        example 90 number\n\
1779        Done.";
1780        ctx.do_edit(EditNotification::Insert { chars: multi_text.into() });
1781        ctx.do_edit(EditNotification::Gesture { line: 1, col: 9, ty: PointSelect });
1782        ctx.do_edit(EditNotification::AddSelectionAbove);
1783        ctx.do_edit(EditNotification::IncreaseNumber);
1784        assert_eq!(harness.debug_render(), "\
1785        example 43| number\n\
1786        example 91| number\n\
1787        Done.");
1788    }
1789
1790    #[test]
1791    fn text_recording() {
1792        use crate::rpc::GestureType::*;
1793        let initial_text = "";
1794        let harness = ContextHarness::new(initial_text);
1795        let mut ctx = harness.make_context();
1796
1797        let recording_name = String::new();
1798
1799        ctx.do_edit(EditNotification::Gesture { line: 0, col: 0, ty: PointSelect });
1800        assert_eq!(harness.debug_render(), "|");
1801
1802        ctx.do_edit(EditNotification::ToggleRecording { recording_name: Some(recording_name.clone()) });
1803
1804        ctx.do_edit(EditNotification::Insert { chars: "Foo ".to_owned() });
1805        ctx.do_edit(EditNotification::Insert { chars: "B".to_owned() });
1806        ctx.do_edit(EditNotification::Insert { chars: "A".to_owned() });
1807        ctx.do_edit(EditNotification::Insert { chars: "R".to_owned() });
1808        assert_eq!(harness.debug_render(), "Foo BAR|");
1809
1810        ctx.do_edit(EditNotification::ToggleRecording { recording_name: Some(recording_name.clone())});
1811        ctx.do_edit(EditNotification::Insert { chars: " ".to_owned() });
1812
1813        ctx.do_edit(EditNotification::PlayRecording { recording_name });
1814        assert_eq!(harness.debug_render(), "Foo BAR Foo BAR|");
1815    }
1816
1817    #[test]
1818    fn movement_recording() {
1819        use crate::rpc::GestureType::*;
1820        let initial_text = "\
1821        this is a string\n\
1822        that has about\n\
1823        four really nice\n\
1824        lines to see.";
1825        let harness = ContextHarness::new(initial_text);
1826        let mut ctx = harness.make_context();
1827
1828        let recording_name = String::new();
1829
1830        ctx.do_edit(EditNotification::Gesture { line: 0, col: 5, ty: PointSelect });
1831        assert_eq!(harness.debug_render(),"\
1832        this |is a string\n\
1833        that has about\n\
1834        four really nice\n\
1835        lines to see." );
1836
1837        ctx.do_edit(EditNotification::ToggleRecording { recording_name: Some(recording_name.clone()) });
1838
1839        // Swap last word of the current line and the line below
1840        ctx.do_edit(EditNotification::AddSelectionBelow);
1841        ctx.do_edit(EditNotification::MoveToRightEndOfLine);
1842        ctx.do_edit(EditNotification::MoveWordLeftAndModifySelection);
1843        ctx.do_edit(EditNotification::Transpose);
1844        ctx.do_edit(EditNotification::CollapseSelections);
1845        ctx.do_edit(EditNotification::MoveToRightEndOfLine);
1846        assert_eq!(harness.debug_render(),"\
1847        this is a about|\n\
1848        that has string\n\
1849        four really nice\n\
1850        lines to see." );
1851
1852        ctx.do_edit(EditNotification::ToggleRecording { recording_name: Some(recording_name.clone())});
1853
1854        ctx.do_edit(EditNotification::Gesture { line: 2, col: 5, ty: PointSelect });
1855        ctx.do_edit(EditNotification::PlayRecording { recording_name: recording_name.clone() });
1856        assert_eq!(harness.debug_render(),"\
1857        this is a about\n\
1858        that has string\n\
1859        four really see.|\n\
1860        lines to nice" );
1861
1862        // Undo entire playback in a single command
1863        ctx.do_edit(EditNotification::Undo);
1864        assert_eq!(harness.debug_render(),"\
1865        this is a about\n\
1866        that has string\n\
1867        four really nice|\n\
1868        lines to see." );
1869
1870        // Make sure we can redo in a single command as well
1871        ctx.do_edit(EditNotification::Redo);
1872        assert_eq!(harness.debug_render(),"\
1873        this is a about\n\
1874        that has string\n\
1875        four really see.|\n\
1876        lines to nice" );
1877
1878        // We shouldn't be able to use cleared recordings
1879        ctx.do_edit(EditNotification::Undo);
1880        ctx.do_edit(EditNotification::Undo);
1881        ctx.do_edit(EditNotification::ClearRecording { recording_name: recording_name.clone() });
1882        ctx.do_edit(EditNotification::PlayRecording { recording_name });
1883        assert_eq!(harness.debug_render(),"\
1884        this is a string\n\
1885        that has about\n\
1886        four really nice|\n\
1887        lines to see." );
1888    }
1889
1890    #[test]
1891    fn test_exact_position() {
1892        use crate::rpc::GestureType::*;
1893        let initial_text = "\
1894        this is a string\n\
1895        that has three\n\
1896        \n\
1897        lines.\n\
1898        And lines with very different length.";
1899        let harness = ContextHarness::new(initial_text);
1900        let mut ctx = harness.make_context();
1901        ctx.do_edit(EditNotification::Gesture { line: 1, col: 5, ty: PointSelect });
1902        ctx.do_edit(EditNotification::AddSelectionAbove);
1903        assert_eq!(harness.debug_render(),"\
1904        this |is a string\n\
1905        that |has three\n\
1906        \n\
1907        lines.\n\
1908        And lines with very different length.");
1909
1910        ctx.do_edit(EditNotification::CollapseSelections);
1911        ctx.do_edit(EditNotification::Gesture { line: 1, col: 5, ty: PointSelect });
1912        ctx.do_edit(EditNotification::AddSelectionBelow);
1913        assert_eq!(harness.debug_render(),"\
1914        this is a string\n\
1915        that |has three\n\
1916        \n\
1917        lines|.\n\
1918        And lines with very different length.");
1919
1920        ctx.do_edit(EditNotification::CollapseSelections);
1921        ctx.do_edit(EditNotification::Gesture { line: 4, col: 10, ty: PointSelect });
1922        ctx.do_edit(EditNotification::AddSelectionAbove);
1923        assert_eq!(harness.debug_render(),"\
1924        this is a string\n\
1925        that has t|hree\n\
1926        \n\
1927        lines.\n\
1928        And lines |with very different length.");
1929    }
1930
1931    #[test]
1932    fn test_illegal_plugin_edit() {
1933        use xi_rope::DeltaBuilder;
1934        use crate::plugins::rpc::{PluginNotification, PluginEdit};
1935        use crate::plugins::PluginPid;
1936
1937        let text = "text";
1938        let harness = ContextHarness::new(text);
1939        let mut ctx = harness.make_context();
1940        let rev_token = ctx.editor.borrow().get_head_rev_token();
1941
1942        let iv = Interval::new(1, 1);
1943        let mut builder = DeltaBuilder::new(0); // wrong length
1944        builder.replace(iv, "1".into());
1945
1946        let edit_one = PluginEdit {
1947            rev: rev_token,
1948            delta: builder.build(),
1949            priority: 55,
1950            after_cursor: false,
1951            undo_group: None,
1952            author: "plugin_one".into(),
1953        };
1954
1955        ctx.do_plugin_cmd(PluginPid(1), PluginNotification::Edit { edit: edit_one });
1956        let new_rev_token = ctx.editor.borrow().get_head_rev_token();
1957        // no change should be made
1958        assert_eq!(rev_token, new_rev_token);
1959    }
1960
1961
1962    #[test]
1963    fn empty_transpose() {
1964        let harness = ContextHarness::new("");
1965        let mut ctx = harness.make_context();
1966
1967        ctx.do_edit(EditNotification::Transpose);
1968
1969        assert_eq!(harness.debug_render(), "|"); // should be noop
1970    }
1971
1972    // This is the issue reported by #962
1973    #[test]
1974    fn eol_multicursor_transpose() {
1975        use crate::rpc::GestureType::*;
1976
1977        let harness = ContextHarness::new("word\n");
1978        let mut ctx = harness.make_context();
1979
1980        ctx.do_edit(EditNotification::Gesture{line: 0, col: 4, ty: PointSelect}); // end of first line
1981        ctx.do_edit(EditNotification::AddSelectionBelow); // add cursor below that, at eof
1982        ctx.do_edit(EditNotification::Transpose);
1983
1984        assert_eq!(harness.debug_render(), "wor\nd|");
1985    }
1986}