par_term_config/config/keybindings_methods.rs
1//! Keybinding management methods for `Config`.
2//!
3//! Covers:
4//! - Merging default keybindings and status-bar widgets into user config
5//! - Generating / synchronising snippet and action keybindings
6
7use super::config_struct::Config;
8
9impl Config {
10 /// Merge default keybindings into the user's config.
11 /// Only adds keybindings for actions that don't already exist in the user's config.
12 /// This ensures new features with default keybindings are available to existing users.
13 pub(crate) fn merge_default_keybindings(&mut self) {
14 let default_keybindings = crate::defaults::keybindings();
15
16 // Get the set of actions already configured by the user (owned strings to avoid borrow issues)
17 let existing_actions: std::collections::HashSet<String> = self
18 .keybindings
19 .iter()
20 .map(|kb| kb.action.clone())
21 .collect();
22
23 // Add any default keybindings whose actions are not already configured
24 let mut added_count = 0;
25 for default_kb in default_keybindings {
26 if !existing_actions.contains(&default_kb.action) {
27 log::info!(
28 "Adding new default keybinding: {} -> {}",
29 default_kb.key,
30 default_kb.action
31 );
32 self.keybindings.push(default_kb);
33 added_count += 1;
34 }
35 }
36
37 if added_count > 0 {
38 log::info!(
39 "Merged {} new default keybinding(s) into user config",
40 added_count
41 );
42 }
43 }
44
45 /// Merge default status bar widgets into the user's config.
46 /// Only adds widgets whose `WidgetId` doesn't already exist in the user's widget list.
47 /// This ensures new built-in widgets are available to existing users.
48 pub(crate) fn merge_default_widgets(&mut self) {
49 let default_widgets = crate::status_bar::default_widgets();
50
51 let existing_ids: std::collections::HashSet<crate::status_bar::WidgetId> = self
52 .status_bar
53 .status_bar_widgets
54 .iter()
55 .map(|w| w.id.clone())
56 .collect();
57
58 let mut added_count = 0;
59 for default_widget in default_widgets {
60 if !existing_ids.contains(&default_widget.id) {
61 log::info!(
62 "Adding new default status bar widget: {:?}",
63 default_widget.id
64 );
65 self.status_bar.status_bar_widgets.push(default_widget);
66 added_count += 1;
67 }
68 }
69
70 if added_count > 0 {
71 log::info!(
72 "Merged {} new default status bar widget(s) into user config",
73 added_count
74 );
75 }
76 }
77
78 /// Generate keybindings for snippets and actions that have keybindings configured.
79 ///
80 /// This method adds or updates keybindings for snippets and actions in the keybindings list,
81 /// using the format `snippet:<id>` for snippets and `action:<id>` for actions.
82 /// If a keybinding for a snippet/action already exists, it will be updated with the new key.
83 pub fn generate_snippet_action_keybindings(&mut self) {
84 use crate::config::KeyBinding;
85
86 // Track actions we've seen to remove stale keybindings later
87 let mut seen_actions = std::collections::HashSet::new();
88 let mut added_count = 0;
89 let mut updated_count = 0;
90
91 // Generate keybindings for snippets
92 for snippet in &self.snippets {
93 if let Some(key) = &snippet.keybinding {
94 let action = format!("snippet:{}", snippet.id);
95 seen_actions.insert(action.clone());
96
97 if !key.is_empty() && snippet.enabled && snippet.keybinding_enabled {
98 // Check if this action already has a keybinding
99 if let Some(existing) =
100 self.keybindings.iter_mut().find(|kb| kb.action == action)
101 {
102 // Update existing keybinding if the key changed
103 if existing.key != *key {
104 log::info!(
105 "Updating keybinding for snippet '{}': {} -> {} (was: {})",
106 snippet.title,
107 key,
108 action,
109 existing.key
110 );
111 existing.key = key.clone();
112 updated_count += 1;
113 }
114 } else {
115 // Add new keybinding
116 log::info!(
117 "Adding keybinding for snippet '{}': {} -> {} (enabled={}, keybinding_enabled={})",
118 snippet.title,
119 key,
120 action,
121 snippet.enabled,
122 snippet.keybinding_enabled
123 );
124 self.keybindings.push(KeyBinding {
125 key: key.clone(),
126 action,
127 });
128 added_count += 1;
129 }
130 } else if !key.is_empty() {
131 log::info!(
132 "Skipping keybinding for snippet '{}': {} (enabled={}, keybinding_enabled={})",
133 snippet.title,
134 key,
135 snippet.enabled,
136 snippet.keybinding_enabled
137 );
138 }
139 }
140 }
141
142 // Generate keybindings for actions
143 for action_config in &self.actions {
144 if let Some(key) = action_config.keybinding() {
145 let action = format!("action:{}", action_config.id());
146 let prefix_only = !self.custom_action_prefix_key.trim().is_empty()
147 && action_config.prefix_char().is_none()
148 && action_config.single_char_keybinding_follow_up().is_some();
149
150 if prefix_only {
151 log::info!(
152 "Skipping global keybinding for action '{}': {} is a prefix follow-up",
153 action_config.title(),
154 key
155 );
156 continue;
157 }
158 seen_actions.insert(action.clone());
159
160 if !key.is_empty() && action_config.keybinding_enabled() {
161 // Check if this action already has a keybinding
162 if let Some(existing) =
163 self.keybindings.iter_mut().find(|kb| kb.action == action)
164 {
165 // Update existing keybinding if the key changed
166 if existing.key != key {
167 log::info!(
168 "Updating keybinding for action '{}': {} -> {} (was: {})",
169 action_config.title(),
170 key,
171 action,
172 existing.key
173 );
174 existing.key = key.to_string();
175 updated_count += 1;
176 }
177 } else {
178 // Add new keybinding
179 log::info!(
180 "Adding keybinding for action '{}': {} -> {} (keybinding_enabled={})",
181 action_config.title(),
182 key,
183 action,
184 action_config.keybinding_enabled()
185 );
186 self.keybindings.push(KeyBinding {
187 key: key.to_string(),
188 action,
189 });
190 added_count += 1;
191 }
192 } else if !key.is_empty() {
193 log::info!(
194 "Skipping keybinding for action '{}': {} (keybinding_enabled={})",
195 action_config.title(),
196 key,
197 action_config.keybinding_enabled()
198 );
199 }
200 }
201 }
202
203 // Remove stale keybindings for snippets that no longer have keybindings or are disabled
204 let original_len = self.keybindings.len();
205 self.keybindings.retain(|kb| {
206 // Keep if it's not a snippet/action keybinding
207 if !kb.action.starts_with("snippet:") && !kb.action.starts_with("action:") {
208 return true;
209 }
210 // Keep if we saw it during our scan
211 seen_actions.contains(&kb.action)
212 });
213 let removed_count = original_len - self.keybindings.len();
214
215 if added_count > 0 || updated_count > 0 || removed_count > 0 {
216 log::info!(
217 "Snippet/Action keybindings: {} added, {} updated, {} removed",
218 added_count,
219 updated_count,
220 removed_count
221 );
222 }
223 }
224}