keybindings.rs

  1use std::fmt::Write as _;
  2
  3use db::anyhow::anyhow;
  4use gpui::{AppContext as _, EventEmitter, FocusHandle, Focusable, actions};
  5use ui::{
  6    ActiveTheme as _, App, ParentElement as _, Render, Styled as _, Table, Window, div, string_cell,
  7};
  8use workspace::{Item, SerializableItem, Workspace, register_serializable_item};
  9
 10use crate::keybindings::persistence::KEYBINDING_EDITORS;
 11
 12actions!(zed, [OpenKeymapEditor]);
 13
 14pub fn init(cx: &mut App) {
 15    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
 16        workspace.register_action(|workspace, _: &OpenKeymapEditor, window, cx| {
 17            let open_keymap_editor = cx.new(|cx| KeymapEditor::new(cx));
 18            workspace.add_item_to_center(Box::new(open_keymap_editor), window, cx);
 19        });
 20    })
 21    .detach();
 22
 23    register_serializable_item::<KeymapEditor>(cx);
 24}
 25
 26struct KeymapEditor {
 27    focus_handle: FocusHandle,
 28}
 29
 30impl EventEmitter<()> for KeymapEditor {}
 31
 32impl Focusable for KeymapEditor {
 33    fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
 34        self.focus_handle.clone()
 35    }
 36}
 37
 38impl KeymapEditor {
 39    fn new(cx: &mut gpui::Context<Self>) -> Self {
 40        Self {
 41            focus_handle: cx.focus_handle(),
 42        }
 43    }
 44}
 45
 46impl SerializableItem for KeymapEditor {
 47    fn serialized_item_kind() -> &'static str {
 48        "KeymapEditor"
 49    }
 50
 51    fn cleanup(
 52        workspace_id: workspace::WorkspaceId,
 53        alive_items: Vec<workspace::ItemId>,
 54        _window: &mut Window,
 55        cx: &mut App,
 56    ) -> gpui::Task<gpui::Result<()>> {
 57        workspace::delete_unloaded_items(
 58            alive_items,
 59            workspace_id,
 60            "keybinding_editors",
 61            &KEYBINDING_EDITORS,
 62            cx,
 63        )
 64    }
 65
 66    fn deserialize(
 67        _project: gpui::Entity<project::Project>,
 68        _workspace: gpui::WeakEntity<Workspace>,
 69        workspace_id: workspace::WorkspaceId,
 70        item_id: workspace::ItemId,
 71        _window: &mut Window,
 72        cx: &mut App,
 73    ) -> gpui::Task<gpui::Result<gpui::Entity<Self>>> {
 74        cx.spawn(async move |cx| {
 75            if KEYBINDING_EDITORS
 76                .get_keybinding_editor(item_id, workspace_id)?
 77                .is_some()
 78            {
 79                cx.new(|cx| KeymapEditor::new(cx))
 80            } else {
 81                Err(anyhow!("No keybinding editor to deserialize"))
 82            }
 83        })
 84    }
 85
 86    fn serialize(
 87        &mut self,
 88        workspace: &mut Workspace,
 89        item_id: workspace::ItemId,
 90        _closing: bool,
 91        _window: &mut Window,
 92        cx: &mut ui::Context<Self>,
 93    ) -> Option<gpui::Task<gpui::Result<()>>> {
 94        let Some(workspace_id) = workspace.database_id() else {
 95            return None;
 96        };
 97        Some(cx.background_spawn(async move {
 98            KEYBINDING_EDITORS
 99                .save_keybinding_editor(item_id, workspace_id)
100                .await
101        }))
102    }
103
104    fn should_serialize(&self, _event: &Self::Event) -> bool {
105        false
106    }
107}
108
109impl Item for KeymapEditor {
110    type Event = ();
111
112    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
113        "Keymap Editor".into()
114    }
115}
116
117impl Render for KeymapEditor {
118    fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
119        let key_bindings_ptr = cx.key_bindings();
120        let lock = key_bindings_ptr.borrow();
121        let key_bindings = lock.bindings();
122
123        let mut table = Table::new(vec!["Command", "Keystrokes", "Context"]);
124        for key_binding in key_bindings {
125            let mut keystroke_text = String::new();
126            for keystroke in key_binding.keystrokes() {
127                write!(&mut keystroke_text, "{} ", keystroke.unparse()).ok();
128            }
129            let keystroke_text = keystroke_text.trim().to_string();
130
131            let context = key_binding
132                .predicate()
133                .map(|predicate| predicate.to_string())
134                .unwrap_or_else(|| "<global>".to_string());
135
136            // dbg!(key_binding.action().name(), &keystroke_text, &context);
137
138            table = table.row(vec![
139                string_cell(key_binding.action().name()),
140                string_cell(keystroke_text),
141                string_cell(context),
142                // TODO: Add a source field
143                // string_cell(keybinding.source().to_string()),
144            ]);
145        }
146
147        let theme = cx.theme();
148
149        div()
150            .size_full()
151            .bg(theme.colors().background)
152            .child(table.striped())
153    }
154}
155
156mod persistence {
157    use db::{define_connection, query, sqlez_macros::sql};
158    use workspace::WorkspaceDb;
159
160    define_connection! {
161        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
162            &[sql!(
163                CREATE TABLE keybinding_editors (
164                    workspace_id INTEGER,
165                    item_id INTEGER UNIQUE,
166
167                    PRIMARY KEY(workspace_id, item_id),
168                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
169                    ON DELETE CASCADE
170                ) STRICT;
171            )];
172    }
173
174    impl KeybindingEditorDb {
175        query! {
176            pub async fn save_keybinding_editor(
177                item_id: workspace::ItemId,
178                workspace_id: workspace::WorkspaceId
179            ) -> Result<()> {
180                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
181                VALUES (?, ?)
182            }
183        }
184
185        query! {
186            pub fn get_keybinding_editor(
187                item_id: workspace::ItemId,
188                workspace_id: workspace::WorkspaceId
189            ) -> Result<Option<workspace::ItemId>> {
190                SELECT item_id
191                FROM keybinding_editors
192                WHERE item_id = ? AND workspace_id = ?
193            }
194        }
195    }
196}