keybindings.rs

  1use std::{ops::Range, sync::Arc};
  2
  3use collections::HashSet;
  4use db::anyhow::anyhow;
  5use editor::{Editor, EditorEvent};
  6use feature_flags::FeatureFlagViewExt;
  7use fs::Fs;
  8use fuzzy::{StringMatch, StringMatchCandidate};
  9use gpui::{
 10    AppContext as _, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
 11    FontWeight, Global, KeyContext, Keystroke, ModifiersChangedEvent, ScrollStrategy, Subscription,
 12    WeakEntity, actions, div,
 13};
 14use settings::KeybindSource;
 15use util::ResultExt;
 16
 17use ui::{
 18    ActiveTheme as _, App, BorrowAppContext, ParentElement as _, Render, SharedString, Styled as _,
 19    Window, prelude::*,
 20};
 21use workspace::{Item, ModalView, SerializableItem, Workspace, register_serializable_item};
 22
 23use crate::{
 24    SettingsUiFeatureFlag,
 25    keybindings::persistence::KEYBINDING_EDITORS,
 26    ui_components::table::{Table, TableInteractionState},
 27};
 28
 29actions!(zed, [OpenKeymapEditor]);
 30
 31pub fn init(cx: &mut App) {
 32    let keymap_event_channel = KeymapEventChannel::new();
 33    cx.set_global(keymap_event_channel);
 34
 35    cx.on_action(|_: &OpenKeymapEditor, cx| {
 36        workspace::with_active_or_new_workspace(cx, move |workspace, window, cx| {
 37            let existing = workspace
 38                .active_pane()
 39                .read(cx)
 40                .items()
 41                .find_map(|item| item.downcast::<KeymapEditor>());
 42
 43            if let Some(existing) = existing {
 44                workspace.activate_item(&existing, true, true, window, cx);
 45            } else {
 46                let keymap_editor =
 47                    cx.new(|cx| KeymapEditor::new(workspace.weak_handle(), window, cx));
 48                workspace.add_item_to_active_pane(Box::new(keymap_editor), None, true, window, cx);
 49            }
 50        });
 51    });
 52
 53    cx.observe_new(|_workspace: &mut Workspace, window, cx| {
 54        let Some(window) = window else { return };
 55
 56        let keymap_ui_actions = [std::any::TypeId::of::<OpenKeymapEditor>()];
 57
 58        command_palette_hooks::CommandPaletteFilter::update_global(cx, |filter, _cx| {
 59            filter.hide_action_types(&keymap_ui_actions);
 60        });
 61
 62        cx.observe_flag::<SettingsUiFeatureFlag, _>(
 63            window,
 64            move |is_enabled, _workspace, _, cx| {
 65                if is_enabled {
 66                    command_palette_hooks::CommandPaletteFilter::update_global(
 67                        cx,
 68                        |filter, _cx| {
 69                            filter.show_action_types(keymap_ui_actions.iter());
 70                        },
 71                    );
 72                } else {
 73                    command_palette_hooks::CommandPaletteFilter::update_global(
 74                        cx,
 75                        |filter, _cx| {
 76                            filter.hide_action_types(&keymap_ui_actions);
 77                        },
 78                    );
 79                }
 80            },
 81        )
 82        .detach();
 83    })
 84    .detach();
 85
 86    register_serializable_item::<KeymapEditor>(cx);
 87}
 88
 89pub struct KeymapEventChannel {}
 90
 91impl Global for KeymapEventChannel {}
 92
 93impl KeymapEventChannel {
 94    fn new() -> Self {
 95        Self {}
 96    }
 97
 98    pub fn trigger_keymap_changed(cx: &mut App) {
 99        let Some(_event_channel) = cx.try_global::<Self>() else {
100            // don't panic if no global defined. This usually happens in tests
101            return;
102        };
103        cx.update_global(|_event_channel: &mut Self, _| {
104            /* triggers observers in KeymapEditors */
105        });
106    }
107}
108
109struct KeymapEditor {
110    workspace: WeakEntity<Workspace>,
111    focus_handle: FocusHandle,
112    _keymap_subscription: Subscription,
113    keybindings: Vec<ProcessedKeybinding>,
114    // corresponds 1 to 1 with keybindings
115    string_match_candidates: Arc<Vec<StringMatchCandidate>>,
116    matches: Vec<StringMatch>,
117    table_interaction_state: Entity<TableInteractionState>,
118    filter_editor: Entity<Editor>,
119    selected_index: Option<usize>,
120}
121
122impl EventEmitter<()> for KeymapEditor {}
123
124impl Focusable for KeymapEditor {
125    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
126        return self.filter_editor.focus_handle(cx);
127    }
128}
129
130impl KeymapEditor {
131    fn new(workspace: WeakEntity<Workspace>, window: &mut Window, cx: &mut Context<Self>) -> Self {
132        let focus_handle = cx.focus_handle();
133
134        let _keymap_subscription =
135            cx.observe_global::<KeymapEventChannel>(Self::update_keybindings);
136        let table_interaction_state = TableInteractionState::new(window, cx);
137
138        let filter_editor = cx.new(|cx| {
139            let mut editor = Editor::single_line(window, cx);
140            editor.set_placeholder_text("Filter action names...", cx);
141            editor
142        });
143
144        cx.subscribe(&filter_editor, |this, _, e: &EditorEvent, cx| {
145            if !matches!(e, EditorEvent::BufferEdited) {
146                return;
147            }
148
149            this.update_matches(cx);
150        })
151        .detach();
152
153        let mut this = Self {
154            workspace,
155            keybindings: vec![],
156            string_match_candidates: Arc::new(vec![]),
157            matches: vec![],
158            focus_handle: focus_handle.clone(),
159            _keymap_subscription,
160            table_interaction_state,
161            filter_editor,
162            selected_index: None,
163        };
164
165        this.update_keybindings(cx);
166
167        this
168    }
169
170    fn update_matches(&mut self, cx: &mut Context<Self>) {
171        let query = self.filter_editor.read(cx).text(cx);
172        let string_match_candidates = self.string_match_candidates.clone();
173        let executor = cx.background_executor().clone();
174        let keybind_count = self.keybindings.len();
175        let query = command_palette::normalize_action_query(&query);
176        let fuzzy_match = cx.background_spawn(async move {
177            fuzzy::match_strings(
178                &string_match_candidates,
179                &query,
180                true,
181                true,
182                keybind_count,
183                &Default::default(),
184                executor,
185            )
186            .await
187        });
188
189        cx.spawn(async move |this, cx| {
190            let matches = fuzzy_match.await;
191            this.update(cx, |this, cx| {
192                this.selected_index.take();
193                this.scroll_to_item(0, ScrollStrategy::Top, cx);
194                this.matches = matches;
195                cx.notify();
196            })
197        })
198        .detach();
199    }
200
201    fn process_bindings(
202        cx: &mut Context<Self>,
203    ) -> (Vec<ProcessedKeybinding>, Vec<StringMatchCandidate>) {
204        let key_bindings_ptr = cx.key_bindings();
205        let lock = key_bindings_ptr.borrow();
206        let key_bindings = lock.bindings();
207        let mut unmapped_action_names = HashSet::from_iter(cx.all_action_names());
208
209        let mut processed_bindings = Vec::new();
210        let mut string_match_candidates = Vec::new();
211
212        for key_binding in key_bindings {
213            let source = key_binding.meta().map(settings::KeybindSource::from_meta);
214
215            let keystroke_text = ui::text_for_keystrokes(key_binding.keystrokes(), cx);
216            let ui_key_binding = Some(
217                ui::KeyBinding::new(key_binding.clone(), cx)
218                    .vim_mode(source == Some(settings::KeybindSource::Vim)),
219            );
220
221            let context = key_binding
222                .predicate()
223                .map(|predicate| predicate.to_string())
224                .unwrap_or_else(|| "<global>".to_string());
225
226            let source = source.map(|source| (source, source.name().into()));
227
228            let action_name = key_binding.action().name();
229            unmapped_action_names.remove(&action_name);
230
231            let index = processed_bindings.len();
232            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
233            processed_bindings.push(ProcessedKeybinding {
234                keystroke_text: keystroke_text.into(),
235                ui_key_binding,
236                action: action_name.into(),
237                action_input: key_binding.action_input(),
238                context: context.into(),
239                source,
240            });
241            string_match_candidates.push(string_match_candidate);
242        }
243
244        let empty = SharedString::new_static("");
245        for action_name in unmapped_action_names.into_iter() {
246            let index = processed_bindings.len();
247            let string_match_candidate = StringMatchCandidate::new(index, &action_name);
248            processed_bindings.push(ProcessedKeybinding {
249                keystroke_text: empty.clone(),
250                ui_key_binding: None,
251                action: (*action_name).into(),
252                action_input: None,
253                context: empty.clone(),
254                source: None,
255            });
256            string_match_candidates.push(string_match_candidate);
257        }
258
259        (processed_bindings, string_match_candidates)
260    }
261
262    fn update_keybindings(self: &mut KeymapEditor, cx: &mut Context<KeymapEditor>) {
263        let (key_bindings, string_match_candidates) = Self::process_bindings(cx);
264        self.keybindings = key_bindings;
265        self.string_match_candidates = Arc::new(string_match_candidates);
266        self.matches = self
267            .string_match_candidates
268            .iter()
269            .enumerate()
270            .map(|(ix, candidate)| StringMatch {
271                candidate_id: ix,
272                score: 0.0,
273                positions: vec![],
274                string: candidate.string.clone(),
275            })
276            .collect();
277
278        self.update_matches(cx);
279        cx.notify();
280    }
281
282    fn dispatch_context(&self, _window: &Window, _cx: &Context<Self>) -> KeyContext {
283        let mut dispatch_context = KeyContext::new_with_defaults();
284        dispatch_context.add("KeymapEditor");
285        dispatch_context.add("menu");
286
287        dispatch_context
288    }
289
290    fn scroll_to_item(&self, index: usize, strategy: ScrollStrategy, cx: &mut App) {
291        let index = usize::min(index, self.matches.len().saturating_sub(1));
292        self.table_interaction_state.update(cx, |this, _cx| {
293            this.scroll_handle.scroll_to_item(index, strategy);
294        });
295    }
296
297    fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
298        if let Some(selected) = self.selected_index {
299            let selected = selected + 1;
300            if selected >= self.matches.len() {
301                self.select_last(&Default::default(), window, cx);
302            } else {
303                self.selected_index = Some(selected);
304                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
305                cx.notify();
306            }
307        } else {
308            self.select_first(&Default::default(), window, cx);
309        }
310    }
311
312    fn select_previous(
313        &mut self,
314        _: &menu::SelectPrevious,
315        window: &mut Window,
316        cx: &mut Context<Self>,
317    ) {
318        if let Some(selected) = self.selected_index {
319            if selected == 0 {
320                return;
321            }
322
323            let selected = selected - 1;
324
325            if selected >= self.matches.len() {
326                self.select_last(&Default::default(), window, cx);
327            } else {
328                self.selected_index = Some(selected);
329                self.scroll_to_item(selected, ScrollStrategy::Center, cx);
330                cx.notify();
331            }
332        } else {
333            self.select_last(&Default::default(), window, cx);
334        }
335    }
336
337    fn select_first(
338        &mut self,
339        _: &menu::SelectFirst,
340        _window: &mut Window,
341        cx: &mut Context<Self>,
342    ) {
343        if self.matches.get(0).is_some() {
344            self.selected_index = Some(0);
345            self.scroll_to_item(0, ScrollStrategy::Center, cx);
346            cx.notify();
347        }
348    }
349
350    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
351        if self.matches.last().is_some() {
352            let index = self.matches.len() - 1;
353            self.selected_index = Some(index);
354            self.scroll_to_item(index, ScrollStrategy::Center, cx);
355            cx.notify();
356        }
357    }
358
359    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
360        let Some(index) = self.selected_index else {
361            return;
362        };
363        let keybind = self.keybindings[self.matches[index].candidate_id].clone();
364
365        self.edit_keybinding(keybind, window, cx);
366    }
367
368    fn edit_keybinding(
369        &mut self,
370        keybind: ProcessedKeybinding,
371        window: &mut Window,
372        cx: &mut Context<Self>,
373    ) {
374        self.workspace
375            .update(cx, |workspace, cx| {
376                let fs = workspace.app_state().fs.clone();
377                workspace.toggle_modal(window, cx, |window, cx| {
378                    let modal = KeybindingEditorModal::new(keybind, fs, window, cx);
379                    window.focus(&modal.focus_handle(cx));
380                    modal
381                });
382            })
383            .log_err();
384    }
385
386    fn focus_search(
387        &mut self,
388        _: &search::FocusSearch,
389        window: &mut Window,
390        cx: &mut Context<Self>,
391    ) {
392        if !self
393            .filter_editor
394            .focus_handle(cx)
395            .contains_focused(window, cx)
396        {
397            window.focus(&self.filter_editor.focus_handle(cx));
398        } else {
399            self.filter_editor.update(cx, |editor, cx| {
400                editor.select_all(&Default::default(), window, cx);
401            });
402        }
403        self.selected_index.take();
404    }
405}
406
407#[derive(Clone)]
408struct ProcessedKeybinding {
409    keystroke_text: SharedString,
410    ui_key_binding: Option<ui::KeyBinding>,
411    action: SharedString,
412    action_input: Option<SharedString>,
413    context: SharedString,
414    source: Option<(KeybindSource, SharedString)>,
415}
416
417impl Item for KeymapEditor {
418    type Event = ();
419
420    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
421        "Keymap Editor".into()
422    }
423}
424
425impl Render for KeymapEditor {
426    fn render(&mut self, window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
427        let row_count = self.matches.len();
428        let theme = cx.theme();
429
430        div()
431            .key_context(self.dispatch_context(window, cx))
432            .on_action(cx.listener(Self::select_next))
433            .on_action(cx.listener(Self::select_previous))
434            .on_action(cx.listener(Self::select_first))
435            .on_action(cx.listener(Self::select_last))
436            .on_action(cx.listener(Self::focus_search))
437            .on_action(cx.listener(Self::confirm))
438            .size_full()
439            .bg(theme.colors().editor_background)
440            .id("keymap-editor")
441            .track_focus(&self.focus_handle)
442            .px_4()
443            .v_flex()
444            .pb_4()
445            .child(
446                h_flex()
447                    .key_context({
448                        let mut context = KeyContext::new_with_defaults();
449                        context.add("BufferSearchBar");
450                        context
451                    })
452                    .w_full()
453                    .h_12()
454                    .px_4()
455                    .my_4()
456                    .border_2()
457                    .border_color(theme.colors().border)
458                    .child(self.filter_editor.clone()),
459            )
460            .child(
461                Table::new()
462                    .interactable(&self.table_interaction_state)
463                    .striped()
464                    .column_widths([rems(24.), rems(16.), rems(32.), rems(8.)])
465                    .header(["Command", "Keystrokes", "Context", "Source"])
466                    .selected_item_index(self.selected_index)
467                    .on_click_row(cx.processor(|this, row_index, _window, _cx| {
468                        this.selected_index = Some(row_index);
469                    }))
470                    .uniform_list(
471                        "keymap-editor-table",
472                        row_count,
473                        cx.processor(move |this, range: Range<usize>, _window, _cx| {
474                            range
475                                .filter_map(|index| {
476                                    let candidate_id = this.matches.get(index)?.candidate_id;
477                                    let binding = &this.keybindings[candidate_id];
478                                    let action = h_flex()
479                                        .items_start()
480                                        .gap_1()
481                                        .child(binding.action.clone())
482                                        .when_some(
483                                            binding.action_input.clone(),
484                                            |this, binding_input| this.child(binding_input),
485                                        );
486                                    let keystrokes = binding.ui_key_binding.clone().map_or(
487                                        binding.keystroke_text.clone().into_any_element(),
488                                        IntoElement::into_any_element,
489                                    );
490                                    let context = binding.context.clone();
491                                    let source = binding
492                                        .source
493                                        .clone()
494                                        .map(|(_source, name)| name)
495                                        .unwrap_or_default();
496                                    Some([
497                                        action.into_any_element(),
498                                        keystrokes,
499                                        context.into_any_element(),
500                                        source.into_any_element(),
501                                    ])
502                                })
503                                .collect()
504                        }),
505                    ),
506            )
507    }
508}
509
510struct KeybindingEditorModal {
511    editing_keybind: ProcessedKeybinding,
512    keybind_editor: Entity<KeybindInput>,
513    fs: Arc<dyn Fs>,
514    error: Option<String>,
515}
516
517impl ModalView for KeybindingEditorModal {}
518
519impl EventEmitter<DismissEvent> for KeybindingEditorModal {}
520
521impl Focusable for KeybindingEditorModal {
522    fn focus_handle(&self, cx: &App) -> FocusHandle {
523        self.keybind_editor.focus_handle(cx)
524    }
525}
526
527impl KeybindingEditorModal {
528    pub fn new(
529        editing_keybind: ProcessedKeybinding,
530        fs: Arc<dyn Fs>,
531        _window: &mut Window,
532        cx: &mut App,
533    ) -> Self {
534        let keybind_editor = cx.new(KeybindInput::new);
535        Self {
536            editing_keybind,
537            fs,
538            keybind_editor,
539            error: None,
540        }
541    }
542}
543
544impl Render for KeybindingEditorModal {
545    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
546        let theme = cx.theme().colors();
547        return v_flex()
548            .gap_4()
549            .w(rems(36.))
550            .child(
551                v_flex()
552                    .items_center()
553                    .text_center()
554                    .bg(theme.background)
555                    .border_color(theme.border)
556                    .border_2()
557                    .px_4()
558                    .py_2()
559                    .w_full()
560                    .child(
561                        div()
562                            .text_lg()
563                            .font_weight(FontWeight::BOLD)
564                            .child("Input desired keybinding, then hit save"),
565                    )
566                    .child(
567                        h_flex()
568                            .w_full()
569                            .child(self.keybind_editor.clone())
570                            .child(
571                                IconButton::new("backspace-btn", ui::IconName::Backspace).on_click(
572                                    cx.listener(|this, _event, _window, cx| {
573                                        this.keybind_editor.update(cx, |editor, cx| {
574                                            editor.keystrokes.pop();
575                                            cx.notify();
576                                        })
577                                    }),
578                                ),
579                            )
580                            .child(IconButton::new("clear-btn", ui::IconName::Eraser).on_click(
581                                cx.listener(|this, _event, _window, cx| {
582                                    this.keybind_editor.update(cx, |editor, cx| {
583                                        editor.keystrokes.clear();
584                                        cx.notify();
585                                    })
586                                }),
587                            )),
588                    )
589                    .child(
590                        h_flex().w_full().items_center().justify_center().child(
591                            Button::new("save-btn", "Save")
592                                .label_size(LabelSize::Large)
593                                .on_click(cx.listener(|this, _event, _window, cx| {
594                                    let existing_keybind = this.editing_keybind.clone();
595                                    let fs = this.fs.clone();
596                                    let new_keystrokes = this
597                                        .keybind_editor
598                                        .read_with(cx, |editor, _| editor.keystrokes.clone());
599                                    if new_keystrokes.is_empty() {
600                                        this.error = Some("Keystrokes cannot be empty".to_string());
601                                        cx.notify();
602                                        return;
603                                    }
604                                    let tab_size =
605                                        cx.global::<settings::SettingsStore>().json_tab_size();
606                                    cx.spawn(async move |this, cx| {
607                                        if let Err(err) = save_keybinding_update(
608                                            existing_keybind,
609                                            &new_keystrokes,
610                                            &fs,
611                                            tab_size,
612                                        )
613                                        .await
614                                        {
615                                            this.update(cx, |this, cx| {
616                                                this.error = Some(err);
617                                                cx.notify();
618                                            })
619                                            .log_err();
620                                        }
621                                    })
622                                    .detach();
623                                })),
624                        ),
625                    ),
626            )
627            .when_some(self.error.clone(), |this, error| {
628                this.child(
629                    div()
630                        .bg(theme.background)
631                        .border_color(theme.border)
632                        .border_2()
633                        .rounded_md()
634                        .child(error),
635                )
636            });
637    }
638}
639
640async fn save_keybinding_update(
641    existing: ProcessedKeybinding,
642    new_keystrokes: &[Keystroke],
643    fs: &Arc<dyn Fs>,
644    tab_size: usize,
645) -> Result<(), String> {
646    let keymap_contents = settings::KeymapFile::load_keymap_file(fs)
647        .await
648        .map_err(|err| format!("Failed to load keymap file: {}", err))?;
649    let existing_keystrokes = existing
650        .ui_key_binding
651        .as_ref()
652        .map(|keybinding| keybinding.key_binding.keystrokes())
653        .unwrap_or_default();
654    let operation = if existing.ui_key_binding.is_some() {
655        settings::KeybindUpdateOperation::Replace {
656            target: settings::KeybindUpdateTarget {
657                context: Some(existing.context.as_ref()).filter(|context| !context.is_empty()),
658                keystrokes: existing_keystrokes,
659                action_name: &existing.action,
660                use_key_equivalents: false,
661                input: existing.action_input.as_ref().map(|input| input.as_ref()),
662            },
663            target_source: existing
664                .source
665                .map(|(source, _name)| source)
666                .unwrap_or(KeybindSource::User),
667            source: settings::KeybindUpdateTarget {
668                context: Some(existing.context.as_ref()).filter(|context| !context.is_empty()),
669                keystrokes: new_keystrokes,
670                action_name: &existing.action,
671                use_key_equivalents: false,
672                input: existing.action_input.as_ref().map(|input| input.as_ref()),
673            },
674        }
675    } else {
676        return Err(
677            "Not Implemented: Creating new bindings from unbound actions is not supported yet."
678                .to_string(),
679        );
680    };
681    let updated_keymap_contents =
682        settings::KeymapFile::update_keybinding(operation, keymap_contents, tab_size)
683            .map_err(|err| format!("Failed to update keybinding: {}", err))?;
684    fs.atomic_write(paths::keymap_file().clone(), updated_keymap_contents)
685        .await
686        .map_err(|err| format!("Failed to write keymap file: {}", err))?;
687    Ok(())
688}
689
690struct KeybindInput {
691    keystrokes: Vec<Keystroke>,
692    focus_handle: FocusHandle,
693}
694
695impl KeybindInput {
696    fn new(cx: &mut Context<Self>) -> Self {
697        let focus_handle = cx.focus_handle();
698        Self {
699            keystrokes: Vec::new(),
700            focus_handle,
701        }
702    }
703
704    fn on_modifiers_changed(
705        &mut self,
706        event: &ModifiersChangedEvent,
707        _window: &mut Window,
708        cx: &mut Context<Self>,
709    ) {
710        if let Some(last) = self.keystrokes.last_mut()
711            && last.key.is_empty()
712        {
713            if !event.modifiers.modified() {
714                self.keystrokes.pop();
715            } else {
716                last.modifiers = event.modifiers;
717            }
718        } else {
719            self.keystrokes.push(Keystroke {
720                modifiers: event.modifiers,
721                key: "".to_string(),
722                key_char: None,
723            });
724        }
725        cx.stop_propagation();
726        cx.notify();
727    }
728
729    fn on_key_down(
730        &mut self,
731        event: &gpui::KeyDownEvent,
732        _window: &mut Window,
733        cx: &mut Context<Self>,
734    ) {
735        if event.is_held {
736            return;
737        }
738        if let Some(last) = self.keystrokes.last_mut()
739            && last.key.is_empty()
740        {
741            *last = event.keystroke.clone();
742        } else {
743            self.keystrokes.push(event.keystroke.clone());
744        }
745        cx.stop_propagation();
746        cx.notify();
747    }
748
749    fn on_key_up(
750        &mut self,
751        event: &gpui::KeyUpEvent,
752        _window: &mut Window,
753        cx: &mut Context<Self>,
754    ) {
755        if let Some(last) = self.keystrokes.last_mut()
756            && !last.key.is_empty()
757            && last.modifiers == event.keystroke.modifiers
758        {
759            self.keystrokes.push(Keystroke {
760                modifiers: event.keystroke.modifiers,
761                key: "".to_string(),
762                key_char: None,
763            });
764        }
765        cx.stop_propagation();
766        cx.notify();
767    }
768}
769
770impl Focusable for KeybindInput {
771    fn focus_handle(&self, _cx: &App) -> FocusHandle {
772        self.focus_handle.clone()
773    }
774}
775
776impl Render for KeybindInput {
777    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
778        let colors = cx.theme().colors();
779        return div()
780            .track_focus(&self.focus_handle)
781            .on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
782            .on_key_down(cx.listener(Self::on_key_down))
783            .on_key_up(cx.listener(Self::on_key_up))
784            .focus(|mut style| {
785                style.border_color = Some(colors.border_focused);
786                style
787            })
788            .h_12()
789            .w_full()
790            .bg(colors.editor_background)
791            .border_2()
792            .border_color(colors.border)
793            .p_4()
794            .flex_row()
795            .text_center()
796            .justify_center()
797            .child(ui::text_for_keystrokes(&self.keystrokes, cx));
798    }
799}
800
801impl SerializableItem for KeymapEditor {
802    fn serialized_item_kind() -> &'static str {
803        "KeymapEditor"
804    }
805
806    fn cleanup(
807        workspace_id: workspace::WorkspaceId,
808        alive_items: Vec<workspace::ItemId>,
809        _window: &mut Window,
810        cx: &mut App,
811    ) -> gpui::Task<gpui::Result<()>> {
812        workspace::delete_unloaded_items(
813            alive_items,
814            workspace_id,
815            "keybinding_editors",
816            &KEYBINDING_EDITORS,
817            cx,
818        )
819    }
820
821    fn deserialize(
822        _project: Entity<project::Project>,
823        workspace: WeakEntity<Workspace>,
824        workspace_id: workspace::WorkspaceId,
825        item_id: workspace::ItemId,
826        window: &mut Window,
827        cx: &mut App,
828    ) -> gpui::Task<gpui::Result<Entity<Self>>> {
829        window.spawn(cx, async move |cx| {
830            if KEYBINDING_EDITORS
831                .get_keybinding_editor(item_id, workspace_id)?
832                .is_some()
833            {
834                cx.update(|window, cx| cx.new(|cx| KeymapEditor::new(workspace, window, cx)))
835            } else {
836                Err(anyhow!("No keybinding editor to deserialize"))
837            }
838        })
839    }
840
841    fn serialize(
842        &mut self,
843        workspace: &mut Workspace,
844        item_id: workspace::ItemId,
845        _closing: bool,
846        _window: &mut Window,
847        cx: &mut ui::Context<Self>,
848    ) -> Option<gpui::Task<gpui::Result<()>>> {
849        let workspace_id = workspace.database_id()?;
850        Some(cx.background_spawn(async move {
851            KEYBINDING_EDITORS
852                .save_keybinding_editor(item_id, workspace_id)
853                .await
854        }))
855    }
856
857    fn should_serialize(&self, _event: &Self::Event) -> bool {
858        false
859    }
860}
861
862mod persistence {
863    use db::{define_connection, query, sqlez_macros::sql};
864    use workspace::WorkspaceDb;
865
866    define_connection! {
867        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
868            &[sql!(
869                CREATE TABLE keybinding_editors (
870                    workspace_id INTEGER,
871                    item_id INTEGER UNIQUE,
872
873                    PRIMARY KEY(workspace_id, item_id),
874                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
875                    ON DELETE CASCADE
876                ) STRICT;
877            )];
878    }
879
880    impl KeybindingEditorDb {
881        query! {
882            pub async fn save_keybinding_editor(
883                item_id: workspace::ItemId,
884                workspace_id: workspace::WorkspaceId
885            ) -> Result<()> {
886                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
887                VALUES (?, ?)
888            }
889        }
890
891        query! {
892            pub fn get_keybinding_editor(
893                item_id: workspace::ItemId,
894                workspace_id: workspace::WorkspaceId
895            ) -> Result<Option<workspace::ItemId>> {
896                SELECT item_id
897                FROM keybinding_editors
898                WHERE item_id = ? AND workspace_id = ?
899            }
900        }
901    }
902}