use hjkl_engine::abbrev::AbbrevTrigger;
use hjkl_engine::types::InsertDir;
use hjkl_vim_types::{InsertReason, Mode};
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_bytes, buf_line_chars, buf_row_count, buf_set_cursor_pos,
buf_set_cursor_rc,
};
use hjkl_engine::tag::{is_html_filetype, scan_tag_opener, sync_paired_tag_on_exit};
pub fn type_text<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>, text: &str) {
use hjkl_buffer::{Edit, Position};
let wraps = ed.settings().textwidth > 0 && ed.settings().formatoptions.contains('t');
let chunks: Vec<String> = if wraps {
text.chars().map(String::from).collect()
} else {
vec![text.to_string()]
};
for chunk in chunks {
if let Some(ch) = chunk.chars().next().filter(|_| wraps) {
auto_wrap_before(ed, ch);
}
let (row, col) = ed.cursor();
ed.mutate_edit(Edit::InsertStr {
at: Position::new(row, col),
text: chunk,
});
}
}
pub fn auto_wrap_before<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
) {
use hjkl_buffer::{Edit, Position};
let textwidth = ed.settings().textwidth;
if textwidth == 0 || ch.is_whitespace() {
return;
}
let tabstop = ed.settings().tabstop;
let is_blank = |c: char| c == ' ' || c == '\t';
loop {
let (row, col) = ed.cursor();
let line = buf_line(ed.buffer(), row).unwrap_or_default();
let chars: Vec<char> = line.chars().collect();
let col = col.min(chars.len());
let typed: String = chars[..col].iter().chain(std::iter::once(&ch)).collect();
if hjkl_buffer::char_col_to_visual_col(&typed, col + 1, tabstop) <= textwidth {
return;
}
let leader = comment_leader(&line, &ed.settings().comments, tabstop);
let fo = &ed.settings().formatoptions;
if !fo.contains(if leader.is_some() { 'c' } else { 't' }) {
return;
}
let leader_len = leader.as_ref().map_or(0, |l| l.len);
let margin = hjkl_buffer::visual_col_to_char_col(&line, textwidth, tabstop);
let mut found = None;
let mut at = col;
loop {
let here = if at == col {
Some(ch)
} else {
chars.get(at).copied()
};
if here.is_some_and(is_blank) {
let mut start = at;
while start > 0 && is_blank(chars[start - 1]) {
start -= 1;
}
if start == 0 || start < leader_len {
break;
}
found = Some(start);
if start <= margin {
break;
}
at = start;
}
if at == 0 {
break;
}
at -= 1;
}
let Some(start) = found else {
return;
};
let mut end = start;
while end < col && is_blank(chars[end]) {
end += 1;
}
let indent: String = match &leader {
Some(l) => l.next.clone(),
None if ed.settings().autoindent => {
chars.iter().take_while(|&&c| is_blank(c)).collect()
}
None => String::new(),
};
let new_col = indent.chars().count() + col.saturating_sub(end);
let at = hjkl_engine::types::Query::rope(ed.buffer()).line_to_char(row) + start;
if !vim(ed).replaying
&& let Some(session) = vim_mut(ed).insert_session.as_mut()
{
session.wraps.push(hjkl_vim_types::InsertWrap {
at,
blanks: chars[start..end].iter().collect(),
indent: indent.chars().count(),
});
}
ed.mutate_edit(Edit::Replace {
start: Position::new(row, start),
end: Position::new(row, end),
with: format!("\n{indent}"),
});
buf_set_cursor_rc(ed.buffer_mut(), row + 1, new_col);
}
}
pub fn insert_char_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
) -> bool {
use hjkl_buffer::{Edit, MotionKind, Position};
ed.sync_buffer_content_from_textarea();
let in_replace = matches!(
vim(ed).insert_session.as_ref().map(|s| &s.reason),
Some(InsertReason::Replace)
);
if !in_replace && !ed.abbrevs_is_empty() {
let iskeyword = ed.settings().iskeyword.clone();
if !is_keyword_char(ch, &iskeyword) {
check_and_apply_abbrev(ed, AbbrevTrigger::NonKeyword(ch));
}
}
maybe_word_undo_break(ed, ch);
if !in_replace {
auto_wrap_before(ed, ch);
}
let cursor = buf_cursor_pos(ed.buffer());
let line_chars = buf_line_chars(ed.buffer(), cursor.row);
if !in_replace
&& !ed.pending_closes().is_empty()
&& let Some(&(pr, _pc, pch)) = ed.pending_closes().last()
&& ch == pch
&& cursor.row == pr
{
let char_at_cursor =
buf_line(ed.buffer(), cursor.row).and_then(|l| l.chars().nth(cursor.col));
if char_at_cursor == Some(ch) {
ed.pending_closes_mut().pop();
let autoclose_tag = ed.settings().autoclose_tag;
if ch == '>' && autoclose_tag && is_html_filetype(&ed.settings().filetype) {
let new_col = cursor.col + 1;
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, new_col);
if let Some(line) = buf_line(ed.buffer(), cursor.row) {
let char_col = new_col.saturating_sub(1);
let byte_col = line
.char_indices()
.nth(char_col)
.map_or(line.len(), |(b, _)| b);
if let Some(tag) = scan_tag_opener(&line, byte_col) {
let close_tag = format!("</{tag}>");
let insert_pos = Position::new(cursor.row, new_col);
ed.mutate_edit(Edit::InsertStr {
at: insert_pos,
text: close_tag,
});
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, new_col);
}
}
} else {
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, cursor.col + 1);
}
return true;
}
}
if in_replace && cursor.col < line_chars {
ed.pending_closes_mut().clear();
ed.mutate_edit(Edit::DeleteRange {
start: cursor,
end: Position::new(cursor.row, cursor.col + 1),
kind: MotionKind::Char,
});
ed.mutate_edit(Edit::InsertChar { at: cursor, ch });
} else if !try_dedent_close_bracket(ed, cursor, ch) {
let autopair = ed.settings().autopair;
let autoclose_tag = ed.settings().autoclose_tag;
let (prev_char, prev2_char) = {
let rope = hjkl_engine::types::Query::rope(ed.buffer());
let row = cursor.row;
if row < rope.len_lines() {
let line = rope.line(row);
let mut n = line.len_chars();
if n > 0 && line.char(n - 1) == '\n' {
n -= 1;
}
let at = |i: usize| (i < n).then(|| line.char(i));
(
cursor.col.checked_sub(1).and_then(at),
cursor.col.checked_sub(2).and_then(at),
)
} else {
(None, None)
}
};
if autopair {
if let Some(close) =
autopair_close_for(ch, &ed.settings().filetype, prev_char, prev2_char)
{
ed.mutate_edit(Edit::InsertChar { at: cursor, ch });
let after = Position::new(cursor.row, cursor.col + 1);
ed.mutate_edit(Edit::InsertChar {
at: after,
ch: close,
});
let between_col = cursor.col + 1;
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, between_col);
ed.pending_closes_mut()
.push((cursor.row, between_col, close));
return true;
}
if ch == '>' && autoclose_tag && is_html_filetype(&ed.settings().filetype) {
ed.mutate_edit(Edit::InsertChar { at: cursor, ch });
let new_col = cursor.col + 1;
if let Some(line) = buf_line(ed.buffer(), cursor.row) {
let char_col = new_col.saturating_sub(1);
let byte_col = line
.char_indices()
.nth(char_col)
.map_or(line.len(), |(b, _)| b);
if let Some(tag) = scan_tag_opener(&line, byte_col) {
let close_tag = format!("</{tag}>");
let insert_pos = Position::new(cursor.row, new_col);
ed.mutate_edit(Edit::InsertStr {
at: insert_pos,
text: close_tag,
});
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, new_col);
}
}
return true;
}
}
ed.mutate_edit(Edit::InsertChar { at: cursor, ch });
}
true
}
pub fn insert_newline_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
use hjkl_buffer::Edit;
ed.sync_buffer_content_from_textarea();
if !ed.abbrevs_is_empty() {
check_and_apply_abbrev(ed, AbbrevTrigger::Cr);
}
maybe_word_undo_break(ed, '\n');
let cursor = buf_cursor_pos(ed.buffer());
let prev_line = buf_line(ed.buffer(), cursor.row).unwrap_or_default();
if ed.settings().autopair && !ed.pending_closes().is_empty() {
let prev_char = if cursor.col > 0 {
prev_line.chars().nth(cursor.col - 1)
} else {
None
};
let next_char = prev_line.chars().nth(cursor.col);
let is_open_pair = matches!(
(prev_char, next_char),
(Some('{'), Some('}')) | (Some('('), Some(')')) | (Some('['), Some(']'))
);
if is_open_pair {
ed.pending_closes_mut().clear();
let base_indent: String = prev_line
.chars()
.take_while(|c| *c == ' ' || *c == '\t')
.collect();
let inner_indent = if ed.settings().expandtab {
let unit = if ed.settings().softtabstop > 0 {
ed.settings().softtabstop
} else {
ed.settings().shiftwidth
};
format!("{base_indent}{}", " ".repeat(unit))
} else {
format!("{base_indent}\t")
};
let text = format!("\n{inner_indent}\n{base_indent}");
ed.mutate_edit(Edit::InsertStr { at: cursor, text });
let new_row = cursor.row + 1;
let new_col = inner_indent.len();
buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
return true;
}
}
if ed.settings().autopair
&& let Some(fence) = detect_code_fence_opener(&prev_line, cursor.col)
{
ed.pending_closes_mut().clear();
let base_indent: String = prev_line
.chars()
.take_while(|c| *c == ' ' || *c == '\t')
.collect();
let text = format!("\n{base_indent}\n{base_indent}{fence}");
ed.mutate_edit(Edit::InsertStr { at: cursor, text });
let new_row = cursor.row + 1;
let new_col = base_indent.chars().count();
buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
return true;
}
let comment_cont = if ed.settings().formatoptions.contains('r') {
continue_comment(ed.buffer(), ed.settings(), cursor.row)
} else {
None
};
ed.pending_closes_mut().clear();
let text = if let Some(cont) = comment_cont {
format!("\n{cont}")
} else {
let before_cursor: String = prev_line.chars().take(cursor.col).collect();
let indent = compute_enter_indent(ed.settings(), &before_cursor);
format!("\n{indent}")
};
let ws_run = if ed.settings().autoindent {
prev_line
.chars()
.skip(cursor.col)
.take_while(|c| *c == ' ' || *c == '\t')
.count()
} else {
0
};
if ws_run > 0 {
ed.mutate_edit(Edit::Replace {
start: cursor,
end: hjkl_buffer::Position::new(cursor.row, cursor.col + ws_run),
with: text,
});
} else {
ed.mutate_edit(Edit::InsertStr { at: cursor, text });
}
true
}
pub fn insert_tab_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
use hjkl_buffer::Edit;
ed.sync_buffer_content_from_textarea();
let cursor = buf_cursor_pos(ed.buffer());
if ed.settings().expandtab {
let sts = ed.settings().softtabstop;
let n = if sts > 0 {
sts - (cursor.col % sts)
} else {
ed.settings().tabstop.max(1)
};
ed.mutate_edit(Edit::InsertStr {
at: cursor,
text: " ".repeat(n),
});
} else {
ed.mutate_edit(Edit::InsertChar {
at: cursor,
ch: '\t',
});
}
true
}
pub fn insert_backspace_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
ed.sync_buffer_content_from_textarea();
if matches!(
vim(ed).insert_session.as_ref().map(|s| &s.reason),
Some(InsertReason::Replace)
) {
return replace_backspace(ed);
}
count_backspace_past_start(ed, generic_backspace)
}
fn count_backspace_past_start<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
backspace: fn(&mut Editor<hjkl_buffer::View, H>) -> bool,
) -> bool {
let before_cursor = ed.cursor();
let before_len = hjkl_engine::types::Query::rope(ed.buffer()).len_chars();
let result = backspace(ed);
let after_cursor = ed.cursor();
let after_len = hjkl_engine::types::Query::rope(ed.buffer()).len_chars();
if after_cursor == before_cursor && after_len == before_len {
return result;
}
if let Some(session) = vim_mut(ed).insert_session.as_mut()
&& before_cursor <= (session.start_row, session.start_col)
{
session.backspaces += 1;
session.deleted += before_len as isize - after_len as isize;
(session.start_row, session.start_col) = after_cursor;
session.row_min = session.row_min.min(after_cursor.0);
}
result
}
pub fn generic_backspace<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
use hjkl_buffer::{Edit, MotionKind, Position};
let cursor = buf_cursor_pos(ed.buffer());
if cursor.col > 0 {
let line = buf_line(ed.buffer(), cursor.row).unwrap_or_default();
if let Some((indent, prefix)) = detect_comment_on_line(&ed.settings().filetype, &line) {
let full_prefix = format!("{indent}{prefix}");
let line_trimmed = line.trim_end_matches(' ');
let prefix_trimmed = full_prefix.trim_end_matches(' ');
if line_trimmed == prefix_trimmed && cursor.col == full_prefix.chars().count() {
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(cursor.row, 0),
end: cursor,
kind: MotionKind::Char,
});
return true;
}
}
}
let sts = ed.settings().softtabstop;
let line = buf_line(ed.buffer(), cursor.row).unwrap_or_default();
let chars: Vec<char> = line.chars().collect();
let is_blank = |c: Option<&char>| matches!(c, Some(' ' | '\t'));
if sts > 0 && cursor.col > 0 && is_blank(chars.get(cursor.col - 1)) {
let tabstop = ed.settings().tabstop;
let vcol = |col: usize| hjkl_buffer::char_col_to_visual_col(&line, col, tabstop);
let want = (vcol(cursor.col) - 1) / sts * sts;
let mut start = cursor.col;
while start > 0 && is_blank(chars.get(start - 1)) && vcol(start - 1) >= want {
start -= 1;
}
if start < cursor.col {
let pad = want.saturating_sub(vcol(start));
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(cursor.row, start),
end: cursor,
kind: MotionKind::Char,
});
if pad > 0 {
ed.mutate_edit(Edit::InsertStr {
at: Position::new(cursor.row, start),
text: " ".repeat(pad),
});
}
return true;
}
}
if cursor.col > 0 {
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(cursor.row, cursor.col - 1),
end: cursor,
kind: MotionKind::Char,
});
true
} else if cursor.row > 0 {
let prev_row = cursor.row - 1;
let prev_chars = buf_line_chars(ed.buffer(), prev_row);
ed.mutate_edit(Edit::JoinLines {
row: prev_row,
count: 1,
with_space: false,
});
buf_set_cursor_rc(ed.buffer_mut(), prev_row, prev_chars);
true
} else {
false
}
}
fn replace_backspace<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>) -> bool {
use hjkl_buffer::{Edit, MotionKind, Position};
let cursor = buf_cursor_pos(ed.buffer());
if cursor.col == 0 {
return generic_backspace(ed);
}
let new_col = cursor.col - 1;
let Some(session) = vim(ed).insert_session.as_ref() else {
return generic_backspace(ed);
};
let start_row = session.start_row;
let start_col = session.start_col;
let before_rope = session.before_rope.clone();
if cursor.row != start_row || new_col < start_col {
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, new_col);
return false;
}
let before_line = hjkl_buffer::rope_line_str(&before_rope, start_row);
let orig_ch = before_line.chars().nth(new_col);
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(cursor.row, new_col),
end: cursor,
kind: MotionKind::Char,
});
if let Some(ch) = orig_ch {
ed.mutate_edit(Edit::InsertChar {
at: Position::new(cursor.row, new_col),
ch,
});
}
buf_set_cursor_rc(ed.buffer_mut(), cursor.row, new_col);
true
}
pub fn insert_delete_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
use hjkl_buffer::{Edit, MotionKind, Position};
ed.sync_buffer_content_from_textarea();
let cursor = buf_cursor_pos(ed.buffer());
let line_chars = buf_line_chars(ed.buffer(), cursor.row);
if cursor.col < line_chars {
ed.mutate_edit(Edit::DeleteRange {
start: cursor,
end: Position::new(cursor.row, cursor.col + 1),
kind: MotionKind::Char,
});
buf_set_cursor_pos(ed.buffer_mut(), cursor);
true
} else if cursor.row + 1 < buf_row_count(ed.buffer()) {
ed.mutate_edit(Edit::JoinLines {
row: cursor.row,
count: 1,
with_space: false,
});
buf_set_cursor_pos(ed.buffer_mut(), cursor);
true
} else {
false
}
}
pub fn insert_arrow_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
dir: InsertDir,
) -> bool {
ed.sync_buffer_content_from_textarea();
ed.pending_closes_mut().clear();
match dir {
InsertDir::Left => {
hjkl_engine::motions::move_left(ed.buffer_mut(), 1);
}
InsertDir::Right => {
hjkl_engine::motions::move_right_to_end(ed.buffer_mut(), 1);
}
InsertDir::Up | InsertDir::Down => insert_vertical(ed, dir == InsertDir::Down),
}
break_undo_group_in_insert(ed);
false
}
fn insert_vertical<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>, down: bool) {
let (start_row, col) = ed.cursor();
if !vim(ed).insert_want_current {
let want = ed.cursor_want(start_row, col);
ed.set_sticky_col(Some(want));
}
vim_mut(ed).insert_want_current = true;
let folds = hjkl_engine::SnapshotFoldProvider::from_buffer(ed.buffer());
let mut sticky = ed.sticky_col();
let tabstop = ed.settings().tabstop;
if down {
hjkl_engine::motions::move_down(ed.buffer_mut(), &folds, 1, &mut sticky, tabstop);
} else {
hjkl_engine::motions::move_up(ed.buffer_mut(), &folds, 1, &mut sticky, tabstop);
}
ed.set_sticky_col(sticky);
let row = ed.cursor().0;
if row != start_row {
let line = buf_line(ed.buffer(), row).unwrap_or_default();
let want = sticky.unwrap_or_default();
let col =
hjkl_buffer::visual_col_to_char_col(&line, want, tabstop).min(line.chars().count());
buf_set_cursor_rc(ed.buffer_mut(), row, col);
}
}
pub fn insert_home_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
ed.sync_buffer_content_from_textarea();
ed.pending_closes_mut().clear();
hjkl_engine::motions::move_line_start(ed.buffer_mut());
break_undo_group_in_insert(ed);
false
}
pub fn insert_end_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
ed.sync_buffer_content_from_textarea();
ed.pending_closes_mut().clear();
hjkl_engine::motions::move_line_end(ed.buffer_mut());
ed.set_sticky_col(Some(usize::MAX));
vim_mut(ed).insert_want_current = true;
break_undo_group_in_insert(ed);
false
}
pub fn insert_pageup_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
viewport_h: u16,
) -> bool {
let rows = viewport_h.saturating_sub(2).max(1) as isize;
ed.scroll_cursor_rows(-rows);
false
}
pub fn insert_pagedown_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
viewport_h: u16,
) -> bool {
let rows = viewport_h.saturating_sub(2).max(1) as isize;
ed.scroll_cursor_rows(rows);
false
}
pub fn insert_ctrl_w_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
use hjkl_buffer::{Edit, MotionKind};
ed.sync_buffer_content_from_textarea();
let cursor = buf_cursor_pos(ed.buffer());
if cursor.row == 0 && cursor.col == 0 {
return true;
}
if cursor.col == 0 {
let prev_row = cursor.row - 1;
let prev_chars = buf_line_chars(ed.buffer(), prev_row);
ed.mutate_edit(Edit::JoinLines {
row: prev_row,
count: 1,
with_space: false,
});
buf_set_cursor_rc(ed.buffer_mut(), prev_row, prev_chars);
return true;
}
let iskeyword = ed.settings().iskeyword.clone();
hjkl_engine::motions::move_word_back(
ed.buffer_mut(),
&hjkl_engine::types::NoopFoldProvider,
false,
1,
&iskeyword,
);
let word_start = buf_cursor_pos(ed.buffer());
if word_start == cursor {
return true;
}
buf_set_cursor_pos(ed.buffer_mut(), cursor);
ed.mutate_edit(Edit::DeleteRange {
start: word_start,
end: cursor,
kind: MotionKind::Char,
});
true
}
pub fn insert_ctrl_u_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
use hjkl_buffer::{Edit, MotionKind, Position};
ed.sync_buffer_content_from_textarea();
let cursor = buf_cursor_pos(ed.buffer());
if cursor.col == 0 {
return true;
}
let session_start_col = vim(ed)
.insert_session
.as_ref()
.filter(|s| s.start_row == cursor.row)
.map(|s| s.start_col);
let target = match session_start_col {
Some(start_col) if cursor.col > start_col => start_col,
_ => {
let line = buf_line(ed.buffer(), cursor.row).unwrap_or_default();
let first_non_blank = line
.chars()
.position(|c| c != ' ' && c != '\t')
.unwrap_or(0);
if cursor.col > first_non_blank {
first_non_blank
} else {
0
}
}
};
if target < cursor.col {
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(cursor.row, target),
end: cursor,
kind: MotionKind::Char,
});
}
true
}
pub fn insert_ctrl_h_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
ed.sync_buffer_content_from_textarea();
count_backspace_past_start(ed, ctrl_h_backspace)
}
fn ctrl_h_backspace<H: hjkl_engine::types::Host>(ed: &mut Editor<hjkl_buffer::View, H>) -> bool {
use hjkl_buffer::{Edit, MotionKind, Position};
let cursor = buf_cursor_pos(ed.buffer());
if cursor.col > 0 {
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(cursor.row, cursor.col - 1),
end: cursor,
kind: MotionKind::Char,
});
} else if cursor.row > 0 {
let prev_row = cursor.row - 1;
let prev_chars = buf_line_chars(ed.buffer(), prev_row);
ed.mutate_edit(Edit::JoinLines {
row: prev_row,
count: 1,
with_space: false,
});
buf_set_cursor_rc(ed.buffer_mut(), prev_row, prev_chars);
}
true
}
pub fn insert_ctrl_a_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
use hjkl_buffer::Edit;
let Some(text) = vim(ed).last_insert_text.clone() else {
return false;
};
if text.is_empty() {
return false;
}
ed.sync_buffer_content_from_textarea();
let cursor = buf_cursor_pos(ed.buffer());
ed.mutate_edit(Edit::InsertStr { at: cursor, text });
true
}
pub fn insert_ctrl_e_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
insert_char_from_adjacent_line(ed, 1)
}
pub fn insert_ctrl_y_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
insert_char_from_adjacent_line(ed, -1)
}
fn insert_char_from_adjacent_line<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
delta: isize,
) -> bool {
use hjkl_buffer::Edit;
ed.sync_buffer_content_from_textarea();
let cursor = buf_cursor_pos(ed.buffer());
let Some(other_row) = cursor.row.checked_add_signed(delta) else {
return false;
};
if other_row >= buf_row_count(ed.buffer()) {
return false;
}
let Some(ch) = buf_line(ed.buffer(), other_row).and_then(|l| l.chars().nth(cursor.col)) else {
return false;
};
ed.mutate_edit(Edit::InsertChar { at: cursor, ch });
true
}
pub fn insert_ctrl_t_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
let (row, col) = ed.cursor();
let sw = ed.settings().shiftwidth;
indent_rows(ed, row, row, 1);
ed.jump_cursor(row, col + sw);
true
}
pub fn insert_ctrl_d_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
let (row, col) = ed.cursor();
let before_len = buf_line_bytes(ed.buffer(), row);
outdent_rows(ed, row, row, 1);
let after_len = buf_line_bytes(ed.buffer(), row);
let stripped = before_len.saturating_sub(after_len);
let new_col = col.saturating_sub(stripped);
ed.jump_cursor(row, new_col);
true
}
pub fn insert_ctrl_o_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
vim_mut(ed).one_shot_normal = true;
vim_mut(ed).mode = Mode::Normal;
vim_mut(ed).current_mode = hjkl_engine::VimMode::Normal;
false
}
pub fn insert_ctrl_r_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
vim_mut(ed).insert_pending_register = true;
false
}
pub fn insert_paste_register_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
reg: char,
) -> bool {
insert_register_text(ed, reg);
true
}
pub fn leave_insert_to_normal_bridge<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
) -> bool {
ed.pending_closes_mut().clear();
if !ed.abbrevs_is_empty() {
check_and_apply_abbrev(ed, AbbrevTrigger::Esc);
}
finish_insert_session(ed);
sync_paired_tag_on_exit(ed);
vim_mut(ed).mode = Mode::Normal;
vim_mut(ed).current_mode = hjkl_engine::VimMode::Normal;
let col = ed.cursor().1;
vim_mut(ed).last_insert_pos = Some(ed.cursor());
if col > 0 {
hjkl_engine::motions::move_left(ed.buffer_mut(), 1);
}
remember_cursor_column(ed);
true
}