use {
reovim_driver_input::{
ExtensionMap, KeyCode, KeyEvent, KeyLookupState, KeySequence, ModeKeyResolver, ModeState,
Modifiers, ResolveContext, ResolveInput, ResolveResult, SessionApiDyn,
},
reovim_kernel::api::v1::{ModeId, Position},
};
use crate::{VimSessionState, ids, modes::VimMode, session_state::ReplaceRestoreEntry};
pub struct VimReplaceResolver {
mode_id: ModeId,
parent_mode_id: ModeId,
}
impl VimReplaceResolver {
#[must_use]
pub const fn new() -> Self {
Self {
mode_id: VimMode::REPLACE_ID,
parent_mode_id: VimMode::INSERT_ID,
}
}
const fn is_insertable(key: &KeyEvent) -> Option<char> {
if key.modifiers.contains(Modifiers::CTRL) || key.modifiers.contains(Modifiers::ALT) {
return None;
}
match key.code {
KeyCode::Char(c) => Some(c),
KeyCode::Tab => Some('\t'),
KeyCode::Enter => Some('\n'),
_ => None,
}
}
const fn is_backspace(key: &KeyEvent) -> bool {
matches!(key.code, KeyCode::Backspace)
|| (key.modifiers.contains(Modifiers::CTRL) && matches!(key.code, KeyCode::Char('h')))
}
}
impl Default for VimReplaceResolver {
fn default() -> Self {
Self::new()
}
}
impl ModeKeyResolver for VimReplaceResolver {
#[cfg_attr(coverage_nightly, coverage(off))]
fn resolve_with_session(
&self,
key: &KeyEvent,
_state: &mut ModeState,
input: &ResolveInput<'_>,
session: &mut dyn SessionApiDyn,
_shared_extensions: &mut ExtensionMap,
client_extensions: &mut ExtensionMap,
) -> ResolveResult {
if let Some(vim) = client_extensions.get_mut::<VimSessionState>() {
vim.record_repeat_key(*key);
}
if Self::is_backspace(key) {
return ResolveResult::Execute(ids::REPLACE_BACKSPACE, ResolveContext::new());
}
if let Some(c) = Self::is_insertable(key) {
if let Some(vim) = client_extensions.get_mut::<VimSessionState>() {
vim.insert_buffer.push(c);
}
if c == '\n' {
push_restore_entry(session, client_extensions);
return ResolveResult::insert_char('\n');
}
return handle_replace_char(session, client_extensions, c);
}
let keys = KeySequence::from_keys(&[*key]);
let lookup_state = input.keymap.query(input.mode, &keys);
match lookup_state {
KeyLookupState::ExactOnly(cmd) | KeyLookupState::ExactWithLonger { exact: cmd, .. } => {
ResolveResult::Execute(cmd, ResolveContext::new())
}
KeyLookupState::PrefixOnly => ResolveResult::Pending,
KeyLookupState::NotFound => ResolveResult::NotHandled,
}
}
fn mode_id(&self) -> &ModeId {
&self.mode_id
}
fn inherits_from(&self) -> Option<&ModeId> {
Some(&self.parent_mode_id)
}
fn reset(&mut self) {
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn push_restore_entry(session: &dyn SessionApiDyn, client_extensions: &mut ExtensionMap) {
let Some(buffer_id) = session.active_buffer() else {
return;
};
let Some(cursor) = session.cursor_position() else {
return;
};
let line_len = session.buffer_line_len(buffer_id, cursor.line).unwrap_or(0);
let original = if cursor.column < line_len {
session
.buffer_line(buffer_id, cursor.line)
.and_then(|line| line.chars().nth(cursor.column))
} else {
None
};
if let Some(vim) = client_extensions.get_mut::<VimSessionState>() {
vim.replace_restore_stack.push(ReplaceRestoreEntry {
position: cursor,
original,
});
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
fn handle_replace_char(
session: &mut dyn SessionApiDyn,
client_extensions: &mut ExtensionMap,
replacement: char,
) -> ResolveResult {
let Some(buffer_id) = session.active_buffer() else {
return ResolveResult::insert_char(replacement);
};
let Some(cursor) = session.cursor_position() else {
return ResolveResult::insert_char(replacement);
};
let line_len = session.buffer_line_len(buffer_id, cursor.line).unwrap_or(0);
if cursor.column < line_len {
let original_char = session
.buffer_line(buffer_id, cursor.line)
.and_then(|line| line.chars().nth(cursor.column));
if let Some(vim) = client_extensions.get_mut::<VimSessionState>() {
vim.replace_restore_stack.push(ReplaceRestoreEntry {
position: cursor,
original: original_char,
});
}
let char_end = cursor.column + original_char.map_or(1, char::len_utf8);
session.delete_range(buffer_id, cursor, Position::new(cursor.line, char_end));
} else {
if let Some(vim) = client_extensions.get_mut::<VimSessionState>() {
vim.replace_restore_stack.push(ReplaceRestoreEntry {
position: cursor,
original: None,
});
}
}
ResolveResult::insert_char(replacement)
}