1use gpui::{
  2    Action, App, AppContext as _, Entity, EventEmitter, FocusHandle, Focusable,
  3    KeyBindingContextPredicate, KeyContext, Keystroke, MouseButton, Render, Subscription, actions,
  4};
  5use itertools::Itertools;
  6use serde_json::json;
  7use ui::{Button, ButtonStyle};
  8use ui::{
  9    ButtonCommon, Clickable, Context, FluentBuilder, InteractiveElement, Label, LabelCommon,
 10    LabelSize, ParentElement, SharedString, StatefulInteractiveElement, Styled, Window, div,
 11    h_flex, px, v_flex,
 12};
 13use workspace::{Item, SplitDirection, Workspace};
 14
 15actions!(
 16    dev,
 17    [
 18        /// Opens the key context view for debugging keybindings.
 19        OpenKeyContextView
 20    ]
 21);
 22
 23pub fn init(cx: &mut App) {
 24    cx.observe_new(|workspace: &mut Workspace, _, _| {
 25        workspace.register_action(|workspace, _: &OpenKeyContextView, window, cx| {
 26            let key_context_view = cx.new(|cx| KeyContextView::new(window, cx));
 27            workspace.split_item(
 28                SplitDirection::Right,
 29                Box::new(key_context_view),
 30                window,
 31                cx,
 32            )
 33        });
 34    })
 35    .detach();
 36}
 37
 38struct KeyContextView {
 39    pending_keystrokes: Option<Vec<Keystroke>>,
 40    last_keystrokes: Option<SharedString>,
 41    last_possibilities: Vec<(SharedString, SharedString, Option<bool>)>,
 42    context_stack: Vec<KeyContext>,
 43    focus_handle: FocusHandle,
 44    _subscriptions: [Subscription; 2],
 45}
 46
 47impl KeyContextView {
 48    pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
 49        let sub1 = cx.observe_keystrokes(|this, e, _, cx| {
 50            let mut pending = this.pending_keystrokes.take().unwrap_or_default();
 51            pending.push(e.keystroke.clone());
 52            let mut possibilities = cx.all_bindings_for_input(&pending);
 53            possibilities.reverse();
 54            this.last_keystrokes = Some(
 55                json!(pending.iter().map(|p| p.unparse()).join(" "))
 56                    .to_string()
 57                    .into(),
 58            );
 59            this.context_stack = e.context_stack.clone();
 60            this.last_possibilities = possibilities
 61                .into_iter()
 62                .map(|binding| {
 63                    let match_state = if let Some(predicate) = binding.predicate() {
 64                        if this.matches(&predicate) {
 65                            if this.action_matches(&e.action, binding.action()) {
 66                                Some(true)
 67                            } else {
 68                                Some(false)
 69                            }
 70                        } else {
 71                            None
 72                        }
 73                    } else if this.action_matches(&e.action, binding.action()) {
 74                        Some(true)
 75                    } else {
 76                        Some(false)
 77                    };
 78                    let predicate = if let Some(predicate) = binding.predicate() {
 79                        format!("{}", predicate)
 80                    } else {
 81                        "".to_string()
 82                    };
 83                    let mut name = binding.action().name();
 84                    if name == "zed::NoAction" {
 85                        name = "(null)"
 86                    }
 87
 88                    (
 89                        name.to_owned().into(),
 90                        json!(predicate).to_string().into(),
 91                        match_state,
 92                    )
 93                })
 94                .collect();
 95            cx.notify();
 96        });
 97        let sub2 = cx.observe_pending_input(window, |this, window, cx| {
 98            this.pending_keystrokes = window.pending_input_keystrokes().map(|k| k.to_vec());
 99            if this.pending_keystrokes.is_some() {
100                this.last_keystrokes.take();
101            }
102            cx.notify();
103        });
104
105        Self {
106            context_stack: Vec::new(),
107            pending_keystrokes: None,
108            last_keystrokes: None,
109            last_possibilities: Vec::new(),
110            focus_handle: cx.focus_handle(),
111            _subscriptions: [sub1, sub2],
112        }
113    }
114}
115
116impl EventEmitter<()> for KeyContextView {}
117
118impl Focusable for KeyContextView {
119    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
120        self.focus_handle.clone()
121    }
122}
123impl KeyContextView {
124    fn set_context_stack(&mut self, stack: Vec<KeyContext>, cx: &mut Context<Self>) {
125        self.context_stack = stack;
126        cx.notify()
127    }
128
129    fn matches(&self, predicate: &KeyBindingContextPredicate) -> bool {
130        predicate.depth_of(&self.context_stack).is_some()
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, _detail: usize, _cx: &App) -> SharedString {
148        "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        window: &mut Window,
159        cx: &mut Context<Self>,
160    ) -> Option<Entity<Self>>
161    where
162        Self: Sized,
163    {
164        Some(cx.new(|cx| KeyContextView::new(window, cx)))
165    }
166}
167
168impl Render for KeyContextView {
169    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
170        use itertools::Itertools;
171
172        let key_equivalents = cx.keyboard_mapper().get_key_equivalents();
173        v_flex()
174            .id("key-context-view")
175            .overflow_scroll()
176            .size_full()
177            .max_h_full()
178            .pt_4()
179            .pl_4()
180            .track_focus(&self.focus_handle)
181            .key_context("KeyContextView")
182            .on_mouse_up_out(
183                MouseButton::Left,
184                cx.listener(|this, _, window, cx| {
185                    this.last_keystrokes.take();
186                    this.set_context_stack(window.context_stack(), cx);
187                }),
188            )
189            .on_mouse_up_out(
190                MouseButton::Right,
191                cx.listener(|_, _, window, cx| {
192                    cx.defer_in(window, |this, window, cx| {
193                        this.last_keystrokes.take();
194                        this.set_context_stack(window.context_stack(), cx);
195                    });
196                }),
197            )
198            .child(Label::new("Keyboard Context").size(LabelSize::Large))
199            .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."))
200            .child(
201                h_flex()
202                    .mt_4()
203                    .gap_4()
204                    .child(
205                        Button::new("open_documentation", "Open Documentation")
206                            .style(ButtonStyle::Filled)
207                            .on_click(|_, _, cx| cx.open_url("https://zed.dev/docs/key-bindings")),
208                    )
209                    .child(
210                        Button::new("view_default_keymap", "View Default Keymap")
211                            .style(ButtonStyle::Filled)
212                            .key_binding(ui::KeyBinding::for_action(
213                                &zed_actions::OpenDefaultKeymap,
214                                window,
215                                cx
216                            ))
217                            .on_click(|_, window, cx| {
218                                window.dispatch_action(zed_actions::OpenDefaultKeymap.boxed_clone(), cx);
219                            }),
220                    )
221                    .child(
222                        Button::new("edit_your_keymap", "Edit Keymap File")
223                            .style(ButtonStyle::Filled)
224                            .key_binding(ui::KeyBinding::for_action(&zed_actions::OpenKeymapFile, window, cx))
225                            .on_click(|_, window, cx| {
226                                window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx);
227                            }),
228                    ),
229            )
230            .child(
231                Label::new("Current Context Stack")
232                    .size(LabelSize::Large)
233                    .mt_8(),
234            )
235            .children({
236                self.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}