completion: fully revert state before apply & insertText common prefix

pull/906/head
Blaž Hrastnik 3 years ago
parent bfb6cff5a9
commit 3edca7854e

@ -468,6 +468,13 @@ impl Transaction {
} }
} }
pub fn compose(mut self, other: Self) -> Self {
self.changes = self.changes.compose(other.changes);
// Other selection takes precedence
self.selection = other.selection;
self
}
pub fn with_selection(mut self, selection: Selection) -> Self { pub fn with_selection(mut self, selection: Selection) -> Self {
self.selection = Some(selection); self.selection = Some(selection);
self self

@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface;
use std::borrow::Cow; use std::borrow::Cow;
use helix_core::Transaction; use helix_core::Transaction;
use helix_view::{graphics::Rect, Document, Editor, View}; use helix_view::{graphics::Rect, Document, Editor};
use crate::commands; use crate::commands;
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent}; use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
@ -83,13 +83,13 @@ impl Completion {
start_offset: usize, start_offset: usize,
trigger_offset: usize, trigger_offset: usize,
) -> Self { ) -> Self {
// let items: Vec<CompletionItem> = Vec::new();
let menu = Menu::new(items, move |editor: &mut Editor, item, event| { let menu = Menu::new(items, move |editor: &mut Editor, item, event| {
fn item_to_transaction( fn item_to_transaction(
doc: &Document, doc: &Document,
view: &View,
item: &CompletionItem, item: &CompletionItem,
offset_encoding: helix_lsp::OffsetEncoding, offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize,
trigger_offset: usize,
) -> Transaction { ) -> Transaction {
if let Some(edit) = &item.text_edit { if let Some(edit) = &item.text_edit {
let edit = match edit { let edit = match edit {
@ -105,63 +105,52 @@ impl Completion {
) )
} else { } else {
let text = item.insert_text.as_ref().unwrap_or(&item.label); let text = item.insert_text.as_ref().unwrap_or(&item.label);
let cursor = doc // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
.selection(view.id) // in these cases we need to check for a common prefix and remove it
.primary() let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
.cursor(doc.text().slice(..)); let text = text.trim_start_matches::<&str>(&prefix);
Transaction::change( Transaction::change(
doc.text(), doc.text(),
vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(), vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
) )
} }
} }
let (view, doc) = current!(editor);
// if more text was entered, remove it
doc.restore(view.id);
match event { match event {
PromptEvent::Abort => {} PromptEvent::Abort => {}
PromptEvent::Update => { PromptEvent::Update => {
let (view, doc) = current!(editor);
// always present here // always present here
let item = item.unwrap(); let item = item.unwrap();
// if more text was entered, remove it let transaction = item_to_transaction(
// TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes doc,
let cursor = doc item,
.selection(view.id) offset_encoding,
.primary() start_offset,
.cursor(doc.text().slice(..)); trigger_offset,
if trigger_offset < cursor { );
let remove = Transaction::change(
doc.text(), // initialize a savepoint
vec![(trigger_offset, cursor, None)].into_iter(), doc.savepoint();
);
doc.apply(&remove, view.id);
}
let transaction = item_to_transaction(doc, view, item, offset_encoding);
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
} }
PromptEvent::Validate => { PromptEvent::Validate => {
let (view, doc) = current!(editor);
// always present here // always present here
let item = item.unwrap(); let item = item.unwrap();
// if more text was entered, remove it let transaction = item_to_transaction(
// TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes doc,
let cursor = doc item,
.selection(view.id) offset_encoding,
.primary() start_offset,
.cursor(doc.text().slice(..)); trigger_offset,
if trigger_offset < cursor { );
let remove = Transaction::change(
doc.text(),
vec![(trigger_offset, cursor, None)].into_iter(),
);
doc.apply(&remove, view.id);
}
let transaction = item_to_transaction(doc, view, item, offset_encoding);
doc.apply(&transaction, view.id); doc.apply(&transaction, view.id);
if let Some(additional_edits) = &item.additional_text_edits { if let Some(additional_edits) = &item.additional_text_edits {

@ -13,7 +13,7 @@ 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, document::Mode,
@ -721,7 +721,7 @@ impl EditorView {
pub fn set_completion( pub fn set_completion(
&mut self, &mut self,
editor: &Editor, editor: &mut Editor,
items: Vec<helix_lsp::lsp::CompletionItem>, items: Vec<helix_lsp::lsp::CompletionItem>,
offset_encoding: helix_lsp::OffsetEncoding, offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize, start_offset: usize,
@ -736,6 +736,9 @@ impl EditorView {
return; return;
} }
// Immediately initialize a savepoint
doc_mut!(editor).savepoint();
// 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);
@ -945,6 +948,9 @@ impl Component for EditorView {
if callback.is_some() { if callback.is_some() {
// assume close_fn // assume close_fn
self.completion = None; self.completion = None;
// Clear any savepoints
let (_, doc) = current!(cxt.editor);
doc.savepoint = None;
cxt.editor.clear_idle_timer(); // don't retrigger cxt.editor.clear_idle_timer(); // don't retrigger
} }
} }
@ -959,6 +965,9 @@ impl Component for EditorView {
completion.update(&mut cxt); completion.update(&mut cxt);
if completion.is_empty() { if completion.is_empty() {
self.completion = None; self.completion = None;
// Clear any savepoints
let (_, doc) = current!(cxt.editor);
doc.savepoint = None;
cxt.editor.clear_idle_timer(); // don't retrigger cxt.editor.clear_idle_timer(); // don't retrigger
} }
} }

@ -97,6 +97,9 @@ pub struct Document {
// it back as it separated from the edits. We could split out the parts manually but that will // it back as it separated from the edits. We could split out the parts manually but that will
// be more troublesome. // be more troublesome.
history: Cell<History>, history: Cell<History>,
pub savepoint: Option<Transaction>,
last_saved_revision: usize, last_saved_revision: usize,
version: i32, // should be usize? version: i32, // should be usize?
@ -328,6 +331,7 @@ impl Document {
text, text,
selections: HashMap::default(), selections: HashMap::default(),
indent_style: DEFAULT_INDENT, indent_style: DEFAULT_INDENT,
line_ending: DEFAULT_LINE_ENDING,
mode: Mode::Normal, mode: Mode::Normal,
restore_cursor: false, restore_cursor: false,
syntax: None, syntax: None,
@ -337,9 +341,9 @@ impl Document {
diagnostics: Vec::new(), diagnostics: Vec::new(),
version: 0, version: 0,
history: Cell::new(History::default()), history: Cell::new(History::default()),
savepoint: None,
last_saved_revision: 0, last_saved_revision: 0,
language_server: None, language_server: None,
line_ending: DEFAULT_LINE_ENDING,
} }
} }
@ -635,6 +639,14 @@ impl Document {
if !transaction.changes().is_empty() { if !transaction.changes().is_empty() {
self.version += 1; self.version += 1;
// generate revert to savepoint
if self.savepoint.is_some() {
take_with(&mut self.savepoint, |prev_revert| {
let revert = transaction.invert(&old_doc);
Some(revert.compose(prev_revert.unwrap()))
});
}
// update tree-sitter syntax tree // update tree-sitter syntax tree
if let Some(syntax) = &mut self.syntax { if let Some(syntax) = &mut self.syntax {
// TODO: no unwrap // TODO: no unwrap
@ -724,6 +736,16 @@ impl Document {
} }
} }
pub fn savepoint(&mut self) {
self.savepoint = Some(Transaction::new(self.text()));
}
pub fn restore(&mut self, view_id: ViewId) {
if let Some(revert) = self.savepoint.take() {
self.apply(&revert, view_id);
}
}
/// Undo modifications to the [`Document`] according to `uk`. /// Undo modifications to the [`Document`] according to `uk`.
pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) { pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) {
let txns = self.history.get_mut().earlier(uk); let txns = self.history.get_mut().earlier(uk);

Loading…
Cancel
Save