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