diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index e602274b7d..a4f040ff9d 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -73,57 +73,58 @@ impl SlashCommandCompletionProvider { let command_name = command_name.to_string(); let editor = self.editor.clone(); let workspace = self.workspace.clone(); - let executor = cx.background_executor().clone(); - executor.clone().spawn(async move { + cx.spawn(|mut cx| async move { let matches = match_strings( &candidates, &command_name, true, usize::MAX, &Default::default(), - executor, + cx.background_executor().clone(), ) .await; - Ok(matches - .into_iter() - .filter_map(|mat| { - let command = commands.command(&mat.string)?; - let mut new_text = mat.string.clone(); - let requires_argument = command.requires_argument(); - if requires_argument { - new_text.push(' '); - } + cx.update(|cx| { + matches + .into_iter() + .filter_map(|mat| { + let command = commands.command(&mat.string)?; + let mut new_text = mat.string.clone(); + let requires_argument = command.requires_argument(); + if requires_argument { + new_text.push(' '); + } - Some(project::Completion { - old_range: name_range.clone(), - documentation: Some(Documentation::SingleLine(command.description())), - new_text, - label: CodeLabel::plain(mat.string.clone(), None), - server_id: LanguageServerId(0), - lsp_completion: Default::default(), - confirm: (!requires_argument).then(|| { - let command_name = mat.string.clone(); - let command_range = command_range.clone(); - let editor = editor.clone(); - let workspace = workspace.clone(); - Arc::new(move |cx: &mut WindowContext| { - editor - .update(cx, |editor, cx| { - editor.run_command( - command_range.clone(), - &command_name, - None, - workspace.clone(), - cx, - ); - }) - .ok(); - }) as Arc<_> - }), + Some(project::Completion { + old_range: name_range.clone(), + documentation: Some(Documentation::SingleLine(command.description())), + new_text, + label: command.label(cx), + server_id: LanguageServerId(0), + lsp_completion: Default::default(), + confirm: (!requires_argument).then(|| { + let command_name = mat.string.clone(); + let command_range = command_range.clone(); + let editor = editor.clone(); + let workspace = workspace.clone(); + Arc::new(move |cx: &mut WindowContext| { + editor + .update(cx, |editor, cx| { + editor.run_command( + command_range.clone(), + &command_name, + None, + workspace.clone(), + cx, + ); + }) + .ok(); + }) as Arc<_> + }), + }) }) - }) - .collect()) + .collect() + }) }) } diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant/src/slash_command/search_command.rs index 267201bac5..9a4dae66e9 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/crates/assistant/src/slash_command/search_command.rs @@ -2,7 +2,7 @@ use super::{file_command::FilePlaceholder, SlashCommand, SlashCommandOutput}; use anyhow::Result; use assistant_slash_command::SlashCommandOutputSection; use gpui::{AppContext, Task, WeakView}; -use language::{LineEnding, LspAdapterDelegate}; +use language::{CodeLabel, HighlightId, LineEnding, LspAdapterDelegate}; use semantic_index::SemanticIndex; use std::{ fmt::Write, @@ -20,6 +20,17 @@ impl SlashCommand for SearchSlashCommand { "search".into() } + fn label(&self, cx: &AppContext) -> CodeLabel { + let mut label = CodeLabel::default(); + label.push_str("search ", None); + label.push_str( + "--n", + cx.theme().syntax().highlight_id("comment").map(HighlightId), + ); + label.filter_range = 0.."search".len(); + label + } + fn description(&self) -> String { "semantically search files".into() } @@ -54,12 +65,27 @@ impl SlashCommand for SearchSlashCommand { let Some(argument) = argument else { return Task::ready(Err(anyhow::anyhow!("missing search query"))); }; - if argument.is_empty() { + + let mut limit = None; + let mut query = String::new(); + for part in argument.split(' ') { + if let Some(parameter) = part.strip_prefix("--") { + if let Ok(count) = parameter.parse::() { + limit = Some(count); + continue; + } + } + + query.push_str(part); + query.push(' '); + } + query.pop(); + + if query.is_empty() { return Task::ready(Err(anyhow::anyhow!("missing search query"))); } let project = workspace.read(cx).project().clone(); - let argument = argument.to_string(); let fs = project.read(cx).fs().clone(); let project_index = cx.update_global(|index: &mut SemanticIndex, cx| index.project_index(project, cx)); @@ -67,7 +93,7 @@ impl SlashCommand for SearchSlashCommand { cx.spawn(|cx| async move { let results = project_index .read_with(&cx, |project_index, cx| { - project_index.search(argument.clone(), 5, cx) + project_index.search(query.clone(), limit.unwrap_or(5), cx) })? .await?; @@ -92,7 +118,7 @@ impl SlashCommand for SearchSlashCommand { let output = cx .background_executor() .spawn(async move { - let mut text = format!("Search results for {argument}:\n"); + let mut text = format!("Search results for {query}:\n"); let mut sections = Vec::new(); for (result, full_path, file_content) in loaded_results { let range_start = result.range.start.min(file_content.len()); @@ -140,7 +166,7 @@ impl SlashCommand for SearchSlashCommand { }); } - let argument = SharedString::from(argument); + let query = SharedString::from(query); sections.push(SlashCommandOutputSection { range: 0..text.len(), render_placeholder: Arc::new(move |id, unfold, _cx| { @@ -148,7 +174,7 @@ impl SlashCommand for SearchSlashCommand { .style(ButtonStyle::Filled) .layer(ElevationIndex::ElevatedSurface) .child(Icon::new(IconName::MagnifyingGlass)) - .child(Label::new(argument.clone())) + .child(Label::new(query.clone())) .on_click(move |_, cx| unfold(cx)) .into_any_element() }), diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index f3b039accf..1692acaf63 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -2,7 +2,7 @@ mod slash_command_registry; use anyhow::Result; use gpui::{AnyElement, AppContext, ElementId, Task, WeakView, WindowContext}; -use language::LspAdapterDelegate; +use language::{CodeLabel, LspAdapterDelegate}; pub use slash_command_registry::*; use std::{ ops::Range, @@ -16,6 +16,9 @@ pub fn init(cx: &mut AppContext) { pub trait SlashCommand: 'static + Send + Sync { fn name(&self) -> String; + fn label(&self, _cx: &AppContext) -> CodeLabel { + CodeLabel::plain(self.name(), None) + } fn description(&self) -> String; fn tooltip_text(&self) -> String; fn complete_argument( diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 37839858fc..5ad8e60315 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -535,7 +535,7 @@ async fn try_fetch_server_binary binary } -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct CodeLabel { /// The text to display. pub text: String, @@ -1540,6 +1540,15 @@ impl CodeLabel { } result } + + pub fn push_str(&mut self, text: &str, highlight: Option) { + let start_ix = self.text.len(); + self.text.push_str(text); + let end_ix = self.text.len(); + if let Some(highlight) = highlight { + self.runs.push((start_ix..end_ix, highlight)); + } + } } impl Ord for LanguageMatcher { diff --git a/crates/theme/src/styles/syntax.rs b/crates/theme/src/styles/syntax.rs index eff2bbc44e..8016445c16 100644 --- a/crates/theme/src/styles/syntax.rs +++ b/crates/theme/src/styles/syntax.rs @@ -44,6 +44,11 @@ impl SyntaxTheme { self.get(name).color.unwrap_or_default() } + pub fn highlight_id(&self, name: &str) -> Option { + let ix = self.highlights.iter().position(|entry| entry.0 == name)?; + Some(ix as u32) + } + /// Returns a new [`Arc`] with the given syntax styles merged in. pub fn merge(base: Arc, user_syntax_styles: Vec<(String, HighlightStyle)>) -> Arc { if user_syntax_styles.is_empty() {