diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 1d305cffb0..37fcb6358e 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -346,6 +346,10 @@ impl CompletionProvider for SlashCommandCompletionProvider { false } } + + fn sort_completions(&self) -> bool { + false + } } impl SlashCommandLine { diff --git a/crates/assistant/src/slash_command/tab_command.rs b/crates/assistant/src/slash_command/tab_command.rs index db9cd76cdc..392f7c65d8 100644 --- a/crates/assistant/src/slash_command/tab_command.rs +++ b/crates/assistant/src/slash_command/tab_command.rs @@ -62,6 +62,16 @@ impl SlashCommand for TabSlashCommand { if has_all_tabs_completion_item { return Task::ready(Ok(Vec::new())); } + + let active_item_path = workspace.as_ref().and_then(|workspace| { + workspace + .update(cx, |workspace, cx| { + let snapshot = active_item_buffer(workspace, cx).ok()?; + snapshot.resolve_file_path(cx, true) + }) + .ok() + .flatten() + }); let current_query = arguments.last().cloned().unwrap_or_default(); let tab_items_search = tab_items_for_queries(workspace, &[current_query], cancel, false, cx); @@ -73,6 +83,9 @@ impl SlashCommand for TabSlashCommand { if argument_set.contains(&path_string) { return None; } + if active_item_path.is_some() && active_item_path == path { + return None; + } Some(ArgumentCompletion { label: path_string.clone().into(), new_text: path_string, @@ -81,15 +94,26 @@ impl SlashCommand for TabSlashCommand { }) }); - Ok(Some(ArgumentCompletion { - label: ALL_TABS_COMPLETION_ITEM.into(), - new_text: ALL_TABS_COMPLETION_ITEM.to_owned(), - replace_previous_arguments: false, - run_command: true, - }) - .into_iter() - .chain(tab_completion_items) - .collect::>()) + let active_item_completion = active_item_path.as_deref().map(|active_item_path| { + let path_string = active_item_path.to_string_lossy().to_string(); + ArgumentCompletion { + label: path_string.clone().into(), + new_text: path_string, + replace_previous_arguments: false, + run_command, + } + }); + + Ok(active_item_completion + .into_iter() + .chain(Some(ArgumentCompletion { + label: ALL_TABS_COMPLETION_ITEM.into(), + new_text: ALL_TABS_COMPLETION_ITEM.to_owned(), + replace_previous_arguments: false, + run_command: true, + })) + .chain(tab_completion_items) + .collect()) }) } @@ -162,19 +186,7 @@ fn tab_items_for_queries( .context("no workspace")? .update(&mut cx, |workspace, cx| { if strict_match && empty_query { - let active_editor = workspace - .active_item(cx) - .context("no active item")? - .downcast::() - .context("active item is not an editor")?; - let snapshot = active_editor - .read(cx) - .buffer() - .read(cx) - .as_singleton() - .context("active editor is not a singleton buffer")? - .read(cx) - .snapshot(); + let snapshot = active_item_buffer(workspace, cx)?; let full_path = snapshot.resolve_file_path(cx, true); return anyhow::Ok(vec![(full_path, snapshot, 0)]); } @@ -279,3 +291,23 @@ fn tab_items_for_queries( .await }) } + +fn active_item_buffer( + workspace: &mut Workspace, + cx: &mut ui::ViewContext, +) -> anyhow::Result { + let active_editor = workspace + .active_item(cx) + .context("no active item")? + .downcast::() + .context("active item is not an editor")?; + let snapshot = active_editor + .read(cx) + .buffer() + .read(cx) + .as_singleton() + .context("active editor is not a singleton buffer")? + .read(cx) + .snapshot(); + Ok(snapshot) +} diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 5007513f68..84a81cb9c9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -900,6 +900,7 @@ enum ContextMenuOrigin { #[derive(Clone)] struct CompletionsMenu { id: CompletionId, + sort_completions: bool, initial_position: Anchor, buffer: Model, completions: Arc>>, @@ -1225,55 +1226,57 @@ impl CompletionsMenu { } let completions = self.completions.read(); - matches.sort_unstable_by_key(|mat| { - // We do want to strike a balance here between what the language server tells us - // to sort by (the sort_text) and what are "obvious" good matches (i.e. when you type - // `Creat` and there is a local variable called `CreateComponent`). - // So what we do is: we bucket all matches into two buckets - // - Strong matches - // - Weak matches - // Strong matches are the ones with a high fuzzy-matcher score (the "obvious" matches) - // and the Weak matches are the rest. - // - // For the strong matches, we sort by the language-servers score first and for the weak - // matches, we prefer our fuzzy finder first. - // - // The thinking behind that: it's useless to take the sort_text the language-server gives - // us into account when it's obviously a bad match. + if self.sort_completions { + matches.sort_unstable_by_key(|mat| { + // We do want to strike a balance here between what the language server tells us + // to sort by (the sort_text) and what are "obvious" good matches (i.e. when you type + // `Creat` and there is a local variable called `CreateComponent`). + // So what we do is: we bucket all matches into two buckets + // - Strong matches + // - Weak matches + // Strong matches are the ones with a high fuzzy-matcher score (the "obvious" matches) + // and the Weak matches are the rest. + // + // For the strong matches, we sort by the language-servers score first and for the weak + // matches, we prefer our fuzzy finder first. + // + // The thinking behind that: it's useless to take the sort_text the language-server gives + // us into account when it's obviously a bad match. - #[derive(PartialEq, Eq, PartialOrd, Ord)] - enum MatchScore<'a> { - Strong { - sort_text: Option<&'a str>, - score: Reverse>, - sort_key: (usize, &'a str), - }, - Weak { - score: Reverse>, - sort_text: Option<&'a str>, - sort_key: (usize, &'a str), - }, - } - - let completion = &completions[mat.candidate_id]; - let sort_key = completion.sort_key(); - let sort_text = completion.lsp_completion.sort_text.as_deref(); - let score = Reverse(OrderedFloat(mat.score)); - - if mat.score >= 0.2 { - MatchScore::Strong { - sort_text, - score, - sort_key, + #[derive(PartialEq, Eq, PartialOrd, Ord)] + enum MatchScore<'a> { + Strong { + sort_text: Option<&'a str>, + score: Reverse>, + sort_key: (usize, &'a str), + }, + Weak { + score: Reverse>, + sort_text: Option<&'a str>, + sort_key: (usize, &'a str), + }, } - } else { - MatchScore::Weak { - score, - sort_text, - sort_key, + + let completion = &completions[mat.candidate_id]; + let sort_key = completion.sort_key(); + let sort_text = completion.lsp_completion.sort_text.as_deref(); + let score = Reverse(OrderedFloat(mat.score)); + + if mat.score >= 0.2 { + MatchScore::Strong { + sort_text, + score, + sort_key, + } + } else { + MatchScore::Weak { + score, + sort_text, + sort_key, + } } - } - }); + }); + } for mat in &mut matches { let completion = &completions[mat.candidate_id]; @@ -4105,6 +4108,7 @@ impl Editor { trigger_kind, }; let completions = provider.completions(&buffer, buffer_position, completion_context, cx); + let sort_completions = provider.sort_completions(); let id = post_inc(&mut self.next_completion_id); let task = cx.spawn(|this, mut cx| { @@ -4116,6 +4120,7 @@ impl Editor { let menu = if let Some(completions) = completions { let mut menu = CompletionsMenu { id, + sort_completions, initial_position: position, match_candidates: completions .iter() @@ -12045,6 +12050,10 @@ pub trait CompletionProvider { trigger_in_words: bool, cx: &mut ViewContext, ) -> bool; + + fn sort_completions(&self) -> bool { + true + } } fn snippet_completions(