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