Skip to content

Add ghostel-window-padding-balance to align the grid vertically - #664

Merged
dakra merged 1 commit into
mainfrom
feat/window-padding-balance
Sep 1, 2026
Merged

dakra merged 1 commit into
mainfrom
feat/window-padding-balance

Conversation

@dakra

@dakra dakra commented Aug 31, 2026 •

Copy link
Copy Markdown
Owner

Fixes #663.

A window is rarely a whole number of terminal rows tall. When nothing precedes the first row (alternate screen, unscrolled primary screen) the leftover pixels sit as a gap below the last row — so tmux's status bar floats above the mode line, while a fixed top row (htop, emacs -nw) looks truncated.

What this adds

ghostel-window-padding-balance (graphical frames on Emacs 29+ only) picks where the leftover goes:

  • top (default) — below the last row, as before.
  • center — split between top and bottom, like ghostty's window-padding-balance. t is accepted as center.
  • bottom — all of it above the first row, so the last row sits flush against the mode line. Natural for a shell under tmux, and consistent with how the primary screen already clips at the top once scrollback exists.

Any other value falls back to top.

Implementation

The pad is a before-string display line above grid row 1 — an empty overlay at point-min whose before-string is a tiny-face newline carrying line-height = pad pixels. A line-height on row 1's own newline was tried first but redisplay drops it when the row fills the window width (emacs -nw menu bar, full echo lines), and the native renderer rewrites that newline; the separate pad line is immune to both. The pad is buffer-wide (the smallest graphical window showing the buffer bounds it), removed once scrollback precedes row 1, and dropped on major-mode change and buffer reuse.

A leftover of a row or more never pads: it means the grid does not fill the window yet (startup, pre-resize), or the body-capped pixel measurement lost the pad row. Without this guard, enabling the option before the terminal started seeded an oversized pad that ratcheted up without bound (found in live testing); such a pad now self-heals on the next anchor.

Default = no impact

With top the pad overlay is never created and window-start/vscroll are byte-for-byte what they were before; the anchor path adds ~0.28 µs of pure elisp per anchor (no extra window-text-pixel-size call), well under 1% of an anchor.

Testing

  • 11 native ERT tests (test/ghostel-scroll-test.el): alt-screen split, floor rounding, bottom takes the full leftover, unfilled grid gets no pad, oversized-pad recovery, row-1 rewrite (full-width and empty), sub-row shrink rebalance, scrollback yield, buffer-wide min across windows, option toggling (including t = center), major-mode drop. The mocked pixel measurement models the real body-capped scan: a pad of a row or more is not counted.
  • Verified live under GUI elate sessions: clean start with bottom set before the terminal (the previously wedging case) settles at the exact sub-row leftover and stays put across output floods; runtime toggling between all three values; pixelwise resize re-settling; plus the original alt-screen matrix (tmux, htop, less, vim, emacs -nw) and dynamic-geometry/lifecycle paths.

make -j8 all green.

@mallet

mallet commented Aug 31, 2026

Copy link
Copy Markdown

I have tried it. It does center a tmux instance instead of aligning it at the top. Would it be possible to change the option to allow either top alignment (current behavior), centering (to mirror Ghostty), or more importantly bottom alignment? When using Ghostel for a Bash shell, I think bottom alignment is more natural, and is actually Ghostel's behavior outside an alternate screen. Therefore, when using tmux to host a shell, it would make sense to align at the bottom too. At least it would be very helpful to have the option.

In Ghostty, there is no visual anchor like Emacs's modeline, so I understand the option was not proposed. The situation in Ghostel is a bit different, especially when switching between Emacs buffers, or with side-by-side buffers.

I have noticed a slight issue, which is extremely minor and does not really need to be fixed. I am still reporting it in case it is more important than I anticipate. With a colored prompt like
PS1="[\e[1;38;5;24;48;5;153m]prompt_line$[\e[0m] "
the top gap includes a few pixels of the same background color (on the left). See the screenshot.
2026-08-31_12-39-17-413776965

Thank you again for all the work!

@dakra
dakra force-pushed the feat/window-padding-balance branch from 91f6635 to bc73aad Compare August 31, 2026 19:21
@dakra dakra changed the title Add ghostel-window-padding-balance to center the grid vertically Add ghostel-window-padding-balance to align the grid vertically Aug 31, 2026
@dakra
dakra force-pushed the feat/window-padding-balance branch from bc73aad to 2f3d682 Compare August 31, 2026 19:31
@dakra

dakra commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

@mallet can you check again, I added a bottom option so you can choose between top (default), center and bottom alignment. The color leak issue should also be fixed.

A window is rarely a whole number of terminal rows tall.  When nothing
precedes the first row (alternate screen, unscrolled primary screen) the
leftover pixels sit as a gap below the last row, so tmux's status bar
floats above the mode line while a fixed top row (htop, emacs -nw) looks
truncated.

The option picks where the leftover goes: top (default) keeps it below
the last row, center splits it like ghostty's window-padding-balance,
bottom moves it all above the first row so the last row sits flush
against the mode line.  The pad is a before-string display line above
row 1 (a line-height on row 1's own newline is dropped by redisplay
when the row fills the window width, and the renderer rewrites that
newline).  A leftover of a row or more never pads: the grid does not
fill the window yet, or the body-capped pixel measurement lost the pad
row, which would otherwise ratchet the pad up without bound.  Graphical
frames on Emacs 29+ only.

Fixes #663
@dakra
dakra force-pushed the feat/window-padding-balance branch from 2f3d682 to dcb58fc Compare August 31, 2026 19:46
@mallet

mallet commented Aug 31, 2026

Copy link
Copy Markdown

@dakra Thank you very much. This is an excellent design, and it will be very useful.

However, at the moment, the options center & bottom both are unstable, behave exactly the same and introduce the same gaps.

In a new Bash shell (Ghostel just launched), pressing any key makes the shell jump up or down (alternately). See below the difference after pressing backspace (once or twice):
2026-08-31_14-52-43-802574234
2026-08-31_14-52-56-106583552
Once one command has been executed in the shell, everything is back to normal and stable.

In tmux, even after a command has been executed (inside tmux and in the hosting shell too), pressing a key makes the tmux area toggle between bottom alignment and top alignment (regardless of the option: center or bottom--but top is fine).
2026-08-31_15-07-53-922361530
2026-08-31_15-08-11-250494744
To be fully accurate, the very first keypress makes the tmux area move both up and down, in one go. The following keypresses make it move up or down, alternatively.

@dakra
dakra force-pushed the feat/window-padding-balance branch from dcb58fc to 17d7b76 Compare August 31, 2026 21:28
@dakra

dakra commented Aug 31, 2026

Copy link
Copy Markdown
Owner Author

@mallet can you try again? and what Emacs version are you running btw? Because I'm on latest master 32 and there it worked but apparently this part changed multiple times in Emacs 30 and 31.

@mallet

mallet commented Aug 31, 2026

Copy link
Copy Markdown

@dakra This is much better. Very stable, no glitches.

I have noticed a slight problem at the start of Ghostel, hence for a raw Bash shell which is not yet full of lines. In bottom mode, it is perfect. But in center & top mode, gaps are applied as if the Bash shell were an alt screen.

In top mode, the initial shell prompt is aligned at the top:
2026-08-31_18-00-34-479827959
When a third line is displayed (the buffer/window is then full), it is corrected: the prompt is perfectly aligned at the bottom and the top line is truncated.

In center mode, there is the slight gap that would be applied with an alt screen:
2026-08-31_18-01-03-041080861
Again, this returns to normal when the buffer/window is full.

The rest is perfect. I have tested center, bottom and top. Except at startup with center & top, I see no issue in raw Bash or tmux, even when resizing.

My exact version is GNU Emacs 30.2 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.50, cairo version 1.18.4) of 2025-11-22, modified by Debian.

@dakra
dakra force-pushed the feat/window-padding-balance branch from 17d7b76 to b46413d Compare September 1, 2026 08:45
@dakra

dakra commented Sep 1, 2026

Copy link
Copy Markdown
Owner Author

@mallet thanks for testing so extensively.
I merged this PR now as is. The bottom/center alignment on a fresh terminal is on purpose.
The alignment happens not if it's in primary or alt screen but if there is scrollback or not.
So in a fresh terminal, when there is no scrollback it's always top aligned (just like when you open a new Emacs buffer).
I guess we could discuss if that behavior should also change but since it's an existing behavior already on main I think merging this is fine.
But please comment (or open a new issue) if you find something else or have a comment.

@dakra
dakra merged commit b46413d into main Sep 1, 2026
33 checks passed
@dakra
dakra deleted the feat/window-padding-balance branch September 1, 2026 08:50
@mallet

mallet commented Sep 1, 2026

Copy link
Copy Markdown

Thank you very much for implementing this @dakra ! Much appreciated.
I will use this version, obviously, and let you know if I find something else.

This branch was previously deployed

1 inactive deployment
github-pages — b46413d6 Deployed Sep 1, 2026 by dakra via deploy #181
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.

Extra vertical gap above the modeline when using tmux

2 participants