T37: programs ask for notifications through OSC 9, 777 and 99, read as Ghostty and kitty read them, OSC 99's chunks gathered by id with base64, options, close, query and alive, and the workspace hears each; OSC 9;4's progress stands as the root record until a program reports through OSC 7501, and again after a full reset
This commit is contained in:
12 files changed
+680
-15
No files matched your search
@@ -0,0 +1,198 @@
|
||||
use super::*;
|
||||
|
||||
fn shown(ask: Option<Ask>) -> Notice {
|
||||
match ask {
|
||||
Some(Ask::Show(notice)) => notice,
|
||||
other => panic!("no notification: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_9_is_iterm2_s_notification_unless_it_is_conemu_s_command() {
|
||||
assert_eq!(shown(nine(b"Hello world")).title, "Hello world");
|
||||
for command in [
|
||||
&b"1;100"[..],
|
||||
b"10",
|
||||
b"10;2",
|
||||
b"11;note",
|
||||
b"12",
|
||||
b"2;box",
|
||||
b"3;",
|
||||
b"3;title",
|
||||
b"5",
|
||||
b"5x",
|
||||
b"6;macro",
|
||||
b"7;cmd",
|
||||
b"8;VAR",
|
||||
b"9;/path",
|
||||
] {
|
||||
assert_eq!(nine(command), None, "{}", String::from_utf8_lossy(command));
|
||||
}
|
||||
for text in [
|
||||
"1", "1a", "10x", "10;5", "13", "2", "2a", "4", "4;", "4;9", "x;y",
|
||||
] {
|
||||
assert_eq!(shown(nine(text.as_bytes())).title, text);
|
||||
}
|
||||
assert_eq!(nine(b""), None);
|
||||
assert_eq!(nine(b"a\xffb"), None, "not UTF-8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_9_4_reads_progress_as_ghostty_does() {
|
||||
let progress = |body: &[u8]| match nine(body) {
|
||||
Some(Ask::Progress(progress)) => progress,
|
||||
other => panic!("no progress: {other:?}"),
|
||||
};
|
||||
assert_eq!(progress(b"4;0"), Progress::Remove);
|
||||
assert_eq!(progress(b"4;1"), Progress::Set(Some(0)));
|
||||
assert_eq!(progress(b"4;1;40"), Progress::Set(Some(40)));
|
||||
assert_eq!(progress(b"4;1;900"), Progress::Set(Some(100)));
|
||||
assert_eq!(progress(b"4;1;x"), Progress::Set(None));
|
||||
assert_eq!(
|
||||
progress(b"4;1;99999999999999999999999"),
|
||||
Progress::Set(None)
|
||||
);
|
||||
assert_eq!(progress(b"4;1;1_0"), Progress::Set(None));
|
||||
assert_eq!(progress(b"4;2"), Progress::Error(None));
|
||||
assert_eq!(progress(b"4;2;50"), Progress::Error(Some(50)));
|
||||
assert_eq!(progress(b"4;3;7"), Progress::Indeterminate);
|
||||
assert_eq!(progress(b"4;4;10"), Progress::Pause(Some(10)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_777_is_rxvt_s_notify() {
|
||||
let notice = shown(rxvt(b"notify;Title;Body;more"));
|
||||
assert_eq!(
|
||||
(notice.title.as_str(), notice.body.as_str()),
|
||||
("Title", "Body;more")
|
||||
);
|
||||
assert_eq!(rxvt(b"other;x;y"), None);
|
||||
assert_eq!(rxvt(b"notify;Title"), None, "no body");
|
||||
assert_eq!(rxvt(b"notify;;"), None, "nothing to say");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_99_gathers_chunks_by_id_and_reads_its_options() {
|
||||
let mut kitty = Kitty::default();
|
||||
assert_eq!(kitty.read(b"i=a:d=0;Hel"), None);
|
||||
assert_eq!(kitty.read(b"i=b:d=0:p=body;other"), None);
|
||||
assert_eq!(kitty.read(b"i=a:d=0:p=body;Wor"), None);
|
||||
let notice = shown(kitty.read(b"i=a:p=body:o=unfocused:a=report,-focus:c=1:w=5000;ld"));
|
||||
assert_eq!(
|
||||
(
|
||||
notice.id.as_deref(),
|
||||
notice.title.as_str(),
|
||||
notice.body.as_str()
|
||||
),
|
||||
(Some("a"), "Hel", "World")
|
||||
);
|
||||
assert_eq!(
|
||||
(
|
||||
notice.occasion,
|
||||
notice.focus,
|
||||
notice.report,
|
||||
notice.close,
|
||||
notice.expire
|
||||
),
|
||||
(Occasion::Unfocused, false, true, true, Some(5000))
|
||||
);
|
||||
let other = shown(kitty.read(b"i=b:p=body;!"));
|
||||
assert_eq!(
|
||||
(other.title.as_str(), other.body.as_str()),
|
||||
("other!", ""),
|
||||
"a lone body is the title"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_99_decodes_base64_and_takes_plain_text() {
|
||||
let mut kitty = Kitty::default();
|
||||
let notice = shown(kitty.read(b"e=1:p=body;SGVsbG8gd29ybGQ="));
|
||||
assert_eq!((notice.title.as_str(), notice.id), ("Hello world", None));
|
||||
let plain = shown(kitty.read(b";Hello world"));
|
||||
assert_eq!(
|
||||
(plain.title.as_str(), plain.focus, plain.report),
|
||||
("Hello world", true, false)
|
||||
);
|
||||
assert_eq!(
|
||||
kitty.read(b"bad:i=c;x").map(|_| ()),
|
||||
Some(()),
|
||||
"a malformed pair passes by"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_99_asks_to_close_and_what_is_alive_and_supported() {
|
||||
let mut kitty = Kitty::default();
|
||||
assert_eq!(kitty.read(b"i=x:p=close;"), Some(Ask::Close("x".into())));
|
||||
assert_eq!(kitty.read(b"p=close;"), None, "no id, no close");
|
||||
assert_eq!(kitty.read(b"i=q:p=?;"), Some(Ask::Query("q".into())));
|
||||
assert_eq!(kitty.read(b"p=?;"), Some(Ask::Query("0".into())));
|
||||
assert_eq!(kitty.read(b"i=y:p=alive;"), Some(Ask::Alive("y".into())));
|
||||
assert_eq!(
|
||||
kitty.read(b"p=icon;x"),
|
||||
None,
|
||||
"an unknown payload passes by"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn osc_99_drops_what_breaks_its_rules() {
|
||||
let mut kitty = Kitty::default();
|
||||
let long = |length: usize, metadata: &str| {
|
||||
let mut body = format!("{metadata};").into_bytes();
|
||||
body.extend(std::iter::repeat_n(b'a', length));
|
||||
body
|
||||
};
|
||||
for body in [
|
||||
b"no payload".to_vec(),
|
||||
b"i=a b;x".to_vec(),
|
||||
b"i=a\"b;x".to_vec(),
|
||||
b"e=1;!!!".to_vec(),
|
||||
b"e=1;/w==".to_vec(),
|
||||
"x\u{85}y"
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.copied()
|
||||
.fold(b";".to_vec(), |mut body, byte| {
|
||||
body.push(byte);
|
||||
body
|
||||
}),
|
||||
long(PLAIN + 1, ""),
|
||||
long(ENCODED + 4, "e=1"),
|
||||
] {
|
||||
assert_eq!(
|
||||
kitty.read(&body),
|
||||
None,
|
||||
"{}",
|
||||
String::from_utf8_lossy(&body)
|
||||
);
|
||||
}
|
||||
for at in 0..PENDING {
|
||||
assert_eq!(kitty.read(format!("i=p{at}:d=0;x").as_bytes()), None);
|
||||
}
|
||||
assert_eq!(kitty.read(b"i=over:d=0;x"), None);
|
||||
assert!(
|
||||
kitty.pending.iter().all(|(id, _)| id != "over"),
|
||||
"past the cap nothing gathers"
|
||||
);
|
||||
let mut big = Kitty::default();
|
||||
for _ in 0..GATHERED / PLAIN {
|
||||
assert_eq!(big.read(&long(PLAIN, "i=g:d=0")), None);
|
||||
}
|
||||
assert_eq!(
|
||||
big.read(&long(PLAIN, "i=g")),
|
||||
None,
|
||||
"over the gathered bound"
|
||||
);
|
||||
assert!(big.pending.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_answers_name_the_asker_s_id() {
|
||||
assert_eq!(
|
||||
answer("q"),
|
||||
"\x1b]99;i=q:p=?;p=title,body,?,alive:o=always\x1b\\"
|
||||
);
|
||||
assert_eq!(alive("0"), "\x1b]99;i=0:p=alive;\x1b\\");
|
||||
}
|
||||
Reference in new issue
Block a user