#![allow(clippy::missing_const_for_fn)] #![allow(clippy::doc_markdown)]
use std::collections::HashMap;
use {
reovim_driver_command_types::ArgValue,
reovim_driver_input::{
KeyCode, KeyEvent, KeyLookupState, KeySequence, ModeTransition, Modifiers, PopResult,
},
reovim_kernel::api::v1::{CommandId, ModeId, Position},
};
use crate::{ids::OperatorId, modes::VimMode};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OperatorType {
Delete,
Yank,
Change,
Lowercase,
Uppercase,
ToggleCase,
}
impl OperatorType {
#[must_use]
pub const fn mode_id(&self) -> ModeId {
match self {
Self::Delete => VimMode::DELETE_ID,
Self::Yank => VimMode::YANK_ID,
Self::Change => VimMode::CHANGE_ID,
Self::Lowercase => VimMode::LOWERCASE_ID,
Self::Uppercase => VimMode::UPPERCASE_ID,
Self::ToggleCase => VimMode::TOGGLE_CASE_ID,
}
}
#[allow(clippy::redundant_clone)] #[must_use]
pub fn operator_id(&self) -> OperatorId {
match self {
Self::Delete => crate::ids::DELETE.clone(),
Self::Yank => crate::ids::YANK.clone(),
Self::Change => crate::ids::CHANGE.clone(),
Self::Lowercase => crate::ids::LOWERCASE.clone(),
Self::Uppercase => crate::ids::UPPERCASE.clone(),
Self::ToggleCase => crate::ids::TOGGLE_CASE_OP.clone(),
}
}
#[must_use]
pub const fn line_key(&self) -> char {
match self {
Self::Delete => 'd',
Self::Yank => 'y',
Self::Change => 'c',
Self::Lowercase => 'u',
Self::Uppercase => 'U',
Self::ToggleCase => '~',
}
}
#[must_use]
#[cfg_attr(coverage_nightly, coverage(off))]
pub const fn display_name(&self) -> &'static str {
match self {
Self::Delete => "DELETE",
Self::Yank => "YANK",
Self::Change => "CHANGE",
Self::Lowercase => "LOWERCASE",
Self::Uppercase => "UPPERCASE",
Self::ToggleCase => "TOGGLE-CASE",
}
}
}
#[must_use]
pub fn is_escape(key: &KeyEvent) -> bool {
key.code == KeyCode::Escape
|| (key.code == KeyCode::Char('[') && key.modifiers.contains(Modifiers::CTRL))
}
#[must_use]
pub fn is_count_digit(key: &KeyEvent, has_count: bool) -> bool {
if key.modifiers != Modifiers::NONE {
return false;
}
match key.code {
KeyCode::Char('1'..='9') => true,
KeyCode::Char('0') => has_count, _ => false,
}
}
#[must_use]
pub fn accumulate_count(key: &KeyEvent, current: Option<usize>) -> Option<usize> {
if let KeyCode::Char(c @ '0'..='9') = key.code {
let digit = c.to_digit(10).expect("valid digit") as usize;
Some(current.unwrap_or(0) * 10 + digit)
} else {
current
}
}
#[must_use]
pub fn is_line_operator_key(key: &KeyEvent, operator: OperatorType) -> bool {
key.modifiers == Modifiers::NONE && key.code == KeyCode::Char(operator.line_key())
}
#[must_use]
pub fn is_linewise_motion(cmd: &CommandId) -> bool {
let name = cmd.name();
matches!(
name,
"cursor-down"
| "cursor-up"
| "document-start"
| "document-end"
| "screen-top"
| "screen-middle"
| "screen-bottom"
| "paragraph-forward"
| "paragraph-backward"
| "next-line"
| "prev-line"
| "whole-line"
)
}
#[must_use]
pub fn is_inclusive_motion(cmd: &CommandId) -> bool {
let name = cmd.name();
matches!(
name,
"line-end" | "word-end" | "word-end-big" | "find-char" | "find-char-back" | "till-char" | "till-char-back" | "match-bracket" )
}
#[must_use]
pub fn is_word_forward_motion(cmd: &CommandId) -> bool {
let name = cmd.name();
matches!(
name,
"word-forward" | "word-forward-big" )
}
#[must_use]
pub fn build_operator_execute(
operator: OperatorType,
start: Position,
end: Position,
linewise: bool,
count: Option<usize>,
register: Option<char>,
) -> PopResult {
let operator_id = operator.operator_id();
let command =
CommandId::from_owned(operator_id.module().clone(), operator_id.name().to_owned());
let mut args = HashMap::new();
args.insert("linewise".to_string(), ArgValue::Bool(linewise));
args.insert("range_start".to_string(), ArgValue::Position(start.line, start.column));
args.insert("range_end".to_string(), ArgValue::Position(end.line, end.column));
args.insert("count".to_string(), ArgValue::Count(count.unwrap_or(1)));
if let Some(reg) = register {
args.insert("register".to_string(), ArgValue::Register(reg));
}
PopResult::ExecuteCommand { command, args }
}
#[must_use]
pub fn build_cancelled() -> ModeTransition {
ModeTransition::Pop {
result: Some(PopResult::Cancelled),
}
}
#[must_use]
pub fn apply_keymap_policy(lookup: &KeyLookupState) -> KeymapAction {
match lookup {
KeyLookupState::ExactOnly(cmd) | KeyLookupState::ExactWithLonger { exact: cmd } => {
KeymapAction::Execute(cmd.clone())
}
KeyLookupState::PrefixOnly => KeymapAction::Pending,
KeyLookupState::NotFound => KeymapAction::Cancel,
}
}
#[derive(Debug, Clone)]
pub enum KeymapAction {
Execute(CommandId),
Pending,
Cancel,
}
#[derive(Debug, Clone)]
pub struct OperatorState {
pub operator: OperatorType,
pub start_position: Option<Position>,
pub operator_count: Option<usize>,
pub motion_count: Option<usize>,
pub register: Option<char>,
pub pending_keys: KeySequence,
pub initialized: bool,
}
impl OperatorState {
#[must_use]
pub fn new(operator: OperatorType) -> Self {
Self {
operator,
start_position: None,
operator_count: None,
motion_count: None,
register: None,
pending_keys: KeySequence::new(),
initialized: false,
}
}
#[must_use]
pub fn with_context(
operator: OperatorType,
count: Option<usize>,
register: Option<char>,
) -> Self {
Self {
operator,
start_position: None,
operator_count: count,
motion_count: None,
register,
pending_keys: KeySequence::new(),
initialized: true, }
}
pub fn set_start_position(&mut self, pos: Position) {
self.start_position = Some(pos);
}
#[must_use]
pub fn effective_count(&self) -> usize {
let op = self.operator_count.unwrap_or(1);
let motion = self.motion_count.unwrap_or(1);
op * motion
}
#[must_use]
pub fn explicit_count(&self) -> Option<usize> {
match (self.operator_count, self.motion_count) {
(None, None) => None,
(Some(op), None) => Some(op),
(None, Some(motion)) => Some(motion),
(Some(op), Some(motion)) => Some(op * motion),
}
}
#[must_use]
pub fn has_motion_count(&self) -> bool {
self.motion_count.is_some()
}
pub fn accumulate_motion_count(&mut self, key: &KeyEvent) {
self.motion_count = accumulate_count(key, self.motion_count);
}
pub fn take_motion_count(&mut self) -> Option<usize> {
self.motion_count.take()
}
pub fn push_key(&mut self, key: KeyEvent) {
self.pending_keys.push(key);
}
#[must_use]
pub fn keys(&self) -> KeySequence {
self.pending_keys.clone()
}
pub fn clear_keys(&mut self) {
self.pending_keys.clear();
}
pub fn reset(&mut self) {
self.start_position = None;
self.operator_count = None;
self.motion_count = None;
self.register = None;
self.pending_keys.clear();
self.initialized = false;
}
}