Make command dispatching work
This commit is contained in:
parent
a1d9f351db
commit
fa153a0d56
11 changed files with 188 additions and 118 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -6153,6 +6153,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings2",
|
"settings2",
|
||||||
"theme2",
|
"theme2",
|
||||||
|
"ui2",
|
||||||
"util",
|
"util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,14 @@ use anyhow::anyhow;
|
||||||
use collections::{CommandPaletteFilter, HashMap};
|
use collections::{CommandPaletteFilter, HashMap};
|
||||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, Action, AnyElement, AnyWindowHandle, AppContext, BorrowWindow, Div, Element,
|
actions, div, Action, AnyElement, AnyWindowHandle, AppContext, BorrowWindow, Component, Div,
|
||||||
EventEmitter, FocusHandle, Keystroke, ParentElement, Render, Styled, View, ViewContext,
|
Element, EventEmitter, FocusHandle, Keystroke, ParentElement, Render, StatelessInteractive,
|
||||||
VisualContext, WeakView,
|
Styled, View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use std::cmp::{self, Reverse};
|
use std::cmp::{self, Reverse};
|
||||||
use ui::modal;
|
use theme::ActiveTheme;
|
||||||
|
use ui::{modal, Label};
|
||||||
use util::{
|
use util::{
|
||||||
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
|
||||||
ResultExt,
|
ResultExt,
|
||||||
|
@ -19,29 +20,17 @@ use zed_actions::OpenZedURL;
|
||||||
actions!(Toggle);
|
actions!(Toggle);
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
dbg!("init");
|
|
||||||
cx.set_global(HitCounts::default());
|
cx.set_global(HitCounts::default());
|
||||||
|
|
||||||
cx.observe_new_views(
|
cx.observe_new_views(
|
||||||
|workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
|
|workspace: &mut Workspace, _: &mut ViewContext<Workspace>| {
|
||||||
dbg!("new workspace found");
|
workspace.modal_layer().register_modal(Toggle, |_, cx| {
|
||||||
workspace
|
let Some(previous_focus_handle) = cx.focused() else {
|
||||||
.modal_layer()
|
return None;
|
||||||
.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();
|
Some(cx.build_view(|cx| CommandPalette::new(previous_focus_handle, cx)))
|
||||||
dbg!(&available_actions);
|
});
|
||||||
|
|
||||||
Some(cx.build_view(|cx| {
|
|
||||||
let delegate =
|
|
||||||
CommandPaletteDelegate::new(cx.view().downgrade(), focus_handle);
|
|
||||||
CommandPalette::new(delegate, cx)
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
@ -52,8 +41,35 @@ pub struct CommandPalette {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CommandPalette {
|
impl CommandPalette {
|
||||||
fn new(delegate: CommandPaletteDelegate, cx: &mut ViewContext<Self>) -> Self {
|
fn new(previous_focus_handle: FocusHandle, cx: &mut ViewContext<Self>) -> Self {
|
||||||
let picker = cx.build_view(|cx| Picker::new(delegate, cx));
|
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 }
|
Self { picker }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,19 +94,10 @@ pub struct CommandInterceptResult {
|
||||||
|
|
||||||
pub struct CommandPaletteDelegate {
|
pub struct CommandPaletteDelegate {
|
||||||
command_palette: WeakView<CommandPalette>,
|
command_palette: WeakView<CommandPalette>,
|
||||||
actions: Vec<Command>,
|
commands: Vec<Command>,
|
||||||
matches: Vec<StringMatch>,
|
matches: Vec<StringMatch>,
|
||||||
selected_ix: usize,
|
selected_ix: usize,
|
||||||
focus_handle: FocusHandle,
|
previous_focus_handle: FocusHandle,
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Event {
|
|
||||||
Dismissed,
|
|
||||||
Confirmed {
|
|
||||||
window: AnyWindowHandle,
|
|
||||||
focused_view_id: usize,
|
|
||||||
action: Box<dyn Action>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Command {
|
struct Command {
|
||||||
|
@ -115,10 +122,15 @@ impl Clone for Command {
|
||||||
struct HitCounts(HashMap<String, usize>);
|
struct HitCounts(HashMap<String, usize>);
|
||||||
|
|
||||||
impl CommandPaletteDelegate {
|
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 {
|
Self {
|
||||||
command_palette,
|
command_palette,
|
||||||
actions: Default::default(),
|
commands,
|
||||||
matches: vec![StringMatch {
|
matches: vec![StringMatch {
|
||||||
candidate_id: 0,
|
candidate_id: 0,
|
||||||
score: 0.,
|
score: 0.,
|
||||||
|
@ -126,7 +138,7 @@ impl CommandPaletteDelegate {
|
||||||
string: "Foo my bar".into(),
|
string: "Foo my bar".into(),
|
||||||
}],
|
}],
|
||||||
selected_ix: 0,
|
selected_ix: 0,
|
||||||
focus_handle,
|
previous_focus_handle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,11 +163,11 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
query: String,
|
query: String,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> gpui::Task<()> {
|
) -> gpui::Task<()> {
|
||||||
let view_id = &self.focus_handle;
|
let view_id = &self.previous_focus_handle;
|
||||||
let window = cx.window();
|
let window = cx.window();
|
||||||
cx.spawn(move |picker, mut cx| async move {
|
cx.spawn(move |picker, mut cx| async move {
|
||||||
let mut actions = picker
|
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");
|
.expect("todo: handle picker no longer being around");
|
||||||
// _ = window
|
// _ = window
|
||||||
// .available_actions(view_id, &cx)
|
// .available_actions(view_id, &cx)
|
||||||
|
@ -276,7 +288,7 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
picker
|
picker
|
||||||
.update(&mut cx, |picker, _| {
|
.update(&mut cx, |picker, _| {
|
||||||
let delegate = &mut picker.delegate;
|
let delegate = &mut picker.delegate;
|
||||||
delegate.actions = actions;
|
delegate.commands = actions;
|
||||||
delegate.matches = matches;
|
delegate.matches = matches;
|
||||||
if delegate.matches.is_empty() {
|
if delegate.matches.is_empty() {
|
||||||
delegate.selected_ix = 0;
|
delegate.selected_ix = 0;
|
||||||
|
@ -290,32 +302,25 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
dbg!("dismissed");
|
|
||||||
self.command_palette
|
self.command_palette
|
||||||
.update(cx, |command_palette, cx| cx.emit(ModalEvent::Dismissed))
|
.update(cx, |_, cx| cx.emit(ModalEvent::Dismissed))
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||||
// if !self.matches.is_empty() {
|
if self.matches.is_empty() {
|
||||||
// let window = cx.window();
|
self.dismissed(cx);
|
||||||
// let focused_view_id = self.focused_view_id;
|
return;
|
||||||
// let action_ix = self.matches[self.selected_ix].candidate_id;
|
}
|
||||||
// let command = self.actions.remove(action_ix);
|
let action_ix = self.matches[self.selected_ix].candidate_id;
|
||||||
// cx.update_default_global(|hit_counts: &mut HitCounts, _| {
|
let command = self.commands.swap_remove(action_ix);
|
||||||
// *hit_counts.0.entry(command.name).or_default() += 1;
|
cx.update_global(|hit_counts: &mut HitCounts, _| {
|
||||||
// });
|
*hit_counts.0.entry(command.name).or_default() += 1;
|
||||||
// let action = command.action;
|
});
|
||||||
|
let action = command.action;
|
||||||
// cx.app_context()
|
cx.focus(&self.previous_focus_handle);
|
||||||
// .spawn(move |mut cx| async move {
|
cx.dispatch_action(action);
|
||||||
// window
|
self.dismissed(cx);
|
||||||
// .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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_match(
|
fn render_match(
|
||||||
|
@ -324,7 +329,26 @@ impl PickerDelegate for CommandPaletteDelegate {
|
||||||
selected: bool,
|
selected: bool,
|
||||||
cx: &mut ViewContext<Picker<Self>>,
|
cx: &mut ViewContext<Picker<Self>>,
|
||||||
) -> Self::ListItem {
|
) -> 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(
|
// fn render_match(
|
||||||
|
|
|
@ -4149,16 +4149,12 @@ fn build_key_listeners(
|
||||||
build_key_listener(
|
build_key_listener(
|
||||||
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
|
move |editor, key_down: &KeyDownEvent, dispatch_context, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
dbg!(&dispatch_context);
|
|
||||||
if let KeyMatch::Some(action) = cx.match_keystroke(
|
if let KeyMatch::Some(action) = cx.match_keystroke(
|
||||||
&global_element_id,
|
&global_element_id,
|
||||||
&key_down.keystroke,
|
&key_down.keystroke,
|
||||||
dispatch_context,
|
dispatch_context,
|
||||||
) {
|
) {
|
||||||
dbg!("got action", &action);
|
|
||||||
return Some(action);
|
return Some(action);
|
||||||
} else {
|
|
||||||
dbg!("not action");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,17 @@ impl dyn Action {
|
||||||
pub fn type_id(&self) -> TypeId {
|
pub fn type_id(&self) -> TypeId {
|
||||||
self.as_any().type_id()
|
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>>;
|
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -114,7 +124,7 @@ lazy_static! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ActionRegistry {
|
struct ActionRegistry {
|
||||||
builders_by_name: HashMap<SharedString, ActionBuilder>,
|
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.
|
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 name = A::qualified_name();
|
||||||
let mut lock = ACTION_REGISTRY.write();
|
let mut lock = ACTION_REGISTRY.write();
|
||||||
lock.builders_by_name.insert(name.clone(), A::build);
|
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);
|
lock.all_names.push(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct an action based on its name and optional JSON parameters sourced from the keymap.
|
/// 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>> {
|
pub fn build_action_from_type(type_id: &TypeId) -> Result<Box<dyn Action>> {
|
||||||
let lock = ACTION_REGISTRY.read();
|
let lock = ACTION_REGISTRY.read();
|
||||||
|
let name = lock
|
||||||
let build_action = lock
|
.names_by_type_id
|
||||||
.builders_by_type_id
|
|
||||||
.get(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.
|
/// 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| {
|
Box::new(move |_, key_down, context, phase, cx| {
|
||||||
if phase == DispatchPhase::Bubble {
|
if phase == DispatchPhase::Bubble {
|
||||||
let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
|
let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
|
||||||
dbg!(&context);
|
|
||||||
if let KeyMatch::Some(action) =
|
if let KeyMatch::Some(action) =
|
||||||
cx.match_keystroke(&global_id, &key_down.keystroke, context)
|
cx.match_keystroke(&global_id, &key_down.keystroke, context)
|
||||||
{
|
{
|
||||||
|
|
|
@ -44,19 +44,6 @@ impl KeyBinding {
|
||||||
pending_keystrokes: &[Keystroke],
|
pending_keystrokes: &[Keystroke],
|
||||||
contexts: &[&DispatchContext],
|
contexts: &[&DispatchContext],
|
||||||
) -> KeyMatch {
|
) -> 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)
|
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
|
||||||
&& self.matches_context(contexts)
|
&& self.matches_context(contexts)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,6 @@ impl KeyMatcher {
|
||||||
keystroke: &Keystroke,
|
keystroke: &Keystroke,
|
||||||
context_stack: &[&DispatchContext],
|
context_stack: &[&DispatchContext],
|
||||||
) -> KeyMatch {
|
) -> KeyMatch {
|
||||||
dbg!(keystroke, &context_stack);
|
|
||||||
let keymap = self.keymap.lock();
|
let keymap = self.keymap.lock();
|
||||||
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
|
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
|
||||||
if keymap.version() != self.keymap_version {
|
if keymap.version() != self.keymap_version {
|
||||||
|
|
|
@ -145,7 +145,7 @@ impl<V> Eq for WeakView<V> {}
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AnyView {
|
pub struct AnyView {
|
||||||
model: AnyModel,
|
model: AnyModel,
|
||||||
initialize: fn(&AnyView, &mut WindowContext) -> AnyBox,
|
pub initialize: fn(&AnyView, &mut WindowContext) -> AnyBox,
|
||||||
layout: fn(&AnyView, &mut AnyBox, &mut WindowContext) -> LayoutId,
|
layout: fn(&AnyView, &mut AnyBox, &mut WindowContext) -> LayoutId,
|
||||||
paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
|
paint: fn(&AnyView, &mut AnyBox, &mut WindowContext),
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,10 @@ impl AnyView {
|
||||||
.compute_layout(layout_id, available_space);
|
.compute_layout(layout_id, available_space);
|
||||||
(self.paint)(self, &mut rendered_element, cx);
|
(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 {
|
impl<V: 'static> Component<V> for AnyView {
|
||||||
|
|
|
@ -228,7 +228,7 @@ pub(crate) struct Frame {
|
||||||
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
|
key_matchers: HashMap<GlobalElementId, KeyMatcher>,
|
||||||
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
|
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
|
||||||
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
pub(crate) focus_listeners: Vec<AnyFocusListener>,
|
||||||
key_dispatch_stack: Vec<KeyDispatchStackFrame>,
|
pub(crate) key_dispatch_stack: Vec<KeyDispatchStackFrame>,
|
||||||
freeze_key_dispatch_stack: bool,
|
freeze_key_dispatch_stack: bool,
|
||||||
focus_parents_by_child: HashMap<FocusId, FocusId>,
|
focus_parents_by_child: HashMap<FocusId, FocusId>,
|
||||||
pub(crate) scene_builder: SceneBuilder,
|
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
|
/// 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
|
/// contexts when matching key events against the keymap. A key listener can be either an action
|
||||||
/// handler or a [KeyDown] / [KeyUp] event listener.
|
/// handler or a [KeyDown] / [KeyUp] event listener.
|
||||||
enum KeyDispatchStackFrame {
|
pub(crate) enum KeyDispatchStackFrame {
|
||||||
Listener {
|
Listener {
|
||||||
event_type: TypeId,
|
event_type: TypeId,
|
||||||
listener: AnyKeyListener,
|
listener: AnyKeyListener,
|
||||||
|
@ -407,6 +407,9 @@ impl<'a> WindowContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.window.focus = Some(handle.id);
|
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 {
|
self.app.push_effect(Effect::FocusChanged {
|
||||||
window_handle: self.window.handle,
|
window_handle: self.window.handle,
|
||||||
focused: Some(handle.id),
|
focused: Some(handle.id),
|
||||||
|
@ -428,6 +431,14 @@ impl<'a> WindowContext<'a> {
|
||||||
self.notify();
|
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
|
/// 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.
|
/// that are currently on the stack to be returned to the app.
|
||||||
pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
|
pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
|
||||||
|
@ -1055,6 +1066,26 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window.dirty = false;
|
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.
|
/// Rotate the current frame and the previous frame, then clear the current frame.
|
||||||
/// We repopulate all state in the current frame during each paint.
|
/// We repopulate all state in the current frame during each paint.
|
||||||
fn start_frame(&mut self) {
|
fn start_frame(&mut self) {
|
||||||
|
@ -1197,7 +1228,7 @@ impl<'a> WindowContext<'a> {
|
||||||
DispatchPhase::Capture,
|
DispatchPhase::Capture,
|
||||||
self,
|
self,
|
||||||
) {
|
) {
|
||||||
self.dispatch_action(action, &key_dispatch_stack[..ix]);
|
self.dispatch_action_internal(action, &key_dispatch_stack[..ix]);
|
||||||
}
|
}
|
||||||
if !self.app.propagate_event {
|
if !self.app.propagate_event {
|
||||||
break;
|
break;
|
||||||
|
@ -1224,7 +1255,10 @@ impl<'a> WindowContext<'a> {
|
||||||
DispatchPhase::Bubble,
|
DispatchPhase::Bubble,
|
||||||
self,
|
self,
|
||||||
) {
|
) {
|
||||||
self.dispatch_action(action, &key_dispatch_stack[..ix]);
|
self.dispatch_action_internal(
|
||||||
|
action,
|
||||||
|
&key_dispatch_stack[..ix],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.app.propagate_event {
|
if !self.app.propagate_event {
|
||||||
|
@ -1296,11 +1330,9 @@ impl<'a> WindowContext<'a> {
|
||||||
self.window.platform_window.prompt(level, msg, answers)
|
self.window.platform_window.prompt(level, msg, answers)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn available_actions(&mut self) -> Vec<Box<dyn Action>> {
|
pub fn available_actions(&self) -> impl Iterator<Item = Box<dyn Action>> + '_ {
|
||||||
let key_dispatch_stack = &self.window.current_frame.key_dispatch_stack;
|
let key_dispatch_stack = &self.window.previous_frame.key_dispatch_stack;
|
||||||
let mut actions = Vec::new();
|
key_dispatch_stack.iter().filter_map(|frame| {
|
||||||
dbg!(key_dispatch_stack.len());
|
|
||||||
for frame in key_dispatch_stack {
|
|
||||||
match frame {
|
match frame {
|
||||||
// todo!factor out a KeyDispatchStackFrame::Action
|
// todo!factor out a KeyDispatchStackFrame::Action
|
||||||
KeyDispatchStackFrame::Listener {
|
KeyDispatchStackFrame::Listener {
|
||||||
|
@ -1308,21 +1340,19 @@ impl<'a> WindowContext<'a> {
|
||||||
listener: _,
|
listener: _,
|
||||||
} => {
|
} => {
|
||||||
match build_action_from_type(event_type) {
|
match build_action_from_type(event_type) {
|
||||||
Ok(action) => {
|
Ok(action) => Some(action),
|
||||||
actions.push(action);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
dbg!(err);
|
dbg!(err);
|
||||||
|
None
|
||||||
} // we'll hit his if TypeId == KeyDown
|
} // we'll hit his if TypeId == KeyDown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KeyDispatchStackFrame::Context(_) => {}
|
KeyDispatchStackFrame::Context(_) => None,
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
actions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_action(
|
pub(crate) fn dispatch_action_internal(
|
||||||
&mut self,
|
&mut self,
|
||||||
action: Box<dyn Action>,
|
action: Box<dyn Action>,
|
||||||
dispatch_stack: &[KeyDispatchStackFrame],
|
dispatch_stack: &[KeyDispatchStackFrame],
|
||||||
|
|
|
@ -10,6 +10,7 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
editor = { package = "editor2", path = "../editor2" }
|
editor = { package = "editor2", path = "../editor2" }
|
||||||
|
ui = { package = "ui2", path = "../ui2" }
|
||||||
gpui = { package = "gpui2", path = "../gpui2" }
|
gpui = { package = "gpui2", path = "../gpui2" }
|
||||||
menu = { package = "menu2", path = "../menu2" }
|
menu = { package = "menu2", path = "../menu2" }
|
||||||
settings = { package = "settings2", path = "../settings2" }
|
settings = { package = "settings2", path = "../settings2" }
|
||||||
|
|
|
@ -5,6 +5,8 @@ use gpui::{
|
||||||
WindowContext,
|
WindowContext,
|
||||||
};
|
};
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
|
use theme::ActiveTheme;
|
||||||
|
use ui::v_stack;
|
||||||
|
|
||||||
pub struct Picker<D: PickerDelegate> {
|
pub struct Picker<D: PickerDelegate> {
|
||||||
pub delegate: D,
|
pub delegate: D,
|
||||||
|
@ -133,7 +135,7 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
impl<D: PickerDelegate> Render for Picker<D> {
|
impl<D: PickerDelegate> Render for Picker<D> {
|
||||||
type Element = Div<Self, StatefulInteractivity<Self>, FocusEnabled<Self>>;
|
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()
|
div()
|
||||||
.context("picker")
|
.context("picker")
|
||||||
.id("picker-container")
|
.id("picker-container")
|
||||||
|
@ -146,18 +148,33 @@ impl<D: PickerDelegate> Render for Picker<D> {
|
||||||
.on_action(Self::cancel)
|
.on_action(Self::cancel)
|
||||||
.on_action(Self::confirm)
|
.on_action(Self::confirm)
|
||||||
.on_action(Self::secondary_confirm)
|
.on_action(Self::secondary_confirm)
|
||||||
.child(self.editor.clone())
|
|
||||||
.child(
|
.child(
|
||||||
uniform_list("candidates", self.delegate.match_count(), {
|
v_stack().gap_px().child(
|
||||||
move |this: &mut Self, visible_range, cx| {
|
v_stack()
|
||||||
let selected_ix = this.delegate.selected_index();
|
.py_0p5()
|
||||||
visible_range
|
.px_1()
|
||||||
.map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
|
.child(div().px_2().py_0p5().child(self.editor.clone())),
|
||||||
.collect()
|
),
|
||||||
}
|
)
|
||||||
})
|
.child(
|
||||||
.track_scroll(self.scroll_handle.clone())
|
div()
|
||||||
.size_full(),
|
.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(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue