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}