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 
17 use std::cell::RefCell;
18 use std::iter;
19 use std::ops::Range;
20 use std::path::Path;
21 use std::time::{Duration, Instant};
22 
23 use serde_json::{self, Value};
24 
25 use xi_rope::{Cursor, Interval, LinesMetric, Rope, RopeDelta};
26 use xi_rpc::{Error as RpcError, RemoteError};
27 use xi_trace::trace_block;
28 
29 use crate::plugins::rpc::{
30     ClientPluginInfo, Hover, PluginBufferInfo, PluginNotification, PluginRequest, PluginUpdate,
31 };
32 use crate::rpc::{EditNotification, EditRequest, LineRange, Position as ClientPosition};
33 
34 use crate::client::Client;
35 use crate::config::{BufferItems, Table};
36 use crate::edit_types::{EventDomain, SpecialEvent};
37 use crate::editor::Editor;
38 use crate::file::FileInfo;
39 use crate::plugins::Plugin;
40 use crate::recorder::Recorder;
41 use crate::selection::InsertDrift;
42 use crate::styles::ThemeStyleMap;
43 use crate::syntax::LanguageId;
44 use crate::tabs::{
45     BufferId, PluginId, ViewId, FIND_VIEW_IDLE_MASK, RENDER_VIEW_IDLE_MASK, REWRAP_VIEW_IDLE_MASK,
46 };
47 use crate::view::View;
48 use crate::width_cache::WidthCache;
49 use crate::WeakXiCore;
50 
51 // Maximum returned result from plugin get_data RPC.
52 pub 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.
58 const 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.
64 pub 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 
82 impl<'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.
with_editor<R, F>(&mut self, f: F) -> R where F: FnOnce(&mut Editor, &mut View, &mut Rope, &BufferItems) -> R,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.
with_view<R, F>(&mut self, f: F) -> R where F: FnOnce(&mut View, &Rope) -> R,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 
with_each_plugin<F: FnMut(&&Plugin)>(&self, f: F)107     fn with_each_plugin<F: FnMut(&&Plugin)>(&self, f: F) {
108         self.plugins.iter().for_each(f)
109     }
110 
do_edit(&mut self, cmd: EditNotification)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 
dispatch_event(&mut self, event: EventDomain)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 
do_special(&mut self, cmd: SpecialEvent)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 
do_edit_sync(&mut self, cmd: EditRequest) -> Result<Value, RemoteError>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 
do_plugin_cmd(&mut self, plugin: PluginId, cmd: PluginNotification)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 
do_plugin_cmd_sync(&mut self, _plugin: PluginId, cmd: PluginRequest) -> Value259     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.
after_edit(&mut self, author: &str)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 
update_views(&self, ed: &Editor, delta: &RopeDelta, last_text: &Rope, drift: InsertDrift)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 
update_plugins(&self, ed: &mut Editor, delta: RopeDelta, author: &str)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.
render_if_needed(&mut self)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 
_finish_delayed_render(&mut self)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.
render(&mut self)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.
386 impl<'a> EventContext<'a> {
finish_init(&mut self, config: &Table)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 
after_save(&mut self, path: &Path)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
close_view(&self) -> bool417     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 
config_changed(&mut self, changes: &Table)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 
language_changed(&mut self, new_language_id: &LanguageId)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 
reload(&mut self, text: Rope)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 
plugin_info(&mut self) -> PluginBufferInfo456     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 
plugin_started(&mut self, plugin: &Plugin)478     pub(crate) fn plugin_started(&mut self, plugin: &Plugin) {
479         self.client.plugin_started(self.view_id, &plugin.name)
480     }
481 
plugin_stopped(&mut self, plugin: &Plugin)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 
do_plugin_update(&mut self, update: Result<Value, RpcError>)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.
text_for_save(&mut self) -> Rope507     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.
update_wrap_settings(&mut self, rewrap_immediately: bool)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.
rewrap(&mut self)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.
do_incremental_find(&mut self)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 
schedule_find(&self)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.
find(&mut self)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.
do_rewrap_batch(&mut self)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 
schedule_rewrap(&self)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 
do_request_lines(&mut self, first: usize, last: usize)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 
selected_line_ranges(&mut self) -> Vec<(usize, usize)>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 
do_reindent(&mut self)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 
do_debug_toggle_comment(&mut self)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 
do_request_hover(&mut self, request_id: usize, position: Option<ClientPosition>)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 
do_show_hover(&mut self, request_id: usize, hover: Result<Hover, RemoteError>)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
get_resolved_position(&mut self, position: Option<ClientPosition>) -> Option<usize>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]
694 mod 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 {
new<S: AsRef<str>>(s: S) -> Self714         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\].
debug_render(&self) -> String738         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 
make_context<'a>(&'a self) -> EventContext<'a>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]
smoke_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]
test_gestures()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]
delete_combining_enclosing_keycaps_tests()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]
delete_variation_selector_tests()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]
delete_emoji_zwj_sequence_tests()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]
delete_flags_tests()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]
delete_emoji_modifier_tests()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]
delete_mixed_edge_cases_tests()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]
delete_tests()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]
simple_indentation_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]
multiline_indentation_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]
number_change_tests()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]
text_recording()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]
movement_recording()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]
test_exact_position()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]
test_illegal_plugin_edit()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]
empty_transpose()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]
eol_multicursor_transpose()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 }
1987