mod direct;
mod motion;
mod operator;
mod surround;
use crate::action::{Ctx, Expr, LastFind, MotionKind, Operator};
use crate::app::App;
use crate::app::eval::word_under_cursor;
use crate::effect::Cmd;
use crate::mode::Mode;
impl App {
pub(super) fn handle_expr(&mut self, expr: Expr, ctx: Ctx) -> Vec<Cmd> {
if expr_modifies_buffer(&expr) {
ed_op!(self, snapshot());
}
self.handle_expr_no_snapshot(expr, ctx)
}
pub(super) fn handle_expr_no_snapshot(&mut self, expr: Expr, ctx: Ctx) -> Vec<Cmd> {
match expr {
Expr::Direct { kind, count } => direct::handle_direct(self, kind, count, ctx),
Expr::Motion(m) => motion::handle_motion(self, m),
Expr::Op {
op,
target,
outer_count,
} => operator::handle_op(self, op, target, outer_count),
Expr::SurroundAdd { target, ch } => surround::handle_add(self, target, ch),
Expr::SurroundChange { from, to } => surround::handle_change(self, from, to),
Expr::SurroundDelete { ch } => surround::handle_delete(self, ch),
}
}
pub(in crate::app) fn goto_line_n_pure(&mut self, n: usize) {
let last = self.active_doc().lines.len().saturating_sub(1);
self.editor.cursor.row = n.saturating_sub(1).min(last);
self.editor.cursor.col = 0;
ed_op_ref!(self, clamp_col(false));
}
}
pub(in crate::app) fn resolve_motion_pure(
motion: MotionKind,
last_find: Option<LastFind>,
) -> (Option<MotionKind>, Option<LastFind>) {
use MotionKind as M;
match motion {
M::RepeatFind { reverse } => {
let Some(lf) = last_find else {
return (None, None);
};
let forward = if reverse { !lf.forward } else { lf.forward };
(
Some(M::FindChar {
ch: lf.ch,
forward,
till: lf.till,
}),
None,
)
}
M::FindChar { ch, forward, till } => (Some(motion), Some(LastFind { ch, forward, till })),
_ => (Some(motion), None),
}
}
fn push_word_search(app: &App, cmds: &mut Vec<Cmd>, forward: bool, jump: bool) {
match word_under_cursor(&app.editor, app.active_doc()) {
Some(word) => {
cmds.push(Cmd::SetSearch {
pattern: word,
forward,
});
if jump {
cmds.push(Cmd::JumpSearch { reverse: false });
}
}
None => cmds.push(Cmd::ToastError("no word under cursor".into())),
}
}
fn cursor_to_first_non_blank(ed: &mut crate::editor::Editor, buf: &crate::editor::Buffer) {
let line = ed.current_line(buf);
let col = line.chars().position(|c| !c.is_whitespace()).unwrap_or(0);
ed.cursor.col = col;
}
pub(super) fn expr_modifies_buffer(expr: &Expr) -> bool {
use crate::action::DirectKind as D;
match expr {
Expr::Direct { kind, .. } => matches!(
kind,
D::OpenLineBelow
| D::OpenLineAbove
| D::Paste
| D::DeleteCharUnderCursor
| D::EnterMode(Mode::Insert)
| D::AppendAfterCursor
| D::AppendAtLineEnd
| D::InsertAtLineStart
| D::ChangeToEol
| D::DeleteToEol
| D::JoinLines
| D::ToggleCase
| D::SubstituteChar
| D::SubstituteLine
| D::ReplaceChar { .. }
| D::Substitute
),
Expr::Motion(_) => false,
Expr::Op { op, .. } => !matches!(op, Operator::Yank),
Expr::SurroundAdd { .. } | Expr::SurroundChange { .. } | Expr::SurroundDelete { .. } => {
true
}
}
}