oxicode_hashline/types.rs
1//! Pure data types shared across the hashline parser, applier, and patcher.
2//! No filesystem, agent runtime, or schema library references — keep it pure.
3
4/// A line-number anchor (1-indexed).
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub struct Anchor {
7 /// The 1-indexed source line this anchor points at.
8 pub line: u32,
9}
10
11/// Where an `insert` edit should land relative to existing content.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Cursor {
14 /// Insert at the beginning of the file (before line 1).
15 Bof,
16 /// Insert at the end of the file (after the last line).
17 Eof,
18 /// Insert immediately before the given anchor line.
19 BeforeAnchor(Anchor),
20 /// Insert immediately after the given anchor line.
21 AfterAnchor(Anchor),
22}
23
24/// Insert mode for replacement lowering (SWAP lowers to Insert::Replacement).
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum InsertMode {
27 /// The insert replaces existing content at the cursor (SWAP lowering).
28 Replacement,
29}
30
31/// Block operations mode (future: block-ops feature).
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum BlockMode {
34 /// Insert content after the resolved block.
35 InsertAfter,
36}
37
38/// A single low-level edit produced by the parser and consumed by the applier.
39#[derive(Debug, Clone)]
40pub enum Edit {
41 /// Insert new text at a cursor position.
42 Insert {
43 /// Where the inserted text lands relative to existing content.
44 cursor: Cursor,
45 /// The literal text to insert.
46 text: String,
47 /// 1-indexed line number of this edit in the patch text (for diagnostics).
48 line_num: u32,
49 /// Position of this edit in the patch's edit list (for stable ordering).
50 index: usize,
51 /// Lowering mode for this insert, if any (e.g. SWAP-to-replacement).
52 mode: Option<InsertMode>,
53 },
54 /// Delete the line at an anchor.
55 Delete {
56 /// The 1-indexed line this delete targets.
57 anchor: Anchor,
58 /// 1-indexed line number of this edit in the patch text (for diagnostics).
59 line_num: u32,
60 /// Position of this edit in the patch's edit list (for stable ordering).
61 index: usize,
62 /// Optional content asserted present at the deleted line (reserved for future guards).
63 old_assertion: Option<String>,
64 },
65}
66
67impl Edit {
68 /// The 1-indexed line number in the patch text where this edit appears
69 /// (for diagnostics).
70 pub fn line_num(&self) -> u32 {
71 match self {
72 Edit::Insert { line_num, .. } | Edit::Delete { line_num, .. } => *line_num,
73 }
74 }
75
76 /// The anchor line this edit targets (for session-chain recovery guards).
77 pub fn anchor_line(&self) -> u32 {
78 match self {
79 Edit::Insert { cursor, .. } => match cursor {
80 Cursor::BeforeAnchor(a) | Cursor::AfterAnchor(a) => a.line,
81 Cursor::Bof => 0,
82 Cursor::Eof => u32::MAX,
83 },
84 Edit::Delete { anchor, .. } => anchor.line,
85 }
86 }
87
88 /// Index in the patch's edit list (for stable ordering).
89 pub fn index(&self) -> usize {
90 match self {
91 Edit::Insert { index, .. } | Edit::Delete { index, .. } => *index,
92 }
93 }
94}
95
96/// Result of applying a parsed set of edits to a text body.
97#[derive(Debug, Clone, Default)]
98pub struct ApplyResult {
99 /// The resulting text after all edits are applied.
100 pub text: String,
101 /// 1-indexed line number of the first changed line, if any.
102 pub first_changed_line: Option<u32>,
103 /// Warning messages generated during apply (boundary repair, landing
104 /// correction, etc.).
105 pub warnings: Vec<String>,
106}
107
108/// A parsed `start.=end` line range (1-indexed, inclusive).
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct ParsedRange {
111 /// First line of the range (inclusive).
112 pub start: u32,
113 /// Last line of the range (inclusive).
114 pub end: u32,
115}
116
117/// Optional hints for [`crate::parser::split_patch_input`].
118#[derive(Debug, Clone, Default)]
119pub struct SplitOptions {
120 /// Root directory for resolving relative paths.
121 pub root: Option<std::path::PathBuf>,
122}
123
124/// Result of [`crate::diff_preview::build_compact_diff_preview`].
125#[derive(Debug, Clone, Default)]
126pub struct CompactDiffPreview {
127 /// Textual preview lines.
128 pub lines: Vec<String>,
129}
130
131/// Optional knobs for compact diff preview.
132#[derive(Debug, Clone)]
133pub struct CompactDiffOptions {
134 /// Maximum unchanged lines kept as context between hunks.
135 pub max_unchanged_context: usize,
136}
137
138impl Default for CompactDiffOptions {
139 fn default() -> Self {
140 Self {
141 max_unchanged_context: 3,
142 }
143 }
144}
145
146/// Resolved 1-indexed inclusive line span of a `replace_block N:` target.
147/// (block-ops feature — future)
148#[derive(Debug, Clone, Copy)]
149pub struct BlockSpan {
150 /// First line of the resolved span (inclusive).
151 pub start: u32,
152 /// Last line of the resolved span (inclusive).
153 pub end: u32,
154}
155
156/// One block anchor resolution (block-ops feature — future).
157#[derive(Debug, Clone)]
158pub struct BlockResolution {
159 /// 1-indexed line that named the block being resolved.
160 pub anchor_line: u32,
161 /// First line of the resolved block (inclusive).
162 pub start: u32,
163 /// Last line of the resolved block (inclusive).
164 pub end: u32,
165 /// The operation to perform on the resolved block.
166 pub op: BlockOp,
167}
168
169/// Kind of block operation to perform (block-ops feature — future).
170#[derive(Debug, Clone, Copy)]
171pub enum BlockOp {
172 /// Replace the resolved block's content.
173 Replace,
174 /// Delete the resolved block.
175 Delete,
176 /// Insert new content after the resolved block.
177 InsertAfter,
178}
179
180/// Request handed to a block resolver (block-ops feature — future).
181#[derive(Debug, Clone)]
182pub struct BlockResolverRequest {
183 /// The source text the resolver searches within.
184 pub text: String,
185 /// 1-indexed line naming the block to resolve.
186 pub anchor_line: u32,
187}
188
189/// Resolves a block anchor to a line span (block-ops feature — future).
190pub type BlockResolver =
191 std::sync::Arc<dyn Fn(&BlockResolverRequest) -> Option<BlockSpan> + Send + Sync>;