diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 157d19f730ed..a201cd538734 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -3132,18 +3132,11 @@ pub mod insert { super::completion(cx); } - fn language_server_completion(cx: &mut Context, ch: char) { - let config = cx.editor.config(); - if !config.auto_completion { - return; - } - + 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(); @@ -3153,11 +3146,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(); } } @@ -4172,6 +4189,13 @@ pub fn completion(cx: &mut Context) { Some(future) => future, None => return, }; + 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; @@ -4184,11 +4208,21 @@ pub fn completion(cx: &mut Context) { let offset = iter.take_while(|ch| chars::char_is_word(*ch)).count(); let start_offset = cursor.saturating_sub(offset); + 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; } @@ -4199,13 +4233,20 @@ 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 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 90e2fed04d78..b473cb6cb8e5 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -459,6 +459,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 f297b44eb08b..284d0f93e324 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -824,7 +824,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 { @@ -962,9 +962,6 @@ impl EditorView { return; } - // Immediately initialize a savepoint - doc_mut!(editor).savepoint(); - editor.last_completion = None; self.last_insert.1.push(InsertEvent::TriggerCompletion); @@ -983,20 +980,20 @@ impl EditorView { } pub fn handle_idle_timeout(&mut self, cx: &mut commands::Context) -> EventResult { - if let Some(completion) = &mut self.completion { - return if completion.ensure_item_resolved(cx) { - EventResult::Consumed(None) - } else { - EventResult::Ignored(None) - }; - } - - if 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); } - crate::commands::insert::idle_completion(cx); - + 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, + }); EventResult::Consumed(None) } } @@ -1226,7 +1223,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 @@ -1278,7 +1275,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 798b54006d97..b6e9fe40c4a0 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -133,7 +133,7 @@ pub struct Document { pub history: Cell, pub config: Arc>, - pub savepoint: Option, + pub savepoint: Option<(i32, Transaction)>, last_saved_revision: usize, version: i32, // should be usize? @@ -825,7 +825,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))) }); } @@ -917,11 +918,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: &mut View) { - 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 042f5bdb4269..56d8c61ff509 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1006,6 +1006,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; }