Extract slash commands to their own crate (#23261)

Marshall Bowers created

This PR extracts the slash command definitions out of the `assistant`
crate and into their own `assistant_slash_commands` crate.

Release Notes:

- N/A

Change summary

Cargo.lock                                                       |  49 
Cargo.toml                                                       |   2 
crates/assistant/Cargo.toml                                      |   7 
crates/assistant/src/assistant.rs                                |  60 
crates/assistant/src/assistant_panel.rs                          | 124 -
crates/assistant/src/context.rs                                  |   5 
crates/assistant/src/context/context_tests.rs                    |   8 
crates/assistant/src/context_store.rs                            |   5 
crates/assistant/src/slash_command.rs                            |  41 
crates/assistant/src/slash_command/selection_command.rs          |  98 -
crates/assistant_slash_commands/Cargo.toml                       |  52 
crates/assistant_slash_commands/LICENSE-GPL                      |   1 
crates/assistant_slash_commands/src/assistant_slash_commands.rs  |  57 
crates/assistant_slash_commands/src/auto_command.rs              |   4 
crates/assistant_slash_commands/src/cargo_workspace_command.rs   |   2 
crates/assistant_slash_commands/src/context_server_command.rs    |   2 
crates/assistant_slash_commands/src/default_command.rs           |   2 
crates/assistant_slash_commands/src/delta_command.rs             |   4 
crates/assistant_slash_commands/src/diagnostics_command.rs       |   4 
crates/assistant_slash_commands/src/docs_command.rs              |   4 
crates/assistant_slash_commands/src/fetch_command.rs             |   2 
crates/assistant_slash_commands/src/file_command.rs              |   4 
crates/assistant_slash_commands/src/now_command.rs               |   2 
crates/assistant_slash_commands/src/project_command.rs           |  28 
crates/assistant_slash_commands/src/prompt_after_summary.txt     |   0 
crates/assistant_slash_commands/src/prompt_before_summary.txt    |   0 
crates/assistant_slash_commands/src/prompt_command.rs            |   2 
crates/assistant_slash_commands/src/search_command.rs            |   8 
crates/assistant_slash_commands/src/selection_command.rs         | 194 ++
crates/assistant_slash_commands/src/streaming_example_command.rs |   2 
crates/assistant_slash_commands/src/symbols_command.rs           |   2 
crates/assistant_slash_commands/src/tab_command.rs               |   4 
crates/assistant_slash_commands/src/terminal_command.rs          |   6 
33 files changed, 445 insertions(+), 340 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -375,9 +375,9 @@ dependencies = [
  "anyhow",
  "assistant_settings",
  "assistant_slash_command",
+ "assistant_slash_commands",
  "assistant_tool",
  "async-watch",
- "cargo_toml",
  "chrono",
  "client",
  "clock",
@@ -392,10 +392,7 @@ dependencies = [
  "fs",
  "futures 0.3.31",
  "fuzzy",
- "globset",
  "gpui",
- "html_to_markdown",
- "http_client",
  "indexed_docs",
  "indoc",
  "language",
@@ -405,7 +402,6 @@ dependencies = [
  "languages",
  "log",
  "lsp",
- "markdown",
  "menu",
  "multi_buffer",
  "open_ai",
@@ -439,7 +435,6 @@ dependencies = [
  "terminal_view",
  "text",
  "theme",
- "toml 0.8.19",
  "tree-sitter-md",
  "ui",
  "unindent",
@@ -554,6 +549,48 @@ dependencies = [
  "workspace",
 ]
 
+[[package]]
+name = "assistant_slash_commands"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "assistant_slash_command",
+ "cargo_toml",
+ "chrono",
+ "collections",
+ "context_server",
+ "editor",
+ "env_logger 0.11.6",
+ "feature_flags",
+ "fs",
+ "futures 0.3.31",
+ "fuzzy",
+ "globset",
+ "gpui",
+ "html_to_markdown",
+ "http_client",
+ "indexed_docs",
+ "language",
+ "language_model",
+ "log",
+ "pretty_assertions",
+ "project",
+ "prompt_library",
+ "rope",
+ "schemars",
+ "semantic_index",
+ "serde",
+ "serde_json",
+ "settings",
+ "smol",
+ "terminal_view",
+ "text",
+ "toml 0.8.19",
+ "ui",
+ "util",
+ "workspace",
+]
+
 [[package]]
 name = "assistant_tool"
 version = "0.1.0"

Cargo.toml 🔗

@@ -8,6 +8,7 @@ members = [
     "crates/assistant2",
     "crates/assistant_settings",
     "crates/assistant_slash_command",
+    "crates/assistant_slash_commands",
     "crates/assistant_tool",
     "crates/assistant_tools",
     "crates/audio",
@@ -198,6 +199,7 @@ assistant = { path = "crates/assistant" }
 assistant2 = { path = "crates/assistant2" }
 assistant_settings = { path = "crates/assistant_settings" }
 assistant_slash_command = { path = "crates/assistant_slash_command" }
+assistant_slash_commands = { path = "crates/assistant_slash_commands" }
 assistant_tool = { path = "crates/assistant_tool" }
 assistant_tools = { path = "crates/assistant_tools" }
 audio = { path = "crates/audio" }

crates/assistant/Cargo.toml 🔗

@@ -24,9 +24,9 @@ test-support = [
 anyhow.workspace = true
 assistant_settings.workspace = true
 assistant_slash_command.workspace = true
+assistant_slash_commands.workspace = true
 assistant_tool.workspace = true
 async-watch.workspace = true
-cargo_toml.workspace = true
 chrono.workspace = true
 client.workspace = true
 clock.workspace = true
@@ -39,10 +39,7 @@ feature_flags.workspace = true
 fs.workspace = true
 futures.workspace = true
 fuzzy.workspace = true
-globset.workspace = true
 gpui.workspace = true
-html_to_markdown.workspace = true
-http_client.workspace = true
 indexed_docs.workspace = true
 indoc.workspace = true
 language.workspace = true
@@ -51,7 +48,6 @@ language_model_selector.workspace = true
 language_models.workspace = true
 log.workspace = true
 lsp.workspace = true
-markdown.workspace = true
 menu.workspace = true
 multi_buffer.workspace = true
 open_ai = { workspace = true, features = ["schemars"] }
@@ -82,7 +78,6 @@ terminal.workspace = true
 terminal_view.workspace = true
 text.workspace = true
 theme.workspace = true
-toml.workspace = true
 ui.workspace = true
 util.workspace = true
 uuid.workspace = true

crates/assistant/src/assistant.rs 🔗

@@ -12,13 +12,13 @@ pub mod slash_command_settings;
 mod streaming_diff;
 mod terminal_inline_assistant;
 
-use crate::slash_command::project_command::ProjectSlashCommandFeatureFlag;
 pub use ::prompt_library::PromptBuilder;
 use ::prompt_library::PromptLoadingParams;
 pub use assistant_panel::{AssistantPanel, AssistantPanelEvent};
 use assistant_settings::AssistantSettings;
 use assistant_slash_command::SlashCommandRegistry;
 pub use assistant_slash_command::{SlashCommandId, SlashCommandWorkingSet};
+use assistant_slash_commands::{ProjectSlashCommandFeatureFlag, SearchSlashCommandFeatureFlag};
 use client::{proto, Client};
 use command_palette_hooks::CommandPaletteFilter;
 pub use context::*;
@@ -35,18 +35,11 @@ pub use patch::*;
 use semantic_index::{CloudEmbeddingProvider, SemanticDb};
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
-use slash_command::search_command::SearchSlashCommandFeatureFlag;
-use slash_command::{
-    auto_command, cargo_workspace_command, default_command, delta_command, diagnostics_command,
-    docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
-    search_command, selection_command, symbols_command, tab_command, terminal_command,
-};
 use std::path::PathBuf;
 use std::sync::Arc;
 pub(crate) use streaming_diff::*;
 use util::ResultExt;
 
-use crate::slash_command::streaming_example_command;
 use crate::slash_command_settings::SlashCommandSettings;
 
 actions!(
@@ -317,27 +310,28 @@ 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(delta_command::DeltaSlashCommand, true);
-    slash_command_registry.register_command(symbols_command::OutlineSlashCommand, true);
-    slash_command_registry.register_command(tab_command::TabSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::FileSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::DeltaSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::OutlineSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::TabSlashCommand, true);
+    slash_command_registry
+        .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::PromptSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::SelectionCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::DefaultSlashCommand, false);
+    slash_command_registry.register_command(assistant_slash_commands::TerminalSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::NowSlashCommand, false);
     slash_command_registry
-        .register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
-    slash_command_registry.register_command(prompt_command::PromptSlashCommand, true);
-    slash_command_registry.register_command(selection_command::SelectionCommand, true);
-    slash_command_registry.register_command(default_command::DefaultSlashCommand, false);
-    slash_command_registry.register_command(terminal_command::TerminalSlashCommand, true);
-    slash_command_registry.register_command(now_command::NowSlashCommand, false);
-    slash_command_registry.register_command(diagnostics_command::DiagnosticsSlashCommand, true);
-    slash_command_registry.register_command(fetch_command::FetchSlashCommand, true);
+        .register_command(assistant_slash_commands::DiagnosticsSlashCommand, true);
+    slash_command_registry.register_command(assistant_slash_commands::FetchSlashCommand, true);
 
     if let Some(prompt_builder) = prompt_builder {
-        cx.observe_flag::<project_command::ProjectSlashCommandFeatureFlag, _>({
+        cx.observe_flag::<assistant_slash_commands::ProjectSlashCommandFeatureFlag, _>({
             let slash_command_registry = slash_command_registry.clone();
             move |is_enabled, _cx| {
                 if is_enabled {
                     slash_command_registry.register_command(
-                        project_command::ProjectSlashCommand::new(prompt_builder.clone()),
+                        assistant_slash_commands::ProjectSlashCommand::new(prompt_builder.clone()),
                         true,
                     );
                 }
@@ -346,23 +340,24 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
         .detach();
     }
 
-    cx.observe_flag::<auto_command::AutoSlashCommandFeatureFlag, _>({
+    cx.observe_flag::<assistant_slash_commands::AutoSlashCommandFeatureFlag, _>({
         let slash_command_registry = slash_command_registry.clone();
         move |is_enabled, _cx| {
             if is_enabled {
                 // [#auto-staff-ship] TODO remove this when /auto is no longer staff-shipped
-                slash_command_registry.register_command(auto_command::AutoCommand, true);
+                slash_command_registry
+                    .register_command(assistant_slash_commands::AutoCommand, true);
             }
         }
     })
     .detach();
 
-    cx.observe_flag::<streaming_example_command::StreamingExampleSlashCommandFeatureFlag, _>({
+    cx.observe_flag::<assistant_slash_commands::StreamingExampleSlashCommandFeatureFlag, _>({
         let slash_command_registry = slash_command_registry.clone();
         move |is_enabled, _cx| {
             if is_enabled {
                 slash_command_registry.register_command(
-                    streaming_example_command::StreamingExampleSlashCommand,
+                    assistant_slash_commands::StreamingExampleSlashCommand,
                     false,
                 );
             }
@@ -374,11 +369,12 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
     cx.observe_global::<SettingsStore>(update_slash_commands_from_settings)
         .detach();
 
-    cx.observe_flag::<search_command::SearchSlashCommandFeatureFlag, _>({
+    cx.observe_flag::<assistant_slash_commands::SearchSlashCommandFeatureFlag, _>({
         let slash_command_registry = slash_command_registry.clone();
         move |is_enabled, _cx| {
             if is_enabled {
-                slash_command_registry.register_command(search_command::SearchSlashCommand, true);
+                slash_command_registry
+                    .register_command(assistant_slash_commands::SearchSlashCommand, true);
             }
         }
     })
@@ -390,17 +386,17 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
     let settings = SlashCommandSettings::get_global(cx);
 
     if settings.docs.enabled {
-        slash_command_registry.register_command(docs_command::DocsSlashCommand, true);
+        slash_command_registry.register_command(assistant_slash_commands::DocsSlashCommand, true);
     } else {
-        slash_command_registry.unregister_command(docs_command::DocsSlashCommand);
+        slash_command_registry.unregister_command(assistant_slash_commands::DocsSlashCommand);
     }
 
     if settings.cargo_workspace.enabled {
         slash_command_registry
-            .register_command(cargo_workspace_command::CargoWorkspaceSlashCommand, true);
+            .register_command(assistant_slash_commands::CargoWorkspaceSlashCommand, true);
     } else {
         slash_command_registry
-            .unregister_command(cargo_workspace_command::CargoWorkspaceSlashCommand);
+            .unregister_command(assistant_slash_commands::CargoWorkspaceSlashCommand);
     }
 }
 

crates/assistant/src/assistant_panel.rs 🔗

@@ -1,25 +1,22 @@
-use crate::slash_command::file_command::codeblock_fence_for_path;
 use crate::{
-    humanize_token_count,
-    prompt_library::open_prompt_library,
-    slash_command::{
-        default_command::DefaultSlashCommand,
-        docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
-        file_command, SlashCommandCompletionProvider,
-    },
-    slash_command_picker,
-    terminal_inline_assistant::TerminalInlineAssistant,
-    Assist, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context,
-    ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole,
-    DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles,
-    InsertIntoEditor, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
-    MessageMetadata, MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus,
-    QuoteSelection, RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus,
+    humanize_token_count, prompt_library::open_prompt_library,
+    slash_command::SlashCommandCompletionProvider, slash_command_picker,
+    terminal_inline_assistant::TerminalInlineAssistant, Assist, AssistantPatch,
+    AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context, ContextEvent, ContextId,
+    ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole, DeployHistory,
+    DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles, InsertIntoEditor,
+    InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata,
+    MessageStatus, NewContext, ParsedSlashCommand, PendingSlashCommandStatus, QuoteSelection,
+    RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus,
     ToggleModelSelector,
 };
 use anyhow::Result;
 use assistant_settings::{AssistantDockPosition, AssistantSettings};
 use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
+use assistant_slash_commands::{
+    selections_creases, DefaultSlashCommand, DocsSlashCommand, DocsSlashCommandArgs,
+    FileSlashCommand,
+};
 use assistant_tool::ToolWorkingSet;
 use client::{proto, zed_urls, Client, Status};
 use collections::{hash_map, BTreeSet, HashMap, HashSet};
@@ -3032,7 +3029,7 @@ impl ContextEditor {
 
         cx.spawn(|_, mut cx| async move {
             let (paths, dragged_file_worktrees) = paths.await;
-            let cmd_name = file_command::FileSlashCommand.name();
+            let cmd_name = FileSlashCommand.name();
 
             context_editor_view
                 .update(&mut cx, |context_editor, cx| {
@@ -3994,99 +3991,6 @@ fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Opti
     None
 }
 
-pub fn selections_creases(
-    workspace: &mut workspace::Workspace,
-    cx: &mut ViewContext<Workspace>,
-) -> Option<Vec<(String, String)>> {
-    let editor = workspace
-        .active_item(cx)
-        .and_then(|item| item.act_as::<Editor>(cx))?;
-
-    let mut creases = vec![];
-    editor.update(cx, |editor, cx| {
-        let selections = editor.selections.all_adjusted(cx);
-        let buffer = editor.buffer().read(cx).snapshot(cx);
-        for selection in selections {
-            let range = editor::ToOffset::to_offset(&selection.start, &buffer)
-                ..editor::ToOffset::to_offset(&selection.end, &buffer);
-            let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
-            if selected_text.is_empty() {
-                continue;
-            }
-            let start_language = buffer.language_at(range.start);
-            let end_language = buffer.language_at(range.end);
-            let language_name = if start_language == end_language {
-                start_language.map(|language| language.code_fence_block_name())
-            } else {
-                None
-            };
-            let language_name = language_name.as_deref().unwrap_or("");
-            let filename = buffer
-                .file_at(selection.start)
-                .map(|file| file.full_path(cx));
-            let text = if language_name == "markdown" {
-                selected_text
-                    .lines()
-                    .map(|line| format!("> {}", line))
-                    .collect::<Vec<_>>()
-                    .join("\n")
-            } else {
-                let start_symbols = buffer
-                    .symbols_containing(selection.start, None)
-                    .map(|(_, symbols)| symbols);
-                let end_symbols = buffer
-                    .symbols_containing(selection.end, None)
-                    .map(|(_, symbols)| symbols);
-
-                let outline_text =
-                    if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
-                        Some(
-                            start_symbols
-                                .into_iter()
-                                .zip(end_symbols)
-                                .take_while(|(a, b)| a == b)
-                                .map(|(a, _)| a.text)
-                                .collect::<Vec<_>>()
-                                .join(" > "),
-                        )
-                    } else {
-                        None
-                    };
-
-                let line_comment_prefix = start_language
-                    .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
-
-                let fence = codeblock_fence_for_path(
-                    filename.as_deref(),
-                    Some(selection.start.row..=selection.end.row),
-                );
-
-                if let Some((line_comment_prefix, outline_text)) =
-                    line_comment_prefix.zip(outline_text)
-                {
-                    let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
-                    format!("{fence}{breadcrumb}{selected_text}\n```")
-                } else {
-                    format!("{fence}{selected_text}\n```")
-                }
-            };
-            let crease_title = if let Some(path) = filename {
-                let start_line = selection.start.row + 1;
-                let end_line = selection.end.row + 1;
-                if start_line == end_line {
-                    format!("{}, Line {}", path.display(), start_line)
-                } else {
-                    format!("{}, Lines {} to {}", path.display(), start_line, end_line)
-                }
-            } else {
-                "Quoted selection".to_string()
-            };
-            creases.push((text, crease_title));
-        }
-    });
-    Some(creases)
-}
-
 fn render_fold_icon_button(
     editor: WeakView<Editor>,
     icon: IconName,

crates/assistant/src/context.rs 🔗

@@ -2,14 +2,15 @@
 mod context_tests;
 
 use crate::{
-    slash_command::{file_command::FileCommandMetadata, SlashCommandLine},
-    AssistantEdit, AssistantPatch, AssistantPatchStatus, MessageId, MessageStatus,
+    slash_command::SlashCommandLine, AssistantEdit, AssistantPatch, AssistantPatchStatus,
+    MessageId, MessageStatus,
 };
 use anyhow::{anyhow, Context as _, Result};
 use assistant_slash_command::{
     SlashCommandContent, SlashCommandEvent, SlashCommandOutputSection, SlashCommandResult,
     SlashCommandWorkingSet,
 };
+use assistant_slash_commands::FileCommandMetadata;
 use assistant_tool::ToolWorkingSet;
 use client::{self, proto, telemetry::Telemetry};
 use clock::ReplicaId;

crates/assistant/src/context/context_tests.rs 🔗

@@ -1,14 +1,14 @@
 use super::{AssistantEdit, MessageCacheMetadata};
 use crate::{
-    assistant_panel, slash_command::file_command, AssistantEditKind, CacheStatus, Context,
-    ContextEvent, ContextId, ContextOperation, InvokedSlashCommandId, MessageId, MessageStatus,
-    PromptBuilder,
+    assistant_panel, AssistantEditKind, CacheStatus, Context, ContextEvent, ContextId,
+    ContextOperation, InvokedSlashCommandId, MessageId, MessageStatus, PromptBuilder,
 };
 use anyhow::Result;
 use assistant_slash_command::{
     ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent, SlashCommandOutput,
     SlashCommandOutputSection, SlashCommandRegistry, SlashCommandResult, SlashCommandWorkingSet,
 };
+use assistant_slash_commands::FileSlashCommand;
 use assistant_tool::ToolWorkingSet;
 use collections::{HashMap, HashSet};
 use fs::FakeFs;
@@ -407,7 +407,7 @@ async fn test_slash_commands(cx: &mut TestAppContext) {
     .await;
 
     let slash_command_registry = cx.update(SlashCommandRegistry::default_global);
-    slash_command_registry.register_command(file_command::FileSlashCommand, false);
+    slash_command_registry.register_command(FileSlashCommand, false);
 
     let registry = Arc::new(LanguageRegistry::test(cx.executor()));
     let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());

crates/assistant/src/context_store.rs 🔗

@@ -1,4 +1,3 @@
-use crate::slash_command::context_server_command;
 use crate::SlashCommandId;
 use crate::{
     Context, ContextEvent, ContextId, ContextOperation, ContextVersion, SavedContext,
@@ -837,14 +836,14 @@ impl ContextStore {
                                 if let Some(prompts) = protocol.list_prompts().await.log_err() {
                                     let slash_command_ids = prompts
                                         .into_iter()
-                                        .filter(context_server_command::acceptable_prompt)
+                                        .filter(assistant_slash_commands::acceptable_prompt)
                                         .map(|prompt| {
                                             log::info!(
                                                 "registering context server command: {:?}",
                                                 prompt.name
                                             );
                                             slash_command_working_set.insert(Arc::new(
-                                                context_server_command::ContextServerSlashCommand::new(
+                                                assistant_slash_commands::ContextServerSlashCommand::new(
                                                     context_server_manager.clone(),
                                                     &server,
                                                     prompt,

crates/assistant/src/slash_command.rs 🔗

@@ -2,11 +2,11 @@ use crate::assistant_panel::ContextEditor;
 use crate::SlashCommandWorkingSet;
 use anyhow::Result;
 use assistant_slash_command::AfterCompletion;
-pub use assistant_slash_command::{SlashCommand, SlashCommandOutput};
+pub use assistant_slash_command::SlashCommand;
 use editor::{CompletionProvider, Editor};
 use fuzzy::{match_strings, StringMatchCandidate};
-use gpui::{AppContext, Model, Task, ViewContext, WeakView, WindowContext};
-use language::{Anchor, Buffer, CodeLabel, Documentation, HighlightId, LanguageServerId, ToPoint};
+use gpui::{Model, Task, ViewContext, WeakView, WindowContext};
+use language::{Anchor, Buffer, Documentation, LanguageServerId, ToPoint};
 use parking_lot::Mutex;
 use project::CompletionIntent;
 use rope::Point;
@@ -19,26 +19,7 @@ use std::{
         Arc,
     },
 };
-use ui::ActiveTheme;
 use workspace::Workspace;
-pub mod auto_command;
-pub mod cargo_workspace_command;
-pub mod context_server_command;
-pub mod default_command;
-pub mod delta_command;
-pub mod diagnostics_command;
-pub mod docs_command;
-pub mod fetch_command;
-pub mod file_command;
-pub mod now_command;
-pub mod project_command;
-pub mod prompt_command;
-pub mod search_command;
-pub mod selection_command;
-pub mod streaming_example_command;
-pub mod symbols_command;
-pub mod tab_command;
-pub mod terminal_command;
 
 pub(crate) struct SlashCommandCompletionProvider {
     cancel_flag: Mutex<Arc<AtomicBool>>,
@@ -409,19 +390,3 @@ impl SlashCommandLine {
         call
     }
 }
-
-pub fn create_label_for_command(
-    command_name: &str,
-    arguments: &[&str],
-    cx: &AppContext,
-) -> CodeLabel {
-    let mut label = CodeLabel::default();
-    label.push_str(command_name, None);
-    label.push_str(" ", None);
-    label.push_str(
-        &arguments.join(" "),
-        cx.theme().syntax().highlight_id("comment").map(HighlightId),
-    );
-    label.filter_range = 0..command_name.len();
-    label
-}

crates/assistant/src/slash_command/selection_command.rs 🔗

@@ -1,98 +0,0 @@
-use crate::assistant_panel::selections_creases;
-use anyhow::{anyhow, Result};
-use assistant_slash_command::{
-    ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
-    SlashCommandOutputSection, SlashCommandResult,
-};
-use futures::StreamExt;
-use gpui::{AppContext, Task, WeakView};
-use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
-use std::sync::atomic::AtomicBool;
-use std::sync::Arc;
-use ui::{IconName, SharedString, WindowContext};
-use workspace::Workspace;
-
-pub(crate) struct SelectionCommand;
-
-impl SlashCommand for SelectionCommand {
-    fn name(&self) -> String {
-        "selection".into()
-    }
-
-    fn label(&self, _cx: &AppContext) -> CodeLabel {
-        CodeLabel::plain(self.name(), None)
-    }
-
-    fn description(&self) -> String {
-        "Insert editor selection".into()
-    }
-
-    fn icon(&self) -> IconName {
-        IconName::Quote
-    }
-
-    fn menu_text(&self) -> String {
-        self.description()
-    }
-
-    fn requires_argument(&self) -> bool {
-        false
-    }
-
-    fn accepts_arguments(&self) -> bool {
-        true
-    }
-
-    fn complete_argument(
-        self: Arc<Self>,
-        _arguments: &[String],
-        _cancel: Arc<AtomicBool>,
-        _workspace: Option<WeakView<Workspace>>,
-        _cx: &mut WindowContext,
-    ) -> Task<Result<Vec<ArgumentCompletion>>> {
-        Task::ready(Err(anyhow!("this command does not require argument")))
-    }
-
-    fn run(
-        self: Arc<Self>,
-        _arguments: &[String],
-        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
-        _context_buffer: BufferSnapshot,
-        workspace: WeakView<Workspace>,
-        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
-        cx: &mut WindowContext,
-    ) -> Task<SlashCommandResult> {
-        let mut events = vec![];
-
-        let Some(creases) = workspace
-            .update(cx, selections_creases)
-            .unwrap_or_else(|e| {
-                events.push(Err(e));
-                None
-            })
-        else {
-            return Task::ready(Err(anyhow!("no active selection")));
-        };
-
-        for (text, title) in creases {
-            events.push(Ok(SlashCommandEvent::StartSection {
-                icon: IconName::TextSnippet,
-                label: SharedString::from(title),
-                metadata: None,
-            }));
-            events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
-                text,
-                run_commands_in_text: false,
-            })));
-            events.push(Ok(SlashCommandEvent::EndSection));
-            events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
-                text: "\n".to_string(),
-                run_commands_in_text: false,
-            })));
-        }
-
-        let result = futures::stream::iter(events).boxed();
-
-        Task::ready(Ok(result))
-    }
-}

crates/assistant_slash_commands/Cargo.toml 🔗

@@ -0,0 +1,52 @@
+[package]
+name = "assistant_slash_commands"
+version = "0.1.0"
+edition = "2021"
+publish = false
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/assistant_slash_commands.rs"
+
+[dependencies]
+anyhow.workspace = true
+assistant_slash_command.workspace = true
+cargo_toml.workspace = true
+chrono.workspace = true
+collections.workspace = true
+context_server.workspace = true
+editor.workspace = true
+feature_flags.workspace = true
+fs.workspace = true
+futures.workspace = true
+fuzzy.workspace = true
+globset.workspace = true
+gpui.workspace = true
+html_to_markdown.workspace = true
+http_client.workspace = true
+indexed_docs.workspace = true
+language.workspace = true
+language_model.workspace = true
+log.workspace = true
+project.workspace = true
+prompt_library.workspace = true
+rope.workspace = true
+schemars.workspace = true
+semantic_index.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+smol.workspace = true
+terminal_view.workspace = true
+text.workspace = true
+toml.workspace = true
+ui.workspace = true
+util.workspace = true
+workspace.workspace = true
+
+[dev-dependencies]
+env_logger.workspace = true
+pretty_assertions.workspace = true
+settings.workspace = true

crates/assistant_slash_commands/src/assistant_slash_commands.rs 🔗

@@ -0,0 +1,57 @@
+mod auto_command;
+mod cargo_workspace_command;
+mod context_server_command;
+mod default_command;
+mod delta_command;
+mod diagnostics_command;
+mod docs_command;
+mod fetch_command;
+mod file_command;
+mod now_command;
+mod project_command;
+mod prompt_command;
+mod search_command;
+mod selection_command;
+mod streaming_example_command;
+mod symbols_command;
+mod tab_command;
+mod terminal_command;
+
+use gpui::AppContext;
+use language::{CodeLabel, HighlightId};
+use ui::ActiveTheme as _;
+
+pub use crate::auto_command::*;
+pub use crate::cargo_workspace_command::*;
+pub use crate::context_server_command::*;
+pub use crate::default_command::*;
+pub use crate::delta_command::*;
+pub use crate::diagnostics_command::*;
+pub use crate::docs_command::*;
+pub use crate::fetch_command::*;
+pub use crate::file_command::*;
+pub use crate::now_command::*;
+pub use crate::project_command::*;
+pub use crate::prompt_command::*;
+pub use crate::search_command::*;
+pub use crate::selection_command::*;
+pub use crate::streaming_example_command::*;
+pub use crate::symbols_command::*;
+pub use crate::tab_command::*;
+pub use crate::terminal_command::*;
+
+pub fn create_label_for_command(
+    command_name: &str,
+    arguments: &[&str],
+    cx: &AppContext,
+) -> CodeLabel {
+    let mut label = CodeLabel::default();
+    label.push_str(command_name, None);
+    label.push_str(" ", None);
+    label.push_str(
+        &arguments.join(" "),
+        cx.theme().syntax().highlight_id("comment").map(HighlightId),
+    );
+    label.filter_range = 0..command_name.len();
+    label
+}

crates/assistant/src/slash_command/auto_command.rs → crates/assistant_slash_commands/src/auto_command.rs 🔗

@@ -18,7 +18,7 @@ use ui::{prelude::*, BorrowAppContext};
 use util::ResultExt;
 use workspace::Workspace;
 
-use crate::slash_command::create_label_for_command;
+use crate::create_label_for_command;
 
 pub struct AutoSlashCommandFeatureFlag;
 
@@ -26,7 +26,7 @@ impl FeatureFlag for AutoSlashCommandFeatureFlag {
     const NAME: &'static str = "auto-slash-command";
 }
 
-pub(crate) struct AutoCommand;
+pub struct AutoCommand;
 
 impl SlashCommand for AutoCommand {
     fn name(&self) -> String {

crates/assistant/src/slash_command/cargo_workspace_command.rs → crates/assistant_slash_commands/src/cargo_workspace_command.rs 🔗

@@ -15,7 +15,7 @@ use std::{
 use ui::prelude::*;
 use workspace::Workspace;
 
-pub(crate) struct CargoWorkspaceSlashCommand;
+pub struct CargoWorkspaceSlashCommand;
 
 impl CargoWorkspaceSlashCommand {
     async fn build_message(fs: Arc<dyn Fs>, path_to_cargo_toml: &Path) -> Result<String> {

crates/assistant/src/slash_command/context_server_command.rs → crates/assistant_slash_commands/src/context_server_command.rs 🔗

@@ -16,7 +16,7 @@ use text::LineEnding;
 use ui::{IconName, SharedString};
 use workspace::Workspace;
 
-use crate::slash_command::create_label_for_command;
+use crate::create_label_for_command;
 
 pub struct ContextServerSlashCommand {
     server_manager: Model<ContextServerManager>,

crates/assistant/src/slash_command/default_command.rs → crates/assistant_slash_commands/src/default_command.rs 🔗

@@ -13,7 +13,7 @@ use std::{
 use ui::prelude::*;
 use workspace::Workspace;
 
-pub(crate) struct DefaultSlashCommand;
+pub struct DefaultSlashCommand;
 
 impl SlashCommand for DefaultSlashCommand {
     fn name(&self) -> String {

crates/assistant/src/slash_command/delta_command.rs → crates/assistant_slash_commands/src/delta_command.rs 🔗

@@ -1,4 +1,4 @@
-use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
+use crate::file_command::{FileCommandMetadata, FileSlashCommand};
 use anyhow::{anyhow, Result};
 use assistant_slash_command::{
     ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
@@ -13,7 +13,7 @@ use text::OffsetRangeExt;
 use ui::prelude::*;
 use workspace::Workspace;
 
-pub(crate) struct DeltaSlashCommand;
+pub struct DeltaSlashCommand;
 
 impl SlashCommand for DeltaSlashCommand {
     fn name(&self) -> String {

crates/assistant/src/slash_command/diagnostics_command.rs → crates/assistant_slash_commands/src/diagnostics_command.rs 🔗

@@ -21,9 +21,9 @@ use util::paths::PathMatcher;
 use util::ResultExt;
 use workspace::Workspace;
 
-use crate::slash_command::create_label_for_command;
+use crate::create_label_for_command;
 
-pub(crate) struct DiagnosticsSlashCommand;
+pub struct DiagnosticsSlashCommand;
 
 impl DiagnosticsSlashCommand {
     fn search_paths(

crates/assistant/src/slash_command/docs_command.rs → crates/assistant_slash_commands/src/docs_command.rs 🔗

@@ -19,7 +19,7 @@ use ui::prelude::*;
 use util::{maybe, ResultExt};
 use workspace::Workspace;
 
-pub(crate) struct DocsSlashCommand;
+pub struct DocsSlashCommand;
 
 impl DocsSlashCommand {
     pub const NAME: &'static str = "docs";
@@ -367,7 +367,7 @@ fn is_item_path_delimiter(char: char) -> bool {
 }
 
 #[derive(Debug, PartialEq, Clone)]
-pub(crate) enum DocsSlashCommandArgs {
+pub enum DocsSlashCommandArgs {
     NoProvider,
     SearchPackageDocs {
         provider: ProviderId,

crates/assistant/src/slash_command/fetch_command.rs → crates/assistant_slash_commands/src/fetch_command.rs 🔗

@@ -23,7 +23,7 @@ enum ContentType {
     Json,
 }
 
-pub(crate) struct FetchSlashCommand;
+pub struct FetchSlashCommand;
 
 impl FetchSlashCommand {
     async fn build_message(http_client: Arc<HttpClientWithUrl>, url: &str) -> Result<String> {

crates/assistant/src/slash_command/file_command.rs → crates/assistant_slash_commands/src/file_command.rs 🔗

@@ -21,7 +21,7 @@ use ui::prelude::*;
 use util::ResultExt;
 use workspace::Workspace;
 
-pub(crate) struct FileSlashCommand;
+pub struct FileSlashCommand;
 
 impl FileSlashCommand {
     fn search_paths(
@@ -561,7 +561,7 @@ mod test {
     use settings::SettingsStore;
     use smol::stream::StreamExt;
 
-    use crate::slash_command::file_command::collect_files;
+    use super::collect_files;
 
     pub fn init_test(cx: &mut gpui::TestAppContext) {
         if std::env::var("RUST_LOG").is_ok() {

crates/assistant/src/slash_command/now_command.rs → crates/assistant_slash_commands/src/now_command.rs 🔗

@@ -12,7 +12,7 @@ use language::{BufferSnapshot, LspAdapterDelegate};
 use ui::prelude::*;
 use workspace::Workspace;
 
-pub(crate) struct NowSlashCommand;
+pub struct NowSlashCommand;
 
 impl SlashCommand for NowSlashCommand {
     fn name(&self) -> String {

crates/assistant/src/slash_command/project_command.rs → crates/assistant_slash_commands/src/project_command.rs 🔗

@@ -1,17 +1,26 @@
-use super::{
-    create_label_for_command, search_command::add_search_result_section, SlashCommand,
-    SlashCommandOutput,
+use std::{
+    fmt::Write as _,
+    ops::DerefMut,
+    sync::{atomic::AtomicBool, Arc},
 };
-use crate::PromptBuilder;
+
 use anyhow::{anyhow, Result};
-use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult};
+use assistant_slash_command::{
+    ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
+    SlashCommandResult,
+};
 use feature_flags::FeatureFlag;
 use gpui::{AppContext, Task, WeakView, WindowContext};
 use language::{Anchor, CodeLabel, LspAdapterDelegate};
 use language_model::{LanguageModelRegistry, LanguageModelTool};
+use prompt_library::PromptBuilder;
 use schemars::JsonSchema;
 use semantic_index::SemanticDb;
 use serde::Deserialize;
+use ui::prelude::*;
+use workspace::Workspace;
+
+use super::{create_label_for_command, search_command::add_search_result_section};
 
 pub struct ProjectSlashCommandFeatureFlag;
 
@@ -19,15 +28,6 @@ impl FeatureFlag for ProjectSlashCommandFeatureFlag {
     const NAME: &'static str = "project-slash-command";
 }
 
-use std::{
-    fmt::Write as _,
-    ops::DerefMut,
-    sync::{atomic::AtomicBool, Arc},
-};
-
-use ui::prelude::*;
-use workspace::Workspace;
-
 pub struct ProjectSlashCommand {
     prompt_builder: Arc<PromptBuilder>,
 }

crates/assistant/src/slash_command/prompt_command.rs → crates/assistant_slash_commands/src/prompt_command.rs 🔗

@@ -10,7 +10,7 @@ use std::sync::{atomic::AtomicBool, Arc};
 use ui::prelude::*;
 use workspace::Workspace;
 
-pub(crate) struct PromptSlashCommand;
+pub struct PromptSlashCommand;
 
 impl SlashCommand for PromptSlashCommand {
     fn name(&self) -> String {

crates/assistant/src/slash_command/search_command.rs → crates/assistant_slash_commands/src/search_command.rs 🔗

@@ -14,10 +14,10 @@ use std::{
 use ui::{prelude::*, IconName};
 use workspace::Workspace;
 
-use crate::slash_command::create_label_for_command;
-use crate::slash_command::file_command::{build_entry_output_section, codeblock_fence_for_path};
+use crate::create_label_for_command;
+use crate::file_command::{build_entry_output_section, codeblock_fence_for_path};
 
-pub(crate) struct SearchSlashCommandFeatureFlag;
+pub struct SearchSlashCommandFeatureFlag;
 
 impl FeatureFlag for SearchSlashCommandFeatureFlag {
     const NAME: &'static str = "search-slash-command";
@@ -27,7 +27,7 @@ impl FeatureFlag for SearchSlashCommandFeatureFlag {
     }
 }
 
-pub(crate) struct SearchSlashCommand;
+pub struct SearchSlashCommand;
 
 impl SlashCommand for SearchSlashCommand {
     fn name(&self) -> String {

crates/assistant_slash_commands/src/selection_command.rs 🔗

@@ -0,0 +1,194 @@
+use anyhow::{anyhow, Result};
+use assistant_slash_command::{
+    ArgumentCompletion, SlashCommand, SlashCommandContent, SlashCommandEvent,
+    SlashCommandOutputSection, SlashCommandResult,
+};
+use editor::Editor;
+use futures::StreamExt;
+use gpui::{AppContext, Task, WeakView};
+use gpui::{SharedString, ViewContext, WindowContext};
+use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
+use std::sync::atomic::AtomicBool;
+use std::sync::Arc;
+use ui::IconName;
+use workspace::Workspace;
+
+use crate::file_command::codeblock_fence_for_path;
+
+pub struct SelectionCommand;
+
+impl SlashCommand for SelectionCommand {
+    fn name(&self) -> String {
+        "selection".into()
+    }
+
+    fn label(&self, _cx: &AppContext) -> CodeLabel {
+        CodeLabel::plain(self.name(), None)
+    }
+
+    fn description(&self) -> String {
+        "Insert editor selection".into()
+    }
+
+    fn icon(&self) -> IconName {
+        IconName::Quote
+    }
+
+    fn menu_text(&self) -> String {
+        self.description()
+    }
+
+    fn requires_argument(&self) -> bool {
+        false
+    }
+
+    fn accepts_arguments(&self) -> bool {
+        true
+    }
+
+    fn complete_argument(
+        self: Arc<Self>,
+        _arguments: &[String],
+        _cancel: Arc<AtomicBool>,
+        _workspace: Option<WeakView<Workspace>>,
+        _cx: &mut WindowContext,
+    ) -> Task<Result<Vec<ArgumentCompletion>>> {
+        Task::ready(Err(anyhow!("this command does not require argument")))
+    }
+
+    fn run(
+        self: Arc<Self>,
+        _arguments: &[String],
+        _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
+        _context_buffer: BufferSnapshot,
+        workspace: WeakView<Workspace>,
+        _delegate: Option<Arc<dyn LspAdapterDelegate>>,
+        cx: &mut WindowContext,
+    ) -> Task<SlashCommandResult> {
+        let mut events = vec![];
+
+        let Some(creases) = workspace
+            .update(cx, selections_creases)
+            .unwrap_or_else(|e| {
+                events.push(Err(e));
+                None
+            })
+        else {
+            return Task::ready(Err(anyhow!("no active selection")));
+        };
+
+        for (text, title) in creases {
+            events.push(Ok(SlashCommandEvent::StartSection {
+                icon: IconName::TextSnippet,
+                label: SharedString::from(title),
+                metadata: None,
+            }));
+            events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
+                text,
+                run_commands_in_text: false,
+            })));
+            events.push(Ok(SlashCommandEvent::EndSection));
+            events.push(Ok(SlashCommandEvent::Content(SlashCommandContent::Text {
+                text: "\n".to_string(),
+                run_commands_in_text: false,
+            })));
+        }
+
+        let result = futures::stream::iter(events).boxed();
+
+        Task::ready(Ok(result))
+    }
+}
+
+pub fn selections_creases(
+    workspace: &mut workspace::Workspace,
+    cx: &mut ViewContext<Workspace>,
+) -> Option<Vec<(String, String)>> {
+    let editor = workspace
+        .active_item(cx)
+        .and_then(|item| item.act_as::<Editor>(cx))?;
+
+    let mut creases = vec![];
+    editor.update(cx, |editor, cx| {
+        let selections = editor.selections.all_adjusted(cx);
+        let buffer = editor.buffer().read(cx).snapshot(cx);
+        for selection in selections {
+            let range = editor::ToOffset::to_offset(&selection.start, &buffer)
+                ..editor::ToOffset::to_offset(&selection.end, &buffer);
+            let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
+            if selected_text.is_empty() {
+                continue;
+            }
+            let start_language = buffer.language_at(range.start);
+            let end_language = buffer.language_at(range.end);
+            let language_name = if start_language == end_language {
+                start_language.map(|language| language.code_fence_block_name())
+            } else {
+                None
+            };
+            let language_name = language_name.as_deref().unwrap_or("");
+            let filename = buffer
+                .file_at(selection.start)
+                .map(|file| file.full_path(cx));
+            let text = if language_name == "markdown" {
+                selected_text
+                    .lines()
+                    .map(|line| format!("> {}", line))
+                    .collect::<Vec<_>>()
+                    .join("\n")
+            } else {
+                let start_symbols = buffer
+                    .symbols_containing(selection.start, None)
+                    .map(|(_, symbols)| symbols);
+                let end_symbols = buffer
+                    .symbols_containing(selection.end, None)
+                    .map(|(_, symbols)| symbols);
+
+                let outline_text =
+                    if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
+                        Some(
+                            start_symbols
+                                .into_iter()
+                                .zip(end_symbols)
+                                .take_while(|(a, b)| a == b)
+                                .map(|(a, _)| a.text)
+                                .collect::<Vec<_>>()
+                                .join(" > "),
+                        )
+                    } else {
+                        None
+                    };
+
+                let line_comment_prefix = start_language
+                    .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
+
+                let fence = codeblock_fence_for_path(
+                    filename.as_deref(),
+                    Some(selection.start.row..=selection.end.row),
+                );
+
+                if let Some((line_comment_prefix, outline_text)) =
+                    line_comment_prefix.zip(outline_text)
+                {
+                    let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
+                    format!("{fence}{breadcrumb}{selected_text}\n```")
+                } else {
+                    format!("{fence}{selected_text}\n```")
+                }
+            };
+            let crease_title = if let Some(path) = filename {
+                let start_line = selection.start.row + 1;
+                let end_line = selection.end.row + 1;
+                if start_line == end_line {
+                    format!("{}, Line {}", path.display(), start_line)
+                } else {
+                    format!("{}, Lines {} to {}", path.display(), start_line, end_line)
+                }
+            } else {
+                "Quoted selection".to_string()
+            };
+            creases.push((text, crease_title));
+        }
+    });
+    Some(creases)
+}

crates/assistant/src/slash_command/streaming_example_command.rs → crates/assistant_slash_commands/src/streaming_example_command.rs 🔗

@@ -22,7 +22,7 @@ impl FeatureFlag for StreamingExampleSlashCommandFeatureFlag {
     const NAME: &'static str = "streaming-example-slash-command";
 }
 
-pub(crate) struct StreamingExampleSlashCommand;
+pub struct StreamingExampleSlashCommand;
 
 impl SlashCommand for StreamingExampleSlashCommand {
     fn name(&self) -> String {

crates/assistant/src/slash_command/symbols_command.rs → crates/assistant_slash_commands/src/symbols_command.rs 🔗

@@ -11,7 +11,7 @@ use std::{path::Path, sync::atomic::AtomicBool};
 use ui::{IconName, WindowContext};
 use workspace::Workspace;
 
-pub(crate) struct OutlineSlashCommand;
+pub struct OutlineSlashCommand;
 
 impl SlashCommand for OutlineSlashCommand {
     fn name(&self) -> String {

crates/assistant/src/slash_command/tab_command.rs → crates/assistant_slash_commands/src/tab_command.rs 🔗

@@ -16,9 +16,9 @@ use ui::{prelude::*, ActiveTheme, WindowContext};
 use util::ResultExt;
 use workspace::Workspace;
 
-use crate::slash_command::file_command::append_buffer_to_output;
+use crate::file_command::append_buffer_to_output;
 
-pub(crate) struct TabSlashCommand;
+pub struct TabSlashCommand;
 
 const ALL_TABS_COMPLETION_ITEM: &str = "all";
 

crates/assistant/src/slash_command/terminal_command.rs → crates/assistant_slash_commands/src/terminal_command.rs 🔗

@@ -12,14 +12,14 @@ use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
 use ui::prelude::*;
 use workspace::{dock::Panel, Workspace};
 
-use crate::DEFAULT_CONTEXT_LINES;
-
 use super::create_label_for_command;
 
-pub(crate) struct TerminalSlashCommand;
+pub struct TerminalSlashCommand;
 
 const LINE_COUNT_ARG: &str = "--line-count";
 
+const DEFAULT_CONTEXT_LINES: usize = 50;
+
 impl SlashCommand for TerminalSlashCommand {
     fn name(&self) -> String {
         "terminal".into()