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