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}