Apply completion edits to all cursors (#4496)

Completion edits - either basic `insert_text` strings or structured
`text_edit`s - are assumed by the LSP spec to apply to the current
cursor (or at least the trigger point). We can use the range (if any)
and text given by the Language Server to create a transaction that
changes all ranges in the current selection though, allowing auto-
complete to affect multiple cursors.
pull/5026/head
Michael Davis 2 years ago committed by GitHub
parent 7210c58a51
commit 2077f5e26a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -57,7 +57,7 @@ pub enum OffsetEncoding {
pub mod util { pub mod util {
use super::*; use super::*;
use helix_core::{diagnostic::NumberOrString, Range, Rope, Transaction}; use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction};
/// Converts a diagnostic in the document to [`lsp::Diagnostic`]. /// Converts a diagnostic in the document to [`lsp::Diagnostic`].
/// ///
@ -196,6 +196,42 @@ pub mod util {
Some(Range::new(start, end)) Some(Range::new(start, end))
} }
/// Creates a [Transaction] from the [lsp::TextEdit] in a completion response.
/// The transaction applies the edit to all cursors.
pub fn generate_transaction_from_completion_edit(
doc: &Rope,
selection: &Selection,
edit: lsp::TextEdit,
offset_encoding: OffsetEncoding,
) -> Transaction {
let replacement: Option<Tendril> = if edit.new_text.is_empty() {
None
} else {
Some(edit.new_text.into())
};
let text = doc.slice(..);
let primary_cursor = selection.primary().cursor(text);
let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
Some(start) => start as i128 - primary_cursor as i128,
None => return Transaction::new(doc),
};
let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
Some(end) => end as i128 - primary_cursor as i128,
None => return Transaction::new(doc),
};
Transaction::change_by_selection(doc, selection, |range| {
let cursor = range.cursor(text);
(
(cursor as i128 + start_offset) as usize,
(cursor as i128 + end_offset) as usize,
replacement.clone(),
)
})
}
pub fn generate_transaction_from_edits( pub fn generate_transaction_from_edits(
doc: &Rope, doc: &Rope,
mut edits: Vec<lsp::TextEdit>, mut edits: Vec<lsp::TextEdit>,

@ -1,5 +1,5 @@
use crate::compositor::{Component, Context, Event, EventResult}; use crate::compositor::{Component, Context, Event, EventResult};
use helix_view::{apply_transaction, editor::CompleteAction}; use helix_view::{apply_transaction, editor::CompleteAction, ViewId};
use tui::buffer::Buffer as Surface; use tui::buffer::Buffer as Surface;
use tui::text::Spans; use tui::text::Spans;
@ -107,6 +107,7 @@ impl Completion {
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_id: ViewId,
item: &CompletionItem, item: &CompletionItem,
offset_encoding: helix_lsp::OffsetEncoding, offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize, start_offset: usize,
@ -121,9 +122,10 @@ impl Completion {
} }
}; };
util::generate_transaction_from_edits( util::generate_transaction_from_completion_edit(
doc.text(), doc.text(),
vec![edit], doc.selection(view_id),
edit,
offset_encoding, // TODO: should probably transcode in Client offset_encoding, // TODO: should probably transcode in Client
) )
} else { } else {
@ -132,10 +134,23 @@ impl Completion {
// in these cases we need to check for a common prefix and remove it // in these cases we need to check for a common prefix and remove it
let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset)); let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
let text = text.trim_start_matches::<&str>(&prefix); let text = text.trim_start_matches::<&str>(&prefix);
Transaction::change(
doc.text(), // TODO: this needs to be true for the numbers to work out correctly
vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(), // in the closure below. It's passed in to a callback as this same
) // formula, but can the value change between the LSP request and
// response? If it does, can we recover?
debug_assert!(
doc.selection(view_id)
.primary()
.cursor(doc.text().slice(..))
== trigger_offset
);
Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
let cursor = range.cursor(doc.text().slice(..));
(cursor, cursor, Some(text.into()))
})
}; };
transaction transaction
@ -164,6 +179,7 @@ impl Completion {
let transaction = item_to_transaction( let transaction = item_to_transaction(
doc, doc,
view.id,
item, item,
offset_encoding, offset_encoding,
start_offset, start_offset,
@ -185,6 +201,7 @@ impl Completion {
let transaction = item_to_transaction( let transaction = item_to_transaction(
doc, doc,
view.id,
item, item,
offset_encoding, offset_encoding,
start_offset, start_offset,

Loading…
Cancel
Save