Skip to content

fix(layout): realize inverse-video after merging faces - #371

Merged
eval-exec merged 3 commits into
eval-exec:mainfrom
ArthurHeymans:fix/layout-inverse-video-realization
Sep 9, 2026
Merged

eval-exec merged 3 commits into
eval-exec:mainfrom
ArthurHeymans:fix/layout-inverse-video-realization

Conversation

@ArthurHeymans

Copy link
Copy Markdown
Contributor

Inverse-video faces were realized before later string and semantic faces were merged. This could put merged colors in the wrong slot, discard escape/nobreak color changes, and apply distant-foreground to the wrong side.

Preserve the pending inverse-video attribute through face resolution, merge later sources against the unswapped colors, and realize the swap once after merging. The escape/nobreak no-op check now compares both color slots, and distant-foreground follows the inverse-video slot.

The `header-line` face in themes such as tsdh-dark carries `:inverse-video t`.
GNU merges every face source (the row's base face, text-property faces, and
overlays) into one lface vector and swaps foreground and background exactly
once, at realization: `face_at_string_position` (src/xfaces.c:7105) copies the
base face's lface, and `load_face_colors` (src/xfaces.c:1389-1400) performs the
swap. A foreground-only face on a header-line string therefore paints its
colour as the background.

neomacs realized the base face's swap before the string's text-property faces
were merged, so a face such as `pi-coding-agent-model-name` (inheriting
`font-lock-type-face`) stayed a purple foreground on the light header line
instead of becoming the purple pill GNU Emacs draws.

Record GNU's merged `:inverse-video` attribute on `ResolvedFace` and let
`apply_specified_face_over` undo the base face's realized swap, merge the new
source against the pre-inverse colours, and swap once at the end. The flag also
participates in `same_resolved_face`, because it decides how later sources merge
over a face.

Add a resolver test for an inverse-video base merged with a foreground-only text
face. Two layout tests that asserted the pre-fix colours for an inverse base now
pin their base face flat, since their subject is remapping and glyphless
rendering rather than inverse video.
`merge_named_active_face` skipped installing a merged escape-glyph,
glyphless-char, nobreak-space, or nobreak-hyphen face when the foreground was
unchanged. GNU swaps a merged foreground into the background when the base face
is `:inverse-video` (`load_face_colors`, src/xfaces.c:1389-1400), and
`nobreak-space` can set `:background` outright (its `min-colors 8` branch), so a
foreground-only comparison discarded real color changes: an escape glyph over an
inverse-video base kept the base face instead of painting its color in the
background.

Compare both slots, including the terminal-default sentinels, before treating
the merge as a no-op. Add a layout test that puts an `(:inverse-video t)` text
property on a control char and asserts the escape-glyph color lands in the
caret's background.
`realize_face` and `apply_specified_face_over` always wrote
`:distant-foreground` into the foreground when the realized foreground and
background were too close. GNU replaces the *background* instead when the face
is `:inverse-video`, because the swap has already moved the colors
(`load_face_colors`, src/xfaces.c:1417-1425).

Write the distant color into whichever slot `:inverse-video` moved the
foreground to. Add a resolver test covering both the plain (foreground) and
inverse-video (background) cases.

@eval-exec eval-exec left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Approved for integration per maintainer direction. Local nextest review confirms the targeted improvements; a remaining face-merge regression involving lossy distant-foreground realization will be fixed on main with a regression test. This approval does not imply that remaining case is resolved.

@eval-exec
eval-exec merged commit 4111d85 into eval-exec:main Sep 9, 2026
26 of 29 checks passed
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.

2 participants