From aec001ad84e50d3e1eb52136b024052c1360a49f Mon Sep 17 00:00:00 2001 From: cossonleo Date: Sat, 10 Sep 2022 22:53:32 +0800 Subject: [PATCH] completion fix --- helix-term/src/commands.rs | 83 ++++++++++++++++++++++++--------- helix-term/src/ui/completion.rs | 10 ++++ helix-term/src/ui/editor.rs | 23 ++++----- helix-view/src/document.rs | 9 ++-- helix-view/src/editor.rs | 4 ++ 5 files changed, 90 insertions(+), 39 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index e869446e..87bac766 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2887,13 +2887,11 @@ pub mod insert { super::completion(cx); } - fn language_server_completion(cx: &mut Context, ch: char) { + pub fn is_server_trigger_char(doc: &Document, ch: char) -> bool { use helix_lsp::lsp; - // if ch matches completion char, trigger completion - let doc = doc_mut!(cx.editor); let language_server = match doc.language_server() { Some(language_server) => language_server, - None => return, + None => return false, }; let capabilities = language_server.capabilities(); @@ -2903,11 +2901,35 @@ pub mod insert { .. }) = &capabilities.completion_provider { - // TODO: what if trigger is multiple chars long - if triggers.iter().any(|trigger| trigger.contains(ch)) { - cx.editor.clear_idle_timer(); - super::completion(cx); + triggers.iter().any(|trigger| trigger.contains(ch)) + } else { + false + } + } + + fn language_server_completion(cx: &mut Context, ch: char) { + use helix_core::chars::char_is_word; + + let config = cx.editor.config(); + if !config.auto_completion { + return; + } + let (view, doc) = current_ref!(cx.editor); + if char_is_word(ch) && doc.savepoint.is_none() { + let text = doc.text().slice(..); + let cursor = doc.selection(view.id).primary().cursor(text); + let mut iter = text.chars_at(cursor); + iter.reverse(); + for _ in 0..config.completion_trigger_len { + if iter.next().map_or(true, |c| !char_is_word(c)) { + return; + } } + cx.editor.reset_idle_timer(); + return; + } + if is_server_trigger_char(doc, ch) { + cx.editor.reset_idle_timer_zero(); } } @@ -3819,6 +3841,13 @@ pub fn completion(cx: &mut Context) { let pos = pos_to_lsp_pos(doc.text(), cursor, offset_encoding); let future = language_server.completion(doc.identifier(), pos, None); + let future = async move { + match future.await { + Ok(v) => Ok(v), + Err(helix_lsp::Error::Timeout) => Ok(serde_json::Value::Null), + Err(e) => Err(e), + } + }; let trigger_offset = cursor; @@ -3832,11 +3861,21 @@ pub fn completion(cx: &mut Context) { let start_offset = cursor.saturating_sub(offset); let prefix = text.slice(start_offset..cursor).to_string(); + doc.savepoint(); + let trigger_version = doc.version(); cx.callback( future, move |editor, compositor, response: Option| { + let doc = doc_mut!(editor); + let savepoint = match doc.savepoint.take() { + Some(s) => s, + None => return, + }; if editor.mode != Mode::Insert { - // we're not in insert mode anymore + return; + } + if savepoint.0 != trigger_version { + doc.savepoint = Some(savepoint); return; } @@ -3847,25 +3886,27 @@ pub fn completion(cx: &mut Context) { is_incomplete: _is_incomplete, items, })) => items, - None => Vec::new(), + None => { + editor.set_status( + "The completion response is none and will request server again", + ); + editor.reset_idle_timer(); + return; + } }; if !prefix.is_empty() { - items = items - .into_iter() - .filter(|item| { - item.filter_text - .as_ref() - .unwrap_or(&item.label) - .starts_with(&prefix) - }) - .collect(); - } + items.retain(|item| match &item.filter_text { + Some(t) => t.starts_with(&prefix), + None => item.label.starts_with(&prefix), + }); + }; if items.is_empty() { - // editor.set_error("No completion available"); + // editor.set_error("No completion available".to_string()); return; } + doc.savepoint = Some(savepoint); let size = compositor.size(); let ui = compositor.find::().unwrap(); ui.set_completion( diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 2d7d4f92..a2d71ca4 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -394,6 +394,16 @@ impl Component for Completion { height = rel_height.min(height); } Rect::new(x, y, width, height) + } else if popup_x > 30 { + let mut height = area.height.saturating_sub(popup_y); + let mut width = popup_x; + if let Some((rel_width, rel_height)) = markdown_doc.required_size((width, height)) { + width = rel_width.min(width); + height = rel_height.min(height); + } + let x = popup_x - width; + let y = popup_y; + Rect::new(x, y, width, height) } else { let half = area.height / 2; let height = 15.min(half); diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 7cb29c3b..eeeb9a9f 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -851,7 +851,7 @@ impl EditorView { } (Mode::Insert, Mode::Normal) => { // if exiting insert mode, remove completion - self.completion = None; + self.clear_completion(cxt.editor); // TODO: Use an on_mode_change hook to remove signature help cxt.jobs.callback(async { @@ -985,9 +985,6 @@ impl EditorView { return; } - // Immediately initialize a savepoint - doc_mut!(editor).savepoint(); - editor.last_completion = None; self.last_insert.1.push(InsertEvent::TriggerCompletion); @@ -1006,23 +1003,20 @@ impl EditorView { } pub fn handle_idle_timeout(&mut self, cx: &mut crate::compositor::Context) -> EventResult { - if self.completion.is_some() - || cx.editor.mode != Mode::Insert - || !cx.editor.config().auto_completion - { + let config = cx.editor.config(); + if cx.editor.mode != Mode::Insert || !config.auto_completion { return EventResult::Ignored(None); } - let mut cx = commands::Context { + self.clear_completion(cx.editor); + commands::completion(&mut commands::Context { register: None, editor: cx.editor, jobs: cx.jobs, count: None, callback: None, on_next_key_callback: None, - }; - crate::commands::insert::idle_completion(&mut cx); - + }); EventResult::Consumed(None) } } @@ -1246,7 +1240,7 @@ impl Component for EditorView { EventResult::Consumed(None) } Event::Key(mut key) => { - cx.editor.reset_idle_timer(); + cx.editor.clear_idle_timer(); canonicalize_key(&mut key); // clear status @@ -1298,7 +1292,8 @@ impl Component for EditorView { if let Some(completion) = &mut self.completion { completion.update(&mut cx); if completion.is_empty() { - self.clear_completion(cx.editor); + self.completion = None; + doc_mut!(cx.editor).savepoint = None; } } } diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 8ebc9002..4b7f89b1 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -112,7 +112,7 @@ pub struct Document { // be more troublesome. pub history: Cell, - pub savepoint: Option, + pub savepoint: Option<(i32, Transaction)>, last_saved_revision: usize, version: i32, // should be usize? @@ -768,7 +768,8 @@ impl Document { if self.savepoint.is_some() { take_with(&mut self.savepoint, |prev_revert| { let revert = transaction.invert(&old_doc); - Some(revert.compose(prev_revert.unwrap())) + let (version, prev_revert) = prev_revert.unwrap(); + Some((version, revert.compose(prev_revert))) }); } @@ -856,11 +857,11 @@ impl Document { } pub fn savepoint(&mut self) { - self.savepoint = Some(Transaction::new(self.text())); + self.savepoint = Some((self.version, Transaction::new(self.text()))); } pub fn restore(&mut self, view_id: ViewId) { - if let Some(revert) = self.savepoint.take() { + if let Some((_, revert)) = self.savepoint.take() { self.apply(&revert, view_id); } } diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 5eff9983..fc8c0c11 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -763,6 +763,10 @@ impl Editor { .reset(Instant::now() + config.idle_timeout); } + pub fn reset_idle_timer_zero(&mut self) { + self.idle_timer.as_mut().reset(Instant::now()); + } + pub fn clear_status(&mut self) { self.status_msg = None; }