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}