@@ -124,6 +124,8 @@ impl Completion {
124124 ) -> Transaction {
125125 use helix_lsp::snippet;
126126 let selection = doc.selection(view_id);
127+ let text = doc.text().slice(..);
128+ let primary_cursor = selection.primary().cursor(text);
127129
128130 let (start_offset, end_offset, new_text) = if let Some(edit) = &item.text_edit {
129131 let edit = match edit {
@@ -133,8 +135,6 @@ impl Completion {
133135 lsp::TextEdit::new(item.replace, item.new_text.clone())
134136 }
135137 };
136− let text = doc.text().slice(..);
137− let primary_cursor = selection.primary().cursor(text);
138138
139139 let start_offset =
140140 match util::lsp_pos_to_pos(doc.text(), edit.range.start, offset_encoding) {
@@ -149,24 +149,26 @@ impl Completion {
149149
150150 (start_offset, end_offset, edit.new_text)
151151 } else {
152− let new_text = item.insert_text.as_ref().unwrap_or(&item.label);
153− // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
154− // in these cases we need to check for a common prefix and remove it
155− let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
156− let new_text = new_text.trim_start_matches::<&str>(&prefix);
157−
158− // TODO: this needs to be true for the numbers to work out correctly
159− // in the closure below. It's passed in to a callback as this same
160− // formula, but can the value change between the LSP request and
161− // response? If it does, can we recover?
162− debug_assert!(
163− doc.selection(view_id)
164− .primary()
165− .cursor(doc.text().slice(..))
166− == trigger_offset
167− );
168−
169− (0, 0, new_text.into())
152+ let new_text = item
153+ .insert_text
154+ .clone()
155+ .unwrap_or_else(|| item.label.clone());
156+
157+ // check that we are still at the correct savepoint
158+ // we can still generate a transaction regardless but if the
159+ // document changed (and not just the selection) then we will
160+ // likely delete the wrong text (same if we applied an edit sent by the LS)
161+ debug_assert!(primary_cursor == trigger_offset);
162+
163+ // TODO: Respect editor.completion_replace?
164+ // Would require detecting the end of the word boundary for every cursor individually.
165+ // We don't do the same for normal `edits, to be consistent we would have to do it for those too
166+
167+ (
168+ start_offset as i128 - primary_cursor as i128,
169+ trigger_offset as i128 - primary_cursor as i128,
170+ new_text,
171+ )
170172 };
171173
172174 if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))