Detailed changes
@@ -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"
@@ -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" }
@@ -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
@@ -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);
}
}
@@ -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,
@@ -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;
@@ -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());
@@ -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,
@@ -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
-}
@@ -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))
- }
-}
@@ -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
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -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
+}
@@ -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 {
@@ -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> {
@@ -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>,
@@ -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 {
@@ -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 {
@@ -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(
@@ -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,
@@ -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> {
@@ -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() {
@@ -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 {
@@ -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>,
}
@@ -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 {
@@ -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 {
@@ -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)
+}
@@ -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 {
@@ -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 {
@@ -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";
@@ -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()