use hjkl_vim_types::{InsertEntry, InsertReason, LastChange, Motion, Operator};
use super::*;
use crate::vim_state::{vim, vim_mut};
use hjkl_engine::Editor;
use hjkl_engine::buf_helpers::{
buf_cursor_pos, buf_line, buf_line_chars, buf_row_count, buf_set_cursor_rc,
};
pub fn enter_insert_i_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
begin_insert(ed, count.max(1), InsertReason::Enter(InsertEntry::I));
}
pub fn enter_insert_shift_i_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
move_to_insert_start(ed);
begin_insert(ed, count.max(1), InsertReason::Enter(InsertEntry::ShiftI));
}
pub fn enter_insert_a_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), 1);
begin_insert(ed, count.max(1), InsertReason::Enter(InsertEntry::A));
}
pub fn enter_insert_shift_a_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
hjkl_engine::motions::move_line_end(ed.buffer_mut());
hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), 1);
begin_insert(ed, count.max(1), InsertReason::Enter(InsertEntry::ShiftA));
}
pub fn open_line_below_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
use hjkl_buffer::{Edit, Position};
move_to_closed_fold_edge(ed, false);
ed.push_undo();
begin_insert_noundo(ed, count.max(1), InsertReason::Open { above: false });
ed.sync_buffer_content_from_textarea();
let row = buf_cursor_pos(ed.buffer()).row;
let line_chars = buf_line_chars(ed.buffer(), row);
let prev_line = buf_line(ed.buffer(), row).unwrap_or_default();
let comment_cont = if ed.settings().formatoptions.contains('o') {
continue_comment(ed.buffer(), ed.settings(), row)
} else {
None
};
let suffix = if let Some(cont) = comment_cont {
format!("\n{cont}")
} else {
let indent = compute_enter_indent(ed.settings(), &prev_line);
format!("\n{indent}")
};
ed.mutate_edit(Edit::InsertStr {
at: Position::new(row, line_chars),
text: suffix,
});
restart_insert_snapshot(ed);
}
fn move_to_closed_fold_edge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
first: bool,
) {
let (row, col) = ed.cursor();
let (top, bot) = expand_linewise_over_closed_folds(ed.buffer(), row, row);
let edge = if first { top } else { bot };
if edge != row {
let col = col.min(buf_line_chars(ed.buffer(), edge).saturating_sub(1));
buf_set_cursor_rc(ed.buffer_mut(), edge, col);
}
}
fn restart_insert_snapshot<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>) {
let (row, col) = ed.cursor();
let rope = hjkl_engine::types::Query::rope(ed.buffer());
if let Some(session) = vim_mut(ed).insert_session.as_mut() {
session.before_rope = rope;
session.row_min = row;
session.row_max = row;
session.start_row = row;
session.start_col = col;
}
}
pub fn open_line_above_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
use hjkl_buffer::{Edit, Position};
move_to_closed_fold_edge(ed, true);
ed.push_undo();
begin_insert_noundo(ed, count.max(1), InsertReason::Open { above: true });
ed.sync_buffer_content_from_textarea();
let row = buf_cursor_pos(ed.buffer()).row;
let comment_cont = if ed.settings().formatoptions.contains('o') {
continue_comment(ed.buffer(), ed.settings(), row)
} else {
None
};
let (insert_text, new_line_content) = if let Some(cont) = comment_cont {
let content = cont.clone();
(format!("{cont}\n"), content)
} else {
let cur = buf_line(ed.buffer(), row).unwrap_or_default();
let indent = compute_enter_indent(ed.settings(), &cur);
let content = indent.clone();
(format!("{indent}\n"), content)
};
ed.mutate_edit(Edit::InsertStr {
at: Position::new(row, 0),
text: insert_text,
});
buf_set_cursor_rc(ed.buffer_mut(), row, new_line_content.chars().count());
restart_insert_snapshot(ed);
}
pub fn enter_replace_mode_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
begin_insert(ed, count.max(1), InsertReason::Replace);
}
pub fn delete_char_forward_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let register = vim(ed).pending_register;
do_char_delete(ed, true, count.max(1));
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::CharDel {
forward: true,
count: count.max(1),
register,
});
}
}
pub fn delete_char_backward_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let register = vim(ed).pending_register;
do_char_delete(ed, false, count.max(1));
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::CharDel {
forward: false,
count: count.max(1),
register,
});
}
}
pub fn substitute_char_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let register = vim(ed).pending_register;
if on_closed_fold(ed) {
apply_op_with_motion(ed, Operator::Change, &Motion::Right, count.max(1));
} else {
substitute_chars(ed, count);
}
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::OpMotion {
op: Operator::Change,
motion: Motion::Right,
count: count.max(1),
inserted: None,
register,
});
}
}
fn substitute_chars<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
use hjkl_buffer::{Edit, MotionKind, Position};
ed.push_undo();
ed.sync_buffer_content_from_textarea();
let mut deleted = String::new();
let count = if refuse_read_only_register(ed) {
0
} else {
count.max(1)
};
for _ in 0..count {
let cursor = buf_cursor_pos(ed.buffer());
let line_chars = buf_line_chars(ed.buffer(), cursor.row);
if cursor.col >= line_chars {
break;
}
let inverse = ed.mutate_edit(Edit::DeleteRange {
start: cursor,
end: Position::new(cursor.row, cursor.col + 1),
kind: MotionKind::Char,
});
if let Edit::InsertStr { text, .. } = inverse {
deleted.push_str(&text);
}
}
if !deleted.is_empty() {
ed.record_yank_to_host(deleted.clone());
let target = vim_mut(ed).pending_register.take();
ed.record_delete(deleted, false, target);
}
begin_insert_noundo(ed, 1, InsertReason::AfterChange);
}
pub fn substitute_line_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let register = vim(ed).pending_register;
execute_line_op(ed, Operator::Change, count.max(1));
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::LineOp {
op: Operator::Change,
count: count.max(1),
inserted: None,
register,
});
}
}
fn on_closed_fold<H: hjkl_engine::types::Host>(ed: &Editor<hjkl_buffer::View, H>) -> bool {
let row = ed.cursor().0;
ed.buffer().is_row_hidden(row) || ed.buffer().is_row_hidden(row + 1)
}
pub fn delete_to_eol_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let register = vim(ed).pending_register;
if refuse_read_only_register(ed) {
} else if count <= 1 && !on_closed_fold(ed) {
ed.push_undo();
delete_to_eol(ed);
hjkl_engine::motions::move_left(ed.buffer_mut(), 1);
remember_cursor_column(ed);
} else {
apply_op_with_motion(ed, Operator::Delete, &Motion::LineEnd, count);
}
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::DeleteToEol {
inserted: None,
register,
});
}
}
pub fn change_to_eol_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let count = count.max(1);
if count <= 1 && !on_closed_fold(ed) {
let register = vim(ed).pending_register;
ed.push_undo();
if !refuse_read_only_register(ed) {
delete_to_eol(ed);
}
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::DeleteToEol {
inserted: None,
register,
});
}
begin_insert_noundo(ed, 1, InsertReason::DeleteToEol);
} else {
if !vim(ed).replaying {
let register = vim(ed).pending_register;
vim_mut(ed).last_change = Some(LastChange::OpMotion {
op: Operator::Change,
motion: Motion::LineEnd,
count,
inserted: None,
register,
});
}
apply_op_with_motion(ed, Operator::Change, &Motion::LineEnd, count);
}
}
pub fn yank_to_eol_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
apply_op_with_motion(ed, Operator::Yank, &Motion::LineEnd, count.max(1));
}
pub fn join_line_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let joins = count.max(2) - 1;
reset_column_for_counted_join_on_last_line(ed, count);
let _group = ed.undo_group();
for _ in 0..joins {
ed.push_undo();
if !join_line(ed) {
break;
}
}
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::JoinLine { count: joins });
}
remember_cursor_column(ed);
}
pub fn toggle_case_at_cursor_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
let _group = ed.undo_group();
for _ in 0..count.max(1) {
ed.push_undo();
if !toggle_case_at_cursor(ed) {
break;
}
}
let cursor = buf_cursor_pos(ed.buffer());
let line_chars = buf_line_chars(ed.buffer(), cursor.row);
if line_chars > 0 && cursor.col >= line_chars {
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, line_chars - 1);
}
remember_operator_column(ed);
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::ToggleCase {
count: count.max(1),
});
}
}
pub fn paste_after_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
paste_bridge(ed, false, count, false, false);
}
pub fn paste_before_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
paste_bridge(ed, true, count, false, false);
}
pub fn paste_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
before: bool,
count: usize,
cursor_after: bool,
reindent: bool,
) {
let count = count.clamp(1, MAX_COUNT);
let register = vim(ed).pending_register;
if do_paste(ed, before, count, cursor_after, reindent) && !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::Paste {
before,
count,
cursor_after,
reindent,
register,
});
}
}
pub fn jump_back_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
for _ in 0..count.max(1) {
if !jump_back(ed) {
break;
}
}
}
pub fn jump_forward_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
count: usize,
) {
for _ in 0..count.max(1) {
if !jump_forward(ed) {
break;
}
}
}
pub fn force_normal_bridge<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>) {
vim_mut(ed).force_normal();
clamp_cursor_to_normal_mode(ed);
}
pub fn mouse_click_doc_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
row: usize,
col: usize,
) {
if vim(ed).is_visual() {
vim_mut(ed).force_normal();
}
break_undo_group_in_insert(ed);
let max_row = buf_row_count(ed.buffer()).saturating_sub(1);
let r = row.min(max_row);
let line_len = buf_line(ed.buffer(), r).map_or(0, |l| l.chars().count());
let cap = if vim(ed).current_mode == hjkl_engine::VimMode::Insert {
line_len
} else {
line_len.saturating_sub(1)
};
let c = col.min(cap);
buf_set_cursor_rc(ed.buffer_mut(), r, c);
remember_cursor_column(ed);
}
pub fn mouse_begin_drag_bridge<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>) {
if !vim(ed).is_visual_char() {
enter_visual_char_bridge(ed);
}
}
pub fn range_for_op_motion_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
motion_key: char,
total_count: usize,
) -> Option<(usize, usize)> {
let start = ed.cursor();
let input = hjkl_engine::input::Input {
key: hjkl_engine::input::Key::Char(motion_key),
ctrl: false,
alt: false,
shift: false,
};
let motion = parse_motion(&input)?;
let motion = match motion {
Motion::FindRepeat { reverse } => match vim(ed).last_find {
Some((ch, forward, till)) => Motion::Find {
ch,
forward: if reverse { !forward } else { forward },
till,
},
None => return None,
},
m => m,
};
apply_motion_cursor_ctx(ed, &motion, total_count, true);
let end = ed.cursor();
buf_set_cursor_rc(ed.buffer_mut(), start.0, start.1);
let (r0, r1) = (start.0.min(end.0), start.0.max(end.0));
Some((r0, r1))
}
pub fn range_for_op_g_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
total_count: usize,
) -> Option<(usize, usize)> {
let start = ed.cursor();
let motion = op_g_motion(ch)?;
apply_motion_cursor_ctx(ed, &motion, total_count, true);
let end = ed.cursor();
buf_set_cursor_rc(ed.buffer_mut(), start.0, start.1);
let (r0, r1) = (start.0.min(end.0), start.0.max(end.0));
Some((r0, r1))
}
pub fn range_for_op_text_obj_bridge<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
ch: char,
inner: bool,
total_count: usize,
) -> Option<(usize, usize)> {
let obj = text_object_from_char(ch)?;
let (start, end, _kind) = text_object_range(ed, obj, inner, total_count.max(1))?;
let (r0, r1) = (start.0.min(end.0), start.0.max(end.0));
Some((r0, r1))
}
pub fn search_repeat_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
forward: bool,
count: usize,
) {
if let Some(pattern) = ed.last_search_pattern() {
ed.push_search_pattern(&pattern);
}
if ed.search_state().pattern.is_none() {
return;
}
let go_forward = ed.last_search_forward() == forward;
for _ in 0..count.max(1) {
if go_forward {
ed.search_advance_forward(true);
} else {
ed.search_advance_backward(true);
}
}
}
pub fn word_search_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
forward: bool,
whole_word: bool,
count: usize,
) {
word_at_cursor_search(ed, forward, whole_word, count.max(1));
}
#[cfg(test)]
mod substitute_char_tests {
use hjkl_buffer::View;
use hjkl_engine::{DefaultHost, Input, Key, Options};
fn inp(key: Key) -> Input {
Input {
key,
ctrl: false,
alt: false,
shift: false,
}
}
#[test]
fn substitute_char_writes_deleted_text_to_unnamed_register() {
let mut ed = crate::vim::vim_editor(
View::from_str("ab\ncd"),
DefaultHost::new(),
Options::default(),
);
ed.settings_mut().motion_sneak = false;
crate::dispatch_input(&mut ed, inp(Key::Char('y')));
crate::dispatch_input(&mut ed, inp(Key::Char('y')));
crate::dispatch_input(&mut ed, inp(Key::Char('s')));
crate::dispatch_input(&mut ed, inp(Key::Esc));
assert_eq!(
ed.with_registers(|r| r.unnamed.text.clone()),
"a",
"s must put the substituted char in the unnamed register"
);
}
}