Skip to content

fix(frame,layout): keep the mini-window one line of the current font after a font change - #353

Open
tag-und-nacht wants to merge 2 commits into
eval-exec:mainfrom
tag-und-nacht:fix/mini-window-follows-font-line-height
Open

fix(frame,layout): keep the mini-window one line of the current font after a font change#353
tag-und-nacht wants to merge 2 commits into
eval-exec:mainfrom
tag-und-nacht:fix/mini-window-follows-font-line-height

Conversation

@tag-und-nacht

Copy link
Copy Markdown
Contributor

Summary

On macOS, user init that switches the default face from the startup font (Menlo 10, 11px lines) to a larger one (PragmataPro, 17px lines) left the echo area clipped: every one-line message showed only its top 11px until the user entered and left the minibuffer. The native window and the frame agreed on 502x430px throughout, and a surface readback matched the on-screen pixels row for row, so this was not window rounding: the minibuffer window kept the 11px it was created with while the frame's line height became 17px.

Two commits, each mirroring the GNU site that keeps the mini-window at one line of the current font (emacs-31.0.90):

  1. fix(frame): the font boundary. GNU's set_new_font_hook ends in adjust_frame_size (..., 3, false, Qfont) (ns_new_font, src/nsterm.m:11425-11428; x_new_font, src/xterm.c:27178-27181). With font outside frame-inhibit-implied-resize (the NS/X default, src/frame.c:7684-7687) that requests a frame keeping FRAME_LINES at the new line height and returns (src/frame.c:993-998); the toolkit's change_frame_size re-enters with inhibit 5 (src/nsterm.m:1906, src/dispnew.c:6726-6728) and reaches resize_frame_windows (src/frame.c:1076-1082), which gives the mini-window unit + decorations pixels with unit the new FRAME_LINE_HEIGHT (src/window.c:5051-5053,5125-5128) and re-derives every window's character edges. sync_live_frame_font_state_in_state now applies that one-line rule through the existing shrink_mini_window when the line height changes, and resyncs the character edges for any metric change, own or shared minibuffer.

  2. fix(layout): the redisplay mechanism. A sub-line mini-window can also arrive through set-window-configuration (the saved leaf's bounds are restored verbatim) or the engine's own metrics sync, and the redisplay-time check counted its allocation as floor(11 / 17).max(1) = 1 row, so a one-line message never asked to grow. GNU resize_mini_window compares pixel heights (src/xdisp.c:13276,13395-13406) and grow_mini_window adds exactly the difference (src/window.c:5896-5930). mini_window_rows is now the one rule for how many whole rows a mini-window height holds, shared by the engine's allocation check and by grow_mini_window_with_max_lines, which lands on whole rows of the current unit instead of adding rows to a stale pixel base (11px + 1 row of 17px gave 28px where GNU gives 17px). A height within half a pixel of k rows counts as k, so an f32 unit such as 11.9px cannot make a 3-row window count as 2 and turn the grow into a no-op the relayout loop would retry until its budget was gone.

Declared, not ported: the implied native resize itself (the frame keeps its pixel size and the root loses lines; a design note for that follow-up is in progress), the width/height frame parameters in the new units (resize_pixelwise owns those), the toolkit gates that skip the resize (NS fullscreen, X tooltip frames), and GNU's unit adding above-line line-spacing with exact-pixel growth (row multiples here; content is never under-grown).

Tests (red first)

  • xfaces::font_size_test::default_face_font_change_resizes_mini_window_to_one_line_of_the_new_font: 11px stayed 11px before, 18px after; plus a previously grown (33px) mini-window resetting to one line, an unchanged line height leaving it alone, and a frame without its own mini-window resyncing its root.
  • engine::tests::inactive_echo_area_grows_a_sub_line_mini_window_to_one_line_of_the_font: a planted sub-line mini-window under a one-line echo message grows to exactly one realized line (stayed at 9px before).
  • window_test::grow_mini_window_lands_on_whole_rows_of_the_current_unit (17px from 11px, 57px from 22px at 19px lines), grow_mini_window_always_moves_from_a_whole_row_count_at_a_fractional_unit, mini_window_rows_tolerates_float_division_at_whole_rows.

Verification

  • cargo fmt --all --check clean; clippy clean on the changed lines (the never_loop errors in window_test.rs and process/tests are pre-existing on main).
  • cargo test -p neovm-core --lib scoped to the font, mini-window and window_cmds tests: 320 pass; cargo test -p neomacs-layout-engine --lib echo and minibuffer tests pass except layout_frame_rust_preserves_propertized_echo_message_faces, which fails identically on main here (ZWJ clustering).
  • GUI (macOS, the reporter's PragmataPro config): after cargo xtask --release, the minibuffer goes from 11px to 17px at 17px lines during init and the surface readback shows the message with its descenders intact in the same 502x462 window.
  • Two independent adversarial review passes before pushing; their confirmed findings (the set-window-configuration path, the GNU path description, the f32 row edge) are what the second commit and the amended first address.

Not run: Linux, Windows, TTY (no platform-specific code is touched).

🤖 Generated with Claude Code

https://claude.ai/code/session_01NxL1hNkQvGiUyXAHJJuyXC

tag-und-nacht and others added 2 commits September 5, 2026 21:01
…hange

A frame is created with the startup font (Menlo 10, 11px lines on macOS)
and user init then sets the default face to a larger font (17px lines).
`sync_live_frame_font_state_in_state` updated `char_height` but the
minibuffer window kept the 11px it was created with:
`window_text_area_bounds_with_chrome` carries the mini-window's pixel
height forward verbatim, so every one-line message was laid out 17px
tall in an 11px window and showed only its top 11px until something else
resized the mini-window (e.g. entering and leaving the minibuffer).

GNU's `set_new_font_hook` ends in `adjust_frame_size (..., 3, false,
Qfont)` (`ns_new_font`, src/nsterm.m:11425-11428; `x_new_font`,
src/xterm.c:27178-27181, emacs-31.0.90).  With `font` outside
`frame-inhibit-implied-resize` (the NS/X default, src/frame.c:7684-7687)
that asks the window system for a frame keeping FRAME_LINES at the new
line height and returns (src/frame.c:993-998); the toolkit's
`change_frame_size` re-enters `adjust_frame_size` with inhibit 5
(src/nsterm.m:1906, src/dispnew.c:6726-6728), which reaches
`resize_frame_windows` (src/frame.c:1076-1082): the mini-window gets
`unit + decorations` pixels with `unit` the new FRAME_LINE_HEIGHT
(src/window.c:5051-5053,5125-5128) and every window's character edges
are re-derived.

Apply that one-line rule at the font boundary through the existing
`shrink_mini_window` (the mini-window has no mode line, so its box height
is `unit`), and re-derive the character edges for any metric change,
own or shared minibuffer.  Declared, not ported: the implied native
resize itself (the frame keeps its pixel size and the root loses lines),
the `width`/`height` parameters in the new units, and the toolkit gates
that skip the resize (NS fullscreen, X tooltip frames).

Verified on macOS: with the user's PragmataPro config the minibuffer went
from 11px to 17px at 17px lines and the surface readback shows the echo
message with its descenders intact in the same 502x462 window.  Linux and
Windows were not run; no platform-specific code is touched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxL1hNkQvGiUyXAHJJuyXC
… check

Adversarial review of the previous commit: fixing the font boundary
alone left the mechanism.  A mini-window shorter than one line of the
current font can also arrive through `set-window-configuration` (the
saved leaf's 11px bounds are restored verbatim, hooks.rs) or through the
engine's own metrics sync (`frame.char_height =
geometry.metrics.line_height`, which never touches the mini-window), and
the redisplay-time check counted its allocation as
`floor(11 / 17).max(1) = 1` row, so a one-line message never asked to
grow and the echo area stayed clipped.

GNU `resize_mini_window` compares the content's pixel height with the
window's (`old_height = WINDOW_BOX_TEXT_HEIGHT (w)`, src/xdisp.c:13276;
`height > old_height` / `height != old_height`, 13395-13406) and
`grow_mini_window` adds exactly that difference (src/window.c:5896-5930),
so the window lands on the content height: one line of the font.

- `mini_window_rows` (neovm-core): the one rule for how many whole rows
  a mini-window height holds, shared by the engine's allocation check and
  by `grow_mini_window_with_max_lines`.  A height shorter than one line
  holds zero rows, not one, so the grow-only comparison fires as GNU's
  does; a height within half a pixel of `k` rows is `k` rows, so an f32
  unit such as 11.9px cannot make a 3-row window count as 2 and turn the
  grow it triggers into a no-op that the relayout loop would retry until
  its budget was gone (second review).
- `grow_mini_window_with_max_lines`: land on whole rows of the current
  unit from the current row count instead of adding rows to the current
  pixel height, which compounded a stale base (11px + 1 row of 17px gave
  28px where GNU gives 17px; a 22px base under 19px lines gave 60px for
  three rows where GNU gives 57px).

Declared, not ported: GNU's unit adds `line-spacing` placed above the
line (src/xdisp.c:13324-13325) and grows by exact pixels, so image or
line-spacing content lands on a row multiple here where GNU keeps the
pixel height; content is never under-grown since the required rows are
`ceil(content / char_h)`.  Also corrects the same-line-height test's
comment from the first commit: GNU skips `resize_frame_windows` there
only for a frame whose height is a whole number of lines.

Tests: the engine grows a planted sub-line mini-window to exactly one
realized line under a one-line echo message (red before: it stayed at
9px); `grow_mini_window` lands on 17px and 57px from stale bases and on
four rows from three at an 11.9px unit; `mini_window_rows` at the f32
edge.  Verified on macOS; Linux and Windows were not run (no
platform-specific code is touched).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxL1hNkQvGiUyXAHJJuyXC
@eval-exec
eval-exec requested review from eval-exec and a balanced review from Copilot September 6, 2026 06:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@eval-exec eval-exec modified the milestones: v0.0.19, v0.0.18 Sep 6, 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.

3 participants