workspace: Add configurable window title formatting (#54379)
## Summary
- Add `window_title_format` and `window_title_separator` settings for
customizing the window title
- Expose `${projectName}`, `${fileName}`, `${filePath}`,
`${relativePath}`, `${fileStem}`, `${remoteName}`, `${remoteHost}`,
`${appName}`, and `${separator}` as the native Zed window-title
variables
- Keep the collab `↗/↙` indicator hard-appended
- Translate VS Code `window.title` / `window.titleSeparator` aliases
into Zed's canonical variables during settings import, including
`${appName}` passthrough support and mapping VS Code's
`${activeEditorMedium}` onto Zed's `${relativePath}`
- Gate path-, remote-, and release-channel-derived lookups on whether
the active template references the corresponding variable, so the
default template avoids the extra work and still uses the existing
`last_window_title` cache to skip redundant platform updates
An alternative approach is at #49638. I went with `${variable}` names
over `$ZED_*`, canonical Zed variable names with VS Code aliases handled
at import time, kept `${separator}` as the only structural token, and
left `${dirty}` out of scope.
## Implementation reasoning and notes
### Variables considered but not included
- **collab/shared indicator variable**: kept hard-appended instead.
Folding it into the template would need whitespace/separator logic the
other variables don't need, and I'd rather not bake those semantics in
without a concrete need for it.
- **dirty variable**: The glyph choice and interaction with OS-level
edited indicators (like macOS's titlebar dot via `set_window_edited`)
felt like a separate design call I wasn't confident making here.
A separator-style config for each of collab/shared/dirty would solve the
above, but I didn't want to add too many additional settings initially.
### Performance
`update_window_title` is on a hot path, so two short-circuits apply:
- Path-, remote-, and release-channel-derived lookups (`absolute_path`,
relative-path string, `file_stem`, `remote_connection_options`,
`ReleaseChannel::try_global`) are gated on `template.contains(...)` via
`WindowTitleNeeds::from_template`, so the default template avoids the
extra work.
- The existing `last_window_title` cache still skips the platform call
when the rendered title is unchanged.
## Screenshots
Settings Editor entries for `window_title_format` and
`window_title_separator`:
<img width="774" height="324" alt="Settings Editor screenshot"
src="https://github.com/user-attachments/assets/b3378ec7-5649-4de3-9de6-164a42bce3fd"
/>
Customized Zed window titles in AltTab app switcher (remote, local,
empty):
<img width="774" height="324" alt="AltTab app switcher screenshot"
src="https://github.com/user-attachments/assets/a189945e-fc68-41b9-aab9-6dc04d5ef4ab"
/>
## Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #14534
Release Notes:
- Added configurable window title formatting with `window_title_format`
/ `window_title_separator`, including `${appName}` / `${remoteName}` /
`${remoteHost}` variables and VS Code `window.title` import support
---------
Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
1 parent
f659fcc000
commit
1aab982b0a
11 files changed
+1008
-32
No files matched your search
@@ -1372,6 +1372,16 @@ impl RemoteConnectionOptions {
|
||||
RemoteConnectionOptions::Mock(_) => "mock",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host(&self) -> String {
|
||||
match self {
|
||||
RemoteConnectionOptions::Ssh(opts) => opts.host.to_string(),
|
||||
RemoteConnectionOptions::Wsl(opts) => opts.distro_name.clone(),
|
||||
RemoteConnectionOptions::Docker(opts) => opts.name.clone(),
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
RemoteConnectionOptions::Mock(opts) => format!("mock-{}", opts.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -1563,6 +1573,17 @@ mod tests {
|
||||
"stream channel should be removed once the consumer has dropped the stream"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ssh_host_ignores_nickname() {
|
||||
let options = RemoteConnectionOptions::Ssh(SshConnectionOptions {
|
||||
host: "1.2.3.4".into(),
|
||||
nickname: Some("My Cool Project".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
assert_eq!(options.host(), "1.2.3.4");
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SshConnectionOptions> for RemoteConnectionOptions {
|
||||
|
||||
Reference in new issue
Block a user