|
| 1 | +use std::sync::Arc; |
| 2 | + |
| 3 | +use arc_swap::ArcSwap; |
| 4 | +use helix_core::{syntax::config::LanguageServerFeature, text_annotations::InlineAnnotation}; |
| 5 | +use helix_event::{register_hook, send_blocking}; |
| 6 | +use helix_lsp::lsp; |
| 7 | +use helix_view::{ |
| 8 | + events::DocumentDidChange, |
| 9 | + handlers::{lsp::InlineCompletionEvent, Handlers}, |
| 10 | + DocumentId, |
| 11 | +}; |
| 12 | + |
| 13 | +use crate::config::Config; |
| 14 | +use tokio::time::Instant; |
| 15 | + |
| 16 | +use crate::job; |
| 17 | + |
| 18 | +pub(super) struct InlineCompletionHandler { |
| 19 | + pending: Option<(DocumentId, usize)>, |
| 20 | + config: Arc<ArcSwap<Config>>, |
| 21 | +} |
| 22 | + |
| 23 | +impl InlineCompletionHandler { |
| 24 | + pub fn new(config: Arc<ArcSwap<Config>>) -> Self { |
| 25 | + Self { |
| 26 | + pending: None, |
| 27 | + config, |
| 28 | + } |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +impl helix_event::AsyncHook for InlineCompletionHandler { |
| 33 | + type Event = InlineCompletionEvent; |
| 34 | + |
| 35 | + fn handle_event(&mut self, event: Self::Event, _: Option<Instant>) -> Option<Instant> { |
| 36 | + self.pending = Some((event.doc, event.cursor)); |
| 37 | + Some(Instant::now() + self.config.load().editor.inline_completion_timeout) |
| 38 | + } |
| 39 | + |
| 40 | + fn finish_debounce(&mut self) { |
| 41 | + let Some((doc_id, cursor)) = self.pending.take() else { |
| 42 | + return; |
| 43 | + }; |
| 44 | + |
| 45 | + job::dispatch_blocking(move |editor, _| { |
| 46 | + let Some(doc) = editor.document(doc_id) else { |
| 47 | + return; |
| 48 | + }; |
| 49 | + |
| 50 | + let Some(ls) = doc |
| 51 | + .language_servers_with_feature(LanguageServerFeature::InlineCompletion) |
| 52 | + .next() |
| 53 | + else { |
| 54 | + return; |
| 55 | + }; |
| 56 | + |
| 57 | + let offset_encoding = ls.offset_encoding(); |
| 58 | + let pos = helix_lsp::util::pos_to_lsp_pos(doc.text(), cursor, offset_encoding); |
| 59 | + let context = lsp::InlineCompletionContext { |
| 60 | + trigger_kind: lsp::InlineCompletionTriggerKind::Automatic, |
| 61 | + selected_completion_info: None, |
| 62 | + }; |
| 63 | + |
| 64 | + let Some(fut) = ls.inline_completion(doc.identifier(), pos, context, None) else { |
| 65 | + return; |
| 66 | + }; |
| 67 | + |
| 68 | + tokio::spawn(async move { |
| 69 | + let Ok(Some(resp)) = fut.await else { return }; |
| 70 | + let items = match resp { |
| 71 | + lsp::InlineCompletionResponse::Array(v) => v, |
| 72 | + lsp::InlineCompletionResponse::List(l) => l.items, |
| 73 | + }; |
| 74 | + let Some(item) = items.into_iter().next() else { |
| 75 | + return; |
| 76 | + }; |
| 77 | + |
| 78 | + job::dispatch(move |editor, _| { |
| 79 | + let Some(doc) = editor.documents.get_mut(&doc_id) else { |
| 80 | + return; |
| 81 | + }; |
| 82 | + doc.inline_completion = Some(InlineAnnotation::new(cursor, item.insert_text)); |
| 83 | + }) |
| 84 | + .await; |
| 85 | + }); |
| 86 | + }); |
| 87 | + } |
| 88 | +} |
| 89 | + |
| 90 | +pub(super) fn register_hooks(handlers: &Handlers) { |
| 91 | + let tx = handlers.inline_completions.clone(); |
| 92 | + |
| 93 | + register_hook!(move |event: &mut DocumentDidChange<'_>| { |
| 94 | + event.doc.inline_completion = None; |
| 95 | + if event.ghost_transaction { |
| 96 | + return Ok(()); |
| 97 | + } |
| 98 | + |
| 99 | + let cursor = event |
| 100 | + .doc |
| 101 | + .selection(event.view) |
| 102 | + .primary() |
| 103 | + .cursor(event.doc.text().slice(..)); |
| 104 | + send_blocking( |
| 105 | + &tx, |
| 106 | + InlineCompletionEvent { |
| 107 | + doc: event.doc.id(), |
| 108 | + cursor, |
| 109 | + }, |
| 110 | + ); |
| 111 | + Ok(()) |
| 112 | + }); |
| 113 | +} |
0 commit comments