on master help sign in
treat replace/insertmode consistently, default to insert
commitd63e570e0a4013f5ad703a9b1ce2d19a06630a82 authorPascal Kuthe <pascal.kuthe@semimod.de> dateThu 9 Mar 2023, 23:21 (4 years ago) parentb1f7528
4 files changed+53−21
book/src/configuration.md+1 helix-lsp/src/lib.rs+32−13 helix-term/src/ui/completion.rs+16−8 helix-view/src/editor.rs+4
book/src/configuration.md+1
@@ -50,6 +50,7 @@ signal to the Helix process on Unix operating systems, such as by using the comm
5050| `auto-save` | Enable automatic saving on the focus moving away from Helix. Requires [focus event support](https://github.com/helix-editor/helix/wiki/Terminal-Support) from your terminal | `false` |
5151| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `400` |
5252| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
53+| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
5354| `auto-info` | Whether to display info boxes | `true` |
5455| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` |
5556| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
helix-lsp/src/lib.rs+32−13
@@ -250,18 +250,27 @@ pub mod util {
250250 /// If the LS did not provide a range for the completion or the range of the
251251 /// primary cursor can not be used for the secondary cursor, this function
252252 /// can be used to find the completion range for a cursor
253− fn find_completion_range(text: RopeSlice, cursor: usize) -> (usize, usize) {
253+ fn find_completion_range(text: RopeSlice, replace_mode: bool, cursor: usize) -> (usize, usize) {
254254 let start = cursor
255255 - text
256256 .chars_at(cursor)
257257 .reversed()
258258 .take_while(|ch| chars::char_is_word(*ch))
259259 .count();
260− (start, cursor)
260+ let mut end = cursor;
261+ if replace_mode {
262+ end += text
263+ .chars_at(cursor)
264+ .skip(1)
265+ .take_while(|ch| chars::char_is_word(*ch))
266+ .count();
267+ }
268+ (start, end)
261269 }
262270 fn completion_range(
263271 text: RopeSlice,
264272 edit_offset: Option<(i128, i128)>,
273+ replace_mode: bool,
265274 cursor: usize,
266275 ) -> Option<(usize, usize)> {
267276 let res = match edit_offset {
@@ -276,7 +285,7 @@ pub mod util {
276285 }
277286 (start_offset as usize, end_offset as usize)
278287 }
279− None => find_completion_range(text, cursor),
288+ None => find_completion_range(text, replace_mode, cursor),
280289 };
281290 Some(res)
282291 }
@@ -287,6 +296,7 @@ pub mod util {
287296 doc: &Rope,
288297 selection: &Selection,
289298 edit_offset: Option<(i128, i128)>,
299+ replace_mode: bool,
290300 new_text: String,
291301 ) -> Transaction {
292302 let replacement: Option<Tendril> = if new_text.is_empty() {
@@ -296,9 +306,13 @@ pub mod util {
296306 };
297307
298308 let text = doc.slice(..);
299− let (removed_start, removed_end) =
300− completion_range(text, edit_offset, selection.primary().cursor(text))
301− .expect("transaction must be valid for primary selection");
309+ let (removed_start, removed_end) = completion_range(
310+ text,
311+ edit_offset,
312+ replace_mode,
313+ selection.primary().cursor(text),
314+ )
315+ .expect("transaction must be valid for primary selection");
302316 let removed_text = text.slice(removed_start..removed_end);
303317
304318 let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
@@ -306,9 +320,9 @@ pub mod util {
306320 selection,
307321 |range| {
308322 let cursor = range.cursor(text);
309− completion_range(text, edit_offset, cursor)
323+ completion_range(text, edit_offset, replace_mode, cursor)
310324 .filter(|(start, end)| text.slice(start..end) == removed_text)
311− .unwrap_or_else(|| find_completion_range(text, cursor))
325+ .unwrap_or_else(|| find_completion_range(text, replace_mode, cursor))
312326 },
313327 |_, _| replacement.clone(),
314328 );
@@ -326,6 +340,7 @@ pub mod util {
326340 doc: &Rope,
327341 selection: &Selection,
328342 edit_offset: Option<(i128, i128)>,
343+ replace_mode: bool,
329344 snippet: snippet::Snippet,
330345 line_ending: &str,
331346 include_placeholder: bool,
@@ -336,9 +351,13 @@ pub mod util {
336351 let mut off = 0i128;
337352 let mut mapped_doc = doc.clone();
338353 let mut selection_tabstops: SmallVec<[_; 1]> = SmallVec::new();
339− let (removed_start, removed_end) =
340− completion_range(text, edit_offset, selection.primary().cursor(text))
341− .expect("transaction must be valid for primary selection");
354+ let (removed_start, removed_end) = completion_range(
355+ text,
356+ edit_offset,
357+ replace_mode,
358+ selection.primary().cursor(text),
359+ )
360+ .expect("transaction must be valid for primary selection");
342361 let removed_text = text.slice(removed_start..removed_end);
343362
344363 let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
@@ -346,9 +365,9 @@ pub mod util {
346365 selection,
347366 |range| {
348367 let cursor = range.cursor(text);
349− completion_range(text, edit_offset, cursor)
368+ completion_range(text, edit_offset, replace_mode, cursor)
350369 .filter(|(start, end)| text.slice(start..end) == removed_text)
351− .unwrap_or_else(|| find_completion_range(text, cursor))
370+ .unwrap_or_else(|| find_completion_range(text, replace_mode, cursor))
352371 },
353372 |replacement_start, replacement_end| {
354373 let mapped_replacement_start = (replacement_start as i128 + off) as usize;
helix-term/src/ui/completion.rs+16−8
@@ -108,6 +108,7 @@ impl Completion {
108108 start_offset: usize,
109109 trigger_offset: usize,
110110 ) -> Self {
111+ let replace_mode = editor.config().completion_replace;
111112 // Sort completion items according to their preselect status (given by the LSP server)
112113 items.sort_by_key(|item| !item.preselect.unwrap_or(false));
113114
@@ -120,18 +121,23 @@ impl Completion {
120121 offset_encoding: helix_lsp::OffsetEncoding,
121122 trigger_offset: usize,
122123 include_placeholder: bool,
124+ replace_mode: bool,
123125 ) -> Transaction {
124126 use helix_lsp::snippet;
125127 let selection = doc.selection(view_id);
126128 let text = doc.text().slice(..);
127129 let primary_cursor = selection.primary().cursor(text);
128130
129− let (start_offset, end_offset, new_text) = if let Some(edit) = &item.text_edit {
131+ let (edit_offset, new_text) = if let Some(edit) = &item.text_edit {
130132 let edit = match edit {
131133 lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
132134 lsp::CompletionTextEdit::InsertAndReplace(item) => {
133− // TODO: support using "insert" instead of "replace" via user config
134− lsp::TextEdit::new(item.replace, item.new_text.clone())
135+ let range = if replace_mode {
136+ item.replace
137+ } else {
138+ item.insert
139+ };
140+ lsp::TextEdit::new(range, item.new_text.clone())
135141 }
136142 };
137143
@@ -157,7 +163,7 @@ impl Completion {
157163 // document changed (and not just the selection) then we will
158164 // likely delete the wrong text (same if we applied an edit sent by the LS)
159165 debug_assert!(primary_cursor == trigger_offset);
160− (None, Some(0), new_text)
166+ (None, new_text)
161167 };
162168
163169 if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))
@@ -170,8 +176,8 @@ impl Completion {
170176 Ok(snippet) => util::generate_transaction_from_snippet(
171177 doc.text(),
172178 selection,
173− start_offset,
174− end_offset,
179+ edit_offset,
180+ replace_mode,
175181 snippet,
176182 doc.line_ending.as_str(),
177183 include_placeholder,
@@ -190,8 +196,8 @@ impl Completion {
190196 util::generate_transaction_from_completion_edit(
191197 doc.text(),
192198 selection,
193− start_offset,
194− end_offset,
199+ edit_offset,
200+ replace_mode,
195201 new_text,
196202 )
197203 }
@@ -224,6 +230,7 @@ impl Completion {
224230 offset_encoding,
225231 trigger_offset,
226232 true,
233+ replace_mode,
227234 );
228235
229236 // initialize a savepoint
@@ -245,6 +252,7 @@ impl Completion {
245252 offset_encoding,
246253 trigger_offset,
247254 false,
255+ replace_mode,
248256 );
249257
250258 doc.apply(&transaction, view.id);
helix-view/src/editor.rs+4
@@ -251,6 +251,9 @@ pub struct Config {
251251 )]
252252 pub idle_timeout: Duration,
253253 pub completion_trigger_len: u8,
254+ /// Whether to instruct the LSP to replace the entire word when applying a completion
255+ /// or to only insert new text
256+ pub completion_replace: bool,
254257 /// Whether to display infoboxes. Defaults to true.
255258 pub auto_info: bool,
256259 pub file_picker: FilePickerConfig,
@@ -738,6 +741,7 @@ impl Default for Config {
738741 color_modes: false,
739742 soft_wrap: SoftWrap::default(),
740743 text_width: 80,
744+ completion_replace: false,
741745 }
742746 }
743747}