use std::collections::VecDeque;
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EditorTextMode {
Replace,
Append,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EditorTextEdit {
pub text: String,
pub mode: EditorTextMode,
pub auto_send_ms: Option<u64>,
}
impl EditorTextEdit {
pub fn from_json(args: &serde_json::Value) -> Result<Self, String> {
let text = args
.get("text")
.and_then(serde_json::Value::as_str)
.ok_or("set_editor_text requires a string `text`")?;
let mode = match args
.get("mode")
.and_then(serde_json::Value::as_str)
.unwrap_or("replace")
.to_ascii_lowercase()
.as_str()
{
"" | "replace" | "set" => EditorTextMode::Replace,
"append" => EditorTextMode::Append,
other => return Err(format!("unknown editor text mode `{other}`")),
};
let auto_send_ms = args
.get("autoSendMs")
.and_then(serde_json::Value::as_u64)
.filter(|ms| *ms > 0);
Ok(Self {
text: text.to_string(),
mode,
auto_send_ms,
})
}
}
#[derive(Clone, Default)]
pub struct EditorTextMailbox {
pending: Arc<Mutex<VecDeque<EditorTextEdit>>>,
}
impl EditorTextMailbox {
pub fn new() -> Self {
Self::default()
}
pub fn push(&self, edit: EditorTextEdit) {
if let Ok(mut pending) = self.pending.lock() {
pending.push_back(edit);
}
}
pub fn handle(&self, args: serde_json::Value) -> Result<serde_json::Value, String> {
let edit = EditorTextEdit::from_json(&args)?;
let len = edit.text.chars().count();
self.push(edit);
Ok(serde_json::json!({ "ok": true, "chars": len }))
}
pub fn take_pending(&self) -> Option<EditorTextEdit> {
self.pending.lock().ok()?.pop_front()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_and_take_round_trip_fifo() {
let mailbox = EditorTextMailbox::new();
assert!(mailbox.take_pending().is_none());
mailbox.push(EditorTextEdit {
text: "first".into(),
mode: EditorTextMode::Replace,
auto_send_ms: None,
});
mailbox.push(EditorTextEdit {
text: "second".into(),
mode: EditorTextMode::Append,
auto_send_ms: Some(3000),
});
let a = mailbox.take_pending().expect("first");
assert_eq!(a.text, "first");
assert_eq!(a.mode, EditorTextMode::Replace);
let b = mailbox.take_pending().expect("second");
assert_eq!(b.text, "second");
assert_eq!(b.mode, EditorTextMode::Append);
assert_eq!(b.auto_send_ms, Some(3000));
assert!(mailbox.take_pending().is_none());
}
#[test]
fn handle_defaults_to_replace_without_auto_send() {
let mailbox = EditorTextMailbox::new();
let out = mailbox
.handle(serde_json::json!({"text": "hello"}))
.expect("ok");
assert_eq!(out["ok"], serde_json::json!(true));
assert_eq!(out["chars"], serde_json::json!(5));
let edit = mailbox.take_pending().unwrap();
assert_eq!(edit.mode, EditorTextMode::Replace);
assert_eq!(edit.auto_send_ms, None);
}
#[test]
fn handle_parses_mode_and_auto_send() {
let mailbox = EditorTextMailbox::new();
mailbox
.handle(serde_json::json!({
"text": "draft", "mode": "append", "autoSendMs": 2500
}))
.expect("ok");
let edit = mailbox.take_pending().unwrap();
assert_eq!(edit.mode, EditorTextMode::Append);
assert_eq!(edit.auto_send_ms, Some(2500));
}
#[test]
fn handle_rejects_missing_text_and_unknown_mode() {
let mailbox = EditorTextMailbox::new();
assert!(mailbox.handle(serde_json::json!({})).is_err());
assert!(mailbox
.handle(serde_json::json!({"text": "x", "mode": "sideways"}))
.is_err());
assert!(mailbox.take_pending().is_none());
}
#[test]
fn zero_auto_send_means_manual_only() {
let mailbox = EditorTextMailbox::new();
mailbox
.handle(serde_json::json!({"text": "x", "autoSendMs": 0}))
.expect("ok");
assert_eq!(mailbox.take_pending().unwrap().auto_send_ms, None);
}
}