Cargo.lock 🔗
@@ -6153,6 +6153,7 @@ dependencies = [
"serde_json",
"settings2",
"theme2",
+ "ui2",
"util",
]
Conrad Irwin created
Cargo.lock | 1
crates/command_palette2/src/command_palette.rs | 152 +++++++++++--------
crates/editor2/src/element.rs | 4
crates/gpui2/src/action.rs | 26 ++
crates/gpui2/src/interactive.rs | 1
crates/gpui2/src/keymap/binding.rs | 13 -
crates/gpui2/src/keymap/matcher.rs | 1
crates/gpui2/src/view.rs | 6
crates/gpui2/src/window.rs | 62 ++++++--
crates/picker2/Cargo.toml | 1
crates/picker2/src/picker2.rs | 41 +++-
11 files changed, 189 insertions(+), 119 deletions(-)
@@ -6153,6 +6153,7 @@ dependencies = [
"serde_json",
"settings2",
"theme2",
+ "ui2",
"util",
]
@@ -2,13 +2,14 @@ use anyhow::anyhow;
use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{
- actions, div, Action, AnyElement, AnyWindowHandle, AppContext, BorrowWindow, Div, Element,
- EventEmitter, FocusHandle, Keystroke, ParentElement, Render, Styled, View, ViewContext,
- VisualContext, WeakView,
+ actions, div, Action, AnyElement, AnyWindowHandle, AppContext, BorrowWindow, Component, Div,
+ Element, EventEmitter, FocusHandle, Keystroke, ParentElement, Render, StatelessInteractive,
+ Styled, View, ViewContext, VisualContext, WeakView,
};
use picker::{Picker, PickerDelegate};
use std::cmp::{self, Reverse};
-use ui::modal;
+use theme::ActiveTheme;
+use ui::{modal, Label};
use util::{
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
ResultExt,
@@ -19,29 +20,17 @@ use zed_actions::OpenZedURL;
actions!(Toggle);
pub fn init(cx: &mut AppContext) {
- dbg!("init");
cx.set_global(HitCounts::default());
cx.observe_new_views(
|workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
- dbg!("new workspace found");
- workspace
- .modal_layer()
- .register_modal(Toggle, |workspace, cx| {
- dbg!("hitting cmd-shift-p");
- let Some(focus_handle) = cx.focused() else {
- return None;
- };
-
- let available_actions = cx.available_actions();
- dbg!(&available_actions);
-
- Some(cx.build_view(|cx| {
- let delegate =
- CommandPaletteDelegate::new(cx.view().downgrade(), focus_handle);
- CommandPalette::new(delegate, cx)
- }))
- });
+ workspace.modal_layer().register_modal(Toggle, |_, cx| {
+ let Some(previous_focus_handle) = cx.focused() else {
+ return None;
+ };
+
+ Some(cx.build_view(|cx| CommandPalette::new(previous_focus_handle, cx)))
+ });
},
)
.detach();
@@ -52,8 +41,35 @@ pub struct CommandPalette {
}
impl CommandPalette {
- fn new(delegate: CommandPaletteDelegate, cx: &mut ViewContext<Self>) -> Self {
- let picker = cx.build_view(|cx| Picker::new(delegate, cx));
+ fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self {
+ let filter = cx.try_global::<CommandPaletteFilter>();
+
+ let commands = cx
+ .available_actions()
+ .into_iter()
+ .filter_map(|action| {
+ let name = action.name();
+ let namespace = name.split("::").next().unwrap_or("malformed action name");
+ if filter.is_some_and(|f| f.filtered_namespaces.contains(namespace)) {
+ return None;
+ }
+
+ Some(Command {
+ name: humanize_action_name(&name),
+ action,
+ keystrokes: vec![], // todo!()
+ })
+ })
+ .collect();
+
+ let delegate =
+ CommandPaletteDelegate::new(cx.view().downgrade(), commands, previous_focus_handle, cx);
+
+ let picker = cx.build_view(|cx| {
+ let picker = Picker::new(delegate, cx);
+ picker.focus(cx);
+ picker
+ });
Self { picker }
}
}
@@ -78,19 +94,10 @@ pub struct CommandInterceptResult {
pub struct CommandPaletteDelegate {
command_palette: WeakView<CommandPalette>,
- actions: Vec<Command>,
+ commands: Vec<Command>,
matches: Vec<StringMatch>,
selected_ix: usize,
- focus_handle: FocusHandle,
-}
-
-pub enum Event {
- Dismissed,
- Confirmed {
- window: AnyWindowHandle,
- focused_view_id: usize,
- action: Box<dyn Action>,
- },
+ previous_focus_handle: FocusHandle,
}
struct Command {
@@ -115,10 +122,15 @@ impl Clone for Command {
struct HitCounts(HashMap<String, usize>);
impl CommandPaletteDelegate {
- pub fn new(command_palette: WeakView<CommandPalette>, focus_handle: FocusHandle) -> Self {
+ fn new(
+ command_palette: WeakView<CommandPalette>,
+ commands: Vec<Command>,
+ previous_focus_handle: FocusHandle,
+ cx: &ViewContext<CommandPalette>,
+ ) -> Self {
Self {
command_palette,
- actions: Default::default(),
+ commands,
matches: vec![StringMatch {
candidate_id: 0,
score: 0.,
@@ -126,7 +138,7 @@ impl CommandPaletteDelegate {
string: "Foo my bar".into(),
}],
selected_ix: 0,
- focus_handle,
+ previous_focus_handle,
}
}
}
@@ -151,11 +163,11 @@ impl PickerDelegate for CommandPaletteDelegate {
query: String,
cx: &mut ViewContext<Picker<Self>>,
) -> gpui::Task<()> {
- let view_id = &self.focus_handle;
+ let view_id = &self.previous_focus_handle;
let window = cx.window();
cx.spawn(move |picker, mut cx| async move {
let mut actions = picker
- .update(&mut cx, |this, _| this.delegate.actions.clone())
+ .update(&mut cx, |this, _| this.delegate.commands.clone())
.expect("todo: handle picker no longer being around");
// _ = window
// .available_actions(view_id, &cx)
@@ -276,7 +288,7 @@ impl PickerDelegate for CommandPaletteDelegate {
picker
.update(&mut cx, |picker, _| {
let delegate = &mut picker.delegate;
- delegate.actions = actions;
+ delegate.commands = actions;
delegate.matches = matches;
if delegate.matches.is_empty() {
delegate.selected_ix = 0;
@@ -290,32 +302,25 @@ impl PickerDelegate for CommandPaletteDelegate {
}
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
- dbg!("dismissed");
self.command_palette
- .update(cx, |command_palette, cx| cx.emit(ModalEvent::Dismissed))
+ .update(cx, |_, cx| cx.emit(ModalEvent::Dismissed))
.log_err();
}
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
- // if !self.matches.is_empty() {
- // let window = cx.window();
- // let focused_view_id = self.focused_view_id;
- // let action_ix = self.matches[self.selected_ix].candidate_id;
- // let command = self.actions.remove(action_ix);
- // cx.update_default_global(|hit_counts: &mut HitCounts, _| {
- // *hit_counts.0.entry(command.name).or_default() += 1;
- // });
- // let action = command.action;
-
- // cx.app_context()
- // .spawn(move |mut cx| async move {
- // window
- // .dispatch_action(focused_view_id, action.as_ref(), &mut cx)
- // .ok_or_else(|| anyhow!("window was closed"))
- // })
- // .detach_and_log_err(cx);
- // }
- self.dismissed(cx)
+ if self.matches.is_empty() {
+ self.dismissed(cx);
+ return;
+ }
+ let action_ix = self.matches[self.selected_ix].candidate_id;
+ let command = self.commands.swap_remove(action_ix);
+ cx.update_global(|hit_counts: &mut HitCounts, _| {
+ *hit_counts.0.entry(command.name).or_default() += 1;
+ });
+ let action = command.action;
+ cx.focus(&self.previous_focus_handle);
+ cx.dispatch_action(action);
+ self.dismissed(cx);
}
fn render_match(
@@ -324,7 +329,26 @@ impl PickerDelegate for CommandPaletteDelegate {
selected: bool,
cx: &mut ViewContext<Picker<Self>>,
) -> Self::ListItem {
- div().child("ooh yeah")
+ let colors = cx.theme().colors();
+ let Some(command) = self
+ .matches
+ .get(ix)
+ .and_then(|m| self.commands.get(m.candidate_id))
+ else {
+ return div();
+ };
+
+ div()
+ .text_color(colors.text)
+ .when(selected, |s| {
+ s.border_l_10().border_color(colors.terminal_ansi_yellow)
+ })
+ .hover(|style| {
+ style
+ .bg(colors.element_active)
+ .text_color(colors.text_accent)
+ })
+ .child(Label::new(command.name.clone()))
}
// fn render_match(
@@ -4149,16 +4149,12 @@ fn build_key_listeners(
build_key_listener(
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
if phase == DispatchPhase::Bubble {
- dbg!(&dispatch_context);
if let KeyMatch::Some(action) = cx.match_keystroke(
&global_element_id,
&key_down.keystroke,
dispatch_context,
) {
- dbg!("got action", &action);
return Some(action);
- } else {
- dbg!("not action");
}
}
@@ -104,7 +104,17 @@ impl dyn Action {
pub fn type_id(&self) -> TypeId {
self.as_any().type_id()
}
+
+ pub fn name(&self) -> SharedString {
+ ACTION_REGISTRY
+ .read()
+ .names_by_type_id
+ .get(&self.type_id())
+ .expect("type is not a registered action")
+ .clone()
+ }
}
+
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
lazy_static! {
@@ -114,7 +124,7 @@ lazy_static! {
#[derive(Default)]
struct ActionRegistry {
builders_by_name: HashMap<SharedString, ActionBuilder>,
- builders_by_type_id: HashMap<TypeId, ActionBuilder>,
+ names_by_type_id: HashMap<TypeId, SharedString>,
all_names: Vec<SharedString>, // So we can return a static slice.
}
@@ -123,20 +133,22 @@ pub fn register_action<A: Action>() {
let name = A::qualified_name();
let mut lock = ACTION_REGISTRY.write();
lock.builders_by_name.insert(name.clone(), A::build);
- lock.builders_by_type_id.insert(TypeId::of::<A>(), A::build);
+ lock.names_by_type_id
+ .insert(TypeId::of::<A>(), name.clone());
lock.all_names.push(name);
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
pub fn build_action_from_type(type_id: &TypeId) -> Result<Box<dyn Action>> {
let lock = ACTION_REGISTRY.read();
-
- let build_action = lock
- .builders_by_type_id
+ let name = lock
+ .names_by_type_id
.get(type_id)
- .ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?;
+ .ok_or_else(|| anyhow!("no action type registered for {:?}", type_id))?
+ .clone();
+ drop(lock);
- (build_action)(None)
+ build_action(&name, None)
}
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
@@ -414,7 +414,6 @@ pub trait ElementInteractivity<V: 'static>: 'static {
Box::new(move |_, key_down, context, phase, cx| {
if phase == DispatchPhase::Bubble {
let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
- dbg!(&context);
if let KeyMatch::Some(action) =
cx.match_keystroke(&global_id, &key_down.keystroke, context)
{
@@ -44,19 +44,6 @@ impl KeyBinding {
pending_keystrokes: &[Keystroke],
contexts: &[&DispatchContext],
) -> KeyMatch {
- let should_debug = self.keystrokes.len() == 1
- && self.keystrokes[0].key == "p"
- && self.keystrokes[0].modifiers.command == true
- && self.keystrokes[0].modifiers.shift == true;
-
- if false && should_debug {
- dbg!(
- &self.keystrokes,
- &pending_keystrokes,
- &contexts,
- &self.matches_context(contexts)
- );
- }
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts)
{
@@ -46,7 +46,6 @@ impl KeyMatcher {
keystroke: &Keystroke,
context_stack: &[&DispatchContext],
) -> KeyMatch {
- dbg!(keystroke, &context_stack);
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
if keymap.version() != self.keymap_version {
@@ -145,7 +145,7 @@ impl<V> Eq for WeakView<V> {}
#[derive(Clone, Debug)]
pub struct AnyView {
model: AnyModel,
- initialize: fn(&AnyView, &mut WindowContext) -> AnyBox,
+ pub initialize: fn(&AnyView, &mut WindowContext) -> AnyBox,
layout: fn(&AnyView, &mut AnyBox, &mut WindowContext) -> LayoutId,
paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
}
@@ -184,6 +184,10 @@ impl AnyView {
.compute_layout(layout_id, available_space);
(self.paint)(self, &mut rendered_element, cx);
}
+
+ pub(crate) fn draw_dispatch_stack(&self, cx: &mut WindowContext) {
+ (self.initialize)(self, cx);
+ }
}
impl<V: 'static> Component<V> for AnyView {
@@ -228,7 +228,7 @@ pub(crate) struct Frame {
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
pub(crate) focus_listeners: Vec<AnyFocusListener>,
- key_dispatch_stack: Vec<KeyDispatchStackFrame>,
+ pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
freeze_key_dispatch_stack: bool,
focus_parents_by_child: HashMap<FocusId, FocusId>,
pub(crate) scene_builder: SceneBuilder,
@@ -327,7 +327,7 @@ impl Window {
/// find the focused element. We interleave key listeners with dispatch contexts so we can use the
/// contexts when matching key events against the keymap. A key listener can be either an action
/// handler or a [KeyDown] / [KeyUp] event listener.
-enum KeyDispatchStackFrame {
+pub(crate) enum KeyDispatchStackFrame {
Listener {
event_type: TypeId,
listener: AnyKeyListener,
@@ -407,6 +407,9 @@ impl<'a> WindowContext<'a> {
}
self.window.focus = Some(handle.id);
+
+ // self.window.current_frame.key_dispatch_stack.clear()
+ // self.window.root_view.initialize()
self.app.push_effect(Effect::FocusChanged {
window_handle: self.window.handle,
focused: Some(handle.id),
@@ -428,6 +431,14 @@ impl<'a> WindowContext<'a> {
self.notify();
}
+ pub fn dispatch_action(&mut self, action: Box<dyn Action>) {
+ self.defer(|cx| {
+ cx.app.propagate_event = true;
+ let stack = cx.dispatch_stack();
+ cx.dispatch_action_internal(action, &stack[..])
+ })
+ }
+
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
/// that are currently on the stack to be returned to the app.
pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
@@ -1055,6 +1066,26 @@ impl<'a> WindowContext<'a> {
self.window.dirty = false;
}
+ pub(crate) fn dispatch_stack(&mut self) -> Vec<KeyDispatchStackFrame> {
+ let root_view = self.window.root_view.take().unwrap();
+ let window = &mut *self.window;
+ let mut spare_frame = Frame::default();
+ mem::swap(&mut spare_frame, &mut window.previous_frame);
+
+ self.start_frame();
+
+ root_view.draw_dispatch_stack(self);
+
+ let window = &mut *self.window;
+ // restore the old values of current and previous frame,
+ // putting the new frame into spare_frame.
+ mem::swap(&mut window.current_frame, &mut window.previous_frame);
+ mem::swap(&mut spare_frame, &mut window.previous_frame);
+ self.window.root_view = Some(root_view);
+
+ spare_frame.key_dispatch_stack
+ }
+
/// Rotate the current frame and the previous frame, then clear the current frame.
/// We repopulate all state in the current frame during each paint.
fn start_frame(&mut self) {
@@ -1197,7 +1228,7 @@ impl<'a> WindowContext<'a> {
DispatchPhase::Capture,
self,
) {
- self.dispatch_action(action, &key_dispatch_stack[..ix]);
+ self.dispatch_action_internal(action, &key_dispatch_stack[..ix]);
}
if !self.app.propagate_event {
break;
@@ -1224,7 +1255,10 @@ impl<'a> WindowContext<'a> {
DispatchPhase::Bubble,
self,
) {
- self.dispatch_action(action, &key_dispatch_stack[..ix]);
+ self.dispatch_action_internal(
+ action,
+ &key_dispatch_stack[..ix],
+ );
}
if !self.app.propagate_event {
@@ -1296,11 +1330,9 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.prompt(level, msg, answers)
}
- pub fn available_actions(&mut self) -> Vec<Box<dyn Action>> {
- let key_dispatch_stack = &self.window.current_frame.key_dispatch_stack;
- let mut actions = Vec::new();
- dbg!(key_dispatch_stack.len());
- for frame in key_dispatch_stack {
+ pub fn available_actions(&self) -> impl Iterator<Item = Box<dyn Action>> + '_ {
+ let key_dispatch_stack = &self.window.previous_frame.key_dispatch_stack;
+ key_dispatch_stack.iter().filter_map(|frame| {
match frame {
// todo!factor out a KeyDispatchStackFrame::Action
KeyDispatchStackFrame::Listener {
@@ -1308,21 +1340,19 @@ impl<'a> WindowContext<'a> {
listener: _,
} => {
match build_action_from_type(event_type) {
- Ok(action) => {
- actions.push(action);
- }
+ Ok(action) => Some(action),
Err(err) => {
dbg!(err);
+ None
} // we'll hit his if TypeId == KeyDown
}
}
- KeyDispatchStackFrame::Context(_) => {}
+ KeyDispatchStackFrame::Context(_) => None,
}
- }
- actions
+ })
}
- fn dispatch_action(
+ pub(crate) fn dispatch_action_internal(
&mut self,
action: Box<dyn Action>,
dispatch_stack: &[KeyDispatchStackFrame],
@@ -10,6 +10,7 @@ doctest = false
[dependencies]
editor = { package = "editor2", path = "../editor2" }
+ui = { package = "ui2", path = "../ui2" }
gpui = { package = "gpui2", path = "../gpui2" }
menu = { package = "menu2", path = "../menu2" }
settings = { package = "settings2", path = "../settings2" }
@@ -5,6 +5,8 @@ use gpui::{
WindowContext,
};
use std::cmp;
+use theme::ActiveTheme;
+use ui::v_stack;
pub struct Picker<D: PickerDelegate> {
pub delegate: D,
@@ -133,7 +135,7 @@ impl<D: PickerDelegate> Picker<D> {
impl<D: PickerDelegate> Render for Picker<D> {
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
- fn render(&mut self, _cx: &mut ViewContext<Self>) -> Self::Element {
+ fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div()
.context("picker")
.id("picker-container")
@@ -146,18 +148,33 @@ impl<D: PickerDelegate> Render for Picker<D> {
.on_action(Self::cancel)
.on_action(Self::confirm)
.on_action(Self::secondary_confirm)
- .child(self.editor.clone())
.child(
- uniform_list("candidates", self.delegate.match_count(), {
- move |this: &mut Self, visible_range, cx| {
- let selected_ix = this.delegate.selected_index();
- visible_range
- .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
- .collect()
- }
- })
- .track_scroll(self.scroll_handle.clone())
- .size_full(),
+ v_stack().gap_px().child(
+ v_stack()
+ .py_0p5()
+ .px_1()
+ .child(div().px_2().py_0p5().child(self.editor.clone())),
+ ),
+ )
+ .child(
+ div()
+ .h_px()
+ .w_full()
+ .bg(cx.theme().colors().element_background),
+ )
+ .child(
+ v_stack().py_0p5().px_1().grow().max_h_96().child(
+ uniform_list("candidates", self.delegate.match_count(), {
+ move |this: &mut Self, visible_range, cx| {
+ let selected_ix = this.delegate.selected_index();
+ visible_range
+ .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
+ .collect()
+ }
+ })
+ .track_scroll(self.scroll_handle.clone())
+ .size_full(),
+ ),
)
}
}