hjkl_buffer/engine_types.rs
1//! Engine-level types relocated from `hjkl-engine` so that [`crate::Content`]
2//! can own per-buffer engine state (undo stack, change log, pending edits,
3//! fold ops) without requiring `hjkl-buffer` to depend on `hjkl-engine`.
4//!
5//! `hjkl-engine` re-exports these via `pub use hjkl_buffer::{...}` so all
6//! existing call sites continue to compile without change.
7
8use std::ops::Range;
9
10// ── Pos ───────────────────────────────────────────────────────────────────
11
12/// Grapheme-indexed position. `line` is zero-based row; `col` is zero-based
13/// grapheme column within that line.
14///
15/// Note that `col` counts graphemes, not bytes or chars. Motions and
16/// rendering both honor grapheme boundaries.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
18pub struct Pos {
19 pub line: u32,
20 pub col: u32,
21}
22
23impl Pos {
24 pub const ORIGIN: Pos = Pos { line: 0, col: 0 };
25
26 pub const fn new(line: u32, col: u32) -> Self {
27 Pos { line, col }
28 }
29}
30
31// ── EngineEdit ────────────────────────────────────────────────────────────
32
33/// A pending or applied edit. Multi-cursor edits fan out to `Vec<EngineEdit>`
34/// ordered in **reverse byte offset** so each entry's positions remain valid
35/// after the prior entry applies.
36///
37/// Named `EngineEdit` here to avoid collision with [`crate::Edit`] (the
38/// buffer-level edit enum). `hjkl-engine` re-exports this as
39/// `pub use hjkl_buffer::EngineEdit as Edit`.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct EngineEdit {
42 pub range: Range<Pos>,
43 pub replacement: String,
44}
45
46impl EngineEdit {
47 pub fn insert(at: Pos, text: impl Into<String>) -> Self {
48 EngineEdit {
49 range: at..at,
50 replacement: text.into(),
51 }
52 }
53
54 pub fn delete(range: Range<Pos>) -> Self {
55 EngineEdit {
56 range,
57 replacement: String::new(),
58 }
59 }
60
61 pub fn replace(range: Range<Pos>, text: impl Into<String>) -> Self {
62 EngineEdit {
63 range,
64 replacement: text.into(),
65 }
66 }
67}
68
69// ── ContentEdit ───────────────────────────────────────────────────────────
70
71/// Engine-native representation of a single buffer mutation in the
72/// shape tree-sitter's `InputEdit` consumes. Emitted by
73/// `hjkl_engine::Editor::mutate_edit` and drained by hosts via
74/// `hjkl_engine::Editor::take_content_edits` so the syntax layer can fan
75/// edits into a retained tree without the engine taking a tree-sitter
76/// dependency.
77///
78/// Positions are `(row, col_byte)` — byte offsets within the row, not
79/// char counts. Multi-row inserts/deletes set `new_end_position.0` /
80/// `old_end_position.0` to the relevant row delta. Conversion to
81/// `tree_sitter::InputEdit` is mechanical (see `apps/hjkl/src/syntax.rs`).
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct ContentEdit {
84 pub start_byte: usize,
85 pub old_end_byte: usize,
86 pub new_end_byte: usize,
87 pub start_position: (u32, u32),
88 pub old_end_position: (u32, u32),
89 pub new_end_position: (u32, u32),
90}
91
92// ── FoldOp ────────────────────────────────────────────────────────────────
93
94/// A fold operation dispatched by the engine's `z…` keystrokes, `:fold*` ex
95/// commands, and the edit-pipeline's "edits inside a fold open it"
96/// invalidation.
97///
98/// `FoldOp` is engine-canonical (per the design doc's resolved
99/// question 8.2): hosts don't invent their own fold-op enums. Each
100/// host that exposes folds embeds a `FoldOp` variant in its `Intent`
101/// enum (or simply observes the engine's pending-fold-op queue via
102/// `hjkl_engine::Editor::take_fold_ops`).
103///
104/// Row indices are zero-based and match the row coordinate space used
105/// by [`crate::Buffer`]'s fold methods.
106#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107#[non_exhaustive]
108pub enum FoldOp {
109 /// `:fold {start,end}` / `zf{motion}` / visual-mode `zf` — register a
110 /// new fold spanning `[start_row, end_row]` (inclusive). The `closed`
111 /// flag matches the underlying [`crate::Fold::closed`].
112 Add {
113 start_row: usize,
114 end_row: usize,
115 closed: bool,
116 },
117 /// `zd` — drop the fold under `row` if any.
118 RemoveAt(usize),
119 /// `zo` — open the fold under `row` if any.
120 OpenAt(usize),
121 /// `zc` — close the fold under `row` if any.
122 CloseAt(usize),
123 /// `za` — flip the fold under `row` between open / closed.
124 ToggleAt(usize),
125 /// `zR` — open every fold in the buffer.
126 OpenAll,
127 /// `zM` — close every fold in the buffer.
128 CloseAll,
129 /// `zE` — eliminate every fold.
130 ClearAll,
131 /// Edit-driven fold invalidation. Drops every fold touching the
132 /// row range `[start_row, end_row]`. Mirrors vim's "edits inside a
133 /// fold open it" behaviour. Fired by the engine's edit pipeline,
134 /// not bound to a `z…` keystroke.
135 Invalidate { start_row: usize, end_row: usize },
136}