rpi_extensions/
editor_text.rs1use std::collections::VecDeque;
23use std::sync::{Arc, Mutex};
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum EditorTextMode {
28 Replace,
30 Append,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct EditorTextEdit {
37 pub text: String,
38 pub mode: EditorTextMode,
39 pub auto_send_ms: Option<u64>,
43}
44
45impl EditorTextEdit {
46 pub fn from_json(args: &serde_json::Value) -> Result<Self, String> {
49 let text = args
50 .get("text")
51 .and_then(serde_json::Value::as_str)
52 .ok_or("set_editor_text requires a string `text`")?;
53 let mode = match args
54 .get("mode")
55 .and_then(serde_json::Value::as_str)
56 .unwrap_or("replace")
57 .to_ascii_lowercase()
58 .as_str()
59 {
60 "" | "replace" | "set" => EditorTextMode::Replace,
61 "append" => EditorTextMode::Append,
62 other => return Err(format!("unknown editor text mode `{other}`")),
63 };
64 let auto_send_ms = args
66 .get("autoSendMs")
67 .and_then(serde_json::Value::as_u64)
68 .filter(|ms| *ms > 0);
69 Ok(Self {
70 text: text.to_string(),
71 mode,
72 auto_send_ms,
73 })
74 }
75}
76
77#[derive(Clone, Default)]
79pub struct EditorTextMailbox {
80 pending: Arc<Mutex<VecDeque<EditorTextEdit>>>,
81}
82
83impl EditorTextMailbox {
84 pub fn new() -> Self {
85 Self::default()
86 }
87
88 pub fn push(&self, edit: EditorTextEdit) {
91 if let Ok(mut pending) = self.pending.lock() {
92 pending.push_back(edit);
93 }
94 }
95
96 pub fn handle(&self, args: serde_json::Value) -> Result<serde_json::Value, String> {
98 let edit = EditorTextEdit::from_json(&args)?;
99 let len = edit.text.chars().count();
100 self.push(edit);
101 Ok(serde_json::json!({ "ok": true, "chars": len }))
102 }
103
104 pub fn take_pending(&self) -> Option<EditorTextEdit> {
106 self.pending.lock().ok()?.pop_front()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn push_and_take_round_trip_fifo() {
116 let mailbox = EditorTextMailbox::new();
117 assert!(mailbox.take_pending().is_none());
118 mailbox.push(EditorTextEdit {
119 text: "first".into(),
120 mode: EditorTextMode::Replace,
121 auto_send_ms: None,
122 });
123 mailbox.push(EditorTextEdit {
124 text: "second".into(),
125 mode: EditorTextMode::Append,
126 auto_send_ms: Some(3000),
127 });
128 let a = mailbox.take_pending().expect("first");
129 assert_eq!(a.text, "first");
130 assert_eq!(a.mode, EditorTextMode::Replace);
131 let b = mailbox.take_pending().expect("second");
132 assert_eq!(b.text, "second");
133 assert_eq!(b.mode, EditorTextMode::Append);
134 assert_eq!(b.auto_send_ms, Some(3000));
135 assert!(mailbox.take_pending().is_none());
136 }
137
138 #[test]
139 fn handle_defaults_to_replace_without_auto_send() {
140 let mailbox = EditorTextMailbox::new();
141 let out = mailbox
142 .handle(serde_json::json!({"text": "hello"}))
143 .expect("ok");
144 assert_eq!(out["ok"], serde_json::json!(true));
145 assert_eq!(out["chars"], serde_json::json!(5));
146 let edit = mailbox.take_pending().unwrap();
147 assert_eq!(edit.mode, EditorTextMode::Replace);
148 assert_eq!(edit.auto_send_ms, None);
149 }
150
151 #[test]
152 fn handle_parses_mode_and_auto_send() {
153 let mailbox = EditorTextMailbox::new();
154 mailbox
155 .handle(serde_json::json!({
156 "text": "draft", "mode": "append", "autoSendMs": 2500
157 }))
158 .expect("ok");
159 let edit = mailbox.take_pending().unwrap();
160 assert_eq!(edit.mode, EditorTextMode::Append);
161 assert_eq!(edit.auto_send_ms, Some(2500));
162 }
163
164 #[test]
165 fn handle_rejects_missing_text_and_unknown_mode() {
166 let mailbox = EditorTextMailbox::new();
167 assert!(mailbox.handle(serde_json::json!({})).is_err());
168 assert!(mailbox
169 .handle(serde_json::json!({"text": "x", "mode": "sideways"}))
170 .is_err());
171 assert!(mailbox.take_pending().is_none());
173 }
174
175 #[test]
176 fn zero_auto_send_means_manual_only() {
177 let mailbox = EditorTextMailbox::new();
178 mailbox
179 .handle(serde_json::json!({"text": "x", "autoSendMs": 0}))
180 .expect("ok");
181 assert_eq!(mailbox.take_pending().unwrap().auto_send_ms, None);
182 }
183}