Skip to main content

twig_sys/
lib.rs

1//! Low-level FFI bindings for Twig's C ABI, plus the build script that links
2//! the native `libtwig.a`.
3//!
4//! This crate is the `-sys` layer: it exposes the raw `extern "C"` functions,
5//! the `#[repr(C)]` type mirrors, and the status/format constants, and nothing
6//! else. The safe, ergonomic API lives in the `twig-doc` crate, which depends
7//! on this one. Direct use is `unsafe` and unstable across ABI-version bumps —
8//! see [`TWIG_ABI_VERSION`].
9#![allow(non_camel_case_types)]
10
11use std::os::raw::{c_char, c_int};
12
13/// The C ABI contract version this binding is written against; see
14/// `twig_abi_version`. Must match the value baked into the linked library
15/// (asserted at runtime by the `abi_version_matches` test in `lib.rs`).
16pub const TWIG_ABI_VERSION: u32 = 6;
17
18// Freeze the canonical 64-bit layout of every `#[repr(C)]` mirror below so it
19// can never silently drift from the Zig `extern struct` it shadows. These are
20// the same offsets asserted on the Zig side in `src/c_abi.zig`; a change on
21// either side that isn't matched on the other fails to compile. Gated on 64-bit
22// (the offsets are the LP64/LLP64 layout); 32-bit targets pack tighter but the
23// C ABI still keeps both languages in agreement.
24#[cfg(target_pointer_width = "64")]
25const _: () = {
26    use std::mem::offset_of;
27    use std::mem::size_of;
28
29    assert!(size_of::<TwigSpan>() == 16);
30    assert!(offset_of!(TwigSpan, start) == 0);
31    assert!(offset_of!(TwigSpan, end) == 8);
32
33    assert!(size_of::<TwigQueryMatch>() == 56);
34    assert!(offset_of!(TwigQueryMatch, node_id) == 0);
35    assert!(offset_of!(TwigQueryMatch, span) == 8);
36    assert!(offset_of!(TwigQueryMatch, content_span) == 24);
37    assert!(offset_of!(TwigQueryMatch, has_content_span) == 40);
38    assert!(offset_of!(TwigQueryMatch, kind) == 48);
39
40    assert!(size_of::<TwigChange>() == 32);
41    assert!(offset_of!(TwigChange, old_span) == 0);
42    assert!(offset_of!(TwigChange, new_span) == 16);
43
44    assert!(size_of::<TwigFlatNode>() == 168);
45    assert!(offset_of!(TwigFlatNode, id) == 0);
46    assert!(offset_of!(TwigFlatNode, parent) == 4);
47    assert!(offset_of!(TwigFlatNode, first_child) == 8);
48    assert!(offset_of!(TwigFlatNode, next_sibling) == 12);
49    assert!(offset_of!(TwigFlatNode, span) == 16);
50    assert!(offset_of!(TwigFlatNode, content_span) == 32);
51    assert!(offset_of!(TwigFlatNode, has_content_span) == 48);
52    assert!(offset_of!(TwigFlatNode, level) == 52);
53    assert!(offset_of!(TwigFlatNode, kind) == 56);
54    assert!(offset_of!(TwigFlatNode, text_ptr) == 64);
55    assert!(offset_of!(TwigFlatNode, text_len) == 72);
56    assert!(offset_of!(TwigFlatNode, destination_ptr) == 80);
57    assert!(offset_of!(TwigFlatNode, destination_len) == 88);
58    assert!(offset_of!(TwigFlatNode, head) == 96);
59    assert!(offset_of!(TwigFlatNode, alignment) == 100);
60    assert!(offset_of!(TwigFlatNode, name_ptr) == 104);
61    assert!(offset_of!(TwigFlatNode, name_len) == 112);
62    assert!(offset_of!(TwigFlatNode, attrs_ptr) == 120);
63    assert!(offset_of!(TwigFlatNode, attrs_len) == 128);
64    assert!(offset_of!(TwigFlatNode, directive_form) == 136);
65    // In what was `directive_form`'s tail padding: `size_of` is still 144 and
66    // every offset above is unchanged. See the ABI note 5 in twig.h.
67    assert!(offset_of!(TwigFlatNode, container_origin) == 140);
68    // Appended, so every offset above keeps its place; only `size_of` moves
69    // (144 -> 168). See `TWIG_ABI_VERSION` note 6.
70    assert!(offset_of!(TwigFlatNode, marker_span) == 144);
71    assert!(offset_of!(TwigFlatNode, has_marker_span) == 160);
72    // In what was `has_marker_span`'s tail padding: `size_of` stays 168.
73    assert!(offset_of!(TwigFlatNode, checked) == 164);
74
75    assert!(size_of::<TwigKeyVal>() == 32);
76    assert!(offset_of!(TwigKeyVal, key) == 0);
77    assert!(offset_of!(TwigKeyVal, key_len) == 8);
78    assert!(offset_of!(TwigKeyVal, value) == 16);
79    assert!(offset_of!(TwigKeyVal, value_len) == 24);
80};
81
82#[repr(transparent)]
83#[derive(Clone, Copy, Debug, Eq, PartialEq)]
84pub struct TwigStatus(pub c_int);
85
86#[allow(dead_code)]
87impl TwigStatus {
88    pub const OK: c_int = 0;
89    pub const INVALID_ARGUMENT: c_int = 1;
90    pub const PARSE_ERROR: c_int = 2;
91    pub const OUT_OF_MEMORY: c_int = 3;
92    pub const UNSUPPORTED_FORMAT: c_int = 4;
93    pub const NOT_FOUND: c_int = 5;
94    pub const AMBIGUOUS: c_int = 6;
95    pub const NOT_EDITABLE: c_int = 7;
96    pub const EDIT_CONFLICT: c_int = 8;
97    pub const UNSAFE_METADATA: c_int = 9;
98    pub const INTERNAL_ERROR: c_int = 255;
99}
100
101#[repr(C)]
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum TwigFormat {
104    Djot = 1,
105    Markdown = 2,
106    Xml = 3,
107    Html = 4,
108    /// Parses, renders, serializes and edits. The link, image, footnote and
109    /// table gestures report `TWIG_STATUS_UNSUPPORTED_FORMAT` over an
110    /// AsciiDoc document (their AsciiDoc spellings have a shape the gesture
111    /// algorithms cannot write); every other gesture works. What the parser
112    /// leaves unmodelled survives as literal source text.
113    Asciidoc = 5,
114    /// Strict CommonMark, a dialect of `Markdown`: one parser under a
115    /// different preset, `md_flags` laid over it. Writes as Markdown.
116    Commonmark = 6,
117    /// GitHub-Flavored Markdown, a dialect of `Markdown` the same way.
118    Gfm = 7,
119}
120
121/// `TWIG_FORMAT_RUNTIME_BASE`: the first format code that names a language
122/// registered at runtime rather than compiled in. Reserved ahead of the
123/// registration entry points, which are a later minor; every [`TwigFormat`]
124/// member is below it, and a code in the range is refused with
125/// `TWIG_STATUS_UNSUPPORTED_FORMAT` until they exist.
126pub const TWIG_FORMAT_RUNTIME_BASE: c_int = 4096;
127const _: () = assert!((TwigFormat::Gfm as c_int) < TWIG_FORMAT_RUNTIME_BASE);
128
129/// Markdown extension flags for the `md_flags` bitmask of `twig_parse_ext` and
130/// `twig_editor_create_ext`.
131pub const TWIG_MD_DIRECTIVES: u32 = 1 << 0;
132pub const TWIG_MD_MATH: u32 = 1 << 1;
133pub const TWIG_MD_HTML_ELEMENTS: u32 = 1 << 2;
134pub const TWIG_MD_HIGHLIGHT: u32 = 1 << 3;
135pub const TWIG_MD_HIGHLIGHT_COLORS: u32 = 1 << 4;
136
137pub enum TwigDocument {}
138
139pub enum TwigEditor {}
140
141pub enum TwigBuilder {}
142
143/// C ABI mirror of Zig's `TwigKeyVal` — one `(key, value)` attribute pair. Used
144/// on the read path ([`TwigFlatNode::attrs_ptr`], borrowed) and the write path
145/// (`twig_builder_set_attrs`, copied). A NULL `value` is a bare attribute.
146#[repr(C)]
147#[derive(Clone, Copy, Debug)]
148pub struct TwigKeyVal {
149    pub key: *const u8,
150    pub key_len: usize,
151    pub value: *const u8,
152    pub value_len: usize,
153}
154
155/// C ABI mirror of Zig's `TwigSpan` — a byte range `[start, end)`.
156#[repr(C)]
157#[derive(Clone, Copy, Debug, Eq, PartialEq)]
158pub struct TwigSpan {
159    pub start: usize,
160    pub end: usize,
161}
162
163/// C ABI mirror of Zig's `TwigQueryMatch` — one node returned by
164/// `twig_document_query`. `content_span` is only meaningful when
165/// `has_content_span` is non-zero. `kind` is a NUL-terminated node-kind name
166/// in static, library-owned storage (never freed).
167#[repr(C)]
168#[derive(Clone, Copy, Debug)]
169pub struct TwigQueryMatch {
170    pub node_id: u32,
171    pub span: TwigSpan,
172    pub content_span: TwigSpan,
173    pub has_content_span: c_int,
174    pub kind: *const c_char,
175}
176
177/// C ABI mirror of Zig's `TwigWarning` — one thing a conversion would silently
178/// lose. `path` is a slash-separated child-index trail from the analyzed root
179/// (empty for the root itself); `kind` is a NUL-terminated node-kind name in
180/// static, library-owned storage. Both borrow, with the lifetime of the
181/// `twig_document_diagnostics` output array they came from.
182#[repr(C)]
183#[derive(Clone, Copy, Debug)]
184pub struct TwigWarning {
185    pub fidelity: c_int,
186    pub path_ptr: *const u8,
187    pub path_len: usize,
188    pub kind: *const c_char,
189}
190
191/// [`TwigWarning::fidelity`] codes. `FAITHFUL` completes the space and is never
192/// the value of a reported warning — a faithful node is not a warning.
193#[allow(dead_code)]
194pub const TWIG_FIDELITY_FAITHFUL: c_int = 0;
195pub const TWIG_FIDELITY_DEGRADED: c_int = 1;
196pub const TWIG_FIDELITY_DROPPED: c_int = 2;
197pub const TWIG_FIDELITY_ATTRS_DEGRADED: c_int = 3;
198pub const TWIG_FIDELITY_ATTRS_DROPPED: c_int = 4;
199
200/// The sentinel `node_id` for "no such node" in a [`TwigFlatNode`] link field.
201pub const TWIG_NO_NODE: u32 = u32::MAX;
202
203/// C ABI mirror of Zig's `TwigChange` — the byte effect of an edit. `old_span`
204/// is the replaced range in the pre-edit source; `new_span` is the range the
205/// replacement occupies in the post-edit source (same start).
206#[repr(C)]
207#[derive(Clone, Copy, Debug)]
208pub struct TwigChange {
209    pub old_span: TwigSpan,
210    pub new_span: TwigSpan,
211}
212
213/// C ABI mirror of Zig's `TwigFlatNode` — one node of the editor's flat tree
214/// snapshot. Link fields (`parent`/`first_child`/`next_sibling`) are ids or
215/// [`TWIG_NO_NODE`]. `content_span` is meaningful only when `has_content_span`.
216/// `kind` is static, library-owned storage; `text`/`destination` pointers
217/// borrow the current parse's payloads (NULL when the kind carries none).
218/// `head`/`alignment` carry a `row`/`cell` payload, each `-1` for a kind that
219/// has none (see [`TWIG_HEAD_NONE`] / [`TWIG_ALIGN_NONE`]). `name_ptr` is a
220/// generic container's name — an HTML/XML tag or a directive's type — and NULL
221/// for every other kind; `directive_form` is a [`TWIG_DIRECTIVE_NONE`]-defaulted
222/// code for which of the three surface forms it took; `container_origin` is a
223/// [`TWIG_CONTAINER_ORIGIN_NONE`]-defaulted code for whether it was WRITTEN as a
224/// tag or a directive (the two questions are different — see the constants);
225/// `attrs_ptr` points at
226/// `attrs_len` [`TwigKeyVal`]s (NULL/0 when the node has no attributes). Both
227/// borrow the current parse's payloads with the same lifetime as `text_ptr`.
228#[repr(C)]
229#[derive(Clone, Copy, Debug)]
230pub struct TwigFlatNode {
231    pub id: u32,
232    pub parent: u32,
233    pub first_child: u32,
234    pub next_sibling: u32,
235    pub span: TwigSpan,
236    pub content_span: TwigSpan,
237    pub has_content_span: c_int,
238    pub level: u32,
239    pub kind: *const c_char,
240    pub text_ptr: *const u8,
241    pub text_len: usize,
242    pub destination_ptr: *const u8,
243    pub destination_len: usize,
244    pub head: c_int,
245    pub alignment: c_int,
246    pub name_ptr: *const u8,
247    pub name_len: usize,
248    pub attrs_ptr: *const TwigKeyVal,
249    pub attrs_len: usize,
250    pub directive_form: c_int,
251    pub container_origin: c_int,
252    pub marker_span: TwigSpan,
253    pub has_marker_span: c_int,
254    pub checked: c_int,
255}
256
257/// `TwigFlatNode::checked` for a node that is not a `task_list_item`.
258pub const TWIG_TASK_CHECKED_NONE: c_int = -1;
259
260/// `TwigFlatNode::head` for a node that is neither a `row` nor a `cell`.
261pub const TWIG_HEAD_NONE: c_int = -1;
262
263/// `TwigFlatNode::alignment` codes; `NONE` means the node isn't a `cell`.
264/// `NONE` is part of the ABI contract even though `Alignment::from_c` folds it
265/// into its catch-all, so spell it out rather than leaving the -1 a mystery.
266#[allow(dead_code)]
267pub const TWIG_ALIGN_NONE: c_int = -1;
268pub const TWIG_ALIGN_DEFAULT: c_int = 0;
269pub const TWIG_ALIGN_LEFT: c_int = 1;
270pub const TWIG_ALIGN_RIGHT: c_int = 2;
271pub const TWIG_ALIGN_CENTER: c_int = 3;
272
273/// `TwigFlatNode::directive_form` codes — the generic-directives proposal's
274/// three spellings: `:name[x]` (text), `::name{…}` (leaf), `:::name{…}` … `:::`
275/// (container). These double as `twig_builder_add_directive`'s `form` argument.
276/// `NONE` means the node isn't a `directive` at all; like [`TWIG_ALIGN_NONE`] it
277/// is a read-path-only code, never something you can build with.
278#[allow(dead_code)]
279pub const TWIG_DIRECTIVE_NONE: c_int = -1;
280pub const TWIG_DIRECTIVE_TEXT: c_int = 0;
281pub const TWIG_DIRECTIVE_LEAF: c_int = 1;
282pub const TWIG_DIRECTIVE_CONTAINER: c_int = 2;
283
284/// `TwigFlatNode::container_origin` codes — whether a generic container was
285/// WRITTEN as a tag or as a directive.
286///
287/// A different question from [`TWIG_DIRECTIVE_TEXT`] and friends, which say
288/// which of the three directive spellings a node's producer draws. HTML's
289/// parser gives `<div>` and `<span>` a `directive_form` (they are the two tags
290/// djot and Markdown have generic spellings for), so `directive_form` cannot
291/// separate an HTML `<div>` from a Markdown `:::div`: those two agree on kind,
292/// on name and on form, field for field. This is the code that separates them.
293///
294/// `NONE` means nothing recorded an origin — the node isn't a container, or no
295/// parser produced it.
296#[allow(dead_code)]
297pub const TWIG_CONTAINER_ORIGIN_NONE: c_int = -1;
298pub const TWIG_CONTAINER_ORIGIN_ELEMENT: c_int = 0;
299pub const TWIG_CONTAINER_ORIGIN_DIRECTIVE: c_int = 1;
300
301// `op` codes for `twig_editor_table_edit` — mirror of `TwigTableOp` in twig.h.
302pub const TWIG_TABLE_INSERT_ROW: c_int = 0;
303pub const TWIG_TABLE_DELETE_ROW: c_int = 1;
304pub const TWIG_TABLE_INSERT_COLUMN: c_int = 2;
305pub const TWIG_TABLE_DELETE_COLUMN: c_int = 3;
306pub const TWIG_TABLE_SET_ALIGNMENT: c_int = 4;
307pub const TWIG_TABLE_MOVE_ROW: c_int = 5;
308pub const TWIG_TABLE_MOVE_COLUMN: c_int = 6;
309
310unsafe extern "C" {
311    pub fn twig_abi_version() -> u32;
312    pub fn twig_version() -> u32;
313    pub fn twig_version_string() -> *const c_char;
314    pub fn twig_parse(
315        input: *const u8,
316        input_len: usize,
317        format: c_int,
318        out_doc: *mut *mut TwigDocument,
319    ) -> TwigStatus;
320    pub fn twig_parse_ext(
321        input: *const u8,
322        input_len: usize,
323        format: c_int,
324        md_flags: u32,
325        out_doc: *mut *mut TwigDocument,
326    ) -> TwigStatus;
327    pub fn twig_document_destroy(doc: *mut TwigDocument);
328    pub fn twig_document_render_html(
329        doc: *mut TwigDocument,
330        out_ptr: *mut *const u8,
331        out_len: *mut usize,
332    ) -> TwigStatus;
333    pub fn twig_document_serialize(
334        doc: *mut TwigDocument,
335        format: c_int,
336        out_ptr: *mut *const u8,
337        out_len: *mut usize,
338    ) -> TwigStatus;
339    pub fn twig_document_ast_json(
340        doc: *mut TwigDocument,
341        out_ptr: *mut *const u8,
342        out_len: *mut usize,
343    ) -> TwigStatus;
344    pub fn twig_document_query(
345        doc: *mut TwigDocument,
346        selector: *const u8,
347        selector_len: usize,
348        out_ptr: *mut *const TwigQueryMatch,
349        out_len: *mut usize,
350    ) -> TwigStatus;
351    pub fn twig_document_node_span(
352        doc: *mut TwigDocument,
353        node_id: u32,
354        out_span: *mut TwigSpan,
355    ) -> TwigStatus;
356    pub fn twig_document_node_content_span(
357        doc: *mut TwigDocument,
358        node_id: u32,
359        out_span: *mut TwigSpan,
360    ) -> TwigStatus;
361    pub fn twig_document_node_marker_span(
362        doc: *mut TwigDocument,
363        node_id: u32,
364        out_span: *mut TwigSpan,
365    ) -> TwigStatus;
366    pub fn twig_document_attrs_span(
367        doc: *mut TwigDocument,
368        node_id: u32,
369        out_span: *mut TwigSpan,
370    ) -> TwigStatus;
371    pub fn twig_document_line_prefix(
372        doc: *mut TwigDocument,
373        offset: usize,
374        out_span: *mut TwigSpan,
375    ) -> TwigStatus;
376    pub fn twig_document_continuation_prefix(
377        doc: *mut TwigDocument,
378        offset: usize,
379        out_ptr: *mut *const u8,
380        out_len: *mut usize,
381        out_columns: *mut usize,
382    ) -> TwigStatus;
383    pub fn twig_document_blank_line_prefix(
384        doc: *mut TwigDocument,
385        offset: usize,
386        out_ptr: *mut *const u8,
387        out_len: *mut usize,
388        out_columns: *mut usize,
389    ) -> TwigStatus;
390    pub fn twig_document_cell_colspan(
391        doc: *mut TwigDocument,
392        node_id: u32,
393        out_colspan: *mut u32,
394    ) -> TwigStatus;
395    pub fn twig_document_cell_rowspan(
396        doc: *mut TwigDocument,
397        node_id: u32,
398        out_rowspan: *mut u32,
399    ) -> TwigStatus;
400    pub fn twig_document_nodes(
401        doc: *mut TwigDocument,
402        out_ptr: *mut *const TwigFlatNode,
403        out_len: *mut usize,
404    ) -> TwigStatus;
405    pub fn twig_document_definitions(
406        doc: *mut TwigDocument,
407        out_ptr: *mut *const TwigQueryMatch,
408        out_len: *mut usize,
409    ) -> TwigStatus;
410    pub fn twig_document_diagnostics(
411        doc: *mut TwigDocument,
412        format: c_int,
413        out_ptr: *mut *const TwigWarning,
414        out_len: *mut usize,
415    ) -> TwigStatus;
416    pub fn twig_document_children(
417        doc: *mut TwigDocument,
418        node_id: u32,
419        out_ptr: *mut *const TwigQueryMatch,
420        out_len: *mut usize,
421    ) -> TwigStatus;
422    pub fn twig_document_subtree(
423        doc: *mut TwigDocument,
424        node_id: u32,
425        out_ptr: *mut *const TwigFlatNode,
426        out_len: *mut usize,
427    ) -> TwigStatus;
428    pub fn twig_document_node_at(
429        doc: *mut TwigDocument,
430        offset: usize,
431        out_match: *mut TwigQueryMatch,
432    ) -> TwigStatus;
433    pub fn twig_document_nodes_at(
434        doc: *mut TwigDocument,
435        offset: usize,
436        out_ptr: *mut *const TwigQueryMatch,
437        out_len: *mut usize,
438    ) -> TwigStatus;
439    pub fn twig_document_node_at_caret(
440        doc: *mut TwigDocument,
441        offset: usize,
442        out_match: *mut TwigQueryMatch,
443    ) -> TwigStatus;
444    pub fn twig_document_nodes_at_caret(
445        doc: *mut TwigDocument,
446        offset: usize,
447        out_ptr: *mut *const TwigQueryMatch,
448        out_len: *mut usize,
449    ) -> TwigStatus;
450
451    pub fn twig_editor_create(
452        input: *const u8,
453        input_len: usize,
454        format: c_int,
455        out_editor: *mut *mut TwigEditor,
456    ) -> TwigStatus;
457    pub fn twig_editor_create_ext(
458        input: *const u8,
459        input_len: usize,
460        format: c_int,
461        md_flags: u32,
462        out_editor: *mut *mut TwigEditor,
463    ) -> TwigStatus;
464    pub fn twig_editor_destroy(editor: *mut TwigEditor);
465    pub fn twig_editor_replace(
466        editor: *mut TwigEditor,
467        locator: *const u8,
468        locator_len: usize,
469        text: *const u8,
470        text_len: usize,
471    ) -> TwigStatus;
472    pub fn twig_editor_replace_content(
473        editor: *mut TwigEditor,
474        locator: *const u8,
475        locator_len: usize,
476        text: *const u8,
477        text_len: usize,
478    ) -> TwigStatus;
479    pub fn twig_editor_insert_before(
480        editor: *mut TwigEditor,
481        locator: *const u8,
482        locator_len: usize,
483        text: *const u8,
484        text_len: usize,
485    ) -> TwigStatus;
486    pub fn twig_editor_insert_after(
487        editor: *mut TwigEditor,
488        locator: *const u8,
489        locator_len: usize,
490        text: *const u8,
491        text_len: usize,
492    ) -> TwigStatus;
493    pub fn twig_editor_insert_child(
494        editor: *mut TwigEditor,
495        locator: *const u8,
496        locator_len: usize,
497        child_index: usize,
498        text: *const u8,
499        text_len: usize,
500    ) -> TwigStatus;
501    pub fn twig_editor_delete(
502        editor: *mut TwigEditor,
503        locator: *const u8,
504        locator_len: usize,
505    ) -> TwigStatus;
506    pub fn twig_editor_delete_smart(
507        editor: *mut TwigEditor,
508        locator: *const u8,
509        locator_len: usize,
510    ) -> TwigStatus;
511    pub fn twig_editor_unwrap(
512        editor: *mut TwigEditor,
513        locator: *const u8,
514        locator_len: usize,
515    ) -> TwigStatus;
516    pub fn twig_editor_filter(
517        editor: *mut TwigEditor,
518        drop: *const u8,
519        drop_len: usize,
520        keep: *const u8,
521        keep_len: usize,
522        unwrap_kept: c_int,
523    ) -> TwigStatus;
524    pub fn twig_editor_source(
525        editor: *mut TwigEditor,
526        out_ptr: *mut *const u8,
527        out_len: *mut usize,
528    ) -> TwigStatus;
529    pub fn twig_editor_document(
530        editor: *mut TwigEditor,
531        out_doc: *mut *mut TwigDocument,
532    ) -> TwigStatus;
533    pub fn twig_editor_ast_json(
534        editor: *mut TwigEditor,
535        out_ptr: *mut *const u8,
536        out_len: *mut usize,
537    ) -> TwigStatus;
538    pub fn twig_editor_query(
539        editor: *mut TwigEditor,
540        selector: *const u8,
541        selector_len: usize,
542        out_ptr: *mut *const TwigQueryMatch,
543        out_len: *mut usize,
544    ) -> TwigStatus;
545    pub fn twig_editor_edit_range(
546        editor: *mut TwigEditor,
547        start: usize,
548        end: usize,
549        text: *const u8,
550        text_len: usize,
551        out_change: *mut TwigChange,
552    ) -> TwigStatus;
553    pub fn twig_editor_last_change(
554        editor: *mut TwigEditor,
555        out_change: *mut TwigChange,
556    ) -> TwigStatus;
557    pub fn twig_editor_undo(editor: *mut TwigEditor, out_change: *mut TwigChange) -> TwigStatus;
558    pub fn twig_editor_redo(editor: *mut TwigEditor, out_change: *mut TwigChange) -> TwigStatus;
559    pub fn twig_editor_coalesce_last(editor: *mut TwigEditor) -> TwigStatus;
560    pub fn twig_editor_revision(editor: *mut TwigEditor) -> u64;
561    pub fn twig_editor_dirty_range(editor: *mut TwigEditor, out_span: *mut TwigSpan) -> TwigStatus;
562    pub fn twig_editor_clear_dirty(editor: *mut TwigEditor) -> TwigStatus;
563    pub fn twig_editor_set_caret_blob(
564        editor: *mut TwigEditor,
565        blob_ptr: *const u8,
566        blob_len: usize,
567    ) -> TwigStatus;
568    pub fn twig_editor_caret_blob(
569        editor: *mut TwigEditor,
570        out_ptr: *mut *const u8,
571        out_len: *mut usize,
572    ) -> TwigStatus;
573    pub fn twig_editor_nodes(
574        editor: *mut TwigEditor,
575        out_ptr: *mut *const TwigFlatNode,
576        out_len: *mut usize,
577    ) -> TwigStatus;
578    pub fn twig_editor_child_spans(
579        editor: *mut TwigEditor,
580        node_id: u32,
581        out_ptr: *mut *const TwigQueryMatch,
582        out_len: *mut usize,
583    ) -> TwigStatus;
584    pub fn twig_editor_subtree(
585        editor: *mut TwigEditor,
586        node_id: u32,
587        out_ptr: *mut *const TwigFlatNode,
588        out_len: *mut usize,
589    ) -> TwigStatus;
590    pub fn twig_editor_node_at(
591        editor: *mut TwigEditor,
592        offset: usize,
593        out_match: *mut TwigQueryMatch,
594    ) -> TwigStatus;
595    pub fn twig_editor_nodes_at(
596        editor: *mut TwigEditor,
597        offset: usize,
598        out_ptr: *mut *const TwigQueryMatch,
599        out_len: *mut usize,
600    ) -> TwigStatus;
601    pub fn twig_editor_wrap_range(
602        editor: *mut TwigEditor,
603        start: usize,
604        end: usize,
605        kind: c_int,
606        out_change: *mut TwigChange,
607    ) -> TwigStatus;
608    pub fn twig_editor_toggle_inline(
609        editor: *mut TwigEditor,
610        start: usize,
611        end: usize,
612        kind: c_int,
613        out_change: *mut TwigChange,
614    ) -> TwigStatus;
615    pub fn twig_editor_set_block(
616        editor: *mut TwigEditor,
617        offset: usize,
618        block_kind: c_int,
619        level: u32,
620        out_change: *mut TwigChange,
621    ) -> TwigStatus;
622    pub fn twig_editor_toggle_block_container(
623        editor: *mut TwigEditor,
624        start: usize,
625        end: usize,
626        container_kind: c_int,
627        out_change: *mut TwigChange,
628    ) -> TwigStatus;
629    pub fn twig_editor_renumber_ordered_lists(
630        editor: *mut TwigEditor,
631        offset: usize,
632        out_change: *mut TwigChange,
633    ) -> TwigStatus;
634    /// Whether `format` can spell `gesture` — the toolbar's gray-out question,
635    /// asked without a document. `kind` is read in that gesture's own kind
636    /// space (a `TWIG_GESTURE_*` doc comment in `twig.h` lists which), and must
637    /// be 0 for a gesture that takes none.
638    pub fn twig_format_supports(
639        format: c_int,
640        gesture: c_int,
641        kind: c_int,
642        out_supported: *mut c_int,
643    ) -> TwigStatus;
644    /// `twig_format_supports` for a document parsed with `md_flags` — the
645    /// question asked of the table the editor actually holds. A Markdown
646    /// extension can widen what may be authored: `==x==` is text under default
647    /// options and a `mark` under `TWIG_MD_HIGHLIGHT`, and
648    /// `TWIG_GESTURE_SET_MARK_COLOR` needs `TWIG_MD_HIGHLIGHT_COLORS` on top.
649    pub fn twig_format_supports_ext(
650        format: c_int,
651        md_flags: u32,
652        gesture: c_int,
653        kind: c_int,
654        out_supported: *mut c_int,
655    ) -> TwigStatus;
656    /// Whether `format` can be authored into at all — 0 for a parse-only
657    /// format. A weaker claim than it looks; see `twig_format_supports`.
658    pub fn twig_format_is_authorable(format: c_int, out_authorable: *mut c_int) -> TwigStatus;
659    pub fn twig_editor_table_edit(
660        editor: *mut TwigEditor,
661        offset: usize,
662        op: c_int,
663        arg: c_int,
664        out_change: *mut TwigChange,
665    ) -> TwigStatus;
666    pub fn twig_editor_insert_table(
667        editor: *mut TwigEditor,
668        offset: usize,
669        rows: usize,
670        cols: usize,
671        out_change: *mut TwigChange,
672    ) -> TwigStatus;
673    pub fn twig_editor_insert_directive(
674        editor: *mut TwigEditor,
675        offset: usize,
676        name_ptr: *const u8,
677        name_len: usize,
678        label_ptr: *const u8,
679        label_len: usize,
680        attrs_ptr: *const TwigKeyVal,
681        attrs_len: usize,
682        out_change: *mut TwigChange,
683    ) -> TwigStatus;
684    pub fn twig_editor_set_block_attrs(
685        editor: *mut TwigEditor,
686        offset: usize,
687        attrs_ptr: *const TwigKeyVal,
688        attrs_len: usize,
689        out_change: *mut TwigChange,
690    ) -> TwigStatus;
691    pub fn twig_editor_wrap_range_attrs(
692        editor: *mut TwigEditor,
693        start: usize,
694        end: usize,
695        attrs_ptr: *const TwigKeyVal,
696        attrs_len: usize,
697        out_change: *mut TwigChange,
698    ) -> TwigStatus;
699    pub fn twig_editor_insert_link(
700        editor: *mut TwigEditor,
701        start: usize,
702        end: usize,
703        destination: *const u8,
704        destination_len: usize,
705        out_change: *mut TwigChange,
706    ) -> TwigStatus;
707    pub fn twig_editor_insert_image(
708        editor: *mut TwigEditor,
709        start: usize,
710        end: usize,
711        destination: *const u8,
712        destination_len: usize,
713        out_change: *mut TwigChange,
714    ) -> TwigStatus;
715
716    pub fn twig_editor_insert_literal(
717        editor: *mut TwigEditor,
718        offset: usize,
719        text: *const u8,
720        text_len: usize,
721        out_change: *mut TwigChange,
722    ) -> TwigStatus;
723
724    pub fn twig_editor_insert_line_break(
725        editor: *mut TwigEditor,
726        offset: usize,
727        out_change: *mut TwigChange,
728    ) -> TwigStatus;
729
730    pub fn twig_editor_insert_thematic_break(
731        editor: *mut TwigEditor,
732        offset: usize,
733        out_change: *mut TwigChange,
734    ) -> TwigStatus;
735
736    pub fn twig_editor_split_block(
737        editor: *mut TwigEditor,
738        offset: usize,
739        out_change: *mut TwigChange,
740    ) -> TwigStatus;
741
742    pub fn twig_editor_join_blocks(
743        editor: *mut TwigEditor,
744        offset: usize,
745        out_change: *mut TwigChange,
746    ) -> TwigStatus;
747    // `language` is OPTIONAL, carried as this ABI's (ptr, len, has_*) triple:
748    // has_language == 0 leaves the fence bare, and "absent" is a different
749    // request from "present but empty".
750    pub fn twig_editor_toggle_code_block(
751        editor: *mut TwigEditor,
752        start: usize,
753        end: usize,
754        language: *const u8,
755        language_len: usize,
756        has_language: c_int,
757        out_change: *mut TwigChange,
758    ) -> TwigStatus;
759    pub fn twig_editor_set_code_language(
760        editor: *mut TwigEditor,
761        offset: usize,
762        language: *const u8,
763        language_len: usize,
764        has_language: c_int,
765        out_change: *mut TwigChange,
766    ) -> TwigStatus;
767    /// Set — or clear, with `has_color == 0` — the colour of the highlight the
768    /// caret at `offset` is inside. Markdown with `TWIG_MD_HIGHLIGHT_COLORS`
769    /// only; the name is a `data-color` value (`red`, `blue`, …).
770    pub fn twig_editor_set_mark_color(
771        editor: *mut TwigEditor,
772        offset: usize,
773        color: *const u8,
774        color_len: usize,
775        has_color: c_int,
776        out_change: *mut TwigChange,
777    ) -> TwigStatus;
778    pub fn twig_editor_toggle_task_item(
779        editor: *mut TwigEditor,
780        offset: usize,
781        out_change: *mut TwigChange,
782    ) -> TwigStatus;
783    pub fn twig_editor_set_task_checked(
784        editor: *mut TwigEditor,
785        offset: usize,
786        checked: c_int,
787        out_change: *mut TwigChange,
788    ) -> TwigStatus;
789    pub fn twig_editor_toggle_task_checked(
790        editor: *mut TwigEditor,
791        offset: usize,
792        out_change: *mut TwigChange,
793    ) -> TwigStatus;
794    pub fn twig_editor_insert_footnote(
795        editor: *mut TwigEditor,
796        offset: usize,
797        label: *const u8,
798        label_len: usize,
799        out_change: *mut TwigChange,
800    ) -> TwigStatus;
801
802    pub fn twig_builder_create(out_builder: *mut *mut TwigBuilder) -> TwigStatus;
803    pub fn twig_builder_destroy(builder: *mut TwigBuilder);
804    pub fn twig_builder_add(builder: *mut TwigBuilder, kind: c_int, out_id: *mut u32)
805    -> TwigStatus;
806    pub fn twig_builder_add_text(
807        builder: *mut TwigBuilder,
808        kind: c_int,
809        text: *const u8,
810        text_len: usize,
811        out_id: *mut u32,
812    ) -> TwigStatus;
813    pub fn twig_builder_add_heading(
814        builder: *mut TwigBuilder,
815        level: u32,
816        out_id: *mut u32,
817    ) -> TwigStatus;
818    pub fn twig_builder_add_code_block(
819        builder: *mut TwigBuilder,
820        lang: *const u8,
821        lang_len: usize,
822        has_lang: c_int,
823        text: *const u8,
824        text_len: usize,
825        out_id: *mut u32,
826    ) -> TwigStatus;
827    pub fn twig_builder_add_raw_block(
828        builder: *mut TwigBuilder,
829        format: *const u8,
830        format_len: usize,
831        text: *const u8,
832        text_len: usize,
833        out_id: *mut u32,
834    ) -> TwigStatus;
835    pub fn twig_builder_add_metadata(
836        builder: *mut TwigBuilder,
837        lang: *const u8,
838        lang_len: usize,
839        text: *const u8,
840        text_len: usize,
841        out_id: *mut u32,
842    ) -> TwigStatus;
843    pub fn twig_builder_add_raw_inline(
844        builder: *mut TwigBuilder,
845        format: *const u8,
846        format_len: usize,
847        text: *const u8,
848        text_len: usize,
849        out_id: *mut u32,
850    ) -> TwigStatus;
851    pub fn twig_builder_add_smart_punctuation(
852        builder: *mut TwigBuilder,
853        punct_kind: c_int,
854        text: *const u8,
855        text_len: usize,
856        out_id: *mut u32,
857    ) -> TwigStatus;
858    pub fn twig_builder_add_link(
859        builder: *mut TwigBuilder,
860        destination: *const u8,
861        destination_len: usize,
862        has_destination: c_int,
863        reference: *const u8,
864        reference_len: usize,
865        has_reference: c_int,
866        out_id: *mut u32,
867    ) -> TwigStatus;
868    pub fn twig_builder_add_image(
869        builder: *mut TwigBuilder,
870        destination: *const u8,
871        destination_len: usize,
872        has_destination: c_int,
873        reference: *const u8,
874        reference_len: usize,
875        has_reference: c_int,
876        out_id: *mut u32,
877    ) -> TwigStatus;
878    pub fn twig_builder_add_directive(
879        builder: *mut TwigBuilder,
880        form: c_int,
881        name: *const u8,
882        name_len: usize,
883        out_id: *mut u32,
884    ) -> TwigStatus;
885    pub fn twig_builder_add_element(
886        builder: *mut TwigBuilder,
887        name: *const u8,
888        name_len: usize,
889        out_id: *mut u32,
890    ) -> TwigStatus;
891    pub fn twig_builder_add_processing_instruction(
892        builder: *mut TwigBuilder,
893        target: *const u8,
894        target_len: usize,
895        data: *const u8,
896        data_len: usize,
897        out_id: *mut u32,
898    ) -> TwigStatus;
899    pub fn twig_builder_add_footnote(
900        builder: *mut TwigBuilder,
901        label: *const u8,
902        label_len: usize,
903        out_id: *mut u32,
904    ) -> TwigStatus;
905    pub fn twig_builder_add_citation(
906        builder: *mut TwigBuilder,
907        label: *const u8,
908        label_len: usize,
909        out_id: *mut u32,
910    ) -> TwigStatus;
911    pub fn twig_builder_add_substitution(
912        builder: *mut TwigBuilder,
913        label: *const u8,
914        label_len: usize,
915        out_id: *mut u32,
916    ) -> TwigStatus;
917    pub fn twig_builder_add_reference(
918        builder: *mut TwigBuilder,
919        label: *const u8,
920        label_len: usize,
921        destination: *const u8,
922        destination_len: usize,
923        out_id: *mut u32,
924    ) -> TwigStatus;
925    pub fn twig_builder_add_bullet_list(
926        builder: *mut TwigBuilder,
927        style: c_int,
928        tight: c_int,
929        out_id: *mut u32,
930    ) -> TwigStatus;
931    pub fn twig_builder_add_ordered_list(
932        builder: *mut TwigBuilder,
933        numbering: c_int,
934        delim: c_int,
935        tight: c_int,
936        start: u32,
937        has_start: c_int,
938        out_id: *mut u32,
939    ) -> TwigStatus;
940    pub fn twig_builder_add_task_list(
941        builder: *mut TwigBuilder,
942        tight: c_int,
943        out_id: *mut u32,
944    ) -> TwigStatus;
945    pub fn twig_builder_add_task_list_item(
946        builder: *mut TwigBuilder,
947        checked: c_int,
948        out_id: *mut u32,
949    ) -> TwigStatus;
950    pub fn twig_builder_add_row(
951        builder: *mut TwigBuilder,
952        head: c_int,
953        out_id: *mut u32,
954    ) -> TwigStatus;
955    pub fn twig_builder_add_cell(
956        builder: *mut TwigBuilder,
957        head: c_int,
958        alignment: c_int,
959        out_id: *mut u32,
960    ) -> TwigStatus;
961    pub fn twig_builder_add_cell_spanning(
962        builder: *mut TwigBuilder,
963        head: c_int,
964        alignment: c_int,
965        colspan: u32,
966        rowspan: u32,
967        out_id: *mut u32,
968    ) -> TwigStatus;
969    pub fn twig_builder_set_children(
970        builder: *mut TwigBuilder,
971        parent: u32,
972        ids: *const u32,
973        ids_len: usize,
974    ) -> TwigStatus;
975    pub fn twig_builder_set_attrs(
976        builder: *mut TwigBuilder,
977        id: u32,
978        kvs: *const TwigKeyVal,
979        kvs_len: usize,
980    ) -> TwigStatus;
981    pub fn twig_builder_render_html(
982        builder: *mut TwigBuilder,
983        root: u32,
984        out_ptr: *mut *const u8,
985        out_len: *mut usize,
986    ) -> TwigStatus;
987    pub fn twig_builder_serialize(
988        builder: *mut TwigBuilder,
989        root: u32,
990        format: c_int,
991        out_ptr: *mut *const u8,
992        out_len: *mut usize,
993    ) -> TwigStatus;
994    pub fn twig_builder_ast_json(
995        builder: *mut TwigBuilder,
996        root: u32,
997        out_ptr: *mut *const u8,
998        out_len: *mut usize,
999    ) -> TwigStatus;
1000    pub fn twig_builder_query(
1001        builder: *mut TwigBuilder,
1002        root: u32,
1003        selector: *const u8,
1004        selector_len: usize,
1005        out_ptr: *mut *const TwigQueryMatch,
1006        out_len: *mut usize,
1007    ) -> TwigStatus;
1008}