use super::{KeyStroke, Shortcut};
use crate::event::Key;
pub const STANDARD_EDIT_CATEGORY: &str = "edit";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum StandardEdit {
Cut,
Copy,
Paste,
PastePlain,
Undo,
Redo,
SelectAll,
}
impl StandardEdit {
pub const ALL: [StandardEdit; 7] = [
StandardEdit::Undo,
StandardEdit::Redo,
StandardEdit::Cut,
StandardEdit::Copy,
StandardEdit::Paste,
StandardEdit::PastePlain,
StandardEdit::SelectAll,
];
pub const fn id(self) -> &'static str {
match self {
StandardEdit::Cut => "edit.cut",
StandardEdit::Copy => "edit.copy",
StandardEdit::Paste => "edit.paste",
StandardEdit::PastePlain => "edit.paste_plain",
StandardEdit::Undo => "edit.undo",
StandardEdit::Redo => "edit.redo",
StandardEdit::SelectAll => "edit.select_all",
}
}
pub fn from_id(id: &str) -> Option<Self> {
Self::ALL.into_iter().find(|command| command.id() == id)
}
pub fn shortcut(self) -> Shortcut {
let (primary, secondary) = self.default_chords();
let builder = Shortcut::new(self.id())
.name(self.english_name())
.category(STANDARD_EDIT_CATEGORY)
.category_label("Edit")
.primary(primary);
match secondary {
Some(secondary) => builder.secondary(secondary).build(),
None => builder.build(),
}
}
fn english_name(self) -> &'static str {
match self {
StandardEdit::Cut => "Cut",
StandardEdit::Copy => "Copy",
StandardEdit::Paste => "Paste",
StandardEdit::PastePlain => "Paste Unformatted",
StandardEdit::Undo => "Undo",
StandardEdit::Redo => "Redo",
StandardEdit::SelectAll => "Select All",
}
}
fn default_chords(self) -> (KeyStroke, Option<KeyStroke>) {
match self {
StandardEdit::Cut => (KeyStroke::ctrl(Key::X), None),
StandardEdit::Copy => (KeyStroke::ctrl(Key::C), None),
StandardEdit::Paste => (KeyStroke::ctrl(Key::V), None),
StandardEdit::PastePlain => (KeyStroke::ctrl_shift(Key::V), None),
StandardEdit::Undo => (KeyStroke::ctrl(Key::Z), None),
StandardEdit::Redo if cfg!(target_os = "windows") => {
(KeyStroke::ctrl(Key::Y), Some(KeyStroke::ctrl_shift(Key::Z)))
}
StandardEdit::Redo => (KeyStroke::ctrl_shift(Key::Z), Some(KeyStroke::ctrl(Key::Y))),
StandardEdit::SelectAll => (KeyStroke::ctrl(Key::A), None),
}
}
}
pub(crate) fn register_standard_shortcuts(registry: &mut super::ShortcutRegistry) {
for command in StandardEdit::ALL {
registry.register(command.shortcut());
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::Modifiers;
use crate::shortcut::ShortcutRegistry;
#[test]
fn ids_round_trip_and_are_unique() {
let mut seen = std::collections::HashSet::new();
for command in StandardEdit::ALL {
assert!(seen.insert(command.id()), "{} listed twice", command.id());
assert_eq!(StandardEdit::from_id(command.id()), Some(command));
assert_eq!(command.shortcut().id, command.id());
}
assert_eq!(StandardEdit::from_id("edit.nothing"), None);
}
#[test]
fn defaults_cover_the_chords_the_editors_used_to_hardcode() {
let mut registry = ShortcutRegistry::new();
register_standard_shortcuts(&mut registry);
let fires = |key: Key, modifiers: Modifiers| {
registry
.find_by_keystroke(KeyStroke::new(key, modifiers))
.map(|s| s.shortcut.id)
};
let accel = Modifiers::COMMAND;
let accel_shift = Modifiers::COMMAND | Modifiers::SHIFT;
assert_eq!(fires(Key::X, accel), Some("edit.cut"));
assert_eq!(fires(Key::C, accel), Some("edit.copy"));
assert_eq!(fires(Key::V, accel), Some("edit.paste"));
assert_eq!(fires(Key::V, accel_shift), Some("edit.paste_plain"));
assert_eq!(fires(Key::Z, accel), Some("edit.undo"));
assert_eq!(fires(Key::Z, accel_shift), Some("edit.redo"));
assert_eq!(fires(Key::Y, accel), Some("edit.redo"));
assert_eq!(fires(Key::A, accel), Some("edit.select_all"));
}
#[test]
fn a_secondary_binding_fires_beside_the_default() {
let mut registry = ShortcutRegistry::new();
register_standard_shortcuts(&mut registry);
let positional = KeyStroke::new(Key::Character('à'), Modifiers::CTRL);
registry.rebind_secondary("edit.undo", Some(positional));
let effective = registry.effective("edit.undo").unwrap();
assert!(effective.matches(KeyStroke::new(Key::Z, Modifiers::COMMAND)));
assert!(effective.matches(positional));
}
}