From 8030c0025af5e8ba39039dbac302ec6bd6ec2e15 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 16 Jan 2025 17:17:07 -0500 Subject: [PATCH] Extract slash commands to their own crate (#23261) This PR extracts the slash command definitions out of the `assistant` crate and into their own `assistant_slash_commands` crate. Release Notes: - N/A --- 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 +--- .../src/slash_command/selection_command.rs | 98 --------- crates/assistant_slash_commands/Cargo.toml | 52 +++++ crates/assistant_slash_commands/LICENSE-GPL | 1 + .../src/assistant_slash_commands.rs | 57 +++++ .../src}/auto_command.rs | 4 +- .../src}/cargo_workspace_command.rs | 2 +- .../src}/context_server_command.rs | 2 +- .../src}/default_command.rs | 2 +- .../src}/delta_command.rs | 4 +- .../src}/diagnostics_command.rs | 4 +- .../src}/docs_command.rs | 4 +- .../src}/fetch_command.rs | 2 +- .../src}/file_command.rs | 4 +- .../src}/now_command.rs | 2 +- .../src}/project_command.rs | 42 ++-- .../src}/prompt_after_summary.txt | 0 .../src}/prompt_before_summary.txt | 0 .../src}/prompt_command.rs | 2 +- .../src}/search_command.rs | 8 +- .../src/selection_command.rs | 194 ++++++++++++++++++ .../src}/streaming_example_command.rs | 2 +- .../src}/symbols_command.rs | 2 +- .../src}/tab_command.rs | 4 +- .../src}/terminal_command.rs | 6 +- 33 files changed, 452 insertions(+), 347 deletions(-) delete mode 100644 crates/assistant/src/slash_command/selection_command.rs create mode 100644 crates/assistant_slash_commands/Cargo.toml create mode 120000 crates/assistant_slash_commands/LICENSE-GPL create mode 100644 crates/assistant_slash_commands/src/assistant_slash_commands.rs rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/auto_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/cargo_workspace_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/context_server_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/default_command.rs (98%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/delta_command.rs (97%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/diagnostics_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/docs_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/fetch_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/file_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/now_command.rs (98%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/project_command.rs (96%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/prompt_after_summary.txt (100%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/prompt_before_summary.txt (100%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/prompt_command.rs (98%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/search_command.rs (95%) create mode 100644 crates/assistant_slash_commands/src/selection_command.rs rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/streaming_example_command.rs (98%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/symbols_command.rs (98%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/tab_command.rs (99%) rename crates/{assistant/src/slash_command => assistant_slash_commands/src}/terminal_command.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index 939e4b4453..346b099d1b 100644 --- a/Cargo.lock +++ b/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" diff --git a/Cargo.toml b/Cargo.toml index 1ce69abdba..0707d24d79 100644 --- a/Cargo.toml +++ b/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" } diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 2b92b7e223..9555681803 100644 --- a/crates/assistant/Cargo.toml +++ b/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 diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index fd5628d68e..a57f4ca3d6 100644 --- a/crates/assistant/src/assistant.rs +++ b/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>, 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(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::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(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::({ + cx.observe_flag::({ 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>, cx: &mut .detach(); } - cx.observe_flag::({ + cx.observe_flag::({ 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::({ + cx.observe_flag::({ 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>, cx: &mut cx.observe_global::(update_slash_commands_from_settings) .detach(); - cx.observe_flag::({ + cx.observe_flag::({ 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); } } diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index fe3840fefc..b962141cfe 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/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, -) -> Option> { - let editor = workspace - .active_item(cx) - .and_then(|item| item.act_as::(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::(); - 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::>() - .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::>() - .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, icon: IconName, diff --git a/crates/assistant/src/context.rs b/crates/assistant/src/context.rs index 99822f10b9..038bc0177d 100644 --- a/crates/assistant/src/context.rs +++ b/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; diff --git a/crates/assistant/src/context/context_tests.rs b/crates/assistant/src/context/context_tests.rs index 756d57e88e..444cb6560a 100644 --- a/crates/assistant/src/context/context_tests.rs +++ b/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()); diff --git a/crates/assistant/src/context_store.rs b/crates/assistant/src/context_store.rs index b1fb79afbd..43fc4b8aa1 100644 --- a/crates/assistant/src/context_store.rs +++ b/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, diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index f9fb4fa803..b4841647df 100644 --- a/crates/assistant/src/slash_command.rs +++ b/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>, @@ -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 -} diff --git a/crates/assistant/src/slash_command/selection_command.rs b/crates/assistant/src/slash_command/selection_command.rs deleted file mode 100644 index e2770fd809..0000000000 --- a/crates/assistant/src/slash_command/selection_command.rs +++ /dev/null @@ -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, - _arguments: &[String], - _cancel: Arc, - _workspace: Option>, - _cx: &mut WindowContext, - ) -> Task>> { - Task::ready(Err(anyhow!("this command does not require argument"))) - } - - fn run( - self: Arc, - _arguments: &[String], - _context_slash_command_output_sections: &[SlashCommandOutputSection], - _context_buffer: BufferSnapshot, - workspace: WeakView, - _delegate: Option>, - cx: &mut WindowContext, - ) -> Task { - 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)) - } -} diff --git a/crates/assistant_slash_commands/Cargo.toml b/crates/assistant_slash_commands/Cargo.toml new file mode 100644 index 0000000000..675daceb11 --- /dev/null +++ b/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 diff --git a/crates/assistant_slash_commands/LICENSE-GPL b/crates/assistant_slash_commands/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/assistant_slash_commands/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/assistant_slash_commands/src/assistant_slash_commands.rs b/crates/assistant_slash_commands/src/assistant_slash_commands.rs new file mode 100644 index 0000000000..8e124412b6 --- /dev/null +++ b/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 +} diff --git a/crates/assistant/src/slash_command/auto_command.rs b/crates/assistant_slash_commands/src/auto_command.rs similarity index 99% rename from crates/assistant/src/slash_command/auto_command.rs rename to crates/assistant_slash_commands/src/auto_command.rs index c72b909010..40b627609d 100644 --- a/crates/assistant/src/slash_command/auto_command.rs +++ b/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 { diff --git a/crates/assistant/src/slash_command/cargo_workspace_command.rs b/crates/assistant_slash_commands/src/cargo_workspace_command.rs similarity index 99% rename from crates/assistant/src/slash_command/cargo_workspace_command.rs rename to crates/assistant_slash_commands/src/cargo_workspace_command.rs index 968238d36e..eed3b60761 100644 --- a/crates/assistant/src/slash_command/cargo_workspace_command.rs +++ b/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, path_to_cargo_toml: &Path) -> Result { diff --git a/crates/assistant/src/slash_command/context_server_command.rs b/crates/assistant_slash_commands/src/context_server_command.rs similarity index 99% rename from crates/assistant/src/slash_command/context_server_command.rs rename to crates/assistant_slash_commands/src/context_server_command.rs index 8c53ddb773..f9baf7e675 100644 --- a/crates/assistant/src/slash_command/context_server_command.rs +++ b/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, diff --git a/crates/assistant/src/slash_command/default_command.rs b/crates/assistant_slash_commands/src/default_command.rs similarity index 98% rename from crates/assistant/src/slash_command/default_command.rs rename to crates/assistant_slash_commands/src/default_command.rs index 9577d2ac7f..6fac51143a 100644 --- a/crates/assistant/src/slash_command/default_command.rs +++ b/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 { diff --git a/crates/assistant/src/slash_command/delta_command.rs b/crates/assistant_slash_commands/src/delta_command.rs similarity index 97% rename from crates/assistant/src/slash_command/delta_command.rs rename to crates/assistant_slash_commands/src/delta_command.rs index f854bd5fdf..ba6c4dde39 100644 --- a/crates/assistant/src/slash_command/delta_command.rs +++ b/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 { diff --git a/crates/assistant/src/slash_command/diagnostics_command.rs b/crates/assistant_slash_commands/src/diagnostics_command.rs similarity index 99% rename from crates/assistant/src/slash_command/diagnostics_command.rs rename to crates/assistant_slash_commands/src/diagnostics_command.rs index 2a10283053..e73d58c7a7 100644 --- a/crates/assistant/src/slash_command/diagnostics_command.rs +++ b/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( diff --git a/crates/assistant/src/slash_command/docs_command.rs b/crates/assistant_slash_commands/src/docs_command.rs similarity index 99% rename from crates/assistant/src/slash_command/docs_command.rs rename to crates/assistant_slash_commands/src/docs_command.rs index b54f708e32..007abeb909 100644 --- a/crates/assistant/src/slash_command/docs_command.rs +++ b/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, diff --git a/crates/assistant/src/slash_command/fetch_command.rs b/crates/assistant_slash_commands/src/fetch_command.rs similarity index 99% rename from crates/assistant/src/slash_command/fetch_command.rs rename to crates/assistant_slash_commands/src/fetch_command.rs index 96ea05c302..b6b7dd5713 100644 --- a/crates/assistant/src/slash_command/fetch_command.rs +++ b/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, url: &str) -> Result { diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant_slash_commands/src/file_command.rs similarity index 99% rename from crates/assistant/src/slash_command/file_command.rs rename to crates/assistant_slash_commands/src/file_command.rs index 5fd26cc2bf..ce2355afcb 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/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() { diff --git a/crates/assistant/src/slash_command/now_command.rs b/crates/assistant_slash_commands/src/now_command.rs similarity index 98% rename from crates/assistant/src/slash_command/now_command.rs rename to crates/assistant_slash_commands/src/now_command.rs index cf81bec926..2db21a72ac 100644 --- a/crates/assistant/src/slash_command/now_command.rs +++ b/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 { diff --git a/crates/assistant/src/slash_command/project_command.rs b/crates/assistant_slash_commands/src/project_command.rs similarity index 96% rename from crates/assistant/src/slash_command/project_command.rs rename to crates/assistant_slash_commands/src/project_command.rs index ee6434ec03..bcef7e2c08 100644 --- a/crates/assistant/src/slash_command/project_command.rs +++ b/crates/assistant_slash_commands/src/project_command.rs @@ -1,33 +1,33 @@ -use super::{ - create_label_for_command, search_command::add_search_result_section, SlashCommand, - SlashCommandOutput, -}; -use crate::PromptBuilder; -use anyhow::{anyhow, Result}; -use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection, SlashCommandResult}; -use feature_flags::FeatureFlag; -use gpui::{AppContext, Task, WeakView, WindowContext}; -use language::{Anchor, CodeLabel, LspAdapterDelegate}; -use language_model::{LanguageModelRegistry, LanguageModelTool}; -use schemars::JsonSchema; -use semantic_index::SemanticDb; -use serde::Deserialize; - -pub struct ProjectSlashCommandFeatureFlag; - -impl FeatureFlag for ProjectSlashCommandFeatureFlag { - const NAME: &'static str = "project-slash-command"; -} - use std::{ fmt::Write as _, ops::DerefMut, sync::{atomic::AtomicBool, Arc}, }; +use anyhow::{anyhow, Result}; +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; + +impl FeatureFlag for ProjectSlashCommandFeatureFlag { + const NAME: &'static str = "project-slash-command"; +} + pub struct ProjectSlashCommand { prompt_builder: Arc, } diff --git a/crates/assistant/src/slash_command/prompt_after_summary.txt b/crates/assistant_slash_commands/src/prompt_after_summary.txt similarity index 100% rename from crates/assistant/src/slash_command/prompt_after_summary.txt rename to crates/assistant_slash_commands/src/prompt_after_summary.txt diff --git a/crates/assistant/src/slash_command/prompt_before_summary.txt b/crates/assistant_slash_commands/src/prompt_before_summary.txt similarity index 100% rename from crates/assistant/src/slash_command/prompt_before_summary.txt rename to crates/assistant_slash_commands/src/prompt_before_summary.txt diff --git a/crates/assistant/src/slash_command/prompt_command.rs b/crates/assistant_slash_commands/src/prompt_command.rs similarity index 98% rename from crates/assistant/src/slash_command/prompt_command.rs rename to crates/assistant_slash_commands/src/prompt_command.rs index 2091201fab..ed926e3e5d 100644 --- a/crates/assistant/src/slash_command/prompt_command.rs +++ b/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 { diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant_slash_commands/src/search_command.rs similarity index 95% rename from crates/assistant/src/slash_command/search_command.rs rename to crates/assistant_slash_commands/src/search_command.rs index ca33ded112..90204ab024 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/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 { diff --git a/crates/assistant_slash_commands/src/selection_command.rs b/crates/assistant_slash_commands/src/selection_command.rs new file mode 100644 index 0000000000..29af653e8e --- /dev/null +++ b/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, + _arguments: &[String], + _cancel: Arc, + _workspace: Option>, + _cx: &mut WindowContext, + ) -> Task>> { + Task::ready(Err(anyhow!("this command does not require argument"))) + } + + fn run( + self: Arc, + _arguments: &[String], + _context_slash_command_output_sections: &[SlashCommandOutputSection], + _context_buffer: BufferSnapshot, + workspace: WeakView, + _delegate: Option>, + cx: &mut WindowContext, + ) -> Task { + 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, +) -> Option> { + let editor = workspace + .active_item(cx) + .and_then(|item| item.act_as::(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::(); + 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::>() + .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::>() + .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) +} diff --git a/crates/assistant/src/slash_command/streaming_example_command.rs b/crates/assistant_slash_commands/src/streaming_example_command.rs similarity index 98% rename from crates/assistant/src/slash_command/streaming_example_command.rs rename to crates/assistant_slash_commands/src/streaming_example_command.rs index 729e75c228..663d9078fa 100644 --- a/crates/assistant/src/slash_command/streaming_example_command.rs +++ b/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 { diff --git a/crates/assistant/src/slash_command/symbols_command.rs b/crates/assistant_slash_commands/src/symbols_command.rs similarity index 98% rename from crates/assistant/src/slash_command/symbols_command.rs rename to crates/assistant_slash_commands/src/symbols_command.rs index 2b261bc368..e09787a91a 100644 --- a/crates/assistant/src/slash_command/symbols_command.rs +++ b/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 { diff --git a/crates/assistant/src/slash_command/tab_command.rs b/crates/assistant_slash_commands/src/tab_command.rs similarity index 99% rename from crates/assistant/src/slash_command/tab_command.rs rename to crates/assistant_slash_commands/src/tab_command.rs index adcb8398be..a343ce111d 100644 --- a/crates/assistant/src/slash_command/tab_command.rs +++ b/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"; diff --git a/crates/assistant/src/slash_command/terminal_command.rs b/crates/assistant_slash_commands/src/terminal_command.rs similarity index 97% rename from crates/assistant/src/slash_command/terminal_command.rs rename to crates/assistant_slash_commands/src/terminal_command.rs index 84dbb7146f..5abc93deed 100644 --- a/crates/assistant/src/slash_command/terminal_command.rs +++ b/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()