@@ -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) ]
89pub 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
163172impl 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( ) ) ;
0 commit comments