use teksilo_core::event::Modifiers;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum TextNavConvention {
Mac,
PcStyle,
}
impl TextNavConvention {
pub(crate) const CURRENT: Self = if cfg!(target_os = "macos") {
Self::Mac
} else {
Self::PcStyle
};
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum CaretStep {
Character,
Word,
LineEdge,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum LineStep {
Line,
Paragraph,
Document,
}
pub(crate) fn caret_step(modifiers: Modifiers) -> CaretStep {
caret_step_for(TextNavConvention::CURRENT, modifiers)
}
pub(crate) fn caret_step_for(convention: TextNavConvention, modifiers: Modifiers) -> CaretStep {
match convention {
TextNavConvention::Mac => {
if modifiers.super_key() {
CaretStep::LineEdge
} else if modifiers.alt() {
CaretStep::Word
} else {
CaretStep::Character
}
}
TextNavConvention::PcStyle => {
if modifiers.ctrl() {
CaretStep::Word
} else {
CaretStep::Character
}
}
}
}
pub(crate) fn line_step(modifiers: Modifiers) -> LineStep {
line_step_for(TextNavConvention::CURRENT, modifiers)
}
pub(crate) fn line_step_for(convention: TextNavConvention, modifiers: Modifiers) -> LineStep {
match convention {
TextNavConvention::Mac => {
if modifiers.super_key() {
LineStep::Document
} else if modifiers.alt() {
LineStep::Paragraph
} else {
LineStep::Line
}
}
TextNavConvention::PcStyle => LineStep::Line,
}
}
pub(crate) fn deletes_word(modifiers: Modifiers) -> bool {
deletes_word_for(TextNavConvention::CURRENT, modifiers)
}
pub(crate) fn deletes_word_for(convention: TextNavConvention, modifiers: Modifiers) -> bool {
match convention {
TextNavConvention::Mac => modifiers.alt() && !modifiers.super_key(),
TextNavConvention::PcStyle => modifiers.ctrl(),
}
}
#[cfg(test)]
mod tests {
use super::TextNavConvention::{Mac, PcStyle};
use super::*;
const ALT: Modifiers = Modifiers::ALT;
const SUPER: Modifiers = Modifiers::SUPER;
const CTRL: Modifiers = Modifiers::CTRL;
const SHIFT: Modifiers = Modifiers::SHIFT;
#[test]
fn mac_puts_word_on_option_and_the_line_edge_on_command() {
assert_eq!(caret_step_for(Mac, Modifiers::NONE), CaretStep::Character);
assert_eq!(caret_step_for(Mac, ALT), CaretStep::Word);
assert_eq!(caret_step_for(Mac, SUPER), CaretStep::LineEdge);
}
#[test]
fn pc_style_puts_word_on_ctrl_and_owns_no_arrow_line_edge() {
assert_eq!(
caret_step_for(PcStyle, Modifiers::NONE),
CaretStep::Character
);
assert_eq!(caret_step_for(PcStyle, CTRL), CaretStep::Word);
assert_eq!(caret_step_for(PcStyle, SUPER), CaretStep::Character);
}
#[test]
fn physical_control_is_not_a_mac_text_chord() {
assert_eq!(caret_step_for(Mac, CTRL), CaretStep::Character);
assert_eq!(line_step_for(Mac, CTRL), LineStep::Line);
assert!(!deletes_word_for(Mac, CTRL));
}
#[test]
fn shift_only_extends_and_never_changes_the_step() {
for (convention, mods) in [
(Mac, ALT),
(Mac, SUPER),
(Mac, Modifiers::NONE),
(PcStyle, CTRL),
(PcStyle, Modifiers::NONE),
] {
assert_eq!(
caret_step_for(convention, mods),
caret_step_for(convention, mods | SHIFT),
"Shift selects; it must not reinterpret the motion"
);
assert_eq!(
line_step_for(convention, mods),
line_step_for(convention, mods | SHIFT),
);
}
}
#[test]
fn mac_vertical_arrows_reach_the_paragraph_and_the_document() {
assert_eq!(line_step_for(Mac, Modifiers::NONE), LineStep::Line);
assert_eq!(line_step_for(Mac, ALT), LineStep::Paragraph);
assert_eq!(line_step_for(Mac, SUPER), LineStep::Document);
}
#[test]
fn pc_style_vertical_arrows_are_always_one_line() {
for m in [Modifiers::NONE, CTRL, ALT, SUPER] {
assert_eq!(line_step_for(PcStyle, m), LineStep::Line);
}
}
#[test]
fn a_two_modifier_arrow_takes_the_coarser_motion() {
assert_eq!(caret_step_for(Mac, ALT | SUPER), CaretStep::LineEdge);
assert_eq!(line_step_for(Mac, ALT | SUPER), LineStep::Document);
}
#[test]
fn delete_word_follows_the_same_modifier_as_word_motion() {
assert!(deletes_word_for(Mac, ALT));
assert!(!deletes_word_for(Mac, Modifiers::NONE));
assert!(deletes_word_for(PcStyle, CTRL));
assert!(!deletes_word_for(PcStyle, Modifiers::NONE));
assert!(!deletes_word_for(PcStyle, ALT));
}
#[test]
fn command_backspace_on_mac_is_not_a_word_delete() {
assert!(!deletes_word_for(Mac, SUPER));
assert!(!deletes_word_for(Mac, ALT | SUPER));
}
#[test]
fn the_current_convention_matches_the_build_target() {
if cfg!(target_os = "macos") {
assert_eq!(TextNavConvention::CURRENT, Mac);
assert_eq!(caret_step(ALT), CaretStep::Word);
} else {
assert_eq!(TextNavConvention::CURRENT, PcStyle);
assert_eq!(caret_step(CTRL), CaretStep::Word);
}
}
}