aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--book/src/configuration.md1
-rw-r--r--helix-lsp/src/lib.rs45
-rw-r--r--helix-term/src/ui/completion.rs24
-rw-r--r--helix-view/src/editor.rs4
4 files changed, 53 insertions, 21 deletions
diff --git a/book/src/configuration.md b/book/src/configuration.md
index aebf5ff0..4b62ca52 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -50,6 +50,7 @@ signal to the Helix process on Unix operating systems, such as by using the comm
| `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` |
| `idle-timeout` | Time in milliseconds since last keypress before idle timers trigger. Used for autocompletion, set to 0 for instant | `400` |
| `completion-trigger-len` | The min-length of word under cursor to trigger autocompletion | `2` |
+| `completion-replace` | Set to `true` to make completions always replace the entire word and not just the part before the cursor | `false` |
| `auto-info` | Whether to display info boxes | `true` |
| `true-color` | Set to `true` to override automatic detection of terminal truecolor support in the event of a false negative | `false` |
| `rulers` | List of column positions at which to display the rulers. Can be overridden by language specific `rulers` in `languages.toml` file | `[]` |
diff --git a/helix-lsp/src/lib.rs b/helix-lsp/src/lib.rs
index 58e8d83d..1463ccb3 100644
--- a/helix-lsp/src/lib.rs
+++ b/helix-lsp/src/lib.rs
@@ -250,18 +250,27 @@ pub mod util {
/// If the LS did not provide a range for the completion or the range of the
/// primary cursor can not be used for the secondary cursor, this function
/// can be used to find the completion range for a cursor
- fn find_completion_range(text: RopeSlice, cursor: usize) -> (usize, usize) {
+ fn find_completion_range(text: RopeSlice, replace_mode: bool, cursor: usize) -> (usize, usize) {
let start = cursor
- text
.chars_at(cursor)
.reversed()
.take_while(|ch| chars::char_is_word(*ch))
.count();
- (start, cursor)
+ let mut end = cursor;
+ if replace_mode {
+ end += text
+ .chars_at(cursor)
+ .skip(1)
+ .take_while(|ch| chars::char_is_word(*ch))
+ .count();
+ }
+ (start, end)
}
fn completion_range(
text: RopeSlice,
edit_offset: Option<(i128, i128)>,
+ replace_mode: bool,
cursor: usize,
) -> Option<(usize, usize)> {
let res = match edit_offset {
@@ -276,7 +285,7 @@ pub mod util {
}
(start_offset as usize, end_offset as usize)
}
- None => find_completion_range(text, cursor),
+ None => find_completion_range(text, replace_mode, cursor),
};
Some(res)
}
@@ -287,6 +296,7 @@ pub mod util {
doc: &Rope,
selection: &Selection,
edit_offset: Option<(i128, i128)>,
+ replace_mode: bool,
new_text: String,
) -> Transaction {
let replacement: Option<Tendril> = if new_text.is_empty() {
@@ -296,9 +306,13 @@ pub mod util {
};
let text = doc.slice(..);
- let (removed_start, removed_end) =
- completion_range(text, edit_offset, selection.primary().cursor(text))
- .expect("transaction must be valid for primary selection");
+ let (removed_start, removed_end) = completion_range(
+ text,
+ edit_offset,
+ replace_mode,
+ selection.primary().cursor(text),
+ )
+ .expect("transaction must be valid for primary selection");
let removed_text = text.slice(removed_start..removed_end);
let (transaction, mut selection) = Transaction::change_by_selection_ignore_overlapping(
@@ -306,9 +320,9 @@ pub mod util {
selection,
|range| {
let cursor = range.cursor(text);
- completion_range(text, edit_offset, cursor)
+ completion_range(text, edit_offset, replace_mode, cursor)
.filter(|(start, end)| text.slice(start..end) == removed_text)
- .unwrap_or_else(|| find_completion_range(text, cursor))
+ .unwrap_or_else(|| find_completion_range(text, replace_mode, cursor))
},
|_, _| replacement.clone(),
);
@@ -326,6 +340,7 @@ pub mod util {
doc: &Rope,
selection: &Selection,
edit_offset: Option<(i128, i128)>,
+ replace_mode: bool,
snippet: snippet::Snippet,
line_ending: &str,
include_placeholder: bool,
@@ -336,9 +351,13 @@ pub mod util {
let mut off = 0i128;
let mut mapped_doc = doc.clone();
let mut selection_tabstops: SmallVec<[_; 1]> = SmallVec::new();
- let (removed_start, removed_end) =
- completion_range(text, edit_offset, selection.primary().cursor(text))
- .expect("transaction must be valid for primary selection");
+ let (removed_start, removed_end) = completion_range(
+ text,
+ edit_offset,
+ replace_mode,
+ selection.primary().cursor(text),
+ )
+ .expect("transaction must be valid for primary selection");
let removed_text = text.slice(removed_start..removed_end);
let (transaction, selection) = Transaction::change_by_selection_ignore_overlapping(
@@ -346,9 +365,9 @@ pub mod util {
selection,
|range| {
let cursor = range.cursor(text);
- completion_range(text, edit_offset, cursor)
+ completion_range(text, edit_offset, replace_mode, cursor)
.filter(|(start, end)| text.slice(start..end) == removed_text)
- .unwrap_or_else(|| find_completion_range(text, cursor))
+ .unwrap_or_else(|| find_completion_range(text, replace_mode, cursor))
},
|replacement_start, replacement_end| {
let mapped_replacement_start = (replacement_start as i128 + off) as usize;
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 99c33781..6303793b 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -108,6 +108,7 @@ impl Completion {
start_offset: usize,
trigger_offset: usize,
) -> Self {
+ let replace_mode = editor.config().completion_replace;
// Sort completion items according to their preselect status (given by the LSP server)
items.sort_by_key(|item| !item.preselect.unwrap_or(false));
@@ -120,18 +121,23 @@ impl Completion {
offset_encoding: helix_lsp::OffsetEncoding,
trigger_offset: usize,
include_placeholder: bool,
+ replace_mode: bool,
) -> Transaction {
use helix_lsp::snippet;
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 (edit_offset, new_text) = if let Some(edit) = &item.text_edit {
let edit = match edit {
lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
lsp::CompletionTextEdit::InsertAndReplace(item) => {
- // TODO: support using "insert" instead of "replace" via user config
- lsp::TextEdit::new(item.replace, item.new_text.clone())
+ let range = if replace_mode {
+ item.replace
+ } else {
+ item.insert
+ };
+ lsp::TextEdit::new(range, item.new_text.clone())
}
};
@@ -157,7 +163,7 @@ impl Completion {
// document changed (and not just the selection) then we will
// likely delete the wrong text (same if we applied an edit sent by the LS)
debug_assert!(primary_cursor == trigger_offset);
- (None, Some(0), new_text)
+ (None, new_text)
};
if matches!(item.kind, Some(lsp::CompletionItemKind::SNIPPET))
@@ -170,8 +176,8 @@ impl Completion {
Ok(snippet) => util::generate_transaction_from_snippet(
doc.text(),
selection,
- start_offset,
- end_offset,
+ edit_offset,
+ replace_mode,
snippet,
doc.line_ending.as_str(),
include_placeholder,
@@ -190,8 +196,8 @@ impl Completion {
util::generate_transaction_from_completion_edit(
doc.text(),
selection,
- start_offset,
- end_offset,
+ edit_offset,
+ replace_mode,
new_text,
)
}
@@ -224,6 +230,7 @@ impl Completion {
offset_encoding,
trigger_offset,
true,
+ replace_mode,
);
// initialize a savepoint
@@ -245,6 +252,7 @@ impl Completion {
offset_encoding,
trigger_offset,
false,
+ replace_mode,
);
doc.apply(&transaction, view.id);
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index c6541105..1b4664ff 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -251,6 +251,9 @@ pub struct Config {
)]
pub idle_timeout: Duration,
pub completion_trigger_len: u8,
+ /// Whether to instruct the LSP to replace the entire word when applying a completion
+ /// or to only insert new text
+ pub completion_replace: bool,
/// Whether to display infoboxes. Defaults to true.
pub auto_info: bool,
pub file_picker: FilePickerConfig,
@@ -738,6 +741,7 @@ impl Default for Config {
color_modes: false,
soft_wrap: SoftWrap::default(),
text_width: 80,
+ completion_replace: false,
}
}
}