Skip to content

Commit

Permalink
make replace completions optional, fix issues with multicursors
Browse files Browse the repository at this point in the history
  • Loading branch information
pascalkuthe committed Jan 30, 2023
1 parent 9e567e3 commit 71a341a
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 14 deletions.
49 changes: 49 additions & 0 deletions helix-core/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,42 @@ impl Transaction {
self
}

/// Generate a transaction from a set of potentially overallping changes.
/// Changes that overlap are ignored
pub fn change_ignore_overlapping<I>(doc: &Rope, changes: I) -> Self
where
I: Iterator<Item = Change>,
{
let len = doc.len_chars();

let (lower, upper) = changes.size_hint();
let size = upper.unwrap_or(lower);
let mut changeset = ChangeSet::with_capacity(2 * size + 1); // rough estimate

let mut last = 0;
for (from, to, tendril) in changes {
if last > from {
continue;
}

// Retain from last "to" to current "from"
changeset.retain(from - last);
let span = to - from;
match tendril {
Some(text) => {
changeset.insert(text);
changeset.delete(span);
}
None => changeset.delete(span),
}
last = to;
}

changeset.retain(len - last);

Self::from(changeset)
}

/// Generate a transaction from a set of changes.
pub fn change<I>(doc: &Rope, changes: I) -> Self
where
Expand Down Expand Up @@ -508,6 +544,19 @@ impl Transaction {
Self::change(doc, selection.iter().map(f))
}

/// Generate a transaction with a change per selection range.
/// Overlapping changes are ignored
pub fn change_by_selection_ignore_overlapping<F>(
doc: &Rope,
selection: &Selection,
f: F,
) -> Self
where
F: FnMut(&Range) -> Change,
{
Self::change_ignore_overlapping(doc, selection.iter().map(f))
}

/// Insert text at each selection head.
pub fn insert(doc: &Rope, selection: &Selection, text: Tendril) -> Self {
Self::change_by_selection(doc, selection, |range| {
Expand Down
2 changes: 1 addition & 1 deletion helix-lsp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ pub mod util {
None => return Transaction::new(doc),
};

Transaction::change_by_selection(doc, selection, |range| {
Transaction::change_by_selection_ignore_overlapping(doc, selection, |range| {
let cursor = range.cursor(text);
(
(cursor as i128 + start_offset) as usize,
Expand Down
53 changes: 40 additions & 13 deletions helix-term/src/ui/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use tui::buffer::Buffer as Surface;

use std::borrow::Cow;

use helix_core::{Change, Transaction};
use helix_core::{chars, Change, Transaction};
use helix_view::{
graphics::Rect,
input::{KeyCode, KeyEvent},
Expand Down Expand Up @@ -95,6 +95,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));

Expand All @@ -107,13 +108,19 @@ impl Completion {
offset_encoding: helix_lsp::OffsetEncoding,
start_offset: usize,
trigger_offset: usize,
replace_mode: bool,
) -> Transaction {
let transaction = if let Some(edit) = &item.text_edit {
let edit = match edit {
lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
lsp::CompletionTextEdit::InsertAndReplace(item) => {
let range = if replace_mode {
item.replace
} else {
item.insert
};
// TODO: support using "insert" instead of "replace" via user config
lsp::TextEdit::new(item.replace, item.new_text.clone())
lsp::TextEdit::new(range, item.new_text.clone())
}
};

Expand All @@ -125,21 +132,39 @@ impl Completion {
)
} else {
let text = item.insert_text.as_ref().unwrap_or(&item.label);
let doc_text = doc.text().slice(..);
let primary_cursor = doc.selection(view_id).primary().cursor(doc_text);

// TODO: this needs to be true for the numbers to work out correctly
// in the closure below. It's passed in to a callback as this same
// TODO: this needs to be true for the closure below to work out
// It's passed in to a callback as this same
// formula, but can the value change between the LSP request and
// response? If it does, can we recover?
debug_assert!(
doc.selection(view_id)
.primary()
.cursor(doc.text().slice(..))
== trigger_offset
);
debug_assert!(primary_cursor == trigger_offset);
let start_offset = primary_cursor - start_offset;

Transaction::change_by_selection(doc.text(), doc.selection(view_id), |_| {
(start_offset, trigger_offset, Some(text.into()))
})
Transaction::change_by_selection_ignore_overlapping(
doc.text(),
doc.selection(view_id),
|range| {
let cursor = range.cursor(doc_text);
let end_offset = if replace_mode {
// in replace mode replace the rest of the word
doc_text
.chars_at(primary_cursor)
.take_while(|ch| chars::char_is_word(*ch))
.count()
} else {
// otherwise only replace up to the current edit
0
};

(
cursor - start_offset,
cursor + end_offset,
Some(text.into()),
)
},
)
};

transaction
Expand Down Expand Up @@ -173,6 +198,7 @@ impl Completion {
offset_encoding,
start_offset,
trigger_offset,
replace_mode,
);

// initialize a savepoint
Expand All @@ -195,6 +221,7 @@ impl Completion {
offset_encoding,
start_offset,
trigger_offset,
replace_mode,
);

doc.apply(&transaction, view.id);
Expand Down
4 changes: 4 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,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,
Expand Down Expand Up @@ -717,6 +720,7 @@ impl Default for Config {
bufferline: BufferLine::default(),
indent_guides: IndentGuidesConfig::default(),
color_modes: false,
completion_replace: false,
}
}
}
Expand Down

0 comments on commit 71a341a

Please sign in to comment.