T40: review round 2: the session's title comes from the tap alone, in the output's order, so the row, the window and a notification name it alike: OSC 0 and 2 read as vte splits them, 15 parts past the number, and up to 65536 bytes; alacritty's title events, which come later, pass by, and a full reset no longer sets the options again to send one
CI / platforms (ubuntu-24.04) (push) Successful in 1m31s
CI / platforms (windows-latest) (push) Successful in 1m37s
CI / macos (push) Canceled after 0s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-17 16:17:55 -04:00
1 parent 22752a28db
commit 140c2a5741
8 files changed
+77 -40

No files matched your search

+23 -15
View File
@@ -181,15 +181,20 @@ fn osc_99_gathers_a_full_encoded_chunk_and_the_lines_base64_carries() {
#[test]
fn titles_reach_the_session_in_their_place_among_notifications() {
let long = "y".repeat(5000);
let many: Vec<String> = (1..=17).map(|at| at.to_string()).collect();
let parts = [
"\\033]2;Old\\007",
"\\033[22t",
"\\033]2;New\\007",
"\\033]9;a\\007",
"\\033[23t",
"\\033]9;b\\007",
"\\033c",
"\\033]9;c\\007",
"\\033]2;Old\\007".to_string(),
"\\033[22t".into(),
"\\033]2;New\\007".into(),
"\\033]9;a\\007".into(),
"\\033[23t".into(),
"\\033]9;b\\007".into(),
"\\033c".into(),
"\\033]9;c\\007".into(),
format!("\\033]2;{long}\\007"),
format!("\\033]2;{}\\007", many.join(";")),
"\\033]9;d\\007".into(),
];
let whole = run(&format!("printf '{}x'", parts.concat()), (40, 4));
let split: Vec<String> = parts
@@ -198,13 +203,16 @@ fn titles_reach_the_session_in_their_place_among_notifications() {
.collect();
let split = run(&format!("{}; printf x", split.join("; ")), (40, 4));
let said = [
"title Old",
"title New",
"show a",
"title Old",
"show b",
"reset",
"show c",
"title Old".to_string(),
"title New".into(),
"show a".into(),
"title Old".into(),
"show b".into(),
"reset".into(),
"show c".into(),
format!("title {long}"),
format!("title {}", many[..15].join(";")),
"show d".into(),
];
assert_eq!(whole.said, said, "one write");
assert_eq!(split.said, said, "a write a sequence");
+1 -1
View File
@@ -108,7 +108,7 @@ impl Run {
);
match self.events.try_recv() {
Ok(PtyEvent::Folder(folder)) => self.folders.push(folder),
Ok(PtyEvent::Term(Event::Title(title))) => self.titles.push(title),
Ok(PtyEvent::Title(title)) => self.titles.push(title),
Ok(PtyEvent::Mark(mark)) => self.marks.push(mark),
Ok(PtyEvent::Term(Event::PtyWrite(reply))) => self.pty.write(reply.into_bytes()),
Ok(PtyEvent::Term(Event::ChildExit(status))) => {
+4 -2
View File
@@ -14,7 +14,7 @@ use super::{
prompts::{self, Prompts},
pty::PtyEvent,
status,
titles::Titles,
titles::{self, Titles},
};
/// The longest OSC body kept; longer ones are dropped whole.
@@ -314,10 +314,12 @@ impl Osc {
}
}
/// The longest body kept: an OSC 99 holds a payload of 4096 bytes past its metadata.
/// The longest body kept: an OSC 99's holds its payload past its metadata, a title's more.
fn most(&self) -> usize {
if self.body.starts_with(b"99;") {
notify::BODY
} else if self.body.starts_with(b"0;") || self.body.starts_with(b"2;") {
titles::LONGEST
} else {
MOST
}
+19
View File
@@ -259,3 +259,22 @@ fn finds_the_title_stack_s_push_and_pop_as_vte_reads_them() {
["PUSH", "2;t", "POP"]
);
}
#[test]
fn a_title_s_body_holds_65536_bytes_and_another_s_4096() {
let osc = |number: &str, length: usize| {
[
format!("\x1b]{number};").as_bytes(),
&vec![b'a'; length],
b"\x07",
]
.concat()
};
let longest = scan(&[&osc("2", titles::LONGEST - 2)]);
assert_eq!(longest.concat().len(), titles::LONGEST);
assert!(scan(&[&osc("0", titles::LONGEST - 1)]).is_empty());
assert!(
scan(&[&osc("7", MOST - 1)]).is_empty(),
"another stays at 4096"
);
}
+15 -2
View File
@@ -1,7 +1,13 @@
/// The titles alacritty saves before it drops the oldest.
const SAVED: usize = 4096;
/// A program's title as alacritty keeps it, read in the output's order: OSC 0 and 2 set it, CSI 22 t saves it, CSI 23 t brings it back, a full reset empties both.
/// vte keeps 16 parameters of an OSC, its number among them.
const PARTS: usize = 15;
/// The longest OSC 0 or 2 body the tap keeps; a longer one drops, logged.
pub(super) const LONGEST: usize = 65536;
/// The title a session shows, read in the output's order as vte and alacritty read it.
#[derive(Default)]
pub(crate) struct Titles {
/// Empty while the program names none.
@@ -10,10 +16,11 @@ pub(crate) struct Titles {
}
impl Titles {
/// Sets the title an OSC 0 or 2 body past its number names: its UTF-8 parameters joined by `;` and trimmed, as alacritty joins them.
/// Sets the title from an OSC 0 or 2 body past its number, as vte splits it and alacritty joins it.
pub fn set(&mut self, rest: &[u8]) -> String {
let parts: Vec<&str> = rest
.split(|&byte| byte == b';')
.take(PARTS)
.filter_map(|part| std::str::from_utf8(part).ok())
.collect();
self.title = parts.join(";").trim().to_owned();
@@ -56,6 +63,12 @@ mod tests {
"a part not in UTF-8 passes by"
);
assert_eq!(titles.set(b""), "");
let parts: Vec<String> = (1..=17).map(|at| at.to_string()).collect();
assert_eq!(
titles.set(parts.join(";").as_bytes()),
parts[..PARTS].join(";"),
"vte keeps 16 parameters, the number among them"
);
}
#[test]
+10 -15
View File
@@ -276,10 +276,14 @@ impl TerminalView {
self.ask(ask, cx);
self.restate(cx);
}
PtyEvent::Title(title) => self.title = title,
PtyEvent::Title(title) => {
self.title.clone_from(&title);
cx.emit(TerminalEvent::Title(title));
}
PtyEvent::Reset => {
self.records.reset();
self.title.clear();
cx.emit(TerminalEvent::Title(String::new()));
self.restate(cx);
}
}
@@ -294,20 +298,11 @@ impl TerminalView {
fn term_event(&mut self, event: Event, cx: &mut Context<Self>) {
match event {
Event::Wakeup => self.shown = true,
Event::MouseCursorDirty => {}
// A reset empties the title and sends only this; setting the options sends the title.
Event::CursorBlinkingChange => {
let settings = config(SettingsStore::get(cx));
self.pty.term.lock().set_options(settings);
}
Event::Title(title) => {
self.title.clone_from(&title);
cx.emit(TerminalEvent::Title(title));
}
Event::ResetTitle => {
self.title.clear();
cx.emit(TerminalEvent::Title(String::new()));
}
// The tap reads the title in the output's order; alacritty's title events come after it.
Event::MouseCursorDirty
| Event::CursorBlinkingChange
| Event::Title(_)
| Event::ResetTitle => {}
Event::Bell => {
log::debug!("terminal {}: a bell, kept silent", self.pty.pid);
cx.emit(TerminalEvent::Bell);