Skip to main content

hjkl_engine/
buf_helpers.rs

1//! Trait-surface cast helpers shared between [`crate::editor`] and the
2//! discipline crates (`hjkl-vim`) that drive it.
3//!
4//! Promoted from `editor.rs` in 0.0.42 (Patch C-δ.7) so the vim free
5//! functions can route their `ed.buffer().*` reaches through the
6//! `Cursor` / `Query` / `BufferEdit` trait surface using the same cast
7//! primitives the editor body uses. Mirrors the pattern lifted into
8//! `motions.rs` in 0.0.40.
9//!
10//! All helpers take a generic `B: <trait> + ?Sized` so they compile
11//! against the in-tree `hjkl_buffer::View` and the engine's mock
12//! buffers (used by motion / search / vim trait-routing tests). The
13//! `Pos { line: u32, col: u32 }` ⇄ `Position { row: usize, col: usize }`
14//! cast lives at the boundary so call sites stay terse.
15
16use crate::types::{Cursor, Query};
17
18/// Read the cursor as a `(row, col)` `usize` tuple — the shape every
19/// editor / vim free fn body expects. One inline cast at the trait
20/// boundary.
21#[inline]
22pub fn buf_cursor_rc<B: Cursor + ?Sized>(b: &B) -> (usize, usize) {
23    let p = Cursor::cursor(b);
24    (p.line as usize, p.col as usize)
25}
26
27/// Read the cursor row.
28#[inline]
29pub fn buf_cursor_row<B: Cursor + ?Sized>(b: &B) -> usize {
30    Cursor::cursor(b).line as usize
31}
32
33/// Read the cursor as an `hjkl_buffer::Position` — the shape the
34/// concrete-buffer call sites consumed before the trait routing.
35#[inline]
36pub fn buf_cursor_pos<B: Cursor + ?Sized>(b: &B) -> hjkl_buffer::Position {
37    let p = Cursor::cursor(b);
38    hjkl_buffer::Position::new(p.line as usize, p.col as usize)
39}
40
41/// Set the cursor from `(row, col)` `usize` coordinates.
42#[inline]
43pub fn buf_set_cursor_rc<B: Cursor + ?Sized>(b: &mut B, row: usize, col: usize) {
44    Cursor::set_cursor(
45        b,
46        crate::types::Pos {
47            line: row as u32,
48            col: col as u32,
49        },
50    );
51}
52
53/// Set the cursor from a concrete `hjkl_buffer::Position`. Routes the
54/// `ed.buffer_mut().set_cursor(Position::new(...))` call sites in
55/// `vim.rs` through the trait surface without a dedicated helper at
56/// each site.
57#[inline]
58pub fn buf_set_cursor_pos<B: Cursor + ?Sized>(b: &mut B, pos: hjkl_buffer::Position) {
59    buf_set_cursor_rc(b, pos.row, pos.col);
60}
61
62/// Number of rows.
63#[inline]
64pub fn buf_row_count<B: Query + ?Sized>(b: &B) -> usize {
65    Query::line_count(b) as usize
66}
67
68/// Return line `row` as an owned `String`, or `None` for out-of-bounds.
69/// `Query::line` returns owned data; callers that only need `&str` should
70/// deref the result with `as_deref()`.
71#[inline]
72pub fn buf_line<B: Query + ?Sized>(b: &B, row: usize) -> Option<String> {
73    let n = Query::line_count(b) as usize;
74    if row >= n {
75        return None;
76    }
77    Some(Query::line(b, row as u32))
78}
79
80/// Length (chars) of `row`. Returns 0 for out-of-bounds rows so call
81/// sites that previously did
82/// `buf.line(r).map(|l| l.chars().count()).unwrap_or(0)` collapse to
83/// one call.
84#[inline]
85pub fn buf_line_chars<B: Query + ?Sized>(b: &B, row: usize) -> usize {
86    buf_line(b, row).map(|l| l.chars().count()).unwrap_or(0)
87}
88
89/// Length (bytes) of `row`. Returns 0 for out-of-bounds rows. The
90/// byte-shape mirror of [`buf_line_chars`] — used by call sites that
91/// pre-0.0.42 inspected `buf.lines()[row].len()`.
92///
93/// Delegates to [`Query::line_bytes`] so backends with row-indexed
94/// storage skip the per-row `String` clone the default walk would do.
95#[inline]
96pub fn buf_line_bytes<B: Query + ?Sized>(b: &B, row: usize) -> usize {
97    Query::line_bytes(b, row)
98}
99
100/// Apply a [`hjkl_buffer::Edit`] and return the inverse for undo.
101///
102/// 0.0.42 (Patch C-δ.7): the `apply_edit` reach is intentionally kept
103/// against the concrete `&mut hjkl_buffer::View` rather than lifted
104/// onto a trait method. Rationale:
105///
106/// - `hjkl_buffer::Edit` is the rich buffer-side enum (~8 variants —
107///   `InsertChar`, `InsertStr`, `DeleteRange`, `JoinLines`,
108///   `SplitLines`, `Replace`, `InsertBlock`, `DeleteBlockChunks`)
109///   with ~700 LOC of `do_*` machinery in `hjkl-buffer`. Lifting it
110///   onto `BufferEdit` would require either an associated `Edit` type
111///   (forces every backend to design its own rich-edit enum just to
112///   compile) or duplicating the 8 variants on the trait surface
113///   (busts the discipline cap).
114/// - `crate::types::Edit` is a separate value type (`Range<Pos>` +
115///   `String` replacement) used by the change-log emitter; it's
116///   intentionally simpler and lossy for block / join / split ops.
117///
118/// Centralizing the reach in this free fn keeps `Editor::mutate_edit`
119/// trait-shaped at the call site (no `self.buffer.<inherent>` hop in
120/// the editor body) and gives 0.1.0 a single seam to flip when the
121/// `B: View` generic lands.
122///
123/// The 0.1.0 design will introduce
124/// `BufferEdit::apply_edit(&mut self, op: Self::Edit) -> Self::Edit`
125/// with `type Edit;` so backends pick their own edit enum. This free
126/// fn forwards there once that lands.
127#[inline]
128pub fn apply_buffer_edit(
129    buf: &mut hjkl_buffer::View,
130    edit: hjkl_buffer::Edit,
131) -> hjkl_buffer::Edit {
132    buf.apply_edit(edit)
133}