Mercurial > forks > helix
view helix-term/src/handlers/completion/word.rs @ 6833:a1c38f138388 draft
(13206) word completion
(grafted from 1efa919fb2b9841da497a9f29b492bf4389858f3)
author | Michael Davis <mcarsondavis@gmail.com> |
---|---|
date | Wed, 14 May 2025 10:23:43 -0400 |
parents | |
children |
line wrap: on
line source
use std::{borrow::Cow, sync::Arc}; use helix_core::{ self as core, chars::char_is_word, completion::CompletionProvider, movement, Transaction, }; use helix_event::TaskHandle; use helix_stdx::rope::RopeSliceExt as _; use helix_view::{ document::SavePoint, handlers::completion::ResponseContext, Document, Editor, ViewId, }; use super::{request::TriggerKind, CompletionItem, CompletionItems, CompletionResponse, Trigger}; const COMPLETION_KIND: &str = "word"; pub(super) fn completion( editor: &Editor, trigger: Trigger, handle: TaskHandle, savepoint: Arc<SavePoint>, ) -> Option<impl FnOnce() -> CompletionResponse> { if !doc!(editor).word_completion_enabled() { return None; } let config = editor.config().word_completion; let doc_config = doc!(editor) .language_config() .and_then(|config| config.word_completion); let trigger_length = doc_config .and_then(|c| c.trigger_length) .unwrap_or(config.trigger_length) .get() as usize; let (view, doc) = current_ref!(editor); let rope = doc.text().clone(); let word_index = editor.handlers.word_index().clone(); let text = doc.text().slice(..); let selection = doc.selection(view.id).clone(); let pos = selection.primary().cursor(text); let cursor = movement::move_prev_word_start(text, core::Range::point(pos), 1); if cursor.head == pos { return None; } if trigger.kind != TriggerKind::Manual && text .slice(cursor.head..) .graphemes() .take(trigger_length) .take_while(|g| g.chars().all(char_is_word)) .count() != trigger_length { return None; } let typed_word_range = cursor.head..pos; let typed_word = text.slice(typed_word_range.clone()); let edit_diff = if typed_word .char(typed_word.len_chars().saturating_sub(1)) .is_whitespace() { 0 } else { typed_word.len_chars() }; if handle.is_canceled() { return None; } let future = move || { let text = rope.slice(..); let typed_word: Cow<_> = text.slice(typed_word_range).into(); let items = word_index .matches(&typed_word) .into_iter() .filter(|word| word.as_str() != typed_word.as_ref()) .map(|word| { let transaction = Transaction::change_by_selection(&rope, &selection, |range| { let cursor = range.cursor(text); (cursor - edit_diff, cursor, Some((&word).into())) }); CompletionItem::Other(core::CompletionItem { transaction, label: word.into(), kind: Cow::Borrowed(COMPLETION_KIND), documentation: None, provider: CompletionProvider::Word, }) }) .collect(); CompletionResponse { items: CompletionItems::Other(items), provider: CompletionProvider::Word, context: ResponseContext { is_incomplete: false, priority: 0, savepoint, }, } }; Some(future) } pub(super) fn retain_valid_completions( trigger: Trigger, doc: &Document, view_id: ViewId, items: &mut Vec<CompletionItem>, ) { if trigger.kind == TriggerKind::Manual { return; } let text = doc.text().slice(..); let cursor = doc.selection(view_id).primary().cursor(text); if text .get_char(cursor.saturating_sub(1)) .is_some_and(|ch| ch.is_whitespace()) { items.retain(|item| { !matches!( item, CompletionItem::Other(core::CompletionItem { provider: CompletionProvider::Word, .. }) ) }); } }