on master help sign in
completion: fully revert state before apply & insertText common prefix
3edca78Blaž Hrastnik
helix-core/src/transaction.rs+7
@@ -468,6 +468,13 @@ impl Transaction {
468 }
468 }
469 }
469 }
470
470
471+ pub fn compose(mut self, other: Self) -> Self {
472+ self.changes = self.changes.compose(other.changes);
473+ // Other selection takes precedence
474+ self.selection = other.selection;
475+ self
476+ }
477+
471 pub fn with_selection(mut self, selection: Selection) -> Self {
478 pub fn with_selection(mut self, selection: Selection) -> Self {
472 self.selection = Some(selection);
479 self.selection = Some(selection);
473 self
480 self
helix-term/src/ui/completion.rs+30−41
@@ -5,7 +5,7 @@ use tui::buffer::Buffer as Surface;
5use std::borrow::Cow;
5use std::borrow::Cow;
6
6
7use helix_core::Transaction;
7use helix_core::Transaction;
8−use helix_view::{graphics::Rect, Document, Editor, View};
8+use helix_view::{graphics::Rect, Document, Editor};
9
9
10use crate::commands;
10use crate::commands;
11use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
11use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
@@ -83,13 +83,13 @@ impl Completion {
83 start_offset: usize,
83 start_offset: usize,
84 trigger_offset: usize,
84 trigger_offset: usize,
85 ) -> Self {
85 ) -> Self {
86− // let items: Vec<CompletionItem> = Vec::new();
87 let menu = Menu::new(items, move |editor: &mut Editor, item, event| {
86 let menu = Menu::new(items, move |editor: &mut Editor, item, event| {
88 fn item_to_transaction(
87 fn item_to_transaction(
89 doc: &Document,
88 doc: &Document,
90− view: &View,
91 item: &CompletionItem,
89 item: &CompletionItem,
92 offset_encoding: helix_lsp::OffsetEncoding,
90 offset_encoding: helix_lsp::OffsetEncoding,
91+ start_offset: usize,
92+ trigger_offset: usize,
93 ) -> Transaction {
93 ) -> Transaction {
94 if let Some(edit) = &item.text_edit {
94 if let Some(edit) = &item.text_edit {
95 let edit = match edit {
95 let edit = match edit {
@@ -105,63 +105,52 @@ impl Completion {
105 )
105 )
106 } else {
106 } else {
107 let text = item.insert_text.as_ref().unwrap_or(&item.label);
107 let text = item.insert_text.as_ref().unwrap_or(&item.label);
108− let cursor = doc
108+ // Some LSPs just give you an insertText with no offset ¯\_(ツ)_/¯
109− .selection(view.id)
109+ // in these cases we need to check for a common prefix and remove it
110− .primary()
110+ let prefix = Cow::from(doc.text().slice(start_offset..trigger_offset));
111− .cursor(doc.text().slice(..));
111+ let text = text.trim_start_matches::<&str>(&prefix);
112 Transaction::change(
112 Transaction::change(
113 doc.text(),
113 doc.text(),
114− vec![(cursor, cursor, Some(text.as_str().into()))].into_iter(),
114+ vec![(trigger_offset, trigger_offset, Some(text.into()))].into_iter(),
115 )
115 )
116 }
116 }
117 }
117 }
118
118
119+ let (view, doc) = current!(editor);
120+
121+ // if more text was entered, remove it
122+ doc.restore(view.id);
123+
119 match event {
124 match event {
120 PromptEvent::Abort => {}
125 PromptEvent::Abort => {}
121 PromptEvent::Update => {
126 PromptEvent::Update => {
122− let (view, doc) = current!(editor);
123−
124 // always present here
127 // always present here
125 let item = item.unwrap();
128 let item = item.unwrap();
126
129
127− // if more text was entered, remove it
130+ let transaction = item_to_transaction(
128− // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes
131+ doc,
129− let cursor = doc
132+ item,
130− .selection(view.id)
133+ offset_encoding,
131− .primary()
134+ start_offset,
132− .cursor(doc.text().slice(..));
133− if trigger_offset < cursor {
134− let remove = Transaction::change(
135− doc.text(),
136− vec![(trigger_offset, cursor, None)].into_iter(),
135+ trigger_offset,
137− );
136+ );
138− doc.apply(&remove, view.id);
137+
139− }
138+ // initialize a savepoint
139+ doc.savepoint();
140
140
141− let transaction = item_to_transaction(doc, view, item, offset_encoding);
142 doc.apply(&transaction, view.id);
141 doc.apply(&transaction, view.id);
143 }
142 }
144 PromptEvent::Validate => {
143 PromptEvent::Validate => {
145− let (view, doc) = current!(editor);
146−
147 // always present here
144 // always present here
148 let item = item.unwrap();
145 let item = item.unwrap();
149
146
150− // if more text was entered, remove it
151− // TODO: ideally to undo we should keep the last completion tx revert, and map it over new changes
152− let cursor = doc
153− .selection(view.id)
154− .primary()
155− .cursor(doc.text().slice(..));
156− if trigger_offset < cursor {
157− let remove = Transaction::change(
158− doc.text(),
159− vec![(trigger_offset, cursor, None)].into_iter(),
160− );
161− doc.apply(&remove, view.id);
162− }
163−
164− let transaction = item_to_transaction(doc, view, item, offset_encoding);
147+ let transaction = item_to_transaction(
148+ doc,
149+ item,
150+ offset_encoding,
151+ start_offset,
152+ trigger_offset,
153+ );
165 doc.apply(&transaction, view.id);
154 doc.apply(&transaction, view.id);
166
155
167 if let Some(additional_edits) = &item.additional_text_edits {
156 if let Some(additional_edits) = &item.additional_text_edits {
helix-term/src/ui/editor.rs+11−2
@@ -13,7 +13,7 @@ use helix_core::{
13 syntax::{self, HighlightEvent},
13 syntax::{self, HighlightEvent},
14 unicode::segmentation::UnicodeSegmentation,
14 unicode::segmentation::UnicodeSegmentation,
15 unicode::width::UnicodeWidthStr,
15 unicode::width::UnicodeWidthStr,
16− LineEnding, Position, Range, Selection,
16+ LineEnding, Position, Range, Selection, Transaction,
17};
17};
18use helix_view::{
18use helix_view::{
19 document::Mode,
19 document::Mode,
@@ -721,7 +721,7 @@ impl EditorView {
721
721
722 pub fn set_completion(
722 pub fn set_completion(
723 &mut self,
723 &mut self,
724− editor: &Editor,
724+ editor: &mut Editor,
725 items: Vec<helix_lsp::lsp::CompletionItem>,
725 items: Vec<helix_lsp::lsp::CompletionItem>,
726 offset_encoding: helix_lsp::OffsetEncoding,
726 offset_encoding: helix_lsp::OffsetEncoding,
727 start_offset: usize,
727 start_offset: usize,
@@ -736,6 +736,9 @@ impl EditorView {
736 return;
736 return;
737 }
737 }
738
738
739+ // Immediately initialize a savepoint
740+ doc_mut!(editor).savepoint();
741+
739 // TODO : propagate required size on resize to completion too
742 // TODO : propagate required size on resize to completion too
740 completion.required_size((size.width, size.height));
743 completion.required_size((size.width, size.height));
741 self.completion = Some(completion);
744 self.completion = Some(completion);
@@ -945,6 +948,9 @@ impl Component for EditorView {
945 if callback.is_some() {
948 if callback.is_some() {
946 // assume close_fn
949 // assume close_fn
947 self.completion = None;
950 self.completion = None;
951+ // Clear any savepoints
952+ let (_, doc) = current!(cxt.editor);
953+ doc.savepoint = None;
948 cxt.editor.clear_idle_timer(); // don't retrigger
954 cxt.editor.clear_idle_timer(); // don't retrigger
949 }
955 }
950 }
956 }
@@ -959,6 +965,9 @@ impl Component for EditorView {
959 completion.update(&mut cxt);
965 completion.update(&mut cxt);
960 if completion.is_empty() {
966 if completion.is_empty() {
961 self.completion = None;
967 self.completion = None;
968+ // Clear any savepoints
969+ let (_, doc) = current!(cxt.editor);
970+ doc.savepoint = None;
962 cxt.editor.clear_idle_timer(); // don't retrigger
971 cxt.editor.clear_idle_timer(); // don't retrigger
963 }
972 }
964 }
973 }
helix-view/src/document.rs+23−1
@@ -97,6 +97,9 @@ pub struct Document {
97 // it back as it separated from the edits. We could split out the parts manually but that will
97 // it back as it separated from the edits. We could split out the parts manually but that will
98 // be more troublesome.
98 // be more troublesome.
99 history: Cell<History>,
99 history: Cell<History>,
100+
101+ pub savepoint: Option<Transaction>,
102+
100 last_saved_revision: usize,
103 last_saved_revision: usize,
101 version: i32, // should be usize?
104 version: i32, // should be usize?
102
105
@@ -328,6 +331,7 @@ impl Document {
328 text,
331 text,
329 selections: HashMap::default(),
332 selections: HashMap::default(),
330 indent_style: DEFAULT_INDENT,
333 indent_style: DEFAULT_INDENT,
334+ line_ending: DEFAULT_LINE_ENDING,
331 mode: Mode::Normal,
335 mode: Mode::Normal,
332 restore_cursor: false,
336 restore_cursor: false,
333 syntax: None,
337 syntax: None,
@@ -337,9 +341,9 @@ impl Document {
337 diagnostics: Vec::new(),
341 diagnostics: Vec::new(),
338 version: 0,
342 version: 0,
339 history: Cell::new(History::default()),
343 history: Cell::new(History::default()),
344+ savepoint: None,
340 last_saved_revision: 0,
345 last_saved_revision: 0,
341 language_server: None,
346 language_server: None,
342− line_ending: DEFAULT_LINE_ENDING,
343 }
347 }
344 }
348 }
345
349
@@ -635,6 +639,14 @@ impl Document {
635 if !transaction.changes().is_empty() {
639 if !transaction.changes().is_empty() {
636 self.version += 1;
640 self.version += 1;
637
641
642+ // generate revert to savepoint
643+ if self.savepoint.is_some() {
644+ take_with(&mut self.savepoint, |prev_revert| {
645+ let revert = transaction.invert(&old_doc);
646+ Some(revert.compose(prev_revert.unwrap()))
647+ });
648+ }
649+
638 // update tree-sitter syntax tree
650 // update tree-sitter syntax tree
639 if let Some(syntax) = &mut self.syntax {
651 if let Some(syntax) = &mut self.syntax {
640 // TODO: no unwrap
652 // TODO: no unwrap
@@ -724,6 +736,16 @@ impl Document {
724 }
736 }
725 }
737 }
726
738
739+ pub fn savepoint(&mut self) {
740+ self.savepoint = Some(Transaction::new(self.text()));
741+ }
742+
743+ pub fn restore(&mut self, view_id: ViewId) {
744+ if let Some(revert) = self.savepoint.take() {
745+ self.apply(&revert, view_id);
746+ }
747+ }
748+
727 /// Undo modifications to the [`Document`] according to `uk`.
749 /// Undo modifications to the [`Document`] according to `uk`.
728 pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) {
750 pub fn earlier(&mut self, view_id: ViewId, uk: helix_core::history::UndoKind) {
729 let txns = self.history.get_mut().earlier(uk);
751 let txns = self.history.get_mut().earlier(uk);