mirror of
https://github.com/helix-editor/helix
synced 2024-11-10 10:34:45 +01:00
treat replace/insertmode consistently, default to insert
This commit is contained in:
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…
Reference in New Issue
Block a user