119 lines
4.2 KiB
Rust
119 lines
4.2 KiB
Rust
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub(crate) enum ComposerEditAction {
|
|
InsertChar(char),
|
|
InsertNewline,
|
|
DeleteBefore,
|
|
DeleteAfter,
|
|
DeleteWordBefore,
|
|
MoveLeft,
|
|
MoveRight,
|
|
MoveWordLeft,
|
|
MoveWordRight,
|
|
MoveStart,
|
|
MoveHome,
|
|
MoveEnd,
|
|
MoveUp,
|
|
MoveDown,
|
|
}
|
|
|
|
impl ComposerEditAction {
|
|
pub(crate) fn is_modifier_action(self) -> bool {
|
|
matches!(
|
|
self,
|
|
Self::InsertNewline
|
|
| Self::DeleteWordBefore
|
|
| Self::MoveWordLeft
|
|
| Self::MoveWordRight
|
|
| Self::MoveStart
|
|
| Self::MoveEnd
|
|
)
|
|
}
|
|
}
|
|
|
|
/// Shared readline-style composer editing keymap used by the normal Pod TUI
|
|
/// and the workspace panel. Callers still own higher-level routing such as
|
|
/// completion popups, Enter submission, Tab target switching, Esc focus, and
|
|
/// row/list navigation.
|
|
pub(crate) fn composer_edit_action(key: KeyEvent) -> Option<ComposerEditAction> {
|
|
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
|
let alt = key.modifiers.contains(KeyModifiers::ALT);
|
|
|
|
match key.code {
|
|
KeyCode::Enter if alt && !ctrl => Some(ComposerEditAction::InsertNewline),
|
|
KeyCode::Char('a') if ctrl && !alt => Some(ComposerEditAction::MoveStart),
|
|
KeyCode::Char('e') if ctrl && !alt => Some(ComposerEditAction::MoveEnd),
|
|
KeyCode::Left if ctrl || alt => Some(ComposerEditAction::MoveWordLeft),
|
|
KeyCode::Right if ctrl || alt => Some(ComposerEditAction::MoveWordRight),
|
|
KeyCode::Backspace if ctrl || alt => Some(ComposerEditAction::DeleteWordBefore),
|
|
KeyCode::Char('w') if ctrl && !alt => Some(ComposerEditAction::DeleteWordBefore),
|
|
KeyCode::Backspace if !ctrl && !alt => Some(ComposerEditAction::DeleteBefore),
|
|
KeyCode::Delete if !ctrl && !alt => Some(ComposerEditAction::DeleteAfter),
|
|
KeyCode::Left if !ctrl && !alt => Some(ComposerEditAction::MoveLeft),
|
|
KeyCode::Right if !ctrl && !alt => Some(ComposerEditAction::MoveRight),
|
|
KeyCode::Up if !ctrl && !alt => Some(ComposerEditAction::MoveUp),
|
|
KeyCode::Down if !ctrl && !alt => Some(ComposerEditAction::MoveDown),
|
|
KeyCode::Home if !alt => Some(ComposerEditAction::MoveHome),
|
|
KeyCode::End if !alt => Some(ComposerEditAction::MoveEnd),
|
|
KeyCode::Char(c) if !ctrl && !alt => Some(ComposerEditAction::InsertChar(c)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn key(code: KeyCode) -> KeyEvent {
|
|
KeyEvent::new(code, KeyModifiers::NONE)
|
|
}
|
|
|
|
fn modified(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
|
|
KeyEvent::new(code, modifiers)
|
|
}
|
|
|
|
#[test]
|
|
fn maps_word_motion_and_word_delete_keys() {
|
|
assert_eq!(
|
|
composer_edit_action(modified(KeyCode::Left, KeyModifiers::CONTROL)),
|
|
Some(ComposerEditAction::MoveWordLeft)
|
|
);
|
|
assert_eq!(
|
|
composer_edit_action(modified(KeyCode::Right, KeyModifiers::ALT)),
|
|
Some(ComposerEditAction::MoveWordRight)
|
|
);
|
|
assert_eq!(
|
|
composer_edit_action(modified(KeyCode::Backspace, KeyModifiers::CONTROL)),
|
|
Some(ComposerEditAction::DeleteWordBefore)
|
|
);
|
|
assert_eq!(
|
|
composer_edit_action(modified(KeyCode::Char('w'), KeyModifiers::CONTROL)),
|
|
Some(ComposerEditAction::DeleteWordBefore)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn leaves_enter_tab_esc_and_control_letters_for_callers() {
|
|
assert_eq!(composer_edit_action(key(KeyCode::Enter)), None);
|
|
assert_eq!(composer_edit_action(key(KeyCode::Tab)), None);
|
|
assert_eq!(composer_edit_action(key(KeyCode::Esc)), None);
|
|
assert_eq!(
|
|
composer_edit_action(modified(KeyCode::Char('j'), KeyModifiers::CONTROL)),
|
|
None
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn plain_letters_remain_text_input() {
|
|
assert_eq!(
|
|
composer_edit_action(key(KeyCode::Char('j'))),
|
|
Some(ComposerEditAction::InsertChar('j'))
|
|
);
|
|
assert_eq!(
|
|
composer_edit_action(key(KeyCode::Char('o'))),
|
|
Some(ComposerEditAction::InsertChar('o'))
|
|
);
|
|
}
|
|
}
|