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