From 14e2ced440a2ba0f1794644f5cc1295a5738ad36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20S=2E=20Szczygie=C5=82?= Date: Tue, 1 Mar 2022 02:45:29 +0100 Subject: [PATCH] Make repeat operator work with completion edits (#1640) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add basic completion replay * use transaction as the last completion * completion replay only on trigger position * cache changes in CompletionAction Co-authored-by: Blaž Hrastnik --- helix-term/src/ui/completion.rs | 33 +++++++++++++++++--- helix-term/src/ui/editor.rs | 55 ++++++++++++++++++++++++++++----- helix-view/src/editor.rs | 10 ++++++ 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index a6e92a19d..abed28461 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -1,10 +1,11 @@ use crate::compositor::{Component, Context, EventResult}; use crossterm::event::{Event, KeyCode, KeyEvent}; +use helix_view::editor::CompleteAction; use tui::buffer::Buffer as Surface; use std::borrow::Cow; -use helix_core::Transaction; +use helix_core::{Change, Transaction}; use helix_view::{graphics::Rect, Document, Editor}; use crate::commands; @@ -92,13 +93,14 @@ impl Completion { start_offset: usize, trigger_offset: usize, ) -> Transaction { - if let Some(edit) = &item.text_edit { + let transaction = if let Some(edit) = &item.text_edit { let edit = match edit { lsp::CompletionTextEdit::Edit(edit) => edit.clone(), lsp::CompletionTextEdit::InsertAndReplace(item) => { unimplemented!("completion: insert_and_replace {:?}", item) } }; + util::generate_transaction_from_edits( doc.text(), vec![edit], @@ -114,7 +116,16 @@ impl Completion { doc.text(), vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(), ) - } + }; + + transaction + } + + fn completion_changes(transaction: &Transaction, trigger_offset: usize) -> Vec { + transaction + .changes_iter() + .filter(|(start, end, _)| (*start..=*end).contains(&trigger_offset)) + .collect() } let (view, doc) = current!(editor); @@ -123,7 +134,9 @@ impl Completion { doc.restore(view.id); match event { - PromptEvent::Abort => {} + PromptEvent::Abort => { + editor.last_completion = None; + } PromptEvent::Update => { // always present here let item = item.unwrap(); @@ -138,8 +151,12 @@ impl Completion { // initialize a savepoint doc.savepoint(); - doc.apply(&transaction, view.id); + + editor.last_completion = Some(CompleteAction { + trigger_offset, + changes: completion_changes(&transaction, trigger_offset), + }); } PromptEvent::Validate => { // always present here @@ -152,8 +169,14 @@ impl Completion { start_offset, trigger_offset, ); + doc.apply(&transaction, view.id); + editor.last_completion = Some(CompleteAction { + trigger_offset, + changes: completion_changes(&transaction, trigger_offset), + }); + // apply additional edits, mostly used to auto import unqualified types let resolved_additional_text_edits = if item.additional_text_edits.is_some() { None diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index b6aaf9e04..9ac724061 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -15,11 +15,11 @@ use helix_core::{ syntax::{self, HighlightEvent}, unicode::segmentation::UnicodeSegmentation, unicode::width::UnicodeWidthStr, - LineEnding, Position, Range, Selection, + LineEnding, Position, Range, Selection, Transaction, }; use helix_view::{ document::{Mode, SCRATCH_BUFFER_NAME}, - editor::CursorShapeConfig, + editor::{CompleteAction, CursorShapeConfig}, graphics::{CursorKind, Modifier, Rect, Style}, input::KeyEvent, keyboard::{KeyCode, KeyModifiers}, @@ -33,11 +33,18 @@ use tui::buffer::Buffer as Surface; pub struct EditorView { pub keymaps: Keymaps, on_next_key: Option>, - last_insert: (commands::MappableCommand, Vec), + last_insert: (commands::MappableCommand, Vec), pub(crate) completion: Option, spinners: ProgressSpinners, } +#[derive(Debug, Clone)] +pub enum InsertEvent { + Key(KeyEvent), + CompletionApply(CompleteAction), + TriggerCompletion, +} + impl Default for EditorView { fn default() -> Self { Self::new(Keymaps::default()) @@ -766,8 +773,33 @@ impl EditorView { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); // then replay the inputs - for &key in &self.last_insert.1.clone() { - self.insert_mode(cxt, key) + for key in self.last_insert.1.clone() { + match key { + InsertEvent::Key(key) => self.insert_mode(cxt, key), + InsertEvent::CompletionApply(compl) => { + let (view, doc) = current!(cxt.editor); + + doc.restore(view.id); + + let text = doc.text().slice(..); + let cursor = doc.selection(view.id).primary().cursor(text); + + let shift_position = + |pos: usize| -> usize { pos + cursor - compl.trigger_offset }; + + let tx = Transaction::change( + doc.text(), + compl.changes.iter().cloned().map(|(start, end, t)| { + (shift_position(start), shift_position(end), t) + }), + ); + doc.apply(&tx, view.id); + } + InsertEvent::TriggerCompletion => { + let (_, doc) = current!(cxt.editor); + doc.savepoint(); + } + } } } _ => { @@ -808,6 +840,9 @@ impl EditorView { // Immediately initialize a savepoint doc_mut!(editor).savepoint(); + editor.last_completion = None; + self.last_insert.1.push(InsertEvent::TriggerCompletion); + // TODO : propagate required size on resize to completion too completion.required_size((size.width, size.height)); self.completion = Some(completion); @@ -1067,9 +1102,6 @@ impl Component for EditorView { } else { match mode { Mode::Insert => { - // record last_insert key - self.last_insert.1.push(key); - // let completion swallow the event if necessary let mut consumed = false; if let Some(completion) = &mut self.completion { @@ -1093,8 +1125,15 @@ impl Component for EditorView { // if completion didn't take the event, we pass it onto commands if !consumed { + if let Some(compl) = cx.editor.last_completion.take() { + self.last_insert.1.push(InsertEvent::CompletionApply(compl)); + } + self.insert_mode(&mut cx, key); + // record last_insert key + self.last_insert.1.push(InsertEvent::Key(key)); + // lastly we recalculate completion if let Some(completion) = &mut self.completion { completion.update(&mut cx); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 3a2a9af8d..0eb613087 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -33,6 +33,7 @@ pub use helix_core::register::Registers; use helix_core::{ auto_pairs::AutoPairs, syntax::{self, AutoPairConfig}, + Change, }; use helix_core::{Position, Selection}; use helix_dap as dap; @@ -301,9 +302,17 @@ pub struct Editor { pub last_motion: Option, pub pseudo_pending: Option, + pub last_completion: Option, + pub exit_code: i32, } +#[derive(Debug, Clone)] +pub struct CompleteAction { + pub trigger_offset: usize, + pub changes: Vec, +} + #[derive(Debug, Copy, Clone)] pub enum Action { Load, @@ -347,6 +356,7 @@ impl Editor { autoinfo: None, idle_timer: Box::pin(sleep(config.idle_timeout)), last_motion: None, + last_completion: None, pseudo_pending: None, config, auto_pairs,