use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::{ error::Error, fmt::{Display, Write}, }; /// A keystroke and associated metadata generated by the platform #[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)] pub struct Keystroke { /// the state of the modifier keys at the time the keystroke was generated pub modifiers: Modifiers, /// key is the character printed on the key that was pressed /// e.g. for option-s, key is "s" /// On layouts that do not have ascii keys (e.g. Thai) /// this will be the ASCII-equivalent character (q instead of ๆ), /// and the typed character will be present in key_char. pub key: String, /// key_char is the character that could have been typed when /// this binding was pressed. /// e.g. for s this is "s", for option-s "ß", and cmd-s None pub key_char: Option, } /// Error type for `Keystroke::parse`. This is used instead of `anyhow::Error` so that Zed can use /// markdown to display it. #[derive(Debug)] pub struct InvalidKeystrokeError { /// The invalid keystroke. pub keystroke: String, } impl Error for InvalidKeystrokeError {} impl Display for InvalidKeystrokeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "Invalid keystroke \"{}\". {}", self.keystroke, KEYSTROKE_PARSE_EXPECTED_MESSAGE ) } } /// Sentence explaining what keystroke parser expects, starting with "Expected ..." pub const KEYSTROKE_PARSE_EXPECTED_MESSAGE: &str = "Expected a sequence of modifiers \ (`ctrl`, `alt`, `shift`, `fn`, `cmd`, `super`, or `win`) \ followed by a key, separated by `-`."; impl Keystroke { /// When matching a key we cannot know whether the user intended to type /// the key_char or the key itself. On some non-US keyboards keys we use in our /// bindings are behind option (for example `$` is typed `alt-ç` on a Czech keyboard), /// and on some keyboards the IME handler converts a sequence of keys into a /// specific character (for example `"` is typed as `" space` on a brazilian keyboard). /// /// This method assumes that `self` was typed and `target' is in the keymap, and checks /// both possibilities for self against the target. pub fn should_match(&self, target: &Keystroke) -> bool { #[cfg(not(target_os = "windows"))] if let Some(key_char) = self .key_char .as_ref() .filter(|key_char| key_char != &&self.key) { let ime_modifiers = Modifiers { control: self.modifiers.control, platform: self.modifiers.platform, ..Default::default() }; if &target.key == key_char && target.modifiers == ime_modifiers { return true; } } #[cfg(target_os = "windows")] if let Some(key_char) = self .key_char .as_ref() .filter(|key_char| key_char != &&self.key) { // On Windows, if key_char is set, then the typed keystroke produced the key_char if &target.key == key_char && target.modifiers == Modifiers::none() { return true; } } target.modifiers == self.modifiers && target.key == self.key } /// key syntax is: /// [secondary-][ctrl-][alt-][shift-][cmd-][fn-]key[->key_char] /// key_char syntax is only used for generating test events, /// secondary means "cmd" on macOS and "ctrl" on other platforms /// when matching a key with an key_char set will be matched without it. pub fn parse(source: &str) -> std::result::Result { let mut modifiers = Modifiers::none(); let mut key = None; let mut key_char = None; let mut components = source.split('-').peekable(); while let Some(component) = components.next() { if component.eq_ignore_ascii_case("ctrl") { modifiers.control = true; continue; } if component.eq_ignore_ascii_case("alt") { modifiers.alt = true; continue; } if component.eq_ignore_ascii_case("shift") { modifiers.shift = true; continue; } if component.eq_ignore_ascii_case("fn") { modifiers.function = true; continue; } if component.eq_ignore_ascii_case("secondary") { if cfg!(target_os = "macos") { modifiers.platform = true; } else { modifiers.control = true; }; continue; } let is_platform = component.eq_ignore_ascii_case("cmd") || component.eq_ignore_ascii_case("super") || component.eq_ignore_ascii_case("win"); if is_platform { modifiers.platform = true; continue; } let mut key_str = component.to_string(); if let Some(next) = components.peek() { if next.is_empty() && source.ends_with('-') { key = Some(String::from("-")); break; } else if next.len() > 1 && next.starts_with('>') { key = Some(key_str); key_char = Some(String::from(&next[1..])); components.next(); } else { return Err(InvalidKeystrokeError { keystroke: source.to_owned(), }); } continue; } if component.len() == 1 && component.as_bytes()[0].is_ascii_uppercase() { // Convert to shift + lowercase char modifiers.shift = true; key_str.make_ascii_lowercase(); } else { // convert ascii chars to lowercase so that named keys like "tab" and "enter" // are accepted case insensitively and stored how we expect so they are matched properly key_str.make_ascii_lowercase() } key = Some(key_str); } // Allow for the user to specify a keystroke modifier as the key itself // This sets the `key` to the modifier, and disables the modifier key = key.or_else(|| { use std::mem; // std::mem::take clears bool incase its true if mem::take(&mut modifiers.shift) { Some("shift".to_string()) } else if mem::take(&mut modifiers.control) { Some("control".to_string()) } else if mem::take(&mut modifiers.alt) { Some("alt".to_string()) } else if mem::take(&mut modifiers.platform) { Some("platform".to_string()) } else if mem::take(&mut modifiers.function) { Some("function".to_string()) } else { None } }); let key = key.ok_or_else(|| InvalidKeystrokeError { keystroke: source.to_owned(), })?; Ok(Keystroke { modifiers, key, key_char, }) } /// Produces a representation of this key that Parse can understand. pub fn unparse(&self) -> String { let mut str = String::new(); if self.modifiers.function { str.push_str("fn-"); } if self.modifiers.control { str.push_str("ctrl-"); } if self.modifiers.alt { str.push_str("alt-"); } if self.modifiers.platform { #[cfg(target_os = "macos")] str.push_str("cmd-"); #[cfg(any(target_os = "linux", target_os = "freebsd"))] str.push_str("super-"); #[cfg(target_os = "windows")] str.push_str("win-"); } if self.modifiers.shift { str.push_str("shift-"); } str.push_str(&self.key); str } /// Returns true if this keystroke left /// the ime system in an incomplete state. pub fn is_ime_in_progress(&self) -> bool { self.key_char.is_none() && (is_printable_key(&self.key) || self.key.is_empty()) && !(self.modifiers.platform || self.modifiers.control || self.modifiers.function || self.modifiers.alt) } /// Returns a new keystroke with the key_char filled. /// This is used for dispatch_keystroke where we want users to /// be able to simulate typing "space", etc. pub fn with_simulated_ime(mut self) -> Self { if self.key_char.is_none() && !self.modifiers.platform && !self.modifiers.control && !self.modifiers.function && !self.modifiers.alt { self.key_char = match self.key.as_str() { "space" => Some(" ".into()), "tab" => Some("\t".into()), "enter" => Some("\n".into()), key if !is_printable_key(key) || key.is_empty() => None, key => { if self.modifiers.shift { Some(key.to_uppercase()) } else { Some(key.into()) } } } } self } } fn is_printable_key(key: &str) -> bool { !matches!( key, "f1" | "f2" | "f3" | "f4" | "f5" | "f6" | "f7" | "f8" | "f9" | "f10" | "f11" | "f12" | "f13" | "f14" | "f15" | "f16" | "f17" | "f18" | "f19" | "f20" | "f21" | "f22" | "f23" | "f24" | "f25" | "f26" | "f27" | "f28" | "f29" | "f30" | "f31" | "f32" | "f33" | "f34" | "f35" | "backspace" | "delete" | "left" | "right" | "up" | "down" | "pageup" | "pagedown" | "insert" | "home" | "end" | "back" | "forward" | "escape" ) } impl std::fmt::Display for Keystroke { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.modifiers.control { #[cfg(target_os = "macos")] f.write_char('^')?; #[cfg(not(target_os = "macos"))] write!(f, "ctrl-")?; } if self.modifiers.alt { #[cfg(target_os = "macos")] f.write_char('⌥')?; #[cfg(not(target_os = "macos"))] write!(f, "alt-")?; } if self.modifiers.platform { #[cfg(target_os = "macos")] f.write_char('⌘')?; #[cfg(any(target_os = "linux", target_os = "freebsd"))] f.write_char('❖')?; #[cfg(target_os = "windows")] f.write_char('⊞')?; } if self.modifiers.shift { #[cfg(target_os = "macos")] f.write_char('⇧')?; #[cfg(not(target_os = "macos"))] write!(f, "shift-")?; } let key = match self.key.as_str() { #[cfg(target_os = "macos")] "backspace" => '⌫', #[cfg(target_os = "macos")] "up" => '↑', #[cfg(target_os = "macos")] "down" => '↓', #[cfg(target_os = "macos")] "left" => '←', #[cfg(target_os = "macos")] "right" => '→', #[cfg(target_os = "macos")] "tab" => '⇥', #[cfg(target_os = "macos")] "escape" => '⎋', #[cfg(target_os = "macos")] "shift" => '⇧', #[cfg(target_os = "macos")] "control" => '⌃', #[cfg(target_os = "macos")] "alt" => '⌥', #[cfg(target_os = "macos")] "platform" => '⌘', key if key.len() == 1 => key.chars().next().unwrap().to_ascii_uppercase(), key => return f.write_str(key), }; f.write_char(key) } } /// The state of the modifier keys at some point in time #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)] pub struct Modifiers { /// The control key #[serde(default)] pub control: bool, /// The alt key /// Sometimes also known as the 'meta' key #[serde(default)] pub alt: bool, /// The shift key #[serde(default)] pub shift: bool, /// The command key, on macos /// the windows key, on windows /// the super key, on linux #[serde(default)] pub platform: bool, /// The function key #[serde(default)] pub function: bool, } impl Modifiers { /// Returns whether any modifier key is pressed. pub fn modified(&self) -> bool { self.control || self.alt || self.shift || self.platform || self.function } /// Whether the semantically 'secondary' modifier key is pressed. /// /// On macOS, this is the command key. /// On Linux and Windows, this is the control key. pub fn secondary(&self) -> bool { #[cfg(target_os = "macos")] { self.platform } #[cfg(not(target_os = "macos"))] { self.control } } /// Returns how many modifier keys are pressed. pub fn number_of_modifiers(&self) -> u8 { self.control as u8 + self.alt as u8 + self.shift as u8 + self.platform as u8 + self.function as u8 } /// Returns [`Modifiers`] with no modifiers. pub fn none() -> Modifiers { Default::default() } /// Returns [`Modifiers`] with just the command key. pub fn command() -> Modifiers { Modifiers { platform: true, ..Default::default() } } /// A Returns [`Modifiers`] with just the secondary key pressed. pub fn secondary_key() -> Modifiers { #[cfg(target_os = "macos")] { Modifiers { platform: true, ..Default::default() } } #[cfg(not(target_os = "macos"))] { Modifiers { control: true, ..Default::default() } } } /// Returns [`Modifiers`] with just the windows key. pub fn windows() -> Modifiers { Modifiers { platform: true, ..Default::default() } } /// Returns [`Modifiers`] with just the super key. pub fn super_key() -> Modifiers { Modifiers { platform: true, ..Default::default() } } /// Returns [`Modifiers`] with just control. pub fn control() -> Modifiers { Modifiers { control: true, ..Default::default() } } /// Returns [`Modifiers`] with just alt. pub fn alt() -> Modifiers { Modifiers { alt: true, ..Default::default() } } /// Returns [`Modifiers`] with just shift. pub fn shift() -> Modifiers { Modifiers { shift: true, ..Default::default() } } /// Returns [`Modifiers`] with command + shift. pub fn command_shift() -> Modifiers { Modifiers { shift: true, platform: true, ..Default::default() } } /// Returns [`Modifiers`] with command + shift. pub fn control_shift() -> Modifiers { Modifiers { shift: true, control: true, ..Default::default() } } /// Checks if this [`Modifiers`] is a subset of another [`Modifiers`]. pub fn is_subset_of(&self, other: &Modifiers) -> bool { (*other & *self) == *self } } impl std::ops::BitOr for Modifiers { type Output = Self; fn bitor(mut self, other: Self) -> Self::Output { self |= other; self } } impl std::ops::BitOrAssign for Modifiers { fn bitor_assign(&mut self, other: Self) { self.control |= other.control; self.alt |= other.alt; self.shift |= other.shift; self.platform |= other.platform; self.function |= other.function; } } impl std::ops::BitXor for Modifiers { type Output = Self; fn bitxor(mut self, rhs: Self) -> Self::Output { self ^= rhs; self } } impl std::ops::BitXorAssign for Modifiers { fn bitxor_assign(&mut self, other: Self) { self.control ^= other.control; self.alt ^= other.alt; self.shift ^= other.shift; self.platform ^= other.platform; self.function ^= other.function; } } impl std::ops::BitAnd for Modifiers { type Output = Self; fn bitand(mut self, rhs: Self) -> Self::Output { self &= rhs; self } } impl std::ops::BitAndAssign for Modifiers { fn bitand_assign(&mut self, other: Self) { self.control &= other.control; self.alt &= other.alt; self.shift &= other.shift; self.platform &= other.platform; self.function &= other.function; } } /// The state of the capslock key at some point in time #[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize, Hash, JsonSchema)] pub struct Capslock { /// The capslock key is on #[serde(default)] pub on: bool, }