treat replace/insertmode consistently, default to insert

pull/5/head
Pascal Kuthe 1 year ago committed by Blaž Hrastnik
parent b1f7528090
commit d63e570e0a

@ -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 | `[]` |

@ -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;

@ -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);

@ -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,
}
}
}

Loading…
Cancel
Save