diff --git a/assets/icons/wand.svg b/assets/icons/wand.svg new file mode 100644 index 0000000000..a6704b1c42 --- /dev/null +++ b/assets/icons/wand.svg @@ -0,0 +1 @@ + diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 3d498d94eb..4f0462b66a 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -73,12 +73,11 @@ use std::{ }; use terminal_view::{terminal_panel::TerminalPanel, TerminalView}; use text::SelectionGoal; -use ui::TintColor; use ui::{ prelude::*, utils::{format_distance_from_now, DateTimeType}, Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem, - ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip, + ListItemSpacing, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip, }; use util::{maybe, ResultExt}; use workspace::{ @@ -4006,13 +4005,7 @@ impl Render for ContextEditor { } else { None }; - let focus_handle = self - .workspace - .update(cx, |workspace, cx| { - Some(workspace.active_item_as::(cx)?.focus_handle(cx)) - }) - .ok() - .flatten(); + v_flex() .key_context("ContextEditor") .capture_action(cx.listener(ContextEditor::cancel)) @@ -4060,28 +4053,7 @@ impl Render for ContextEditor { .child( h_flex() .gap_1() - .child(render_inject_context_menu(cx.view().downgrade(), cx)) - .child( - IconButton::new("quote-button", IconName::Quote) - .icon_size(IconSize::Small) - .on_click(|_, cx| { - cx.dispatch_action(QuoteSelection.boxed_clone()); - }) - .tooltip(move |cx| { - cx.new_view(|cx| { - Tooltip::new("Insert Selection").key_binding( - focus_handle.as_ref().and_then(|handle| { - KeyBinding::for_action_in( - &QuoteSelection, - &handle, - cx, - ) - }), - ) - }) - .into() - }), - ), + .child(render_inject_context_menu(cx.view().downgrade(), cx)), ) .child( h_flex() @@ -4376,6 +4348,7 @@ fn render_inject_context_menu( Button::new("trigger", "Add Context") .icon(IconName::Plus) .icon_size(IconSize::Small) + .icon_color(Color::Muted) .icon_position(IconPosition::Start) .tooltip(|cx| Tooltip::text("Type / to insert via keyboard", cx)), ) @@ -4550,7 +4523,7 @@ impl Render for ContextEditorToolbarItem { .w_full() .justify_between() .gap_2() - .child(Label::new("Insert Context")) + .child(Label::new("Add Context")) .child(Label::new("/ command").color(Color::Muted)) .into_any() }, @@ -4574,7 +4547,7 @@ impl Render for ContextEditorToolbarItem { } }, ) - .action("Insert Selection", QuoteSelection.boxed_clone()) + .action("Add Selection", QuoteSelection.boxed_clone()) })) } }), diff --git a/crates/assistant/src/slash_command/auto_command.rs b/crates/assistant/src/slash_command/auto_command.rs index cc73f36ebf..61f720be6d 100644 --- a/crates/assistant/src/slash_command/auto_command.rs +++ b/crates/assistant/src/slash_command/auto_command.rs @@ -14,7 +14,7 @@ use language_model::{ use semantic_index::{FileSummary, SemanticDb}; use smol::channel; use std::sync::{atomic::AtomicBool, Arc}; -use ui::{BorrowAppContext, WindowContext}; +use ui::{prelude::*, BorrowAppContext, WindowContext}; use util::ResultExt; use workspace::Workspace; @@ -37,6 +37,10 @@ impl SlashCommand for AutoCommand { "Automatically infer what context to add".into() } + fn icon(&self) -> IconName { + IconName::Wand + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command/delta_command.rs b/crates/assistant/src/slash_command/delta_command.rs index c9985d9f00..5c8bc2b023 100644 --- a/crates/assistant/src/slash_command/delta_command.rs +++ b/crates/assistant/src/slash_command/delta_command.rs @@ -10,6 +10,7 @@ use gpui::{Task, WeakView, WindowContext}; use language::{BufferSnapshot, LspAdapterDelegate}; use std::sync::{atomic::AtomicBool, Arc}; use text::OffsetRangeExt; +use ui::prelude::*; use workspace::Workspace; pub(crate) struct DeltaSlashCommand; @@ -27,6 +28,10 @@ impl SlashCommand for DeltaSlashCommand { self.description() } + fn icon(&self) -> IconName { + IconName::Diff + } + fn requires_argument(&self) -> bool { false } diff --git a/crates/assistant/src/slash_command/diagnostics_command.rs b/crates/assistant/src/slash_command/diagnostics_command.rs index c7475445ce..3f1e3e5e71 100644 --- a/crates/assistant/src/slash_command/diagnostics_command.rs +++ b/crates/assistant/src/slash_command/diagnostics_command.rs @@ -98,6 +98,10 @@ impl SlashCommand for DiagnosticsSlashCommand { "Insert diagnostics".into() } + fn icon(&self) -> IconName { + IconName::XCircle + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command/file_command.rs b/crates/assistant/src/slash_command/file_command.rs index 1d0fa2bf3e..3964754029 100644 --- a/crates/assistant/src/slash_command/file_command.rs +++ b/crates/assistant/src/slash_command/file_command.rs @@ -117,7 +117,7 @@ impl SlashCommand for FileSlashCommand { } fn description(&self) -> String { - "Insert file".into() + "Insert file and/or directory".into() } fn menu_text(&self) -> String { @@ -128,6 +128,10 @@ impl SlashCommand for FileSlashCommand { true } + fn icon(&self) -> IconName { + IconName::File + } + fn complete_argument( self: Arc, arguments: &[String], diff --git a/crates/assistant/src/slash_command/project_command.rs b/crates/assistant/src/slash_command/project_command.rs index d14cb310ad..ee6434ec03 100644 --- a/crates/assistant/src/slash_command/project_command.rs +++ b/crates/assistant/src/slash_command/project_command.rs @@ -24,7 +24,8 @@ use std::{ ops::DerefMut, sync::{atomic::AtomicBool, Arc}, }; -use ui::{BorrowAppContext as _, IconName}; + +use ui::prelude::*; use workspace::Workspace; pub struct ProjectSlashCommand { @@ -50,6 +51,10 @@ impl SlashCommand for ProjectSlashCommand { "Generate a semantic search based on context".into() } + fn icon(&self) -> IconName { + IconName::Folder + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command/prompt_command.rs b/crates/assistant/src/slash_command/prompt_command.rs index 079d1425af..9eb44d3418 100644 --- a/crates/assistant/src/slash_command/prompt_command.rs +++ b/crates/assistant/src/slash_command/prompt_command.rs @@ -21,6 +21,10 @@ impl SlashCommand for PromptSlashCommand { "Insert prompt from library".into() } + fn icon(&self) -> IconName { + IconName::Library + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command/search_command.rs b/crates/assistant/src/slash_command/search_command.rs index 9c4938ce93..f4bc3e36b6 100644 --- a/crates/assistant/src/slash_command/search_command.rs +++ b/crates/assistant/src/slash_command/search_command.rs @@ -38,6 +38,10 @@ impl SlashCommand for SearchSlashCommand { "Search your project semantically".into() } + fn icon(&self) -> IconName { + IconName::SearchCode + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command/symbols_command.rs b/crates/assistant/src/slash_command/symbols_command.rs index 468c8d7126..2b261bc368 100644 --- a/crates/assistant/src/slash_command/symbols_command.rs +++ b/crates/assistant/src/slash_command/symbols_command.rs @@ -22,6 +22,10 @@ impl SlashCommand for OutlineSlashCommand { "Insert symbols for active tab".into() } + fn icon(&self) -> IconName { + IconName::ListTree + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command/tab_command.rs b/crates/assistant/src/slash_command/tab_command.rs index 771c0765ee..1579938c92 100644 --- a/crates/assistant/src/slash_command/tab_command.rs +++ b/crates/assistant/src/slash_command/tab_command.rs @@ -12,7 +12,7 @@ use std::{ path::PathBuf, sync::{atomic::AtomicBool, Arc}, }; -use ui::{ActiveTheme, WindowContext}; +use ui::{prelude::*, ActiveTheme, WindowContext}; use util::ResultExt; use workspace::Workspace; @@ -31,6 +31,10 @@ impl SlashCommand for TabSlashCommand { "Insert open tabs (active tab by default)".to_owned() } + fn icon(&self) -> IconName { + IconName::FileTree + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command/terminal_command.rs b/crates/assistant/src/slash_command/terminal_command.rs index 2ca1d4041b..84dbb7146f 100644 --- a/crates/assistant/src/slash_command/terminal_command.rs +++ b/crates/assistant/src/slash_command/terminal_command.rs @@ -33,6 +33,10 @@ impl SlashCommand for TerminalSlashCommand { "Insert terminal output".into() } + fn icon(&self) -> IconName { + IconName::Terminal + } + fn menu_text(&self) -> String { self.description() } diff --git a/crates/assistant/src/slash_command_picker.rs b/crates/assistant/src/slash_command_picker.rs index 35ae90d412..3d667d7f82 100644 --- a/crates/assistant/src/slash_command_picker.rs +++ b/crates/assistant/src/slash_command_picker.rs @@ -1,19 +1,13 @@ use std::sync::Arc; use assistant_slash_command::SlashCommandRegistry; -use gpui::AnyElement; -use gpui::DismissEvent; -use gpui::WeakView; -use picker::PickerEditorPosition; -use ui::ListItemSpacing; - -use gpui::SharedString; -use gpui::Task; -use picker::{Picker, PickerDelegate}; -use ui::{prelude::*, ListItem, PopoverMenu, PopoverTrigger}; +use gpui::{AnyElement, DismissEvent, SharedString, Task, WeakView}; +use picker::{Picker, PickerDelegate, PickerEditorPosition}; +use ui::{prelude::*, KeyBinding, ListItem, ListItemSpacing, PopoverMenu, PopoverTrigger}; use crate::assistant_panel::ContextEditor; +use crate::QuoteSelection; #[derive(IntoElement)] pub(super) struct SlashCommandSelector { @@ -27,6 +21,7 @@ struct SlashCommandInfo { name: SharedString, description: SharedString, args: Option, + icon: IconName, } #[derive(Clone)] @@ -37,6 +32,7 @@ enum SlashCommandEntry { renderer: fn(&mut WindowContext<'_>) -> AnyElement, on_confirm: fn(&mut WindowContext<'_>), }, + QuoteButton, } impl AsRef for SlashCommandEntry { @@ -44,6 +40,7 @@ impl AsRef for SlashCommandEntry { match self { SlashCommandEntry::Info(SlashCommandInfo { name, .. }) | SlashCommandEntry::Advert { name, .. } => name, + SlashCommandEntry::QuoteButton => "Quote Selection", } } } @@ -145,16 +142,23 @@ impl PickerDelegate for SlashCommandDelegate { } ret } + fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext>) { if let Some(command) = self.filtered_commands.get(self.selected_index) { - if let SlashCommandEntry::Info(info) = command { - self.active_context_editor - .update(cx, |context_editor, cx| { - context_editor.insert_command(&info.name, cx) - }) - .ok(); - } else if let SlashCommandEntry::Advert { on_confirm, .. } = command { - on_confirm(cx); + match command { + SlashCommandEntry::Info(info) => { + self.active_context_editor + .update(cx, |context_editor, cx| { + context_editor.insert_command(&info.name, cx) + }) + .ok(); + } + SlashCommandEntry::QuoteButton => { + cx.dispatch_action(Box::new(QuoteSelection)); + } + SlashCommandEntry::Advert { on_confirm, .. } => { + on_confirm(cx); + } } cx.emit(DismissEvent); } @@ -181,46 +185,78 @@ impl PickerDelegate for SlashCommandDelegate { .spacing(ListItemSpacing::Dense) .selected(selected) .child( - h_flex() + v_flex() .group(format!("command-entry-label-{ix}")) .w_full() .min_w(px(250.)) .child( - v_flex() - .child( - h_flex() - .child(div().font_buffer(cx).child({ - let mut label = format!("/{}", info.name); - if let Some(args) = - info.args.as_ref().filter(|_| selected) - { - label.push_str(&args); - } - Label::new(label).size(LabelSize::Small) - })) - .children(info.args.clone().filter(|_| !selected).map( - |args| { - div() - .font_buffer(cx) - .child( - Label::new(args) - .size(LabelSize::Small) - .color(Color::Muted), - ) - .visible_on_hover(format!( - "command-entry-label-{ix}" - )) - }, - )), - ) - .child( - Label::new(info.description.clone()) - .size(LabelSize::Small) - .color(Color::Muted), - ), + h_flex() + .gap_1p5() + .child(Icon::new(info.icon).size(IconSize::XSmall)) + .child(div().font_buffer(cx).child({ + let mut label = format!("{}", info.name); + if let Some(args) = info.args.as_ref().filter(|_| selected) + { + label.push_str(&args); + } + Label::new(label).size(LabelSize::Small) + })) + .children(info.args.clone().filter(|_| !selected).map( + |args| { + div() + .font_buffer(cx) + .child( + Label::new(args) + .size(LabelSize::Small) + .color(Color::Muted), + ) + .visible_on_hover(format!( + "command-entry-label-{ix}" + )) + }, + )), + ) + .child( + Label::new(info.description.clone()) + .size(LabelSize::Small) + .color(Color::Muted), ), ), ), + SlashCommandEntry::QuoteButton => { + let focus = cx.focus_handle(); + let key_binding = KeyBinding::for_action_in(&QuoteSelection, &focus, cx); + + Some( + ListItem::new(ix) + .inset(true) + .spacing(ListItemSpacing::Dense) + .selected(selected) + .child( + v_flex() + .child( + h_flex() + .gap_1p5() + .child(Icon::new(IconName::Quote).size(IconSize::XSmall)) + .child( + div().font_buffer(cx).child( + Label::new("selection").size(LabelSize::Small), + ), + ), + ) + .child( + h_flex() + .gap_1p5() + .child( + Label::new("Insert editor selection") + .color(Color::Muted) + .size(LabelSize::Small), + ) + .children(key_binding.map(|kb| kb.render(cx))), + ), + ), + ) + } SlashCommandEntry::Advert { renderer, .. } => Some( ListItem::new(ix) .inset(true) @@ -251,31 +287,50 @@ impl RenderOnce for SlashCommandSelector { name: command_name.into(), description: menu_text, args, + icon: command.icon(), })) }) - .chain([SlashCommandEntry::Advert { - name: "create-your-command".into(), - renderer: |cx| { - v_flex() - .child( - h_flex() - .font_buffer(cx) - .items_center() - .gap_1() - .child(div().font_buffer(cx).child( - Label::new("create-your-command").size(LabelSize::Small), - )) - .child(Icon::new(IconName::ArrowUpRight).size(IconSize::XSmall)), - ) - .child( - Label::new("Learn how to create a custom command") - .size(LabelSize::Small) - .color(Color::Muted), - ) - .into_any_element() + .chain([ + SlashCommandEntry::Advert { + name: "create-your-command".into(), + renderer: |cx| { + v_flex() + .w_full() + .child( + h_flex() + .w_full() + .font_buffer(cx) + .items_center() + .justify_between() + .child( + h_flex() + .items_center() + .gap_1p5() + .child(Icon::new(IconName::Plus).size(IconSize::XSmall)) + .child( + div().font_buffer(cx).child( + Label::new("create-your-command") + .size(LabelSize::Small), + ), + ), + ) + .child( + Icon::new(IconName::ArrowUpRight) + .size(IconSize::XSmall) + .color(Color::Muted), + ), + ) + .child( + Label::new("Create your custom command") + .size(LabelSize::Small) + .color(Color::Muted), + ) + .into_any_element() + }, + on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"), }, - on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"), - }]) + SlashCommandEntry::QuoteButton, + ]) .collect::>(); let delegate = SlashCommandDelegate { diff --git a/crates/assistant_slash_command/src/assistant_slash_command.rs b/crates/assistant_slash_command/src/assistant_slash_command.rs index de247602d8..58f4fcb9b4 100644 --- a/crates/assistant_slash_command/src/assistant_slash_command.rs +++ b/crates/assistant_slash_command/src/assistant_slash_command.rs @@ -62,6 +62,9 @@ pub type SlashCommandResult = Result String; + fn icon(&self) -> IconName { + IconName::Slash + } fn label(&self, _cx: &AppContext) -> CodeLabel { CodeLabel::plain(self.name(), None) } diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index 890476f5fe..e11b6edf32 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -284,6 +284,7 @@ pub enum IconName { Update, UserGroup, Visible, + Wand, Warning, WholeWord, XCircle,