Cargo.lock 🔗
@@ -455,6 +455,7 @@ dependencies = [
"language",
"parking_lot",
"serde",
+ "serde_json",
"workspace",
]
Antonio Scandurra and Roy created
Release Notes:
- Added a new `/delta` command to re-insert changed files that were
previously included in a context.
---------
Co-authored-by: Roy <roy@anthropic.com>
Cargo.lock | 1
crates/assistant/src/assistant.rs | 7
crates/assistant/src/assistant_panel.rs | 17
crates/assistant/src/context.rs | 15
crates/assistant/src/context/context_tests.rs | 5
crates/assistant/src/slash_command.rs | 1
crates/assistant/src/slash_command/auto_command.rs | 4
crates/assistant/src/slash_command/context_server_command.rs | 6
crates/assistant/src/slash_command/default_command.rs | 5
crates/assistant/src/slash_command/delta_command.rs | 109 ++
crates/assistant/src/slash_command/diagnostics_command.rs | 234 +---
crates/assistant/src/slash_command/docs_command.rs | 5
crates/assistant/src/slash_command/fetch_command.rs | 5
crates/assistant/src/slash_command/file_command.rs | 260 ++--
crates/assistant/src/slash_command/now_command.rs | 5
crates/assistant/src/slash_command/project_command.rs | 5
crates/assistant/src/slash_command/prompt_command.rs | 5
crates/assistant/src/slash_command/search_command.rs | 3
crates/assistant/src/slash_command/symbols_command.rs | 5
crates/assistant/src/slash_command/tab_command.rs | 47
crates/assistant/src/slash_command/terminal_command.rs | 5
crates/assistant/src/slash_command/workflow_command.rs | 5
crates/assistant_slash_command/Cargo.toml | 1
crates/assistant_slash_command/src/assistant_slash_command.rs | 13
crates/extension/src/extension_slash_command.rs | 5
crates/proto/proto/zed.proto | 1
26 files changed, 408 insertions(+), 366 deletions(-)
@@ -455,6 +455,7 @@ dependencies = [
"language",
"parking_lot",
"serde",
+ "serde_json",
"workspace",
]
@@ -41,9 +41,9 @@ use semantic_index::{CloudEmbeddingProvider, SemanticDb};
use serde::{Deserialize, Serialize};
use settings::{update_settings_file, Settings, SettingsStore};
use slash_command::{
- auto_command, context_server_command, default_command, diagnostics_command, docs_command,
- fetch_command, file_command, now_command, project_command, prompt_command, search_command,
- symbols_command, tab_command, terminal_command, workflow_command,
+ auto_command, context_server_command, default_command, delta_command, diagnostics_command,
+ docs_command, fetch_command, file_command, now_command, project_command, prompt_command,
+ search_command, symbols_command, tab_command, terminal_command, workflow_command,
};
use std::path::PathBuf;
use std::sync::Arc;
@@ -367,6 +367,7 @@ fn register_slash_commands(prompt_builder: Option<Arc<PromptBuilder>>, cx: &mut
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(project_command::ProjectSlashCommand, true);
@@ -1906,7 +1906,22 @@ impl ContextEditor {
cx: &mut ViewContext<Self>,
) {
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
- let output = command.run(arguments, workspace, self.lsp_adapter_delegate.clone(), cx);
+ let context = self.context.read(cx);
+ let sections = context
+ .slash_command_output_sections()
+ .into_iter()
+ .filter(|section| section.is_valid(context.buffer().read(cx)))
+ .cloned()
+ .collect::<Vec<_>>();
+ let snapshot = context.buffer().read(cx).snapshot();
+ let output = command.run(
+ arguments,
+ §ions,
+ snapshot,
+ workspace,
+ self.lsp_adapter_delegate.clone(),
+ cx,
+ );
self.context.update(cx, |context, cx| {
context.insert_command_output(
command_range,
@@ -48,7 +48,7 @@ use std::{
};
use telemetry_events::AssistantKind;
use text::BufferSnapshot;
-use util::{post_inc, TryFutureExt};
+use util::{post_inc, ResultExt, TryFutureExt};
use uuid::Uuid;
#[derive(Clone, Eq, PartialEq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
@@ -162,6 +162,9 @@ impl ContextOperation {
)?,
icon: section.icon_name.parse()?,
label: section.label.into(),
+ metadata: section
+ .metadata
+ .and_then(|metadata| serde_json::from_str(&metadata).log_err()),
})
})
.collect::<Result<Vec<_>>>()?,
@@ -242,6 +245,9 @@ impl ContextOperation {
)),
icon_name: icon_name.to_string(),
label: section.label.to_string(),
+ metadata: section.metadata.as_ref().and_then(|metadata| {
+ serde_json::to_string(metadata).log_err()
+ }),
}
})
.collect(),
@@ -635,12 +641,13 @@ impl Context {
.slash_command_output_sections
.iter()
.filter_map(|section| {
- let range = section.range.to_offset(buffer);
- if section.range.start.is_valid(buffer) && !range.is_empty() {
+ if section.is_valid(buffer) {
+ let range = section.range.to_offset(buffer);
Some(assistant_slash_command::SlashCommandOutputSection {
range,
icon: section.icon,
label: section.label.clone(),
+ metadata: section.metadata.clone(),
})
} else {
None
@@ -1825,6 +1832,7 @@ impl Context {
..buffer.anchor_before(start + section.range.end),
icon: section.icon,
label: section.label,
+ metadata: section.metadata,
})
.collect::<Vec<_>>();
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
@@ -2977,6 +2985,7 @@ impl SavedContext {
..buffer.anchor_before(section.range.end),
icon: section.icon,
label: section.label,
+ metadata: section.metadata,
}
})
.collect(),
@@ -12,7 +12,7 @@ use assistant_slash_command::{
use collections::HashSet;
use fs::FakeFs;
use gpui::{AppContext, Model, SharedString, Task, TestAppContext, WeakView};
-use language::{Buffer, LanguageRegistry, LspAdapterDelegate};
+use language::{Buffer, BufferSnapshot, LanguageRegistry, LspAdapterDelegate};
use language_model::{LanguageModelCacheConfiguration, LanguageModelRegistry, Role};
use parking_lot::Mutex;
use project::Project;
@@ -1089,6 +1089,7 @@ async fn test_random_context_collaboration(cx: &mut TestAppContext, mut rng: Std
range: section_start..section_end,
icon: ui::IconName::Ai,
label: "section".into(),
+ metadata: None,
});
}
@@ -1425,6 +1426,8 @@ impl SlashCommand for FakeSlashCommand {
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,
@@ -22,6 +22,7 @@ use workspace::Workspace;
pub mod auto_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;
@@ -1,7 +1,7 @@
use super::create_label_for_command;
use super::{SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Result};
-use assistant_slash_command::ArgumentCompletion;
+use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use feature_flags::FeatureFlag;
use futures::StreamExt;
use gpui::{AppContext, AsyncAppContext, Task, WeakView};
@@ -87,6 +87,8 @@ impl SlashCommand for AutoCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
+ _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
+ _context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -9,7 +9,7 @@ use context_servers::{
protocol::PromptInfo,
};
use gpui::{Task, WeakView, WindowContext};
-use language::{CodeLabel, LspAdapterDelegate};
+use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use text::LineEnding;
@@ -96,7 +96,6 @@ impl SlashCommand for ContextServerSlashCommand {
replace_previous_arguments: false,
})
.collect();
-
Ok(completions)
})
} else {
@@ -107,6 +106,8 @@ impl SlashCommand for ContextServerSlashCommand {
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,
@@ -141,6 +142,7 @@ impl SlashCommand for ContextServerSlashCommand {
.description
.unwrap_or(format!("Result from {}", prompt_name)),
),
+ metadata: None,
}],
text: prompt,
run_commands_in_text: false,
@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use gpui::{Task, WeakView};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use std::{
fmt::Write,
sync::{atomic::AtomicBool, Arc},
@@ -43,6 +43,8 @@ impl SlashCommand for DefaultSlashCommand {
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,
@@ -70,6 +72,7 @@ impl SlashCommand for DefaultSlashCommand {
range: 0..text.len(),
icon: IconName::Library,
label: "Default".into(),
+ metadata: None,
}],
text,
run_commands_in_text: true,
@@ -0,0 +1,109 @@
+use crate::slash_command::file_command::{FileCommandMetadata, FileSlashCommand};
+use anyhow::Result;
+use assistant_slash_command::{
+ ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
+};
+use collections::HashSet;
+use futures::future;
+use gpui::{Task, WeakView, WindowContext};
+use language::{BufferSnapshot, LspAdapterDelegate};
+use std::sync::{atomic::AtomicBool, Arc};
+use text::OffsetRangeExt;
+use workspace::Workspace;
+
+pub(crate) struct DeltaSlashCommand;
+
+impl SlashCommand for DeltaSlashCommand {
+ fn name(&self) -> String {
+ "delta".into()
+ }
+
+ fn description(&self) -> String {
+ "re-insert changed files".into()
+ }
+
+ fn menu_text(&self) -> String {
+ "Re-insert Changed Files".into()
+ }
+
+ fn requires_argument(&self) -> bool {
+ false
+ }
+
+ fn complete_argument(
+ self: Arc<Self>,
+ _arguments: &[String],
+ _cancellation_flag: Arc<AtomicBool>,
+ _workspace: Option<WeakView<Workspace>>,
+ _cx: &mut WindowContext,
+ ) -> Task<Result<Vec<ArgumentCompletion>>> {
+ unimplemented!()
+ }
+
+ 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<Result<SlashCommandOutput>> {
+ let mut paths = HashSet::default();
+ let mut file_command_old_outputs = Vec::new();
+ let mut file_command_new_outputs = Vec::new();
+ for section in context_slash_command_output_sections.iter().rev() {
+ if let Some(metadata) = section
+ .metadata
+ .as_ref()
+ .and_then(|value| serde_json::from_value::<FileCommandMetadata>(value.clone()).ok())
+ {
+ if paths.insert(metadata.path.clone()) {
+ file_command_old_outputs.push(
+ context_buffer
+ .as_rope()
+ .slice(section.range.to_offset(&context_buffer)),
+ );
+ file_command_new_outputs.push(Arc::new(FileSlashCommand).run(
+ &[metadata.path.clone()],
+ context_slash_command_output_sections,
+ context_buffer.clone(),
+ workspace.clone(),
+ delegate.clone(),
+ cx,
+ ));
+ }
+ }
+ }
+
+ cx.background_executor().spawn(async move {
+ let mut output = SlashCommandOutput::default();
+
+ let file_command_new_outputs = future::join_all(file_command_new_outputs).await;
+ for (old_text, new_output) in file_command_old_outputs
+ .into_iter()
+ .zip(file_command_new_outputs)
+ {
+ if let Ok(new_output) = new_output {
+ if let Some(file_command_range) = new_output.sections.first() {
+ let new_text = &new_output.text[file_command_range.range.clone()];
+ if old_text.chars().ne(new_text.chars()) {
+ output.sections.extend(new_output.sections.into_iter().map(
+ |section| SlashCommandOutputSection {
+ range: output.text.len() + section.range.start
+ ..output.text.len() + section.range.end,
+ icon: section.icon,
+ label: section.label,
+ metadata: section.metadata,
+ },
+ ));
+ output.text.push_str(&new_output.text);
+ }
+ }
+ }
+ }
+
+ Ok(output)
+ })
+ }
+}
@@ -9,10 +9,9 @@ use language::{
};
use project::{DiagnosticSummary, PathMatchCandidateSet, Project};
use rope::Point;
-use std::fmt::Write;
-use std::path::{Path, PathBuf};
use std::{
- ops::Range,
+ fmt::Write,
+ path::{Path, PathBuf},
sync::{atomic::AtomicBool, Arc},
};
use ui::prelude::*;
@@ -163,6 +162,8 @@ impl SlashCommand for DiagnosticsSlashCommand {
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,
@@ -175,68 +176,7 @@ impl SlashCommand for DiagnosticsSlashCommand {
let task = collect_diagnostics(workspace.read(cx).project().clone(), options, cx);
- cx.spawn(move |_| async move {
- let Some((text, sections)) = task.await? else {
- return Ok(SlashCommandOutput {
- sections: vec![SlashCommandOutputSection {
- range: 0..1,
- icon: IconName::Library,
- label: "No Diagnostics".into(),
- }],
- text: "\n".to_string(),
- run_commands_in_text: true,
- });
- };
-
- let sections = sections
- .into_iter()
- .map(|(range, placeholder_type)| SlashCommandOutputSection {
- range,
- icon: match placeholder_type {
- PlaceholderType::Root(_, _) => IconName::Warning,
- PlaceholderType::File(_) => IconName::File,
- PlaceholderType::Diagnostic(DiagnosticType::Error, _) => IconName::XCircle,
- PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
- IconName::Warning
- }
- },
- label: match placeholder_type {
- PlaceholderType::Root(summary, source) => {
- let mut label = String::new();
- label.push_str("Diagnostics");
- if let Some(source) = source {
- write!(label, " ({})", source).unwrap();
- }
-
- if summary.error_count > 0 || summary.warning_count > 0 {
- label.push(':');
-
- if summary.error_count > 0 {
- write!(label, " {} errors", summary.error_count).unwrap();
- if summary.warning_count > 0 {
- label.push_str(",");
- }
- }
-
- if summary.warning_count > 0 {
- write!(label, " {} warnings", summary.warning_count).unwrap();
- }
- }
-
- label.into()
- }
- PlaceholderType::File(file_path) => file_path.into(),
- PlaceholderType::Diagnostic(_, message) => message.into(),
- },
- })
- .collect();
-
- Ok(SlashCommandOutput {
- text,
- sections,
- run_commands_in_text: false,
- })
- })
+ cx.spawn(move |_| async move { task.await?.ok_or_else(|| anyhow!("No diagnostics found")) })
}
}
@@ -277,7 +217,7 @@ fn collect_diagnostics(
project: Model<Project>,
options: Options,
cx: &mut AppContext,
-) -> Task<Result<Option<(String, Vec<(Range<usize>, PlaceholderType)>)>>> {
+) -> Task<Result<Option<SlashCommandOutput>>> {
let error_source = if let Some(path_matcher) = &options.path_matcher {
debug_assert_eq!(path_matcher.sources().len(), 1);
Some(path_matcher.sources().first().cloned().unwrap_or_default())
@@ -318,13 +258,13 @@ fn collect_diagnostics(
.collect();
cx.spawn(|mut cx| async move {
- let mut text = String::new();
+ let mut output = SlashCommandOutput::default();
+
if let Some(error_source) = error_source.as_ref() {
- writeln!(text, "diagnostics: {}", error_source).unwrap();
+ writeln!(output.text, "diagnostics: {}", error_source).unwrap();
} else {
- writeln!(text, "diagnostics").unwrap();
+ writeln!(output.text, "diagnostics").unwrap();
}
- let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
let mut project_summary = DiagnosticSummary::default();
for (project_path, path, summary) in diagnostic_summaries {
@@ -341,10 +281,10 @@ fn collect_diagnostics(
continue;
}
- let last_end = text.len();
+ let last_end = output.text.len();
let file_path = path.to_string_lossy().to_string();
if !glob_is_exact_file_match {
- writeln!(&mut text, "{file_path}").unwrap();
+ writeln!(&mut output.text, "{file_path}").unwrap();
}
if let Some(buffer) = project_handle
@@ -352,75 +292,73 @@ fn collect_diagnostics(
.await
.log_err()
{
- collect_buffer_diagnostics(
- &mut text,
- &mut sections,
- cx.read_model(&buffer, |buffer, _| buffer.snapshot())?,
- options.include_warnings,
- );
+ let snapshot = cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
+ collect_buffer_diagnostics(&mut output, &snapshot, options.include_warnings);
}
if !glob_is_exact_file_match {
- sections.push((
- last_end..text.len().saturating_sub(1),
- PlaceholderType::File(file_path),
- ))
+ output.sections.push(SlashCommandOutputSection {
+ range: last_end..output.text.len().saturating_sub(1),
+ icon: IconName::File,
+ label: file_path.into(),
+ metadata: None,
+ });
}
}
// No diagnostics found
- if sections.is_empty() {
+ if output.sections.is_empty() {
return Ok(None);
}
- sections.push((
- 0..text.len(),
- PlaceholderType::Root(project_summary, error_source),
- ));
- Ok(Some((text, sections)))
- })
-}
-
-pub fn buffer_has_error_diagnostics(snapshot: &BufferSnapshot) -> bool {
- for (_, group) in snapshot.diagnostic_groups(None) {
- let entry = &group.entries[group.primary_ix];
- if entry.diagnostic.severity == DiagnosticSeverity::ERROR {
- return true;
+ let mut label = String::new();
+ label.push_str("Diagnostics");
+ if let Some(source) = error_source {
+ write!(label, " ({})", source).unwrap();
}
- }
- false
-}
-pub fn write_single_file_diagnostics(
- output: &mut String,
- path: Option<&Path>,
- snapshot: &BufferSnapshot,
-) -> bool {
- if let Some(path) = path {
- if buffer_has_error_diagnostics(&snapshot) {
- output.push_str("/diagnostics ");
- output.push_str(&path.to_string_lossy());
- return true;
+ if project_summary.error_count > 0 || project_summary.warning_count > 0 {
+ label.push(':');
+
+ if project_summary.error_count > 0 {
+ write!(label, " {} errors", project_summary.error_count).unwrap();
+ if project_summary.warning_count > 0 {
+ label.push_str(",");
+ }
+ }
+
+ if project_summary.warning_count > 0 {
+ write!(label, " {} warnings", project_summary.warning_count).unwrap();
+ }
}
- }
- false
+
+ output.sections.insert(
+ 0,
+ SlashCommandOutputSection {
+ range: 0..output.text.len(),
+ icon: IconName::Warning,
+ label: label.into(),
+ metadata: None,
+ },
+ );
+
+ Ok(Some(output))
+ })
}
-fn collect_buffer_diagnostics(
- text: &mut String,
- sections: &mut Vec<(Range<usize>, PlaceholderType)>,
- snapshot: BufferSnapshot,
+pub fn collect_buffer_diagnostics(
+ output: &mut SlashCommandOutput,
+ snapshot: &BufferSnapshot,
include_warnings: bool,
) {
for (_, group) in snapshot.diagnostic_groups(None) {
let entry = &group.entries[group.primary_ix];
- collect_diagnostic(text, sections, entry, &snapshot, include_warnings)
+ collect_diagnostic(output, entry, &snapshot, include_warnings)
}
}
fn collect_diagnostic(
- text: &mut String,
- sections: &mut Vec<(Range<usize>, PlaceholderType)>,
+ output: &mut SlashCommandOutput,
entry: &DiagnosticEntry<Anchor>,
snapshot: &BufferSnapshot,
include_warnings: bool,
@@ -428,17 +366,17 @@ fn collect_diagnostic(
const EXCERPT_EXPANSION_SIZE: u32 = 2;
const MAX_MESSAGE_LENGTH: usize = 2000;
- let ty = match entry.diagnostic.severity {
+ let (ty, icon) = match entry.diagnostic.severity {
DiagnosticSeverity::WARNING => {
if !include_warnings {
return;
}
- DiagnosticType::Warning
+ ("warning", IconName::Warning)
}
- DiagnosticSeverity::ERROR => DiagnosticType::Error,
+ DiagnosticSeverity::ERROR => ("error", IconName::XCircle),
_ => return,
};
- let prev_len = text.len();
+ let prev_len = output.text.len();
let range = entry.range.to_point(snapshot);
let diagnostic_row_number = range.start.row + 1;
@@ -448,11 +386,11 @@ fn collect_diagnostic(
let excerpt_range =
Point::new(start_row, 0).to_offset(&snapshot)..Point::new(end_row, 0).to_offset(&snapshot);
- text.push_str("```");
+ output.text.push_str("```");
if let Some(language_name) = snapshot.language().map(|l| l.code_fence_block_name()) {
- text.push_str(&language_name);
+ output.text.push_str(&language_name);
}
- text.push('\n');
+ output.text.push('\n');
let mut buffer_text = String::new();
for chunk in snapshot.text_for_range(excerpt_range) {
@@ -461,46 +399,26 @@ fn collect_diagnostic(
for (i, line) in buffer_text.lines().enumerate() {
let line_number = start_row + i as u32 + 1;
- writeln!(text, "{}", line).unwrap();
+ writeln!(output.text, "{}", line).unwrap();
if line_number == diagnostic_row_number {
- text.push_str("//");
- let prev_len = text.len();
- write!(text, " {}: ", ty.as_str()).unwrap();
- let padding = text.len() - prev_len;
+ output.text.push_str("//");
+ let prev_len = output.text.len();
+ write!(output.text, " {}: ", ty).unwrap();
+ let padding = output.text.len() - prev_len;
let message = util::truncate(&entry.diagnostic.message, MAX_MESSAGE_LENGTH)
.replace('\n', format!("\n//{:padding$}", "").as_str());
- writeln!(text, "{message}").unwrap();
+ writeln!(output.text, "{message}").unwrap();
}
}
- writeln!(text, "```").unwrap();
- sections.push((
- prev_len..text.len().saturating_sub(1),
- PlaceholderType::Diagnostic(ty, entry.diagnostic.message.clone()),
- ))
-}
-
-#[derive(Clone)]
-pub enum PlaceholderType {
- Root(DiagnosticSummary, Option<String>),
- File(String),
- Diagnostic(DiagnosticType, String),
-}
-
-#[derive(Copy, Clone)]
-pub enum DiagnosticType {
- Warning,
- Error,
-}
-
-impl DiagnosticType {
- pub fn as_str(&self) -> &'static str {
- match self {
- DiagnosticType::Warning => "warning",
- DiagnosticType::Error => "error",
- }
- }
+ writeln!(output.text, "```").unwrap();
+ output.sections.push(SlashCommandOutputSection {
+ range: prev_len..output.text.len().saturating_sub(1),
+ icon,
+ label: entry.diagnostic.message.clone().into(),
+ metadata: None,
+ });
}
@@ -12,7 +12,7 @@ use indexed_docs::{
DocsDotRsProvider, IndexedDocsRegistry, IndexedDocsStore, LocalRustdocProvider, PackageName,
ProviderId,
};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath};
use ui::prelude::*;
use util::{maybe, ResultExt};
@@ -269,6 +269,8 @@ impl SlashCommand for DocsSlashCommand {
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,
@@ -349,6 +351,7 @@ impl SlashCommand for DocsSlashCommand {
range,
icon: IconName::FileDoc,
label: format!("docs ({provider}): {key}",).into(),
+ metadata: None,
})
.collect(),
run_commands_in_text: false,
@@ -11,7 +11,7 @@ use futures::AsyncReadExt;
use gpui::{Task, WeakView};
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -128,6 +128,8 @@ impl SlashCommand for FetchSlashCommand {
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,
@@ -161,6 +163,7 @@ impl SlashCommand for FetchSlashCommand {
range,
icon: IconName::AtSign,
label: format!("fetch {}", url).into(),
+ metadata: None,
}],
run_commands_in_text: false,
})
@@ -1,10 +1,11 @@
-use super::{diagnostics_command::write_single_file_diagnostics, SlashCommand, SlashCommandOutput};
+use super::{diagnostics_command::collect_buffer_diagnostics, SlashCommand, SlashCommandOutput};
use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{AfterCompletion, ArgumentCompletion, SlashCommandOutputSection};
use fuzzy::PathMatch;
use gpui::{AppContext, Model, Task, View, WeakView};
use language::{BufferSnapshot, CodeLabel, HighlightId, LineEnding, LspAdapterDelegate};
use project::{PathMatchCandidateSet, Project};
+use serde::{Deserialize, Serialize};
use std::{
fmt::Write,
ops::Range,
@@ -175,6 +176,8 @@ impl SlashCommand for FileSlashCommand {
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,
@@ -187,54 +190,15 @@ impl SlashCommand for FileSlashCommand {
return Task::ready(Err(anyhow!("missing path")));
};
- let task = collect_files(workspace.read(cx).project().clone(), arguments, cx);
-
- cx.foreground_executor().spawn(async move {
- let output = task.await?;
- Ok(SlashCommandOutput {
- text: output.completion_text,
- sections: output
- .files
- .into_iter()
- .map(|file| {
- build_entry_output_section(
- file.range_in_text,
- Some(&file.path),
- file.entry_type == EntryType::Directory,
- None,
- )
- })
- .collect(),
- run_commands_in_text: true,
- })
- })
+ collect_files(workspace.read(cx).project().clone(), arguments, cx)
}
}
-#[derive(Clone, Copy, PartialEq, Debug)]
-enum EntryType {
- File,
- Directory,
-}
-
-#[derive(Clone, PartialEq, Debug)]
-struct FileCommandOutput {
- completion_text: String,
- files: Vec<OutputFile>,
-}
-
-#[derive(Clone, PartialEq, Debug)]
-struct OutputFile {
- range_in_text: Range<usize>,
- path: PathBuf,
- entry_type: EntryType,
-}
-
fn collect_files(
project: Model<Project>,
glob_inputs: &[String],
cx: &mut AppContext,
-) -> Task<Result<FileCommandOutput>> {
+) -> Task<Result<SlashCommandOutput>> {
let Ok(matchers) = glob_inputs
.into_iter()
.map(|glob_input| {
@@ -254,8 +218,7 @@ fn collect_files(
.collect::<Vec<_>>();
cx.spawn(|mut cx| async move {
- let mut text = String::new();
- let mut ranges = Vec::new();
+ let mut output = SlashCommandOutput::default();
for snapshot in snapshots {
let worktree_id = snapshot.id();
let mut directory_stack: Vec<(Arc<Path>, String, usize)> = Vec::new();
@@ -279,11 +242,12 @@ fn collect_files(
break;
}
let (_, entry_name, start) = directory_stack.pop().unwrap();
- ranges.push(OutputFile {
- range_in_text: start..text.len().saturating_sub(1),
- path: PathBuf::from(entry_name),
- entry_type: EntryType::Directory,
- });
+ output.sections.push(build_entry_output_section(
+ start..output.text.len().saturating_sub(1),
+ Some(&PathBuf::from(entry_name)),
+ true,
+ None,
+ ));
}
let filename = entry
@@ -315,21 +279,23 @@ fn collect_files(
continue;
}
let prefix_paths = folded_directory_names_stack.drain(..).as_slice().join("/");
- let entry_start = text.len();
+ let entry_start = output.text.len();
if prefix_paths.is_empty() {
if is_top_level_directory {
- text.push_str(&path_including_worktree_name.to_string_lossy());
+ output
+ .text
+ .push_str(&path_including_worktree_name.to_string_lossy());
is_top_level_directory = false;
} else {
- text.push_str(&filename);
+ output.text.push_str(&filename);
}
directory_stack.push((entry.path.clone(), filename, entry_start));
} else {
let entry_name = format!("{}/{}", prefix_paths, &filename);
- text.push_str(&entry_name);
+ output.text.push_str(&entry_name);
directory_stack.push((entry.path.clone(), entry_name, entry_start));
}
- text.push('\n');
+ output.text.push('\n');
} else if entry.is_file() {
let Some(open_buffer_task) = project_handle
.update(&mut cx, |project, cx| {
@@ -340,28 +306,13 @@ fn collect_files(
continue;
};
if let Some(buffer) = open_buffer_task.await.log_err() {
- let buffer_snapshot =
- cx.read_model(&buffer, |buffer, _| buffer.snapshot())?;
- let prev_len = text.len();
- collect_file_content(
- &mut text,
- &buffer_snapshot,
- path_including_worktree_name.to_string_lossy().to_string(),
- );
- text.push('\n');
- if !write_single_file_diagnostics(
- &mut text,
+ let snapshot = buffer.read_with(&cx, |buffer, _| buffer.snapshot())?;
+ append_buffer_to_output(
+ &snapshot,
Some(&path_including_worktree_name),
- &buffer_snapshot,
- ) {
- text.pop();
- }
- ranges.push(OutputFile {
- range_in_text: prev_len..text.len(),
- path: path_including_worktree_name,
- entry_type: EntryType::File,
- });
- text.push('\n');
+ &mut output,
+ )
+ .log_err();
}
}
}
@@ -371,42 +322,26 @@ fn collect_files(
let mut root_path = PathBuf::new();
root_path.push(snapshot.root_name());
root_path.push(&dir);
- ranges.push(OutputFile {
- range_in_text: start..text.len(),
- path: root_path,
- entry_type: EntryType::Directory,
- });
+ output.sections.push(build_entry_output_section(
+ start..output.text.len(),
+ Some(&root_path),
+ true,
+ None,
+ ));
} else {
- ranges.push(OutputFile {
- range_in_text: start..text.len(),
- path: PathBuf::from(entry.as_str()),
- entry_type: EntryType::Directory,
- });
+ output.sections.push(build_entry_output_section(
+ start..output.text.len(),
+ Some(&PathBuf::from(entry.as_str())),
+ true,
+ None,
+ ));
}
}
}
- Ok(FileCommandOutput {
- completion_text: text,
- files: ranges,
- })
+ Ok(output)
})
}
-fn collect_file_content(buffer: &mut String, snapshot: &BufferSnapshot, filename: String) {
- let mut content = snapshot.text();
- LineEnding::normalize(&mut content);
- buffer.reserve(filename.len() + content.len() + 9);
- buffer.push_str(&codeblock_fence_for_path(
- Some(&PathBuf::from(filename)),
- None,
- ));
- buffer.push_str(&content);
- if !buffer.ends_with('\n') {
- buffer.push('\n');
- }
- buffer.push_str("```");
-}
-
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
let mut text = String::new();
write!(text, "```").unwrap();
@@ -429,6 +364,11 @@ pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32
text
}
+#[derive(Serialize, Deserialize)]
+pub struct FileCommandMetadata {
+ pub path: String,
+}
+
pub fn build_entry_output_section(
range: Range<usize>,
path: Option<&Path>,
@@ -454,6 +394,16 @@ pub fn build_entry_output_section(
range,
icon,
label: label.into(),
+ metadata: if is_directory {
+ None
+ } else {
+ path.and_then(|path| {
+ serde_json::to_value(FileCommandMetadata {
+ path: path.to_string_lossy().to_string(),
+ })
+ .ok()
+ })
+ },
}
}
@@ -539,6 +489,36 @@ mod custom_path_matcher {
}
}
+pub fn append_buffer_to_output(
+ buffer: &BufferSnapshot,
+ path: Option<&Path>,
+ output: &mut SlashCommandOutput,
+) -> Result<()> {
+ let prev_len = output.text.len();
+
+ let mut content = buffer.text();
+ LineEnding::normalize(&mut content);
+ output.text.push_str(&codeblock_fence_for_path(path, None));
+ output.text.push_str(&content);
+ if !output.text.ends_with('\n') {
+ output.text.push('\n');
+ }
+ output.text.push_str("```");
+ output.text.push('\n');
+
+ let section_ix = output.sections.len();
+ collect_buffer_diagnostics(output, buffer, false);
+
+ output.sections.insert(
+ section_ix,
+ build_entry_output_section(prev_len..output.text.len(), path, false, None),
+ );
+
+ output.text.push('\n');
+
+ Ok(())
+}
+
#[cfg(test)]
mod test {
use fs::FakeFs;
@@ -591,9 +571,9 @@ mod test {
.await
.unwrap();
- assert!(result_1.completion_text.starts_with("root/dir"));
+ assert!(result_1.text.starts_with("root/dir"));
// 4 files + 2 directories
- assert_eq!(6, result_1.files.len());
+ assert_eq!(result_1.sections.len(), 6);
let result_2 = cx
.update(|cx| collect_files(project.clone(), &["root/dir/".to_string()], cx))
@@ -607,9 +587,9 @@ mod test {
.await
.unwrap();
- assert!(result.completion_text.starts_with("root/dir"));
+ assert!(result.text.starts_with("root/dir"));
// 5 files + 2 directories
- assert_eq!(7, result.files.len());
+ assert_eq!(result.sections.len(), 7);
// Ensure that the project lasts until after the last await
drop(project);
@@ -654,36 +634,27 @@ mod test {
.unwrap();
// Sanity check
- assert!(result.completion_text.starts_with("zed/assets/themes\n"));
- assert_eq!(7, result.files.len());
+ assert!(result.text.starts_with("zed/assets/themes\n"));
+ assert_eq!(result.sections.len(), 7);
// Ensure that full file paths are included in the real output
- assert!(result
- .completion_text
- .contains("zed/assets/themes/andromeda/LICENSE"));
- assert!(result
- .completion_text
- .contains("zed/assets/themes/ayu/LICENSE"));
- assert!(result
- .completion_text
- .contains("zed/assets/themes/summercamp/LICENSE"));
-
- assert_eq!("summercamp", result.files[5].path.to_string_lossy());
+ assert!(result.text.contains("zed/assets/themes/andromeda/LICENSE"));
+ assert!(result.text.contains("zed/assets/themes/ayu/LICENSE"));
+ assert!(result.text.contains("zed/assets/themes/summercamp/LICENSE"));
+
+ assert_eq!(result.sections[5].label, "summercamp");
// Ensure that things are in descending order, with properly relativized paths
assert_eq!(
- "zed/assets/themes/andromeda/LICENSE",
- result.files[0].path.to_string_lossy()
- );
- assert_eq!("andromeda", result.files[1].path.to_string_lossy());
- assert_eq!(
- "zed/assets/themes/ayu/LICENSE",
- result.files[2].path.to_string_lossy()
+ result.sections[0].label,
+ "zed/assets/themes/andromeda/LICENSE"
);
- assert_eq!("ayu", result.files[3].path.to_string_lossy());
+ assert_eq!(result.sections[1].label, "andromeda");
+ assert_eq!(result.sections[2].label, "zed/assets/themes/ayu/LICENSE");
+ assert_eq!(result.sections[3].label, "ayu");
assert_eq!(
- "zed/assets/themes/summercamp/LICENSE",
- result.files[4].path.to_string_lossy()
+ result.sections[4].label,
+ "zed/assets/themes/summercamp/LICENSE"
);
// Ensure that the project lasts until after the last await
@@ -723,27 +694,24 @@ mod test {
.await
.unwrap();
- assert!(result.completion_text.starts_with("zed/assets/themes\n"));
- assert_eq!(
- "zed/assets/themes/LICENSE",
- result.files[0].path.to_string_lossy()
- );
+ assert!(result.text.starts_with("zed/assets/themes\n"));
+ assert_eq!(result.sections[0].label, "zed/assets/themes/LICENSE");
assert_eq!(
- "zed/assets/themes/summercamp/LICENSE",
- result.files[1].path.to_string_lossy()
+ result.sections[1].label,
+ "zed/assets/themes/summercamp/LICENSE"
);
assert_eq!(
- "zed/assets/themes/summercamp/subdir/LICENSE",
- result.files[2].path.to_string_lossy()
+ result.sections[2].label,
+ "zed/assets/themes/summercamp/subdir/LICENSE"
);
assert_eq!(
- "zed/assets/themes/summercamp/subdir/subsubdir/LICENSE",
- result.files[3].path.to_string_lossy()
+ result.sections[3].label,
+ "zed/assets/themes/summercamp/subdir/subsubdir/LICENSE"
);
- assert_eq!("subsubdir", result.files[4].path.to_string_lossy());
- assert_eq!("subdir", result.files[5].path.to_string_lossy());
- assert_eq!("summercamp", result.files[6].path.to_string_lossy());
- assert_eq!("zed/assets/themes", result.files[7].path.to_string_lossy());
+ assert_eq!(result.sections[4].label, "subsubdir");
+ assert_eq!(result.sections[5].label, "subdir");
+ assert_eq!(result.sections[6].label, "summercamp");
+ assert_eq!(result.sections[7].label, "zed/assets/themes");
// Ensure that the project lasts until after the last await
drop(project);
@@ -7,7 +7,7 @@ use assistant_slash_command::{
};
use chrono::Local;
use gpui::{Task, WeakView};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -43,6 +43,8 @@ impl SlashCommand for NowSlashCommand {
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,
@@ -57,6 +59,7 @@ impl SlashCommand for NowSlashCommand {
range,
icon: IconName::CountdownTimer,
label: now.to_rfc2822().into(),
+ metadata: None,
}],
run_commands_in_text: false,
}))
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use fs::Fs;
use gpui::{AppContext, Model, Task, WeakView};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use project::{Project, ProjectPath};
use std::{
fmt::Write,
@@ -118,6 +118,8 @@ impl SlashCommand for ProjectSlashCommand {
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,
@@ -140,6 +142,7 @@ impl SlashCommand for ProjectSlashCommand {
range,
icon: IconName::FileTree,
label: "Project".into(),
+ metadata: None,
}],
run_commands_in_text: false,
})
@@ -3,7 +3,7 @@ use crate::prompt_library::PromptStore;
use anyhow::{anyhow, Context, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use gpui::{Task, WeakView};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::{atomic::AtomicBool, Arc};
use ui::prelude::*;
use workspace::Workspace;
@@ -56,6 +56,8 @@ impl SlashCommand for PromptSlashCommand {
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,
@@ -95,6 +97,7 @@ impl SlashCommand for PromptSlashCommand {
range,
icon: IconName::Library,
label: title,
+ metadata: None,
}],
run_commands_in_text: true,
})
@@ -60,6 +60,8 @@ impl SlashCommand for SearchSlashCommand {
fn run(
self: Arc<Self>,
arguments: &[String],
+ _context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
+ _context_buffer: language::BufferSnapshot,
workspace: WeakView<Workspace>,
_delegate: Option<Arc<dyn LspAdapterDelegate>>,
cx: &mut WindowContext,
@@ -168,6 +170,7 @@ impl SlashCommand for SearchSlashCommand {
range: 0..text.len(),
icon: IconName::MagnifyingGlass,
label: query,
+ metadata: None,
});
SlashCommandOutput {
@@ -3,7 +3,7 @@ use anyhow::{anyhow, Context as _, Result};
use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use editor::Editor;
use gpui::{Task, WeakView};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use std::sync::Arc;
use std::{path::Path, sync::atomic::AtomicBool};
use ui::{IconName, WindowContext};
@@ -41,6 +41,8 @@ impl SlashCommand for OutlineSlashCommand {
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,
@@ -77,6 +79,7 @@ impl SlashCommand for OutlineSlashCommand {
range: 0..outline_text.len(),
icon: IconName::ListTree,
label: path.to_string_lossy().to_string().into(),
+ metadata: None,
}],
text: outline_text,
run_commands_in_text: false,
@@ -1,21 +1,17 @@
-use super::{
- diagnostics_command::write_single_file_diagnostics,
- file_command::{build_entry_output_section, codeblock_fence_for_path},
- SlashCommand, SlashCommandOutput,
-};
+use super::{file_command::append_buffer_to_output, SlashCommand, SlashCommandOutput};
use anyhow::{Context, Result};
-use assistant_slash_command::ArgumentCompletion;
+use assistant_slash_command::{ArgumentCompletion, SlashCommandOutputSection};
use collections::{HashMap, HashSet};
use editor::Editor;
use futures::future::join_all;
use gpui::{Entity, Task, WeakView};
use language::{BufferSnapshot, CodeLabel, HighlightId, LspAdapterDelegate};
use std::{
- fmt::Write,
path::PathBuf,
sync::{atomic::AtomicBool, Arc},
};
use ui::{ActiveTheme, WindowContext};
+use util::ResultExt;
use workspace::Workspace;
pub(crate) struct TabSlashCommand;
@@ -131,6 +127,8 @@ impl SlashCommand for TabSlashCommand {
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,
@@ -144,40 +142,11 @@ impl SlashCommand for TabSlashCommand {
);
cx.background_executor().spawn(async move {
- let mut sections = Vec::new();
- let mut text = String::new();
- let mut has_diagnostics = false;
+ let mut output = SlashCommandOutput::default();
for (full_path, buffer, _) in tab_items_search.await? {
- let section_start_ix = text.len();
- text.push_str(&codeblock_fence_for_path(full_path.as_deref(), None));
- for chunk in buffer.as_rope().chunks() {
- text.push_str(chunk);
- }
- if !text.ends_with('\n') {
- text.push('\n');
- }
- writeln!(text, "```").unwrap();
- if write_single_file_diagnostics(&mut text, full_path.as_deref(), &buffer) {
- has_diagnostics = true;
- }
- if !text.ends_with('\n') {
- text.push('\n');
- }
-
- let section_end_ix = text.len() - 1;
- sections.push(build_entry_output_section(
- section_start_ix..section_end_ix,
- full_path.as_deref(),
- false,
- None,
- ));
+ append_buffer_to_output(&buffer, full_path.as_deref(), &mut output).log_err();
}
-
- Ok(SlashCommandOutput {
- text,
- sections,
- run_commands_in_text: has_diagnostics,
- })
+ Ok(output)
})
}
}
@@ -6,7 +6,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use gpui::{AppContext, Task, View, WeakView};
-use language::{CodeLabel, LspAdapterDelegate};
+use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate};
use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
use ui::prelude::*;
use workspace::{dock::Panel, Workspace};
@@ -57,6 +57,8 @@ impl SlashCommand for TerminalSlashCommand {
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,
@@ -91,6 +93,7 @@ impl SlashCommand for TerminalSlashCommand {
range,
icon: IconName::Terminal,
label: "Terminal".into(),
+ metadata: None,
}],
run_commands_in_text: false,
}))
@@ -8,7 +8,7 @@ use assistant_slash_command::{
ArgumentCompletion, SlashCommand, SlashCommandOutput, SlashCommandOutputSection,
};
use gpui::{Task, WeakView};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use workspace::Workspace;
@@ -53,6 +53,8 @@ impl SlashCommand for WorkflowSlashCommand {
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,
@@ -68,6 +70,7 @@ impl SlashCommand for WorkflowSlashCommand {
range,
icon: IconName::Route,
label: "Workflow".into(),
+ metadata: None,
}],
run_commands_in_text: false,
})
@@ -19,4 +19,5 @@ gpui.workspace = true
language.workspace = true
parking_lot.workspace = true
serde.workspace = true
+serde_json.workspace = true
workspace.workspace = true
@@ -2,7 +2,7 @@ mod slash_command_registry;
use anyhow::Result;
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
-use language::{CodeLabel, LspAdapterDelegate};
+use language::{BufferSnapshot, CodeLabel, LspAdapterDelegate, OffsetRangeExt};
use serde::{Deserialize, Serialize};
pub use slash_command_registry::*;
use std::{
@@ -77,6 +77,8 @@ pub trait SlashCommand: 'static + Send + Sync {
fn run(
self: Arc<Self>,
arguments: &[String],
+ context_slash_command_output_sections: &[SlashCommandOutputSection<language::Anchor>],
+ context_buffer: BufferSnapshot,
workspace: WeakView<Workspace>,
// TODO: We're just using the `LspAdapterDelegate` here because that is
// what the extension API is already expecting.
@@ -94,7 +96,7 @@ pub type RenderFoldPlaceholder = Arc<
+ Fn(ElementId, Arc<dyn Fn(&mut WindowContext)>, &mut WindowContext) -> AnyElement,
>;
-#[derive(Debug, Default)]
+#[derive(Debug, Default, PartialEq)]
pub struct SlashCommandOutput {
pub text: String,
pub sections: Vec<SlashCommandOutputSection<usize>>,
@@ -106,4 +108,11 @@ pub struct SlashCommandOutputSection<T> {
pub range: Range<T>,
pub icon: IconName,
pub label: SharedString,
+ pub metadata: Option<serde_json::Value>,
+}
+
+impl SlashCommandOutputSection<language::Anchor> {
+ pub fn is_valid(&self, buffer: &language::TextBuffer) -> bool {
+ self.range.start.is_valid(buffer) && !self.range.to_offset(buffer).is_empty()
+ }
}
@@ -6,7 +6,7 @@ use assistant_slash_command::{
};
use futures::FutureExt;
use gpui::{Task, WeakView, WindowContext};
-use language::LspAdapterDelegate;
+use language::{BufferSnapshot, LspAdapterDelegate};
use ui::prelude::*;
use wasmtime_wasi::WasiView;
use workspace::Workspace;
@@ -82,6 +82,8 @@ impl SlashCommand for ExtensionSlashCommand {
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,
@@ -121,6 +123,7 @@ impl SlashCommand for ExtensionSlashCommand {
range: section.range.into(),
icon: IconName::Code,
label: section.label.into(),
+ metadata: None,
})
.collect(),
run_commands_in_text: false,
@@ -2390,6 +2390,7 @@ message SlashCommandOutputSection {
AnchorRange range = 1;
string icon_name = 2;
string label = 3;
+ optional string metadata = 4;
}
message ContextOperation {