mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
assistant: Add the "create your command" item (#16467)
This PR adds an extra item to the slash command picker that links users to the doc that teaches how to create a custom one. Release Notes: - N/A --------- Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
This commit is contained in:
parent
0bea4d5fa6
commit
bac8e81e73
@ -1,9 +1,11 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use assistant_slash_command::SlashCommandRegistry;
|
use assistant_slash_command::SlashCommandRegistry;
|
||||||
|
use gpui::AnyElement;
|
||||||
use gpui::DismissEvent;
|
use gpui::DismissEvent;
|
||||||
use gpui::WeakView;
|
use gpui::WeakView;
|
||||||
use picker::PickerEditorPosition;
|
use picker::PickerEditorPosition;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use ui::ListItemSpacing;
|
use ui::ListItemSpacing;
|
||||||
|
|
||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
@ -24,11 +26,31 @@ pub(super) struct SlashCommandSelector<T: PopoverTrigger> {
|
|||||||
struct SlashCommandInfo {
|
struct SlashCommandInfo {
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
description: SharedString,
|
description: SharedString,
|
||||||
|
args: Option<SharedString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum SlashCommandEntry {
|
||||||
|
Info(SlashCommandInfo),
|
||||||
|
Advert {
|
||||||
|
name: SharedString,
|
||||||
|
renderer: fn(&mut WindowContext<'_>) -> AnyElement,
|
||||||
|
on_confirm: fn(&mut WindowContext<'_>),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for SlashCommandEntry {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
SlashCommandEntry::Info(SlashCommandInfo { name, .. })
|
||||||
|
| SlashCommandEntry::Advert { name, .. } => name,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct SlashCommandDelegate {
|
pub(crate) struct SlashCommandDelegate {
|
||||||
all_commands: Vec<SlashCommandInfo>,
|
all_commands: Vec<SlashCommandEntry>,
|
||||||
filtered_commands: Vec<SlashCommandInfo>,
|
filtered_commands: Vec<SlashCommandEntry>,
|
||||||
active_context_editor: WeakView<ContextEditor>,
|
active_context_editor: WeakView<ContextEditor>,
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
}
|
}
|
||||||
@ -80,7 +102,7 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|model_info| {
|
.filter(|model_info| {
|
||||||
model_info
|
model_info
|
||||||
.name
|
.as_ref()
|
||||||
.to_lowercase()
|
.to_lowercase()
|
||||||
.contains(&query.to_lowercase())
|
.contains(&query.to_lowercase())
|
||||||
})
|
})
|
||||||
@ -98,13 +120,42 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn separators_after_indices(&self) -> Vec<usize> {
|
||||||
|
let mut ret = vec![];
|
||||||
|
let mut previous_is_advert = false;
|
||||||
|
|
||||||
|
for (index, command) in self.filtered_commands.iter().enumerate() {
|
||||||
|
if previous_is_advert {
|
||||||
|
if let SlashCommandEntry::Info(_) = command {
|
||||||
|
previous_is_advert = false;
|
||||||
|
debug_assert_ne!(
|
||||||
|
index, 0,
|
||||||
|
"index cannot be zero, as we can never have a separator at 0th position"
|
||||||
|
);
|
||||||
|
ret.push(index - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let SlashCommandEntry::Advert { .. } = command {
|
||||||
|
previous_is_advert = true;
|
||||||
|
if index != 0 {
|
||||||
|
ret.push(index - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
if let Some(command) = self.filtered_commands.get(self.selected_index) {
|
if let Some(command) = self.filtered_commands.get(self.selected_index) {
|
||||||
self.active_context_editor
|
if let SlashCommandEntry::Info(info) = command {
|
||||||
.update(cx, |context_editor, cx| {
|
self.active_context_editor
|
||||||
context_editor.insert_command(&command.name, cx)
|
.update(cx, |context_editor, cx| {
|
||||||
})
|
context_editor.insert_command(&info.name, cx)
|
||||||
.ok();
|
})
|
||||||
|
.ok();
|
||||||
|
} else if let SlashCommandEntry::Advert { on_confirm, .. } = command {
|
||||||
|
on_confirm(cx);
|
||||||
|
}
|
||||||
cx.emit(DismissEvent);
|
cx.emit(DismissEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,30 +170,63 @@ impl PickerDelegate for SlashCommandDelegate {
|
|||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let command_info = self.filtered_commands.get(ix)?;
|
let command_info = self.filtered_commands.get(ix)?;
|
||||||
|
|
||||||
Some(
|
match command_info {
|
||||||
ListItem::new(ix)
|
SlashCommandEntry::Info(info) => Some(
|
||||||
.inset(true)
|
ListItem::new(ix)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.inset(true)
|
||||||
.selected(selected)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
.child(
|
.selected(selected)
|
||||||
h_flex().w_full().min_w(px(220.)).child(
|
.child(
|
||||||
v_flex()
|
h_flex()
|
||||||
|
.group(format!("command-entry-label-{ix}"))
|
||||||
|
.w_full()
|
||||||
|
.min_w(px(220.))
|
||||||
.child(
|
.child(
|
||||||
Label::new(format!("/{}", command_info.name))
|
v_flex()
|
||||||
.size(LabelSize::Small),
|
.child(
|
||||||
)
|
h_flex()
|
||||||
.child(
|
.child(div().font_buffer(cx).child({
|
||||||
Label::new(command_info.description.clone())
|
let mut label = format!("/{}", info.name);
|
||||||
.size(LabelSize::Small)
|
if let Some(args) =
|
||||||
.color(Color::Muted),
|
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),
|
||||||
|
)
|
||||||
|
.visible_on_hover(format!(
|
||||||
|
"command-entry-label-{ix}"
|
||||||
|
))
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
Label::new(info.description.clone())
|
||||||
|
.size(LabelSize::Small)
|
||||||
|
.color(Color::Muted),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
SlashCommandEntry::Advert { renderer, .. } => Some(
|
||||||
|
ListItem::new(ix)
|
||||||
|
.inset(true)
|
||||||
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.selected(selected)
|
||||||
|
.child(renderer(cx)),
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,11 +239,41 @@ impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
|||||||
.filter_map(|command_name| {
|
.filter_map(|command_name| {
|
||||||
let command = self.registry.command(&command_name)?;
|
let command = self.registry.command(&command_name)?;
|
||||||
let menu_text = SharedString::from(Arc::from(command.menu_text()));
|
let menu_text = SharedString::from(Arc::from(command.menu_text()));
|
||||||
Some(SlashCommandInfo {
|
let label = command.label(cx);
|
||||||
|
let args = label.filter_range.end.ne(&label.text.len()).then(|| {
|
||||||
|
SharedString::from(
|
||||||
|
label.text[label.filter_range.end..label.text.len()].to_owned(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Some(SlashCommandEntry::Info(SlashCommandInfo {
|
||||||
name: command_name.into(),
|
name: command_name.into(),
|
||||||
description: menu_text,
|
description: menu_text,
|
||||||
})
|
args,
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
.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()
|
||||||
|
},
|
||||||
|
on_confirm: |cx| cx.open_url("https://zed.dev/docs/extensions/slash-commands"),
|
||||||
|
}])
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let delegate = SlashCommandDelegate {
|
let delegate = SlashCommandDelegate {
|
||||||
|
@ -524,7 +524,7 @@ impl<D: PickerDelegate> Picker<D> {
|
|||||||
picker
|
picker
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.border_b_1()
|
.border_b_1()
|
||||||
.pb(px(-1.0))
|
.py(px(-1.0))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user