Detailed changes
@@ -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<Arc<PromptBuilder>>, 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);
@@ -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());
@@ -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;
@@ -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<Self>,
- _query: String,
- _cancel: Arc<AtomicBool>,
- _workspace: Option<WeakView<Workspace>>,
- _cx: &mut AppContext,
- ) -> Task<Result<Vec<ArgumentCompletion>>> {
- Task::ready(Err(anyhow!("this command does not require argument")))
- }
-
- fn requires_argument(&self) -> bool {
- false
- }
-
- fn run(
- self: Arc<Self>,
- _argument: Option<&str>,
- workspace: WeakView<Workspace>,
- _delegate: Option<Arc<dyn LspAdapterDelegate>>,
- cx: &mut WindowContext,
- ) -> Task<Result<SlashCommandOutput>> {
- 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::<Editor>()
- .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)))
- }
-}
@@ -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<Self> {
+ 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<Self> {
+ 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<Self>,
- _query: String,
+ query: String,
_cancel: Arc<std::sync::atomic::AtomicBool>,
_workspace: Option<WeakView<Workspace>>,
_cx: &mut AppContext,
) -> Task<Result<Vec<ArgumentCompletion>>> {
- 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<Self>,
- _argument: Option<&str>,
+ argument: Option<&str>,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
) -> Task<Result<SlashCommandOutput>> {
- 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::<Editor>()
+ .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::<Editor>(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::<Editor>(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)),
}
}
}