Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/handlers/completion/resolve.rs')
| -rw-r--r-- | helix-term/src/handlers/completion/resolve.rs | 173 |
1 files changed, 0 insertions, 173 deletions
diff --git a/helix-term/src/handlers/completion/resolve.rs b/helix-term/src/handlers/completion/resolve.rs deleted file mode 100644 index 802d6f51..00000000 --- a/helix-term/src/handlers/completion/resolve.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::sync::Arc; - -use helix_lsp::lsp; -use tokio::sync::mpsc::Sender; -use tokio::time::{Duration, Instant}; - -use helix_event::{send_blocking, AsyncHook, TaskController, TaskHandle}; -use helix_view::Editor; - -use super::LspCompletionItem; -use crate::handlers::completion::CompletionItem; -use crate::job; - -/// 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. -pub struct ResolveHandler { - last_request: Option<Arc<LspCompletionItem>>, - resolver: Sender<ResolveRequest>, -} - -impl ResolveHandler { - pub fn new() -> ResolveHandler { - ResolveHandler { - last_request: None, - resolver: ResolveTimeout::default().spawn(), - } - } - - pub fn ensure_item_resolved(&mut self, editor: &mut Editor, item: &mut LspCompletionItem) { - if item.resolved { - return; - } - // We consider an item to be fully resolved if it has non-empty, none-`None` details, - // docs and additional text-edits. Ideally we could use `is_some` instead of this - // check but some language servers send values like `Some([])` for additional text - // edits although the items need to be resolved. This is probably a consequence of - // how `null` works in the JavaScript world. - let is_resolved = item - .item - .documentation - .as_ref() - .is_some_and(|docs| match docs { - lsp::Documentation::String(text) => !text.is_empty(), - lsp::Documentation::MarkupContent(markup) => !markup.value.is_empty(), - }) - && item - .item - .detail - .as_ref() - .is_some_and(|detail| !detail.is_empty()) - && item - .item - .additional_text_edits - .as_ref() - .is_some_and(|edits| !edits.is_empty()); - if is_resolved { - item.resolved = true; - return; - } - if self.last_request.as_deref().is_some_and(|it| it == item) { - return; - } - let Some(ls) = editor.language_servers.get_by_id(item.provider).cloned() else { - item.resolved = true; - return; - }; - if matches!( - ls.capabilities().completion_provider, - Some(lsp::CompletionOptions { - resolve_provider: Some(true), - .. - }) - ) { - let item = Arc::new(item.clone()); - self.last_request = Some(item.clone()); - send_blocking(&self.resolver, ResolveRequest { item, ls }) - } else { - item.resolved = true; - } - } -} - -struct ResolveRequest { - item: Arc<LspCompletionItem>, - ls: Arc<helix_lsp::Client>, -} - -#[derive(Default)] -struct ResolveTimeout { - next_request: Option<ResolveRequest>, - in_flight: Option<Arc<LspCompletionItem>>, - task_controller: TaskController, -} - -impl AsyncHook for ResolveTimeout { - type Event = ResolveRequest; - - fn handle_event( - &mut self, - request: Self::Event, - timeout: Option<tokio::time::Instant>, - ) -> Option<tokio::time::Instant> { - if self - .next_request - .as_ref() - .is_some_and(|old_request| old_request.item == request.item) - { - timeout - } else if self - .in_flight - .as_ref() - .is_some_and(|old_request| old_request.item == request.item.item) - { - self.next_request = None; - None - } else { - self.next_request = Some(request); - Some(Instant::now() + Duration::from_millis(150)) - } - } - - fn finish_debounce(&mut self) { - let Some(request) = self.next_request.take() else { - return; - }; - let token = self.task_controller.restart(); - self.in_flight = Some(request.item.clone()); - tokio::spawn(request.execute(token)); - } -} - -impl ResolveRequest { - async fn execute(self, cancel: TaskHandle) { - let future = self.ls.resolve_completion_item(&self.item.item); - let Some(resolved_item) = helix_event::cancelable_future(future, cancel).await else { - return; - }; - job::dispatch(move |_, compositor| { - if let Some(completion) = &mut compositor - .find::<crate::ui::EditorView>() - .unwrap() - .completion - { - let resolved_item = CompletionItem::Lsp(match resolved_item { - Ok(item) => LspCompletionItem { - item, - resolved: true, - ..*self.item - }, - Err(err) => { - log::error!("completion resolve request failed: {err}"); - // set item to resolved so we don't request it again - // we could also remove it but that oculd be odd ui - let mut item = (*self.item).clone(); - item.resolved = true; - item - } - }); - completion.replace_item(&*self.item, resolved_item); - }; - }) - .await - } -} |