hjkl 0.16.0

Vim-modal terminal editor: standalone TUI built on the hjkl engine.
//! Application-level actions dispatched from chord bindings.
//!
//! Each variant corresponds to one app-handled binding currently
//! in `event_loop.rs`. The enum is used as the action type for
//! `Keymap<AppAction>`.

/// Every action the app can perform in response to a chord binding.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AppAction {
    // ── File / buffer pickers ──────────────────────────────────────────
    OpenFilePicker,
    OpenBufferPicker,
    OpenGrepPicker,

    // ── Git commands ───────────────────────────────────────────────────
    GitStatus,
    GitLog,
    GitBranch,
    GitFileHistory,
    GitStashes,
    GitTags,
    GitRemotes,

    // ── LSP ───────────────────────────────────────────────────────────
    ShowDiagAtCursor,
    LspCodeActions,
    LspRename,
    LspGotoDef,
    LspGotoDecl,
    LspGotoRef,
    LspGotoImpl,
    LspGotoTypeDef,

    // ── Tab navigation ─────────────────────────────────────────────────
    Tabnext,
    Tabprev,

    // ── Buffer navigation ──────────────────────────────────────────────
    BufferNext,
    BufferPrev,

    // ── Diagnostic navigation ─────────────────────────────────────────
    DiagNext,
    DiagPrev,
    DiagNextError,
    DiagPrevError,

    // ── Window focus ───────────────────────────────────────────────────
    FocusLeft,
    FocusBelow,
    FocusAbove,
    FocusRight,
    FocusNext,
    FocusPrev,

    // ── Window management ─────────────────────────────────────────────
    CloseFocusedWindow,
    OnlyFocusedWindow,
    SwapWithSibling,
    MoveWindowToNewTab,
    NewSplit,

    // ── Window resize ─────────────────────────────────────────────────
    /// Grow height by count (negative = shrink).
    ResizeHeight(i32),
    /// Grow width by count (negative = shrink).
    ResizeWidth(i32),
    EqualizeLayout,
    MaximizeHeight,
    MaximizeWidth,

    // ── App lifecycle ─────────────────────────────────────────────────
    QuitOrClose,

    // ── Pending-state chords (hjkl-vim reducer) ───────────────────────
    /// `r<x>` — begin Replace pending state with the given count.
    /// The app stores `Some(hjkl_vim::PendingState::Replace { count })` and
    /// routes the next key through `hjkl_vim::step` instead of the trie.
    BeginPendingReplace {
        count: u32,
    },
    /// Begin a Find pending state for `f<x>` / `F<x>` / `t<x>` / `T<x>`.
    /// `forward` = true for f/t, false for F/T.
    /// `till` = true for t/T (stop one char before target), false for f/F.
    BeginPendingFind {
        forward: bool,
        till: bool,
        count: u32,
    },
    /// Begin a `g<x>` pending state. The app stores
    /// `Some(hjkl_vim::PendingState::AfterG { count })` and routes the next key
    /// through `hjkl_vim::step` instead of the trie or engine FSM.
    BeginPendingAfterG {
        count: u32,
    },
    /// Begin a `z<x>` pending state. The app stores
    /// `Some(hjkl_vim::PendingState::AfterZ { count })` and routes the next key
    /// through `hjkl_vim::step` instead of the trie or engine FSM.
    BeginPendingAfterZ {
        count: u32,
    },
    /// Begin an op-pending state for `d` / `y` / `c` / `>` / `<` from Normal
    /// mode. The app stores `Some(hjkl_vim::PendingState::AfterOp { op, count1,
    /// inner_count: 0 })` and routes the next key through `hjkl_vim::step`.
    /// `count1` is the prefix count buffered before the operator key.
    BeginPendingAfterOp {
        op: hjkl_vim::OperatorKind,
        count1: u32,
    },
    /// Begin a `"<reg>` register-prefix chord in Normal mode. The app stores
    /// `Some(hjkl_vim::PendingState::SelectRegister)` and routes the next key
    /// through `hjkl_vim::step`. The register char (no fields here — captured
    /// by the second key) is passed to `Editor::set_pending_register`.
    BeginPendingSelectRegister,
    /// Begin a `m<x>` mark-set chord in Normal mode. The app stores
    /// `Some(hjkl_vim::PendingState::SetMark)` and routes the next key
    /// through `hjkl_vim::step`. The mark char is captured by the second key
    /// and passed to `Editor::set_mark_at_cursor`.
    BeginPendingSetMark,
    /// Begin a `'<x>` mark-goto-line chord in Normal mode. The app stores
    /// `Some(hjkl_vim::PendingState::GotoMarkLine)` and routes the next key
    /// through `hjkl_vim::step`. The mark char is captured by the second key
    /// and passed to `Editor::goto_mark_line`.
    BeginPendingGotoMarkLine,
    /// Begin a `` `<x> `` mark-goto-char chord in Normal and Visual modes.
    /// The app stores `Some(hjkl_vim::PendingState::GotoMarkChar)` and routes
    /// the next key through `hjkl_vim::step`. The mark char is captured by the
    /// second key and passed to `Editor::goto_mark_char`.
    BeginPendingGotoMarkChar,
    /// `q` pressed in Normal mode. Branches on `Editor::is_recording_macro()`:
    ///   - If recording: calls `Editor::stop_macro_record()` (bare `q` stop).
    ///   - If not recording: sets `PendingState::RecordMacroTarget` and waits
    ///     for the register char.
    ///
    /// The `count` field is accepted for interface consistency with other pending
    /// actions but is not consumed (macros don't use a count prefix on `q`).
    QChord {
        count: u32,
    },
    /// `@` pressed in Normal mode. Sets `PendingState::PlayMacroTarget { count }`
    /// and waits for the register char. The resolved count (pending_count prefix
    /// or action default) is stored in the state and passed to `PlayMacro` on
    /// the next key.
    BeginPendingPlayMacro {
        count: u32,
    },

    /// `.` pressed in Normal mode — dot-repeat. Replays the last buffered
    /// change `count` times via `Editor::replay_last_change`. Phase 5c of
    /// kryptic-sh/hjkl#71: chord moves from engine FSM `.` arm into the app
    /// keymap. Engine FSM `.` arm stays for macro-replay defensive coverage;
    /// `LastChange` storage stays on engine.
    DotRepeat {
        count: u32,
    },

    // ── Cursor motions (Phase 3a — hjkl-vim keymap path) ──────────────
    /// Engine-level cursor motion executed via the hjkl-vim keymap path.
    ///
    /// Bypasses the engine FSM — the host calls `Editor::apply_motion(kind,
    /// count)` directly. `count` is the action-default multiplier; the
    /// dispatch arm combines it with any buffered `pending_count` prefix.
    ///
    /// The engine FSM arms for the same keys are kept intact for macro-replay
    /// defensive coverage (macros re-feed raw keys through the FSM). This
    /// variant becomes authoritative for user input.
    Motion {
        kind: hjkl_vim::MotionKind,
        count: u32,
    },

    // ── Visual-mode inline operators (Phase 4e — hjkl#70) ─────────────
    /// Visual-mode operator fired inline against the current selection.
    ///
    /// When dispatched the active visual selection range is resolved from
    /// the engine, a range-mutation primitive is called directly, and the
    /// engine exits visual mode. Bound for `d` / `c` / `y` / `>` / `<` in
    /// `HjklMode::Visual` (which covers Visual, VisualLine, and VisualBlock
    /// per the mode-collapse in `keymap.rs`).
    ///
    /// VisualBlock ops fall back to the engine FSM because block-shape
    /// range-mutation requires `apply_block_operator`, which is not exposed
    /// as a public primitive. That gap is tracked in the Phase 4e notes.
    VisualOp {
        op: hjkl_vim::OperatorKind,
        count: u32,
    },

    // ── Phase 6.4: insert-mode entry ──────────────────────────────────────
    /// `i` — enter Insert mode at the current cursor position.
    /// `count` is stored in the insert session for dot-repeat replay.
    EnterInsertI {
        count: u32,
    },
    /// `I` — move to first non-blank then enter Insert mode.
    /// `count` is stored for dot-repeat.
    EnterInsertShiftI {
        count: u32,
    },
    /// `a` — advance cursor one cell then enter Insert mode (append).
    /// `count` is stored for dot-repeat.
    EnterInsertA {
        count: u32,
    },
    /// `A` — move to end-of-line then enter Insert mode (append at end).
    /// `count` is stored for dot-repeat.
    EnterInsertShiftA {
        count: u32,
    },
    /// `o` — open new line below and enter Insert mode.
    /// `count` is stored for dot-repeat.
    EnterInsertO {
        count: u32,
    },
    /// `O` — open new line above and enter Insert mode.
    /// `count` is stored for dot-repeat.
    EnterInsertShiftO {
        count: u32,
    },
    /// `R` — enter Replace mode (overstrike). `count` is for replay.
    EnterReplace {
        count: u32,
    },

    // ── Phase 6.4: char / line mutation ops ───────────────────────────────
    /// `x` — delete `count` chars forward from the cursor.
    DeleteCharForward {
        count: u32,
    },
    /// `X` — delete `count` chars backward from the cursor.
    DeleteCharBackward {
        count: u32,
    },
    /// `s` — substitute `count` chars (delete then Insert).
    SubstituteChar {
        count: u32,
    },
    /// `S` — substitute the current line (equivalent to `cc`).
    SubstituteLine {
        count: u32,
    },
    /// `D` — delete from cursor to end-of-line.
    DeleteToEol,
    /// `C` — change from cursor to end-of-line (delete to EOL + Insert).
    ChangeToEol,
    /// `Y` — yank from cursor to end-of-line. `count` multiplies the motion.
    YankToEol {
        count: u32,
    },
    /// `J` — join `count` lines (default 2).
    JoinLine {
        count: u32,
    },
    /// `~` — toggle case of `count` chars from the cursor.
    ToggleCase {
        count: u32,
    },
    /// `p` — paste unnamed register (or `"r`) after the cursor.
    PasteAfter {
        count: u32,
    },
    /// `P` — paste unnamed register (or `"r`) before the cursor.
    PasteBefore {
        count: u32,
    },

    // ── Phase 6.4: undo / redo ────────────────────────────────────────────
    /// `u` — undo one step in the undo history.
    Undo,
    /// `<C-r>` in Normal mode — redo one step in the redo history.
    Redo,

    // ── Phase 6.4: jumplist ───────────────────────────────────────────────
    /// `<C-o>` — jump back `count` entries in the jumplist.
    JumpBack {
        count: u32,
    },
    /// `<C-i>` / `Tab` — jump forward `count` entries in the jumplist.
    JumpForward {
        count: u32,
    },

    // ── Phase 6.4: scroll ops ─────────────────────────────────────────────
    /// `<C-f>` / `<C-b>` — scroll cursor by one full viewport height.
    /// `dir = Down` for `<C-f>`, `Up` for `<C-b>`.
    ///
    /// Note: Phase 3g already bound `<C-f>`/`<C-b>` as `Motion` variants
    /// (FullPageDown / FullPageUp). These variants provide the Phase 6.4
    /// primitive path for future re-binding once Phase 6.8 deletes the FSM
    /// fallthrough.
    #[allow(dead_code)]
    ScrollFullPage {
        dir: hjkl_engine::ScrollDir,
        count: u32,
    },
    /// `<C-d>` / `<C-u>` — scroll cursor by half the viewport height.
    /// `dir = Down` for `<C-d>`, `Up` for `<C-u>`.
    ///
    /// Note: Phase 3g already bound `<C-d>`/`<C-u>` as `Motion` variants
    /// (HalfPageDown / HalfPageUp). These variants provide the Phase 6.4
    /// primitive path for future re-binding once Phase 6.8 deletes the FSM
    /// fallthrough.
    #[allow(dead_code)]
    ScrollHalfPage {
        dir: hjkl_engine::ScrollDir,
        count: u32,
    },
    /// `<C-e>` / `<C-y>` — scroll viewport `count` lines without moving cursor.
    /// `dir = Down` for `<C-e>`, `Up` for `<C-y>`.
    ScrollLine {
        dir: hjkl_engine::ScrollDir,
        count: u32,
    },

    // ── Phase 6.4: search repeat ──────────────────────────────────────────
    /// `n` / `N` — repeat the last search. `forward = true` keeps direction.
    SearchRepeat {
        forward: bool,
        count: u32,
    },
    /// `*` / `#` / `g*` / `g#` — search for word under cursor.
    /// `forward` chooses direction; `whole_word` wraps with `\b` anchors.
    WordSearch {
        forward: bool,
        whole_word: bool,
        count: u32,
    },

    // ── Phase 6.4: visual entry / exit ────────────────────────────────────
    /// `v` from Normal — enter charwise Visual mode.
    EnterVisualChar,
    /// `V` from Normal — enter linewise Visual mode.
    EnterVisualLine,
    /// `<C-v>` from Normal — enter Visual-block mode.
    EnterVisualBlock,
    /// `gv` — restore the last visual selection.
    ReenterLastVisual,
    /// `o` in Visual / VisualLine / VisualBlock — toggle cursor/anchor.
    VisualToggleAnchor,

    // ── User runtime maps (`:map` / `:noremap` family) ─────────────────
    /// User-defined `:map` / `:noremap` runtime mapping. When the trie matches
    /// the LHS, the dispatcher unrolls `keys` according to `recursive`:
    ///   - `recursive = true`  → feed each key back through `dispatch_keymap_in_mode`
    ///     (the RHS can trigger further chord bindings).
    ///   - `recursive = false` → replay each key straight to the engine.
    Replay {
        keys: Vec<hjkl_keymap::KeyEvent>,
        recursive: bool,
    },
}