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