T30: the main window opens at the size it was left at: its size, read once the bounds settle and kept in the settings unless the window is maximized or full screen, opens the next window, shrunk where it passes the screen
CI / platforms (ubuntu-24.04) (push) Successful in 1m1s
CI / platforms (windows-latest) (push) Successful in 1m40s
CI / macos (push) Successful in 12m35s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-16 03:43:15 -04:00
1 parent a5dbfbcbae
commit 1bf420ce41
6 files changed
+93 -9

No files matched your search

+2 -1
View File
@@ -30,7 +30,7 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
## Layout
- `build.rs`: on Windows, the icon into the executable as resource 1, through `assets/icons/elyterminal.rc`. `.cargo/config.toml`: Windows builds carry the C runtime. `rust-toolchain.toml`: the toolchain rustup installs.
- `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/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, at the size the settings keep, which it keeps as it settles. `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; `omarchy`, on Linux, wears Omarchy 4's current theme and follows its switches.
- `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; `herdr` follows herdr's focused pane through herdr's socket while herdr's client leads the pty's foreground. `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.
@@ -93,3 +93,4 @@ ELYTerminal. A terminal on GPUI and Ely components. `PRD.md` says what it does;
- 2026-10-15: on Omarchy 4 (Linux) ELYTerminal follows the system's theme. Omarchy renders the current theme's `colors.toml` through its own resolver into `~/.local/state/omarchy/current/theme/ghostty.conf`, under `$HOME` whatever `XDG_STATE_HOME` says, so ELYTerminal reads that file and wears what Ghostty wears there: its color lines go through the strict parser the bundled themes use, and the rest, such as the glass block, pass by. Omarchy writes no `cursor-text`, so the background stands in, as in Ghostty and Omarchy's kitty template. ELYTerminal reads the file twice a second, since Omarchy signals only Alacritty, kitty and Ghostty, by name, and wears it whenever its text changes and parses; a check that fails is logged once while it lasts and tried again, so a file made readable again is worn though nothing else about it changed. The Omarchy theme is the default unless that file is surely absent, so a folder that cannot be read makes a fresh install wear it and the start name the error; another pick stops following. At start a missing file means no Omarchy unless the settings wear it, a file that cannot be read stops the start, and one that does not parse stops it only when worn, else it is logged and no Omarchy card shows. While running, a missing file is a switch in progress, and any other failure keeps the last theme. Omarchy 3's `~/.config/omarchy/current` gets no shim.
- 2026-10-16: on Hyprland 0.56 a window blurs only while Hyprland's own `decoration:blur:enabled` is on: its `shouldBlur` reads that option first, then the window's `no_blur`, RGBX and opaque rules, then the blur region a surface sets through `ext_background_effect_v1`, which Hyprland offers with the blur capability whatever the option says. Omarchy 4.0.4's default `looknfeel.lua` turns the option off, so on Omarchy no window blurs until the user turns it on; ELYTerminal leaves the compositor's settings alone. On the owner's Omarchy the log named `ext_background_effect_v1` and the pane's region, and the wallpaper behind the window blurred with the option on and showed sharp with it off.
- 2026-10-16: herdr 0.9 keeps its panes' OSC 7 and writes only a title, notifications, OSC 52 and bells to the terminal it runs in, so no terminal hears a folder from inside it; herdr's server knows each pane's `cwd`, the folder its shell reported, else the shell's own. On macOS and Linux each session asks twice a second which process group leads its pty's foreground (`tcgetpgrp` on a copy of the master) and what executable that group's leader runs: an exec keeps the group and swaps the executable, so `exec herdr` from a shell is a new program, and one that did not read is tried again; on Linux the ` (deleted)` mark an update leaves on a running executable is set aside. When that executable is `herdr` and its arguments start herdr's terminal client (no command, `session attach NAME` or `client`), ELYTerminal reads the client's arguments and variables (`/proc` on Linux; `KERN_PROCARGS2` on macOS, which hides only the variables of Apple's own programs) and finds its socket as herdr 0.9 does: `--session NAME`, then `HERDR_SOCKET_PATH`, then `HERDR_SESSION`, under `$XDG_CONFIG_HOME/herdr`, else `~/.config/herdr`; a relative socket lies in the client's folder, as herdr reads it (`/proc/PID/cwd`; `proc_pidinfo` on macOS). It asks that socket for `session.snapshot` twice a second, a connection each, since herdr closes one after its reply, and reports the focused pane's `cwd` as the session's folder when it changes and again each time herdr takes the foreground. Focus belongs to herdr's server, so clients of one server follow one focus; a client of another machine's server (`--remote`, `--machine`) is logged, not followed. A failure is logged once while it lasts. Windows has none of this: there herdr is a beta on named pipes.
- 2026-10-16: the main window opens at the size it was left at, which the owner asked for on macOS, Windows and Linux: `window_width` and `window_height` in the settings hold the size gpui's `window_bounds` gives 0.5 s after the bounds settle, saved through `SettingsStore::update`, which writes only a change, so a drag saves once and a move saves nothing. A maximized or full-screen window keeps the size from before; its state and size are read once they settle, since X11 marks a window maximized after its new size, and gpui's X11 window then gives a maximized window's own size. The window opens centered, its place not kept, and a kept side past the primary display's visible area shrinks to it, logged. A size outside 100 to 16384 stops the start, as any value out of range does.
+1
View File
@@ -61,6 +61,7 @@ Ghostty is the bar for what the terminal does. ELYTerminal owns its engine on `a
- 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, 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 window opens at the size it was left at, on macOS, Windows and Linux: the size is kept in settings once it settles; a maximized or full-screen window keeps the size from before; a kept size past the screen shrinks to it.
- The title shows the active session's title, and no other text.
- The pointer over the terminal is the arrow.
- 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.
+1 -1
View File
@@ -44,7 +44,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. On Windows and Linux a gear beside the right one opens Settings.
- A button at each end of the title bar hides or shows its side column; the window opens as it was left, at the size it was left at. On Windows and Linux a gear beside the right one opens Settings.
- Files shows the session's folder as a tree with Git's marks. Inside herdr, on macOS and Linux, the folder is that of herdr's focused pane.
- 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. On Omarchy 4, an Omarchy theme follows the system's theme as it switches, and is the default there.
+70 -6
View File
@@ -1,10 +1,10 @@
use std::borrow::Cow;
use std::{borrow::Cow, time::Duration};
use anyhow::Context as _;
use ely_gpui_component::theme::ActiveTheme;
use gpui::{
App, AppContext, Bounds, TitlebarOptions, WindowBounds, WindowDecorations, WindowHandle,
WindowOptions, px, size,
App, AppContext, Bounds, Context, Pixels, Size, Task, TitlebarOptions, Window, WindowBounds,
WindowDecorations, WindowHandle, WindowOptions, px, size,
};
#[cfg(target_os = "macos")]
@@ -17,6 +17,9 @@ use crate::{
workspace::{self, TITLE, Workspace},
};
/// How long the window's size holds before it is kept, so a drag saves once.
const SETTLE: Duration = Duration::from_millis(500);
/// The id the macOS bundle and Linux desktop entry share.
pub const APP_ID: &str = "com.zacharyzhang.elyterminal";
@@ -85,7 +88,8 @@ pub fn start(cx: &mut App) -> anyhow::Result<()> {
/// Opens the main window running `launch`.
pub fn open(launch: Launch, cx: &mut App) -> anyhow::Result<WindowHandle<Workspace>> {
let bounds = Bounds::centered(None, size(px(1200.0), px(760.0)), cx);
let kept = opening_size(cx);
let bounds = Bounds::centered(None, kept, cx);
let background = blur::appearance(SettingsStore::get(cx));
let options = WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
@@ -105,11 +109,71 @@ pub fn open(launch: Launch, cx: &mut App) -> anyhow::Result<WindowHandle<Workspa
window
.observe_window_appearance(|_, cx| themes::apply(cx))
.detach();
cx.new(|cx| Workspace::new(pty, events, launch, window, cx))
cx.new(|cx| {
keep_size(window, cx);
Workspace::new(pty, events, launch, window, cx)
})
})?;
log::info!(
"app: main window open, blur: {background:?} on {}",
"app: main window open at {} by {}, blur: {background:?} on {}",
f32::from(kept.width),
f32::from(kept.height),
std::env::consts::OS
);
Ok(window)
}
/// The kept size, within the primary display's visible area where that is known.
fn opening_size(cx: &App) -> Size<Pixels> {
let settings = SettingsStore::get(cx);
let room = cx
.primary_display()
.map(|display| display.visible_bounds().size);
let fit = |want: f32, side: Option<Pixels>| match side.map(f32::from) {
Some(side) if side > 0.0 && want > side => {
log::info!(
"app: a kept side of {want} px passes the screen's {side}; the window takes {side}"
);
px(side)
}
_ => px(want),
};
size(
fit(settings.window_width, room.map(|area| area.width)),
fit(settings.window_height, room.map(|area| area.height)),
)
}
/// Keeps the window's size in the settings once it settles; maximized or full screen, the size before stays.
fn keep_size(window: &mut Window, cx: &mut Context<Workspace>) {
let mut _settling: Option<Task<()>> = None;
cx.observe_window_bounds(window, move |_, window, cx| {
_settling = Some(cx.spawn_in(window, async move |_, cx| {
cx.background_executor().timer(SETTLE).await;
// Read once settled: X11 marks a window maximized after its new size.
let kept = cx.update(|window, cx| {
if window.is_maximized() || window.is_fullscreen() {
return Ok(());
}
let area = window.window_bounds().get_bounds().size;
let (width, height) = (
f32::from(area.width).round(),
f32::from(area.height).round(),
);
SettingsStore::update(cx, |settings| {
settings.window_width = width;
settings.window_height = height;
})
.with_context(|| {
format!("app: the window's size, {width} by {height}, is not kept")
})
});
match kept {
Ok(Ok(())) => {}
Ok(Err(error)) => log::error!("{error:#}"),
Err(_) => log::debug!("app: the window closed before its size settled"),
}
}));
})
.detach();
}
+17
View File
@@ -75,6 +75,9 @@ pub struct Settings {
pub show_sessions: bool,
pub show_panel: bool,
pub panel: PanelTab,
/// The main window's size, unmaximized, in pixels.
pub window_width: f32,
pub window_height: f32,
}
impl Default for Settings {
@@ -100,6 +103,8 @@ impl Default for Settings {
show_sessions: true,
show_panel: true,
panel: PanelTab::Git,
window_width: 1200.0,
window_height: 760.0,
}
}
}
@@ -109,6 +114,7 @@ pub const LINE_HEIGHT: RangeInclusive<f32> = 1.0..=2.0;
pub const OPACITY: RangeInclusive<f32> = 0.3..=1.0;
pub const PADDING: RangeInclusive<f32> = 0.0..=64.0;
pub const SCROLLBACK: RangeInclusive<u32> = 0..=100_000;
pub const WINDOW: RangeInclusive<f32> = 100.0..=16384.0;
impl Settings {
/// Every value inside its range, every theme built in; the error names the field.
@@ -119,6 +125,8 @@ impl Settings {
within("padding_x", self.padding_x, &PADDING)?;
within("padding_y", self.padding_y, &PADDING)?;
within("scrollback", self.scrollback, &SCROLLBACK)?;
within("window_width", self.window_width, &WINDOW)?;
within("window_height", self.window_height, &WINDOW)?;
for (field, id) in [
("theme", Some(&self.theme)),
("dark_theme", self.dark_theme.as_ref()),
@@ -225,6 +233,15 @@ mod tests {
error.to_string(),
"scrollback is 100001, outside 0 to 100000"
);
let settings = Settings {
window_width: 50.0,
..Settings::default()
};
let error = settings.validate().expect_err("too narrow");
assert_eq!(
error.to_string(),
"window_width is 50, outside 100 to 16384"
);
}
#[test]
+2 -1
View File
@@ -9,7 +9,7 @@ What no check reached: no one has looked at the window on Windows, which CI buil
| PRD line | Task | Evidence | Status |
|---|---|---|---|
| One window: sessions left, terminal middle, Git or Files right | T11 | T11 review; a real X11 window shows the terminal and the right column | Pass |
| Every setting has a control in the settings window | T16–T18 | 17 of the 20 fields have a row; `show_sessions`, `show_panel` and `panel` are the main window's toggles and switch, which §6 keeps in the file | Pass |
| Every setting has a control in the settings window | T16–T18, T30 | 17 of the 22 fields have a row; `show_sessions`, `show_panel` and `panel` are the main window's toggles and switch, and `window_width` and `window_height` its size, which §6 keeps in the file | Pass |
| Silent: no sound, bell, notification, update check, telemetry, tip or onboarding | T10, T12 | a bell is logged and marks a hidden row; the code holds no network call or notification | Pass |
| Clean: no subtitle, tagline, logging option, status bar or badge without state | T11–T18 | reviews of the layout and settings tasks | Pass |
| Built-in themes, each with a preview, Catppuccin among them; a theme colors terminal and chrome | T03, T17 | `themes::tests`; T17 review | Pass |
@@ -70,6 +70,7 @@ What no check reached: no one has looked at the window on Windows, which CI buil
|---|---|---|---|
| `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, 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 window opens at the size it was left at; maximized or full screen, the size before stays; a kept size past the screen shrinks to it | T30 | `a_value_out_of_range_names_its_field`; on X11 under xfwm4: the first start opened at 1200 by 760 and wrote nothing; a resize to 900 by 600 was kept with one write, and the next start opened at 900 by 600 and wrote nothing; maximized by xfwm4's Alt+F10 the window spanned the 1400 by 900 screen while the file kept 900 by 600, and restored it was 900 by 600 again; a kept 3000 by 2000 opened at 1400 by 900, logged; on macOS under `shot`, a kept 900 by 600 opened at 900 by 600 and a kept 5000 by 4000 at the visible 1920 by 1113, logged; a resize on macOS and any run on Windows not yet shown | Partial |
| The title shows the session's title, nothing else | T11 | log: "the title is", per session shown | Pass |
| The pointer over the terminal is the arrow | T26 | the terminal sets no pointer, so gpui's arrow shows; on X11 with the pointer over the grid, gpui asked for no I-beam ("text"), which it had asked for, and failed to load under Xvfb, before | Pass |
| The title bar is transparent | T11, T19 | T19 review | Pass |