Make repeat operator work with completion edits (#1640)

* 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 <blaz@mxxn.io>
pull/1726/head
Mateusz S. Szczygieł 3 years ago committed by GitHub
parent e83cdf3fd3
commit 14e2ced440
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,10 +1,11 @@
use crate::compositor::{Component, Context, EventResult}; use crate::compositor::{Component, Context, EventResult};
use crossterm::event::{Event, KeyCode, KeyEvent}; use crossterm::event::{Event, KeyCode, KeyEvent};
use helix_view::editor::CompleteAction;
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use std::borrow::Cow; use std::borrow::Cow;
use helix_core::Transaction; use helix_core::{Change, Transaction};
use helix_view::{graphics::Rect, Document, Editor}; use helix_view::{graphics::Rect, Document, Editor};
use crate::commands; use crate::commands;
@ -92,13 +93,14 @@ impl Completion {
start_offset: usize, start_offset: usize,
trigger_offset: usize, trigger_offset: usize,
) -> Transaction { ) -> Transaction {
if let Some(edit) = &item.text_edit { let transaction = if let Some(edit) = &item.text_edit {
let edit = match edit { let edit = match edit {
lsp::CompletionTextEdit::Edit(edit) => edit.clone(), lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
lsp::CompletionTextEdit::InsertAndReplace(item) => { lsp::CompletionTextEdit::InsertAndReplace(item) => {
unimplemented!("completion: insert_and_replace {:?}", item) unimplemented!("completion: insert_and_replace {:?}", item)
} }
}; };
util::generate_transaction_from_edits( util::generate_transaction_from_edits(
doc.text(), doc.text(),
vec![edit], vec![edit],
@ -114,7 +116,16 @@ impl Completion {
doc.text(), doc.text(),
vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(), vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
) )
};
transaction
} }
fn completion_changes(transaction: &Transaction, trigger_offset: usize) -> Vec<Change> {
transaction
.changes_iter()
.filter(|(start, end, _)| (*start..=*end).contains(&trigger_offset))
.collect()
} }
let (view, doc) = current!(editor); let (view, doc) = current!(editor);
@ -123,7 +134,9 @@ impl Completion {
doc.restore(view.id); doc.restore(view.id);
match event { match event {
PromptEvent::Abort => {} PromptEvent::Abort => {
editor.last_completion = None;
}
PromptEvent::Update => { PromptEvent::Update => {
// always present here // always present here
let item = item.unwrap(); let item = item.unwrap();
@ -138,8 +151,12 @@ impl Completion {
// initialize a savepoint // initialize a savepoint
doc.savepoint(); doc.savepoint();
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
editor.last_completion = Some(CompleteAction {
trigger_offset,
changes: completion_changes(&transaction, trigger_offset),
});
} }
PromptEvent::Validate => { PromptEvent::Validate => {
// always present here // always present here
@ -152,8 +169,14 @@ impl Completion {
start_offset, start_offset,
trigger_offset, trigger_offset,
); );
doc.apply(&transaction, view.id); 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 // apply additional edits, mostly used to auto import unqualified types
let resolved_additional_text_edits = if item.additional_text_edits.is_some() { let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
None None

@ -15,11 +15,11 @@ use helix_core::{
syntax::{self, HighlightEvent}, syntax::{self, HighlightEvent},
unicode::segmentation::UnicodeSegmentation, unicode::segmentation::UnicodeSegmentation,
unicode::width::UnicodeWidthStr, unicode::width::UnicodeWidthStr,
LineEnding, Position, Range, Selection, LineEnding, Position, Range, Selection, Transaction,
}; };
use helix_view::{ use helix_view::{
document::{Mode, SCRATCH_BUFFER_NAME}, document::{Mode, SCRATCH_BUFFER_NAME},
editor::CursorShapeConfig, editor::{CompleteAction, CursorShapeConfig},
graphics::{CursorKind, Modifier, Rect, Style}, graphics::{CursorKind, Modifier, Rect, Style},
input::KeyEvent, input::KeyEvent,
keyboard::{KeyCode, KeyModifiers}, keyboard::{KeyCode, KeyModifiers},
@ -33,11 +33,18 @@ use tui::buffer::Buffer as Surface;
pub struct EditorView { pub struct EditorView {
pub keymaps: Keymaps, pub keymaps: Keymaps,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>, on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
last_insert: (commands::MappableCommand, Vec<KeyEvent>), last_insert: (commands::MappableCommand, Vec<InsertEvent>),
pub(crate) completion: Option<Completion>, pub(crate) completion: Option<Completion>,
spinners: ProgressSpinners, spinners: ProgressSpinners,
} }
#[derive(Debug, Clone)]
pub enum InsertEvent {
Key(KeyEvent),
CompletionApply(CompleteAction),
TriggerCompletion,
}
impl Default for EditorView { impl Default for EditorView {
fn default() -> Self { fn default() -> Self {
Self::new(Keymaps::default()) Self::new(Keymaps::default())
@ -766,8 +773,33 @@ impl EditorView {
// first execute whatever put us into insert mode // first execute whatever put us into insert mode
self.last_insert.0.execute(cxt); self.last_insert.0.execute(cxt);
// then replay the inputs // then replay the inputs
for &key in &self.last_insert.1.clone() { for key in self.last_insert.1.clone() {
self.insert_mode(cxt, key) 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 // Immediately initialize a savepoint
doc_mut!(editor).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 // TODO : propagate required size on resize to completion too
completion.required_size((size.width, size.height)); completion.required_size((size.width, size.height));
self.completion = Some(completion); self.completion = Some(completion);
@ -1067,9 +1102,6 @@ impl Component for EditorView {
} else { } else {
match mode { match mode {
Mode::Insert => { Mode::Insert => {
// record last_insert key
self.last_insert.1.push(key);
// let completion swallow the event if necessary // let completion swallow the event if necessary
let mut consumed = false; let mut consumed = false;
if let Some(completion) = &mut self.completion { 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 completion didn't take the event, we pass it onto commands
if !consumed { 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); self.insert_mode(&mut cx, key);
// record last_insert key
self.last_insert.1.push(InsertEvent::Key(key));
// lastly we recalculate completion // lastly we recalculate completion
if let Some(completion) = &mut self.completion { if let Some(completion) = &mut self.completion {
completion.update(&mut cx); completion.update(&mut cx);

@ -33,6 +33,7 @@ pub use helix_core::register::Registers;
use helix_core::{ use helix_core::{
auto_pairs::AutoPairs, auto_pairs::AutoPairs,
syntax::{self, AutoPairConfig}, syntax::{self, AutoPairConfig},
Change,
}; };
use helix_core::{Position, Selection}; use helix_core::{Position, Selection};
use helix_dap as dap; use helix_dap as dap;
@ -301,9 +302,17 @@ pub struct Editor {
pub last_motion: Option<Motion>, pub last_motion: Option<Motion>,
pub pseudo_pending: Option<String>, pub pseudo_pending: Option<String>,
pub last_completion: Option<CompleteAction>,
pub exit_code: i32, pub exit_code: i32,
} }
#[derive(Debug, Clone)]
pub struct CompleteAction {
pub trigger_offset: usize,
pub changes: Vec<Change>,
}
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Action { pub enum Action {
Load, Load,
@ -347,6 +356,7 @@ impl Editor {
autoinfo: None, autoinfo: None,
idle_timer: Box::pin(sleep(config.idle_timeout)), idle_timer: Box::pin(sleep(config.idle_timeout)),
last_motion: None, last_motion: None,
last_completion: None,
pseudo_pending: None, pseudo_pending: None,
config, config,
auto_pairs, auto_pairs,

Loading…
Cancel
Save