Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/handlers/completion/request.rs')
| -rw-r--r-- | helix-term/src/handlers/completion/request.rs | 159 |
1 files changed, 79 insertions, 80 deletions
diff --git a/helix-term/src/handlers/completion/request.rs b/helix-term/src/handlers/completion/request.rs index fd65cd4d..9c37b7e3 100644 --- a/helix-term/src/handlers/completion/request.rs +++ b/helix-term/src/handlers/completion/request.rs @@ -5,14 +5,14 @@ use std::time::Duration; use arc_swap::ArcSwap; use futures_util::Future; use helix_core::completion::CompletionProvider; -use helix_core::syntax::config::LanguageServerFeature; +use helix_core::syntax::LanguageServerFeature; use helix_event::{cancelable_future, TaskController, TaskHandle}; use helix_lsp::lsp; use helix_lsp::lsp::{CompletionContext, CompletionTriggerKind}; use helix_lsp::util::pos_to_lsp_pos; use helix_stdx::rope::RopeSliceExt; -use helix_view::document::{Mode, SavePoint}; -use helix_view::handlers::completion::{CompletionEvent, ResponseContext}; +use helix_view::document::Mode; +use helix_view::handlers::lsp::CompletionEvent; use helix_view::{Document, DocumentId, Editor, ViewId}; use tokio::task::JoinSet; use tokio::time::{timeout_at, Instant}; @@ -28,8 +28,6 @@ use crate::job::{dispatch, dispatch_blocking}; use crate::ui; use crate::ui::editor::InsertEvent; -use super::word; - #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(super) enum TriggerKind { Auto, @@ -47,7 +45,8 @@ pub(super) struct Trigger { #[derive(Debug)] pub struct CompletionHandler { - /// The currently active trigger which will cause a completion request after the timeout. + /// currently active trigger which will cause a + /// completion request after the timeout trigger: Option<Trigger>, in_flight: Option<Trigger>, task_controller: TaskController, @@ -82,12 +81,12 @@ impl helix_event::AsyncHook for CompletionHandler { doc, view, } => { - // Technically it shouldn't be possible to switch views/documents in insert mode - // but people may create weird keymaps/use the mouse so let's be extra careful. + // techically it shouldn't be possible to switch views/documents in insert mode + // but people may create weird keymaps/use the mouse so lets be extra careful if self .trigger .or(self.in_flight) - .is_none_or(|trigger| trigger.doc != doc || trigger.view != view) + .map_or(true, |trigger| trigger.doc != doc || trigger.view != view) { self.trigger = Some(Trigger { pos: trigger_pos, @@ -164,7 +163,7 @@ fn request_completions( editor: &mut Editor, compositor: &mut Compositor, ) { - let (view, doc) = current_ref!(editor); + let (view, doc) = current!(editor); if compositor .find::<ui::EditorView>() @@ -181,17 +180,15 @@ fn request_completions( if trigger.view != view.id || trigger.doc != doc.id() || cursor < trigger.pos { return; } - // This looks odd... Why are we not using the trigger position from the `trigger` here? Won't - // that mean that the trigger char doesn't get send to the language server if we type fast - // enough? Yes that is true but it's not actually a problem. The language server will resolve - // the completion to the identifier anyway (in fact sending the later position is necessary to - // get the right results from language servers that provide incomplete completion list). We - // rely on the trigger offset and primary cursor matching for multi-cursor completions so this - // is definitely necessary from our side too. + // this looks odd... Why are we not using the trigger position from + // the `trigger` here? Won't that mean that the trigger char doesn't get + // send to the LS if we type fast enougn? Yes that is true but it's + // not actually a problem. The LSP will resolve the completion to the identifier + // anyway (in fact sending the later position is necessary to get the right results + // from LSPs that provide incomplete completion list). We rely on trigger offset + // and primary cursor matching for multi-cursor completions so this is definitely + // necessary from our side too. trigger.pos = cursor; - let doc = doc_mut!(editor, &doc.id()); - let savepoint = doc.savepoint(view); - let text = doc.text(); let trigger_text = text.slice(..cursor); let mut seen_language_servers = HashSet::new(); @@ -237,49 +234,53 @@ fn request_completions( view.id, context, -(priority as i8), - savepoint.clone(), )); } - if let Some(path_completion_request) = path_completion( - doc.selection(view.id).clone(), - doc, - handle.clone(), - savepoint.clone(), - ) { - requests.spawn_blocking(path_completion_request); - } - if let Some(word_completion_request) = - word::completion(editor, trigger, handle.clone(), savepoint) + if let Some(path_completion_request) = + path_completion(cursor, text.clone(), doc, handle.clone()) { - requests.spawn_blocking(word_completion_request); + requests.spawn_blocking(path_completion_request); } + let savepoint = doc.savepoint(view); + let ui = compositor.find::<ui::EditorView>().unwrap(); ui.last_insert.1.push(InsertEvent::RequestCompletion); let handle_ = handle.clone(); let request_completions = async move { - let mut context = HashMap::new(); - let Some(mut response) = handle_response(&mut requests, false).await else { + let mut incomplete_completion_lists = HashMap::new(); + let Some(response) = handle_response(&mut requests, false).await else { return; }; + if response.incomplete { + incomplete_completion_lists.insert(response.provider, response.priority); + } let mut items: Vec<_> = Vec::new(); - response.take_items(&mut items); - context.insert(response.provider, response.context); + response.into_items(&mut items); let deadline = Instant::now() + Duration::from_millis(100); loop { - let Some(mut response) = timeout_at(deadline, handle_response(&mut requests, false)) + let Some(response) = timeout_at(deadline, handle_response(&mut requests, false)) .await .ok() .flatten() else { break; }; - response.take_items(&mut items); - context.insert(response.provider, response.context); + if response.incomplete { + incomplete_completion_lists.insert(response.provider, response.priority); + } + response.into_items(&mut items); } dispatch(move |editor, compositor| { - show_completion(editor, compositor, items, context, trigger) + show_completion( + editor, + compositor, + items, + incomplete_completion_lists, + trigger, + savepoint, + ) }) .await; if !requests.is_empty() { @@ -295,7 +296,6 @@ fn request_completions_from_language_server( view: ViewId, context: lsp::CompletionContext, priority: i8, - savepoint: Arc<SavePoint>, ) -> impl Future<Output = CompletionResponse> { let provider = ls.id(); let offset_encoding = ls.offset_encoding(); @@ -304,16 +304,17 @@ fn request_completions_from_language_server( let pos = pos_to_lsp_pos(text, cursor, offset_encoding); let doc_id = doc.identifier(); - // it's important that this is before the async block (and that this is not an async function) + // it's important that this is berofe the async block (and that this is not an async function) // to ensure the request is dispatched right away before any new edit notifications let completion_response = ls.completion(doc_id, pos, None, context).unwrap(); async move { let response: Option<lsp::CompletionResponse> = completion_response .await + .and_then(|json| serde_json::from_value(json).map_err(helix_lsp::Error::Parse)) .inspect_err(|err| log::error!("completion request failed: {err}")) .ok() .flatten(); - let (mut items, is_incomplete) = match response { + let (mut items, incomplete) = match response { Some(lsp::CompletionResponse::Array(items)) => (items, false), Some(lsp::CompletionResponse::List(lsp::CompletionList { is_incomplete, @@ -328,47 +329,45 @@ fn request_completions_from_language_server( }); CompletionResponse { items: CompletionItems::Lsp(items), - context: ResponseContext { - is_incomplete, - priority, - savepoint, - }, + incomplete, provider: CompletionProvider::Lsp(provider), + priority, } } } -pub fn request_incomplete_completion_list(editor: &mut Editor, handle: TaskHandle) { - let handler = &mut editor.handlers.completions; - let mut requests = JoinSet::new(); - let mut savepoint = None; - for (&provider, context) in &handler.active_completions { - if !context.is_incomplete { - continue; - } - let CompletionProvider::Lsp(ls_id) = provider else { - log::error!("non-lsp incomplete completion lists"); - continue; - }; - let Some(ls) = editor.language_servers.get_by_id(ls_id) else { - continue; - }; - let (view, doc) = current!(editor); - let savepoint = savepoint.get_or_insert_with(|| doc.savepoint(view)).clone(); - let request = request_completions_from_language_server( - ls, - doc, - view.id, - CompletionContext { - trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, - trigger_character: None, - }, - context.priority, - savepoint, - ); - requests.spawn(request); - } - if !requests.is_empty() { - tokio::spawn(replace_completions(handle, requests, true)); +pub fn request_incomplete_completion_list( + editor: &mut Editor, + ui: &mut ui::Completion, + handle: TaskHandle, +) { + if ui.incomplete_completion_lists.is_empty() { + return; } + let (view, doc) = current_ref!(editor); + let mut requests = JoinSet::new(); + log::error!("request incomplete completions"); + ui.incomplete_completion_lists + .retain(|&provider, &mut priority| { + let CompletionProvider::Lsp(ls_id) = provider else { + unimplemented!("non-lsp incomplete completion lists") + }; + let Some(ls) = editor.language_server_by_id(ls_id) else { + return false; + }; + log::error!("request incomplete completions2"); + let request = request_completions_from_language_server( + ls, + doc, + view.id, + CompletionContext { + trigger_kind: CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, + trigger_character: None, + }, + priority, + ); + requests.spawn(request); + true + }); + tokio::spawn(replace_completions(handle, requests, true)); } |