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