1use 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
19pub const VERSION: u32 = 1;
21pub const APC_LIMIT: usize = 65_536;
24pub const CREDITS: u32 = 2;
27pub const MAX_BLOB: usize = 16 << 20;
29
30pub mod kind {
32 pub const COL: &str = "col";
34 pub const ROW: &str = "row";
36 pub const CARD: &str = "card";
38 pub const SECTION: &str = "section";
40 pub const RULE: &str = "rule";
42 pub const SPACER: &str = "spacer";
44 pub const TEXT: &str = "text";
46 pub const MD: &str = "md";
48 pub const CODE: &str = "code";
50 pub const DIFF: &str = "diff";
52 pub const ANSI: &str = "ansi";
54 pub const ROWS: &str = "rows";
56 pub const MATH: &str = "math";
58 pub const KV: &str = "kv";
60 pub const TABLE: &str = "table";
62 pub const TREE: &str = "tree";
64 pub const BADGE: &str = "badge";
66 pub const KBD: &str = "kbd";
68 pub const ICON: &str = "icon";
70 pub const IMAGE: &str = "image";
72 pub const LIST: &str = "list";
74 pub const ITEM: &str = "item";
76 pub const TABS: &str = "tabs";
78 pub const PICKER: &str = "picker";
80 pub const SPINNER: &str = "spinner";
82 pub const SHIMMER: &str = "shimmer";
84 pub const ELAPSED: &str = "elapsed";
86 pub const RATE: &str = "rate";
88 pub const PROGRESS: &str = "progress";
90 pub const METER: &str = "meter";
92 pub const CHART: &str = "chart";
94 pub const EFFORT: &str = "effort";
96 pub const EDITOR: &str = "editor";
98 pub const INPUT: &str = "input";
100 pub const STATUS: &str = "status";
102 pub const SEG: &str = "seg";
104 pub const TOAST: &str = "toast";
106 pub const OVERLAY: &str = "overlay";
108 pub const TOOL: &str = "tool";
110 pub const AGENT: &str = "agent";
112 pub const CHECKLIST: &str = "checklist";
114 pub const BLOCK: &str = "block";
116 pub const PREFS: &str = "prefs";
118 pub const EL: &str = "el";
120
121 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 pub const TEXT_KINDS: [&str; 9] = [TEXT, MD, CODE, ANSI, MATH, EDITOR, INPUT, SHIMMER, EL];
132
133 pub fn has_text(kind: &str) -> bool {
135 TEXT_KINDS.contains(&kind)
136 }
137}
138
139pub mod feature {
141 pub const EDIT: &str = "edit";
143 pub const UNDO: &str = "undo";
145 pub const SEND: &str = "send";
147
148 pub const BLOBS: &str = "blobs";
150 pub const SETTLE: &str = "settle";
152 pub const ADOPT: &str = "adopt";
154 pub const DOCK: &str = "dock";
156 pub const PROGRAM_PALETTE: &str = "program-palette";
158 pub const REDUCE_MOTION: &str = "reduce-motion";
160 pub const ASIDE: &str = "aside";
163 pub const SCROLL: &str = "scroll";
165 pub const STYLES: &str = "styles";
167 pub const FLOW: &str = "flow";
169}
170
171#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
177#[serde(rename_all = "lowercase")]
178pub enum Mode {
179 #[default]
181 Inline,
182 Screen,
184 Flow,
186}
187
188#[derive(Clone, Debug, PartialEq, Eq)]
190pub enum Query {
191 Hello {
193 app: Option<String>,
195 ver: Option<String>,
197 features: Vec<String>,
199 },
200 Blobs {
202 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#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
238pub struct Open {
239 pub id: String,
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub mode: Option<Mode>,
244 #[serde(skip_serializing_if = "Option::is_none")]
246 pub title: Option<String>,
247 #[serde(skip_serializing_if = "Option::is_none")]
249 pub role: Option<String>,
250 #[serde(skip_serializing_if = "Option::is_none")]
252 pub listen: Option<bool>,
253 #[serde(skip_serializing_if = "Option::is_none")]
255 pub adopt: Option<bool>,
256}
257
258#[derive(Clone, Debug, PartialEq, Serialize)]
260pub struct Frame {
261 pub sf: String,
263 pub s: u64,
265 pub ops: Vec<Op>,
267}
268
269#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
271#[serde(rename_all = "lowercase")]
272pub enum TextMode {
273 Append,
275 Replace,
277}
278
279#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
281#[serde(rename_all = "lowercase")]
282pub enum RevealAt {
283 Start,
285 End,
287 #[default]
289 Nearest,
290}
291
292#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
294#[serde(rename_all = "kebab-case")]
295pub enum ScrollBy {
296 LineUp,
298 LineDown,
300 PageUp,
302 PageDown,
304 Start,
306 End,
308}
309
310#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
312pub struct WireNode {
313 pub id: String,
315 pub k: String,
317 #[serde(default, skip_serializing_if = "Map::is_empty")]
319 pub p: Map<String, Value>,
320 #[serde(default, skip_serializing_if = "Vec::is_empty")]
322 pub c: Vec<Self>,
323}
324
325#[derive(Clone, Debug, PartialEq)]
327pub enum Op {
328 Add {
330 parent: String,
332 before: Option<String>,
334 node: WireNode,
336 },
337 Set {
339 id: String,
341 props: Map<String, Value>,
343 },
344 Text {
346 id: String,
348 mode: TextMode,
350 text: String,
352 },
353 Splice {
355 id: String,
357 at: usize,
359 del: usize,
361 text: String,
363 },
364 Move {
366 id: String,
368 parent: String,
370 before: Option<String>,
372 },
373 Del {
375 id: String,
377 },
378 Settle {
380 id: String,
382 },
383 Focus {
385 id: Option<String>,
387 },
388 Reveal {
390 id: String,
392 at: RevealAt,
394 },
395 Scroll {
397 id: String,
399 by: ScrollBy,
401 },
402 Suspend,
404 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#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
474pub struct PaletteNames {
475 #[serde(skip_serializing_if = "Option::is_none")]
477 pub dark: Option<String>,
478 #[serde(skip_serializing_if = "Option::is_none")]
480 pub light: Option<String>,
481}
482
483#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
485pub struct Palette {
486 #[serde(skip_serializing_if = "Option::is_none")]
488 pub sf: Option<String>,
489 #[serde(skip_serializing_if = "Option::is_none")]
491 pub dark: Option<BTreeMap<String, String>>,
492 #[serde(skip_serializing_if = "Option::is_none")]
494 pub light: Option<BTreeMap<String, String>>,
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub name: Option<PaletteNames>,
498}
499
500#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
502pub struct Sheet {
503 #[serde(skip_serializing_if = "Option::is_none")]
505 pub sf: Option<String>,
506 pub name: String,
508 #[serde(skip_serializing_if = "Option::is_none")]
510 pub css: Option<String>,
511}
512
513#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
515pub struct Close {
516 pub id: String,
518 pub keep: bool,
520}
521
522#[derive(Clone, Debug, PartialEq, Eq)]
524pub struct Blob {
525 pub id: String,
527 pub mime: Option<String>,
529 pub data: Vec<u8>,
531}
532
533impl Blob {
534 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
546pub 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#[derive(Clone, Debug, PartialEq)]
559pub enum Message {
560 Query(Query),
562 Open(Open),
564 Frame(Frame),
566 Blob(Blob),
568 Palette(Palette),
570 Sheet(Sheet),
572 Close(Close),
574}
575
576impl Message {
577 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 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 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
621const fn key_byte(b: u8) -> bool {
623 b.is_ascii_alphanumeric() || b == b'_' || b == b'-'
624}
625
626const fn value_byte(b: u8) -> bool {
628 b >= 0x21 && b <= 0x7e && b != b';'
629}
630
631fn 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
641fn 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
655pub 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
673fn 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
688pub 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#[derive(Clone, Debug)]
724pub struct Encoder {
725 limit: usize,
727 next_chunk: u64,
729}
730
731impl Default for Encoder {
732 fn default() -> Self {
733 Self::new(APC_LIMIT)
734 }
735}
736
737impl Encoder {
738 pub const fn new(limit: usize) -> Self {
740 Self { limit, next_chunk: 1 }
741 }
742
743 pub const fn limit(&self) -> usize {
745 self.limit
746 }
747
748 pub const fn set_limit(&mut self, limit: usize) {
750 self.limit = limit;
751 }
752
753 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
770fn 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#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
791pub struct Cell {
792 #[serde(default)]
794 pub w: f64,
795 #[serde(default)]
797 pub h: f64,
798}
799
800#[derive(Clone, Debug, Default, PartialEq, Deserialize)]
802#[serde(rename_all = "camelCase")]
803pub struct Hello {
804 #[serde(default)]
806 pub v: u32,
807 #[serde(default)]
809 pub term: String,
810 #[serde(default)]
812 pub ver: String,
813 #[serde(default)]
815 pub kinds: Vec<String>,
816 #[serde(default)]
818 pub features: Vec<String>,
819 #[serde(default = "default_apc")]
821 pub apc: usize,
822 #[serde(default = "default_credits")]
824 pub credits: u32,
825 #[serde(default)]
827 pub cols: u32,
828 #[serde(default)]
830 pub cell: Option<Cell>,
831 #[serde(default)]
833 pub dark: bool,
834 #[serde(default)]
836 pub reduce_motion: bool,
837 #[serde(default)]
840 pub hour12: bool,
841 #[serde(skip)]
843 pub raw: Map<String, Value>,
844}
845
846const fn default_apc() -> usize {
848 APC_LIMIT
849}
850
851const fn default_credits() -> u32 {
853 CREDITS
854}
855
856#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize)]
858pub struct BlobsReply {
859 #[serde(default)]
861 pub have: Vec<String>,
862 #[serde(skip)]
864 pub raw: Map<String, Value>,
865}
866
867#[derive(Clone, Debug, PartialEq)]
869pub enum Reply {
870 Hello(Hello),
872 Blobs(BlobsReply),
874 Unknown(Map<String, Value>),
876}
877
878trait Raw: DeserializeOwned {
880 fn set_raw(&mut self, raw: Map<String, Value>);
882}
883
884fn 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 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 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
920macro_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 #[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 {
956 sf: String,
958 s: u64,
960 }
961);
962
963event_struct!(
964 Resize {
966 sf: Option<String>,
968 cols: u32,
970 cell: Option<Cell>,
972 visible: Option<bool>,
974 }
975);
976
977event_struct!(
978 Theme {
980 dark: bool,
982 }
983);
984
985event_struct!(
986 Motion {
988 reduce: bool,
990 }
991);
992
993event_struct!(
994 Visible {
996 sf: Option<String>,
998 visible: bool,
1000 }
1001);
1002
1003event_struct!(
1004 Toggle {
1006 sf: Option<String>,
1008 id: String,
1010 collapsed: bool,
1012 key: Option<Value>,
1014 }
1015);
1016
1017event_struct!(
1018 Select {
1020 sf: Option<String>,
1022 id: String,
1024 item: String,
1026 values: Option<Map<String, Value>>,
1028 }
1029);
1030
1031event_struct!(
1032 Action {
1034 sf: Option<String>,
1036 id: String,
1038 act: String,
1040 value: Option<String>,
1042 mods: Vec<String>,
1044 values: Option<Map<String, Value>>,
1046 }
1047);
1048
1049event_struct!(
1050 Change {
1052 sf: Option<String>,
1054 id: String,
1056 value: Value,
1058 checked: Option<bool>,
1060 name: Option<String>,
1062 item: Option<String>,
1064 values: Option<Map<String, Value>>,
1066 }
1067);
1068
1069event_struct!(
1070 Focus {
1072 sf: Option<String>,
1074 id: String,
1076 }
1077);
1078
1079event_struct!(
1080 Edit {
1082 sf: Option<String>,
1084 id: String,
1086 from: usize,
1088 to: usize,
1090 text: String,
1092 cursor: usize,
1094 len: usize,
1096 }
1097);
1098
1099event_struct!(
1100 Undo {
1102 sf: Option<String>,
1104 id: String,
1106 }
1107);
1108
1109event_struct!(
1110 SendText {
1112 sf: Option<String>,
1114 id: String,
1116 text: String,
1118 }
1119);
1120
1121event_struct!(
1122 ErrorEvent {
1124 sf: Option<String>,
1126 s: Option<u64>,
1128 op: Option<u64>,
1130 msg: String,
1132 sheet: Option<String>,
1134 id: Option<String>,
1136 }
1137);
1138
1139event_struct!(
1140 Gone {
1142 sf: Option<String>,
1144 ids: Vec<String>,
1146 }
1147);
1148
1149#[derive(Clone, Debug, PartialEq)]
1151pub enum Event {
1152 Ack(Ack),
1154 Resize(Resize),
1156 Theme(Theme),
1158 Motion(Motion),
1160 Visible(Visible),
1162 Toggle(Toggle),
1164 Select(Select),
1166 Activate(Select),
1168 Action(Action),
1170 Change(Change),
1172 Focus(Focus),
1174 Edit(Edit),
1176 Undo(Undo),
1178 Send(SendText),
1180 Error(ErrorEvent),
1182 Gone(Gone),
1184 Unknown(Map<String, Value>),
1186}
1187
1188impl Event {
1189 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 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 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 pub fn sf(&self) -> Option<&str> {
1250 self.raw().get("sf").and_then(Value::as_str)
1251 }
1252
1253 pub fn id(&self) -> Option<&str> {
1255 self.raw().get("id").and_then(Value::as_str)
1256 }
1257}