mirror of https://github.com/helix-editor/helix
Make repeat operator work with completion edits (#1640)
* add basic completion replay * use transaction as the last completion * completion replay only on trigger position * cache changes in CompletionAction Co-authored-by: Blaž Hrastnik <blaz@mxxn.io>pull/1726/head
parent
e83cdf3fd3
commit
14e2ced440
|
@ -1,10 +1,11 @@
|
||||||
use crate::compositor::{Component, Context, EventResult};
|
use crate::compositor::{Component, Context, EventResult};
|
||||||
use crossterm::event::{Event, KeyCode, KeyEvent};
|
use crossterm::event::{Event, KeyCode, KeyEvent};
|
||||||
|
use helix_view::editor::CompleteAction;
|
||||||
use tui::buffer::Buffer as Surface;
|
use tui::buffer::Buffer as Surface;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use helix_core::Transaction;
|
use helix_core::{Change, Transaction};
|
||||||
use helix_view::{graphics::Rect, Document, Editor};
|
use helix_view::{graphics::Rect, Document, Editor};
|
||||||
|
|
||||||
use crate::commands;
|
use crate::commands;
|
||||||
|
@ -92,13 +93,14 @@ impl Completion {
|
||||||
start_offset: usize,
|
start_offset: usize,
|
||||||
trigger_offset: usize,
|
trigger_offset: usize,
|
||||||
) -> Transaction {
|
) -> Transaction {
|
||||||
if let Some(edit) = &item.text_edit {
|
let transaction = if let Some(edit) = &item.text_edit {
|
||||||
let edit = match edit {
|
let edit = match edit {
|
||||||
lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
|
lsp::CompletionTextEdit::Edit(edit) => edit.clone(),
|
||||||
lsp::CompletionTextEdit::InsertAndReplace(item) => {
|
lsp::CompletionTextEdit::InsertAndReplace(item) => {
|
||||||
unimplemented!("completion: insert_and_replace {:?}", item)
|
unimplemented!("completion: insert_and_replace {:?}", item)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
util::generate_transaction_from_edits(
|
util::generate_transaction_from_edits(
|
||||||
doc.text(),
|
doc.text(),
|
||||||
vec![edit],
|
vec![edit],
|
||||||
|
@ -114,7 +116,16 @@ impl Completion {
|
||||||
doc.text(),
|
doc.text(),
|
||||||
vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
|
vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
|
||||||
)
|
)
|
||||||
}
|
};
|
||||||
|
|
||||||
|
transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
fn completion_changes(transaction: &Transaction, trigger_offset: usize) -> Vec<Change> {
|
||||||
|
transaction
|
||||||
|
.changes_iter()
|
||||||
|
.filter(|(start, end, _)| (*start..=*end).contains(&trigger_offset))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
let (view, doc) = current!(editor);
|
let (view, doc) = current!(editor);
|
||||||
|
@ -123,7 +134,9 @@ impl Completion {
|
||||||
doc.restore(view.id);
|
doc.restore(view.id);
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
PromptEvent::Abort => {}
|
PromptEvent::Abort => {
|
||||||
|
editor.last_completion = None;
|
||||||
|
}
|
||||||
PromptEvent::Update => {
|
PromptEvent::Update => {
|
||||||
// always present here
|
// always present here
|
||||||
let item = item.unwrap();
|
let item = item.unwrap();
|
||||||
|
@ -138,8 +151,12 @@ impl Completion {
|
||||||
|
|
||||||
// initialize a savepoint
|
// initialize a savepoint
|
||||||
doc.savepoint();
|
doc.savepoint();
|
||||||
|
|
||||||
doc.apply(&transaction, view.id);
|
doc.apply(&transaction, view.id);
|
||||||
|
|
||||||
|
editor.last_completion = Some(CompleteAction {
|
||||||
|
trigger_offset,
|
||||||
|
changes: completion_changes(&transaction, trigger_offset),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
PromptEvent::Validate => {
|
PromptEvent::Validate => {
|
||||||
// always present here
|
// always present here
|
||||||
|
@ -152,8 +169,14 @@ impl Completion {
|
||||||
start_offset,
|
start_offset,
|
||||||
trigger_offset,
|
trigger_offset,
|
||||||
);
|
);
|
||||||
|
|
||||||
doc.apply(&transaction, view.id);
|
doc.apply(&transaction, view.id);
|
||||||
|
|
||||||
|
editor.last_completion = Some(CompleteAction {
|
||||||
|
trigger_offset,
|
||||||
|
changes: completion_changes(&transaction, trigger_offset),
|
||||||
|
});
|
||||||
|
|
||||||
// apply additional edits, mostly used to auto import unqualified types
|
// apply additional edits, mostly used to auto import unqualified types
|
||||||
let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
|
let resolved_additional_text_edits = if item.additional_text_edits.is_some() {
|
||||||
None
|
None
|
||||||
|
|
|
@ -15,11 +15,11 @@ use helix_core::{
|
||||||
syntax::{self, HighlightEvent},
|
syntax::{self, HighlightEvent},
|
||||||
unicode::segmentation::UnicodeSegmentation,
|
unicode::segmentation::UnicodeSegmentation,
|
||||||
unicode::width::UnicodeWidthStr,
|
unicode::width::UnicodeWidthStr,
|
||||||
LineEnding, Position, Range, Selection,
|
LineEnding, Position, Range, Selection, Transaction,
|
||||||
};
|
};
|
||||||
use helix_view::{
|
use helix_view::{
|
||||||
document::{Mode, SCRATCH_BUFFER_NAME},
|
document::{Mode, SCRATCH_BUFFER_NAME},
|
||||||
editor::CursorShapeConfig,
|
editor::{CompleteAction, CursorShapeConfig},
|
||||||
graphics::{CursorKind, Modifier, Rect, Style},
|
graphics::{CursorKind, Modifier, Rect, Style},
|
||||||
input::KeyEvent,
|
input::KeyEvent,
|
||||||
keyboard::{KeyCode, KeyModifiers},
|
keyboard::{KeyCode, KeyModifiers},
|
||||||
|
@ -33,11 +33,18 @@ use tui::buffer::Buffer as Surface;
|
||||||
pub struct EditorView {
|
pub struct EditorView {
|
||||||
pub keymaps: Keymaps,
|
pub keymaps: Keymaps,
|
||||||
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
|
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
|
||||||
last_insert: (commands::MappableCommand, Vec<KeyEvent>),
|
last_insert: (commands::MappableCommand, Vec<InsertEvent>),
|
||||||
pub(crate) completion: Option<Completion>,
|
pub(crate) completion: Option<Completion>,
|
||||||
spinners: ProgressSpinners,
|
spinners: ProgressSpinners,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum InsertEvent {
|
||||||
|
Key(KeyEvent),
|
||||||
|
CompletionApply(CompleteAction),
|
||||||
|
TriggerCompletion,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for EditorView {
|
impl Default for EditorView {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(Keymaps::default())
|
Self::new(Keymaps::default())
|
||||||
|
@ -766,8 +773,33 @@ impl EditorView {
|
||||||
// first execute whatever put us into insert mode
|
// first execute whatever put us into insert mode
|
||||||
self.last_insert.0.execute(cxt);
|
self.last_insert.0.execute(cxt);
|
||||||
// then replay the inputs
|
// then replay the inputs
|
||||||
for &key in &self.last_insert.1.clone() {
|
for key in self.last_insert.1.clone() {
|
||||||
self.insert_mode(cxt, key)
|
match key {
|
||||||
|
InsertEvent::Key(key) => self.insert_mode(cxt, key),
|
||||||
|
InsertEvent::CompletionApply(compl) => {
|
||||||
|
let (view, doc) = current!(cxt.editor);
|
||||||
|
|
||||||
|
doc.restore(view.id);
|
||||||
|
|
||||||
|
let text = doc.text().slice(..);
|
||||||
|
let cursor = doc.selection(view.id).primary().cursor(text);
|
||||||
|
|
||||||
|
let shift_position =
|
||||||
|
|pos: usize| -> usize { pos + cursor - compl.trigger_offset };
|
||||||
|
|
||||||
|
let tx = Transaction::change(
|
||||||
|
doc.text(),
|
||||||
|
compl.changes.iter().cloned().map(|(start, end, t)| {
|
||||||
|
(shift_position(start), shift_position(end), t)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
doc.apply(&tx, view.id);
|
||||||
|
}
|
||||||
|
InsertEvent::TriggerCompletion => {
|
||||||
|
let (_, doc) = current!(cxt.editor);
|
||||||
|
doc.savepoint();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -808,6 +840,9 @@ impl EditorView {
|
||||||
// Immediately initialize a savepoint
|
// Immediately initialize a savepoint
|
||||||
doc_mut!(editor).savepoint();
|
doc_mut!(editor).savepoint();
|
||||||
|
|
||||||
|
editor.last_completion = None;
|
||||||
|
self.last_insert.1.push(InsertEvent::TriggerCompletion);
|
||||||
|
|
||||||
// TODO : propagate required size on resize to completion too
|
// TODO : propagate required size on resize to completion too
|
||||||
completion.required_size((size.width, size.height));
|
completion.required_size((size.width, size.height));
|
||||||
self.completion = Some(completion);
|
self.completion = Some(completion);
|
||||||
|
@ -1067,9 +1102,6 @@ impl Component for EditorView {
|
||||||
} else {
|
} else {
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Insert => {
|
Mode::Insert => {
|
||||||
// record last_insert key
|
|
||||||
self.last_insert.1.push(key);
|
|
||||||
|
|
||||||
// let completion swallow the event if necessary
|
// let completion swallow the event if necessary
|
||||||
let mut consumed = false;
|
let mut consumed = false;
|
||||||
if let Some(completion) = &mut self.completion {
|
if let Some(completion) = &mut self.completion {
|
||||||
|
@ -1093,8 +1125,15 @@ impl Component for EditorView {
|
||||||
|
|
||||||
// if completion didn't take the event, we pass it onto commands
|
// if completion didn't take the event, we pass it onto commands
|
||||||
if !consumed {
|
if !consumed {
|
||||||
|
if let Some(compl) = cx.editor.last_completion.take() {
|
||||||
|
self.last_insert.1.push(InsertEvent::CompletionApply(compl));
|
||||||
|
}
|
||||||
|
|
||||||
self.insert_mode(&mut cx, key);
|
self.insert_mode(&mut cx, key);
|
||||||
|
|
||||||
|
// record last_insert key
|
||||||
|
self.last_insert.1.push(InsertEvent::Key(key));
|
||||||
|
|
||||||
// lastly we recalculate completion
|
// lastly we recalculate completion
|
||||||
if let Some(completion) = &mut self.completion {
|
if let Some(completion) = &mut self.completion {
|
||||||
completion.update(&mut cx);
|
completion.update(&mut cx);
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub use helix_core::register::Registers;
|
||||||
use helix_core::{
|
use helix_core::{
|
||||||
auto_pairs::AutoPairs,
|
auto_pairs::AutoPairs,
|
||||||
syntax::{self, AutoPairConfig},
|
syntax::{self, AutoPairConfig},
|
||||||
|
Change,
|
||||||
};
|
};
|
||||||
use helix_core::{Position, Selection};
|
use helix_core::{Position, Selection};
|
||||||
use helix_dap as dap;
|
use helix_dap as dap;
|
||||||
|
@ -301,9 +302,17 @@ pub struct Editor {
|
||||||
pub last_motion: Option<Motion>,
|
pub last_motion: Option<Motion>,
|
||||||
pub pseudo_pending: Option<String>,
|
pub pseudo_pending: Option<String>,
|
||||||
|
|
||||||
|
pub last_completion: Option<CompleteAction>,
|
||||||
|
|
||||||
pub exit_code: i32,
|
pub exit_code: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CompleteAction {
|
||||||
|
pub trigger_offset: usize,
|
||||||
|
pub changes: Vec<Change>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Load,
|
Load,
|
||||||
|
@ -347,6 +356,7 @@ impl Editor {
|
||||||
autoinfo: None,
|
autoinfo: None,
|
||||||
idle_timer: Box::pin(sleep(config.idle_timeout)),
|
idle_timer: Box::pin(sleep(config.idle_timeout)),
|
||||||
last_motion: None,
|
last_motion: None,
|
||||||
|
last_completion: None,
|
||||||
pseudo_pending: None,
|
pseudo_pending: None,
|
||||||
config,
|
config,
|
||||||
auto_pairs,
|
auto_pairs,
|
||||||
|
|
Loading…
Reference in New Issue