on master help sign in
Apply completion edits to all cursors (#4496)
2077f5eMichael Davis
helix-lsp/src/lib.rs+37−1
@@ -57,7 +57,7 @@ pub enum OffsetEncoding {
57
57
58pub mod util {
58pub mod util {
59 use super::*;
59 use super::*;
60− use helix_core::{diagnostic::NumberOrString, Range, Rope, Transaction};
60+ use helix_core::{diagnostic::NumberOrString, Range, Rope, Selection, Tendril, Transaction};
61
61
62 /// Converts a diagnostic in the document to [`lsp::Diagnostic`].
62 /// Converts a diagnostic in the document to [`lsp::Diagnostic`].
63 ///
63 ///
@@ -196,6 +196,42 @@ pub mod util {
196 Some(Range::new(start, end))
196 Some(Range::new(start, end))
197 }
197 }
198
198
199+ /// Creates a [Transaction] from the [lsp::TextEdit] in a completion response.
200+ /// The transaction applies the edit to all cursors.
201+ pub fn generate_transaction_from_completion_edit(
202+ doc: &Rope,
203+ selection: &Selection,
204+ edit: lsp::TextEdit,
205+ offset_encoding: OffsetEncoding,
206+ ) -> Transaction {
207+ let replacement: Option<Tendril> = if edit.new_text.is_empty() {
208+ None
209+ } else {
210+ Some(edit.new_text.into())
211+ };
212+
213+ let text = doc.slice(..);
214+ let primary_cursor = selection.primary().cursor(text);
215+
216+ let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
217+ Some(start) => start as i128 - primary_cursor as i128,
218+ None => return Transaction::new(doc),
219+ };
220+ let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
221+ Some(end) => end as i128 - primary_cursor as i128,
222+ None => return Transaction::new(doc),
223+ };
224+
225+ Transaction::change_by_selection(doc, selection, |range| {
226+ let cursor = range.cursor(text);
227+ (
228+ (cursor as i128 + start_offset) as usize,
229+ (cursor as i128 + end_offset) as usize,
230+ replacement.clone(),
231+ )
232+ })
233+ }
234+
199 pub fn generate_transaction_from_edits(
235 pub fn generate_transaction_from_edits(
200 doc: &Rope,
236 doc: &Rope,
201 mut edits: Vec<lsp::TextEdit>,
237 mut edits: Vec<lsp::TextEdit>,
helix-term/src/ui/completion.rs+24−7
@@ -1,5 +1,5 @@
1use crate::compositor::{Component, Context, Event, EventResult};
1use crate::compositor::{Component, Context, Event, EventResult};
2−use helix_view::{apply_transaction, editor::CompleteAction};
2+use helix_view::{apply_transaction, editor::CompleteAction, ViewId};
3use tui::buffer::Buffer as Surface;
3use tui::buffer::Buffer as Surface;
4use tui::text::Spans;
4use tui::text::Spans;
5
5
@@ -107,6 +107,7 @@ impl Completion {
107 let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| {
107 let menu = Menu::new(items, (), move |editor: &mut Editor, item, event| {
108 fn item_to_transaction(
108 fn item_to_transaction(
109 doc: &Document,
109 doc: &Document,
110+ view_id: ViewId,
110 item: &CompletionItem,
111 item: &CompletionItem,
111 offset_encoding: helix_lsp::OffsetEncoding,
112 offset_encoding: helix_lsp::OffsetEncoding,
112 start_offset: usize,
113 start_offset: usize,
@@ -121,9 +122,10 @@ impl Completion {
121 }
122 }
122 };
123 };
123
124
124− util::generate_transaction_from_edits(
125+ util::generate_transaction_from_completion_edit(
125 doc.text(),
126 doc.text(),
127+ doc.selection(view_id),
126− vec![edit],
128+ edit,
127 offset_encoding, // TODO: should probably transcode in Client
129 offset_encoding, // TODO: should probably transcode in Client
128 )
130 )
129 } else {
131 } else {
@@ -132,10 +134,23 @@ impl Completion {
132 // in these cases we need to check for a common prefix and remove it
134 // in these cases we need to check for a common prefix and remove it
133 let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
135 let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
134 let text = text.trim_start_matches::<&str>(&prefix);
136 let text = text.trim_start_matches::<&str>(&prefix);
135− Transaction::change(
137+
136− doc.text(),
138+ // TODO: this needs to be true for the numbers to work out correctly
139+ // in the closure below. It's passed in to a callback as this same
140+ // formula, but can the value change between the LSP request and
141+ // response? If it does, can we recover?
142+ debug_assert!(
143+ doc.selection(view_id)
144+ .primary()
145+ .cursor(doc.text().slice(..))
137− vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
146+ == trigger_offset
147+ );
148+
149+ Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
150+ let cursor = range.cursor(doc.text().slice(..));
151+
152+ (cursor, cursor, Some(text.into()))
138− )
153+ })
139 };
154 };
140
155
141 transaction
156 transaction
@@ -164,6 +179,7 @@ impl Completion {
164
179
165 let transaction = item_to_transaction(
180 let transaction = item_to_transaction(
166 doc,
181 doc,
182+ view.id,
167 item,
183 item,
168 offset_encoding,
184 offset_encoding,
169 start_offset,
185 start_offset,
@@ -185,6 +201,7 @@ impl Completion {
185
201
186 let transaction = item_to_transaction(
202 let transaction = item_to_transaction(
187 doc,
203 doc,
204+ view.id,
188 item,
205 item,
189 offset_encoding,
206 offset_encoding,
190 start_offset,
207 start_offset,