feat(terminal): track, draw, and assert on the window title - #91
feat(terminal): track, draw, and assert on the window title#91aymanbagabas wants to merge 2 commits into
Conversation
A program announces what it is doing by setting the window title with `OSC 0` or `OSC 2`, and shell-use threw that away. An agent driving a terminal could see what a program printed but not what it called itself, which is exactly what long-running tools report progress through. The emulator now tracks the title, and it is reachable everywhere the other terminal properties are: `state`, `get title`, `expect title`, and `wait title`, in the CLI and in both bindings. Screenshots draw it in the title bar. An empty title reports as none rather than as a blank one. Programs clear the title that way on exit, so keeping the two apart would only give every caller the same special case to write. Widths are measured in terminal columns, not characters. A CJK title is half as many characters as columns, so counting characters drew a snapshot frame four columns out of true and, in the centred title bar, spilled the title over the window controls at both ends. Snapshots only record the title when asked with `--include-title`. A shell prompt routinely sets it to a username, hostname, and absolute path, so recording it by default would pin every stored baseline to one machine and make it change on `cd` while the screen stayed the same. The title stack (`CSI 22 t` / `CSI 23 t`) comes free with this, since alacritty implements a pop as setting the title it popped; there is a test rather than any code for it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
Windows ConPTY gives a session the program's path as its window title as soon as it opens, so asserting a fresh session reports none failed there while the feature itself worked. That a fresh emulator has no title, and that an empty one resets rather than storing a blank, are claims about the emulator rather than about the platform. Both are already pinned in the conformance suite, which runs without a PTY and against every backend. The end-to-end test keeps to what only it can prove: that a title set by a real program in a real shell arrives intact, and that it replaced whatever the session started with. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
cpendery
left a comment
There was a problem hiding this comment.
nit: can we also update the bindings readmes with the new methods?
| // rather than allowed to push the corner out of line. | ||
| let label = title.and_then(|title| { | ||
| let room = width.checked_sub(4).filter(|room| *room > 0)?; | ||
| Some(format!(" {} ", truncate_to_columns(title, room))) |
There was a problem hiding this comment.
are we sure we want to truncate the title for the snapshot boxes? it might make more sense to expand the {view}\n{attributes} such that the attributes could be {title: "title", colors: {}, } and so on.
maybe we only drop into the attributes it the title would be truncated
|
|
||
| use bitflags::bitflags; | ||
| use compact_str::CompactString; | ||
| use unicode_width::UnicodeWidthChar; |
There was a problem hiding this comment.
I think you'll run into some weird width calculation issues using this, maybe UnicodeWidthStr is a better option
| // Off by default: a shell prompt routinely sets the title to a username, | ||
| // hostname, and absolute path, which would pin every baseline to one | ||
| // machine and make it change on `cd` while the screen stayed the same. | ||
| let title = include_title.then(|| title_of(session)).flatten(); |
There was a problem hiding this comment.
nit: might have a weird race case here where we release the title lock on the session, the pty continues, and then we snapshot the grid. should flag & address in a follow up pr
A program announces what it is doing by setting the window title with
OSC 0orOSC 2, and shell-use threw that away. An agent driving a terminal could see what a program printed but not what it called itself — which is exactly where long-running tools report progress, and how a shell says which command is running.The emulator now tracks the title, and it is reachable everywhere the other terminal properties already are.
Before / after
Same session, same command, screenshotted by the same
screenshot --out:The title is drawn centred in the title bar, clamped so it can never run under the window controls, and truncated with an ellipsis when it does not fit. No title leaves the bar exactly as it was, so every screenshot taken without one is byte-identical to before.
Surfaces
state.title,get title,expect title, andwait title, plusget_title/wait_title/expect_titlein the Python and JS bindings. All four take--regex,--not, and--timeoutwhere the sibling text commands do.Widths are counted in columns, not characters
A terminal lays out by column. A CJK glyph is one
charbut two columns; a combining mark is onecharbut none. Counting characters drew the snapshot frame four columns out of true, and in the centred title bar it sized the title at half its real width so it spilled over the window controls at both ends.One shared
truncate_to_columnsinterminal/cell.rsserves both. This is the only reasonunicode-widthis now named inCargo.toml:alacritty_terminalalready pulls the identical 0.2.2, so declaring it added one line toCargo.lockand zero new packages. Neitheralacritty_terminalnorvtere-exports it, and hand-rolling East-Asian-width tables would be both reinvention and wrong on emoji and combining marks — the tests cover🚀 buildande\u{301}clairfor exactly that reason.An empty title means reset
Programs clear the title by sending an empty one, so
OSC 2;reports asNonerather than as a title that happens to be blank. Keeping the two apart would only hand every caller the same special case to write.Snapshots record the title only when asked
expect snapshot --include-titleputs it in the frame's top border; the default leaves it out.This is deliberate and worth a look during review. A stock Ubuntu
.bashrcsetsPS1='\[\e]0;\u@\h: \w\a\]…', so a normal shell writes its own title on every prompt. Recorded by default, that is:which pins the baseline to one username, hostname, and absolute path, and changes on
cdwhile the screen stays identical. Off by default, a snapshot with no title is byte-identical to one taken before this PR, so every stored baseline keeps passing.Free
The title stack (
CSI 22 tpush,CSI 23 tpop) works, because alacritty implements a pop as setting the title it popped and that routes through the same event. There is a test for it rather than any code.Notes for review
Event::Title/Event::ResetTitlein the existingCaptureProxyrather than read back off the terminal, because alacritty exposes no way to read it:Term.titleandTerm.colorsare both private fields, but onlycolorshas a public getter (pub fn colors(&self) -> &Colors). There is noTerm::title(), andset_titleis aHandlersetter. Alacritty's own frontend consumes the same events for the same reason.OSC 1(icon name) is deliberately not supported: vte never dispatches it, and with a single drawn title there is nothing for a separate icon name to do.</text><script>cannot inject an element.