Preserve sections generated by slash commands when reloading a context (#13199)
Release Notes: - N/A
This commit is contained in:
parent
195a270e18
commit
e4ba336971
19 changed files with 250 additions and 348 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -425,6 +425,7 @@ dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"serde",
|
||||||
"workspace",
|
"workspace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -11523,6 +11524,7 @@ dependencies = [
|
||||||
"gpui",
|
"gpui",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
"menu",
|
"menu",
|
||||||
|
"serde",
|
||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"story",
|
"story",
|
||||||
|
|
|
@ -15,9 +15,8 @@ use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||||
use client::telemetry::Telemetry;
|
use client::telemetry::Telemetry;
|
||||||
use collections::{BTreeSet, HashMap, HashSet};
|
use collections::{BTreeSet, HashMap, HashSet};
|
||||||
use editor::actions::ShowCompletions;
|
|
||||||
use editor::{
|
use editor::{
|
||||||
actions::{FoldAt, MoveToEndOfLine, Newline, UnfoldAt},
|
actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
|
||||||
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, ToDisplayPoint},
|
display_map::{BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, ToDisplayPoint},
|
||||||
scroll::{Autoscroll, AutoscrollStrategy},
|
scroll::{Autoscroll, AutoscrollStrategy},
|
||||||
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
|
||||||
|
@ -36,7 +35,7 @@ use gpui::{
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use language::{
|
use language::{
|
||||||
language_settings::SoftWrap, AnchorRangeExt, AutoindentMode, Buffer, LanguageRegistry,
|
language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
|
||||||
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
|
LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
|
||||||
};
|
};
|
||||||
use multi_buffer::MultiBufferRow;
|
use multi_buffer::MultiBufferRow;
|
||||||
|
@ -1013,6 +1012,7 @@ pub struct Context {
|
||||||
edit_suggestions: Vec<EditSuggestion>,
|
edit_suggestions: Vec<EditSuggestion>,
|
||||||
pending_slash_commands: Vec<PendingSlashCommand>,
|
pending_slash_commands: Vec<PendingSlashCommand>,
|
||||||
edits_since_last_slash_command_parse: language::Subscription,
|
edits_since_last_slash_command_parse: language::Subscription,
|
||||||
|
slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
|
||||||
message_anchors: Vec<MessageAnchor>,
|
message_anchors: Vec<MessageAnchor>,
|
||||||
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
messages_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
next_message_id: MessageId,
|
next_message_id: MessageId,
|
||||||
|
@ -1054,6 +1054,7 @@ impl Context {
|
||||||
next_message_id: Default::default(),
|
next_message_id: Default::default(),
|
||||||
edit_suggestions: Vec::new(),
|
edit_suggestions: Vec::new(),
|
||||||
pending_slash_commands: Vec::new(),
|
pending_slash_commands: Vec::new(),
|
||||||
|
slash_command_output_sections: Vec::new(),
|
||||||
edits_since_last_slash_command_parse,
|
edits_since_last_slash_command_parse,
|
||||||
summary: None,
|
summary: None,
|
||||||
pending_summary: Task::ready(None),
|
pending_summary: Task::ready(None),
|
||||||
|
@ -1090,11 +1091,12 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize(&self, cx: &AppContext) -> SavedContext {
|
fn serialize(&self, cx: &AppContext) -> SavedContext {
|
||||||
|
let buffer = self.buffer.read(cx);
|
||||||
SavedContext {
|
SavedContext {
|
||||||
id: self.id.clone(),
|
id: self.id.clone(),
|
||||||
zed: "context".into(),
|
zed: "context".into(),
|
||||||
version: SavedContext::VERSION.into(),
|
version: SavedContext::VERSION.into(),
|
||||||
text: self.buffer.read(cx).text(),
|
text: buffer.text(),
|
||||||
message_metadata: self.messages_metadata.clone(),
|
message_metadata: self.messages_metadata.clone(),
|
||||||
messages: self
|
messages: self
|
||||||
.messages(cx)
|
.messages(cx)
|
||||||
|
@ -1108,6 +1110,22 @@ impl Context {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|summary| summary.text.clone())
|
.map(|summary| summary.text.clone())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
|
slash_command_output_sections: self
|
||||||
|
.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() {
|
||||||
|
Some(SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon: section.icon,
|
||||||
|
label: section.label.clone(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1159,6 +1177,19 @@ impl Context {
|
||||||
next_message_id,
|
next_message_id,
|
||||||
edit_suggestions: Vec::new(),
|
edit_suggestions: Vec::new(),
|
||||||
pending_slash_commands: Vec::new(),
|
pending_slash_commands: Vec::new(),
|
||||||
|
slash_command_output_sections: saved_context
|
||||||
|
.slash_command_output_sections
|
||||||
|
.into_iter()
|
||||||
|
.map(|section| {
|
||||||
|
let buffer = buffer.read(cx);
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range: buffer.anchor_after(section.range.start)
|
||||||
|
..buffer.anchor_before(section.range.end),
|
||||||
|
icon: section.icon,
|
||||||
|
label: section.label,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
edits_since_last_slash_command_parse,
|
edits_since_last_slash_command_parse,
|
||||||
summary: Some(Summary {
|
summary: Some(Summary {
|
||||||
text: saved_context.summary,
|
text: saved_context.summary,
|
||||||
|
@ -1457,10 +1488,17 @@ impl Context {
|
||||||
.map(|section| SlashCommandOutputSection {
|
.map(|section| SlashCommandOutputSection {
|
||||||
range: buffer.anchor_after(start + section.range.start)
|
range: buffer.anchor_after(start + section.range.start)
|
||||||
..buffer.anchor_before(start + section.range.end),
|
..buffer.anchor_before(start + section.range.end),
|
||||||
render_placeholder: section.render_placeholder,
|
icon: section.icon,
|
||||||
|
label: section.label,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
|
|
||||||
|
this.slash_command_output_sections
|
||||||
|
.extend(sections.iter().cloned());
|
||||||
|
this.slash_command_output_sections
|
||||||
|
.sort_by(|a, b| a.range.cmp(&b.range, buffer));
|
||||||
|
|
||||||
ContextEvent::SlashCommandFinished {
|
ContextEvent::SlashCommandFinished {
|
||||||
output_range: buffer.anchor_after(start)
|
output_range: buffer.anchor_after(start)
|
||||||
..buffer.anchor_before(new_end),
|
..buffer.anchor_before(new_end),
|
||||||
|
@ -2224,6 +2262,7 @@ impl ContextEditor {
|
||||||
cx.subscribe(&editor, Self::handle_editor_event),
|
cx.subscribe(&editor, Self::handle_editor_event),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let sections = context.read(cx).slash_command_output_sections.clone();
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
context,
|
context,
|
||||||
editor,
|
editor,
|
||||||
|
@ -2237,6 +2276,7 @@ impl ContextEditor {
|
||||||
_subscriptions,
|
_subscriptions,
|
||||||
};
|
};
|
||||||
this.update_message_headers(cx);
|
this.update_message_headers(cx);
|
||||||
|
this.insert_slash_command_output_sections(sections, cx);
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2631,21 +2671,27 @@ impl ContextEditor {
|
||||||
FoldPlaceholder {
|
FoldPlaceholder {
|
||||||
render: Arc::new({
|
render: Arc::new({
|
||||||
let editor = cx.view().downgrade();
|
let editor = cx.view().downgrade();
|
||||||
let render_placeholder = section.render_placeholder.clone();
|
let icon = section.icon;
|
||||||
move |fold_id, fold_range, cx| {
|
let label = section.label.clone();
|
||||||
|
move |fold_id, fold_range, _cx| {
|
||||||
let editor = editor.clone();
|
let editor = editor.clone();
|
||||||
let unfold = Arc::new(move |cx: &mut WindowContext| {
|
ButtonLike::new(fold_id)
|
||||||
editor
|
.style(ButtonStyle::Filled)
|
||||||
.update(cx, |editor, cx| {
|
.layer(ElevationIndex::ElevatedSurface)
|
||||||
let buffer_start = fold_range
|
.child(Icon::new(icon))
|
||||||
.start
|
.child(Label::new(label.clone()).single_line())
|
||||||
.to_point(&editor.buffer().read(cx).read(cx));
|
.on_click(move |_, cx| {
|
||||||
let buffer_row = MultiBufferRow(buffer_start.row);
|
editor
|
||||||
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
.update(cx, |editor, cx| {
|
||||||
})
|
let buffer_start = fold_range
|
||||||
.ok();
|
.start
|
||||||
});
|
.to_point(&editor.buffer().read(cx).read(cx));
|
||||||
render_placeholder(fold_id.into(), unfold, cx)
|
let buffer_row = MultiBufferRow(buffer_start.row);
|
||||||
|
editor.unfold_at(&UnfoldAt { buffer_row }, cx);
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
})
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
constrain_width: false,
|
constrain_width: false,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
use crate::{assistant_settings::OpenAiModel, MessageId, MessageMetadata};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
@ -27,10 +28,22 @@ pub struct SavedContext {
|
||||||
pub messages: Vec<SavedMessage>,
|
pub messages: Vec<SavedMessage>,
|
||||||
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
|
pub slash_command_output_sections: Vec<SlashCommandOutputSection<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SavedContext {
|
impl SavedContext {
|
||||||
pub const VERSION: &'static str = "0.2.0";
|
pub const VERSION: &'static str = "0.3.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct SavedContextV0_2_0 {
|
||||||
|
pub id: Option<String>,
|
||||||
|
pub zed: String,
|
||||||
|
pub version: String,
|
||||||
|
pub text: String,
|
||||||
|
pub messages: Vec<SavedMessage>,
|
||||||
|
pub message_metadata: HashMap<MessageId, MessageMetadata>,
|
||||||
|
pub summary: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -100,6 +113,20 @@ impl ContextStore {
|
||||||
SavedContext::VERSION => {
|
SavedContext::VERSION => {
|
||||||
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
Ok(serde_json::from_value::<SavedContext>(saved_context_json)?)
|
||||||
}
|
}
|
||||||
|
"0.2.0" => {
|
||||||
|
let saved_context =
|
||||||
|
serde_json::from_value::<SavedContextV0_2_0>(saved_context_json)?;
|
||||||
|
Ok(SavedContext {
|
||||||
|
id: saved_context.id,
|
||||||
|
zed: saved_context.zed,
|
||||||
|
version: saved_context.version,
|
||||||
|
text: saved_context.text,
|
||||||
|
messages: saved_context.messages,
|
||||||
|
message_metadata: saved_context.message_metadata,
|
||||||
|
summary: saved_context.summary,
|
||||||
|
slash_command_output_sections: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
"0.1.0" => {
|
"0.1.0" => {
|
||||||
let saved_context =
|
let saved_context =
|
||||||
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
serde_json::from_value::<SavedContextV0_1_0>(saved_context_json)?;
|
||||||
|
@ -111,6 +138,7 @@ impl ContextStore {
|
||||||
messages: saved_context.messages,
|
messages: saved_context.messages,
|
||||||
message_metadata: saved_context.message_metadata,
|
message_metadata: saved_context.message_metadata,
|
||||||
summary: saved_context.summary,
|
summary: saved_context.summary,
|
||||||
|
slash_command_output_sections: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
_ => Err(anyhow!("unrecognized saved context version: {}", version)),
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
use super::{
|
use super::{
|
||||||
file_command::{codeblock_fence_for_path, EntryPlaceholder},
|
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||||
SlashCommand, SlashCommandOutput,
|
SlashCommand, SlashCommandOutput,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use ui::{IntoElement, WindowContext};
|
use ui::WindowContext;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct ActiveSlashCommand;
|
pub(crate) struct ActiveSlashCommand;
|
||||||
|
@ -81,19 +80,12 @@ impl SlashCommand for ActiveSlashCommand {
|
||||||
let range = 0..text.len();
|
let range = 0..text.len();
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![build_entry_output_section(
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
path.as_deref(),
|
||||||
EntryPlaceholder {
|
false,
|
||||||
id,
|
None,
|
||||||
path: path.clone(),
|
)],
|
||||||
is_directory: false,
|
|
||||||
line_range: None,
|
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::{prompt_command::PromptPlaceholder, SlashCommand, SlashCommandOutput};
|
use super::{SlashCommand, SlashCommandOutput};
|
||||||
use crate::prompt_library::PromptStore;
|
use crate::prompt_library::PromptStore;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
|
@ -68,14 +68,8 @@ impl SlashCommand for DefaultSlashCommand {
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::Library,
|
||||||
PromptPlaceholder {
|
label: "Default".into(),
|
||||||
title: "Default".into(),
|
|
||||||
id,
|
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
text,
|
text,
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::{SlashCommand, SlashCommandOutput};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use fuzzy::{PathMatch, StringMatchCandidate};
|
use fuzzy::{PathMatch, StringMatchCandidate};
|
||||||
use gpui::{svg, AppContext, Model, RenderOnce, Task, View, WeakView};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{
|
use language::{
|
||||||
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
Anchor, BufferSnapshot, DiagnosticEntry, DiagnosticSeverity, LspAdapterDelegate,
|
||||||
OffsetRangeExt, ToOffset,
|
OffsetRangeExt, ToOffset,
|
||||||
|
@ -14,7 +14,7 @@ use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use util::paths::PathMatcher;
|
use util::paths::PathMatcher;
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
@ -164,14 +164,45 @@ impl SlashCommand for DiagnosticsCommand {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(range, placeholder_type)| SlashCommandOutputSection {
|
.map(|(range, placeholder_type)| SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: match placeholder_type {
|
||||||
DiagnosticsPlaceholder {
|
PlaceholderType::Root(_, _) => IconName::ExclamationTriangle,
|
||||||
id,
|
PlaceholderType::File(_) => IconName::File,
|
||||||
unfold,
|
PlaceholderType::Diagnostic(DiagnosticType::Error, _) => {
|
||||||
placeholder_type: placeholder_type.clone(),
|
IconName::XCircle
|
||||||
}
|
}
|
||||||
.into_any_element()
|
PlaceholderType::Diagnostic(DiagnosticType::Warning, _) => {
|
||||||
}),
|
IconName::ExclamationTriangle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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(),
|
.collect(),
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
|
@ -223,10 +254,10 @@ fn collect_diagnostics(
|
||||||
options: Options,
|
options: Options,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
|
) -> Task<Result<(String, Vec<(Range<usize>, PlaceholderType)>)>> {
|
||||||
let header = if let Some(path_matcher) = &options.path_matcher {
|
let error_source = if let Some(path_matcher) = &options.path_matcher {
|
||||||
format!("diagnostics: {}", path_matcher.source())
|
Some(path_matcher.source().to_string())
|
||||||
} else {
|
} else {
|
||||||
"diagnostics".to_string()
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let project_handle = project.downgrade();
|
let project_handle = project.downgrade();
|
||||||
|
@ -234,7 +265,11 @@ fn collect_diagnostics(
|
||||||
|
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
writeln!(text, "{}", &header).unwrap();
|
if let Some(error_source) = error_source.as_ref() {
|
||||||
|
writeln!(text, "diagnostics: {}", error_source).unwrap();
|
||||||
|
} else {
|
||||||
|
writeln!(text, "diagnostics").unwrap();
|
||||||
|
}
|
||||||
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
|
let mut sections: Vec<(Range<usize>, PlaceholderType)> = Vec::new();
|
||||||
|
|
||||||
let mut project_summary = DiagnosticSummary::default();
|
let mut project_summary = DiagnosticSummary::default();
|
||||||
|
@ -276,7 +311,7 @@ fn collect_diagnostics(
|
||||||
}
|
}
|
||||||
sections.push((
|
sections.push((
|
||||||
0..text.len(),
|
0..text.len(),
|
||||||
PlaceholderType::Root(project_summary, header),
|
PlaceholderType::Root(project_summary, error_source),
|
||||||
));
|
));
|
||||||
|
|
||||||
Ok((text, sections))
|
Ok((text, sections))
|
||||||
|
@ -362,12 +397,12 @@ fn collect_diagnostic(
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum PlaceholderType {
|
pub enum PlaceholderType {
|
||||||
Root(DiagnosticSummary, String),
|
Root(DiagnosticSummary, Option<String>),
|
||||||
File(String),
|
File(String),
|
||||||
Diagnostic(DiagnosticType, String),
|
Diagnostic(DiagnosticType, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, IntoElement)]
|
#[derive(Copy, Clone)]
|
||||||
pub enum DiagnosticType {
|
pub enum DiagnosticType {
|
||||||
Warning,
|
Warning,
|
||||||
Error,
|
Error,
|
||||||
|
@ -381,64 +416,3 @@ impl DiagnosticType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct DiagnosticsPlaceholder {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub placeholder_type: PlaceholderType,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for DiagnosticsPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
let (icon, content) = match self.placeholder_type {
|
|
||||||
PlaceholderType::Root(summary, title) => (
|
|
||||||
h_flex()
|
|
||||||
.w_full()
|
|
||||||
.gap_0p5()
|
|
||||||
.when(summary.error_count > 0, |this| {
|
|
||||||
this.child(DiagnosticType::Error)
|
|
||||||
.child(Label::new(summary.error_count.to_string()))
|
|
||||||
})
|
|
||||||
.when(summary.warning_count > 0, |this| {
|
|
||||||
this.child(DiagnosticType::Warning)
|
|
||||||
.child(Label::new(summary.warning_count.to_string()))
|
|
||||||
})
|
|
||||||
.into_any_element(),
|
|
||||||
Label::new(title),
|
|
||||||
),
|
|
||||||
PlaceholderType::File(file) => (
|
|
||||||
Icon::new(IconName::File).into_any_element(),
|
|
||||||
Label::new(file),
|
|
||||||
),
|
|
||||||
PlaceholderType::Diagnostic(diagnostic_type, message) => (
|
|
||||||
diagnostic_type.into_any_element(),
|
|
||||||
Label::new(message).single_line(),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(icon)
|
|
||||||
.child(content)
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for DiagnosticType {
|
|
||||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
svg()
|
|
||||||
.size(cx.text_style().font_size)
|
|
||||||
.flex_none()
|
|
||||||
.map(|icon| match self {
|
|
||||||
DiagnosticType::Error => icon
|
|
||||||
.path(IconName::XCircle.path())
|
|
||||||
.text_color(Color::Error.color(cx)),
|
|
||||||
DiagnosticType::Warning => icon
|
|
||||||
.path(IconName::ExclamationTriangle.path())
|
|
||||||
.text_color(Color::Warning.color(cx)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ use gpui::{AppContext, Task, WeakView};
|
||||||
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
use html_to_markdown::{convert_html_to_markdown, markdown, TagHandler};
|
||||||
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||||
|
@ -152,37 +152,11 @@ impl SlashCommand for FetchSlashCommand {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::AtSign,
|
||||||
FetchPlaceholder {
|
label: format!("fetch {}", url).into(),
|
||||||
id,
|
|
||||||
unfold,
|
|
||||||
url: url.clone(),
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
struct FetchPlaceholder {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
pub url: SharedString,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for FetchPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::AtSign))
|
|
||||||
.child(Label::new(format!("fetch {url}", url = self.url)))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use fuzzy::PathMatch;
|
use fuzzy::PathMatch;
|
||||||
use gpui::{AppContext, Model, RenderOnce, SharedString, Task, View, WeakView};
|
use gpui::{AppContext, Model, Task, View, WeakView};
|
||||||
use language::{LineEnding, LspAdapterDelegate};
|
use language::{LineEnding, LspAdapterDelegate};
|
||||||
use project::{PathMatchCandidateSet, Worktree};
|
use project::{PathMatchCandidateSet, Worktree};
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -12,7 +12,7 @@ use std::{
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use util::{paths::PathMatcher, ResultExt};
|
use util::{paths::PathMatcher, ResultExt};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
@ -156,18 +156,13 @@ impl SlashCommand for FileSlashCommand {
|
||||||
text,
|
text,
|
||||||
sections: ranges
|
sections: ranges
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(range, path, entry_type)| SlashCommandOutputSection {
|
.map(|(range, path, entry_type)| {
|
||||||
range,
|
build_entry_output_section(
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
range,
|
||||||
EntryPlaceholder {
|
Some(&path),
|
||||||
path: Some(path.clone()),
|
entry_type == EntryType::Directory,
|
||||||
is_directory: entry_type == EntryType::Directory,
|
None,
|
||||||
line_range: None,
|
)
|
||||||
id,
|
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
|
@ -349,44 +344,6 @@ async fn collect_file_content(
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct EntryPlaceholder {
|
|
||||||
pub path: Option<PathBuf>,
|
|
||||||
pub is_directory: bool,
|
|
||||||
pub line_range: Option<Range<u32>>,
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for EntryPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
let title = if let Some(path) = self.path.as_ref() {
|
|
||||||
SharedString::from(path.to_string_lossy().to_string())
|
|
||||||
} else {
|
|
||||||
SharedString::from("untitled")
|
|
||||||
};
|
|
||||||
let icon = if self.is_directory {
|
|
||||||
IconName::Folder
|
|
||||||
} else {
|
|
||||||
IconName::File
|
|
||||||
};
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(icon))
|
|
||||||
.child(Label::new(title))
|
|
||||||
.when_some(self.line_range, |button, line_range| {
|
|
||||||
button.child(Label::new(":")).child(Label::new(format!(
|
|
||||||
"{}-{}",
|
|
||||||
line_range.start, line_range.end
|
|
||||||
)))
|
|
||||||
})
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
|
pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32>>) -> String {
|
||||||
let mut text = String::new();
|
let mut text = String::new();
|
||||||
write!(text, "```").unwrap();
|
write!(text, "```").unwrap();
|
||||||
|
@ -408,3 +365,31 @@ pub fn codeblock_fence_for_path(path: Option<&Path>, row_range: Option<Range<u32
|
||||||
text.push('\n');
|
text.push('\n');
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn build_entry_output_section(
|
||||||
|
range: Range<usize>,
|
||||||
|
path: Option<&Path>,
|
||||||
|
is_directory: bool,
|
||||||
|
line_range: Option<Range<u32>>,
|
||||||
|
) -> SlashCommandOutputSection<usize> {
|
||||||
|
let mut label = if let Some(path) = path {
|
||||||
|
path.to_string_lossy().to_string()
|
||||||
|
} else {
|
||||||
|
"untitled".to_string()
|
||||||
|
};
|
||||||
|
if let Some(line_range) = line_range {
|
||||||
|
write!(label, ":{}-{}", line_range.start, line_range.end).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon = if is_directory {
|
||||||
|
IconName::Folder
|
||||||
|
} else {
|
||||||
|
IconName::File
|
||||||
|
};
|
||||||
|
|
||||||
|
SlashCommandOutputSection {
|
||||||
|
range,
|
||||||
|
icon,
|
||||||
|
label: label.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -53,9 +53,8 @@ impl SlashCommand for NowSlashCommand {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::CountdownTimer,
|
||||||
NowPlaceholder { id, unfold, now }.into_any_element()
|
label: now.to_rfc3339().into(),
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -10,7 +10,7 @@ use std::{
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct ProjectSlashCommand;
|
pub(crate) struct ProjectSlashCommand;
|
||||||
|
@ -138,15 +138,8 @@ impl SlashCommand for ProjectSlashCommand {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::FileTree,
|
||||||
ButtonLike::new(id)
|
label: "Project".into(),
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::FileTree))
|
|
||||||
.child(Label::new("Project"))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,7 +5,7 @@ use assistant_slash_command::SlashCommandOutputSection;
|
||||||
use gpui::{AppContext, Task, WeakView};
|
use gpui::{AppContext, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::sync::{atomic::AtomicBool, Arc};
|
use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct PromptSlashCommand;
|
pub(crate) struct PromptSlashCommand;
|
||||||
|
@ -78,36 +78,11 @@ impl SlashCommand for PromptSlashCommand {
|
||||||
text: prompt,
|
text: prompt,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::Library,
|
||||||
PromptPlaceholder {
|
label: title,
|
||||||
id,
|
|
||||||
unfold,
|
|
||||||
title: title.clone(),
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: true,
|
run_commands_in_text: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
pub struct PromptPlaceholder {
|
|
||||||
pub title: SharedString,
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for PromptPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::Library))
|
|
||||||
.child(Label::new(self.title))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use http::{AsyncBody, HttpClient, HttpClientWithUrl};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use project::{Project, ProjectPath};
|
use project::{Project, ProjectPath};
|
||||||
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
|
use rustdoc::{convert_rustdoc_to_markdown, CrateName, LocalProvider, RustdocSource, RustdocStore};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use util::{maybe, ResultExt};
|
use util::{maybe, ResultExt};
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
@ -213,57 +213,26 @@ impl SlashCommand for RustdocSlashCommand {
|
||||||
cx.foreground_executor().spawn(async move {
|
cx.foreground_executor().spawn(async move {
|
||||||
let (source, text) = text.await?;
|
let (source, text) = text.await?;
|
||||||
let range = 0..text.len();
|
let range = 0..text.len();
|
||||||
|
let crate_path = module_path
|
||||||
|
.map(|module_path| format!("{}::{}", crate_name, module_path))
|
||||||
|
.unwrap_or_else(|| crate_name.to_string());
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::FileRust,
|
||||||
RustdocPlaceholder {
|
label: format!(
|
||||||
id,
|
"rustdoc ({source}): {crate_path}",
|
||||||
unfold,
|
source = match source {
|
||||||
source,
|
RustdocSource::Index => "index",
|
||||||
crate_name: crate_name.clone(),
|
RustdocSource::Local => "local",
|
||||||
module_path: module_path.clone(),
|
RustdocSource::DocsDotRs => "docs.rs",
|
||||||
}
|
}
|
||||||
.into_any_element()
|
)
|
||||||
}),
|
.into(),
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
|
||||||
struct RustdocPlaceholder {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub unfold: Arc<dyn Fn(&mut WindowContext)>,
|
|
||||||
pub source: RustdocSource,
|
|
||||||
pub crate_name: CrateName,
|
|
||||||
pub module_path: Option<SharedString>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderOnce for RustdocPlaceholder {
|
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
|
||||||
let unfold = self.unfold;
|
|
||||||
|
|
||||||
let crate_path = self
|
|
||||||
.module_path
|
|
||||||
.map(|module_path| format!("{crate_name}::{module_path}", crate_name = self.crate_name))
|
|
||||||
.unwrap_or(self.crate_name.to_string());
|
|
||||||
|
|
||||||
ButtonLike::new(self.id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::FileRust))
|
|
||||||
.child(Label::new(format!(
|
|
||||||
"rustdoc ({source}): {crate_path}",
|
|
||||||
source = match self.source {
|
|
||||||
RustdocSource::Index => "index",
|
|
||||||
RustdocSource::Local => "local",
|
|
||||||
RustdocSource::DocsDotRs => "docs.rs",
|
|
||||||
}
|
|
||||||
)))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use super::{
|
use super::{
|
||||||
file_command::{codeblock_fence_for_path, EntryPlaceholder},
|
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||||
SlashCommand, SlashCommandOutput,
|
SlashCommand, SlashCommandOutput,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
@ -12,7 +12,7 @@ use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex, Icon, IconName};
|
use ui::{prelude::*, IconName};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
@ -151,34 +151,19 @@ impl SlashCommand for SearchSlashCommand {
|
||||||
text.push_str(&excerpt);
|
text.push_str(&excerpt);
|
||||||
writeln!(text, "\n```\n").unwrap();
|
writeln!(text, "\n```\n").unwrap();
|
||||||
let section_end_ix = text.len() - 1;
|
let section_end_ix = text.len() - 1;
|
||||||
|
sections.push(build_entry_output_section(
|
||||||
sections.push(SlashCommandOutputSection {
|
section_start_ix..section_end_ix,
|
||||||
range: section_start_ix..section_end_ix,
|
Some(&full_path),
|
||||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
false,
|
||||||
EntryPlaceholder {
|
Some(start_row + 1..end_row + 1),
|
||||||
id,
|
));
|
||||||
path: Some(full_path.clone()),
|
|
||||||
is_directory: false,
|
|
||||||
line_range: Some(start_row + 1..end_row + 1),
|
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = SharedString::from(query);
|
let query = SharedString::from(query);
|
||||||
sections.push(SlashCommandOutputSection {
|
sections.push(SlashCommandOutputSection {
|
||||||
range: 0..text.len(),
|
range: 0..text.len(),
|
||||||
render_placeholder: Arc::new(move |id, unfold, _cx| {
|
icon: IconName::MagnifyingGlass,
|
||||||
ButtonLike::new(id)
|
label: query,
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::MagnifyingGlass))
|
|
||||||
.child(Label::new(query.clone()))
|
|
||||||
.on_click(move |_, cx| unfold(cx))
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
SlashCommandOutput {
|
SlashCommandOutput {
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
use super::{
|
use super::{
|
||||||
file_command::{codeblock_fence_for_path, EntryPlaceholder},
|
file_command::{build_entry_output_section, codeblock_fence_for_path},
|
||||||
SlashCommand, SlashCommandOutput,
|
SlashCommand, SlashCommandOutput,
|
||||||
};
|
};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::SlashCommandOutputSection;
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{AppContext, Entity, Task, WeakView};
|
use gpui::{AppContext, Entity, Task, WeakView};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use std::{fmt::Write, sync::Arc};
|
use std::{fmt::Write, sync::Arc};
|
||||||
use ui::{IntoElement, WindowContext};
|
use ui::WindowContext;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
pub(crate) struct TabsSlashCommand;
|
pub(crate) struct TabsSlashCommand;
|
||||||
|
@ -89,20 +88,12 @@ impl SlashCommand for TabsSlashCommand {
|
||||||
}
|
}
|
||||||
writeln!(text, "```\n").unwrap();
|
writeln!(text, "```\n").unwrap();
|
||||||
let section_end_ix = text.len() - 1;
|
let section_end_ix = text.len() - 1;
|
||||||
|
sections.push(build_entry_output_section(
|
||||||
sections.push(SlashCommandOutputSection {
|
section_start_ix..section_end_ix,
|
||||||
range: section_start_ix..section_end_ix,
|
full_path.as_deref(),
|
||||||
render_placeholder: Arc::new(move |id, unfold, _| {
|
false,
|
||||||
EntryPlaceholder {
|
None,
|
||||||
id,
|
));
|
||||||
path: full_path.clone(),
|
|
||||||
is_directory: false,
|
|
||||||
line_range: None,
|
|
||||||
unfold,
|
|
||||||
}
|
|
||||||
.into_any_element()
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SlashCommandOutput {
|
Ok(SlashCommandOutput {
|
||||||
|
|
|
@ -18,4 +18,5 @@ derive_more.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
language.workspace = true
|
language.workspace = true
|
||||||
parking_lot.workspace = true
|
parking_lot.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
workspace.workspace = true
|
workspace.workspace = true
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
mod slash_command_registry;
|
mod slash_command_registry;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gpui::{AnyElement, AppContext, ElementId, Task, WeakView, WindowContext};
|
use gpui::{AnyElement, AppContext, ElementId, SharedString, Task, WeakView, WindowContext};
|
||||||
use language::{CodeLabel, LspAdapterDelegate};
|
use language::{CodeLabel, LspAdapterDelegate};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
pub use slash_command_registry::*;
|
pub use slash_command_registry::*;
|
||||||
use std::{
|
use std::{
|
||||||
ops::Range,
|
ops::Range,
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use workspace::Workspace;
|
use workspace::{ui::IconName, Workspace};
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
SlashCommandRegistry::default_global(cx);
|
SlashCommandRegistry::default_global(cx);
|
||||||
|
@ -55,8 +56,9 @@ pub struct SlashCommandOutput {
|
||||||
pub run_commands_in_text: bool,
|
pub run_commands_in_text: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct SlashCommandOutputSection<T> {
|
pub struct SlashCommandOutputSection<T> {
|
||||||
pub range: Range<T>,
|
pub range: Range<T>,
|
||||||
pub render_placeholder: RenderFoldPlaceholder,
|
pub icon: IconName,
|
||||||
|
pub label: SharedString,
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,9 @@ use std::sync::{atomic::AtomicBool, Arc};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
|
||||||
use futures::FutureExt;
|
use futures::FutureExt;
|
||||||
use gpui::{AppContext, IntoElement, Task, WeakView, WindowContext};
|
use gpui::{AppContext, Task, WeakView, WindowContext};
|
||||||
use language::LspAdapterDelegate;
|
use language::LspAdapterDelegate;
|
||||||
use ui::{prelude::*, ButtonLike, ElevationIndex};
|
use ui::prelude::*;
|
||||||
use wasmtime_wasi::WasiView;
|
use wasmtime_wasi::WasiView;
|
||||||
use workspace::Workspace;
|
use workspace::Workspace;
|
||||||
|
|
||||||
|
@ -87,18 +87,8 @@ impl SlashCommand for ExtensionSlashCommand {
|
||||||
text,
|
text,
|
||||||
sections: vec![SlashCommandOutputSection {
|
sections: vec![SlashCommandOutputSection {
|
||||||
range,
|
range,
|
||||||
render_placeholder: Arc::new({
|
icon: IconName::Code,
|
||||||
let command_name = command_name.clone();
|
label: command_name,
|
||||||
move |id, unfold, _cx| {
|
|
||||||
ButtonLike::new(id)
|
|
||||||
.style(ButtonStyle::Filled)
|
|
||||||
.layer(ElevationIndex::ElevatedSurface)
|
|
||||||
.child(Icon::new(IconName::Code))
|
|
||||||
.child(Label::new(command_name.clone()))
|
|
||||||
.on_click(move |_event, cx| unfold(cx))
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}],
|
}],
|
||||||
run_commands_in_text: false,
|
run_commands_in_text: false,
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,6 +17,7 @@ chrono.workspace = true
|
||||||
gpui.workspace = true
|
gpui.workspace = true
|
||||||
itertools = { workspace = true, optional = true }
|
itertools = { workspace = true, optional = true }
|
||||||
menu.workspace = true
|
menu.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
smallvec.workspace = true
|
smallvec.workspace = true
|
||||||
story = { workspace = true, optional = true }
|
story = { workspace = true, optional = true }
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
|
use gpui::{svg, AnimationElement, Hsla, IntoElement, Rems, Transformation};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use strum::EnumIter;
|
use strum::EnumIter;
|
||||||
|
|
||||||
use crate::{prelude::*, Indicator};
|
use crate::{prelude::*, Indicator};
|
||||||
|
@ -76,7 +77,7 @@ impl IconSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Copy, Clone, EnumIter)]
|
#[derive(Debug, PartialEq, Copy, Clone, EnumIter, Serialize, Deserialize)]
|
||||||
pub enum IconName {
|
pub enum IconName {
|
||||||
Ai,
|
Ai,
|
||||||
ArrowCircle,
|
ArrowCircle,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue