Skip to content

feat(splits): add auto-equalize-splits config option - #12595

Closed
mistra-be wants to merge 3 commits into
ghostty-org:mainfrom
mistra-be:feat/auto-equalize-splits
Closed

mistra-be wants to merge 3 commits into
ghostty-org:mainfrom
mistra-be:feat/auto-equalize-splits

Conversation

@mistra-be

Copy link
Copy Markdown

Summary

Adds a new bool config option auto-equalize-splits (default false)
that, when enabled, automatically equalizes all splits in the current
window whenever a split is created or closed. Equivalent to invoking
equalize_splits immediately after every create/close, but unlike a
chained keybind it works across close_surface too.

Closes #3861.

Motivation

Issue #3861 asks for iTerm2-style auto-equalize on every split. Today
the only way to get this is to chain equalize_splits after every
split-creating keybind (works fine post #10015), and there is no
way to do it after close_surface: chained keybinds containing a
closing action exit the chain immediately because the Surface self
pointer is freed (see src/Surface.zig closingAction guard, and
the discussion with @jcollie in #11040).

Rather than reaching across the apprt boundary from inside the
freed-Surface code path, this PR plumbs a single config flag down to
the runtime layer that already owns the split tree, and equalizes
in place there - on macOS in BaseTerminalController, on GTK in
SplitTree. No use-after-free, no chain-machinery changes, no new
actions.

Approach

  • src/config/Config.zig: new @\"auto-equalize-splits\": bool = false
    field with doc comment. Auto-wired by the existing reflective config
    parser, so CLI flag and +show-config --default --docs pick it up
    for free.
  • macOS (Ghostty.Config.swift, BaseTerminalController.swift):
    add accessor and DerivedConfig field. When the flag is true,
    newSplit() and removeSurfaceNode() pass tree.equalized()
    (instead of the raw inserted/removed tree) to replaceSurfaceTree.
    Because equalization runs on a value-typed copy before publishing,
    it's invisible to listeners and undo/redo restores exactly the
    pre-operation tree.
  • GTK (src/apprt/gtk/class/split_tree.zig): new autoEqualize
    helper centralizes the config check and the equalize/deinit/replace
    dance. Called from newSplit and the surface-removal path right
    before setTree.

Behavior change

Default: zero behavior change. Off by default.

With auto-equalize-splits = true:

  • Creating a split anywhere in the window resizes every split in
    that window so all leaves are weighted equally (same algorithm as
    the existing equalize_splits action / divider double-click).
  • Closing a split does the same on the surviving tree.
  • Manual resizes elsewhere in the same window are reset by any
    subsequent create/close. This is documented in the doc comment;
    it matches the user-requested 'always equalize' semantics and is
    consistent with how the existing equalize_splits action behaves.

Tests performed

  • zig build -Demit-macos-app=false clean on the latest tip of main.
  • macos/build.nu --configuration ReleaseLocal --action build clean
    on Xcode 26.4 + macOS Tahoe 26.4.1 arm64 with Zig 0.15.2 (Homebrew
    zig@0.15, contains the patch noted in HACKING.md for the Xcode
    26.4 linking bug).
  • zig build test -Dtest-filter=Config and -Dtest-filter=split clean.
  • Resulting binary's +show-config --default shows
    auto-equalize-splits = false; --docs renders the new doc comment.
  • Manual UI verification of create/close auto-equalize is pending on
    this PR; happy to extend the SplitTree Swift tests if reviewers want
    a programmatic regression there.

Risks / open questions

  1. Whole-tree scope. equalized() is recursive, so the option
    resets manual resizes in the whole window on any create/close.
    Could be scoped to the affected sub-split only; chose whole-window
    to match user intent ('always equal') and the existing PR macOS: equalize splits when double tapping on SplitView divider #9524
    double-click behavior. Happy to narrow it if you'd prefer.
  2. GTK parity. Implemented the GTK side too so the option isn't
    a half-feature, but I didn't add Linux end-to-end tests. The Swift
    side is the platform I actually run.
  3. Default value. I left it false for safety; if you'd rather
    ship it true as the new default I can flip it in a follow-up
    commit.

AI disclosure (per AI_POLICY.md)

This PR was authored with AI assistance via OpenCode + Claude
(opencode.ai, model claude-opus-4-7) used for codebase research,
design discussion, and code drafting. The patch is small (~50 lines)
and the human author has read every changed line, understands the
ownership of surfaceTree / Surface.Tree, and can explain why
equalizing before replaceSurfaceTree / setTree rather than after
is the right call (avoids touching the closingAction guard, keeps
undo blocks consistent).

mistra-be added 3 commits May 5, 2026 22:41
Adds a new bool config option that, when enabled, automatically
equalizes all splits in the current window whenever a split is
created or closed. Defaults to false to preserve existing behavior.

This is the public-facing knob; the actual equalization is performed
by the apprt layer (macOS, GTK) which owns the split tree, in
follow-up commits.

Refs: #3861, #11040
When auto-equalize-splits is enabled, the macOS terminal controller
runs SplitTree.equalized() on the new tree right before publishing
it to the controller, both when adding a new split and when removing
one. Equalization happens on a value-typed copy of the tree before
replaceSurfaceTree commits it, so it's invisible to listeners and
keeps undo/redo intact. No use-after-free risk: the surface tree is
owned by the controller and is rewritten on the main queue, far away
from the closing-action guard in src/Surface.zig that tripped up
chained keybinds.

Refs: #3861, #11040
GTK split-tree mirrors the macOS behavior: when the user enables
auto-equalize-splits the new tree is equalized in place before
setTree publishes it, both in newSplit and in the surface-removal
path. A small autoEqualize helper centralizes the config check and
the deinit/replace dance, and is a no-op when the option is off.

Refs: #3861
@ghostty-vouch

ghostty-vouch Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Hi @orbanconsult, thanks for your interest in contributing!

This project requires that pull request authors are vouched, and you are not in the list of vouched users.

This PR will be closed automatically. See https://github.com/ghostty-org/ghostty/blob/main/CONTRIBUTING.md for more details.

@ghostty-vouch ghostty-vouch Bot closed this May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Splits equalizing on splitting

1 participant