use hjkl_vim_types::{InsertReason, LastChange, Mode, Motion, Operator, RangeKind, VisualExtent};
use hjkl_engine::rope_util::{rope_line_to_str, rope_to_lines_vec};
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(crate) fn apply_visual_operator<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
op: Operator,
count: usize,
) {
let levels = count.max(1);
match vim(ed).mode {
Mode::VisualLine => {
let cursor_row = buf_cursor_pos(ed.buffer()).row;
let top = cursor_row.min(vim(ed).visual_line_anchor);
let bot = cursor_row.max(vim(ed).visual_line_anchor);
ed.set_yank_linewise(true);
match op {
Operator::Yank => {
let text = read_vim_range(ed, (top, 0), (bot, 0), RangeKind::Linewise);
if !text.is_empty() {
ed.record_yank_to_host(text.clone());
let target = vim_mut(ed).pending_register.take();
ed.record_yank(text, true, target);
}
buf_set_cursor_rc(ed.buffer_mut(), top, 0);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Delete => {
ed.push_undo();
let total = buf_row_count(ed.buffer());
let deleted_through_last = bot + 1 >= total;
cut_vim_range(ed, (top, 0), (bot, 0), RangeKind::Linewise);
let total_after = buf_row_count(ed.buffer());
let raw_target = if deleted_through_last {
top.saturating_sub(1).min(total_after.saturating_sub(1))
} else {
top.min(total_after.saturating_sub(1))
};
let target_row = if raw_target > 0
&& raw_target + 1 == total_after
&& buf_line(ed.buffer(), raw_target)
.map(|s| s.is_empty())
.unwrap_or(false)
{
raw_target - 1
} else {
raw_target
};
buf_set_cursor_rc(ed.buffer_mut(), target_row, 0);
record_visual_last_change(
ed,
op,
VisualExtent::Line {
lines: bot - top + 1,
},
);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Change => {
record_visual_last_change(
ed,
op,
VisualExtent::Line {
lines: bot - top + 1,
},
);
change_linewise_rows(ed, top, bot);
}
Operator::Uppercase
| Operator::Lowercase
| Operator::ToggleCase
| Operator::Rot13 => {
let bot = buf_cursor_pos(ed.buffer())
.row
.max(vim(ed).visual_line_anchor);
apply_case_op_to_selection(ed, op, (top, 0), (bot, 0), RangeKind::Linewise);
move_first_non_whitespace(ed);
record_visual_last_change(
ed,
op,
VisualExtent::Line {
lines: bot - top + 1,
},
);
}
Operator::Indent | Operator::Outdent => {
ed.push_undo();
let (cursor_row, _) = ed.cursor();
let bot = cursor_row.max(vim(ed).visual_line_anchor);
if op == Operator::Indent {
indent_rows(ed, top, bot, levels);
} else {
outdent_rows(ed, top, bot, levels);
}
record_visual_last_change(
ed,
op,
VisualExtent::Line {
lines: bot - top + 1,
},
);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Reflow => {
ed.push_undo();
let (cursor_row, _) = ed.cursor();
let bot = cursor_row.max(vim(ed).visual_line_anchor);
reflow_rows(ed, top, bot);
vim_mut(ed).mode = Mode::Normal;
}
Operator::ReflowKeepCursor => {
let saved = ed.cursor();
ed.push_undo();
let (cursor_row, _) = ed.cursor();
let bot = cursor_row.max(vim(ed).visual_line_anchor);
let (before, after) = reflow_rows_keep_cursor(ed, top, bot);
let (new_row, new_col) =
reflow_keep_cursor(top, saved.0, saved.1, &before, &after);
buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
vim_mut(ed).mode = Mode::Normal;
}
Operator::AutoIndent => {
ed.push_undo();
let (cursor_row, _) = ed.cursor();
let bot = cursor_row.max(vim(ed).visual_line_anchor);
auto_indent_rows(ed, top, bot);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Filter => {}
Operator::Comment => {}
Operator::Fold => unreachable!("Visual zf takes its own path"),
}
}
Mode::Visual => {
ed.set_yank_linewise(false);
let anchor = vim(ed).visual_anchor;
let cursor = ed.cursor();
let (top, bot) = order(anchor, cursor);
match op {
Operator::Yank => {
let text = read_vim_range(ed, top, bot, RangeKind::Inclusive);
if !text.is_empty() {
ed.record_yank_to_host(text.clone());
let target = vim_mut(ed).pending_register.take();
ed.record_yank(text, false, target);
}
buf_set_cursor_rc(ed.buffer_mut(), top.0, top.1);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Delete => {
ed.push_undo();
cut_vim_range(ed, top, bot, RangeKind::Inclusive);
record_visual_last_change(ed, op, charwise_extent(top, bot));
vim_mut(ed).mode = Mode::Normal;
}
Operator::Change => {
ed.push_undo();
record_visual_last_change(ed, op, charwise_extent(top, bot));
cut_vim_range(ed, top, bot, RangeKind::Inclusive);
begin_insert_noundo(ed, 1, InsertReason::AfterChange);
}
Operator::Uppercase
| Operator::Lowercase
| Operator::ToggleCase
| Operator::Rot13 => {
let anchor = vim(ed).visual_anchor;
let cursor = ed.cursor();
let (top, bot) = order(anchor, cursor);
apply_case_op_to_selection(ed, op, top, bot, RangeKind::Inclusive);
record_visual_last_change(ed, op, charwise_extent(top, bot));
}
Operator::Indent | Operator::Outdent => {
ed.push_undo();
let anchor = vim(ed).visual_anchor;
let cursor = ed.cursor();
let (top, bot) = order(anchor, cursor);
if op == Operator::Indent {
indent_rows(ed, top.0, bot.0, levels);
} else {
outdent_rows(ed, top.0, bot.0, levels);
}
record_visual_last_change(
ed,
op,
VisualExtent::Line {
lines: bot.0 - top.0 + 1,
},
);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Reflow => {
ed.push_undo();
let anchor = vim(ed).visual_anchor;
let cursor = ed.cursor();
let (top, bot) = order(anchor, cursor);
reflow_rows(ed, top.0, bot.0);
vim_mut(ed).mode = Mode::Normal;
}
Operator::ReflowKeepCursor => {
let saved = ed.cursor();
ed.push_undo();
let anchor = vim(ed).visual_anchor;
let cursor = ed.cursor();
let (top, bot) = order(anchor, cursor);
let (before, after) = reflow_rows_keep_cursor(ed, top.0, bot.0);
let (new_row, new_col) =
reflow_keep_cursor(top.0, saved.0, saved.1, &before, &after);
buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
vim_mut(ed).mode = Mode::Normal;
}
Operator::AutoIndent => {
ed.push_undo();
let anchor = vim(ed).visual_anchor;
let cursor = ed.cursor();
let (top, bot) = order(anchor, cursor);
auto_indent_rows(ed, top.0, bot.0);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Filter => {}
Operator::Comment => {}
Operator::Fold => unreachable!("Visual zf takes its own path"),
}
}
Mode::VisualBlock => apply_block_operator(ed, op, levels),
_ => {}
}
}
fn record_visual_last_change<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
op: Operator,
extent: VisualExtent,
) {
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::VisualOp {
op,
extent,
inserted: None,
});
}
}
fn charwise_extent(top: (usize, usize), bot: (usize, usize)) -> VisualExtent {
if top.0 == bot.0 {
VisualExtent::Char {
lines: 1,
width: bot.1 - top.1 + 1,
}
} else {
VisualExtent::Char {
lines: bot.0 - top.0 + 1,
width: bot.1 + 1,
}
}
}
fn block_extent(top: usize, bot: usize, left: usize, right: usize, to_eol: bool) -> VisualExtent {
VisualExtent::Block {
rows: bot - top + 1,
cols: right + 1 - left,
to_eol,
}
}
pub(crate) fn block_bounds<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
) -> (usize, usize, usize, usize) {
let (ar, ac) = vim(ed).block_anchor;
let (cr, _) = ed.cursor();
let cc = vim(ed).block_vcol;
let top = ar.min(cr);
let bot = ar.max(cr);
let left = ac.min(cc);
let right = ac.max(cc);
(top, bot, left, right)
}
pub(crate) fn update_block_vcol<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
motion: &Motion,
) {
match motion {
Motion::LineEnd => {
vim_mut(ed).block_vcol = ed.cursor().1;
vim_mut(ed).block_to_eol = true;
}
Motion::Left
| Motion::Right
| Motion::SpaceFwd
| Motion::BackspaceBack
| Motion::WordFwd
| Motion::BigWordFwd
| Motion::WordBack
| Motion::BigWordBack
| Motion::WordEnd
| Motion::BigWordEnd
| Motion::WordEndBack
| Motion::BigWordEndBack
| Motion::LineStart
| Motion::FirstNonBlank
| Motion::Find { .. }
| Motion::FindRepeat { .. }
| Motion::MatchBracket => {
vim_mut(ed).block_vcol = ed.cursor().1;
vim_mut(ed).block_to_eol = false;
}
_ => {}
}
}
pub(crate) fn apply_block_operator<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
op: Operator,
count: usize,
) {
let (top, bot, left, right) = block_bounds(ed);
let to_eol = vim(ed).block_to_eol;
let yank = block_yank(ed, top, bot, left, right, to_eol);
let block_width = if to_eol {
yank.split('\n')
.map(|s| s.chars().count())
.max()
.unwrap_or(0)
} else {
right + 1 - left
};
match op {
Operator::Yank => {
if !yank.is_empty() {
ed.record_yank_to_host(yank.clone());
let target = vim_mut(ed).pending_register.take();
ed.record_yank_block(yank, block_width, target);
}
vim_mut(ed).mode = Mode::Normal;
ed.jump_cursor(top, left);
}
Operator::Delete => {
ed.push_undo();
delete_block_contents(ed, top, bot, left, right, to_eol);
if !yank.is_empty() {
ed.record_yank_to_host(yank.clone());
let target = vim_mut(ed).pending_register.take();
ed.record_delete_block(yank, block_width, target);
}
vim_mut(ed).mode = Mode::Normal;
let line_len = buf_line_chars(ed.buffer(), top);
ed.jump_cursor(top, left.min(line_len.saturating_sub(1)));
record_visual_last_change(ed, op, block_extent(top, bot, left, right, to_eol));
}
Operator::Change => {
ed.push_undo();
record_visual_last_change(ed, op, block_extent(top, bot, left, right, to_eol));
delete_block_contents(ed, top, bot, left, right, to_eol);
if !yank.is_empty() {
ed.record_yank_to_host(yank.clone());
let target = vim_mut(ed).pending_register.take();
ed.record_delete_block(yank, block_width, target);
}
ed.jump_cursor(top, left);
begin_insert_noundo(
ed,
1,
InsertReason::BlockChange {
top,
bot,
col: left,
},
);
}
Operator::Uppercase | Operator::Lowercase | Operator::ToggleCase | Operator::Rot13 => {
ed.push_undo();
transform_block_case(ed, op, top, bot, left, right, to_eol);
vim_mut(ed).mode = Mode::Normal;
ed.jump_cursor(top, left);
record_visual_last_change(ed, op, block_extent(top, bot, left, right, to_eol));
}
Operator::Indent | Operator::Outdent => {
ed.push_undo();
if op == Operator::Indent {
indent_rows(ed, top, bot, count.max(1));
} else {
outdent_rows(ed, top, bot, count.max(1));
}
vim_mut(ed).mode = Mode::Normal;
}
Operator::Fold => unreachable!("Visual zf takes its own path"),
Operator::Reflow => {
ed.push_undo();
reflow_rows(ed, top, bot);
vim_mut(ed).mode = Mode::Normal;
}
Operator::ReflowKeepCursor => {
let saved = ed.cursor();
ed.push_undo();
let (before, after) = reflow_rows_keep_cursor(ed, top, bot);
let (new_row, new_col) = reflow_keep_cursor(top, saved.0, saved.1, &before, &after);
buf_set_cursor_rc(ed.buffer_mut(), new_row, new_col);
vim_mut(ed).mode = Mode::Normal;
}
Operator::AutoIndent => {
ed.push_undo();
auto_indent_rows(ed, top, bot);
vim_mut(ed).mode = Mode::Normal;
}
Operator::Filter => {}
Operator::Comment => {}
}
}
pub(crate) fn transform_block_case<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
op: Operator,
top: usize,
bot: usize,
left: usize,
right: usize,
to_eol: bool,
) {
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
for r in top..=bot.min(lines.len().saturating_sub(1)) {
let chars: Vec<char> = lines[r].chars().collect();
if left >= chars.len() {
continue;
}
let row_right = if to_eol { chars.len() } else { right };
let end = (row_right + 1).min(chars.len());
let head: String = chars[..left].iter().collect();
let mid: String = chars[left..end].iter().collect();
let tail: String = chars[end..].iter().collect();
let transformed = match op {
Operator::Uppercase => mid.to_uppercase(),
Operator::Lowercase => mid.to_lowercase(),
Operator::ToggleCase => toggle_case_str(&mid),
Operator::Rot13 => rot13_str(&mid),
_ => mid,
};
lines[r] = format!("{head}{transformed}{tail}");
}
let saved_yank = ed.yank().to_string();
let saved_linewise = ed.yank_linewise();
ed.restore(lines, (top, left));
ed.set_yank(saved_yank);
ed.set_yank_linewise(saved_linewise);
}
pub(crate) fn block_yank<H: hjkl_engine::types::Host>(
ed: &Editor<hjkl_buffer::View, H>,
top: usize,
bot: usize,
left: usize,
right: usize,
to_eol: bool,
) -> String {
let rope = hjkl_engine::types::Query::rope(ed.buffer());
let n = rope.len_lines();
let mut rows: Vec<String> = Vec::new();
for r in top..=bot {
if r >= n {
break;
}
let line = rope_line_to_str(&rope, r);
let chars: Vec<char> = line.chars().collect();
let row_right = if to_eol { chars.len() } else { right };
let end = (row_right + 1).min(chars.len());
if !to_eol && left > chars.len() {
rows.push(" ".repeat(right + 1 - left));
} else if left >= chars.len() {
rows.push(String::new());
} else {
rows.push(chars[left..end].iter().collect());
}
}
rows.join("\n")
}
pub(crate) fn delete_block_contents<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
top: usize,
bot: usize,
left: usize,
right: usize,
to_eol: bool,
) {
use hjkl_buffer::{Edit, MotionKind, Position};
ed.sync_buffer_content_from_textarea();
let last_row = bot.min(buf_row_count(ed.buffer()).saturating_sub(1));
if last_row < top {
return;
}
if to_eol {
for row in top..=last_row {
let row_right = buf_line_chars(ed.buffer(), row);
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(row, left),
end: Position::new(row, row_right),
kind: MotionKind::Block,
});
}
} else {
ed.mutate_edit(Edit::DeleteRange {
start: Position::new(top, left),
end: Position::new(last_row, right),
kind: MotionKind::Block,
});
}
}
pub(crate) fn block_replace<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
) {
let (top, bot, left, right) = block_bounds(ed);
let to_eol = vim(ed).block_to_eol;
if !vim(ed).replaying {
vim_mut(ed).last_change = Some(LastChange::VisualBlockReplace {
ch,
rows: bot - top + 1,
cols: right + 1 - left,
to_eol,
});
}
block_replace_bounds(ed, ch, top, bot, left, right, to_eol);
}
pub(crate) fn block_replace_bounds<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
top: usize,
bot: usize,
left: usize,
right: usize,
to_eol: bool,
) {
ed.push_undo();
ed.sync_buffer_content_from_textarea();
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
for r in top..=bot.min(lines.len().saturating_sub(1)) {
let chars: Vec<char> = lines[r].chars().collect();
if left >= chars.len() {
continue;
}
let row_right = if to_eol { chars.len() } else { right };
let end = (row_right + 1).min(chars.len());
let before: String = chars[..left].iter().collect();
let middle: String = std::iter::repeat_n(ch, end - left).collect();
let after: String = chars[end..].iter().collect();
lines[r] = format!("{before}{middle}{after}");
}
reset_textarea_lines(ed, lines);
vim_mut(ed).mode = Mode::Normal;
ed.jump_cursor(top, left);
}
pub(crate) fn replicate_block_text<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
inserted: &str,
top: usize,
bot: usize,
col: usize,
pad: bool,
to_eol: bool,
) {
use hjkl_buffer::{Edit, Position};
for r in (top + 1)..=bot {
let line_len = buf_line_chars(ed.buffer(), r);
let row_col = if to_eol { line_len } else { col };
if row_col > line_len {
if !pad {
continue;
}
let pad_str: String = std::iter::repeat_n(' ', row_col - line_len).collect();
ed.mutate_edit(Edit::InsertStr {
at: Position::new(r, line_len),
text: pad_str,
});
}
ed.mutate_edit(Edit::InsertStr {
at: Position::new(r, row_col),
text: inserted.to_string(),
});
}
}
pub(crate) fn replay_block_visual_op<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
op: Operator,
rows: usize,
cols: usize,
to_eol: bool,
inserted: Option<String>,
) {
let (top, left) = ed.cursor();
let bot = top + rows.saturating_sub(1);
let right = left + cols.saturating_sub(1);
let record_delete = |ed: &mut Editor<hjkl_buffer::View, H>| {
let yank = block_yank(ed, top, bot, left, right, to_eol);
if !yank.is_empty() {
let block_width = if to_eol {
yank.split('\n')
.map(|s| s.chars().count())
.max()
.unwrap_or(0)
} else {
right + 1 - left
};
ed.record_yank_to_host(yank.clone());
ed.record_delete_block(yank, block_width, None);
}
};
match op {
Operator::Delete => {
ed.push_undo();
record_delete(ed);
delete_block_contents(ed, top, bot, left, right, to_eol);
let line_len = buf_line_chars(ed.buffer(), top);
ed.jump_cursor(top, left.min(line_len.saturating_sub(1)));
}
Operator::Uppercase | Operator::Lowercase | Operator::ToggleCase | Operator::Rot13 => {
ed.push_undo();
transform_block_case(ed, op, top, bot, left, right, to_eol);
ed.jump_cursor(top, left);
}
Operator::Change => {
ed.push_undo();
record_delete(ed);
delete_block_contents(ed, top, bot, left, right, to_eol);
let text = inserted.unwrap_or_default();
if text.is_empty() {
ed.jump_cursor(top, left);
} else {
use hjkl_buffer::{Edit, Position};
ed.mutate_edit(Edit::InsertStr {
at: Position::new(top, left),
text: text.clone(),
});
replicate_block_text(ed, &text, top, bot, left, false, false);
let ins_chars = text.chars().count();
let line_len = buf_line_chars(ed.buffer(), top);
ed.jump_cursor(
top,
(left + ins_chars)
.saturating_sub(1)
.min(line_len.saturating_sub(1)),
);
}
}
_ => {}
}
vim_mut(ed).mode = Mode::Normal;
}
pub(crate) fn replay_block_replace<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
rows: usize,
cols: usize,
to_eol: bool,
) {
let (top, left) = ed.cursor();
let bot = top + rows.saturating_sub(1);
let right = left + cols.saturating_sub(1);
block_replace_bounds(ed, ch, top, bot, left, right, to_eol);
}
pub(crate) fn replay_block_insert<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
text: &str,
rows: usize,
cols: usize,
to_eol: bool,
append: bool,
) {
use hjkl_buffer::{Edit, Position};
let (top, left) = ed.cursor();
let bot = top + rows.saturating_sub(1);
ed.push_undo();
ed.sync_buffer_content_from_textarea();
let top_col = if append {
if to_eol {
buf_line_chars(ed.buffer(), top)
} else {
left + cols
}
} else {
left
};
let line_len = buf_line_chars(ed.buffer(), top);
if append && top_col > line_len {
let pad_str: String = std::iter::repeat_n(' ', top_col - line_len).collect();
ed.mutate_edit(Edit::InsertStr {
at: Position::new(top, line_len),
text: pad_str,
});
}
ed.mutate_edit(Edit::InsertStr {
at: Position::new(top, top_col),
text: text.to_string(),
});
replicate_block_text(ed, text, top, bot, top_col, append, to_eol);
ed.jump_cursor(top, left);
vim_mut(ed).mode = Mode::Normal;
}
pub(crate) fn visual_replace_char<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
) {
let linewise = matches!(vim(ed).mode, Mode::VisualLine);
let (top, bot) = if linewise {
let cursor_row = buf_cursor_pos(ed.buffer()).row;
let anchor_row = vim(ed).visual_line_anchor;
(
(cursor_row.min(anchor_row), 0),
(cursor_row.max(anchor_row), 0),
)
} else {
let anchor = vim(ed).visual_anchor;
let cursor = ed.cursor();
order(anchor, cursor)
};
if !vim(ed).replaying {
let extent = if linewise {
VisualExtent::Line {
lines: bot.0 - top.0 + 1,
}
} else {
charwise_extent(top, bot)
};
vim_mut(ed).last_change = Some(LastChange::VisualReplace { ch, extent });
}
replace_range_with_char(ed, linewise, top, bot, ch);
}
fn replace_range_with_char<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
linewise: bool,
top: (usize, usize),
bot: (usize, usize),
ch: char,
) {
ed.push_undo();
ed.sync_buffer_content_from_textarea();
let mut lines: Vec<String> = rope_to_lines_vec(&hjkl_engine::types::Query::rope(ed.buffer()));
let last_row = bot.0.min(lines.len().saturating_sub(1));
for (row, line) in lines.iter_mut().enumerate().take(last_row + 1).skip(top.0) {
let chars: Vec<char> = line.chars().collect();
let lo = if !linewise && row == top.0 { top.1 } else { 0 };
let hi = if !linewise && row == bot.0 {
(bot.1 + 1).min(chars.len())
} else {
chars.len()
};
if lo >= hi {
continue;
}
let before: String = chars[..lo].iter().collect();
let middle: String = std::iter::repeat_n(ch, hi - lo).collect();
let after: String = chars[hi..].iter().collect();
*line = format!("{before}{middle}{after}");
}
reset_textarea_lines(ed, lines);
vim_mut(ed).mode = Mode::Normal;
ed.jump_cursor(top.0, if linewise { 0 } else { top.1 });
}
pub(crate) fn replay_visual_replace<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
ch: char,
extent: VisualExtent,
) {
let (r, c) = ed.cursor();
match extent {
VisualExtent::Line { lines } => {
let top = (r, 0);
let bot = (r + lines.saturating_sub(1), 0);
replace_range_with_char(ed, true, top, bot, ch);
}
VisualExtent::Char { lines, width } => {
let top = (r, c);
let bot = if lines <= 1 {
(r, c + width.saturating_sub(1))
} else {
(r + lines - 1, width.saturating_sub(1))
};
replace_range_with_char(ed, false, top, bot, ch);
}
VisualExtent::Block { .. } => {}
}
}
pub(crate) fn reset_textarea_lines<H: hjkl_engine::types::Host>(
ed: &mut Editor<hjkl_buffer::View, H>,
lines: Vec<String>,
) {
let cursor = ed.cursor();
hjkl_engine::types::BufferEdit::replace_all(ed.buffer_mut(), &lines.join("\n"));
buf_set_cursor_rc(ed.buffer_mut(), cursor.0, cursor.1);
ed.mark_content_dirty();
}