keybindings.rs

  1use std::{fmt::Write as _, ops::Range};
  2
  3use db::anyhow::anyhow;
  4use gpui::{
  5    AppContext as _, Context, Entity, EventEmitter, FocusHandle, Focusable, Global, Subscription,
  6    actions, div,
  7};
  8
  9use ui::{
 10    ActiveTheme as _, App, BorrowAppContext, ParentElement as _, Render, SharedString, Styled as _,
 11    Window, prelude::*,
 12};
 13use workspace::{Item, SerializableItem, Workspace, register_serializable_item};
 14
 15use crate::{
 16    keybindings::persistence::KEYBINDING_EDITORS,
 17    ui_components::table::{Table, TableInteractionState},
 18};
 19
 20actions!(zed, [OpenKeymapEditor]);
 21
 22pub fn init(cx: &mut App) {
 23    let keymap_event_channel = KeymapEventChannel::new();
 24    cx.set_global(keymap_event_channel);
 25
 26    cx.observe_new(|workspace: &mut Workspace, _window, _cx| {
 27        workspace.register_action(|workspace, _: &OpenKeymapEditor, window, cx| {
 28            let open_keymap_editor = KeymapEditor::new(window, cx);
 29            workspace.add_item_to_center(Box::new(open_keymap_editor), window, cx);
 30        });
 31    })
 32    .detach();
 33
 34    register_serializable_item::<KeymapEditor>(cx);
 35}
 36
 37pub struct KeymapEventChannel {}
 38
 39impl Global for KeymapEventChannel {}
 40
 41impl KeymapEventChannel {
 42    fn new() -> Self {
 43        Self {}
 44    }
 45
 46    pub fn trigger_keymap_changed(cx: &mut App) {
 47        cx.update_global(|_event_channel: &mut Self, _| {
 48            /* triggers observers in KeymapEditors */
 49        });
 50    }
 51}
 52
 53struct KeymapEditor {
 54    focus_handle: FocusHandle,
 55    _keymap_subscription: Subscription,
 56    processed_bindings: Vec<ProcessedKeybinding>,
 57    table_interaction_state: Entity<TableInteractionState>,
 58}
 59
 60impl EventEmitter<()> for KeymapEditor {}
 61
 62impl Focusable for KeymapEditor {
 63    fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
 64        self.focus_handle.clone()
 65    }
 66}
 67
 68impl KeymapEditor {
 69    fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
 70        let this = cx.new(|cx| {
 71            let focus_handle = cx.focus_handle();
 72
 73            let _keymap_subscription = cx.observe_global::<KeymapEventChannel>(|this, cx| {
 74                let key_bindings = Self::process_bindings(cx);
 75                this.processed_bindings = key_bindings;
 76            });
 77
 78            let table_interaction_state = TableInteractionState::new(window, cx);
 79
 80            Self {
 81                focus_handle: focus_handle.clone(),
 82                _keymap_subscription,
 83                processed_bindings: vec![],
 84                table_interaction_state,
 85            }
 86        });
 87        this
 88    }
 89
 90    fn process_bindings(cx: &mut Context<Self>) -> Vec<ProcessedKeybinding> {
 91        let key_bindings_ptr = cx.key_bindings();
 92        let lock = key_bindings_ptr.borrow();
 93        let key_bindings = lock.bindings();
 94
 95        let mut processed_bindings = Vec::new();
 96
 97        for key_binding in key_bindings {
 98            let mut keystroke_text = String::new();
 99            for keystroke in key_binding.keystrokes() {
100                write!(&mut keystroke_text, "{} ", keystroke.unparse()).ok();
101            }
102            let keystroke_text = keystroke_text.trim().to_string();
103
104            let context = key_binding
105                .predicate()
106                .map(|predicate| predicate.to_string())
107                .unwrap_or_else(|| "<global>".to_string());
108
109            let source = key_binding
110                .meta()
111                .map(|meta| settings::KeybindSource::from_meta(meta).name().into());
112
113            processed_bindings.push(ProcessedKeybinding {
114                keystroke_text: keystroke_text.into(),
115                action: key_binding.action().name().into(),
116                context: context.into(),
117                source,
118            })
119        }
120        processed_bindings
121    }
122}
123
124struct ProcessedKeybinding {
125    keystroke_text: SharedString,
126    action: SharedString,
127    context: SharedString,
128    source: Option<SharedString>,
129}
130
131impl Item for KeymapEditor {
132    type Event = ();
133
134    fn tab_content_text(&self, _detail: usize, _cx: &App) -> ui::SharedString {
135        "Keymap Editor".into()
136    }
137}
138
139impl Render for KeymapEditor {
140    fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
141        if self.processed_bindings.is_empty() {
142            self.processed_bindings = Self::process_bindings(cx);
143        }
144
145        let row_count = self.processed_bindings.len();
146
147        let theme = cx.theme();
148
149        div()
150            .size_full()
151            .bg(theme.colors().background)
152            .id("keymap-editor")
153            .track_focus(&self.focus_handle)
154            .child(
155                Table::new()
156                    .interactable(&self.table_interaction_state)
157                    .header(["Command", "Keystrokes", "Context", "Source"])
158                    .column_widths([rems(16.), rems(24.), rems(32.), rems(8.)])
159                    .uniform_list(
160                        "keymap-editor-table",
161                        row_count,
162                        cx.processor(move |this, range: Range<usize>, _window, _cx| {
163                            range
164                                .map(|index| {
165                                    let binding = &this.processed_bindings[index];
166                                    [
167                                        binding.action.clone(),
168                                        binding.keystroke_text.clone(),
169                                        binding.context.clone(),
170                                        binding.source.clone().unwrap_or_default(),
171                                    ]
172                                    .map(IntoElement::into_any_element)
173                                })
174                                .collect()
175                        }),
176                    ),
177            )
178    }
179}
180
181impl SerializableItem for KeymapEditor {
182    fn serialized_item_kind() -> &'static str {
183        "KeymapEditor"
184    }
185
186    fn cleanup(
187        workspace_id: workspace::WorkspaceId,
188        alive_items: Vec<workspace::ItemId>,
189        _window: &mut Window,
190        cx: &mut App,
191    ) -> gpui::Task<gpui::Result<()>> {
192        workspace::delete_unloaded_items(
193            alive_items,
194            workspace_id,
195            "keybinding_editors",
196            &KEYBINDING_EDITORS,
197            cx,
198        )
199    }
200
201    fn deserialize(
202        _project: gpui::Entity<project::Project>,
203        _workspace: gpui::WeakEntity<Workspace>,
204        workspace_id: workspace::WorkspaceId,
205        item_id: workspace::ItemId,
206        window: &mut Window,
207        cx: &mut App,
208    ) -> gpui::Task<gpui::Result<gpui::Entity<Self>>> {
209        window.spawn(cx, async move |cx| {
210            if KEYBINDING_EDITORS
211                .get_keybinding_editor(item_id, workspace_id)?
212                .is_some()
213            {
214                cx.update(KeymapEditor::new)
215            } else {
216                Err(anyhow!("No keybinding editor to deserialize"))
217            }
218        })
219    }
220
221    fn serialize(
222        &mut self,
223        workspace: &mut Workspace,
224        item_id: workspace::ItemId,
225        _closing: bool,
226        _window: &mut Window,
227        cx: &mut ui::Context<Self>,
228    ) -> Option<gpui::Task<gpui::Result<()>>> {
229        let workspace_id = workspace.database_id()?;
230        Some(cx.background_spawn(async move {
231            KEYBINDING_EDITORS
232                .save_keybinding_editor(item_id, workspace_id)
233                .await
234        }))
235    }
236
237    fn should_serialize(&self, _event: &Self::Event) -> bool {
238        false
239    }
240}
241
242mod persistence {
243    use db::{define_connection, query, sqlez_macros::sql};
244    use workspace::WorkspaceDb;
245
246    define_connection! {
247        pub static ref KEYBINDING_EDITORS: KeybindingEditorDb<WorkspaceDb> =
248            &[sql!(
249                CREATE TABLE keybinding_editors (
250                    workspace_id INTEGER,
251                    item_id INTEGER UNIQUE,
252
253                    PRIMARY KEY(workspace_id, item_id),
254                    FOREIGN KEY(workspace_id) REFERENCES workspaces(workspace_id)
255                    ON DELETE CASCADE
256                ) STRICT;
257            )];
258    }
259
260    impl KeybindingEditorDb {
261        query! {
262            pub async fn save_keybinding_editor(
263                item_id: workspace::ItemId,
264                workspace_id: workspace::WorkspaceId
265            ) -> Result<()> {
266                INSERT OR REPLACE INTO keybinding_editors(item_id, workspace_id)
267                VALUES (?, ?)
268            }
269        }
270
271        query! {
272            pub fn get_keybinding_editor(
273                item_id: workspace::ItemId,
274                workspace_id: workspace::WorkspaceId
275            ) -> Result<Option<workspace::ItemId>> {
276                SELECT item_id
277                FROM keybinding_editors
278                WHERE item_id = ? AND workspace_id = ?
279            }
280        }
281    }
282}