Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/ui/completion.rs')
-rw-r--r--helix-term/src/ui/completion.rs115
1 files changed, 18 insertions, 97 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs
index 2b3437e1..7a08c90c 100644
--- a/helix-term/src/ui/completion.rs
+++ b/helix-term/src/ui/completion.rs
@@ -1,9 +1,7 @@
use crate::{
compositor::{Component, Context, Event, EventResult},
- handlers::trigger_auto_completion,
- job,
+ handlers::{completion::ResolveHandler, trigger_auto_completion},
};
-use helix_event::AsyncHook;
use helix_view::{
document::SavePoint,
editor::CompleteAction,
@@ -12,17 +10,16 @@ use helix_view::{
theme::{Modifier, Style},
ViewId,
};
-use tokio::time::Instant;
use tui::{buffer::Buffer as Surface, text::Span};
-use std::{borrow::Cow, sync::Arc, time::Duration};
+use std::{borrow::Cow, sync::Arc};
use helix_core::{chars, Change, Transaction};
use helix_view::{graphics::Rect, Document, Editor};
use crate::ui::{menu, Markdown, Menu, Popup, PromptEvent};
-use helix_lsp::{lsp, util, OffsetEncoding};
+use helix_lsp::{lsp, util, LanguageServerId, OffsetEncoding};
impl menu::Item for CompletionItem {
type Data = ();
@@ -104,7 +101,7 @@ pub struct Completion {
#[allow(dead_code)]
trigger_offset: usize,
filter: String,
- resolve_handler: tokio::sync::mpsc::Sender<CompletionItem>,
+ resolve_handler: ResolveHandler,
}
impl Completion {
@@ -365,7 +362,7 @@ impl Completion {
// TODO: expand nucleo api to allow moving straight to a Utf32String here
// and avoid allocation during matching
filter: String::from(fragment),
- resolve_handler: ResolveHandler::default().spawn(),
+ resolve_handler: ResolveHandler::new(),
};
// need to recompute immediately in case start_offset != trigger_offset
@@ -383,7 +380,16 @@ impl Completion {
language_server: &helix_lsp::Client,
completion_item: lsp::CompletionItem,
) -> Option<lsp::CompletionItem> {
- let future = language_server.resolve_completion_item(completion_item)?;
+ if !matches!(
+ language_server.capabilities().completion_provider,
+ Some(lsp::CompletionOptions {
+ resolve_provider: Some(true),
+ ..
+ })
+ ) {
+ return None;
+ }
+ let future = language_server.resolve_completion_item(&completion_item);
let response = helix_lsp::block_on(future);
match response {
Ok(item) => Some(item),
@@ -416,7 +422,7 @@ impl Completion {
self.popup.contents().is_empty()
}
- fn replace_item(&mut self, old_item: CompletionItem, new_item: CompletionItem) {
+ pub fn replace_item(&mut self, old_item: &CompletionItem, new_item: CompletionItem) {
self.popup.contents_mut().replace_option(old_item, new_item);
}
@@ -438,12 +444,12 @@ impl Component for Completion {
self.popup.render(area, surface, cx);
// if we have a selection, render a markdown popup on top/below with info
- let option = match self.popup.contents().selection() {
+ let option = match self.popup.contents_mut().selection_mut() {
Some(option) => option,
None => return,
};
if !option.resolved {
- helix_event::send_blocking(&self.resolve_handler, option.clone());
+ self.resolve_handler.ensure_item_resolved(cx.editor, option);
}
// need to render:
// option.detail
@@ -541,88 +547,3 @@ impl Component for Completion {
markdown_doc.render(doc_area, surface, cx);
}
}
-
-/// A hook for resolving incomplete completion items.
-///
-/// From the [LSP spec](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_completion):
-///
-/// > If computing full completion items is expensive, servers can additionally provide a
-/// > handler for the completion item resolve request. ...
-/// > A typical use case is for example: the `textDocument/completion` request doesn't fill
-/// > in the `documentation` property for returned completion items since it is expensive
-/// > to compute. When the item is selected in the user interface then a
-/// > 'completionItem/resolve' request is sent with the selected completion item as a parameter.
-/// > The returned completion item should have the documentation property filled in.
-#[derive(Debug, Default)]
-struct ResolveHandler {
- trigger: Option<CompletionItem>,
- request: Option<helix_event::CancelTx>,
-}
-
-impl AsyncHook for ResolveHandler {
- type Event = CompletionItem;
-
- fn handle_event(
- &mut self,
- item: Self::Event,
- timeout: Option<tokio::time::Instant>,
- ) -> Option<tokio::time::Instant> {
- if self
- .trigger
- .as_ref()
- .is_some_and(|trigger| trigger == &item)
- {
- timeout
- } else {
- self.trigger = Some(item);
- self.request = None;
- Some(Instant::now() + Duration::from_millis(150))
- }
- }
-
- fn finish_debounce(&mut self) {
- let Some(item) = self.trigger.take() else { return };
- let (tx, rx) = helix_event::cancelation();
- self.request = Some(tx);
- job::dispatch_blocking(move |editor, _| resolve_completion_item(editor, item, rx))
- }
-}
-
-fn resolve_completion_item(
- editor: &mut Editor,
- item: CompletionItem,
- cancel: helix_event::CancelRx,
-) {
- let Some(language_server) = editor.language_server_by_id(item.language_server_id) else {
- return;
- };
-
- let Some(future) = language_server.resolve_completion_item(item.item.clone()) else {
- return;
- };
-
- tokio::spawn(async move {
- match helix_event::cancelable_future(future, cancel).await {
- Some(Ok(resolved_item)) => {
- job::dispatch(move |_, compositor| {
- if let Some(completion) = &mut compositor
- .find::<crate::ui::EditorView>()
- .unwrap()
- .completion
- {
- let resolved_item = CompletionItem {
- item: resolved_item,
- language_server_id: item.language_server_id,
- resolved: true,
- };
-
- completion.replace_item(item, resolved_item);
- };
- })
- .await
- }
- Some(Err(err)) => log::error!("completion resolve request failed: {err}"),
- None => (),
- }
- });
-}