T23: the owner's checks on macOS: on macOS 27 gpui's Selection material drew no blur, so blur/macos gives the window's effect view underWindowBackground, radius 30; a button at each end of the title bar hides and shows its column, kept in the settings; shot reads an effect view's blur radius; new comments cut to ten words
CI / platforms (ubuntu-24.04) (push) Successful in 2m9s
CI / platforms (windows-latest) (push) Successful in 2m32s
CI / macos (push) Successful in 17m37s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-15 14:42:28 -04:00
1 parent be8d0e20c5
commit 09930c2934
12 files changed
+216 -55

No files matched your search

+4 -3
View File
@@ -13,7 +13,7 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
## Commands
- `cargo run` opens the app. `RUST_LOG=elyterminal=debug cargo run` logs more; `trace` logs each paint.
- `cargo run --example shot -- [--keys "SPECS"] <png> <program> [args…]` runs a program in the main window, plays SPECS on it, and writes a PNG of the window (macOS; the app it runs under needs Screen Recording, or the picture may hold the window's ground alone, and `shot` warns), naming the AppKit views under its content: gpui's blur is a `BlurredView`, and a capture of one window shows its alpha, not the blur behind it; it logs the menu bar, each item with its key. It shows its window without activating the app, so keystrokes or clicks meant for other apps stay there; gpui draws a window only while macOS shows it, so one on another Space or under a full-screen app keeps its last frame. Run it with `HOME` pointing at a temp folder to try settings without touching your own. SPECS, space-separated:
- `cargo run --example shot -- [--keys "SPECS"] <png> <program> [args…]` runs a program in the main window, plays SPECS on it, and writes a PNG of the window (macOS; the app it runs under needs Screen Recording, or the picture may hold the window's ground alone, and `shot` warns), naming the AppKit views under its content, an effect view with its blur radius, `none` when it draws no blur, and a capture of one window shows its alpha, not the blur behind it; it logs the menu bar, each item with its key. It shows its window without activating the app, so keystrokes or clicks meant for other apps stay there; gpui draws a window only while macOS shows it, so one on another Space or under a full-screen app keeps its last frame. Run it with `HOME` pointing at a temp folder to try settings without touching your own. SPECS, space-separated:
- keys as real AppKit events: `a`, `ctrl-c`, `lopt-f` and `ropt-f` (left and right Option), `cmd-v`, `repeat-up` (AppKit's repeat flag);
- input method calls: `mark:é` composes, `commit:é` types;
- `activate` and `deactivate` the app, which takes real keystrokes while active: keep the two specs adjacent. macOS may delay or refuse the activation; each `shot: typed` line says whether the app was active;
@@ -24,7 +24,7 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
## Layout
- `src/main.rs`: start. `src/lib.rs`: the modules, public so tests and examples reach them. `src/app.rs`: fonts, settings, integration, then the main window. `src/menus.rs`: the menu bar, the keys of Quit, the font's size and Minimize, and what they do. `src/blur.rs`: what the window shows of the desktop, from the opacity and blur settings, set as they change; `blur/wayland` asks a Wayland compositor for blur behind the window's surface through `ext_background_effect_v1`; `blur/x11` names the window's pane in `_KDE_NET_WM_BLUR_BEHIND_REGION`. `src/workspace.rs`: the window's content, Ely's `ColumnShell` with the sessions, terminal and right columns, their toggles, the right column's Git and Files switch, the title, and the sessions' lifecycle. `src/sessions.rs`: a session and its row. `src/files.rs`: the Files panel, the shown session's folder as Ely's `Tree` with Git's marks. `src/git.rs`: git through its command line: Files' marks, and the Git panel's status, stage, unstage, discard, commit and log; `src/git/diff.rs` reads git's diff into Ely's stretches. `src/git/panel.rs`: the Git panel's state and actions; `src/git/panel/view.rs`: how it draws.
- `src/main.rs`: start. `src/lib.rs`: the modules, public so tests and examples reach them. `src/app.rs`: fonts, settings, integration, then the main window. `src/menus.rs`: the menu bar, the keys of Quit, the font's size and Minimize, and what they do. `src/blur.rs`: what the window shows of the desktop, from the opacity and blur settings, set as they change; `blur/macos` gives gpui's NSVisualEffectView a material that blurs; `blur/wayland` asks a Wayland compositor for blur behind the window's surface through `ext_background_effect_v1`; `blur/x11` names the window's pane in `_KDE_NET_WM_BLUR_BEHIND_REGION`. `src/workspace.rs`: the window's content, Ely's `ColumnShell` with the sessions, terminal and right columns, their toggles, the right column's Git and Files switch, the title, and the sessions' lifecycle. `src/sessions.rs`: a session and its row. `src/files.rs`: the Files panel, the shown session's folder as Ely's `Tree` with Git's marks. `src/git.rs`: git through its command line: Files' marks, and the Git panel's status, stage, unstage, discard, commit and log; `src/git/diff.rs` reads git's diff into Ely's stretches. `src/git/panel.rs`: the Git panel's state and actions; `src/git/panel/view.rs`: how it draws.
- `src/settings`: the model, its ranges, and `SettingsStore`, the global that loads, checks and saves `settings.json`; `window` is the settings window, `window/appearance` its Appearance page, `window/terminal` its Terminal page. `tests/fonts.rs` brings its own main: gpui builds its Mac platform only on the main thread.
- `src/themes`: built-in terminal themes, the chrome palette each maps to, and `apply`, which dresses the app for the system's appearance.
- `src/term`: the terminal engine. `pty` starts a program through alacritty's event loop; `tap` wraps the pty so its reader scans OSC 7 and the mouse modes before the parser. `unix` starts the program itself, so a failed `chdir` fails the start; Windows uses alacritty's ConPTY. `shell` writes the integration scripts at start and builds each shell's launch. `frame` reads the grid into styled rows, `boxes` turns box drawing, block elements and powerline arrows into rects and polygons (`boxes/outline` strokes curves and diagonals into polygons), `ink` resolves colors, `paint` draws rows, decorations and the cursor, `view` is the entity: pty events, metrics, blink, resize. `zoom` holds the points Bigger and Smaller add to the Size setting, unsaved. `keys` turns key events into bytes, the kitty protocol and, in `keys/legacy`, xterm's encoding, with no gpui types; `input` is the view's key listeners, the bindings that keep Tab and F6 from Ely's focus moves, and its IME handler; `mac` reads the Option side and repeat flag of AppKit's current event, keeps held keys repeating, and hands Command key-ups to the focused view. `mouse` encodes mouse reports, with no gpui types; `links` finds the link at a cell; `pointer` is the view's mouse listeners: selection, reports, wheel, links, copy and paste. `find` is the find box: Ely's `FindWidget`, and Ely's `find_all` over the grid's shown text.
@@ -62,7 +62,7 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
- 2026-10-14: a key's release goes out only when the terminal saw its press, so a key a binding takes sends no release, as in kitty.
- 2026-10-14: find opens on Cmd-F (Ctrl-Shift-F off macOS) beside the grid, outside the `Terminal` key context, so its keys never reach the program. Matches wash in Ely's `warning` color, the current one stronger; the newest is current first, Enter steps older and Shift-Enter newer. Shift-Enter binds under `TerminalFind > ElyInput`, the context of Ely's text input, whose own Shift-Enter binding takes the key. Find reads the grid as it shows: combining marks ride their cells, hidden cells are blank, wrapped rows join, words are Unicode; alacritty's own search reads no marks and knows only ASCII word edges. New output, a resize and a settings change search again; the current match stays the same occurrence: its line kept, grown or cut at its end, held under the same line above or in its place, before such a line elsewhere, nearest where scrolling moved it; else the nearest by line, then column. Searching and following take one pass over the text and the matches.
- 2026-10-14: the program hears focus in and out under mode 1004 when the terminal's focus or the window's activation changes. The window wears the program's title, the app's name while it names none. A bell is logged and makes no sound. A full reset, `ESC c`, empties alacritty's title and sends only `CursorBlinkingChange`, in the parser's order, synchronized updates too; on that event the view replays the title through `set_options`, which sends it.
- 2026-10-14: the title bar is transparent, as Ghostty's: the content runs under it, Ely's column headers drag the window, and the terminal's header shows the program's title. macOS draws its traffic lights at Ely's origin; elsewhere Ely draws the controls, so windows ask for client decorations, which only Linux reads, and Ely's `ResizeBorder` gives the edges. Cmd-B and Cmd-Alt-B (Ctrl-Shift-B and Ctrl-Shift-Alt-B off macOS) toggle the sessions and right columns, kept in the settings.
- 2026-10-14: the title bar is transparent, as Ghostty's: the content runs under it, Ely's column headers drag the window, and the terminal's header shows the program's title. macOS draws its traffic lights at Ely's origin; elsewhere Ely draws the controls, so windows ask for client decorations, which only Linux reads, and Ely's `ResizeBorder` gives the edges. Cmd-B and Cmd-Alt-B (Ctrl-Shift-B and Ctrl-Shift-Alt-B off macOS) toggle the sessions and right columns, kept in the settings. A button at each end of the title bar does the same: `PanelLeft` leads the first column, right after the traffic lights, and `PanelRight` ends the last, so neither moves when a column hides; its tooltip says Hide or Show.
- 2026-10-14: session keys are Ghostty's: Cmd-T, Cmd-W, Cmd-1 to Cmd-8, Cmd-9 for the last, Ctrl-Tab and Ctrl-Shift-Tab; off macOS Ctrl-Shift-T, Ctrl-Shift-W and Alt with the digits. A digit past the last session goes on to the program. A new session runs the Shell setting in home or in the shown session's folder, as Start in says. A session whose program ends closes its row; the last row closes the window, and with no window left the app quits. A bell in a hidden session marks its row until it is shown. A hidden terminal hears no mouse release, so hiding it ends its mouse gesture as releases would: the selection drag ends, and the program hears each held button come up. A row's title is the program's, or the app's name while it names none.
- 2026-10-14: Files shows the shown session's folder in Ely's `Tree`, not `FileTree`, which takes every path up front and drops empty folders. A folder is read off the main thread when it first opens; the root and the open folders are read again whenever the session reports a folder, so at each prompt. Git's marks come from `git status --porcelain=v2 -z -- .`, run with `--no-optional-locks`, so it never holds `index.lock` against the user's own git, and under the user's `status.showUntrackedFiles`, so a home folder kept in git stays quick. An untracked folder marks what it holds. On macOS, whose file systems ignore Unicode forms, a name keeps its form on disk and meets Git's mark in composed form, as git's `core.precomposeunicode` writes it. A label built from a path shows each control character as its picture, `␊` for a newline: gpui shapes no newline, and a file may be named with one. A name not in UTF-8 keeps its bytes: its label shows it lossily, and its tree key adds the bytes after a NUL, which no path holds, so no two names share a key. A program without shell integration gets a line in the right column until it reports a folder. A control in a column header keeps its presses: Ely's header zooms the window on any double press.
- 2026-10-14: git runs in English with `--literal-pathspecs` and without optional locks. Paths stay git's bytes until shown, so a name not in UTF-8 still names its own file; off Unix git writes UTF-8. Status reads porcelain v2. Paths to stage and unstage go in on stdin, NUL-ended (`--pathspec-from-file`), so any name and any count pass; an empty list is refused, since git would take it as the whole tree. Unstaging is `git reset`, which works before the first commit, and a staged rename unstages with its old path; a copy is a new file, its source no part of it. A diff is git's own, without external tools or text converters, a conflict against HEAD, our side, so a file we deleted reads as new; it is read into Ely's stretches; a binary file has none, and an untracked folder none either. Without an index git exits 1 for a difference and a failure alike; only a difference prints. What git warns of while it works goes to the log. The log needs a commit at HEAD: git refuses it before the first.
@@ -76,3 +76,4 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
- 2026-10-15: on a client-drawn window, so on Linux, Ely's `ResizeBorder` paints its pane solid and shadowed under the content, which kept the desktop from showing at all. `ResizeBorder::glass`, added to Ely for this, leaves the pane to the content while the window is see-through: no ground, nor the shadow gpui draws under the box, which would tint it. So the blur covers the pane alone and the resize margin stays clear. On X11 the window names its pane in `_KDE_NET_WM_BLUR_BEHIND_REGION` (CARDINAL, 32: x, y, width, height in the window's pixels), as Ghostty names its own, and drops the property when Blurred goes; KWin reads it, other compositors pass it by. It goes over a connection of ELYTerminal's own to `$DISPLAY`, which gpui opens too: a property belongs to the window, whoever sets it. gpui's own `org_kde_kwin_blur` on KDE's Wayland still covers the whole surface, margin included, beyond ELYTerminal's reach.
- 2026-10-15: the macOS menu bar is the PRD's: ELYTerminal (Settings…, Quit), Shell (New Session, Close Session), Edit (Copy, Paste, Select All, Find), View (Toggle Sessions, Toggle Right Panel, Bigger, Smaller, Reset Size), Window (Minimize, Zoom, then the windows macOS lists). An item shows the key bound to its action, so the menus are set after the bindings; Copy, Paste and Select All go through AppKit's `copy:`, `paste:` and `selectAll:`, as gpui's `OsAction` maps them. They are Ely's text input actions, which `forms` exports for this, and the terminal handles them too: gpui sends a menu's action to what holds focus and enables the item only where a handler waits, so one action serves the terminal, find and every field. The new keys are Ghostty's: Cmd-Q, Cmd-A, Cmd-= and Cmd-+, Cmd-minus, Cmd-0, Cmd-M. Off macOS Quit and Select All take Ctrl-Shift, since Ctrl with a letter types a control byte; the font's size takes plain Ctrl with =, +, minus and 0, which type none, as in Ghostty, Alacritty and Windows Terminal; Minimize has no key there. Bigger and Smaller move every session's font a point, inside the Size range, and Reset Size goes back to the setting; nothing saves the change, as in Ghostty. Select All runs from the first character in the history to the last on the screen, the blanks around them left out, as Ghostty's, though a space under a combining mark shows a character and counts as one; copy on select takes it, and a blank grid selects nothing.
- 2026-10-15: CI runs on self-hosted Gitea runners, each job with what its runner must hold. macOS runs on the host, with fish and PowerShell installed, or the two shell tests fail as they should. Windows runs on the host, with rustup's `cargo` and `rustc` proxies beside `rustup` and Visual Studio's C++ tools. Ubuntu (`ubuntu-24.04`) runs as root in `docker.gitea.com/runner-images:ubuntu-24.04`, installs the libraries gpui links, and runs the tests as a user it makes, since root ignores the file modes they check; its runner serves this repository alone, in a VM of its own, so a build cannot starve other containers. The Linux and Windows jobs build without debuginfo, as `scripts/linux.sh` does: with it, linking gpui's test binaries outgrew that VM's 8 GB. A run that waits on a missing runner stays queued: cancel stale ones before a new runner registers, or it runs every one. The shell tests type a line only once the line editor reads raw keys, canonical mode off: on CI, a line typed before PowerShell's first read was echoed by the terminal and never run.
- 2026-10-15: on macOS 27 gpui's `Blurred` gives its NSVisualEffectView the `Selection` material, which builds no backdrop layer, so the desktop showed through sharp. `blur/macos` sets the view to `underWindowBackground` whenever the window turns Blurred: a gaussian blur of radius 30, the radius the owner's Ghostty uses; gpui's `updateLayer` still strips the material's tint and saturation. The view's presence proved nothing, so `shot` reads the radius off its backdrop layer.
+1 -1
View File
@@ -43,13 +43,13 @@ wayland-server = "0.31"
[target.'cfg(target_os = "macos")'.dependencies]
block = "0.1"
objc = "0.2"
raw-window-handle = "0.6"
unicode-normalization = "0.1"
[target.'cfg(target_os = "macos")'.dev-dependencies]
core-graphics = { version = "0.24", features = ["highsierra"] }
foreign-types = "0.5"
image = { version = "0.25", default-features = false, features = ["png"] }
raw-window-handle = "0.6"
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("cargo-clippy"))'] }
+2 -2
View File
@@ -58,8 +58,8 @@ Ghostty is the bar for what the terminal does. ELYTerminal owns its engine on `a
## 6. Main window and layout
- Ely's `ColumnShell`: sessions column, terminal column, right column. The window opens with blur when the setting is on.
- The sessions column and the right column toggle and keep their state in settings.
- The title shows the active session's title. Nothing else in the title bar.
- The sessions column and the right column toggle, each by a button at its end of the title bar or by key, and keep their state in settings, so the window opens as it was left.
- The title shows the active session's title, and no other text.
- The title bar is transparent, as Ghostty's: the window's background, opacity and blur run under it, so it wears the terminal's color.
## 7. Sessions panel
+1
View File
@@ -24,6 +24,7 @@ The app writes its log to stderr. `RUST_LOG=elyterminal=debug` writes more; `tra
- The terminal runs on `alacritty_terminal`: 256 colors and truecolor, bold, dim, italic, five underline styles, wide characters and combining marks. Box drawing, block elements and powerline shapes are drawn as geometry, so lines join across cells. Keys follow xterm and the kitty keyboard protocol; input methods compose at the cursor; programs can track the mouse; pastes are bracketed when a program asks; OSC 8 and plain links open with the link press below; find searches the scrollback.
- Shell integration for zsh, bash, fish and PowerShell: each prompt reports its folder and title.
- Sessions: new, close and switch. A bell marks a hidden session's row and makes no sound.
- A button at each end of the title bar hides or shows its side column; the window opens as it was left.
- Files shows the session's folder as a tree with Git's marks.
- Git shows the branch, stages, unstages, discards, commits, lists recent commits and opens diffs, all through the `git` command line.
- Themes: Ely Dark, Ely Light, Catppuccin Mocha, Macchiato, Frappé and Latte, Tokyo Night, Dracula, Nord, Gruvbox Dark, Rosé Pine, Solarized Light, One Light and Kanagawa Wave. A second theme can take over while the system is dark.
+56 -13
View File
@@ -1,7 +1,11 @@
use std::path::Path;
use anyhow::{Context as _, bail};
use objc::{class, msg_send, runtime::Object, sel, sel_impl};
use objc::{
class, msg_send,
runtime::{BOOL, Object, YES},
sel, sel_impl,
};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
/// A gpui window's number, as CoreGraphics knows it.
@@ -29,7 +33,7 @@ pub fn front(number: u32) -> anyhow::Result<()> {
Ok(())
}
/// AppKit's class of each view under the content.
/// Each view's AppKit class; an effect view adds its blur.
pub fn views(number: u32) -> anyhow::Result<Vec<String>> {
unsafe {
let app: *mut Object = msg_send![class!(NSApplication), sharedApplication];
@@ -40,24 +44,63 @@ pub fn views(number: u32) -> anyhow::Result<Vec<String>> {
let count: u64 = msg_send![views, count];
let named = (0..count).map(|ix| {
let view: *mut Object = msg_send![views, objectAtIndex: ix];
let class: *mut Object = msg_send![view, className];
let text: *const std::ffi::c_char = msg_send![class, UTF8String];
std::ffi::CStr::from_ptr(text)
.to_string_lossy()
.into_owned()
let name = text(msg_send![view, className]);
let effect: BOOL = msg_send![view, isKindOfClass: class!(NSVisualEffectView)];
if effect == YES {
let radius = blur(msg_send![view, layer]).unwrap_or_else(|| "none".into());
format!("{name}, blur {radius}")
} else {
name
}
});
Ok(named.collect())
}
}
/// The first gaussian blur's radius at or under `layer`.
unsafe fn blur(layer: *mut Object) -> Option<String> {
if layer.is_null() {
return None;
}
unsafe {
let filters: *mut Object = msg_send![layer, filters];
let count: u64 = if filters.is_null() {
0
} else {
msg_send![filters, count]
};
for ix in 0..count {
let filter: *mut Object = msg_send![filters, objectAtIndex: ix];
if text(msg_send![filter, name]) == "gaussianBlur" {
let key: *mut Object =
msg_send![class!(NSString), stringWithUTF8String: c"inputRadius".as_ptr()];
let radius: *mut Object = msg_send![filter, valueForKey: key];
return Some(text(msg_send![radius, description]));
}
}
let sublayers: *mut Object = msg_send![layer, sublayers];
let count: u64 = if sublayers.is_null() {
0
} else {
msg_send![sublayers, count]
};
(0..count).find_map(|ix| blur(msg_send![sublayers, objectAtIndex: ix]))
}
}
/// An NSString's text, `nil` for none.
fn text(string: *mut Object) -> String {
if string.is_null() {
return "nil".into();
}
let bytes: *const std::ffi::c_char = unsafe { msg_send![string, UTF8String] };
unsafe { std::ffi::CStr::from_ptr(bytes) }
.to_string_lossy()
.into_owned()
}
/// Each menu item with its key and modifier mask.
pub fn menus() -> Vec<String> {
let text = |string: *mut Object| -> String {
let bytes: *const std::ffi::c_char = unsafe { msg_send![string, UTF8String] };
unsafe { std::ffi::CStr::from_ptr(bytes) }
.to_string_lossy()
.into_owned()
};
let mut lines = Vec::new();
unsafe {
let app: *mut Object = msg_send![class!(NSApplication), sharedApplication];
+13 -2
View File
@@ -4,6 +4,8 @@ use gpui::{Window, WindowBackgroundAppearance};
use crate::settings::Settings;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "linux")]
mod wayland;
#[cfg(target_os = "linux")]
@@ -32,10 +34,15 @@ pub struct Blur {
impl Blur {
/// The window as `WindowOptions` opened it from these settings.
pub fn new(window: &Window, settings: &Settings) -> Self {
#[cfg(not(target_os = "linux"))]
let shown = appearance(settings);
#[cfg(target_os = "macos")]
if shown == WindowBackgroundAppearance::Blurred {
macos::blur(window);
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
let _ = window;
Self {
shown: appearance(settings),
shown,
#[cfg(target_os = "linux")]
wayland: wayland::Effect::on(window),
#[cfg(target_os = "linux")]
@@ -57,6 +64,10 @@ impl Blur {
self.shown = wanted;
window.set_background_appearance(wanted);
log::info!("blur: the window is {wanted:?} on {}", std::env::consts::OS);
#[cfg(target_os = "macos")]
if wanted == WindowBackgroundAppearance::Blurred {
macos::blur(window);
}
}
/// Keeps a Linux compositor's blur on the window's pane while Blurred; each frame, sending only a change.
+50
View File
@@ -0,0 +1,50 @@
use anyhow::bail;
use gpui::Window;
use objc::{
class, msg_send,
runtime::{BOOL, Object, YES},
sel, sel_impl,
};
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
/// `NSVisualEffectMaterialUnderWindowBackground`.
const UNDER_WINDOW_BACKGROUND: isize = 21;
/// Gives gpui's effect view a blurring material; logs either way.
pub fn blur(window: &Window) {
match effect_view(window) {
Ok(view) => {
unsafe {
let _: () = msg_send![view, setMaterial: UNDER_WINDOW_BACKGROUND];
}
log::info!(
"blur: the window's NSVisualEffectView takes underWindowBackground; gpui's Selection material draws no blur on macOS 27"
);
}
Err(error) => log::error!("blur: the window does not blur: {error:#}"),
}
}
/// The NSVisualEffectView gpui puts under the window's content for `Blurred`.
fn effect_view(window: &Window) -> anyhow::Result<*mut Object> {
let handle = HasWindowHandle::window_handle(window)
.map_err(|error| anyhow::anyhow!("no native handle: {error:?}"))?;
let RawWindowHandle::AppKit(appkit) = handle.as_raw() else {
bail!("not an AppKit window");
};
unsafe {
let view = appkit.ns_view.as_ptr() as *mut Object;
let native: *mut Object = msg_send![view, window];
let content: *mut Object = msg_send![native, contentView];
let views: *mut Object = msg_send![content, subviews];
let count: usize = msg_send![views, count];
for ix in 0..count {
let view: *mut Object = msg_send![views, objectAtIndex: ix];
let effect: BOOL = msg_send![view, isKindOfClass: class!(NSVisualEffectView)];
if effect == YES {
return Ok(view);
}
}
}
bail!("gpui put no NSVisualEffectView under the content")
}
+1 -1
View File
@@ -186,7 +186,7 @@ impl Pty {
#[cfg(all(test, unix))]
impl Pty {
/// Whether the program reads keys one by one, as a line editor does.
/// Whether the program reads raw keys, canonical mode off.
pub(crate) fn reads_keys(&self) -> bool {
use rustix_openpty::rustix::termios::{LocalModes, tcgetattr};
let modes = tcgetattr(&self.master).expect("the pty's modes");
+1 -1
View File
@@ -108,7 +108,7 @@ impl Run {
}
}
/// Types `line` at a prompt, once its line editor reads raw keys.
/// Types `line` once the line editor reads raw keys.
fn send(&mut self, line: &str) {
self.until("line editor", |run| run.pty.reads_keys());
self.pty.write(format!("{line}\r").into_bytes());
+1 -1
View File
@@ -45,7 +45,7 @@ impl Pty {
self.child.id()
}
/// A copy of the master, for tests to read its modes.
/// A copy of the master; tests read its modes.
#[cfg(test)]
pub fn master(&self) -> io::Result<File> {
self.file.try_clone()
+60 -19
View File
@@ -320,14 +320,55 @@ fn show_tab(value: &SharedString, cx: &mut App) {
}
}
/// A header button that hides or shows a column, saved.
fn column_toggle(
workspace: &Entity<Workspace>,
(id, icon): (&'static str, IconName),
tip: String,
flip: fn(&mut Settings),
) -> IconButton {
let workspace = workspace.clone();
IconButton::new(id, icon)
.variant(ButtonVariant::Ghost)
.size(ControlSize::Sm)
.tooltip(tip)
.on_click(move |_, _, cx| {
cx.stop_propagation();
workspace.update(cx, |this, cx| this.toggle(flip, cx));
})
}
impl Render for Workspace {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
self.background.fit(window);
let settings = SettingsStore::get(cx);
let width = cx.theme().sidebar_width(false);
let workspace = cx.entity();
let tip =
|shown: bool, what: &str| format!("{} {what}", if shown { "Hide" } else { "Show" });
// It leads the first column or ends the last.
let left = column_toggle(
&workspace,
("toggle-sessions", IconName::PanelLeft),
tip(settings.show_sessions, "sessions"),
|settings| settings.show_sessions = !settings.show_sessions,
);
let right = column_toggle(
&workspace,
("toggle-panel", IconName::PanelRight),
tip(settings.show_panel, "Git and Files"),
|settings| settings.show_panel = !settings.show_panel,
);
let mut shell = ColumnShell::new("workspace").glass();
let shown = self
.sessions
.get(self.active)
.map(|session| session.terminal.clone());
let mut terminal = ShellColumn::new("terminal")
.title(self.title().to_string())
.children(shown);
if settings.show_sessions {
let (workspace, home) = (cx.entity(), dirs::home_dir());
let home = dirs::home_dir();
let rows = self.sessions.iter().enumerate().map(|(index, session)| {
let active = index == self.active;
sessions::row(&workspace, index, session, active, home.as_deref(), cx)
@@ -342,25 +383,22 @@ impl Render for Workspace {
workspace.update(cx, |this, cx| this.new_session(window, cx))
});
shell = shell.column(
ShellColumn::new("sessions").width(width).action(new).child(
div()
.id("sessions")
.size_full()
.overflow_y_scroll()
.p_2()
.child(list),
),
ShellColumn::new("sessions")
.width(width)
.leading(left)
.action(new)
.child(
div()
.id("sessions")
.size_full()
.overflow_y_scroll()
.p_2()
.child(list),
),
);
} else {
terminal = terminal.leading(left);
}
let shown = self
.sessions
.get(self.active)
.map(|session| session.terminal.clone());
shell = shell.column(
ShellColumn::new("terminal")
.title(self.title().to_string())
.children(shown),
);
if settings.show_panel {
let tab = settings.panel;
let chosen = match tab {
@@ -392,14 +430,17 @@ impl Render for Workspace {
};
// Git's list draws every row, so terminal output must not redraw it.
let fill = || StyleRefinement::default().size_full();
shell = shell.column(
shell = shell.column(terminal).column(
ShellColumn::new("panel")
.width(width)
.leading(switch)
.action(right)
.children(quiet.then(caption))
.child(body(tab == PanelTab::Git).child(self.git.clone().cached(fill())))
.child(body(tab == PanelTab::Files).child(self.files.clone())),
);
} else {
shell = shell.column(terminal.action(right));
}
// The context sits above the focus root, so its keys work wherever focus is.
div()
+26 -12
View File
@@ -2,7 +2,7 @@
`PRD.md` line by line, as of T23. Status: **Pass** when a test, a run or a review shows it; **Partial** when part of it was shown; **Open** when nothing has shown it yet. Reviews are in `tasks/reviews.md`; tests are named by module (`cargo test -- MODULE`).
What no check reached: no build has run on Windows; no real KWin or Wayland desktop drew the blur. On 2026-10-15 macOS gave `shot` blank pictures for old and new builds alike, so that day's macOS checks read the app's log.
What no check reached: no one has looked at the window on Windows, which CI builds and tests; no real KWin or Wayland desktop drew the blur. On 2026-10-15 macOS gave `shot` the window's ground alone, for old and new builds alike, while the terminal it ran under lacked Screen Recording (`CGPreflightScreenCaptureAccess` false); that day's macOS checks read the app's log.
## 1. Product
@@ -23,8 +23,8 @@ What no check reached: no build has run on Windows; no real KWin or Wayland desk
| Ely by pinned rev; gpui and gpui_platform from `gpui-pre` at Ely's version | T01 | `Cargo.toml`: Ely `a2cb531`, `gpui-pre` `=0.3.8` | Pass |
| `alacritty_terminal` 0.26 | T04 | `=0.26.0` | Pass |
| Logs through `log` to stderr, `RUST_LOG` sets the level, no log setting | T01 | `src/main.rs`; no such field in `Settings` | Pass |
| CI on Gitea Actions: macOS builds, tests and lints; Ubuntu and Windows build and test | T01, T23 | `.github/workflows/ci.yml`; see §13 | Open |
| Private repo; no credential in any file or commit | T01 | each review scans the tree and every reachable object: 0 hits (T22: 398 objects) | Pass |
| CI on Gitea Actions: macOS builds, tests and lints; Ubuntu and Windows build and test | T01, T23 | `.github/workflows/ci.yml`; run 910 on `be8d0e2`, see the CI notes | Pass |
| Private repo; no credential in any file or commit | T01 | T22's review scanned the tree and its 398 reachable objects: 0 hits; T23's scans for the Gitea token and the runners' SSH details: 0 hits | Pass |
## 3. Settings model and storage
@@ -67,7 +67,7 @@ What no check reached: no build has run on Windows; no real KWin or Wayland desk
| PRD line | Task | Evidence | Status |
|---|---|---|---|
| `ColumnShell` with three columns; blur when the setting is on | T11, T19 | `shot` names a `BlurredView` under the window | Pass |
| Columns toggle and keep their state | T11 | Cmd-B, the app active, saved `show_sessions` false; Ctrl-Shift-B hid the sessions on a real X11 window | Pass |
| Columns toggle and keep their state | T11, T23 | a click on each title bar button hid its column and saved it, and a restart opened the window with both hidden (147 columns against 87); Cmd-B with the app active; Ctrl-Shift-B on a real X11 window | Pass |
| The title shows the session's title, nothing else | T11 | log: "the title is", per session shown | Pass |
| The title bar is transparent | T11, T19 | T19 review | Pass |
@@ -113,8 +113,8 @@ What no check reached: no build has run on Windows; no real KWin or Wayland desk
| PRD line | Task | Evidence | Status |
|---|---|---|---|
| macOS: `Blurred` (NSVisualEffectView) | T19 | `BlurredView` comes and goes with the settings | Pass |
| Windows: `Blurred` (acrylic) | T19 | the same gpui call; no Windows build has run | Open |
| macOS: `Blurred` (NSVisualEffectView) | T19, T23 | `shot` reads `BlurredView, blur 30` off the view's backdrop layer; the owner saw the desktop blur | Pass |
| Windows: `Blurred` (acrylic) | T19 | the same gpui call; CI builds and tests on Windows, but no one has seen the window | Partial |
| Wayland: `ext_background_effect_v1` beside gpui's `org_kde_kwin_blur` | T20 | `blur::wayland` tests on a `wayland-server` compositor; no real compositor | Partial |
| X11: `_KDE_NET_WM_BLUR_BEHIND_REGION`, removed when blur turns off | T21 | `blur::x11` test on Xvfb; the property on a real xfwm4 window; KWin not run | Partial |
| Neither protocol: transparency without blur; the log names the path | T20, T21 | the log lines in `src/blur` | Pass |
@@ -130,14 +130,28 @@ What no check reached: no build has run on Windows; no real KWin or Wayland desk
| PRD line | Evidence | Status |
|---|---|---|
| Each task ticked with a passing review | T01–T22 ticked; T04 and T10 reached the three-round cap without a passing round; T23 open | Partial |
| Each task ticked with a passing review | each task is ticked only after its review, as `tasks/reviews.md` records; T04 and T10 reached the three-round cap without a passing round, their last fixes standing on red-green tests | Partial |
| `scripts/check.sh` passes | macOS: 135 tests, clippy, fmt, 500 lines; Linux in Colima: 138 tests, clippy | Pass |
| CI green on macOS, Ubuntu and Windows | see the CI notes below | Open |
| CI green on macOS, Ubuntu and Windows | run 910 on `be8d0e2`: all three jobs pass; see the CI notes below | Pass |
## CI notes
As probed on 2026-10-15, each job fails on its runner, not on the code:
On 2026-10-15 every job first failed on its runner, not on the code. With the owner's leave, each runner was put right:
- macOS, on the Mac mini runner: Homebrew is on the PATH, but fish and PowerShell are not installed, so `fish_loads_its_script_and_restores_its_data_dirs` and `powershell_reports_its_folder_byte_for_byte_and_keeps_the_status` fail, as they should without their shells.
- Windows, on its runner: `C:\Users\zhang\.cargo\bin` holds `rustup.exe` alone, without the `cargo.exe` and `rustc.exe` proxies, so the toolchain step stops at `rustc: command not found`. `cargo clippy --target x86_64-pc-windows-msvc --all-targets -D warnings` passes on macOS, with a stand-in for the MSVC assembler and `llvm-rc`; no Windows test has run.
- Ubuntu: no runner carries the `ubuntu-24.04` label, so the job never starts. The tests pass in a Linux container (`scripts/linux.sh test --locked`).
- macOS: fish and PowerShell were missing, so the two shell tests failed, as they should without their shells. Homebrew installed both.
- Windows: rustup's `.cargo\bin` held `rustup.exe` alone, without the `cargo.exe` and `rustc.exe` proxies, so the toolchain step stopped at `rustc: command not found`. rustup's installer, run again, put them back.
- Ubuntu: no runner carried `ubuntu-24.04`, so 71 runs waited; they were cancelled, and a runner for this repository alone runs the job in Docker, in a VM of its own. Its first build was killed linking gpui's test binaries with debuginfo in 8 GB, so the Linux and Windows jobs build without debuginfo, as `scripts/linux.sh` does.
A clippy run for `x86_64-pc-windows-msvc` on macOS, with stand-ins for the MSVC assembler and `llvm-rc`, found `testlog` unused on Windows; it now builds only beside the Unix tests that use it.
Run 907 then failed the PowerShell test on macOS: it typed its probe right after the first folder report, which pwsh 7.6.6 sends before PSReadLine reads its first key, and the line was echoed, never run. The shell tests now type a line only once the line editor reads raw keys.
Run 910, on `be8d0e2`, passed all three jobs: macOS ran `scripts/check.sh` (135 tests, the fonts check, clippy, fmt, the 500-line rule) and the build; Ubuntu ran 138 tests and the build; Windows ran 113 tests and the build, without the Unix-only pty and shell tests.
## The owner's checks
On 2026-10-15 the owner ran the app on macOS and found three faults, now fixed:
- The desktop showed through sharp: on macOS 27 gpui's `Selection` material builds no blur; the window's view now takes `underWindowBackground`, radius 30.
- The debug build lagged; the release build does not.
- No button hid the side columns; each now has one at its end of the title bar, its state saved.