Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'helix-term/src/handlers/completion/word.rs')
-rw-r--r--helix-term/src/handlers/completion/word.rs134
1 files changed, 134 insertions, 0 deletions
diff --git a/helix-term/src/handlers/completion/word.rs b/helix-term/src/handlers/completion/word.rs
new file mode 100644
index 00000000..aa204bf8
--- /dev/null
+++ b/helix-term/src/handlers/completion/word.rs
@@ -0,0 +1,134 @@
+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,
+ ..
+ })
+ )
+ });
+ }
+}