Skip to content

Commit 08a6b5e

Browse files
committed
Add super/cmd key support
1 parent 3758fa9 commit 08a6b5e

File tree

3 files changed

+85
-3
lines changed

3 files changed

+85
-3
lines changed

crates/atuin/src/command/client/search/interactive.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ impl State {
334334
ctrl: false,
335335
alt: false,
336336
shift: false,
337+
super_key: false,
337338
};
338339
let seq = KeyInput::Sequence(vec![pending_single, single.clone()]);
339340
let action = keymap

crates/atuin/src/command/client/search/keybindings/key.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
55

66
/// A single key press with modifiers (e.g. `ctrl-c`, `alt-f`, `enter`).
77
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8+
#[allow(clippy::struct_excessive_bools)]
89
pub struct SingleKey {
910
pub code: KeyCodeValue,
1011
pub ctrl: bool,
1112
pub alt: bool,
1213
pub shift: bool,
14+
pub super_key: bool,
1315
}
1416

1517
/// The key code portion of a key press.
@@ -45,19 +47,21 @@ impl SingleKey {
4547
let ctrl = event.modifiers.contains(KeyModifiers::CONTROL);
4648
let alt = event.modifiers.contains(KeyModifiers::ALT);
4749
let shift = event.modifiers.contains(KeyModifiers::SHIFT);
50+
let super_key = event.modifiers.contains(KeyModifiers::SUPER);
4851

4952
let code = match event.code {
5053
KeyCode::Char(' ') => KeyCodeValue::Space,
5154
KeyCode::Char(c) => {
5255
// If shift is the only modifier and it's an uppercase letter,
5356
// we store the uppercase char directly and clear the shift flag
5457
// since the case already encodes it.
55-
if shift && !ctrl && !alt && c.is_ascii_uppercase() {
58+
if shift && !ctrl && !alt && !super_key && c.is_ascii_uppercase() {
5659
return SingleKey {
5760
code: KeyCodeValue::Char(c),
5861
ctrl: false,
5962
alt: false,
6063
shift: false,
64+
super_key: false,
6165
};
6266
}
6367
KeyCodeValue::Char(c)
@@ -89,6 +93,7 @@ impl SingleKey {
8993
} else {
9094
shift
9195
},
96+
super_key,
9297
}
9398
}
9499

@@ -100,13 +105,15 @@ impl SingleKey {
100105
let mut ctrl = false;
101106
let mut alt = false;
102107
let mut shift = false;
108+
let mut super_key = false;
103109

104110
// All parts except the last are modifiers
105111
for &part in &parts[..parts.len() - 1] {
106112
match part.to_lowercase().as_str() {
107113
"ctrl" => ctrl = true,
108114
"alt" => alt = true,
109115
"shift" => shift = true,
116+
"super" | "cmd" | "win" => super_key = true,
110117
_ => return Err(format!("unknown modifier: {part}")),
111118
}
112119
}
@@ -136,12 +143,13 @@ impl SingleKey {
136143
if chars.len() == 1 {
137144
let c = chars[0];
138145
// An uppercase letter implies shift (unless shift already specified)
139-
if c.is_ascii_uppercase() && !ctrl && !alt {
146+
if c.is_ascii_uppercase() && !ctrl && !alt && !super_key {
140147
return Ok(SingleKey {
141148
code: KeyCodeValue::Char(c),
142149
ctrl: false,
143150
alt: false,
144151
shift: false,
152+
super_key: false,
145153
});
146154
}
147155
KeyCodeValue::Char(c)
@@ -156,12 +164,16 @@ impl SingleKey {
156164
ctrl,
157165
alt,
158166
shift,
167+
super_key,
159168
})
160169
}
161170
}
162171

163172
impl fmt::Display for SingleKey {
164173
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174+
if self.super_key {
175+
write!(f, "super-")?;
176+
}
165177
if self.ctrl {
166178
write!(f, "ctrl-")?;
167179
}
@@ -365,6 +377,72 @@ mod tests {
365377
assert_eq!(from_event, parsed);
366378
}
367379

380+
#[test]
381+
fn parse_super_modifier() {
382+
let k = SingleKey::parse("super-a").unwrap();
383+
assert_eq!(k.code, KeyCodeValue::Char('a'));
384+
assert!(k.super_key);
385+
assert!(!k.ctrl && !k.alt && !k.shift);
386+
387+
// "cmd" is an alias for "super"
388+
let k2 = SingleKey::parse("cmd-a").unwrap();
389+
assert_eq!(k, k2);
390+
391+
// "win" is an alias for "super"
392+
let k3 = SingleKey::parse("win-a").unwrap();
393+
assert_eq!(k, k3);
394+
}
395+
396+
#[test]
397+
fn parse_super_with_other_modifiers() {
398+
let k = SingleKey::parse("super-ctrl-c").unwrap();
399+
assert_eq!(k.code, KeyCodeValue::Char('c'));
400+
assert!(k.super_key && k.ctrl);
401+
assert!(!k.alt && !k.shift);
402+
}
403+
404+
#[test]
405+
fn display_super_modifier() {
406+
let k = SingleKey::parse("super-a").unwrap();
407+
assert_eq!(k.to_string(), "super-a");
408+
409+
let k = SingleKey::parse("super-ctrl-x").unwrap();
410+
assert_eq!(k.to_string(), "super-ctrl-x");
411+
}
412+
413+
#[test]
414+
fn display_round_trip_super() {
415+
let k = KeyInput::parse("super-a").unwrap();
416+
let display = k.to_string();
417+
let k2 = KeyInput::parse(&display).unwrap();
418+
assert_eq!(k, k2, "round-trip failed for super-a");
419+
}
420+
421+
#[test]
422+
fn from_event_super() {
423+
let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::SUPER);
424+
let k = SingleKey::from_event(&event);
425+
assert_eq!(k.code, KeyCodeValue::Char('a'));
426+
assert!(k.super_key);
427+
assert!(!k.ctrl && !k.alt && !k.shift);
428+
}
429+
430+
#[test]
431+
fn from_event_super_matches_parsed() {
432+
let event = KeyEvent::new(KeyCode::Char('a'), KeyModifiers::SUPER);
433+
let from_event = SingleKey::from_event(&event);
434+
let parsed = SingleKey::parse("super-a").unwrap();
435+
assert_eq!(from_event, parsed);
436+
}
437+
438+
#[test]
439+
fn super_uppercase_preserves_super() {
440+
// super-G should keep the super flag (unlike bare "G" which clears shift)
441+
let k = SingleKey::parse("super-G").unwrap();
442+
assert_eq!(k.code, KeyCodeValue::Char('G'));
443+
assert!(k.super_key);
444+
}
445+
368446
#[test]
369447
fn parse_errors() {
370448
assert!(SingleKey::parse("ctrl-alt-shift-xxx").is_err());

docs/docs/configuration/advanced-key-binding.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,10 @@ Modifiers are prefixed with a dash separator. Multiple modifiers can be combined
5454
"ctrl-c", "alt-f", "ctrl-alt-x"
5555
```
5656

57-
Available modifiers: `ctrl`, `alt`, `shift`.
57+
Available modifiers: `ctrl`, `alt`, `shift`, `super` (also accepted as `cmd` or `win`).
58+
59+
!!! warning
60+
The `super` modifier (Cmd on macOS, Win on Windows) **requires** the kitty keyboard protocol. Only terminals that implement this protocol will report the Super modifier to applications. Even in supported terminals, some Super+key combinations may be intercepted by the terminal or OS (e.g. Cmd+C for copy, Cmd+V for paste, or Cmd+T for opening a new tab).
5861

5962
### Uppercase letters
6063

0 commit comments

Comments
 (0)