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.rs159
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));
}