mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
assistant: Add action footer and refine slash command popover (#16360)
- [x] Put the slash command popover on the footer - [x] Refine the popover (change it to a picker) - [x] Add more options dropdown on the assistant's toolbar - [x] Add quote selection button on the footer --- Release Notes: - N/A --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Co-authored-by: Nate Butler <iamnbutler@gmail.com> Co-authored-by: Kirill Bulatov <mail4score@gmail.com>
This commit is contained in:
parent
23d56a1a84
commit
2180dbdb50
1
assets/icons/ellipsis_vertical.svg
Normal file
1
assets/icons/ellipsis_vertical.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-ellipsis-vertical"><circle cx="12" cy="12" r="1"/><circle cx="12" cy="5" r="1"/><circle cx="12" cy="19" r="1"/></svg>
|
After Width: | Height: | Size: 320 B |
1
assets/icons/slash.svg
Normal file
1
assets/icons/slash.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-slash"><path d="M22 2 2 22"/></svg>
|
After Width: | Height: | Size: 238 B |
1
assets/icons/slash_square.svg
Normal file
1
assets/icons/slash_square.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-square-slash"><rect width="18" height="18" x="3" y="3" rx="2"/><line x1="9" x2="15" y1="15" y2="9"/></svg>
|
After Width: | Height: | Size: 309 B |
@ -9,6 +9,7 @@ mod model_selector;
|
||||
mod prompt_library;
|
||||
mod prompts;
|
||||
mod slash_command;
|
||||
mod slash_command_picker;
|
||||
pub mod slash_command_settings;
|
||||
mod streaming_diff;
|
||||
mod terminal_inline_assistant;
|
||||
|
@ -9,6 +9,7 @@ use crate::{
|
||||
file_command::codeblock_fence_for_path,
|
||||
SlashCommandCompletionProvider, SlashCommandRegistry,
|
||||
},
|
||||
slash_command_picker,
|
||||
terminal_inline_assistant::TerminalInlineAssistant,
|
||||
Assist, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore, CycleMessageRole,
|
||||
DeployHistory, DeployPromptLibrary, InlineAssist, InlineAssistId, InlineAssistant,
|
||||
@ -1718,6 +1719,7 @@ pub struct ContextEditor {
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
error_message: Option<SharedString>,
|
||||
show_accept_terms: bool,
|
||||
slash_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||
}
|
||||
|
||||
const DEFAULT_TAB_TITLE: &str = "New Context";
|
||||
@ -1779,6 +1781,7 @@ impl ContextEditor {
|
||||
assistant_panel,
|
||||
error_message: None,
|
||||
show_accept_terms: false,
|
||||
slash_menu_handle: Default::default(),
|
||||
};
|
||||
this.update_message_headers(cx);
|
||||
this.update_image_blocks(cx);
|
||||
@ -2007,7 +2010,7 @@ impl ContextEditor {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
|
||||
pub fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
|
||||
if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.transact(cx, |editor, cx| {
|
||||
@ -3589,11 +3592,11 @@ impl ContextEditor {
|
||||
button.tooltip(move |_| tooltip.clone())
|
||||
})
|
||||
.layer(ElevationIndex::ModalSurface)
|
||||
.child(Label::new(button_text))
|
||||
.children(
|
||||
KeyBinding::for_action_in(&Assist, &focus_handle, cx)
|
||||
.map(|binding| binding.into_any_element()),
|
||||
)
|
||||
.child(Label::new(button_text))
|
||||
.on_click(move |_event, cx| {
|
||||
focus_handle.dispatch_action(&Assist, cx);
|
||||
})
|
||||
@ -3623,7 +3626,13 @@ impl Render for ContextEditor {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let focus_handle = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
Some(workspace.active_item_as::<Editor>(cx)?.focus_handle(cx))
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
v_flex()
|
||||
.key_context("ContextEditor")
|
||||
.capture_action(cx.listener(ContextEditor::cancel))
|
||||
@ -3700,14 +3709,47 @@ impl Render for ContextEditor {
|
||||
)
|
||||
})
|
||||
.child(
|
||||
h_flex().flex_none().relative().child(
|
||||
h_flex().w_full().relative().child(
|
||||
h_flex()
|
||||
.p_2()
|
||||
.w_full()
|
||||
.absolute()
|
||||
.right_4()
|
||||
.bottom_2()
|
||||
.justify_end()
|
||||
.child(self.render_send_button(cx)),
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.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")
|
||||
.meta("Press to quote via keyboard")
|
||||
.key_binding(focus_handle.as_ref().and_then(
|
||||
|handle| {
|
||||
KeyBinding::for_action_in(
|
||||
&QuoteSelection,
|
||||
&handle,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
))
|
||||
})
|
||||
.into()
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_end()
|
||||
.child(div().child(self.render_send_button(cx))),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@ -3956,6 +3998,37 @@ pub struct ContextEditorToolbarItem {
|
||||
model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
|
||||
}
|
||||
|
||||
fn active_editor_focus_handle(
|
||||
workspace: &WeakView<Workspace>,
|
||||
cx: &WindowContext<'_>,
|
||||
) -> Option<FocusHandle> {
|
||||
workspace.upgrade().and_then(|workspace| {
|
||||
Some(
|
||||
workspace
|
||||
.read(cx)
|
||||
.active_item_as::<Editor>(cx)?
|
||||
.focus_handle(cx),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_inject_context_menu(
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> impl IntoElement {
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
|
||||
slash_command_picker::SlashCommandSelector::new(
|
||||
commands.clone(),
|
||||
active_context_editor,
|
||||
IconButton::new("trigger", IconName::SlashSquare)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(|cx| {
|
||||
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
impl ContextEditorToolbarItem {
|
||||
pub fn new(
|
||||
workspace: &Workspace,
|
||||
@ -3971,70 +4044,6 @@ impl ContextEditorToolbarItem {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_inject_context_menu(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||
let commands = SlashCommandRegistry::global(cx);
|
||||
let active_editor_focus_handle = self.workspace.upgrade().and_then(|workspace| {
|
||||
Some(
|
||||
workspace
|
||||
.read(cx)
|
||||
.active_item_as::<Editor>(cx)?
|
||||
.focus_handle(cx),
|
||||
)
|
||||
});
|
||||
let active_context_editor = self.active_context_editor.clone();
|
||||
|
||||
PopoverMenu::new("inject-context-menu")
|
||||
.trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
|
||||
Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
|
||||
}))
|
||||
.menu(move |cx| {
|
||||
let active_context_editor = active_context_editor.clone()?;
|
||||
ContextMenu::build(cx, |mut menu, _cx| {
|
||||
for command_name in commands.featured_command_names() {
|
||||
if let Some(command) = commands.command(&command_name) {
|
||||
let menu_text = SharedString::from(Arc::from(command.menu_text()));
|
||||
menu = menu.custom_entry(
|
||||
{
|
||||
let command_name = command_name.clone();
|
||||
move |_cx| {
|
||||
h_flex()
|
||||
.gap_4()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.child(Label::new(menu_text.clone()))
|
||||
.child(
|
||||
Label::new(format!("/{command_name}"))
|
||||
.color(Color::Muted),
|
||||
)
|
||||
.into_any()
|
||||
}
|
||||
},
|
||||
{
|
||||
let active_context_editor = active_context_editor.clone();
|
||||
move |cx| {
|
||||
active_context_editor
|
||||
.update(cx, |context_editor, cx| {
|
||||
context_editor.insert_command(&command_name, cx)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(active_editor_focus_handle) = active_editor_focus_handle.clone() {
|
||||
menu = menu
|
||||
.context(active_editor_focus_handle)
|
||||
.action("Quote Selection", Box::new(QuoteSelection));
|
||||
}
|
||||
|
||||
menu
|
||||
})
|
||||
.into()
|
||||
})
|
||||
}
|
||||
|
||||
fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
|
||||
let context = &self
|
||||
.active_context_editor
|
||||
@ -4081,24 +4090,16 @@ impl ContextEditorToolbarItem {
|
||||
impl Render for ContextEditorToolbarItem {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let left_side = h_flex()
|
||||
.pl_1()
|
||||
.gap_2()
|
||||
.flex_1()
|
||||
.min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
|
||||
.when(self.active_context_editor.is_some(), |left_side| {
|
||||
left_side
|
||||
.child(
|
||||
IconButton::new("regenerate-context", IconName::ArrowCircle)
|
||||
.visible_on_hover("toolbar")
|
||||
.tooltip(|cx| Tooltip::text("Regenerate Summary", cx))
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||
})),
|
||||
)
|
||||
.child(self.model_summary_editor.clone())
|
||||
left_side.child(self.model_summary_editor.clone())
|
||||
});
|
||||
let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
|
||||
let active_model = LanguageModelRegistry::read_global(cx).active_model();
|
||||
|
||||
let weak_self = cx.view().downgrade();
|
||||
let right_side = h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
@ -4148,7 +4149,70 @@ impl Render for ContextEditorToolbarItem {
|
||||
.with_handle(self.model_selector_menu_handle.clone()),
|
||||
)
|
||||
.children(self.render_remaining_tokens(cx))
|
||||
.child(self.render_inject_context_menu(cx));
|
||||
.child(
|
||||
PopoverMenu::new("context-editor-popover")
|
||||
.trigger(
|
||||
IconButton::new("context-editor-trigger", IconName::EllipsisVertical)
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(|cx| Tooltip::text("Open Context Options", cx)),
|
||||
)
|
||||
.menu({
|
||||
let weak_self = weak_self.clone();
|
||||
move |cx| {
|
||||
let weak_self = weak_self.clone();
|
||||
Some(ContextMenu::build(cx, move |menu, cx| {
|
||||
let context = weak_self
|
||||
.update(cx, |this, cx| {
|
||||
active_editor_focus_handle(&this.workspace, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten();
|
||||
menu.when_some(context, |menu, context| menu.context(context))
|
||||
.entry("Regenerate Context Title", None, {
|
||||
let weak_self = weak_self.clone();
|
||||
move |cx| {
|
||||
weak_self
|
||||
.update(cx, |_, cx| {
|
||||
cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.custom_entry(
|
||||
|_| {
|
||||
h_flex()
|
||||
.w_full()
|
||||
.justify_between()
|
||||
.gap_2()
|
||||
.child(Label::new("Insert Context"))
|
||||
.child(Label::new("/ command").color(Color::Muted))
|
||||
.into_any()
|
||||
},
|
||||
{
|
||||
let weak_self = weak_self.clone();
|
||||
move |cx| {
|
||||
weak_self
|
||||
.update(cx, |this, cx| {
|
||||
if let Some(editor) =
|
||||
&this.active_context_editor
|
||||
{
|
||||
editor
|
||||
.update(cx, |this, cx| {
|
||||
this.slash_menu_handle
|
||||
.toggle(cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
},
|
||||
)
|
||||
.action("Insert Selection", QuoteSelection.boxed_clone())
|
||||
}))
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
h_flex()
|
||||
.size_full()
|
||||
|
201
crates/assistant/src/slash_command_picker.rs
Normal file
201
crates/assistant/src/slash_command_picker.rs
Normal file
@ -0,0 +1,201 @@
|
||||
use assistant_slash_command::SlashCommandRegistry;
|
||||
use gpui::DismissEvent;
|
||||
use gpui::WeakView;
|
||||
use picker::PickerEditorPosition;
|
||||
|
||||
use std::sync::Arc;
|
||||
use ui::ListItemSpacing;
|
||||
|
||||
use gpui::SharedString;
|
||||
use gpui::Task;
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use ui::{prelude::*, ListItem, PopoverMenu, PopoverMenuHandle, PopoverTrigger};
|
||||
|
||||
use crate::assistant_panel::ContextEditor;
|
||||
|
||||
#[derive(IntoElement)]
|
||||
pub struct SlashCommandSelector<T: PopoverTrigger> {
|
||||
handle: Option<PopoverMenuHandle<Picker<SlashCommandDelegate>>>,
|
||||
registry: Arc<SlashCommandRegistry>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
trigger: T,
|
||||
info_text: Option<SharedString>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct SlashCommandInfo {
|
||||
name: SharedString,
|
||||
description: SharedString,
|
||||
}
|
||||
|
||||
pub struct SlashCommandDelegate {
|
||||
all_commands: Vec<SlashCommandInfo>,
|
||||
filtered_commands: Vec<SlashCommandInfo>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> SlashCommandSelector<T> {
|
||||
pub fn new(
|
||||
registry: Arc<SlashCommandRegistry>,
|
||||
active_context_editor: WeakView<ContextEditor>,
|
||||
trigger: T,
|
||||
) -> Self {
|
||||
SlashCommandSelector {
|
||||
handle: None,
|
||||
registry,
|
||||
active_context_editor,
|
||||
trigger,
|
||||
info_text: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_handle(mut self, handle: PopoverMenuHandle<Picker<SlashCommandDelegate>>) -> Self {
|
||||
self.handle = Some(handle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_info_text(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.info_text = Some(text.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PickerDelegate for SlashCommandDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn match_count(&self) -> usize {
|
||||
self.filtered_commands.len()
|
||||
}
|
||||
|
||||
fn selected_index(&self) -> usize {
|
||||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
self.selected_index = ix.min(self.filtered_commands.len().saturating_sub(1));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
"Select a command...".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
let all_commands = self.all_commands.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let filtered_commands = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
if query.is_empty() {
|
||||
all_commands
|
||||
} else {
|
||||
all_commands
|
||||
.into_iter()
|
||||
.filter(|model_info| {
|
||||
model_info
|
||||
.name
|
||||
.to_lowercase()
|
||||
.contains(&query.to_lowercase())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.delegate.filtered_commands = filtered_commands;
|
||||
this.delegate.set_selected_index(0, cx);
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
if let Some(command) = self.filtered_commands.get(self.selected_index) {
|
||||
self.active_context_editor
|
||||
.update(cx, |context_editor, cx| {
|
||||
context_editor.insert_command(&command.name, cx)
|
||||
})
|
||||
.ok();
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
|
||||
|
||||
fn editor_position(&self) -> PickerEditorPosition {
|
||||
PickerEditorPosition::End
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
_: &mut ViewContext<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let command_info = self.filtered_commands.get(ix)?;
|
||||
|
||||
Some(
|
||||
ListItem::new(ix)
|
||||
.inset(true)
|
||||
.spacing(ListItemSpacing::Sparse)
|
||||
.selected(selected)
|
||||
.child(
|
||||
h_flex().w_full().min_w(px(220.)).child(
|
||||
v_flex()
|
||||
.child(
|
||||
Label::new(format!("/{}", command_info.name))
|
||||
.size(LabelSize::Small),
|
||||
)
|
||||
.child(
|
||||
Label::new(command_info.description.clone())
|
||||
.size(LabelSize::Small)
|
||||
.color(Color::Muted),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PopoverTrigger> RenderOnce for SlashCommandSelector<T> {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
let all_models = self
|
||||
.registry
|
||||
.featured_command_names()
|
||||
.into_iter()
|
||||
.filter_map(|command_name| {
|
||||
let command = self.registry.command(&command_name)?;
|
||||
let menu_text = SharedString::from(Arc::from(command.menu_text()));
|
||||
Some(SlashCommandInfo {
|
||||
name: command_name.into(),
|
||||
description: menu_text,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let delegate = SlashCommandDelegate {
|
||||
all_commands: all_models.clone(),
|
||||
active_context_editor: self.active_context_editor.clone(),
|
||||
filtered_commands: all_models,
|
||||
selected_index: 0,
|
||||
};
|
||||
|
||||
let picker_view = cx.new_view(|cx| {
|
||||
let picker = Picker::uniform_list(delegate, cx).max_height(Some(rems(20.).into()));
|
||||
picker
|
||||
});
|
||||
|
||||
PopoverMenu::new("model-switcher")
|
||||
.menu(move |_cx| Some(picker_view.clone()))
|
||||
.trigger(self.trigger)
|
||||
.attach(gpui::AnchorCorner::TopLeft)
|
||||
.anchor(gpui::AnchorCorner::BottomLeft)
|
||||
.offset(gpui::Point {
|
||||
x: px(0.0),
|
||||
y: px(-16.0),
|
||||
})
|
||||
}
|
||||
}
|
@ -51,6 +51,15 @@ pub struct Picker<D: PickerDelegate> {
|
||||
is_modal: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||
pub enum PickerEditorPosition {
|
||||
#[default]
|
||||
/// Render the editor at the start of the picker. Usually the top
|
||||
Start,
|
||||
/// Render the editor at the end of the picker. Usually the bottom
|
||||
End,
|
||||
}
|
||||
|
||||
pub trait PickerDelegate: Sized + 'static {
|
||||
type ListItem: IntoElement;
|
||||
|
||||
@ -103,8 +112,16 @@ pub trait PickerDelegate: Sized + 'static {
|
||||
None
|
||||
}
|
||||
|
||||
fn editor_position(&self) -> PickerEditorPosition {
|
||||
PickerEditorPosition::default()
|
||||
}
|
||||
|
||||
fn render_editor(&self, editor: &View<Editor>, _cx: &mut ViewContext<Picker<Self>>) -> Div {
|
||||
v_flex()
|
||||
.when(
|
||||
self.editor_position() == PickerEditorPosition::End,
|
||||
|this| this.child(Divider::horizontal()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.overflow_hidden()
|
||||
@ -113,7 +130,10 @@ pub trait PickerDelegate: Sized + 'static {
|
||||
.px_3()
|
||||
.child(editor.clone()),
|
||||
)
|
||||
.child(Divider::horizontal())
|
||||
.when(
|
||||
self.editor_position() == PickerEditorPosition::Start,
|
||||
|this| this.child(Divider::horizontal()),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_match(
|
||||
@ -555,6 +575,8 @@ impl<D: PickerDelegate> ModalView for Picker<D> {}
|
||||
|
||||
impl<D: PickerDelegate> Render for Picker<D> {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let editor_position = self.delegate.editor_position();
|
||||
|
||||
v_flex()
|
||||
.key_context("Picker")
|
||||
.size_full()
|
||||
@ -574,9 +596,15 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
||||
.on_action(cx.listener(Self::secondary_confirm))
|
||||
.on_action(cx.listener(Self::confirm_completion))
|
||||
.on_action(cx.listener(Self::confirm_input))
|
||||
.child(match &self.head {
|
||||
Head::Editor(editor) => self.delegate.render_editor(&editor.clone(), cx),
|
||||
Head::Empty(empty_head) => div().child(empty_head.clone()),
|
||||
.children(match &self.head {
|
||||
Head::Editor(editor) => {
|
||||
if editor_position == PickerEditorPosition::Start {
|
||||
Some(self.delegate.render_editor(&editor.clone(), cx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Head::Empty(empty_head) => Some(div().child(empty_head.clone())),
|
||||
})
|
||||
.when(self.delegate.match_count() > 0, |el| {
|
||||
el.child(
|
||||
@ -602,5 +630,15 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
||||
)
|
||||
})
|
||||
.children(self.delegate.render_footer(cx))
|
||||
.children(match &self.head {
|
||||
Head::Editor(editor) => {
|
||||
if editor_position == PickerEditorPosition::End {
|
||||
Some(self.delegate.render_editor(&editor.clone(), cx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Head::Empty(empty_head) => Some(div().child(empty_head.clone())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +157,7 @@ pub enum IconName {
|
||||
Disconnected,
|
||||
Download,
|
||||
Ellipsis,
|
||||
EllipsisVertical,
|
||||
Envelope,
|
||||
Escape,
|
||||
ExclamationTriangle,
|
||||
@ -233,6 +234,8 @@ pub enum IconName {
|
||||
Server,
|
||||
Settings,
|
||||
Shift,
|
||||
Slash,
|
||||
SlashSquare,
|
||||
Sliders,
|
||||
SlidersAlt,
|
||||
Snip,
|
||||
@ -320,6 +323,7 @@ impl IconName {
|
||||
IconName::Disconnected => "icons/disconnected.svg",
|
||||
IconName::Download => "icons/download.svg",
|
||||
IconName::Ellipsis => "icons/ellipsis.svg",
|
||||
IconName::EllipsisVertical => "icons/ellipsis_vertical.svg",
|
||||
IconName::Envelope => "icons/feedback.svg",
|
||||
IconName::Escape => "icons/escape.svg",
|
||||
IconName::ExclamationTriangle => "icons/warning.svg",
|
||||
@ -396,6 +400,8 @@ impl IconName {
|
||||
IconName::Server => "icons/server.svg",
|
||||
IconName::Settings => "icons/file_icons/settings.svg",
|
||||
IconName::Shift => "icons/shift.svg",
|
||||
IconName::Slash => "icons/slash.svg",
|
||||
IconName::SlashSquare => "icons/slash_square.svg",
|
||||
IconName::Sliders => "icons/sliders.svg",
|
||||
IconName::SlidersAlt => "icons/sliders-alt.svg",
|
||||
IconName::Snip => "icons/snip.svg",
|
||||
|
Loading…
Reference in New Issue
Block a user