use gpui::{
App, Context, EventEmitter, FocusHandle, Focusable, KeyBinding, SharedString, Window, actions,
div, prelude::*, px,
};
use theme::Theme;
use crate::{input, popover, search::SearchList, surface::Surfaced as _};
actions!(
bezel_command_palette,
[SelectNext, SelectPrevious, Confirm, Dismiss]
);
pub const KEY_CONTEXT: &str = "CommandPalette";
pub fn init(cx: &mut App) {
cx.bind_keys(bindings());
}
pub fn bindings() -> Vec<KeyBinding> {
let mut bindings = Vec::new();
let ctx = Some(KEY_CONTEXT);
bindings.extend([
KeyBinding::new("down", SelectNext, ctx),
KeyBinding::new("up", SelectPrevious, ctx),
KeyBinding::new("enter", Confirm, ctx),
KeyBinding::new("escape", Dismiss, ctx),
KeyBinding::new("ctrl-n", SelectNext, ctx),
KeyBinding::new("ctrl-p", SelectPrevious, ctx),
]);
bindings
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaletteEvent {
Selected(usize),
Dismissed,
}
pub struct CommandPalette {
search: SearchList,
focus_handle: FocusHandle,
}
impl EventEmitter<PaletteEvent> for CommandPalette {}
impl CommandPalette {
pub fn new(items: Vec<SharedString>, cx: &mut Context<Self>) -> Self {
Self {
search: SearchList::new(
items,
"Type a command…",
|view: &mut Self| &mut view.search,
cx,
),
focus_handle: cx.focus_handle(),
}
}
pub fn focus(&self, window: &mut Window, cx: &mut App) {
window.focus(&self.search.query.focus_handle(cx), cx);
}
pub fn query_text(&self, cx: &App) -> SharedString {
self.search.query.read(cx).content().clone()
}
pub fn active_item(&self) -> Option<usize> {
self.search.filter.active_item()
}
fn choose(&mut self, item: usize, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(PaletteEvent::Selected(item));
}
fn select_next(&mut self, _: &SelectNext, _: &mut Window, cx: &mut Context<Self>) {
self.search.filter.step(1);
cx.notify();
}
fn select_previous(&mut self, _: &SelectPrevious, _: &mut Window, cx: &mut Context<Self>) {
self.search.filter.step(-1);
cx.notify();
}
fn confirm(&mut self, _: &Confirm, _: &mut Window, cx: &mut Context<Self>) {
if let Some(item) = self.active_item() {
cx.emit(PaletteEvent::Selected(item));
}
}
fn dismiss(&mut self, _: &Dismiss, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(PaletteEvent::Dismissed);
}
}
impl Focusable for CommandPalette {
fn focus_handle(&self, _: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for CommandPalette {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = Theme::of(cx).clone();
let card = popover::popover_card(&theme)
.w(px(420.0))
.child(
self.search
.body(&theme, None, |view| &mut view.search, Self::choose, cx),
);
div()
.key_context(KEY_CONTEXT)
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::select_next))
.on_action(cx.listener(Self::select_previous))
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::dismiss))
.child(card.surface(&theme, theme.popover_surface))
}
}
pub use input::KEY_CONTEXT as FIELD_KEY_CONTEXT;