Skip to main content

tern_sdk/
wire.rs

1//! Layer 1: the wire. Constants, the messages a program sends and receives,
2//! the encoder (framing, chunking, blobs) and the decoder of replies and
3//! events.
4//!
5//! Every message is one APC string, `ESC _ tsp;<verb>[;k=v]*;<body> ESC \`.
6//! A body over the negotiated `apc` limit is cut by [`chunks`] exactly as
7//! Tern's own `tsp_chunks` cuts it, and each piece is sent with `c=<id>`
8//! (and `m=1` on all but the last).
9
10use std::{collections::BTreeMap, fmt};
11
12use base64::Engine as _;
13use serde::{Deserialize, Serialize, Serializer, de::DeserializeOwned, ser::SerializeSeq};
14use serde_json::{Map, Value};
15use sha2::{Digest as _, Sha256};
16
17use crate::Error;
18
19/// The protocol version this SDK speaks.
20pub const VERSION: u32 = 1;
21/// The largest body sent in one message unless the `hello` reply says
22/// otherwise.
23pub const APC_LIMIT: usize = 65_536;
24/// How many frames may be unacknowledged unless the `hello` reply says
25/// otherwise.
26pub const CREDITS: u32 = 2;
27/// The largest blob Tern takes, in decoded bytes.
28pub const MAX_BLOB: usize = 16 << 20;
29
30/// Node kinds, as the `k` of a node and the `kinds` of the `hello` reply.
31pub mod kind {
32	/// A vertical stack.
33	pub const COL: &str = "col";
34	/// A horizontal flex row.
35	pub const ROW: &str = "row";
36	/// A card with a head and a body.
37	pub const CARD: &str = "card";
38	/// A disclosure group with no ring.
39	pub const SECTION: &str = "section";
40	/// A divider hairline.
41	pub const RULE: &str = "rule";
42	/// Vertical space.
43	pub const SPACER: &str = "spacer";
44	/// Styled spans.
45	pub const TEXT: &str = "text";
46	/// Markdown.
47	pub const MD: &str = "md";
48	/// Highlighted code.
49	pub const CODE: &str = "code";
50	/// A unified or split diff.
51	pub const DIFF: &str = "diff";
52	/// Output with escape sequences.
53	pub const ANSI: &str = "ansi";
54	/// Pre-rendered ANSI rows.
55	pub const ROWS: &str = "rows";
56	/// TeX math.
57	pub const MATH: &str = "math";
58	/// An aligned key/value list.
59	pub const KV: &str = "kv";
60	/// A table.
61	pub const TABLE: &str = "table";
62	/// A disclosure tree.
63	pub const TREE: &str = "tree";
64	/// A pill chip.
65	pub const BADGE: &str = "badge";
66	/// Keycaps.
67	pub const KBD: &str = "kbd";
68	/// A named icon.
69	pub const ICON: &str = "icon";
70	/// An image from a blob.
71	pub const IMAGE: &str = "image";
72	/// A selectable list.
73	pub const LIST: &str = "list";
74	/// One list row.
75	pub const ITEM: &str = "item";
76	/// A tab strip.
77	pub const TABS: &str = "tabs";
78	/// A searchable picker sheet.
79	pub const PICKER: &str = "picker";
80	/// An activity indicator.
81	pub const SPINNER: &str = "spinner";
82	/// A shimmering label.
83	pub const SHIMMER: &str = "shimmer";
84	/// A live timer.
85	pub const ELAPSED: &str = "elapsed";
86	/// A number easing between updates.
87	pub const RATE: &str = "rate";
88	/// A progress bar.
89	pub const PROGRESS: &str = "progress";
90	/// A value as a bar, ring or grid.
91	pub const METER: &str = "meter";
92	/// Bars, a sparkline or a heatmap.
93	pub const CHART: &str = "chart";
94	/// A thinking-effort glyph.
95	pub const EFFORT: &str = "effort";
96	/// A multi-line text field.
97	pub const EDITOR: &str = "editor";
98	/// A single-line text field.
99	pub const INPUT: &str = "input";
100	/// A status bar strip.
101	pub const STATUS: &str = "status";
102	/// A status bar segment.
103	pub const SEG: &str = "seg";
104	/// A transient notice.
105	pub const TOAST: &str = "toast";
106	/// A floating panel.
107	pub const OVERLAY: &str = "overlay";
108	/// A coding-agent tool call.
109	pub const TOOL: &str = "tool";
110	/// A subagent row.
111	pub const AGENT: &str = "agent";
112	/// A todo list.
113	pub const CHECKLIST: &str = "checklist";
114	/// A shell command's output (Tern's own).
115	pub const BLOCK: &str = "block";
116	/// A settings page.
117	pub const PREFS: &str = "prefs";
118	/// A plain HTML element.
119	pub const EL: &str = "el";
120
121	/// Every kind of protocol version 1.
122	pub const ALL: [&str; 44] = [
123		COL, ROW, CARD, SECTION, RULE, SPACER, TEXT, MD, CODE, DIFF, ANSI, ROWS, MATH, KV, TABLE,
124		TREE, BADGE, KBD, ICON, IMAGE, LIST, ITEM, TABS, PICKER, SPINNER, SHIMMER, ELAPSED, RATE,
125		PROGRESS, METER, CHART, EFFORT, EDITOR, INPUT, STATUS, SEG, TOAST, OVERLAY, TOOL, AGENT,
126		CHECKLIST, BLOCK, PREFS, EL,
127	];
128
129	/// The kinds whose `text` prop is primary text, updated with `text` and
130	/// `splice` ops.
131	pub const TEXT_KINDS: [&str; 9] = [TEXT, MD, CODE, ANSI, MATH, EDITOR, INPUT, SHIMMER, EL];
132
133	/// Whether `kind` keeps primary text in its `text` prop.
134	pub fn has_text(kind: &str) -> bool {
135		TEXT_KINDS.contains(&kind)
136	}
137}
138
139/// Feature names of the `hello` query (program) and reply (terminal).
140pub mod feature {
141	/// Program: applies `edit` events (native editing).
142	pub const EDIT: &str = "edit";
143	/// Program: applies `undo` events.
144	pub const UNDO: &str = "undo";
145	/// Program: submits text from `send` events.
146	pub const SEND: &str = "send";
147
148	/// Terminal: the `b` verb and the `blobs` query.
149	pub const BLOBS: &str = "blobs";
150	/// Terminal: the `settle` op is a useful hint.
151	pub const SETTLE: &str = "settle";
152	/// Terminal: `o` with `adopt:true` reopens a closed surface.
153	pub const ADOPT: &str = "adopt";
154	/// Terminal: the `dock` region sticks to the pane's bottom.
155	pub const DOCK: &str = "dock";
156	/// Terminal: the `t` verb colors the surface.
157	pub const PROGRAM_PALETTE: &str = "program-palette";
158	/// Terminal: `reduceMotion` and `motion` events are reported.
159	pub const REDUCE_MOTION: &str = "reduce-motion";
160	/// Terminal: a `prefs` node in an inline surface's `layer` docks beside
161	/// the pane.
162	pub const ASIDE: &str = "aside";
163	/// Terminal: the `scroll` op.
164	pub const SCROLL: &str = "scroll";
165	/// Terminal: the `s` verb (stylesheets).
166	pub const STYLES: &str = "styles";
167	/// Terminal: `flow` surfaces and `listen:false`.
168	pub const FLOW: &str = "flow";
169}
170
171// ---------------------------------------------------------------------------
172// Program → terminal
173// ---------------------------------------------------------------------------
174
175/// A surface mode.
176#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
177#[serde(rename_all = "lowercase")]
178pub enum Mode {
179	/// At the cursor row, owning the pane while live (a session program).
180	#[default]
181	Inline,
182	/// Over the whole pane, like the alternate screen.
183	Screen,
184	/// At the cursor row among the output around it, kept as output.
185	Flow,
186}
187
188/// A `q` query.
189#[derive(Clone, Debug, PartialEq, Eq)]
190pub enum Query {
191	/// `{"q":"hello","v":[1],"app"?,"ver"?,"features"?}`.
192	Hello {
193		/// The program's name.
194		app:      Option<String>,
195		/// The program's version.
196		ver:      Option<String>,
197		/// Program features (`edit`, `undo`, `send`).
198		features: Vec<String>,
199	},
200	/// `{"q":"blobs","ids":[…]}`: which blobs Tern still holds.
201	Blobs {
202		/// Blob ids (lowercase SHA-256 hex).
203		ids: Vec<String>,
204	},
205}
206
207impl Serialize for Query {
208	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
209		use serde::ser::SerializeMap as _;
210		match self {
211			Self::Hello { app, ver, features } => {
212				let mut m = s.serialize_map(None)?;
213				m.serialize_entry("q", "hello")?;
214				m.serialize_entry("v", &[VERSION])?;
215				if let Some(app) = app {
216					m.serialize_entry("app", app)?;
217				}
218				if let Some(ver) = ver {
219					m.serialize_entry("ver", ver)?;
220				}
221				if !features.is_empty() {
222					m.serialize_entry("features", features)?;
223				}
224				m.end()
225			},
226			Self::Blobs { ids } => {
227				let mut m = s.serialize_map(Some(2))?;
228				m.serialize_entry("q", "blobs")?;
229				m.serialize_entry("ids", ids)?;
230				m.end()
231			},
232		}
233	}
234}
235
236/// An `o` message: open a surface.
237#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
238pub struct Open {
239	/// The surface id; frames and events carry it as `sf`.
240	pub id:     String,
241	/// The mode (`inline` when absent).
242	#[serde(skip_serializing_if = "Option::is_none")]
243	pub mode:   Option<Mode>,
244	/// Names the pane until the program sets its own title.
245	#[serde(skip_serializing_if = "Option::is_none")]
246	pub title:  Option<String>,
247	/// `data-surface` on the region elements, for stylesheets.
248	#[serde(skip_serializing_if = "Option::is_none")]
249	pub role:   Option<String>,
250	/// `false`: the program never reads its input.
251	#[serde(skip_serializing_if = "Option::is_none")]
252	pub listen: Option<bool>,
253	/// `true` reopens a closed inline surface with this id.
254	#[serde(skip_serializing_if = "Option::is_none")]
255	pub adopt:  Option<bool>,
256}
257
258/// An `f` message: an atomic batch of ops on one surface.
259#[derive(Clone, Debug, PartialEq, Serialize)]
260pub struct Frame {
261	/// The surface id.
262	pub sf:  String,
263	/// The frame's sequence number, from 1 per surface.
264	pub s:   u64,
265	/// The ops, applied in order.
266	pub ops: Vec<Op>,
267}
268
269/// How a `text` op changes primary text.
270#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
271#[serde(rename_all = "lowercase")]
272pub enum TextMode {
273	/// Add to the end (the streaming path).
274	Append,
275	/// Set the whole text.
276	Replace,
277}
278
279/// Where a `reveal` op scrolls a node.
280#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
281#[serde(rename_all = "lowercase")]
282pub enum RevealAt {
283	/// The node's start at the scroller's start.
284	Start,
285	/// The node's end at the scroller's end.
286	End,
287	/// The least scrolling that shows the node.
288	#[default]
289	Nearest,
290}
291
292/// How far a `scroll` op moves.
293#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
294#[serde(rename_all = "kebab-case")]
295pub enum ScrollBy {
296	/// One line up.
297	LineUp,
298	/// One line down.
299	LineDown,
300	/// A viewport less a line up.
301	PageUp,
302	/// A viewport less a line down.
303	PageDown,
304	/// To the start.
305	Start,
306	/// To the end (resumes following).
307	End,
308}
309
310/// A node with its id, as an `add` op carries it.
311#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
312pub struct WireNode {
313	/// The node id.
314	pub id: String,
315	/// The kind.
316	pub k:  String,
317	/// Props (omitted when empty).
318	#[serde(default, skip_serializing_if = "Map::is_empty")]
319	pub p:  Map<String, Value>,
320	/// Children (omitted when empty).
321	#[serde(default, skip_serializing_if = "Vec::is_empty")]
322	pub c:  Vec<Self>,
323}
324
325/// One frame op.
326#[derive(Clone, Debug, PartialEq)]
327pub enum Op {
328	/// `["add", id, parent, before|null, node]`.
329	Add {
330		/// Parent id.
331		parent: String,
332		/// The sibling to insert before, or last.
333		before: Option<String>,
334		/// The subtree; its id is the op's.
335		node:   WireNode,
336	},
337	/// `["set", id, props]`: merge props shallowly; `null` deletes.
338	Set {
339		/// Node id.
340		id:    String,
341		/// Props to replace.
342		props: Map<String, Value>,
343	},
344	/// `["text", id, mode, string]`.
345	Text {
346		/// Node id.
347		id:   String,
348		/// Append or replace.
349		mode: TextMode,
350		/// The text.
351		text: String,
352	},
353	/// `["splice", id, at, del, string]` in UTF-16 units.
354	Splice {
355		/// Node id.
356		id:   String,
357		/// Offset in UTF-16 units.
358		at:   usize,
359		/// UTF-16 units removed.
360		del:  usize,
361		/// Text inserted.
362		text: String,
363	},
364	/// `["move", id, parent, before|null]`.
365	Move {
366		/// Node id.
367		id:     String,
368		/// New parent id.
369		parent: String,
370		/// The sibling to move before, or last.
371		before: Option<String>,
372	},
373	/// `["del", id]`.
374	Del {
375		/// Node id.
376		id: String,
377	},
378	/// `["settle", id]`: a hint that the subtree won't change soon.
379	Settle {
380		/// Node id.
381		id: String,
382	},
383	/// `["focus", id|null]`: the field owning the caret.
384	Focus {
385		/// Field id, or none.
386		id: Option<String>,
387	},
388	/// `["reveal", id, at]`.
389	Reveal {
390		/// Node id.
391		id: String,
392		/// Where to scroll it.
393		at: RevealAt,
394	},
395	/// `["scroll", id, by]`.
396	Scroll {
397		/// A node in the scroll container.
398		id: String,
399		/// How far.
400		by: ScrollBy,
401	},
402	/// `["suspend"]`: hand the pane back to the grid.
403	Suspend,
404	/// `["resume"]`: take the pane again.
405	Resume,
406}
407
408impl Serialize for Op {
409	fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
410		let mut seq = s.serialize_seq(None)?;
411		match self {
412			Self::Add { parent, before, node } => {
413				seq.serialize_element("add")?;
414				seq.serialize_element(&node.id)?;
415				seq.serialize_element(parent)?;
416				seq.serialize_element(before)?;
417				seq.serialize_element(node)?;
418			},
419			Self::Set { id, props } => {
420				seq.serialize_element("set")?;
421				seq.serialize_element(id)?;
422				seq.serialize_element(props)?;
423			},
424			Self::Text { id, mode, text } => {
425				seq.serialize_element("text")?;
426				seq.serialize_element(id)?;
427				seq.serialize_element(mode)?;
428				seq.serialize_element(text)?;
429			},
430			Self::Splice { id, at, del, text } => {
431				seq.serialize_element("splice")?;
432				seq.serialize_element(id)?;
433				seq.serialize_element(at)?;
434				seq.serialize_element(del)?;
435				seq.serialize_element(text)?;
436			},
437			Self::Move { id, parent, before } => {
438				seq.serialize_element("move")?;
439				seq.serialize_element(id)?;
440				seq.serialize_element(parent)?;
441				seq.serialize_element(before)?;
442			},
443			Self::Del { id } => {
444				seq.serialize_element("del")?;
445				seq.serialize_element(id)?;
446			},
447			Self::Settle { id } => {
448				seq.serialize_element("settle")?;
449				seq.serialize_element(id)?;
450			},
451			Self::Focus { id } => {
452				seq.serialize_element("focus")?;
453				seq.serialize_element(id)?;
454			},
455			Self::Reveal { id, at } => {
456				seq.serialize_element("reveal")?;
457				seq.serialize_element(id)?;
458				seq.serialize_element(at)?;
459			},
460			Self::Scroll { id, by } => {
461				seq.serialize_element("scroll")?;
462				seq.serialize_element(id)?;
463				seq.serialize_element(by)?;
464			},
465			Self::Suspend => seq.serialize_element("suspend")?,
466			Self::Resume => seq.serialize_element("resume")?,
467		}
468		seq.end()
469	}
470}
471
472/// The names of a palette's two variants.
473#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
474pub struct PaletteNames {
475	/// The dark variant's name.
476	#[serde(skip_serializing_if = "Option::is_none")]
477	pub dark:  Option<String>,
478	/// The light variant's name.
479	#[serde(skip_serializing_if = "Option::is_none")]
480	pub light: Option<String>,
481}
482
483/// A `t` message: the program palette, token → `#rrggbb` per variant.
484#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
485pub struct Palette {
486	/// The surface (the live one when absent).
487	#[serde(skip_serializing_if = "Option::is_none")]
488	pub sf:    Option<String>,
489	/// The dark variant.
490	#[serde(skip_serializing_if = "Option::is_none")]
491	pub dark:  Option<BTreeMap<String, String>>,
492	/// The light variant.
493	#[serde(skip_serializing_if = "Option::is_none")]
494	pub light: Option<BTreeMap<String, String>>,
495	/// The variants' names.
496	#[serde(skip_serializing_if = "Option::is_none")]
497	pub name:  Option<PaletteNames>,
498}
499
500/// An `s` message: install, replace or (without `css`) remove a stylesheet.
501#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
502pub struct Sheet {
503	/// The surface (the live one when absent).
504	#[serde(skip_serializing_if = "Option::is_none")]
505	pub sf:   Option<String>,
506	/// The sheet's name (1–64 of `A-Z a-z 0-9 _ -`).
507	pub name: String,
508	/// The CSS; absent removes the sheet.
509	#[serde(skip_serializing_if = "Option::is_none")]
510	pub css:  Option<String>,
511}
512
513/// An `x` message: close a surface.
514#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
515pub struct Close {
516	/// The surface id.
517	pub id:   String,
518	/// Keep it on screen (`main` stays in the scrollback).
519	pub keep: bool,
520}
521
522/// A `b` message: binary data named by its SHA-256.
523#[derive(Clone, Debug, PartialEq, Eq)]
524pub struct Blob {
525	/// The lowercase hex SHA-256 of `data`.
526	pub id:   String,
527	/// The MIME type.
528	pub mime: Option<String>,
529	/// The bytes.
530	pub data: Vec<u8>,
531}
532
533impl Blob {
534	/// A blob of `data`, its id computed.
535	///
536	/// # Errors
537	/// [`Error::BlobTooLarge`] over [`MAX_BLOB`] bytes.
538	pub fn new(data: Vec<u8>, mime: Option<String>) -> Result<Self, Error> {
539		if data.len() > MAX_BLOB {
540			return Err(Error::BlobTooLarge(data.len()));
541		}
542		Ok(Self { id: blob_id(&data), mime, data })
543	}
544}
545
546/// The id of a blob: the lowercase hex SHA-256 of its bytes.
547pub fn blob_id(data: &[u8]) -> String {
548	use fmt::Write as _;
549	let digest = Sha256::digest(data);
550	let mut out = String::with_capacity(64);
551	for b in digest {
552		let _ = write!(out, "{b:02x}");
553	}
554	out
555}
556
557/// Any program → terminal message.
558#[derive(Clone, Debug, PartialEq)]
559pub enum Message {
560	/// `q`.
561	Query(Query),
562	/// `o`.
563	Open(Open),
564	/// `f`.
565	Frame(Frame),
566	/// `b`.
567	Blob(Blob),
568	/// `t`.
569	Palette(Palette),
570	/// `s`.
571	Sheet(Sheet),
572	/// `x`.
573	Close(Close),
574}
575
576impl Message {
577	/// The one-letter verb.
578	pub const fn verb(&self) -> &'static str {
579		match self {
580			Self::Query(_) => "q",
581			Self::Open(_) => "o",
582			Self::Frame(_) => "f",
583			Self::Blob(_) => "b",
584			Self::Palette(_) => "t",
585			Self::Sheet(_) => "s",
586			Self::Close(_) => "x",
587		}
588	}
589
590	/// The parameters, in order (only a blob has any).
591	pub fn params(&self) -> Vec<(&'static str, &str)> {
592		match self {
593			Self::Blob(b) => {
594				let mut out = vec![("id", b.id.as_str())];
595				if let Some(mime) = &b.mime {
596					out.push(("mime", mime.as_str()));
597				}
598				out
599			},
600			_ => Vec::new(),
601		}
602	}
603
604	/// The body: compact JSON, or base64 for a blob.
605	///
606	/// # Errors
607	/// When serialization fails (it doesn't for these types).
608	pub fn body(&self) -> Result<String, Error> {
609		Ok(match self {
610			Self::Query(m) => serde_json::to_string(m)?,
611			Self::Open(m) => serde_json::to_string(m)?,
612			Self::Frame(m) => serde_json::to_string(m)?,
613			Self::Blob(b) => base64::engine::general_purpose::STANDARD.encode(&b.data),
614			Self::Palette(m) => serde_json::to_string(m)?,
615			Self::Sheet(m) => serde_json::to_string(m)?,
616			Self::Close(m) => serde_json::to_string(m)?,
617		})
618	}
619}
620
621/// A byte of a parameter key.
622const fn key_byte(b: u8) -> bool {
623	b.is_ascii_alphanumeric() || b == b'_' || b == b'-'
624}
625
626/// A byte of a parameter value: printable ASCII other than `;`.
627const fn value_byte(b: u8) -> bool {
628	b >= 0x21 && b <= 0x7e && b != b';'
629}
630
631/// Whether `rest`, placed after a message's parameters, would be read as one
632/// more parameter: a `key=value` segment with a `;` after it.
633fn leads_with_parameter(rest: &[u8]) -> bool {
634	let key = rest.iter().take_while(|&&b| key_byte(b)).count();
635	if key == 0 || rest.get(key) != Some(&b'=') {
636		return false;
637	}
638	rest[key + 1..].iter().find(|&&b| !value_byte(b)) == Some(&b';')
639}
640
641/// Where the chunk of `body` starting at `start` ends.
642fn chunk_end(body: &[u8], start: usize, limit: usize) -> usize {
643	let target = start.saturating_add(limit.max(1));
644	if target >= body.len() {
645		return body.len();
646	}
647	let safe = |at: usize| body[at] & 0xc0 != 0x80 && !leads_with_parameter(&body[at..]);
648	(start + 1..=target)
649		.rev()
650		.find(|&at| safe(at))
651		.or_else(|| (target + 1..body.len()).find(|&at| safe(at)))
652		.unwrap_or(body.len())
653}
654
655/// Splits a body into chunk bodies exactly as Tern's `tsp_chunks` does.
656///
657/// Each piece is at most `limit` bytes, cut where a cut is safe: on a UTF-8
658/// boundary, and never where the next piece would lead with a
659/// parameter-shaped segment. A body within `limit` is one chunk; an empty
660/// body has none.
661pub fn chunks(body: &[u8], limit: usize) -> impl Iterator<Item = &[u8]> {
662	let mut start = 0;
663	std::iter::from_fn(move || {
664		(start < body.len()).then(|| {
665			let end = chunk_end(body, start, limit);
666			let piece = &body[start..end];
667			start = end;
668			piece
669		})
670	})
671}
672
673/// Appends one APC string `ESC _ tsp;<verb>;<params…>;<body> ESC \`.
674fn push_apc(out: &mut Vec<u8>, verb: &str, params: &[(&str, &str)], body: &[u8]) {
675	out.extend_from_slice(b"\x1b_tsp;");
676	out.extend_from_slice(verb.as_bytes());
677	out.push(b';');
678	for (k, v) in params {
679		out.extend_from_slice(k.as_bytes());
680		out.push(b'=');
681		out.extend_from_slice(v.as_bytes());
682		out.push(b';');
683	}
684	out.extend_from_slice(body);
685	out.extend_from_slice(b"\x1b\\");
686}
687
688/// Encodes one message of `verb` with `params` and an already serialized
689/// `body`, chunked under `limit` with chunk id `chunk` when it is longer.
690///
691/// # Example
692/// ```
693/// let out = tern_sdk::wire::encode("o", &[], br#"{"id":"s1"}"#, 65536, "1");
694/// assert_eq!(out, b"\x1b_tsp;o;{\"id\":\"s1\"}\x1b\\");
695/// ```
696pub fn encode(
697	verb: &str,
698	params: &[(&str, &str)],
699	body: &[u8],
700	limit: usize,
701	chunk: &str,
702) -> Vec<u8> {
703	let mut out = Vec::with_capacity(body.len() + 16);
704	if body.len() <= limit {
705		push_apc(&mut out, verb, params, body);
706		return out;
707	}
708	let pieces: Vec<&[u8]> = chunks(body, limit).collect();
709	let last = pieces.len() - 1;
710	for (i, piece) in pieces.into_iter().enumerate() {
711		let mut ps: Vec<(&str, &str)> = if i == 0 { params.to_vec() } else { Vec::new() };
712		ps.push(("c", chunk));
713		if i < last {
714			ps.push(("m", "1"));
715		}
716		push_apc(&mut out, verb, &ps, piece);
717	}
718	out
719}
720
721/// Encodes messages under the session's `apc` limit, numbering chunk
722/// sequences with a base-36 counter.
723#[derive(Clone, Debug)]
724pub struct Encoder {
725	/// The largest body per APC string.
726	limit:      usize,
727	/// The next chunk id.
728	next_chunk: u64,
729}
730
731impl Default for Encoder {
732	fn default() -> Self {
733		Self::new(APC_LIMIT)
734	}
735}
736
737impl Encoder {
738	/// An encoder chunking bodies over `limit` bytes.
739	pub const fn new(limit: usize) -> Self {
740		Self { limit, next_chunk: 1 }
741	}
742
743	/// The limit in use.
744	pub const fn limit(&self) -> usize {
745		self.limit
746	}
747
748	/// Changes the limit (the `hello` reply's `apc`).
749	pub const fn set_limit(&mut self, limit: usize) {
750		self.limit = limit;
751	}
752
753	/// The bytes of `message`, chunked when its body is over the limit.
754	///
755	/// # Errors
756	/// When the body can't be serialized.
757	pub fn encode(&mut self, message: &Message) -> Result<Vec<u8>, Error> {
758		let body = message.body()?;
759		let chunk = if body.len() > self.limit {
760			let id = base36(self.next_chunk);
761			self.next_chunk += 1;
762			id
763		} else {
764			String::new()
765		};
766		Ok(encode(message.verb(), &message.params(), body.as_bytes(), self.limit, &chunk))
767	}
768}
769
770/// `n` in lowercase base 36.
771fn base36(mut n: u64) -> String {
772	const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
773	let mut out = Vec::new();
774	loop {
775		out.push(DIGITS[(n % 36) as usize]);
776		n /= 36;
777		if n == 0 {
778			break;
779		}
780	}
781	out.reverse();
782	String::from_utf8(out).unwrap_or_default()
783}
784
785// ---------------------------------------------------------------------------
786// Terminal → program
787// ---------------------------------------------------------------------------
788
789/// A cell size in pixels.
790#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
791pub struct Cell {
792	/// Width.
793	#[serde(default)]
794	pub w: f64,
795	/// Height.
796	#[serde(default)]
797	pub h: f64,
798}
799
800/// The `hello` reply: what the terminal draws and supports.
801#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
802#[serde(rename_all = "camelCase")]
803pub struct Hello {
804	/// The version picked (`1`).
805	#[serde(default)]
806	pub v:             u32,
807	/// The terminal (`tern`).
808	#[serde(default)]
809	pub term:          String,
810	/// The terminal's version.
811	#[serde(default)]
812	pub ver:           String,
813	/// Every kind it draws.
814	#[serde(default)]
815	pub kinds:         Vec<String>,
816	/// Terminal features.
817	#[serde(default)]
818	pub features:      Vec<String>,
819	/// The largest body per message.
820	#[serde(default = "default_apc")]
821	pub apc:           usize,
822	/// Frames that may be unacknowledged.
823	#[serde(default = "default_credits")]
824	pub credits:       u32,
825	/// The pane's width in columns.
826	#[serde(default)]
827	pub cols:          u32,
828	/// The cell size.
829	#[serde(default)]
830	pub cell:          Option<Cell>,
831	/// Whether the terminal shows its dark appearance.
832	#[serde(default)]
833	pub dark:          bool,
834	/// Whether Reduce Motion is on.
835	#[serde(default)]
836	pub reduce_motion: bool,
837	/// Whether the user's system reads a 12-hour clock (`3:05 PM`); false
838	/// (24-hour) on terminals that don't say.
839	#[serde(default)]
840	pub hour12:        bool,
841	/// The reply as received.
842	#[serde(skip)]
843	pub raw:           Map<String, Value>,
844}
845
846/// The default of `apc`.
847const fn default_apc() -> usize {
848	APC_LIMIT
849}
850
851/// The default of `credits`.
852const fn default_credits() -> u32 {
853	CREDITS
854}
855
856/// The `blobs` reply: which of the asked blobs Tern holds.
857#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
858pub struct BlobsReply {
859	/// The ids held, in the order asked.
860	#[serde(default)]
861	pub have: Vec<String>,
862	/// The reply as received.
863	#[serde(skip)]
864	pub raw:  Map<String, Value>,
865}
866
867/// An `r` message: the reply to a query.
868#[derive(Clone, Debug, PartialEq)]
869pub enum Reply {
870	/// `hello`.
871	Hello(Hello),
872	/// `blobs`.
873	Blobs(BlobsReply),
874	/// Any other reply, kept raw.
875	Unknown(Map<String, Value>),
876}
877
878/// Types decoded from a raw object that keep it.
879trait Raw: DeserializeOwned {
880	/// Stores the raw object.
881	fn set_raw(&mut self, raw: Map<String, Value>);
882}
883
884/// Decodes `map` as `T`, or `None` when its fields don't fit.
885fn typed<T: Raw>(map: Map<String, Value>) -> Result<T, Map<String, Value>> {
886	let value = Value::Object(map);
887	let decoded = T::deserialize(&value);
888	let Value::Object(map) = value else {
889		unreachable!("built as an object")
890	};
891	match decoded {
892		Ok(mut t) => {
893			t.set_raw(map);
894			Ok(t)
895		},
896		Err(_) => Err(map),
897	}
898}
899
900impl Reply {
901	/// Decodes a reply body; fields that don't fit keep it raw.
902	pub fn decode(map: Map<String, Value>) -> Self {
903		match map.get("r").and_then(Value::as_str) {
904			Some("hello") => typed(map).map_or_else(Self::Unknown, Self::Hello),
905			Some("blobs") => typed(map).map_or_else(Self::Unknown, Self::Blobs),
906			_ => Self::Unknown(map),
907		}
908	}
909
910	/// The reply as received.
911	pub const fn raw(&self) -> &Map<String, Value> {
912		match self {
913			Self::Hello(r) => &r.raw,
914			Self::Blobs(r) => &r.raw,
915			Self::Unknown(r) => r,
916		}
917	}
918}
919
920/// Defines an event payload struct that keeps its raw object.
921macro_rules! event_struct {
922	($(#[$m:meta])* $name:ident { $($(#[$fm:meta])* $field:ident : $ty:ty),* $(,)? }) => {
923		$(#[$m])*
924		#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
925		#[allow(clippy::derive_partial_eq_without_eq, reason = "some payloads hold floats")]
926		pub struct $name {
927			$($(#[$fm])* #[serde(default)] pub $field: $ty,)*
928			/// The event as received.
929			#[serde(skip)]
930			pub raw: Map<String, Value>,
931		}
932
933		impl Raw for $name {
934			fn set_raw(&mut self, raw: Map<String, Value>) {
935				self.raw = raw;
936			}
937		}
938	};
939}
940
941impl Raw for Hello {
942	fn set_raw(&mut self, raw: Map<String, Value>) {
943		self.raw = raw;
944	}
945}
946
947impl Raw for BlobsReply {
948	fn set_raw(&mut self, raw: Map<String, Value>) {
949		self.raw = raw;
950	}
951}
952
953event_struct!(
954	/// `ack`: the highest frame applied and drawn.
955	Ack {
956		/// The surface.
957		sf: String,
958		/// The frame's sequence number.
959		s:  u64,
960	}
961);
962
963event_struct!(
964	/// `resize`: the pane's width or cell size changed.
965	Resize {
966		/// The surface.
967		sf: Option<String>,
968		/// The width in columns.
969		cols: u32,
970		/// The cell size.
971		cell: Option<Cell>,
972		/// Whether the pane is visible.
973		visible: Option<bool>,
974	}
975);
976
977event_struct!(
978	/// `theme`: the appearance switched.
979	Theme {
980		/// Whether it is dark now.
981		dark: bool,
982	}
983);
984
985event_struct!(
986	/// `motion`: Reduce Motion was toggled.
987	Motion {
988		/// Whether motion is reduced now.
989		reduce: bool,
990	}
991);
992
993event_struct!(
994	/// `visible`: the pane was hidden or shown.
995	Visible {
996		/// The surface.
997		sf: Option<String>,
998		/// Whether it shows.
999		visible: bool,
1000	}
1001);
1002
1003event_struct!(
1004	/// `toggle`: a collapsible node was folded or unfolded.
1005	Toggle {
1006		/// The surface.
1007		sf: Option<String>,
1008		/// The node.
1009		id: String,
1010		/// Whether it is folded now.
1011		collapsed: bool,
1012		/// The node's `key` prop (or a `tree` item).
1013		key: Option<Value>,
1014	}
1015);
1016
1017event_struct!(
1018	/// `select` or `activate`: an item was picked or opened.
1019	Select {
1020		/// The surface.
1021		sf: Option<String>,
1022		/// The node (the list, for a list's item).
1023		id: String,
1024		/// The item.
1025		item: String,
1026		/// Form values, inside an `el` form.
1027		values: Option<Map<String, Value>>,
1028	}
1029);
1030
1031event_struct!(
1032	/// `action`: a pointer action with a custom name.
1033	Action {
1034		/// The surface.
1035		sf: Option<String>,
1036		/// The node.
1037		id: String,
1038		/// The action's name (before any `=`).
1039		act: String,
1040		/// The part after `=` in the action's name.
1041		value: Option<String>,
1042		/// Modifiers held, in the order `shift`, `ctrl`, `alt`, `meta`.
1043		mods: Vec<String>,
1044		/// Form values, inside an `el` form.
1045		values: Option<Map<String, Value>>,
1046	}
1047);
1048
1049event_struct!(
1050	/// `change`: an `el` control flipped, or a `prefs` row changed.
1051	Change {
1052		/// The surface.
1053		sf: Option<String>,
1054		/// The node.
1055		id: String,
1056		/// The control's value, or the `prefs` row's new value.
1057		value: Value,
1058		/// The control's new state.
1059		checked: Option<bool>,
1060		/// The control's name.
1061		name: Option<String>,
1062		/// The `prefs` row.
1063		item: Option<String>,
1064		/// Form values, inside an `el` form.
1065		values: Option<Map<String, Value>>,
1066	}
1067);
1068
1069event_struct!(
1070	/// `focus`: a click asked for the keys in a field.
1071	Focus {
1072		/// The surface.
1073		sf: Option<String>,
1074		/// The field.
1075		id: String,
1076	}
1077);
1078
1079event_struct!(
1080	/// `edit`: native editing replaced UTF-16 `[from, to)` with `text`.
1081	Edit {
1082		/// The surface.
1083		sf: Option<String>,
1084		/// The field.
1085		id: String,
1086		/// Start, UTF-16 units.
1087		from: usize,
1088		/// End, UTF-16 units.
1089		to: usize,
1090		/// The replacement.
1091		text: String,
1092		/// The caret afterwards.
1093		cursor: usize,
1094		/// The length of the text Tern saw.
1095		len: usize,
1096	}
1097);
1098
1099event_struct!(
1100	/// `undo`: undo the last change to a field.
1101	Undo {
1102		/// The surface.
1103		sf: Option<String>,
1104		/// The field.
1105		id: String,
1106	}
1107);
1108
1109event_struct!(
1110	/// `send`: submit text through a composer.
1111	SendText {
1112		/// The surface.
1113		sf: Option<String>,
1114		/// The field.
1115		id: String,
1116		/// The text.
1117		text: String,
1118	}
1119);
1120
1121event_struct!(
1122	/// `error`: a rejected op, message, stylesheet or element.
1123	ErrorEvent {
1124		/// The surface, when the message named one.
1125		sf: Option<String>,
1126		/// The frame.
1127		s: Option<u64>,
1128		/// The op's index in the frame.
1129		op: Option<u64>,
1130		/// What went wrong.
1131		msg: String,
1132		/// The stylesheet.
1133		sheet: Option<String>,
1134		/// The element.
1135		id: Option<String>,
1136	}
1137);
1138
1139event_struct!(
1140	/// `gone`: nodes or a surface Tern dropped.
1141	Gone {
1142		/// The surface.
1143		sf: Option<String>,
1144		/// The ids dropped.
1145		ids: Vec<String>,
1146	}
1147);
1148
1149/// An `e` message: an event.
1150#[derive(Clone, Debug, PartialEq)]
1151pub enum Event {
1152	/// `ack`.
1153	Ack(Ack),
1154	/// `resize`.
1155	Resize(Resize),
1156	/// `theme`.
1157	Theme(Theme),
1158	/// `motion`.
1159	Motion(Motion),
1160	/// `visible`.
1161	Visible(Visible),
1162	/// `toggle`.
1163	Toggle(Toggle),
1164	/// `select`.
1165	Select(Select),
1166	/// `activate`.
1167	Activate(Select),
1168	/// `action`.
1169	Action(Action),
1170	/// `change`.
1171	Change(Change),
1172	/// `focus`.
1173	Focus(Focus),
1174	/// `edit`.
1175	Edit(Edit),
1176	/// `undo`.
1177	Undo(Undo),
1178	/// `send`.
1179	Send(SendText),
1180	/// `error`.
1181	Error(ErrorEvent),
1182	/// `gone`.
1183	Gone(Gone),
1184	/// Any other event, or a known one whose fields don't fit, kept raw.
1185	Unknown(Map<String, Value>),
1186}
1187
1188impl Event {
1189	/// Decodes an event body; fields that don't fit keep it raw.
1190	pub fn decode(map: Map<String, Value>) -> Self {
1191		let ev = map
1192			.get("ev")
1193			.and_then(Value::as_str)
1194			.unwrap_or_default()
1195			.to_owned();
1196		match ev.as_str() {
1197			"ack" => typed(map).map_or_else(Self::Unknown, Self::Ack),
1198			"resize" => typed(map).map_or_else(Self::Unknown, Self::Resize),
1199			"theme" => typed(map).map_or_else(Self::Unknown, Self::Theme),
1200			"motion" => typed(map).map_or_else(Self::Unknown, Self::Motion),
1201			"visible" => typed(map).map_or_else(Self::Unknown, Self::Visible),
1202			"toggle" => typed(map).map_or_else(Self::Unknown, Self::Toggle),
1203			"select" => typed(map).map_or_else(Self::Unknown, Self::Select),
1204			"activate" => typed(map).map_or_else(Self::Unknown, Self::Activate),
1205			"action" => typed(map).map_or_else(Self::Unknown, Self::Action),
1206			"change" => typed(map).map_or_else(Self::Unknown, Self::Change),
1207			"focus" => typed(map).map_or_else(Self::Unknown, Self::Focus),
1208			"edit" => typed(map).map_or_else(Self::Unknown, Self::Edit),
1209			"undo" => typed(map).map_or_else(Self::Unknown, Self::Undo),
1210			"send" => typed(map).map_or_else(Self::Unknown, Self::Send),
1211			"error" => typed(map).map_or_else(Self::Unknown, Self::Error),
1212			"gone" => typed(map).map_or_else(Self::Unknown, Self::Gone),
1213			_ => Self::Unknown(map),
1214		}
1215	}
1216
1217	/// The event as received.
1218	pub const fn raw(&self) -> &Map<String, Value> {
1219		match self {
1220			Self::Ack(e) => &e.raw,
1221			Self::Resize(e) => &e.raw,
1222			Self::Theme(e) => &e.raw,
1223			Self::Motion(e) => &e.raw,
1224			Self::Visible(e) => &e.raw,
1225			Self::Toggle(e) => &e.raw,
1226			Self::Select(e) | Self::Activate(e) => &e.raw,
1227			Self::Action(e) => &e.raw,
1228			Self::Change(e) => &e.raw,
1229			Self::Focus(e) => &e.raw,
1230			Self::Edit(e) => &e.raw,
1231			Self::Undo(e) => &e.raw,
1232			Self::Send(e) => &e.raw,
1233			Self::Error(e) => &e.raw,
1234			Self::Gone(e) => &e.raw,
1235			Self::Unknown(e) => e,
1236		}
1237	}
1238
1239	/// The event's name (`ev`).
1240	pub fn name(&self) -> &str {
1241		self
1242			.raw()
1243			.get("ev")
1244			.and_then(Value::as_str)
1245			.unwrap_or_default()
1246	}
1247
1248	/// The surface the event is about, when it names one.
1249	pub fn sf(&self) -> Option<&str> {
1250		self.raw().get("sf").and_then(Value::as_str)
1251	}
1252
1253	/// The node the event is about, when it names one.
1254	pub fn id(&self) -> Option<&str> {
1255		self.raw().get("id").and_then(Value::as_str)
1256	}
1257}