action_completion_provider.rs

  1use collections::HashMap;
  2use command_palette;
  3use editor::{CompletionProvider, Editor};
  4use fuzzy::StringMatchCandidate;
  5use gpui::{Context, Entity, SharedString, Window};
  6use language::{self, ToOffset};
  7use project::{self, CompletionDisplayOptions};
  8
  9pub struct ActionCompletionProvider {
 10    action_names: Vec<&'static str>,
 11    humanized_names: HashMap<&'static str, SharedString>,
 12}
 13
 14impl ActionCompletionProvider {
 15    pub fn new(
 16        action_names: Vec<&'static str>,
 17        humanized_names: HashMap<&'static str, SharedString>,
 18    ) -> Self {
 19        Self {
 20            action_names,
 21            humanized_names,
 22        }
 23    }
 24}
 25
 26impl CompletionProvider for ActionCompletionProvider {
 27    fn completions(
 28        &self,
 29        buffer: &Entity<language::Buffer>,
 30        buffer_position: language::Anchor,
 31        _trigger: editor::CompletionContext,
 32        _window: &mut Window,
 33        cx: &mut Context<Editor>,
 34    ) -> gpui::Task<anyhow::Result<Vec<project::CompletionResponse>>> {
 35        let buffer = buffer.read(cx);
 36        let mut count_back = 0;
 37
 38        for char in buffer.reversed_chars_at(buffer_position) {
 39            if char.is_ascii_alphanumeric() || char == '_' || char == ':' {
 40                count_back += 1;
 41            } else {
 42                break;
 43            }
 44        }
 45
 46        let start_anchor = buffer.anchor_before(
 47            buffer_position
 48                .to_offset(&buffer)
 49                .saturating_sub(count_back),
 50        );
 51
 52        let replace_range = start_anchor..buffer_position;
 53        let snapshot = buffer.text_snapshot();
 54        let query: String = snapshot.text_for_range(replace_range.clone()).collect();
 55        let normalized_query = command_palette::normalize_action_query(&query);
 56
 57        let candidates: Vec<StringMatchCandidate> = self
 58            .action_names
 59            .iter()
 60            .enumerate()
 61            .map(|(ix, &name)| {
 62                let humanized = self
 63                    .humanized_names
 64                    .get(name)
 65                    .cloned()
 66                    .unwrap_or_else(|| name.into());
 67                StringMatchCandidate::new(ix, &humanized)
 68            })
 69            .collect();
 70
 71        let executor = cx.background_executor().clone();
 72        let executor_for_fuzzy = executor.clone();
 73        let action_names = self.action_names.clone();
 74        let humanized_names = self.humanized_names.clone();
 75
 76        executor.spawn(async move {
 77            let matches = fuzzy::match_strings(
 78                &candidates,
 79                &normalized_query,
 80                true,
 81                true,
 82                action_names.len(),
 83                &Default::default(),
 84                executor_for_fuzzy,
 85            )
 86            .await;
 87
 88            let completions: Vec<project::Completion> = matches
 89                .iter()
 90                .take(50)
 91                .map(|m| {
 92                    let action_name = action_names[m.candidate_id];
 93                    let humanized = humanized_names
 94                        .get(action_name)
 95                        .cloned()
 96                        .unwrap_or_else(|| action_name.into());
 97
 98                    project::Completion {
 99                        replace_range: replace_range.clone(),
100                        label: language::CodeLabel::plain(humanized.to_string(), None),
101                        new_text: action_name.to_string(),
102                        documentation: None,
103                        source: project::CompletionSource::Custom,
104                        icon_path: None,
105                        match_start: None,
106                        snippet_deduplication_key: None,
107                        insert_text_mode: None,
108                        confirm: None,
109                    }
110                })
111                .collect();
112
113            Ok(vec![project::CompletionResponse {
114                completions,
115                display_options: CompletionDisplayOptions {
116                    dynamic_width: true,
117                },
118                is_incomplete: false,
119            }])
120        })
121    }
122
123    fn is_completion_trigger(
124        &self,
125        _buffer: &Entity<language::Buffer>,
126        _position: language::Anchor,
127        text: &str,
128        _trigger_in_words: bool,
129        _cx: &mut Context<Editor>,
130    ) -> bool {
131        text.chars().last().is_some_and(|last_char| {
132            last_char.is_ascii_alphanumeric() || last_char == '_' || last_char == ':'
133        })
134    }
135}