allow LSP insert text to replace non-matching prefixes (#5469)

Most LSPs will complete case-insensitive matches, particularly from
lowercase to uppercase.  In some cases, notably Pyright, this is given
as a simple insert text instead of TextEdit.  When this happens, the
prefix text was left unedited.
This commit is contained in:
Taylor C. Richberger 2023-01-10 12:21:44 -07:00 committed by Blaž Hrastnik
parent ce1fb9e64c
commit 9b4326b18b

View File

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