1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
//! Crokey helps incorporate configurable keybindings in [crossterm](https://github.com/crossterm-rs/crossterm)
//! based terminal applications by providing functions
//! - parsing key combinations from strings
//! - describing key combinations in strings
use {
crate::KeyCombination,
crossterm::event::{KeyCode::*, KeyModifiers},
std::fmt,
};
/// A formatter to produce key combinations descriptions.
///
/// ```
/// use {
/// crokey::*,
/// crossterm::event::{
/// KeyCode,
/// KeyEvent,
/// KeyModifiers,
/// },
/// };
///
/// let format = KeyCombinationFormat::default();
/// assert_eq!(format.to_string(key!(shift-a)), "Shift-a");
/// assert_eq!(format.to_string(key!(ctrl-c)), "Ctrl-c");
///
/// // A more compact format
/// let format = KeyCombinationFormat::default()
/// .with_implicit_shift()
/// .with_control("^");
/// assert_eq!(format.to_string(key!(shift-a)), "A");
/// assert_eq!(format.to_string(key!(ctrl-c)), "^c");
///
/// // A long format with lowercased modifiers
/// let format = KeyCombinationFormat::default()
/// .with_lowercase_modifiers();
/// assert_eq!(format.to_string(key!(ctrl-enter)), "ctrl-Enter");
/// assert_eq!(format.to_string(key!(home)), "Home");
/// assert_eq!(
/// format.to_string(
/// KeyCombination::new(
/// KeyCode::F(6),
/// KeyModifiers::ALT,
/// )
/// ),
/// "alt-F6",
/// );
/// assert_eq!(
/// format.to_string(
/// KeyCombination::new(
/// (KeyCode::Char('u'), KeyCode::Char('i')),
/// KeyModifiers::NONE,
/// )
/// ),
/// "i-u",
/// );
///
/// ```
#[derive(Debug, Clone)]
pub struct KeyCombinationFormat {
pub control: String,
pub alt: String,
pub shift: String,
pub enter: String,
pub uppercase_shift: bool,
pub key_separator: String,
}
impl Default for KeyCombinationFormat {
fn default() -> Self {
Self {
control: "Ctrl-".to_string(),
alt: "Alt-".to_string(),
shift: "Shift-".to_string(),
enter: "Enter".to_string(),
uppercase_shift: false,
key_separator: "-".to_string(),
}
}
}
impl KeyCombinationFormat {
pub fn with_lowercase_modifiers(mut self) -> Self {
self.control = self.control.to_lowercase();
self.alt = self.alt.to_lowercase();
self.shift = self.shift.to_lowercase();
self
}
pub fn with_control<S: Into<String>>(mut self, s: S) -> Self {
self.control = s.into();
self
}
pub fn with_alt<S: Into<String>>(mut self, s: S) -> Self {
self.alt = s.into();
self
}
pub fn with_shift<S: Into<String>>(mut self, s: S) -> Self {
self.shift = s.into();
self
}
pub fn with_implicit_shift(mut self) -> Self {
self.shift = "".to_string();
self.uppercase_shift = true;
self
}
/// return a wrapper of the key implementing Display
///
/// ```
/// use crokey::*;
/// let format = KeyCombinationFormat::default();
/// let k = format.format(key!(f6));
/// let s = format!("k={}", k);
/// assert_eq!(s, "k=F6");
/// ```
pub fn format<K: Into<KeyCombination>>(&self, key: K) -> FormattedKeyCombination {
FormattedKeyCombination { format: self, key: key.into() }
}
/// return the key formatted into a string
///
/// `format.to_string(key)` is equivalent to `format.format(key).to_string()`.
pub fn to_string<K: Into<KeyCombination>>(&self, key: K) -> String {
self.format(key).to_string()
}
}
pub struct FormattedKeyCombination<'s> {
format: &'s KeyCombinationFormat,
key: KeyCombination,
}
impl<'s> fmt::Display for FormattedKeyCombination<'s> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let format = &self.format;
let key = &self.key;
if key.modifiers.contains(KeyModifiers::CONTROL) {
write!(f, "{}", format.control)?;
}
if key.modifiers.contains(KeyModifiers::ALT) {
write!(f, "{}", format.alt)?;
}
if key.modifiers.contains(KeyModifiers::SHIFT) {
write!(f, "{}", format.shift)?;
}
for (i, code) in key.codes.iter().enumerate() {
if i > 0 {
write!(f, "{}", format.key_separator)?;
}
match code {
Char(' ') => {
write!(f, "Space")?;
}
Char('-') => {
write!(f, "Hyphen")?;
}
Char('\r') | Char('\n') | Enter => {
write!(f, "{}", format.enter)?;
}
Char(c) if key.modifiers.contains(KeyModifiers::SHIFT) && format.uppercase_shift => {
write!(f, "{}", c.to_ascii_uppercase())?;
}
Char(c) => {
write!(f, "{}", c.to_ascii_lowercase())?;
}
F(u) => {
write!(f, "F{u}")?;
}
_ => {
write!(f, "{:?}", code)?;
}
}
}
Ok(())
}
}