use super::{cursor_to_first_non_blank, resolve_motion_pure};
use crate::action::{Operator, Target};
use crate::app::App;
use crate::app::eval::is_inclusive_motion;
use crate::editor::Cursor;
use crate::effect::Cmd;
use crate::mode::Mode;
pub(super) fn handle_op(app: &mut App, op: Operator, target: Target, outer_count: u32) -> Vec<Cmd> {
let mut cmds = Vec::new();
match target {
Target::LineWise => {
if matches!(op, Operator::Indent | Operator::Dedent) {
let indent = app.indent_settings();
let start_row = app.buffer.cursor.row;
let last = app.buffer.lines.len().saturating_sub(1);
let span = outer_count.max(1) as usize - 1;
let end_row = start_row.saturating_add(span).min(last);
for r in start_row..=end_row {
if matches!(op, Operator::Indent) {
app.buffer.indent_line(r, indent);
} else {
app.buffer.dedent_line(r, indent);
}
}
app.buffer.cursor.row = start_row;
cursor_to_first_non_blank(&mut app.buffer);
} else if matches!(op, Operator::Comment | Operator::BlockComment) {
let rows = comment_target_rows(app, outer_count);
apply_comment_op(app, op, &rows, None, &mut cmds);
} else {
for _ in 0..outer_count {
match op {
Operator::Delete => app.buffer.delete_line(),
Operator::Yank => {
app.buffer.yank_line();
cmds.push(Cmd::SyncYank);
cmds.push(Cmd::ToastInfo("yanked".into()));
}
Operator::Change => {
cmds.push(Cmd::ToastError("change not implemented yet".into()));
}
Operator::Indent
| Operator::Dedent
| Operator::Comment
| Operator::BlockComment => unreachable!(),
}
}
}
}
Target::Motion(m) => {
let (resolved, last_find_update) = resolve_motion_pure(m.motion, app.last_find);
if let Some(lf) = last_find_update {
cmds.push(Cmd::SetLastFind(lf));
}
let Some(resolved) = resolved else {
cmds.push(Cmd::ToastError("no previous find".into()));
return cmds;
};
let inclusive = is_inclusive_motion(resolved);
for _ in 0..outer_count {
let start = app.buffer.cursor;
let target = app.buffer.motion_target(start, resolved, m.count);
let end = if inclusive {
app.buffer.advance_one(target)
} else {
target
};
apply_op_range(app, op, start, end, &mut cmds);
}
}
Target::SearchMatch { reverse } => {
let forward = app.search.last_forward ^ reverse;
for _ in 0..outer_count {
let Some((start, end_incl)) = app.search.find_match_range(&app.buffer, forward)
else {
cmds.push(Cmd::ToastError("pattern not found".into()));
break;
};
let end = app.buffer.advance_one(end_incl);
apply_op_range(app, op, start, end, &mut cmds);
}
}
Target::TextObject { scope, object } => {
for _ in 0..outer_count {
match app.buffer.text_object_range(scope, object) {
Some((start, end)) => apply_op_range(app, op, start, end, &mut cmds),
None => {
cmds.push(Cmd::ToastError("no matching object".into()));
break;
}
}
}
}
}
cmds
}
fn apply_op_range(app: &mut App, op: Operator, start: Cursor, end: Cursor, cmds: &mut Vec<Cmd>) {
match op {
Operator::Delete => app.buffer.delete_range(start, end),
Operator::Yank => {
app.buffer.yank_range(start, end);
cmds.push(Cmd::SyncYank);
cmds.push(Cmd::ToastInfo("yanked".into()));
}
Operator::Change => {
app.buffer.delete_range(start, end);
cmds.push(Cmd::EnterMode(Mode::Insert));
}
Operator::Indent | Operator::Dedent => {
let indent = app.indent_settings();
let (lo, hi) = if (start.row, start.col) <= (end.row, end.col) {
(start.row, end.row)
} else {
(end.row, start.row)
};
for r in lo..=hi {
if matches!(op, Operator::Indent) {
app.buffer.indent_line(r, indent);
} else {
app.buffer.dedent_line(r, indent);
}
}
app.buffer.cursor.row = lo;
cursor_to_first_non_blank(&mut app.buffer);
}
Operator::Comment => {
let (lo, hi) = order_range(start, end);
let rows: Vec<usize> = (lo.row..=hi.row).collect();
apply_comment_op(app, op, &rows, None, cmds);
}
Operator::BlockComment => {
let (lo, hi) = order_range(start, end);
let rows: Vec<usize> = (lo.row..=hi.row).collect();
apply_comment_op(app, op, &rows, Some((lo, hi)), cmds);
}
}
}
fn order_range(a: Cursor, b: Cursor) -> (Cursor, Cursor) {
if (a.row, a.col) <= (b.row, b.col) {
(a, b)
} else {
(b, a)
}
}
fn apply_comment_op(
app: &mut App,
op: Operator,
rows: &[usize],
range: Option<(Cursor, Cursor)>,
cmds: &mut Vec<Cmd>,
) {
let ok = match op {
Operator::Comment => app.apply_line_comment(rows),
Operator::BlockComment => app.apply_block_comment(rows, range),
_ => unreachable!(),
};
if !ok {
let msg = match op {
Operator::Comment => "no comment token for this buffer",
Operator::BlockComment => "no block- or line-comment tokens for this buffer",
_ => unreachable!(),
};
cmds.push(Cmd::ToastError(msg.into()));
}
}
fn comment_target_rows(app: &App, count: u32) -> Vec<usize> {
if app.buffer.extra_cursors.is_empty() {
let start = app.buffer.cursor.row;
let max = app.buffer.lines.len();
(0..count as usize)
.map(|i| start + i)
.take_while(|&r| r < max)
.collect()
} else {
std::iter::once(app.buffer.cursor.row)
.chain(app.buffer.extra_cursors.iter().map(|c| c.row))
.collect()
}
}