T23: the shell tests type a line only once the line editor reads raw keys: on CI's pwsh 7.6.6 the probe, typed right after the first folder report, came before PSReadLine's first read and was echoed, never run; shot warns when the app it runs under lacks Screen Recording, which left this Mac's pictures with the window's ground alone; AGENTS.md records the CI runners, the build without debuginfo and the typing rule
This commit is contained in:
5 files changed
+37
-4
No files matched your search
@@ -1,6 +1,6 @@
|
||||
# AGENTS.md
|
||||
|
||||
ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does; `TASKS.md` says how far it is.
|
||||
ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does; `TASKS.md` says how far it is; `tasks/acceptance.md` checks the PRD line by line.
|
||||
|
||||
## Stack
|
||||
|
||||
@@ -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; no permission needed for the app's own window), 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: 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:
|
||||
- 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;
|
||||
@@ -75,3 +75,4 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
|
||||
- 2026-10-15: on Wayland the window asks for `ext_background_effect_v1` beside gpui's `org_kde_kwin_blur`. A guest connection on gpui's own display binds it, takes the effect for the window's surface once, and blurs the window's pane: the surface less Ely's resize margin on each free edge of a client-drawn window, worked out each frame and sent only when it changes; it goes and comes back with Blurred and lands at the surface's next commit, which every frame makes. The guest connection reads the display only while the window opens, before gpui's loop runs, and is never dropped: a drop destroys its proxies through the display, which gpui may close first. The log names what the compositor offers, so which way blur goes: `ext_background_effect_v1`, gpui's `org_kde_kwin_blur`, or none, the desktop clear. Its test runs a `wayland-server` compositor on a socket pair.
|
||||
- 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.
|
||||
@@ -83,9 +83,15 @@ pub fn menus() -> Vec<String> {
|
||||
/// Writes window `number` as a PNG.
|
||||
pub fn snapshot(number: u32, path: &Path) -> anyhow::Result<()> {
|
||||
use core_graphics::{
|
||||
access::ScreenCaptureAccess,
|
||||
geometry::{CGPoint, CGRect, CGSize},
|
||||
window,
|
||||
};
|
||||
if !ScreenCaptureAccess.preflight() {
|
||||
log::warn!(
|
||||
"shot: the app shot runs under lacks Screen Recording, so the picture may hold the window's ground alone"
|
||||
);
|
||||
}
|
||||
let image = window::create_image(
|
||||
CGRect::new(&CGPoint::new(0.0, 0.0), &CGSize::new(0.0, 0.0)),
|
||||
window::kCGWindowListOptionIncludingWindow,
|
||||
|
||||
@@ -56,6 +56,8 @@ pub struct Pty {
|
||||
pub term: Arc<FairMutex<Term<Listener>>>,
|
||||
pub pid: u32,
|
||||
notifier: Notifier,
|
||||
#[cfg(all(test, unix))]
|
||||
master: std::fs::File,
|
||||
}
|
||||
|
||||
/// What a pty reports, in order.
|
||||
@@ -109,6 +111,10 @@ pub fn spawn(
|
||||
)
|
||||
})?;
|
||||
let pid = child_id(&raw)?;
|
||||
#[cfg(all(test, unix))]
|
||||
let master = raw
|
||||
.master()
|
||||
.map_err(|error| anyhow::anyhow!("pty {pid}: no copy of the master: {error}"))?;
|
||||
log::info!(
|
||||
"pty {pid}: started {:?} in {}",
|
||||
launch.program,
|
||||
@@ -135,6 +141,8 @@ pub fn spawn(
|
||||
term,
|
||||
pid,
|
||||
notifier,
|
||||
#[cfg(all(test, unix))]
|
||||
master,
|
||||
},
|
||||
events,
|
||||
))
|
||||
@@ -176,6 +184,16 @@ impl Pty {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix))]
|
||||
impl Pty {
|
||||
/// Whether the program reads keys one by one, as a line editor does.
|
||||
pub(crate) fn reads_keys(&self) -> bool {
|
||||
use rustix_openpty::rustix::termios::{LocalModes, tcgetattr};
|
||||
let modes = tcgetattr(&self.master).expect("the pty's modes");
|
||||
!modes.local_modes.contains(LocalModes::ICANON)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Pty {
|
||||
fn drop(&mut self) {
|
||||
if self.notifier.0.send(Msg::Shutdown).is_err() {
|
||||
|
||||
@@ -108,7 +108,9 @@ impl Run {
|
||||
}
|
||||
}
|
||||
|
||||
fn send(&self, line: &str) {
|
||||
/// Types `line` at a prompt, once its 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());
|
||||
}
|
||||
|
||||
@@ -131,9 +133,9 @@ impl Run {
|
||||
/// The prompt's folder, the script loaded by `probe`, then a cd into the inner folder.
|
||||
fn walk(&mut self, probe: &str) {
|
||||
let start = self.start.clone();
|
||||
self.send(probe);
|
||||
self.until("first folder", |run| run.folders.contains(&start));
|
||||
assert_eq!(self.folders[0], start);
|
||||
self.send(probe);
|
||||
self.until("loaded script", |run| run.shows("ELY-LOADED"));
|
||||
self.send("cd in*");
|
||||
let inner = start.join(INNER);
|
||||
|
||||
@@ -44,6 +44,12 @@ impl Pty {
|
||||
pub fn id(&self) -> u32 {
|
||||
self.child.id()
|
||||
}
|
||||
|
||||
/// A copy of the master, for tests to read its modes.
|
||||
#[cfg(test)]
|
||||
pub fn master(&self) -> io::Result<File> {
|
||||
self.file.try_clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts `program`, or the login shell, in `cwd` on a new pty of `size`.
|
||||
|
||||
Reference in new issue
Block a user