Skip to main content

twig/
ffi.rs

1use std::os::raw::{c_char, c_int};
2
3/// The C ABI contract version this binding is written against; see
4/// `twig_abi_version`. Must match the value baked into the linked library
5/// (asserted at runtime by the `abi_version_matches` test in `lib.rs`).
6pub const TWIG_ABI_VERSION: u32 = 3;
7
8// Freeze the canonical 64-bit layout of every `#[repr(C)]` mirror below so it
9// can never silently drift from the Zig `extern struct` it shadows. These are
10// the same offsets asserted on the Zig side in `src/c_abi.zig`; a change on
11// either side that isn't matched on the other fails to compile. Gated on 64-bit
12// (the offsets are the LP64/LLP64 layout); 32-bit targets pack tighter but the
13// C ABI still keeps both languages in agreement.
14#[cfg(target_pointer_width = "64")]
15const _: () = {
16    use std::mem::offset_of;
17    use std::mem::size_of;
18
19    assert!(size_of::<TwigSpan>() == 16);
20    assert!(offset_of!(TwigSpan, start) == 0);
21    assert!(offset_of!(TwigSpan, end) == 8);
22
23    assert!(size_of::<TwigQueryMatch>() == 56);
24    assert!(offset_of!(TwigQueryMatch, node_id) == 0);
25    assert!(offset_of!(TwigQueryMatch, span) == 8);
26    assert!(offset_of!(TwigQueryMatch, content_span) == 24);
27    assert!(offset_of!(TwigQueryMatch, has_content_span) == 40);
28    assert!(offset_of!(TwigQueryMatch, kind) == 48);
29
30    assert!(size_of::<TwigChange>() == 32);
31    assert!(offset_of!(TwigChange, old_span) == 0);
32    assert!(offset_of!(TwigChange, new_span) == 16);
33
34    assert!(size_of::<TwigFlatNode>() == 136);
35    assert!(offset_of!(TwigFlatNode, id) == 0);
36    assert!(offset_of!(TwigFlatNode, parent) == 4);
37    assert!(offset_of!(TwigFlatNode, first_child) == 8);
38    assert!(offset_of!(TwigFlatNode, next_sibling) == 12);
39    assert!(offset_of!(TwigFlatNode, span) == 16);
40    assert!(offset_of!(TwigFlatNode, content_span) == 32);
41    assert!(offset_of!(TwigFlatNode, has_content_span) == 48);
42    assert!(offset_of!(TwigFlatNode, level) == 52);
43    assert!(offset_of!(TwigFlatNode, kind) == 56);
44    assert!(offset_of!(TwigFlatNode, text_ptr) == 64);
45    assert!(offset_of!(TwigFlatNode, text_len) == 72);
46    assert!(offset_of!(TwigFlatNode, destination_ptr) == 80);
47    assert!(offset_of!(TwigFlatNode, destination_len) == 88);
48    assert!(offset_of!(TwigFlatNode, head) == 96);
49    assert!(offset_of!(TwigFlatNode, alignment) == 100);
50    assert!(offset_of!(TwigFlatNode, name_ptr) == 104);
51    assert!(offset_of!(TwigFlatNode, name_len) == 112);
52    assert!(offset_of!(TwigFlatNode, attrs_ptr) == 120);
53    assert!(offset_of!(TwigFlatNode, attrs_len) == 128);
54
55    assert!(size_of::<TwigKeyVal>() == 32);
56    assert!(offset_of!(TwigKeyVal, key) == 0);
57    assert!(offset_of!(TwigKeyVal, key_len) == 8);
58    assert!(offset_of!(TwigKeyVal, value) == 16);
59    assert!(offset_of!(TwigKeyVal, value_len) == 24);
60};
61
62#[repr(transparent)]
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
64pub struct TwigStatus(pub c_int);
65
66#[allow(dead_code)]
67impl TwigStatus {
68    pub const OK: c_int = 0;
69    pub const INVALID_ARGUMENT: c_int = 1;
70    pub const PARSE_ERROR: c_int = 2;
71    pub const OUT_OF_MEMORY: c_int = 3;
72    pub const UNSUPPORTED_FORMAT: c_int = 4;
73    pub const NOT_FOUND: c_int = 5;
74    pub const AMBIGUOUS: c_int = 6;
75    pub const NOT_EDITABLE: c_int = 7;
76    pub const EDIT_CONFLICT: c_int = 8;
77    pub const UNSAFE_METADATA: c_int = 9;
78    pub const INTERNAL_ERROR: c_int = 255;
79}
80
81#[repr(C)]
82#[derive(Clone, Copy, Debug, Eq, PartialEq)]
83pub enum TwigFormat {
84    Djot = 1,
85    Markdown = 2,
86    Xml = 3,
87    Html = 4,
88}
89
90/// Markdown extension flags for the `md_flags` bitmask of `twig_parse_ext` and
91/// `twig_editor_create_ext`.
92pub const TWIG_MD_DIRECTIVES: u32 = 1 << 0;
93pub const TWIG_MD_MATH: u32 = 1 << 1;
94pub const TWIG_MD_HTML_ELEMENTS: u32 = 1 << 2;
95
96pub enum TwigDocument {}
97
98pub enum TwigEditor {}
99
100pub enum TwigBuilder {}
101
102/// C ABI mirror of Zig's `TwigKeyVal` — one `(key, value)` attribute pair. Used
103/// on the read path ([`TwigFlatNode::attrs_ptr`], borrowed) and the write path
104/// (`twig_builder_set_attrs`, copied). A NULL `value` is a bare attribute.
105#[repr(C)]
106#[derive(Clone, Copy, Debug)]
107pub struct TwigKeyVal {
108    pub key: *const u8,
109    pub key_len: usize,
110    pub value: *const u8,
111    pub value_len: usize,
112}
113
114/// C ABI mirror of Zig's `TwigSpan` — a byte range `[start, end)`.
115#[repr(C)]
116#[derive(Clone, Copy, Debug, Eq, PartialEq)]
117pub struct TwigSpan {
118    pub start: usize,
119    pub end: usize,
120}
121
122/// C ABI mirror of Zig's `TwigQueryMatch` — one node returned by
123/// `twig_document_query`. `content_span` is only meaningful when
124/// `has_content_span` is non-zero. `kind` is a NUL-terminated node-kind name
125/// in static, library-owned storage (never freed).
126#[repr(C)]
127#[derive(Clone, Copy, Debug)]
128pub struct TwigQueryMatch {
129    pub node_id: u32,
130    pub span: TwigSpan,
131    pub content_span: TwigSpan,
132    pub has_content_span: c_int,
133    pub kind: *const c_char,
134}
135
136/// The sentinel `node_id` for "no such node" in a [`TwigFlatNode`] link field.
137pub const TWIG_NO_NODE: u32 = u32::MAX;
138
139/// C ABI mirror of Zig's `TwigChange` — the byte effect of an edit. `old_span`
140/// is the replaced range in the pre-edit source; `new_span` is the range the
141/// replacement occupies in the post-edit source (same start).
142#[repr(C)]
143#[derive(Clone, Copy, Debug)]
144pub struct TwigChange {
145    pub old_span: TwigSpan,
146    pub new_span: TwigSpan,
147}
148
149/// C ABI mirror of Zig's `TwigFlatNode` — one node of the editor's flat tree
150/// snapshot. Link fields (`parent`/`first_child`/`next_sibling`) are ids or
151/// [`TWIG_NO_NODE`]. `content_span` is meaningful only when `has_content_span`.
152/// `kind` is static, library-owned storage; `text`/`destination` pointers
153/// borrow the current parse's payloads (NULL when the kind carries none).
154/// `head`/`alignment` carry a `row`/`cell` payload, each `-1` for a kind that
155/// has none (see [`TWIG_HEAD_NONE`] / [`TWIG_ALIGN_NONE`]). `name_ptr` is a
156/// generic element's tag name (NULL for every other kind); `attrs_ptr` points at
157/// `attrs_len` [`TwigKeyVal`]s (NULL/0 when the node has no attributes). Both
158/// borrow the current parse's payloads with the same lifetime as `text_ptr`.
159#[repr(C)]
160#[derive(Clone, Copy, Debug)]
161pub struct TwigFlatNode {
162    pub id: u32,
163    pub parent: u32,
164    pub first_child: u32,
165    pub next_sibling: u32,
166    pub span: TwigSpan,
167    pub content_span: TwigSpan,
168    pub has_content_span: c_int,
169    pub level: u32,
170    pub kind: *const c_char,
171    pub text_ptr: *const u8,
172    pub text_len: usize,
173    pub destination_ptr: *const u8,
174    pub destination_len: usize,
175    pub head: c_int,
176    pub alignment: c_int,
177    pub name_ptr: *const u8,
178    pub name_len: usize,
179    pub attrs_ptr: *const TwigKeyVal,
180    pub attrs_len: usize,
181}
182
183/// `TwigFlatNode::head` for a node that is neither a `row` nor a `cell`.
184pub const TWIG_HEAD_NONE: c_int = -1;
185
186/// `TwigFlatNode::alignment` codes; `NONE` means the node isn't a `cell`.
187/// `NONE` is part of the ABI contract even though `Alignment::from_c` folds it
188/// into its catch-all, so spell it out rather than leaving the -1 a mystery.
189#[allow(dead_code)]
190pub const TWIG_ALIGN_NONE: c_int = -1;
191pub const TWIG_ALIGN_DEFAULT: c_int = 0;
192pub const TWIG_ALIGN_LEFT: c_int = 1;
193pub const TWIG_ALIGN_RIGHT: c_int = 2;
194pub const TWIG_ALIGN_CENTER: c_int = 3;
195
196unsafe extern "C" {
197    pub fn twig_abi_version() -> u32;
198    pub fn twig_version() -> u32;
199    pub fn twig_version_string() -> *const c_char;
200    pub fn twig_parse(
201        input: *const u8,
202        input_len: usize,
203        format: c_int,
204        out_doc: *mut *mut TwigDocument,
205    ) -> TwigStatus;
206    pub fn twig_parse_ext(
207        input: *const u8,
208        input_len: usize,
209        format: c_int,
210        md_flags: u32,
211        out_doc: *mut *mut TwigDocument,
212    ) -> TwigStatus;
213    pub fn twig_document_destroy(doc: *mut TwigDocument);
214    pub fn twig_document_render_html(
215        doc: *mut TwigDocument,
216        out_ptr: *mut *const u8,
217        out_len: *mut usize,
218    ) -> TwigStatus;
219    pub fn twig_document_serialize(
220        doc: *mut TwigDocument,
221        format: c_int,
222        out_ptr: *mut *const u8,
223        out_len: *mut usize,
224    ) -> TwigStatus;
225    pub fn twig_document_ast_json(
226        doc: *mut TwigDocument,
227        out_ptr: *mut *const u8,
228        out_len: *mut usize,
229    ) -> TwigStatus;
230    pub fn twig_document_query(
231        doc: *mut TwigDocument,
232        selector: *const u8,
233        selector_len: usize,
234        out_ptr: *mut *const TwigQueryMatch,
235        out_len: *mut usize,
236    ) -> TwigStatus;
237
238    pub fn twig_editor_create(
239        input: *const u8,
240        input_len: usize,
241        format: c_int,
242        out_editor: *mut *mut TwigEditor,
243    ) -> TwigStatus;
244    pub fn twig_editor_create_ext(
245        input: *const u8,
246        input_len: usize,
247        format: c_int,
248        md_flags: u32,
249        out_editor: *mut *mut TwigEditor,
250    ) -> TwigStatus;
251    pub fn twig_editor_destroy(editor: *mut TwigEditor);
252    pub fn twig_editor_replace(
253        editor: *mut TwigEditor,
254        locator: *const u8,
255        locator_len: usize,
256        text: *const u8,
257        text_len: usize,
258    ) -> TwigStatus;
259    pub fn twig_editor_replace_content(
260        editor: *mut TwigEditor,
261        locator: *const u8,
262        locator_len: usize,
263        text: *const u8,
264        text_len: usize,
265    ) -> TwigStatus;
266    pub fn twig_editor_insert_before(
267        editor: *mut TwigEditor,
268        locator: *const u8,
269        locator_len: usize,
270        text: *const u8,
271        text_len: usize,
272    ) -> TwigStatus;
273    pub fn twig_editor_insert_after(
274        editor: *mut TwigEditor,
275        locator: *const u8,
276        locator_len: usize,
277        text: *const u8,
278        text_len: usize,
279    ) -> TwigStatus;
280    pub fn twig_editor_insert_child(
281        editor: *mut TwigEditor,
282        locator: *const u8,
283        locator_len: usize,
284        child_index: usize,
285        text: *const u8,
286        text_len: usize,
287    ) -> TwigStatus;
288    pub fn twig_editor_delete(
289        editor: *mut TwigEditor,
290        locator: *const u8,
291        locator_len: usize,
292    ) -> TwigStatus;
293    pub fn twig_editor_delete_smart(
294        editor: *mut TwigEditor,
295        locator: *const u8,
296        locator_len: usize,
297    ) -> TwigStatus;
298    pub fn twig_editor_unwrap(
299        editor: *mut TwigEditor,
300        locator: *const u8,
301        locator_len: usize,
302    ) -> TwigStatus;
303    pub fn twig_editor_filter(
304        editor: *mut TwigEditor,
305        drop: *const u8,
306        drop_len: usize,
307        keep: *const u8,
308        keep_len: usize,
309        unwrap_kept: c_int,
310    ) -> TwigStatus;
311    pub fn twig_editor_source(
312        editor: *mut TwigEditor,
313        out_ptr: *mut *const u8,
314        out_len: *mut usize,
315    ) -> TwigStatus;
316    pub fn twig_editor_ast_json(
317        editor: *mut TwigEditor,
318        out_ptr: *mut *const u8,
319        out_len: *mut usize,
320    ) -> TwigStatus;
321    pub fn twig_editor_query(
322        editor: *mut TwigEditor,
323        selector: *const u8,
324        selector_len: usize,
325        out_ptr: *mut *const TwigQueryMatch,
326        out_len: *mut usize,
327    ) -> TwigStatus;
328    pub fn twig_editor_edit_range(
329        editor: *mut TwigEditor,
330        start: usize,
331        end: usize,
332        text: *const u8,
333        text_len: usize,
334        out_change: *mut TwigChange,
335    ) -> TwigStatus;
336    pub fn twig_editor_last_change(
337        editor: *mut TwigEditor,
338        out_change: *mut TwigChange,
339    ) -> TwigStatus;
340    pub fn twig_editor_undo(editor: *mut TwigEditor, out_change: *mut TwigChange) -> TwigStatus;
341    pub fn twig_editor_redo(editor: *mut TwigEditor, out_change: *mut TwigChange) -> TwigStatus;
342    pub fn twig_editor_coalesce_last(editor: *mut TwigEditor) -> TwigStatus;
343    pub fn twig_editor_revision(editor: *mut TwigEditor) -> u64;
344    pub fn twig_editor_dirty_range(editor: *mut TwigEditor, out_span: *mut TwigSpan)
345        -> TwigStatus;
346    pub fn twig_editor_clear_dirty(editor: *mut TwigEditor) -> TwigStatus;
347    pub fn twig_editor_set_caret_blob(
348        editor: *mut TwigEditor,
349        blob_ptr: *const u8,
350        blob_len: usize,
351    ) -> TwigStatus;
352    pub fn twig_editor_caret_blob(
353        editor: *mut TwigEditor,
354        out_ptr: *mut *const u8,
355        out_len: *mut usize,
356    ) -> TwigStatus;
357    pub fn twig_editor_nodes(
358        editor: *mut TwigEditor,
359        out_ptr: *mut *const TwigFlatNode,
360        out_len: *mut usize,
361    ) -> TwigStatus;
362    pub fn twig_editor_child_spans(
363        editor: *mut TwigEditor,
364        node_id: u32,
365        out_ptr: *mut *const TwigQueryMatch,
366        out_len: *mut usize,
367    ) -> TwigStatus;
368    pub fn twig_editor_subtree(
369        editor: *mut TwigEditor,
370        node_id: u32,
371        out_ptr: *mut *const TwigFlatNode,
372        out_len: *mut usize,
373    ) -> TwigStatus;
374    pub fn twig_editor_node_at(
375        editor: *mut TwigEditor,
376        offset: usize,
377        out_match: *mut TwigQueryMatch,
378    ) -> TwigStatus;
379    pub fn twig_editor_nodes_at(
380        editor: *mut TwigEditor,
381        offset: usize,
382        out_ptr: *mut *const TwigQueryMatch,
383        out_len: *mut usize,
384    ) -> TwigStatus;
385    pub fn twig_editor_wrap_range(
386        editor: *mut TwigEditor,
387        start: usize,
388        end: usize,
389        kind: c_int,
390        out_change: *mut TwigChange,
391    ) -> TwigStatus;
392    pub fn twig_editor_toggle_inline(
393        editor: *mut TwigEditor,
394        start: usize,
395        end: usize,
396        kind: c_int,
397        out_change: *mut TwigChange,
398    ) -> TwigStatus;
399    pub fn twig_editor_set_block(
400        editor: *mut TwigEditor,
401        offset: usize,
402        block_kind: c_int,
403        level: u32,
404        out_change: *mut TwigChange,
405    ) -> TwigStatus;
406    pub fn twig_editor_toggle_block_container(
407        editor: *mut TwigEditor,
408        start: usize,
409        end: usize,
410        container_kind: c_int,
411        out_change: *mut TwigChange,
412    ) -> TwigStatus;
413    pub fn twig_editor_insert_link(
414        editor: *mut TwigEditor,
415        start: usize,
416        end: usize,
417        destination: *const u8,
418        destination_len: usize,
419        out_change: *mut TwigChange,
420    ) -> TwigStatus;
421
422    pub fn twig_builder_create(out_builder: *mut *mut TwigBuilder) -> TwigStatus;
423    pub fn twig_builder_destroy(builder: *mut TwigBuilder);
424    pub fn twig_builder_add(builder: *mut TwigBuilder, kind: c_int, out_id: *mut u32) -> TwigStatus;
425    pub fn twig_builder_add_text(
426        builder: *mut TwigBuilder,
427        kind: c_int,
428        text: *const u8,
429        text_len: usize,
430        out_id: *mut u32,
431    ) -> TwigStatus;
432    pub fn twig_builder_add_heading(builder: *mut TwigBuilder, level: u32, out_id: *mut u32) -> TwigStatus;
433    pub fn twig_builder_add_code_block(
434        builder: *mut TwigBuilder,
435        lang: *const u8,
436        lang_len: usize,
437        has_lang: c_int,
438        text: *const u8,
439        text_len: usize,
440        out_id: *mut u32,
441    ) -> TwigStatus;
442    pub fn twig_builder_add_raw_block(
443        builder: *mut TwigBuilder,
444        format: *const u8,
445        format_len: usize,
446        text: *const u8,
447        text_len: usize,
448        out_id: *mut u32,
449    ) -> TwigStatus;
450    pub fn twig_builder_add_metadata(
451        builder: *mut TwigBuilder,
452        lang: *const u8,
453        lang_len: usize,
454        text: *const u8,
455        text_len: usize,
456        out_id: *mut u32,
457    ) -> TwigStatus;
458    pub fn twig_builder_add_raw_inline(
459        builder: *mut TwigBuilder,
460        format: *const u8,
461        format_len: usize,
462        text: *const u8,
463        text_len: usize,
464        out_id: *mut u32,
465    ) -> TwigStatus;
466    pub fn twig_builder_add_smart_punctuation(
467        builder: *mut TwigBuilder,
468        punct_kind: c_int,
469        text: *const u8,
470        text_len: usize,
471        out_id: *mut u32,
472    ) -> TwigStatus;
473    pub fn twig_builder_add_link(
474        builder: *mut TwigBuilder,
475        destination: *const u8,
476        destination_len: usize,
477        has_destination: c_int,
478        reference: *const u8,
479        reference_len: usize,
480        has_reference: c_int,
481        out_id: *mut u32,
482    ) -> TwigStatus;
483    pub fn twig_builder_add_image(
484        builder: *mut TwigBuilder,
485        destination: *const u8,
486        destination_len: usize,
487        has_destination: c_int,
488        reference: *const u8,
489        reference_len: usize,
490        has_reference: c_int,
491        out_id: *mut u32,
492    ) -> TwigStatus;
493    pub fn twig_builder_add_directive(
494        builder: *mut TwigBuilder,
495        form: c_int,
496        name: *const u8,
497        name_len: usize,
498        out_id: *mut u32,
499    ) -> TwigStatus;
500    pub fn twig_builder_add_element(
501        builder: *mut TwigBuilder,
502        name: *const u8,
503        name_len: usize,
504        out_id: *mut u32,
505    ) -> TwigStatus;
506    pub fn twig_builder_add_processing_instruction(
507        builder: *mut TwigBuilder,
508        target: *const u8,
509        target_len: usize,
510        data: *const u8,
511        data_len: usize,
512        out_id: *mut u32,
513    ) -> TwigStatus;
514    pub fn twig_builder_add_footnote(
515        builder: *mut TwigBuilder,
516        label: *const u8,
517        label_len: usize,
518        out_id: *mut u32,
519    ) -> TwigStatus;
520    pub fn twig_builder_add_reference(
521        builder: *mut TwigBuilder,
522        label: *const u8,
523        label_len: usize,
524        destination: *const u8,
525        destination_len: usize,
526        out_id: *mut u32,
527    ) -> TwigStatus;
528    pub fn twig_builder_add_bullet_list(
529        builder: *mut TwigBuilder,
530        style: c_int,
531        tight: c_int,
532        out_id: *mut u32,
533    ) -> TwigStatus;
534    pub fn twig_builder_add_ordered_list(
535        builder: *mut TwigBuilder,
536        numbering: c_int,
537        delim: c_int,
538        tight: c_int,
539        start: u32,
540        has_start: c_int,
541        out_id: *mut u32,
542    ) -> TwigStatus;
543    pub fn twig_builder_add_task_list(builder: *mut TwigBuilder, tight: c_int, out_id: *mut u32) -> TwigStatus;
544    pub fn twig_builder_add_task_list_item(builder: *mut TwigBuilder, checked: c_int, out_id: *mut u32) -> TwigStatus;
545    pub fn twig_builder_add_row(builder: *mut TwigBuilder, head: c_int, out_id: *mut u32) -> TwigStatus;
546    pub fn twig_builder_add_cell(builder: *mut TwigBuilder, head: c_int, alignment: c_int, out_id: *mut u32) -> TwigStatus;
547    pub fn twig_builder_set_children(
548        builder: *mut TwigBuilder,
549        parent: u32,
550        ids: *const u32,
551        ids_len: usize,
552    ) -> TwigStatus;
553    pub fn twig_builder_set_attrs(
554        builder: *mut TwigBuilder,
555        id: u32,
556        kvs: *const TwigKeyVal,
557        kvs_len: usize,
558    ) -> TwigStatus;
559    pub fn twig_builder_render_html(
560        builder: *mut TwigBuilder,
561        root: u32,
562        out_ptr: *mut *const u8,
563        out_len: *mut usize,
564    ) -> TwigStatus;
565    pub fn twig_builder_serialize(
566        builder: *mut TwigBuilder,
567        root: u32,
568        format: c_int,
569        out_ptr: *mut *const u8,
570        out_len: *mut usize,
571    ) -> TwigStatus;
572    pub fn twig_builder_ast_json(
573        builder: *mut TwigBuilder,
574        root: u32,
575        out_ptr: *mut *const u8,
576        out_len: *mut usize,
577    ) -> TwigStatus;
578    pub fn twig_builder_query(
579        builder: *mut TwigBuilder,
580        root: u32,
581        selector: *const u8,
582        selector_len: usize,
583        out_ptr: *mut *const TwigQueryMatch,
584        out_len: *mut usize,
585    ) -> TwigStatus;
586}