1use std::collections::BTreeMap;
2
3use unicode_width::UnicodeWidthStr;
4
5use crate::command_parser::{
6 parse_command_string, CommandArgument, CommandParseError, ParsedCommand, ParsedCommands,
7};
8
9use super::{
10 defaults, key_string_lookup_key, key_string_lookup_string, strip_flags, KeyCode,
11 KEYC_MASK_MODIFIERS,
12};
13
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
16pub enum KeyBindingSortOrder {
17 #[default]
19 Key,
20 Modifier,
22 Name,
24}
25
26impl KeyBindingSortOrder {
27 #[must_use]
29 pub fn parse(value: &str) -> Option<Self> {
30 if value.eq_ignore_ascii_case("key") || value.eq_ignore_ascii_case("index") {
31 Some(Self::Key)
32 } else if value.eq_ignore_ascii_case("modifier") {
33 Some(Self::Modifier)
34 } else if value.eq_ignore_ascii_case("name") || value.eq_ignore_ascii_case("title") {
35 Some(Self::Name)
36 } else {
37 None
38 }
39 }
40}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44pub struct KeyBinding {
45 key: KeyCode,
46 note: Option<String>,
47 repeat: bool,
48 commands: ParsedCommands,
49}
50
51impl KeyBinding {
52 #[must_use]
54 pub const fn key(&self) -> KeyCode {
55 self.key
56 }
57
58 #[must_use]
60 pub fn note(&self) -> Option<&str> {
61 self.note.as_deref()
62 }
63
64 #[must_use]
66 pub const fn repeat(&self) -> bool {
67 self.repeat
68 }
69
70 #[must_use]
72 pub const fn commands(&self) -> &ParsedCommands {
73 &self.commands
74 }
75}
76
77#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct KeyBindingTable {
80 name: String,
81 references: usize,
82 active: BTreeMap<KeyCode, KeyBinding>,
83 defaults: BTreeMap<KeyCode, KeyBinding>,
84}
85
86impl KeyBindingTable {
87 #[must_use]
89 pub fn name(&self) -> &str {
90 &self.name
91 }
92
93 #[must_use]
95 pub const fn references(&self) -> usize {
96 self.references
97 }
98
99 #[must_use]
101 pub const fn active(&self) -> &BTreeMap<KeyCode, KeyBinding> {
102 &self.active
103 }
104
105 #[must_use]
107 pub const fn defaults(&self) -> &BTreeMap<KeyCode, KeyBinding> {
108 &self.defaults
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct KeyBindingDisplay {
115 table_name: String,
116 binding: KeyBinding,
117 key_string: String,
118 command_string: String,
119}
120
121impl KeyBindingDisplay {
122 #[must_use]
124 pub fn table_name(&self) -> &str {
125 &self.table_name
126 }
127
128 #[must_use]
130 pub const fn binding(&self) -> &KeyBinding {
131 &self.binding
132 }
133
134 #[must_use]
136 pub fn key_string(&self) -> &str {
137 &self.key_string
138 }
139
140 #[must_use]
142 pub fn command_string(&self) -> &str {
143 &self.command_string
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct KeyBindingTableRef {
150 name: String,
151 created: bool,
152}
153
154impl KeyBindingTableRef {
155 #[must_use]
157 pub fn name(&self) -> &str {
158 &self.name
159 }
160
161 #[must_use]
163 pub const fn created(&self) -> bool {
164 self.created
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct KeyBindingStore {
171 tables: BTreeMap<String, KeyBindingTable>,
172}
173
174impl Default for KeyBindingStore {
175 fn default() -> Self {
176 Self::with_defaults().expect("embedded default bindings must parse")
177 }
178}
179
180impl KeyBindingStore {
181 #[must_use]
183 pub fn new() -> Self {
184 Self {
185 tables: BTreeMap::new(),
186 }
187 }
188
189 pub fn with_defaults() -> Result<Self, CommandParseError> {
191 let mut store = Self::new();
192 for default in defaults::DEFAULT_BINDING_STRINGS {
193 let parsed = parse_command_string(default)?;
194 for command in parsed.commands() {
195 store.apply_parsed_default(command)?;
196 }
197 }
198 store.snapshot_defaults();
199 Ok(store)
200 }
201
202 #[must_use]
204 pub fn table(&self, name: &str) -> Option<&KeyBindingTable> {
205 self.tables.get(name)
206 }
207
208 pub fn tables(&self) -> impl Iterator<Item = &KeyBindingTable> {
210 self.tables.values()
211 }
212
213 pub fn get_table(&mut self, name: &str, create: bool) -> Option<KeyBindingTableRef> {
215 if let Some(table) = self.tables.get_mut(name) {
216 table.references = table.references.saturating_add(1);
217 return Some(KeyBindingTableRef {
218 name: name.to_owned(),
219 created: false,
220 });
221 }
222 if !create {
223 return None;
224 }
225
226 self.tables.insert(
227 name.to_owned(),
228 KeyBindingTable {
229 name: name.to_owned(),
230 references: 1,
231 active: BTreeMap::new(),
232 defaults: BTreeMap::new(),
233 },
234 );
235 Some(KeyBindingTableRef {
236 name: name.to_owned(),
237 created: true,
238 })
239 }
240
241 pub fn unref_table(&mut self, name: &str) {
243 let should_remove = if let Some(table) = self.tables.get_mut(name) {
244 table.references = table.references.saturating_sub(1);
245 table.references == 0 && table.active.is_empty() && table.defaults.is_empty()
246 } else {
247 false
248 };
249 if should_remove {
250 self.tables.remove(name);
251 }
252 }
253
254 pub fn add_binding(
256 &mut self,
257 table_name: &str,
258 key: KeyCode,
259 note: Option<String>,
260 repeat: bool,
261 commands: Option<ParsedCommands>,
262 ) -> bool {
263 let table = self.ensure_table_mut(table_name);
264 let key = strip_flags(key);
265 if commands.is_none() {
266 if let Some(binding) = table.active.get_mut(&key) {
267 if let Some(note) = note {
268 binding.note = Some(note);
269 }
270 if repeat {
271 binding.repeat = true;
272 }
273 return true;
274 }
275 return false;
276 }
277
278 table.active.insert(
279 key,
280 KeyBinding {
281 key,
282 note,
283 repeat,
284 commands: commands.expect("checked commands presence"),
285 },
286 );
287 true
288 }
289
290 pub fn remove_binding(&mut self, table_name: &str, key: KeyCode) -> bool {
292 let key = strip_flags(key);
293 let Some(table) = self.tables.get_mut(table_name) else {
294 return false;
295 };
296 let removed = table.active.remove(&key).is_some();
297 self.remove_table_if_empty(table_name);
298 removed
299 }
300
301 pub fn reset_binding(&mut self, table_name: &str, key: KeyCode) {
303 let key = strip_flags(key);
304 let Some(table) = self.tables.get_mut(table_name) else {
305 return;
306 };
307 if let Some(default) = table.defaults.get(&key).cloned() {
308 table.active.insert(key, default);
309 } else {
310 table.active.remove(&key);
311 }
312 self.remove_table_if_empty(table_name);
313 }
314
315 pub fn remove_table(&mut self, table_name: &str) -> bool {
317 let Some(table) = self.tables.get_mut(table_name) else {
318 return false;
319 };
320 let removed = !table.active.is_empty();
321 table.active.clear();
322 self.remove_table_if_empty(table_name);
323 removed
324 }
325
326 pub fn reset_table(&mut self, table_name: &str) {
328 let Some(table) = self.tables.get_mut(table_name) else {
329 return;
330 };
331 if table.defaults.is_empty() {
332 self.remove_table_if_empty(table_name);
333 return;
334 }
335 table.active = table.defaults.clone();
336 }
337
338 #[must_use]
340 pub fn get_binding(&self, table_name: &str, key: KeyCode) -> Option<&KeyBinding> {
341 self.tables
342 .get(table_name)
343 .and_then(|table| table.active.get(&strip_flags(key)))
344 }
345
346 #[must_use]
348 pub fn get_default_binding(&self, table_name: &str, key: KeyCode) -> Option<&KeyBinding> {
349 self.tables
350 .get(table_name)
351 .and_then(|table| table.defaults.get(&strip_flags(key)))
352 }
353
354 #[must_use]
356 pub fn list_bindings(
357 &self,
358 table_name: Option<&str>,
359 sort_order: KeyBindingSortOrder,
360 reversed: bool,
361 ) -> Vec<KeyBindingDisplay> {
362 let mut bindings = if let Some(table_name) = table_name {
363 self.tables
364 .get(table_name)
365 .into_iter()
366 .flat_map(|table| {
367 table
368 .active
369 .values()
370 .cloned()
371 .map(|binding| KeyBindingDisplay {
372 table_name: table.name.clone(),
373 key_string: key_string_lookup_key(binding.key, false),
374 command_string: binding.commands.to_tmux_string(),
375 binding,
376 })
377 .collect::<Vec<_>>()
378 })
379 .collect::<Vec<_>>()
380 } else {
381 self.tables
382 .values()
383 .flat_map(|table| {
384 table
385 .active
386 .values()
387 .cloned()
388 .map(|binding| KeyBindingDisplay {
389 table_name: table.name.clone(),
390 key_string: key_string_lookup_key(binding.key, false),
391 command_string: binding.commands.to_tmux_string(),
392 binding,
393 })
394 .collect::<Vec<_>>()
395 })
396 .collect::<Vec<_>>()
397 };
398
399 bindings.sort_by(|left, right| {
400 let ordering = match sort_order {
401 KeyBindingSortOrder::Key => left.binding.key.cmp(&right.binding.key),
402 KeyBindingSortOrder::Modifier => (left.binding.key & KEYC_MASK_MODIFIERS)
403 .cmp(&(right.binding.key & KEYC_MASK_MODIFIERS)),
404 KeyBindingSortOrder::Name => left
405 .table_name
406 .to_ascii_lowercase()
407 .cmp(&right.table_name.to_ascii_lowercase()),
408 };
409 let ordering = if ordering.is_eq() {
410 left.table_name
411 .to_ascii_lowercase()
412 .cmp(&right.table_name.to_ascii_lowercase())
413 .then_with(|| left.binding.key.cmp(&right.binding.key))
414 } else {
415 ordering
416 };
417 if reversed {
418 ordering.reverse()
419 } else {
420 ordering
421 }
422 });
423 bindings
424 }
425
426 #[must_use]
428 pub fn has_repeat(bindings: &[KeyBindingDisplay]) -> bool {
429 bindings.iter().any(|binding| binding.binding.repeat)
430 }
431
432 #[must_use]
434 pub fn key_string_width(bindings: &[KeyBindingDisplay]) -> usize {
435 bindings
436 .iter()
437 .map(|binding| UnicodeWidthStr::width(binding.key_string.as_str()))
438 .max()
439 .unwrap_or(0)
440 }
441
442 #[must_use]
444 pub fn key_table_width(bindings: &[KeyBindingDisplay]) -> usize {
445 bindings
446 .iter()
447 .map(|binding| UnicodeWidthStr::width(binding.table_name.as_str()))
448 .max()
449 .unwrap_or(0)
450 }
451
452 fn apply_parsed_default(&mut self, command: &ParsedCommand) -> Result<(), CommandParseError> {
453 let (table, key, note, repeat, commands) = parse_bind_command(command)?;
454 let _ = self.add_binding(&table, key, note, repeat, Some(commands));
455 Ok(())
456 }
457
458 fn snapshot_defaults(&mut self) {
459 for table in self.tables.values_mut() {
460 if table.defaults.is_empty() {
461 table.defaults = table.active.clone();
462 }
463 }
464 }
465
466 fn ensure_table_mut(&mut self, name: &str) -> &mut KeyBindingTable {
467 self.tables
468 .entry(name.to_owned())
469 .or_insert_with(|| KeyBindingTable {
470 name: name.to_owned(),
471 references: 0,
472 active: BTreeMap::new(),
473 defaults: BTreeMap::new(),
474 })
475 }
476
477 fn remove_table_if_empty(&mut self, table_name: &str) {
478 let should_remove = self.tables.get(table_name).is_some_and(|table| {
479 table.references == 0 && table.active.is_empty() && table.defaults.is_empty()
480 });
481 if should_remove {
482 self.tables.remove(table_name);
483 }
484 }
485}
486
487fn parse_bind_command(
488 command: &ParsedCommand,
489) -> Result<(String, KeyCode, Option<String>, bool, ParsedCommands), CommandParseError> {
490 let mut index = 0;
491 let mut table_name: Option<String> = None;
492 let mut note = None;
493 let mut repeat = false;
494 let arguments = command.arguments();
495
496 while let Some(argument) = arguments.get(index) {
497 let Some(value) = argument.as_string() else {
498 break;
499 };
500 match value {
501 "-T" => {
502 index += 1;
503 table_name = Some(
504 arguments
505 .get(index)
506 .and_then(|argument| argument.as_string())
507 .ok_or_else(|| CommandParseError::new(1, "bind-key missing -T key-table"))?
508 .to_owned(),
509 );
510 }
511 _ if value.starts_with("-T") && value.len() > 2 => {
512 table_name = Some(value[2..].to_owned());
513 }
514 "-n" => table_name = Some("root".to_owned()),
515 "-N" => {
516 index += 1;
517 note = Some(
518 arguments
519 .get(index)
520 .and_then(|argument| argument.as_string())
521 .ok_or_else(|| CommandParseError::new(1, "bind-key missing -N note"))?
522 .to_owned(),
523 );
524 }
525 _ if value.starts_with("-N") && value.len() > 2 => {
526 note = Some(value[2..].to_owned());
527 }
528 "-r" => repeat = true,
529 _ if value.starts_with('-') && value.len() > 1 => {
530 return Err(CommandParseError::new(
531 1,
532 format!("unsupported default bind-key flag: {value}"),
533 ));
534 }
535 _ => break,
536 }
537 index += 1;
538 }
539
540 let key_string = arguments
541 .get(index)
542 .and_then(|argument| argument.as_string())
543 .ok_or_else(|| CommandParseError::new(1, "bind-key missing key"))?;
544 index += 1;
545 let key = key_string_lookup_string(key_string)
546 .ok_or_else(|| CommandParseError::new(1, format!("unknown key: {key_string}")))?;
547
548 let commands = match arguments.get(index) {
549 Some(CommandArgument::Commands(commands)) => commands.clone(),
550 Some(CommandArgument::String(command)) => parse_command_string(command)?,
551 None => {
552 return Err(CommandParseError::new(
553 1,
554 "default bind-key must include a command list",
555 ))
556 }
557 };
558
559 Ok((
560 table_name.unwrap_or_else(|| "prefix".to_owned()),
561 key,
562 note,
563 repeat,
564 commands,
565 ))
566}