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