correctly handle completion rerequest

pull/16/head
Pascal Kuthe 2 years ago committed by Blaž Hrastnik
parent 91da0dc172
commit 5406e9f629

@ -33,7 +33,7 @@ use helix_core::{
use helix_view::{
clipboard::ClipboardType,
document::{FormatterError, Mode, SCRATCH_BUFFER_NAME},
editor::{Action, Motion},
editor::{Action, CompleteAction, Motion},
info::Info,
input::KeyEvent,
keyboard::KeyCode,
@ -4254,7 +4254,12 @@ pub fn completion(cx: &mut Context) {
iter.reverse();
let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count();
let start_offset = cursor.saturating_sub(offset);
let savepoint = doc.savepoint(view);
let savepoint = if let Some(CompleteAction::Selected { savepoint }) = &cx.editor.last_completion
{
savepoint.clone()
} else {
doc.savepoint(view)
};
let trigger_doc = doc.id();
let trigger_view = view.id;

@ -209,14 +209,27 @@ impl Completion {
let (view, doc) = current!(editor);
// if more text was entered, remove it
doc.restore(view, &savepoint);
match event {
PromptEvent::Abort => {
editor.last_completion = None;
}
PromptEvent::Abort => {}
PromptEvent::Update => {
// Update creates "ghost" transactiosn which are not send to the
// lsp server to avoid messing up rerequesting completions. Once a
// completion has been selected (with) tab it's always accepted whenever anything
// is typed. The only way to avoid that is to explicitly abort the completion
// with esc/c-c. This will remove the "ghost" transaction.
//
// The ghost transaction is modeled with a transaction that is not send to the LS.
// (apply_temporary) and a savepoint. It's extremly important this savepoint is restored
// (also without sending the transaction to the LS) *before any further transaction is applied*.
// Otherwise incremental sync breaks (since the state of the LS doesn't match the state the transaction
// is applied to).
if editor.last_completion.is_none() {
editor.last_completion = Some(CompleteAction::Selected {
savepoint: doc.savepoint(view),
})
}
// if more text was entered, remove it
doc.restore(view, &savepoint, false);
// always present here
let item = item.unwrap();
@ -229,19 +242,20 @@ impl Completion {
true,
replace_mode,
);
// initialize a savepoint
doc.apply(&transaction, view.id);
editor.last_completion = Some(CompleteAction {
trigger_offset,
changes: completion_changes(&transaction, trigger_offset),
});
doc.apply_temporary(&transaction, view.id);
}
PromptEvent::Validate => {
if let Some(CompleteAction::Selected { savepoint }) =
editor.last_completion.take()
{
doc.restore(view, &savepoint, false);
}
// always present here
let item = item.unwrap();
// if more text was entered, remove it
doc.restore(view, &savepoint, true);
let transaction = item_to_transaction(
doc,
view.id,
@ -251,10 +265,9 @@ impl Completion {
false,
replace_mode,
);
doc.apply(&transaction, view.id);
editor.last_completion = Some(CompleteAction {
editor.last_completion = Some(CompleteAction::Applied {
trigger_offset,
changes: completion_changes(&transaction, trigger_offset),
});
@ -270,7 +283,6 @@ impl Completion {
} else {
Self::resolve_completion_item(doc, item.clone())
};
if let Some(additional_edits) = resolved_item
.as_ref()
.and_then(|item| item.additional_text_edits.as_ref())

@ -19,7 +19,7 @@ use helix_core::{
syntax::{self, HighlightEvent},
text_annotations::TextAnnotations,
unicode::width::UnicodeWidthStr,
visual_offset_from_block, Position, Range, Selection, Transaction,
visual_offset_from_block, Change, Position, Range, Selection, Transaction,
};
use helix_view::{
document::{Mode, SavePoint, SCRATCH_BUFFER_NAME},
@ -48,7 +48,10 @@ pub struct EditorView {
#[derive(Debug, Clone)]
pub enum InsertEvent {
Key(KeyEvent),
CompletionApply(CompleteAction),
CompletionApply {
trigger_offset: usize,
changes: Vec<Change>,
},
TriggerCompletion,
RequestCompletion,
}
@ -813,7 +816,7 @@ impl EditorView {
}
(Mode::Insert, Mode::Normal) => {
// if exiting insert mode, remove completion
self.completion = None;
self.clear_completion(cxt.editor);
cxt.editor.completion_request_handle = None;
// TODO: Use an on_mode_change hook to remove signature help
@ -891,22 +894,25 @@ impl EditorView {
for key in self.last_insert.1.clone() {
match key {
InsertEvent::Key(key) => self.insert_mode(cxt, key),
InsertEvent::CompletionApply(compl) => {
InsertEvent::CompletionApply {
trigger_offset,
changes,
} => {
let (view, doc) = current!(cxt.editor);
if let Some(last_savepoint) = last_savepoint.as_deref() {
doc.restore(view, last_savepoint);
doc.restore(view, last_savepoint, true);
}
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 };
|pos: usize| -> usize { pos + cursor - trigger_offset };
let tx = Transaction::change(
doc.text(),
compl.changes.iter().cloned().map(|(start, end, t)| {
changes.iter().cloned().map(|(start, end, t)| {
(shift_position(start), shift_position(end), t)
}),
);
@ -979,6 +985,21 @@ impl EditorView {
pub fn clear_completion(&mut self, editor: &mut Editor) {
self.completion = None;
if let Some(last_completion) = editor.last_completion.take() {
match last_completion {
CompleteAction::Applied {
trigger_offset,
changes,
} => self.last_insert.1.push(InsertEvent::CompletionApply {
trigger_offset,
changes,
}),
CompleteAction::Selected { savepoint } => {
let (view, doc) = current!(editor);
doc.restore(view, &savepoint, false);
}
}
}
// Clear any savepoints
editor.clear_idle_timer(); // don't retrigger
@ -1265,12 +1286,22 @@ impl Component for EditorView {
jobs: cx.jobs,
scroll: None,
};
completion.handle_event(event, &mut cx)
};
if let EventResult::Consumed(callback) = res {
consumed = true;
if let EventResult::Consumed(callback) =
completion.handle_event(event, &mut cx)
{
consumed = true;
Some(callback)
} else if let EventResult::Consumed(callback) =
completion.handle_event(&Event::Key(key!(Enter)), &mut cx)
{
Some(callback)
} else {
None
}
};
if let Some(callback) = res {
if callback.is_some() {
// assume close_fn
self.clear_completion(cx.editor);
@ -1286,10 +1317,6 @@ 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

@ -1034,7 +1034,12 @@ impl Document {
}
/// Apply a [`Transaction`] to the [`Document`] to change its text.
fn apply_impl(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
fn apply_impl(
&mut self,
transaction: &Transaction,
view_id: ViewId,
emit_lsp_notification: bool,
) -> bool {
use helix_core::Assoc;
let old_doc = self.text().clone();
@ -1130,25 +1135,31 @@ impl Document {
apply_inlay_hint_changes(padding_after_inlay_hints);
}
// emit lsp notification
if let Some(language_server) = self.language_server() {
let notify = language_server.text_document_did_change(
self.versioned_identifier(),
&old_doc,
self.text(),
changes,
);
if emit_lsp_notification {
// emit lsp notification
if let Some(language_server) = self.language_server() {
let notify = language_server.text_document_did_change(
self.versioned_identifier(),
&old_doc,
self.text(),
changes,
);
if let Some(notify) = notify {
tokio::spawn(notify);
if let Some(notify) = notify {
tokio::spawn(notify);
}
}
}
}
success
}
/// Apply a [`Transaction`] to the [`Document`] to change its text.
pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
fn apply_inner(
&mut self,
transaction: &Transaction,
view_id: ViewId,
emit_lsp_notification: bool,
) -> bool {
// store the state just before any changes are made. This allows us to undo to the
// state just before a transaction was applied.
if self.changes.is_empty() && !transaction.changes().is_empty() {
@ -1158,7 +1169,7 @@ impl Document {
});
}
let success = self.apply_impl(transaction, view_id);
let success = self.apply_impl(transaction, view_id, emit_lsp_notification);
if !transaction.changes().is_empty() {
// Compose this transaction with the previous one
@ -1168,12 +1179,23 @@ impl Document {
}
success
}
/// Apply a [`Transaction`] to the [`Document`] to change its text.
pub fn apply(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
self.apply_inner(transaction, view_id, true)
}
/// Apply a [`Transaction`] to the [`Document`] to change its text.
/// without notifying the language servers. This is useful for temporary transactions
/// that must not influence the server.
pub fn apply_temporary(&mut self, transaction: &Transaction, view_id: ViewId) -> bool {
self.apply_inner(transaction, view_id, false)
}
fn undo_redo_impl(&mut self, view: &mut View, undo: bool) -> bool {
let mut history = self.history.take();
let txn = if undo { history.undo() } else { history.redo() };
let success = if let Some(txn) = txn {
self.apply_impl(txn, view.id)
self.apply_impl(txn, view.id, true)
} else {
false
};
@ -1213,7 +1235,7 @@ impl Document {
savepoint
}
pub fn restore(&mut self, view: &mut View, savepoint: &SavePoint) {
pub fn restore(&mut self, view: &mut View, savepoint: &SavePoint, emit_lsp_notification: bool) {
assert_eq!(
savepoint.view, view.id,
"Savepoint must not be used with a different view!"
@ -1228,7 +1250,7 @@ impl Document {
let savepoint_ref = self.savepoints.remove(savepoint_idx);
let mut revert = savepoint.revert.lock();
self.apply(&revert, view.id);
self.apply_inner(&revert, view.id, emit_lsp_notification);
*revert = Transaction::new(self.text()).with_selection(self.selection(view.id).clone());
self.savepoints.push(savepoint_ref)
}
@ -1241,7 +1263,7 @@ impl Document {
};
let mut success = false;
for txn in txns {
if self.apply_impl(&txn, view.id) {
if self.apply_impl(&txn, view.id, true) {
success = true;
}
}

@ -1,7 +1,7 @@
use crate::{
align_view,
clipboard::{get_clipboard_provider, ClipboardProvider},
document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode},
document::{DocumentSavedEventFuture, DocumentSavedEventResult, Mode, SavePoint},
graphics::{CursorKind, Rect},
info::Info,
input::KeyEvent,
@ -906,9 +906,14 @@ enum ThemeAction {
}
#[derive(Debug, Clone)]
pub struct CompleteAction {
pub trigger_offset: usize,
pub changes: Vec<Change>,
pub enum CompleteAction {
Applied {
trigger_offset: usize,
changes: Vec<Change>,
},
/// A savepoint of the currently active completion. The completion
/// MUST be restored before sending any event to the LSP
Selected { savepoint: Arc<SavePoint> },
}
#[derive(Debug, Copy, Clone)]

Loading…
Cancel
Save