Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/handlers/completion.rs')
| -rw-r--r-- | helix-term/src/handlers/completion.rs | 271 |
1 files changed, 0 insertions, 271 deletions
diff --git a/helix-term/src/handlers/completion.rs b/helix-term/src/handlers/completion.rs deleted file mode 100644 index 22bb0ce0..00000000 --- a/helix-term/src/handlers/completion.rs +++ /dev/null @@ -1,271 +0,0 @@ -use std::collections::HashMap; - -use helix_core::chars::char_is_word; -use helix_core::completion::CompletionProvider; -use helix_core::syntax::config::LanguageServerFeature; -use helix_event::{register_hook, TaskHandle}; -use helix_lsp::lsp; -use helix_stdx::rope::RopeSliceExt; -use helix_view::document::Mode; -use helix_view::handlers::completion::{CompletionEvent, ResponseContext}; -use helix_view::Editor; -use tokio::task::JoinSet; - -use crate::commands; -use crate::compositor::Compositor; -use crate::events::{OnModeSwitch, PostCommand, PostInsertChar}; -use crate::handlers::completion::request::{request_incomplete_completion_list, Trigger}; -use crate::job::dispatch; -use crate::keymap::MappableCommand; -use crate::ui::lsp::signature_help::SignatureHelp; -use crate::ui::{self, Popup}; - -use super::Handlers; - -pub use item::{CompletionItem, CompletionItems, CompletionResponse, LspCompletionItem}; -pub use request::CompletionHandler; -pub use resolve::ResolveHandler; - -mod item; -mod path; -mod request; -mod resolve; -mod word; - -async fn handle_response( - requests: &mut JoinSet<CompletionResponse>, - is_incomplete: bool, -) -> Option<CompletionResponse> { - loop { - let response = requests.join_next().await?.unwrap(); - if !is_incomplete && !response.context.is_incomplete && response.items.is_empty() { - continue; - } - return Some(response); - } -} - -async fn replace_completions( - handle: TaskHandle, - mut requests: JoinSet<CompletionResponse>, - is_incomplete: bool, -) { - while let Some(mut response) = handle_response(&mut requests, is_incomplete).await { - let handle = handle.clone(); - dispatch(move |editor, compositor| { - let editor_view = compositor.find::<ui::EditorView>().unwrap(); - let Some(completion) = &mut editor_view.completion else { - return; - }; - if handle.is_canceled() { - log::info!("dropping outdated completion response"); - return; - } - - completion.replace_provider_completions(&mut response, is_incomplete); - if completion.is_empty() { - editor_view.clear_completion(editor); - // clearing completions might mean we want to immediately re-request them (usually - // this occurs if typing a trigger char) - trigger_auto_completion(editor, false); - } else { - editor - .handlers - .completions - .active_completions - .insert(response.provider, response.context); - } - }) - .await; - } -} - -fn show_completion( - editor: &mut Editor, - compositor: &mut Compositor, - mut items: Vec<CompletionItem>, - context: HashMap<CompletionProvider, ResponseContext>, - trigger: Trigger, -) { - let (view, doc) = current_ref!(editor); - // check if the completion request is stale. - // - // Completions are completed asynchronously and therefore the user could - //switch document/view or leave insert mode. In all of thoise cases the - // completion should be discarded - if editor.mode != Mode::Insert || view.id != trigger.view || doc.id() != trigger.doc { - return; - } - - let size = compositor.size(); - let ui = compositor.find::<ui::EditorView>().unwrap(); - if ui.completion.is_some() { - return; - } - word::retain_valid_completions(trigger, doc, view.id, &mut items); - editor.handlers.completions.active_completions = context; - - let completion_area = ui.set_completion(editor, items, trigger.pos, size); - let signature_help_area = compositor - .find_id::<Popup<SignatureHelp>>(SignatureHelp::ID) - .map(|signature_help| signature_help.area(size, editor)); - // Delete the signature help popup if they intersect. - if matches!((completion_area, signature_help_area),(Some(a), Some(b)) if a.intersects(b)) { - compositor.remove(SignatureHelp::ID); - } -} - -pub fn trigger_auto_completion(editor: &Editor, trigger_char_only: bool) { - let config = editor.config.load(); - if !config.auto_completion { - return; - } - let (view, doc): (&helix_view::View, &helix_view::Document) = current_ref!(editor); - let mut text = doc.text().slice(..); - let cursor = doc.selection(view.id).primary().cursor(text); - text = doc.text().slice(..cursor); - - let is_trigger_char = doc - .language_servers_with_feature(LanguageServerFeature::Completion) - .any(|ls| { - matches!(&ls.capabilities().completion_provider, Some(lsp::CompletionOptions { - trigger_characters: Some(triggers), - .. - }) if triggers.iter().any(|trigger| text.ends_with(trigger))) - }); - - let cursor_char = text - .get_bytes_at(text.len_bytes()) - .and_then(|t| t.reversed().next()); - - #[cfg(windows)] - let is_path_completion_trigger = matches!(cursor_char, Some(b'/' | b'\\')); - #[cfg(not(windows))] - let is_path_completion_trigger = matches!(cursor_char, Some(b'/')); - - let handler = &editor.handlers.completions; - if is_trigger_char || (is_path_completion_trigger && doc.path_completion_enabled()) { - handler.event(CompletionEvent::TriggerChar { - cursor, - doc: doc.id(), - view: view.id, - }); - return; - } - - let is_auto_trigger = !trigger_char_only - && doc - .text() - .chars_at(cursor) - .reversed() - .take(config.completion_trigger_len as usize) - .all(char_is_word); - - if is_auto_trigger { - handler.event(CompletionEvent::AutoTrigger { - cursor, - doc: doc.id(), - view: view.id, - }); - } -} - -fn update_completion_filter(cx: &mut commands::Context, c: Option<char>) { - cx.callback.push(Box::new(move |compositor, cx| { - let editor_view = compositor.find::<ui::EditorView>().unwrap(); - if let Some(completion) = &mut editor_view.completion { - completion.update_filter(c); - if completion.is_empty() || c.is_some_and(|c| !char_is_word(c)) { - editor_view.clear_completion(cx.editor); - // clearing completions might mean we want to immediately rerequest them (usually - // this occurs if typing a trigger char) - if c.is_some() { - trigger_auto_completion(cx.editor, false); - } - } else { - let handle = cx.editor.handlers.completions.request_controller.restart(); - request_incomplete_completion_list(cx.editor, handle) - } - } - })) -} - -fn clear_completions(cx: &mut commands::Context) { - cx.callback.push(Box::new(|compositor, cx| { - let editor_view = compositor.find::<ui::EditorView>().unwrap(); - editor_view.clear_completion(cx.editor); - })) -} - -fn completion_post_command_hook( - PostCommand { command, cx }: &mut PostCommand<'_, '_>, -) -> anyhow::Result<()> { - if cx.editor.mode == Mode::Insert { - if cx.editor.last_completion.is_some() { - match command { - MappableCommand::Static { - name: "delete_word_forward" | "delete_char_forward" | "completion", - .. - } => (), - MappableCommand::Static { - name: "delete_char_backward", - .. - } => update_completion_filter(cx, None), - _ => clear_completions(cx), - } - } else { - let event = match command { - MappableCommand::Static { - name: "delete_char_backward" | "delete_word_forward" | "delete_char_forward", - .. - } => { - let (view, doc) = current!(cx.editor); - let primary_cursor = doc - .selection(view.id) - .primary() - .cursor(doc.text().slice(..)); - CompletionEvent::DeleteText { - cursor: primary_cursor, - } - } - // hacks: some commands are handeled elsewhere and we don't want to - // cancel in that case - MappableCommand::Static { - name: "completion" | "insert_mode" | "append_mode", - .. - } => return Ok(()), - _ => CompletionEvent::Cancel, - }; - cx.editor.handlers.completions.event(event); - } - } - Ok(()) -} - -pub(super) fn register_hooks(_handlers: &Handlers) { - register_hook!(move |event: &mut PostCommand<'_, '_>| completion_post_command_hook(event)); - - register_hook!(move |event: &mut OnModeSwitch<'_, '_>| { - if event.old_mode == Mode::Insert { - event - .cx - .editor - .handlers - .completions - .event(CompletionEvent::Cancel); - clear_completions(event.cx); - } else if event.new_mode == Mode::Insert { - trigger_auto_completion(event.cx.editor, false) - } - Ok(()) - }); - - register_hook!(move |event: &mut PostInsertChar<'_, '_>| { - if event.cx.editor.last_completion.is_some() { - update_completion_filter(event.cx, Some(event.c)) - } else { - trigger_auto_completion(event.cx.editor, false); - } - Ok(()) - }); -} |