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 default_index: Option<usize>,
120}
121
122impl KeyBindingDisplay {
123 #[must_use]
125 pub fn table_name(&self) -> &str {
126 &self.table_name
127 }
128
129 #[must_use]
131 pub const fn binding(&self) -> &KeyBinding {
132 &self.binding
133 }
134
135 #[must_use]
137 pub fn key_string(&self) -> &str {
138 &self.key_string
139 }
140
141 #[must_use]
143 pub fn command_string(&self) -> &str {
144 &self.command_string
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct KeyBindingTableRef {
151 name: String,
152 created: bool,
153}
154
155impl KeyBindingTableRef {
156 #[must_use]
158 pub fn name(&self) -> &str {
159 &self.name
160 }
161
162 #[must_use]
164 pub const fn created(&self) -> bool {
165 self.created
166 }
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct KeyBindingStore {
172 tables: BTreeMap<String, KeyBindingTable>,
173}
174
175impl Default for KeyBindingStore {
176 fn default() -> Self {
177 Self::with_defaults().expect("embedded default bindings must parse")
178 }
179}
180
181impl KeyBindingStore {
182 #[must_use]
184 pub fn new() -> Self {
185 Self {
186 tables: BTreeMap::new(),
187 }
188 }
189
190 pub fn with_defaults() -> Result<Self, CommandParseError> {
192 let mut store = Self::new();
193 for default in defaults::DEFAULT_BINDING_STRINGS {
194 let parsed = parse_command_string(default)?;
195 for command in parsed.commands() {
196 store.apply_parsed_default(command)?;
197 }
198 }
199 store.snapshot_defaults();
200 Ok(store)
201 }
202
203 #[must_use]
205 pub fn table(&self, name: &str) -> Option<&KeyBindingTable> {
206 self.tables.get(name)
207 }
208
209 pub fn tables(&self) -> impl Iterator<Item = &KeyBindingTable> {
211 self.tables.values()
212 }
213
214 pub fn get_table(&mut self, name: &str, create: bool) -> Option<KeyBindingTableRef> {
216 if let Some(table) = self.tables.get_mut(name) {
217 table.references = table.references.saturating_add(1);
218 return Some(KeyBindingTableRef {
219 name: name.to_owned(),
220 created: false,
221 });
222 }
223 if !create {
224 return None;
225 }
226
227 self.tables.insert(
228 name.to_owned(),
229 KeyBindingTable {
230 name: name.to_owned(),
231 references: 1,
232 active: BTreeMap::new(),
233 defaults: BTreeMap::new(),
234 },
235 );
236 Some(KeyBindingTableRef {
237 name: name.to_owned(),
238 created: true,
239 })
240 }
241
242 pub fn unref_table(&mut self, name: &str) {
244 let should_remove = if let Some(table) = self.tables.get_mut(name) {
245 table.references = table.references.saturating_sub(1);
246 table.references == 0 && table.active.is_empty() && table.defaults.is_empty()
247 } else {
248 false
249 };
250 if should_remove {
251 self.tables.remove(name);
252 }
253 }
254
255 pub fn add_binding(
257 &mut self,
258 table_name: &str,
259 key: KeyCode,
260 note: Option<String>,
261 repeat: bool,
262 commands: Option<ParsedCommands>,
263 ) -> bool {
264 let table = self.ensure_table_mut(table_name);
265 let key = strip_flags(key);
266 if commands.is_none() {
267 if let Some(binding) = table.active.get_mut(&key) {
268 if let Some(note) = note {
269 binding.note = Some(note);
270 }
271 if repeat {
272 binding.repeat = true;
273 }
274 return true;
275 }
276 return false;
277 }
278
279 table.active.insert(
280 key,
281 KeyBinding {
282 key,
283 note,
284 repeat,
285 commands: commands.expect("checked commands presence"),
286 },
287 );
288 true
289 }
290
291 pub fn remove_binding(&mut self, table_name: &str, key: KeyCode) -> bool {
293 let key = strip_flags(key);
294 let Some(table) = self.tables.get_mut(table_name) else {
295 return false;
296 };
297 let removed = table.active.remove(&key).is_some();
298 self.remove_table_if_empty(table_name);
299 removed
300 }
301
302 pub fn reset_binding(&mut self, table_name: &str, key: KeyCode) {
304 let key = strip_flags(key);
305 let Some(table) = self.tables.get_mut(table_name) else {
306 return;
307 };
308 if let Some(default) = table.defaults.get(&key).cloned() {
309 table.active.insert(key, default);
310 } else {
311 table.active.remove(&key);
312 }
313 self.remove_table_if_empty(table_name);
314 }
315
316 pub fn remove_table(&mut self, table_name: &str) -> bool {
318 let Some(table) = self.tables.get_mut(table_name) else {
319 return false;
320 };
321 let removed = !table.active.is_empty();
322 table.active.clear();
323 self.remove_table_if_empty(table_name);
324 removed
325 }
326
327 pub fn reset_table(&mut self, table_name: &str) {
329 let Some(table) = self.tables.get_mut(table_name) else {
330 return;
331 };
332 if table.defaults.is_empty() {
333 self.remove_table_if_empty(table_name);
334 return;
335 }
336 table.active = table.defaults.clone();
337 }
338
339 #[must_use]
341 pub fn get_binding(&self, table_name: &str, key: KeyCode) -> Option<&KeyBinding> {
342 self.tables
343 .get(table_name)
344 .and_then(|table| table.active.get(&strip_flags(key)))
345 }
346
347 #[must_use]
349 pub fn get_default_binding(&self, table_name: &str, key: KeyCode) -> Option<&KeyBinding> {
350 self.tables
351 .get(table_name)
352 .and_then(|table| table.defaults.get(&strip_flags(key)))
353 }
354
355 #[must_use]
357 pub fn list_bindings(
358 &self,
359 table_name: Option<&str>,
360 sort_order: KeyBindingSortOrder,
361 reversed: bool,
362 ) -> Vec<KeyBindingDisplay> {
363 let mut bindings = if let Some(table_name) = table_name {
364 self.tables
365 .get(table_name)
366 .into_iter()
367 .flat_map(|table| {
368 table
369 .active
370 .values()
371 .cloned()
372 .map(|binding| display_binding(table, binding))
373 .collect::<Vec<_>>()
374 })
375 .collect::<Vec<_>>()
376 } else {
377 self.tables
378 .values()
379 .flat_map(|table| {
380 table
381 .active
382 .values()
383 .cloned()
384 .map(|binding| display_binding(table, binding))
385 .collect::<Vec<_>>()
386 })
387 .collect::<Vec<_>>()
388 };
389
390 bindings.sort_by(|left, right| {
391 let ordering = match sort_order {
392 KeyBindingSortOrder::Key => match (left.default_index, right.default_index) {
393 (Some(left), Some(right)) => left.cmp(&right),
394 _ => left.binding.key.cmp(&right.binding.key),
395 },
396 KeyBindingSortOrder::Modifier => (left.binding.key & KEYC_MASK_MODIFIERS)
397 .cmp(&(right.binding.key & KEYC_MASK_MODIFIERS)),
398 KeyBindingSortOrder::Name => left
399 .table_name
400 .to_ascii_lowercase()
401 .cmp(&right.table_name.to_ascii_lowercase()),
402 };
403 let ordering = if ordering.is_eq() {
404 left.table_name
405 .to_ascii_lowercase()
406 .cmp(&right.table_name.to_ascii_lowercase())
407 .then_with(|| left.binding.key.cmp(&right.binding.key))
408 } else {
409 ordering
410 };
411 if reversed {
412 ordering.reverse()
413 } else {
414 ordering
415 }
416 });
417 bindings
418 }
419
420 #[must_use]
422 pub fn has_repeat(bindings: &[KeyBindingDisplay]) -> bool {
423 bindings.iter().any(|binding| binding.binding.repeat)
424 }
425
426 #[must_use]
428 pub fn key_string_width(bindings: &[KeyBindingDisplay]) -> usize {
429 bindings
430 .iter()
431 .map(|binding| UnicodeWidthStr::width(binding.key_string.as_str()))
432 .max()
433 .unwrap_or(0)
434 }
435
436 #[must_use]
438 pub fn key_table_width(bindings: &[KeyBindingDisplay]) -> usize {
439 bindings
440 .iter()
441 .map(|binding| UnicodeWidthStr::width(binding.table_name.as_str()))
442 .max()
443 .unwrap_or(0)
444 }
445
446 fn apply_parsed_default(&mut self, command: &ParsedCommand) -> Result<(), CommandParseError> {
447 let (table, key, note, repeat, commands) = parse_bind_command(command)?;
448 let _ = self.add_binding(&table, key, note, repeat, Some(commands));
449 Ok(())
450 }
451
452 fn snapshot_defaults(&mut self) {
453 for table in self.tables.values_mut() {
454 if table.defaults.is_empty() {
455 table.defaults = table.active.clone();
456 }
457 }
458 }
459
460 fn ensure_table_mut(&mut self, name: &str) -> &mut KeyBindingTable {
461 self.tables
462 .entry(name.to_owned())
463 .or_insert_with(|| KeyBindingTable {
464 name: name.to_owned(),
465 references: 0,
466 active: BTreeMap::new(),
467 defaults: BTreeMap::new(),
468 })
469 }
470
471 fn remove_table_if_empty(&mut self, table_name: &str) {
472 let should_remove = self.tables.get(table_name).is_some_and(|table| {
473 table.references == 0 && table.active.is_empty() && table.defaults.is_empty()
474 });
475 if should_remove {
476 self.tables.remove(table_name);
477 }
478 }
479}
480
481fn display_binding(table: &KeyBindingTable, binding: KeyBinding) -> KeyBindingDisplay {
482 let default_display = table
483 .defaults
484 .get(&binding.key)
485 .filter(|default| *default == &binding)
486 .and_then(|_| defaults::list_keys_display(&table.name, binding.key));
487 let key_string = default_display.map_or_else(
488 || key_string_lookup_key(binding.key, false),
489 |display| display.key_string.to_owned(),
490 );
491 let command_string = default_display.map_or_else(
492 || binding.commands.to_tmux_string(),
493 |display| display.command_string.to_owned(),
494 );
495 KeyBindingDisplay {
496 table_name: table.name.clone(),
497 binding,
498 key_string,
499 command_string,
500 default_index: default_display.map(|display| display.index),
501 }
502}
503
504fn parse_bind_command(
505 command: &ParsedCommand,
506) -> Result<(String, KeyCode, Option<String>, bool, ParsedCommands), CommandParseError> {
507 let mut index = 0;
508 let mut table_name: Option<String> = None;
509 let mut note = None;
510 let mut repeat = false;
511 let arguments = command.arguments();
512
513 while let Some(argument) = arguments.get(index) {
514 let Some(value) = argument.as_string() else {
515 break;
516 };
517 match value {
518 "-T" => {
519 index += 1;
520 table_name = Some(
521 arguments
522 .get(index)
523 .and_then(|argument| argument.as_string())
524 .ok_or_else(|| CommandParseError::new(1, "bind-key missing -T key-table"))?
525 .to_owned(),
526 );
527 }
528 _ if value.starts_with("-T") && value.len() > 2 => {
529 table_name = Some(value[2..].to_owned());
530 }
531 "-n" => table_name = Some("root".to_owned()),
532 "-N" => {
533 index += 1;
534 note = Some(
535 arguments
536 .get(index)
537 .and_then(|argument| argument.as_string())
538 .ok_or_else(|| CommandParseError::new(1, "bind-key missing -N note"))?
539 .to_owned(),
540 );
541 }
542 _ if value.starts_with("-N") && value.len() > 2 => {
543 note = Some(value[2..].to_owned());
544 }
545 "-r" => repeat = true,
546 _ if value.starts_with('-') && value.len() > 1 => {
547 return Err(CommandParseError::new(
548 1,
549 format!("unsupported default bind-key flag: {value}"),
550 ));
551 }
552 _ => break,
553 }
554 index += 1;
555 }
556
557 let key_string = arguments
558 .get(index)
559 .and_then(|argument| argument.as_string())
560 .ok_or_else(|| CommandParseError::new(1, "bind-key missing key"))?;
561 index += 1;
562 let key = key_string_lookup_string(key_string)
563 .ok_or_else(|| CommandParseError::new(1, format!("unknown key: {key_string}")))?;
564
565 let commands = match arguments.get(index) {
566 Some(CommandArgument::Commands(commands)) => commands.clone(),
567 Some(CommandArgument::String(command)) => parse_command_string(command)?,
568 None => {
569 return Err(CommandParseError::new(
570 1,
571 "default bind-key must include a command list",
572 ))
573 }
574 };
575
576 Ok((
577 table_name.unwrap_or_else(|| "prefix".to_owned()),
578 key,
579 note,
580 repeat,
581 commands,
582 ))
583}