diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index 5cf3c39b98..34b0e5cfd4 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -35,9 +35,9 @@ use semantic_index::{CloudEmbeddingProvider, SemanticIndex}; use serde::{Deserialize, Serialize}; use settings::{update_settings_file, Settings, SettingsStore}; use slash_command::{ - active_command, default_command, diagnostics_command, docs_command, fetch_command, - file_command, now_command, project_command, prompt_command, search_command, symbols_command, - tabs_command, terminal_command, workflow_command, + default_command, diagnostics_command, docs_command, fetch_command, file_command, now_command, + project_command, prompt_command, search_command, symbols_command, tabs_command, + terminal_command, workflow_command, }; use std::sync::Arc; pub(crate) use streaming_diff::*; @@ -282,7 +282,6 @@ fn update_active_language_model_from_settings(cx: &mut AppContext) { fn register_slash_commands(prompt_builder: Option>, cx: &mut AppContext) { let slash_command_registry = SlashCommandRegistry::global(cx); slash_command_registry.register_command(file_command::FileSlashCommand, true); - slash_command_registry.register_command(active_command::ActiveSlashCommand, true); slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true); slash_command_registry.register_command(tabs_command::TabsSlashCommand, true); slash_command_registry.register_command(project_command::ProjectSlashCommand, true); diff --git a/crates/assistant/src/context.rs b/crates/assistant/src/context.rs index 2faa2a2092..66c56fed50 100644 --- a/crates/assistant/src/context.rs +++ b/crates/assistant/src/context.rs @@ -2522,11 +2522,7 @@ pub struct SavedContextMetadata { #[cfg(test)] mod tests { use super::*; - use crate::{ - assistant_panel, prompt_library, - slash_command::{active_command, file_command}, - MessageId, - }; + use crate::{assistant_panel, prompt_library, slash_command::file_command, MessageId}; use assistant_slash_command::{ArgumentCompletion, SlashCommand}; use fs::FakeFs; use gpui::{AppContext, TestAppContext, WeakView}; @@ -2883,7 +2879,6 @@ mod tests { let slash_command_registry = cx.update(SlashCommandRegistry::default_global); slash_command_registry.register_command(file_command::FileSlashCommand, false); - slash_command_registry.register_command(active_command::ActiveSlashCommand, false); let registry = Arc::new(LanguageRegistry::test(cx.executor())); let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap()); diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 0fd7dbcd85..847a1bd39b 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -17,7 +17,6 @@ use std::{ use ui::ActiveTheme; use workspace::Workspace; -pub mod active_command; pub mod default_command; pub mod diagnostics_command; pub mod docs_command; diff --git a/crates/assistant/src/slash_command/active_command.rs b/crates/assistant/src/slash_command/active_command.rs deleted file mode 100644 index ad11e0ee08..0000000000 --- a/crates/assistant/src/slash_command/active_command.rs +++ /dev/null @@ -1,102 +0,0 @@ -use super::{ - diagnostics_command::write_single_file_diagnostics, - file_command::{build_entry_output_section, codeblock_fence_for_path}, - SlashCommand, SlashCommandOutput, -}; -use anyhow::{anyhow, Result}; -use assistant_slash_command::ArgumentCompletion; -use editor::Editor; -use gpui::{AppContext, Task, WeakView}; -use language::LspAdapterDelegate; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; -use ui::WindowContext; -use workspace::Workspace; - -pub(crate) struct ActiveSlashCommand; - -impl SlashCommand for ActiveSlashCommand { - fn name(&self) -> String { - "active".into() - } - - fn description(&self) -> String { - "insert active tab".into() - } - - fn menu_text(&self) -> String { - "Insert Active Tab".into() - } - - fn complete_argument( - self: Arc, - _query: String, - _cancel: Arc, - _workspace: Option>, - _cx: &mut AppContext, - ) -> Task>> { - Task::ready(Err(anyhow!("this command does not require argument"))) - } - - fn requires_argument(&self) -> bool { - false - } - - fn run( - self: Arc, - _argument: Option<&str>, - workspace: WeakView, - _delegate: Option>, - cx: &mut WindowContext, - ) -> Task> { - let output = workspace.update(cx, |workspace, cx| { - let Some(active_item) = workspace.active_item(cx) else { - return Task::ready(Err(anyhow!("no active tab"))); - }; - let Some(buffer) = active_item - .downcast::() - .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton()) - else { - return Task::ready(Err(anyhow!("active tab is not an editor"))); - }; - - let snapshot = buffer.read(cx).snapshot(); - let path = snapshot.resolve_file_path(cx, true); - let task = cx.background_executor().spawn({ - let path = path.clone(); - async move { - let mut output = String::new(); - output.push_str(&codeblock_fence_for_path(path.as_deref(), None)); - for chunk in snapshot.as_rope().chunks() { - output.push_str(chunk); - } - if !output.ends_with('\n') { - output.push('\n'); - } - output.push_str("```\n"); - let has_diagnostics = - write_single_file_diagnostics(&mut output, path.as_deref(), &snapshot); - if output.ends_with('\n') { - output.pop(); - } - (output, has_diagnostics) - } - }); - cx.foreground_executor().spawn(async move { - let (text, has_diagnostics) = task.await; - let range = 0..text.len(); - Ok(SlashCommandOutput { - text, - sections: vec![build_entry_output_section( - range, - path.as_deref(), - false, - None, - )], - run_commands_in_text: has_diagnostics, - }) - }) - }); - output.unwrap_or_else(|error| Task::ready(Err(error))) - } -} diff --git a/crates/assistant/src/slash_command/tabs_command.rs b/crates/assistant/src/slash_command/tabs_command.rs index 41da270d5a..52ab6e632f 100644 --- a/crates/assistant/src/slash_command/tabs_command.rs +++ b/crates/assistant/src/slash_command/tabs_command.rs @@ -3,7 +3,7 @@ use super::{ file_command::{build_entry_output_section, codeblock_fence_for_path}, SlashCommand, SlashCommandOutput, }; -use anyhow::{anyhow, Result}; +use anyhow::Result; use assistant_slash_command::ArgumentCompletion; use collections::HashMap; use editor::Editor; @@ -15,6 +15,44 @@ use workspace::Workspace; pub(crate) struct TabsSlashCommand; +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +enum TabsArgument { + #[default] + Active, + All, +} + +impl TabsArgument { + fn for_query(mut query: String) -> Vec { + query.make_ascii_lowercase(); + let query = query.trim(); + + let mut matches = Vec::new(); + if Self::Active.name().contains(&query) { + matches.push(Self::Active); + } + if Self::All.name().contains(&query) { + matches.push(Self::All); + } + matches + } + + fn name(&self) -> &'static str { + match self { + Self::Active => "active", + Self::All => "all", + } + } + + fn from_name(name: &str) -> Option { + match name { + "active" => Some(Self::Active), + "all" => Some(Self::All), + _ => None, + } + } +} + impl SlashCommand for TabsSlashCommand { fn name(&self) -> String { "tabs".into() @@ -29,52 +67,79 @@ impl SlashCommand for TabsSlashCommand { } fn requires_argument(&self) -> bool { - false + true } fn complete_argument( self: Arc, - _query: String, + query: String, _cancel: Arc, _workspace: Option>, _cx: &mut AppContext, ) -> Task>> { - Task::ready(Err(anyhow!("this command does not require argument"))) + let arguments = TabsArgument::for_query(query); + Task::ready(Ok(arguments + .into_iter() + .map(|arg| ArgumentCompletion { + label: arg.name().to_owned(), + new_text: arg.name().to_owned(), + run_command: true, + }) + .collect())) } fn run( self: Arc, - _argument: Option<&str>, + argument: Option<&str>, workspace: WeakView, _delegate: Option>, cx: &mut WindowContext, ) -> Task> { - let open_buffers = workspace.update(cx, |workspace, cx| { - let mut timestamps_by_entity_id = HashMap::default(); - let mut open_buffers = Vec::new(); - - for pane in workspace.panes() { - let pane = pane.read(cx); - for entry in pane.activation_history() { - timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp); - } + let argument = argument + .and_then(TabsArgument::from_name) + .unwrap_or_default(); + let open_buffers = workspace.update(cx, |workspace, cx| match argument { + TabsArgument::Active => { + let Some(active_item) = workspace.active_item(cx) else { + anyhow::bail!("no active item") + }; + let Some(buffer) = active_item + .downcast::() + .and_then(|editor| editor.read(cx).buffer().read(cx).as_singleton()) + else { + anyhow::bail!("active item is not an editor") + }; + let snapshot = buffer.read(cx).snapshot(); + let full_path = snapshot.resolve_file_path(cx, true); + anyhow::Ok(vec![(full_path, snapshot, 0)]) } + TabsArgument::All => { + let mut timestamps_by_entity_id = HashMap::default(); + let mut open_buffers = Vec::new(); - for editor in workspace.items_of_type::(cx) { - if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() { - if let Some(timestamp) = timestamps_by_entity_id.get(&editor.entity_id()) { - let snapshot = buffer.read(cx).snapshot(); - let full_path = snapshot.resolve_file_path(cx, true); - open_buffers.push((full_path, snapshot, *timestamp)); + for pane in workspace.panes() { + let pane = pane.read(cx); + for entry in pane.activation_history() { + timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp); } } - } - open_buffers + for editor in workspace.items_of_type::(cx) { + if let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() { + if let Some(timestamp) = timestamps_by_entity_id.get(&editor.entity_id()) { + let snapshot = buffer.read(cx).snapshot(); + let full_path = snapshot.resolve_file_path(cx, true); + open_buffers.push((full_path, snapshot, *timestamp)); + } + } + } + + Ok(open_buffers) + } }); match open_buffers { - Ok(mut open_buffers) => cx.background_executor().spawn(async move { + Ok(Ok(mut open_buffers)) => cx.background_executor().spawn(async move { open_buffers.sort_by_key(|(_, _, timestamp)| *timestamp); let mut sections = Vec::new(); @@ -112,7 +177,7 @@ impl SlashCommand for TabsSlashCommand { run_commands_in_text: has_diagnostics, }) }), - Err(error) => Task::ready(Err(error)), + Ok(Err(error)) | Err(error) => Task::ready(Err(error)), } } }