use hjkl_engine::Editor as EngineEditor;
type Editor<H> = EngineEditor<hjkl_buffer::Buffer, H>;
pub struct StepBookkeeping {
pub pending_was_macro_chord: bool,
pub was_insert: bool,
pub pre_visual_snapshot: Option<hjkl_engine::vim::LastVisual>,
}
pub(crate) fn begin_step<H: hjkl_engine::Host>(
ed: &mut Editor<H>,
input: hjkl_engine::Input,
) -> Result<StepBookkeeping, bool> {
use hjkl_engine::input::Key;
use hjkl_engine::vim::{Mode, Pending};
ed.sync_buffer_content_from_textarea();
let now = std::time::Instant::now();
let host_now = ed.host().now();
let timed_out = match ed.vim.last_input_host_at {
Some(prev) => host_now.saturating_sub(prev) > ed.settings().timeout_len,
None => false,
};
if timed_out {
let chord_in_flight = !matches!(ed.vim.pending, Pending::None)
|| ed.vim.count != 0
|| ed.vim.pending_register.is_some()
|| ed.vim.insert_pending_register;
if chord_in_flight {
ed.vim.clear_pending_prefix();
}
}
ed.vim.last_input_at = Some(now);
ed.vim.last_input_host_at = Some(host_now);
if ed.vim.recording_macro.is_some()
&& !ed.vim.replaying_macro
&& matches!(ed.vim.pending, Pending::None)
&& ed.vim.mode != Mode::Insert
&& input.key == Key::Char('q')
&& !input.ctrl
&& !input.alt
{
let reg = ed.vim.recording_macro.take().unwrap();
let keys = std::mem::take(&mut ed.vim.recording_keys);
let text = hjkl_engine::input::encode_macro(&keys);
ed.set_named_register_text(reg.to_ascii_lowercase(), text);
return Err(true);
}
let pending_was_macro_chord = matches!(
ed.vim.pending,
Pending::RecordMacroTarget | Pending::PlayMacroTarget { .. }
);
let was_insert = ed.vim.mode == Mode::Insert;
let pre_visual_snapshot = match ed.vim.mode {
Mode::Visual => Some(hjkl_engine::vim::LastVisual {
mode: Mode::Visual,
anchor: ed.vim.visual_anchor,
cursor: ed.cursor(),
block_vcol: 0,
}),
Mode::VisualLine => Some(hjkl_engine::vim::LastVisual {
mode: Mode::VisualLine,
anchor: (ed.vim.visual_line_anchor, 0),
cursor: ed.cursor(),
block_vcol: 0,
}),
Mode::VisualBlock => Some(hjkl_engine::vim::LastVisual {
mode: Mode::VisualBlock,
anchor: ed.vim.block_anchor,
cursor: ed.cursor(),
block_vcol: ed.vim.block_vcol,
}),
_ => None,
};
Ok(StepBookkeeping {
pending_was_macro_chord,
was_insert,
pre_visual_snapshot,
})
}
pub(crate) fn end_step<H: hjkl_engine::Host>(
ed: &mut Editor<H>,
input: hjkl_engine::Input,
bk: StepBookkeeping,
consumed: bool,
) -> bool {
use hjkl_engine::input::Key;
use hjkl_engine::vim::{Mode, Pending};
let StepBookkeeping {
pending_was_macro_chord,
was_insert,
pre_visual_snapshot,
} = bk;
if let Some(snap) = pre_visual_snapshot
&& !matches!(
ed.vim.mode,
Mode::Visual | Mode::VisualLine | Mode::VisualBlock
)
{
let (lo, hi) = match snap.mode {
Mode::Visual => {
if snap.anchor <= snap.cursor {
(snap.anchor, snap.cursor)
} else {
(snap.cursor, snap.anchor)
}
}
Mode::VisualLine => {
let r_lo = snap.anchor.0.min(snap.cursor.0);
let r_hi = snap.anchor.0.max(snap.cursor.0);
let vl_rope = ed.buffer().rope();
let r_hi_clamped = r_hi.min(vl_rope.len_lines().saturating_sub(1));
let last_col = hjkl_buffer::rope_line_str(&vl_rope, r_hi_clamped)
.chars()
.count()
.saturating_sub(1);
((r_lo, 0), (r_hi, last_col))
}
Mode::VisualBlock => {
let (r1, c1) = snap.anchor;
let (r2, c2) = snap.cursor;
((r1.min(r2), c1.min(c2)), (r1.max(r2), c1.max(c2)))
}
_ => {
if snap.anchor <= snap.cursor {
(snap.anchor, snap.cursor)
} else {
(snap.cursor, snap.anchor)
}
}
};
ed.set_mark('<', lo);
ed.set_mark('>', hi);
ed.vim.last_visual = Some(snap);
}
if !was_insert
&& ed.vim.one_shot_normal
&& ed.vim.mode == Mode::Normal
&& matches!(ed.vim.pending, Pending::None)
{
ed.vim.one_shot_normal = false;
ed.vim.mode = Mode::Insert;
}
ed.sync_buffer_content_from_textarea();
if !ed.vim.viewport_pinned {
ed.ensure_cursor_in_scrolloff();
}
ed.vim.viewport_pinned = false;
if ed.vim.recording_macro.is_some()
&& !ed.vim.replaying_macro
&& input.key != Key::Char('q')
&& !pending_was_macro_chord
{
ed.vim.recording_keys.push(input);
}
ed.vim.current_mode = ed.vim.public_mode();
hjkl_engine::vim::drop_blame_if_left_normal(ed);
consumed
}