#[derive(Debug, Clone)]
pub struct TextAreaKeyMap {
pub left: Vec<KeyBinding>,
pub right: Vec<KeyBinding>,
pub up: Vec<KeyBinding>,
pub down: Vec<KeyBinding>,
pub word_left: Vec<KeyBinding>,
pub word_right: Vec<KeyBinding>,
pub line_start: Vec<KeyBinding>,
pub line_end: Vec<KeyBinding>,
pub doc_start: Vec<KeyBinding>,
pub doc_end: Vec<KeyBinding>,
pub delete_before: Vec<KeyBinding>,
pub delete_after: Vec<KeyBinding>,
pub delete_word_before: Vec<KeyBinding>,
pub delete_word_after: Vec<KeyBinding>,
pub delete_line: Vec<KeyBinding>,
pub select_all: Vec<KeyBinding>,
pub copy: Vec<KeyBinding>,
pub cut: Vec<KeyBinding>,
pub paste: Vec<KeyBinding>,
pub undo: Vec<KeyBinding>,
pub redo: Vec<KeyBinding>,
pub newline: Vec<KeyBinding>,
pub tab: Vec<KeyBinding>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyBinding {
pub key: KeyType,
pub modifiers: Modifiers,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KeyType {
Char(char),
Up,
Down,
Left,
Right,
Home,
End,
PageUp,
PageDown,
Backspace,
Delete,
Enter,
Tab,
Escape,
Space,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Modifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
}
impl Modifiers {
pub const NONE: Self = Self {
ctrl: false,
alt: false,
shift: false,
};
pub const CTRL: Self = Self {
ctrl: true,
alt: false,
shift: false,
};
pub const ALT: Self = Self {
ctrl: false,
alt: true,
shift: false,
};
pub const SHIFT: Self = Self {
ctrl: false,
alt: false,
shift: true,
};
pub const CTRL_SHIFT: Self = Self {
ctrl: true,
alt: false,
shift: true,
};
}
impl KeyBinding {
pub fn new(key: KeyType, modifiers: Modifiers) -> Self {
Self { key, modifiers }
}
pub fn char(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::NONE)
}
pub fn ctrl(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::CTRL)
}
pub fn alt(c: char) -> Self {
Self::new(KeyType::Char(c), Modifiers::ALT)
}
pub fn special(key: KeyType) -> Self {
Self::new(key, Modifiers::NONE)
}
pub fn ctrl_special(key: KeyType) -> Self {
Self::new(key, Modifiers::CTRL)
}
pub fn matches(&self, input: &str, key: &crate::hooks::Key) -> bool {
if self.modifiers.ctrl != key.ctrl
|| self.modifiers.alt != key.alt
|| self.modifiers.shift != key.shift
{
return false;
}
match &self.key {
KeyType::Char(c) => {
if input.len() == 1 {
input.starts_with(*c)
} else {
false
}
}
KeyType::Up => key.up_arrow,
KeyType::Down => key.down_arrow,
KeyType::Left => key.left_arrow,
KeyType::Right => key.right_arrow,
KeyType::Home => key.home,
KeyType::End => key.end,
KeyType::PageUp => key.page_up,
KeyType::PageDown => key.page_down,
KeyType::Backspace => key.backspace,
KeyType::Delete => key.delete,
KeyType::Enter => key.return_key,
KeyType::Tab => key.tab,
KeyType::Escape => key.escape,
KeyType::Space => key.space,
}
}
}
impl Default for TextAreaKeyMap {
fn default() -> Self {
Self {
left: vec![KeyBinding::special(KeyType::Left)],
right: vec![KeyBinding::special(KeyType::Right)],
up: vec![KeyBinding::special(KeyType::Up)],
down: vec![KeyBinding::special(KeyType::Down)],
word_left: vec![
KeyBinding::ctrl_special(KeyType::Left),
KeyBinding::alt('b'),
],
word_right: vec![
KeyBinding::ctrl_special(KeyType::Right),
KeyBinding::alt('f'),
],
line_start: vec![KeyBinding::special(KeyType::Home), KeyBinding::ctrl('a')],
line_end: vec![KeyBinding::special(KeyType::End), KeyBinding::ctrl('e')],
doc_start: vec![KeyBinding::ctrl_special(KeyType::Home)],
doc_end: vec![KeyBinding::ctrl_special(KeyType::End)],
delete_before: vec![KeyBinding::special(KeyType::Backspace)],
delete_after: vec![KeyBinding::special(KeyType::Delete)],
delete_word_before: vec![
KeyBinding::ctrl_special(KeyType::Backspace),
KeyBinding::ctrl('w'),
],
delete_word_after: vec![KeyBinding::ctrl_special(KeyType::Delete)],
delete_line: vec![KeyBinding::ctrl('k')],
select_all: vec![KeyBinding::ctrl('a')],
copy: vec![KeyBinding::ctrl('c')],
cut: vec![KeyBinding::ctrl('x')],
paste: vec![KeyBinding::ctrl('v')],
undo: vec![KeyBinding::ctrl('z')],
redo: vec![
KeyBinding::ctrl('y'),
KeyBinding::new(KeyType::Char('z'), Modifiers::CTRL_SHIFT),
],
newline: vec![KeyBinding::special(KeyType::Enter)],
tab: vec![KeyBinding::special(KeyType::Tab)],
}
}
}
impl TextAreaKeyMap {
pub fn new() -> Self {
Self::default()
}
pub fn minimal() -> Self {
Self {
left: vec![KeyBinding::special(KeyType::Left)],
right: vec![KeyBinding::special(KeyType::Right)],
up: vec![KeyBinding::special(KeyType::Up)],
down: vec![KeyBinding::special(KeyType::Down)],
word_left: vec![],
word_right: vec![],
line_start: vec![KeyBinding::special(KeyType::Home)],
line_end: vec![KeyBinding::special(KeyType::End)],
doc_start: vec![],
doc_end: vec![],
delete_before: vec![KeyBinding::special(KeyType::Backspace)],
delete_after: vec![KeyBinding::special(KeyType::Delete)],
delete_word_before: vec![],
delete_word_after: vec![],
delete_line: vec![],
select_all: vec![],
copy: vec![],
cut: vec![],
paste: vec![],
undo: vec![],
redo: vec![],
newline: vec![KeyBinding::special(KeyType::Enter)],
tab: vec![KeyBinding::special(KeyType::Tab)],
}
}
pub fn disabled() -> Self {
Self {
left: vec![],
right: vec![],
up: vec![],
down: vec![],
word_left: vec![],
word_right: vec![],
line_start: vec![],
line_end: vec![],
doc_start: vec![],
doc_end: vec![],
delete_before: vec![],
delete_after: vec![],
delete_word_before: vec![],
delete_word_after: vec![],
delete_line: vec![],
select_all: vec![],
copy: vec![],
cut: vec![],
paste: vec![],
undo: vec![],
redo: vec![],
newline: vec![],
tab: vec![],
}
}
pub fn match_action(&self, input: &str, key: &crate::hooks::Key) -> Option<TextAreaAction> {
for binding in &self.left {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveLeft);
}
}
for binding in &self.right {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveRight);
}
}
for binding in &self.up {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveUp);
}
}
for binding in &self.down {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveDown);
}
}
for binding in &self.word_left {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveWordLeft);
}
}
for binding in &self.word_right {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveWordRight);
}
}
for binding in &self.line_start {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveToLineStart);
}
}
for binding in &self.line_end {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveToLineEnd);
}
}
for binding in &self.doc_start {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveToStart);
}
}
for binding in &self.doc_end {
if binding.matches(input, key) {
return Some(TextAreaAction::MoveToEnd);
}
}
for binding in &self.delete_before {
if binding.matches(input, key) {
return Some(TextAreaAction::DeleteBefore);
}
}
for binding in &self.delete_after {
if binding.matches(input, key) {
return Some(TextAreaAction::DeleteAfter);
}
}
for binding in &self.delete_word_before {
if binding.matches(input, key) {
return Some(TextAreaAction::DeleteWordBefore);
}
}
for binding in &self.delete_word_after {
if binding.matches(input, key) {
return Some(TextAreaAction::DeleteWordAfter);
}
}
for binding in &self.delete_line {
if binding.matches(input, key) {
return Some(TextAreaAction::DeleteLine);
}
}
for binding in &self.select_all {
if binding.matches(input, key) {
return Some(TextAreaAction::SelectAll);
}
}
for binding in &self.copy {
if binding.matches(input, key) {
return Some(TextAreaAction::Copy);
}
}
for binding in &self.cut {
if binding.matches(input, key) {
return Some(TextAreaAction::Cut);
}
}
for binding in &self.paste {
if binding.matches(input, key) {
return Some(TextAreaAction::Paste);
}
}
for binding in &self.undo {
if binding.matches(input, key) {
return Some(TextAreaAction::Undo);
}
}
for binding in &self.redo {
if binding.matches(input, key) {
return Some(TextAreaAction::Redo);
}
}
for binding in &self.newline {
if binding.matches(input, key) {
return Some(TextAreaAction::InsertNewline);
}
}
for binding in &self.tab {
if binding.matches(input, key) {
return Some(TextAreaAction::InsertTab);
}
}
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextAreaAction {
MoveLeft,
MoveRight,
MoveUp,
MoveDown,
MoveWordLeft,
MoveWordRight,
MoveToLineStart,
MoveToLineEnd,
MoveToStart,
MoveToEnd,
DeleteBefore,
DeleteAfter,
DeleteWordBefore,
DeleteWordAfter,
DeleteLine,
SelectAll,
Copy,
Cut,
Paste,
Undo,
Redo,
InsertNewline,
InsertTab,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_keymap() {
let keymap = TextAreaKeyMap::default();
assert!(!keymap.left.is_empty());
assert!(!keymap.delete_before.is_empty());
}
#[test]
fn test_minimal_keymap() {
let keymap = TextAreaKeyMap::minimal();
assert!(!keymap.left.is_empty());
assert!(keymap.word_left.is_empty());
}
#[test]
fn test_disabled_keymap() {
let keymap = TextAreaKeyMap::disabled();
assert!(keymap.left.is_empty());
assert!(keymap.delete_before.is_empty());
}
}