on master help sign in
Handle snippets for LSPs not providing offsets for completion
8c2e447Andrii Grynenko
helix-lsp/src/lib.rs+7−26
@@ -252,26 +252,17 @@ pub mod util {
252 pub fn generate_transaction_from_completion_edit(
252 pub fn generate_transaction_from_completion_edit(
253 doc: &Rope,
253 doc: &Rope,
254 selection: &Selection,
254 selection: &Selection,
255− edit: lsp::TextEdit,
255+ start_offset: i128,
256− offset_encoding: OffsetEncoding,
256+ end_offset: i128,
257+ new_text: String,
257 ) -> Transaction {
258 ) -> Transaction {
258− let replacement: Option<Tendril> = if edit.new_text.is_empty() {
259+ let replacement: Option<Tendril> = if new_text.is_empty() {
259 None
260 None
260 } else {
261 } else {
261− Some(edit.new_text.into())
262+ Some(new_text.into())
262 };
263 };
263
264
264 let text = doc.slice(..);
265 let text = doc.slice(..);
265− let primary_cursor = selection.primary().cursor(text);
266−
267− let start_offset = match lsp_pos_to_pos(doc, edit.range.start, offset_encoding) {
268− Some(start) => start as i128 - primary_cursor as i128,
269− None => return Transaction::new(doc),
270− };
271− let end_offset = match lsp_pos_to_pos(doc, edit.range.end, offset_encoding) {
272− Some(end) => end as i128 - primary_cursor as i128,
273− None => return Transaction::new(doc),
274− };
275
266
276 Transaction::change_by_selection(doc, selection, |range| {
267 Transaction::change_by_selection(doc, selection, |range| {
277 let cursor = range.cursor(text);
268 let cursor = range.cursor(text);
@@ -288,23 +279,13 @@ pub mod util {
288 pub fn generate_transaction_from_snippet(
279 pub fn generate_transaction_from_snippet(
289 doc: &Rope,
280 doc: &Rope,
290 selection: &Selection,
281 selection: &Selection,
291− edit_range: &lsp::Range,
282+ start_offset: i128,
283+ end_offset: i128,
292 snippet: snippet::Snippet,
284 snippet: snippet::Snippet,
293 line_ending: &str,
285 line_ending: &str,
294 include_placeholder: bool,
286 include_placeholder: bool,
295− offset_encoding: OffsetEncoding,
296 ) -> Transaction {
287 ) -> Transaction {
297 let text = doc.slice(..);
288 let text = doc.slice(..);
298− let primary_cursor = selection.primary().cursor(text);
299−
300− let start_offset = match lsp_pos_to_pos(doc, edit_range.start, offset_encoding) {
301− Some(start) => start as i128 - primary_cursor as i128,
302− None => return Transaction::new(doc),
303− };
304− let end_offset = match lsp_pos_to_pos(doc, edit_range.end, offset_encoding) {
305− Some(end) => end as i128 - primary_cursor as i128,
306− None => return Transaction::new(doc),
307− };
308
289
309 // For each cursor store offsets for the first tabstop
290 // For each cursor store offsets for the first tabstop
310 let mut cursor_tabstop_offsets = Vec::<SmallVec<[(i128, i128); 1]>>::new();
291 let mut cursor_tabstop_offsets = Vec::<SmallVec<[(i128, i128); 1]>>::new();
helix-term/src/ui/completion.rs+54−41
@@ -121,8 +121,9 @@ impl Completion {
121 include_placeholder: bool,
121 include_placeholder: bool,
122 ) -> Transaction {
122 ) -> Transaction {
123 use helix_lsp::snippet;
123 use helix_lsp::snippet;
124+ let selection = doc.selection(view_id);
124
125
125− if let Some(edit) = &item.text_edit {
126+ let (start_offset, end_offset, new_text) = if let Some(edit) = &item.text_edit {
126 let edit = match edit {
127 let edit = match edit {
127 lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
128 lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
128 lsp::CompletionTextEdit::InsertAndReplace(item) => {
129 lsp::CompletionTextEdit::InsertAndReplace(item) => {
@@ -130,46 +131,27 @@ impl Completion {
130 lsp::TextEdit::new(item.replace, item.new_text.clone())
131 lsp::TextEdit::new(item.replace, item.new_text.clone())
131 }
132 }
132 };
133 };
133−
134+ let text = doc.text().slice(..);
134− if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))
135+ let primary_cursor = selection.primary().cursor(text);
135− || matches!(
136+
136− item.insert_text_format,
137+ let start_offset =
137− Some(lsp::InsertTextFormat::SNIPPET)
138− )
139− {
140− match snippet::parse(&edit.new_text) {
141− Ok(snippet) => util::generate_transaction_from_snippet(
142− doc.text(),
143− doc.selection(view_id),
144− &edit.range,
145− snippet,
146− doc.line_ending.as_str(),
147− include_placeholder,
148− offset_encoding,
138+ match util::lsp_pos_to_pos(doc.text(), edit.range.start, offset_encoding) {
149− ),
139+ Some(start) => start as i128 - primary_cursor as i128,
150− Err(err) => {
151− log::error!(
152− "Failed to parse snippet: {:?}, remaining output: {}",
153− &edit.new_text,
154− err
155− );
156− Transaction::new(doc.text())
140+ None => return Transaction::new(doc.text()),
157− }
158− }
141+ };
159− } else {
142+ let end_offset =
160− util::generate_transaction_from_completion_edit(
143+ match util::lsp_pos_to_pos(doc.text(), edit.range.end, offset_encoding) {
161− doc.text(),
144+ Some(end) => end as i128 - primary_cursor as i128,
162− doc.selection(view_id),
145+ None => return Transaction::new(doc.text()),
163− edit,
164− offset_encoding, // TODO: should probably transcode in Client
165− )
166− }
146+ };
147+
148+ (start_offset, end_offset, edit.new_text)
167 } else {
149 } else {
168− let text = item.insert_text.as_ref().unwrap_or(&item.label);
150+ let new_text = item.insert_text.as_ref().unwrap_or(&item.label);
169 // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
151 // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
170 // in these cases we need to check for a common prefix and remove it
152 // in these cases we need to check for a common prefix and remove it
171 let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
153 let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
172− let text = text.trim_start_matches::<&str>(&prefix);
154+ let new_text = new_text.trim_start_matches::<&str>(&prefix);
173
155
174 // TODO: this needs to be true for the numbers to work out correctly
156 // TODO: this needs to be true for the numbers to work out correctly
175 // in the closure below. It's passed in to a callback as this same
157 // in the closure below. It's passed in to a callback as this same
@@ -182,11 +164,42 @@ impl Completion {
182 == trigger_offset
164 == trigger_offset
183 );
165 );
184
166
185− Transaction::change_by_selection(doc.text(), doc.selection(view_id), |range| {
167+ (0, 0, new_text.into())
186− let cursor = range.cursor(doc.text().slice(..));
168+ };
187
169
188− (cursor, cursor, Some(text.into()))
170+ if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))
171+ || matches!(
172+ item.insert_text_format,
173+ Some(lsp::InsertTextFormat::SNIPPET)
174+ )
175+ {
176+ match snippet::parse(&new_text) {
177+ Ok(snippet) => util::generate_transaction_from_snippet(
178+ doc.text(),
179+ selection,
180+ start_offset,
181+ end_offset,
182+ snippet,
183+ doc.line_ending.as_str(),
184+ include_placeholder,
185+ ),
186+ Err(err) => {
187+ log::error!(
188+ "Failed to parse snippet: {:?}, remaining output: {}",
189+ &new_text,
190+ err
191+ );
192+ Transaction::new(doc.text())
193+ }
189− })
194+ }
195+ } else {
196+ util::generate_transaction_from_completion_edit(
197+ doc.text(),
198+ selection,
199+ start_offset,
200+ end_offset,
201+ new_text,
202+ )
190 }
203 }
191 }
204 }
192
205