key_context_view.rs

  1use gpui::{
  2    actions, Action, AppContext, EventEmitter, FocusHandle, FocusableView,
  3    KeyBindingContextPredicate, KeyContext, Keystroke, MouseButton, Render, Subscription,
  4};
  5use itertools::Itertools;
  6use serde_json::json;
  7use ui::{
  8    div, h_flex, px, v_flex, ButtonCommon, Clickable, FluentBuilder, InteractiveElement, Label,
  9    LabelCommon, LabelSize, ParentElement, SharedString, StatefulInteractiveElement, Styled,
 10    ViewContext, VisualContext, WindowContext,
 11};
 12use ui::{Button, ButtonStyle};
 13use workspace::Item;
 14use workspace::Workspace;
 15
 16actions!(debug, [OpenKeyContextView]);
 17
 18pub fn init(cx: &mut AppContext) {
 19    cx.observe_new_views(|workspace: &mut Workspace, _| {
 20        workspace.register_action(|workspace, _: &OpenKeyContextView, cx| {
 21            let key_context_view = cx.new_view(KeyContextView::new);
 22            workspace.add_item_to_active_pane(Box::new(key_context_view), None, true, cx)
 23        });
 24    })
 25    .detach();
 26}
 27
 28struct KeyContextView {
 29    pending_keystrokes: Option<Vec<Keystroke>>,
 30    last_keystrokes: Option<SharedString>,
 31    last_possibilities: Vec<(SharedString, SharedString, Option<bool>)>,
 32    context_stack: Vec<KeyContext>,
 33    focus_handle: FocusHandle,
 34    _subscriptions: [Subscription; 2],
 35}
 36
 37impl KeyContextView {
 38    pub fn new(cx: &mut ViewContext<Self>) -> Self {
 39        let sub1 = cx.observe_keystrokes(|this, e, cx| {
 40            let mut pending = this.pending_keystrokes.take().unwrap_or_default();
 41            pending.push(e.keystroke.clone());
 42            let mut possibilities = cx.all_bindings_for_input(&pending);
 43            possibilities.reverse();
 44            this.context_stack = cx.context_stack();
 45            this.last_keystrokes = Some(
 46                json!(pending.iter().map(|p| p.unparse()).join(" "))
 47                    .to_string()
 48                    .into(),
 49            );
 50            this.last_possibilities = possibilities
 51                .into_iter()
 52                .map(|binding| {
 53                    let match_state = if let Some(predicate) = binding.predicate() {
 54                        if this.matches(predicate) {
 55                            if this.action_matches(&e.action, binding.action()) {
 56                                Some(true)
 57                            } else {
 58                                Some(false)
 59                            }
 60                        } else {
 61                            None
 62                        }
 63                    } else {
 64                        if this.action_matches(&e.action, binding.action()) {
 65                            Some(true)
 66                        } else {
 67                            Some(false)
 68                        }
 69                    };
 70                    let predicate = if let Some(predicate) = binding.predicate() {
 71                        format!("{}", predicate)
 72                    } else {
 73                        "".to_string()
 74                    };
 75                    let mut name = binding.action().name();
 76                    if name == "zed::NoAction" {
 77                        name = "(null)"
 78                    }
 79
 80                    (
 81                        name.to_owned().into(),
 82                        json!(predicate).to_string().into(),
 83                        match_state,
 84                    )
 85                })
 86                .collect();
 87        });
 88        let sub2 = cx.observe_pending_input(|this, cx| {
 89            this.pending_keystrokes = cx
 90                .pending_input_keystrokes()
 91                .map(|k| k.iter().cloned().collect());
 92            if this.pending_keystrokes.is_some() {
 93                this.last_keystrokes.take();
 94            }
 95            cx.notify();
 96        });
 97
 98        Self {
 99            context_stack: Vec::new(),
100            pending_keystrokes: None,
101            last_keystrokes: None,
102            last_possibilities: Vec::new(),
103            focus_handle: cx.focus_handle(),
104            _subscriptions: [sub1, sub2],
105        }
106    }
107}
108
109impl EventEmitter<()> for KeyContextView {}
110
111impl FocusableView for KeyContextView {
112    fn focus_handle(&self, _: &AppContext) -> gpui::FocusHandle {
113        self.focus_handle.clone()
114    }
115}
116impl KeyContextView {
117    fn set_context_stack(&mut self, stack: Vec<KeyContext>, cx: &mut ViewContext<Self>) {
118        self.context_stack = stack;
119        cx.notify()
120    }
121
122    fn matches(&self, predicate: &KeyBindingContextPredicate) -> bool {
123        let mut stack = self.context_stack.clone();
124        while !stack.is_empty() {
125            if predicate.eval(&stack) {
126                return true;
127            }
128            stack.pop();
129        }
130        false
131    }
132
133    fn action_matches(&self, a: &Option<Box<dyn Action>>, b: &dyn Action) -> bool {
134        if let Some(last_action) = a {
135            last_action.partial_eq(b)
136        } else {
137            b.name() == "zed::NoAction"
138        }
139    }
140}
141
142impl Item for KeyContextView {
143    type Event = ();
144
145    fn to_item_events(_: &Self::Event, _: impl FnMut(workspace::item::ItemEvent)) {}
146
147    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
148        Some("Keyboard Context".into())
149    }
150
151    fn telemetry_event_text(&self) -> Option<&'static str> {
152        None
153    }
154
155    fn clone_on_split(
156        &self,
157        _workspace_id: Option<workspace::WorkspaceId>,
158        cx: &mut ViewContext<Self>,
159    ) -> Option<gpui::View<Self>>
160    where
161        Self: Sized,
162    {
163        Some(cx.new_view(Self::new))
164    }
165}
166
167impl Render for KeyContextView {
168    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl ui::IntoElement {
169        use itertools::Itertools;
170        v_flex()
171            .id("key-context-view")
172            .overflow_scroll()
173            .size_full()
174            .max_h_full()
175            .pt_4()
176            .pl_4()
177            .track_focus(&self.focus_handle)
178            .key_context("KeyContextView")
179            .on_mouse_up_out(
180                MouseButton::Left,
181                cx.listener(|this, _, cx| {
182                    this.last_keystrokes.take();
183                    this.set_context_stack(cx.context_stack(), cx);
184                }),
185            )
186            .on_mouse_up_out(
187                MouseButton::Right,
188                cx.listener(|_, _, cx| {
189                    cx.defer(|this, cx| {
190                        this.last_keystrokes.take();
191                        this.set_context_stack(cx.context_stack(), cx);
192                    });
193                }),
194            )
195            .child(Label::new("Keyboard Context").size(LabelSize::Large))
196            .child(Label::new("This view lets you determine the current context stack for creating custom key bindings in Zed. When a keyboard shortcut is triggered, it also shows all the possible contexts it could have triggered in, and which one matched."))
197            .child(
198                h_flex()
199                    .mt_4()
200                    .gap_4()
201                    .child(
202                        Button::new("default", "Open Documentation")
203                            .style(ButtonStyle::Filled)
204                            .on_click(|_, cx| cx.open_url("https://zed.dev/docs/key-bindings")),
205                    )
206                    .child(
207                        Button::new("default", "View default keymap")
208                            .style(ButtonStyle::Filled)
209                            .key_binding(ui::KeyBinding::for_action(
210                                &zed_actions::OpenDefaultKeymap,
211                                cx,
212                            ))
213                            .on_click(|_, cx| {
214                                cx.dispatch_action(workspace::SplitRight.boxed_clone());
215                                cx.dispatch_action(zed_actions::OpenDefaultKeymap.boxed_clone());
216                            }),
217                    )
218                    .child(
219                        Button::new("default", "Edit your keymap")
220                            .style(ButtonStyle::Filled)
221                            .key_binding(ui::KeyBinding::for_action(&zed_actions::OpenKeymap, cx))
222                            .on_click(|_, cx| {
223                                cx.dispatch_action(workspace::SplitRight.boxed_clone());
224                                cx.dispatch_action(zed_actions::OpenKeymap.boxed_clone());
225                            }),
226                    ),
227            )
228            .child(
229                Label::new("Current Context Stack")
230                    .size(LabelSize::Large)
231                    .mt_8(),
232            )
233            .children({
234                cx.context_stack().iter().enumerate().map(|(i, context)| {
235                    let primary = context.primary().map(|e| e.key.clone()).unwrap_or_default();
236                    let secondary = context
237                        .secondary()
238                        .map(|e| {
239                            if let Some(value) = e.value.as_ref() {
240                                format!("{}={}", e.key, value)
241                            } else {
242                                e.key.to_string()
243                            }
244                        })
245                        .join(" ");
246                    Label::new(format!("{} {}", primary, secondary)).ml(px(12. * (i + 1) as f32))
247                })
248            })
249            .child(Label::new("Last Keystroke").mt_4().size(LabelSize::Large))
250            .when_some(self.pending_keystrokes.as_ref(), |el, keystrokes| {
251                el.child(
252                    Label::new(format!(
253                        "Waiting for more input: {}",
254                        keystrokes.iter().map(|k| k.unparse()).join(" ")
255                    ))
256                    .ml(px(12.)),
257                )
258            })
259            .when_some(self.last_keystrokes.as_ref(), |el, keystrokes| {
260                el.child(Label::new(format!("Typed: {}", keystrokes)).ml_4())
261                    .children(
262                        self.last_possibilities
263                            .iter()
264                            .map(|(name, predicate, state)| {
265                                let (text, color) = match state {
266                                    Some(true) => ("(match)", ui::Color::Success),
267                                    Some(false) => ("(low precedence)", ui::Color::Hint),
268                                    None => ("(no match)", ui::Color::Error),
269                                };
270                                h_flex()
271                                    .gap_2()
272                                    .ml_8()
273                                    .child(div().min_w(px(200.)).child(Label::new(name.clone())))
274                                    .child(Label::new(predicate.clone()))
275                                    .child(Label::new(text).color(color))
276                            }),
277                    )
278            })
279    }
280}