@@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface;
55use std::borrow::Cow;
66
77use helix_core::Transaction;
8−use helix_view::{graphics::Rect, Document, Editor, View};
8+use helix_view::{graphics::Rect, Document, Editor};
99
1010use crate::commands;
1111use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
@@ -83,13 +83,13 @@ impl Completion {
8383 start_offset: usize,
8484 trigger_offset: usize,
8585 ) -> Self {
86− // let items: Vec<CompletionItem> = Vec::new();
8786 let menu = Menu::new(items, move |editor: &mut Editor, item, event| {
8887 fn item_to_transaction(
8988 doc: &Document,
90− view: &View,
9189 item: &CompletionItem,
9290 offset_encoding: helix_lsp::OffsetEncoding,
91+ start_offset: usize,
92+ trigger_offset: usize,
9393 ) -> Transaction {
9494 if let Some(edit) = &item.text_edit {
9595 let edit = match edit {
@@ -105,63 +105,52 @@ impl Completion {
105105 )
106106 } else {
107107 let text = item.insert_text.as_ref().unwrap_or(&item.label);
108− let cursor = doc
109− .selection(view.id)
110− .primary()
111− .cursor(doc.text().slice(..));
108+ // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
109+ // in these cases we need to check for a common prefix and remove it
110+ let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
111+ let text = text.trim_start_matches::<&str>(&prefix);
112112 Transaction::change(
113113 doc.text(),
114− vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(),
114+ vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
115115 )
116116 }
117117 }
118118
119+ let (view, doc) = current!(editor);
120+
121+ // if more text was entered, remove it
122+ doc.restore(view.id);
123+
119124 match event {
120125 PromptEvent::Abort => {}
121126 PromptEvent::Update => {
122− let (view, doc) = current!(editor);
123−
124127 // always present here
125128 let item = item.unwrap();
126129
127− // if more text was entered, remove it
128− // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes
129− let cursor = doc
130− .selection(view.id)
131− .primary()
132− .cursor(doc.text().slice(..));
133− if trigger_offset < cursor {
134− let remove = Transaction::change(
135− doc.text(),
136− vec![(trigger_offset, cursor, None)].into_iter(),
137− );
138− doc.apply(&remove, view.id);
139− }
130+ let transaction = item_to_transaction(
131+ doc,
132+ item,
133+ offset_encoding,
134+ start_offset,
135+ trigger_offset,
136+ );
137+
138+ // initialize a savepoint
139+ doc.savepoint();
140140
141− let transaction = item_to_transaction(doc, view, item, offset_encoding);
142141 doc.apply(&transaction, view.id);
143142 }
144143 PromptEvent::Validate => {
145− let (view, doc) = current!(editor);
146−
147144 // always present here
148145 let item = item.unwrap();
149146
150− // if more text was entered, remove it
151− // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes
152− let cursor = doc
153− .selection(view.id)
154− .primary()
155− .cursor(doc.text().slice(..));
156− if trigger_offset < cursor {
157− let remove = Transaction::change(
158− doc.text(),
159− vec![(trigger_offset, cursor, None)].into_iter(),
160− );
161− doc.apply(&remove, view.id);
162− }
163−
164− let transaction = item_to_transaction(doc, view, item, offset_encoding);
147+ let transaction = item_to_transaction(
148+ doc,
149+ item,
150+ offset_encoding,
151+ start_offset,
152+ trigger_offset,
153+ );
165154 doc.apply(&transaction, view.id);
166155
167156 if let Some(additional_edits) = &item.additional_text_edits {