nandi/jolt-nativepublic Fork 0
c7d6ea8b8cda7e4ab2805c20463e0d0f770580c6
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

ffi.clj · 116 lines · 5.7 KBClojure Blame HistoryRaw
Bind the terminal backend from the side that renders into it 3cfee15 nandi 17d ago1(ns glimmer-tui.ffi
2 "Raw bindings for libjolttui's retained-tree ABI
3 (`crates/jolt-tui/include/jolttui.h`).
4
5 Nothing here interprets a prop or an event; that is `glimmer-tui.core`'s job.
6 This namespace exists so the boundary is one readable list of symbols, and so
7 the marshalling rules that come with it are stated once.
8
9 **Strings returned by this library are borrowed.** They come out of scratch
10 buffers the next call of the same family overwrites — props, tags and screen
11 lines share one, dumps have their own, and an event's name and its text have
12 one each, so an event can be read whole. Jolt copies a `:string` return into a
13 Scheme string as it crosses, so holding the value is safe.
14
15 **Every call belongs to the thread that opened the session.** The library
16 keeps its state in thread-local storage, so a call from anywhere else is inert
17 rather than unsound — still a bug. `glimmer-tui.core` routes off-thread work
18 through glimmer's `schedule`."
19 (:require [jolt.ffi :as ffi]))
20
21;; --- the session -------------------------------------------------------------
22;; Two ways in, one code path out of them: `raw-open` takes the terminal, and
23;; `raw-headless` opens a session of a fixed size with no terminal at all —
24;; same layout, same painting, same focus ring, read back with `screen-line`.
25(ffi/defcfn raw-open "tui_open" [:int] :int)
26(ffi/defcfn raw-headless "tui_headless" [:int :int] :int)
27(ffi/defcfn close! "tui_close" [] :void)
28(ffi/defcfn raw-should-close "tui_should_close" [] :int)
29(ffi/defcfn quit-loop! "tui_quit" [] :void)
30
31;; --- the loop ----------------------------------------------------------------
32;; `tick` waits up to timeout_ms for input and answers how much it handled;
33;; `frame!` lays the tree out, paints it and sends only the cells that changed.
34(ffi/defcfn tick "tui_tick" [:int] :int)
35(ffi/defcfn frame! "tui_frame" [] :void)
36
37;; --- the screen, in cells ----------------------------------------------------
38(ffi/defcfn screen-width "tui_screen_width" [] :int)
39(ffi/defcfn screen-height "tui_screen_height" [] :int)
40(ffi/defcfn screen-line "tui_screen_line" [:int] :string)
41
42;; --- input by hand -----------------------------------------------------------
43;; The same entry points a real terminal's input arrives through, so a test
44;; drives the UI exactly as a person does.
45(ffi/defcfn raw-feed-key "tui_feed_key" [:string] :int)
46(ffi/defcfn raw-feed-click "tui_feed_click" [:int :int] :int)
47(ffi/defcfn raw-feed-wheel "tui_feed_wheel" [:int :int :int] :int)
48(ffi/defcfn focus "tui_focus" [] :int)
49
50;; --- the tree ----------------------------------------------------------------
51(ffi/defcfn tree-root "tui_tree_root" [] :int)
52(ffi/defcfn node-new "tui_node_new" [:string] :int)
53(ffi/defcfn node-free! "tui_node_free" [:int] :void)
54(ffi/defcfn raw-node-exists "tui_node_exists" [:int] :int)
55
56(ffi/defcfn node-set-str! "tui_node_set_str" [:int :string :string] :void)
57(ffi/defcfn node-set-num! "tui_node_set_num" [:int :string :double] :void)
58(ffi/defcfn raw-node-set-bool "tui_node_set_bool" [:int :string :int] :void)
59(ffi/defcfn node-clear-props! "tui_node_clear_props" [:int] :void)
60
61(ffi/defcfn node-get-str "tui_node_get_str" [:int :string] :string)
62(ffi/defcfn node-get-num "tui_node_get_num" [:int :string] :double)
63(ffi/defcfn raw-node-get-bool "tui_node_get_bool" [:int :string] :int)
64
65(ffi/defcfn node-tag "tui_node_tag" [:int] :string)
66(ffi/defcfn node-parent "tui_node_parent" [:int] :int)
67(ffi/defcfn node-child-count "tui_node_child_count" [:int] :int)
68(ffi/defcfn node-child-at "tui_node_child_at" [:int :int] :int)
69(ffi/defcfn tree-dump "tui_tree_dump" [:int] :string)
70
71(ffi/defcfn raw-node-append "tui_node_append" [:int :int] :int)
72(ffi/defcfn node-remove! "tui_node_remove" [:int :int] :void)
73(ffi/defcfn raw-node-insert-after "tui_node_insert_after" [:int :int :int] :int)
74(ffi/defcfn raw-node-replace "tui_node_replace" [:int :int :int] :int)
75
76;; --- events ------------------------------------------------------------------
77(ffi/defcfn raw-poll-event "tui_tree_poll_event" [] :int)
78(ffi/defcfn event-node "tui_tree_event_node" [] :int)
79(ffi/defcfn event-name "tui_tree_event_name" [] :string)
80(ffi/defcfn event-text "tui_tree_event_text" [] :string)
81(ffi/defcfn event-num "tui_tree_event_num" [] :double)
82
83;; --- the int/bool seam -------------------------------------------------------
84;; C has no booleans; every predicate here crosses as 0 or 1. Converting at the
85;; binding rather than at each call site keeps the rest of the backend written
86;; in jolt's own truthiness.
87
88(defn open!
89 "Take the terminal: raw mode, the alternate screen, the cursor hidden unless a
90 focused entry asks for it, and mouse reporting when `mouse?`."
91 [mouse?]
92 (not (zero? (raw-open (if mouse? 1 0)))))
93
94(defn headless!
95 "Open a session of `width` by `height` cells with no terminal at all."
96 [width height]
97 (not (zero? (raw-headless width height))))
98
99(defn should-close? [] (not (zero? (raw-should-close))))
100(defn node-exists? [node] (not (zero? (raw-node-exists node))))
101(defn node-set-bool! [node key value] (raw-node-set-bool node key (if value 1 0)))
102(defn node-get-bool [node key] (not (zero? (raw-node-get-bool node key))))
103(defn node-append! [parent child] (not (zero? (raw-node-append parent child))))
104(defn node-insert-after! [parent child sibling]
105 (not (zero? (raw-node-insert-after parent child sibling))))
106(defn node-replace! [parent old new] (not (zero? (raw-node-replace parent old new))))
107(defn poll-event! [] (not (zero? (raw-poll-event))))
108
109(defn feed-key!
110 "Type one key, named as the terminal names it. True when the backend acted on
111 it, false when it went out as a `key` event instead."
112 [name]
113 (not (zero? (raw-feed-key name))))
114
115(defn feed-click! [x y] (not (zero? (raw-feed-click x y))))
116(defn feed-wheel! [x y by] (not (zero? (raw-feed-wheel x y by))))