Skip to content

fix(native): open the code and diff views without fontconfig on Windows and macOS - #828

Merged
omartelo merged 8 commits into
mainfrom
fix/native-code-view-without-fontconfig
Oct 19, 2026
Merged

omartelo merged 8 commits into
mainfrom
fix/native-code-view-without-fontconfig

Conversation

@omartelo

@omartelo omartelo commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

On Windows (and macOS without Homebrew's fontconfig), 0.71.x's native window exits with status 1 right after it starts. A user's lich-native.log ends on:

fontconfig not found: fc-match is not on PATH (resolving monospace:regular)

The code and diff views were built with godemirror.New / NewDiffView, which resolve a family name only through fc-match. run returned that error and the window exited, so lich fell back to the web window.

Fix

  • code_view_fontconfig.go (Linux and the BSDs) keeps godemirror.New / NewDiffView, with godemirror's fallback for characters the family lacks.
  • code_view_fontscan.go (Windows, macOS) reads the four styles from the files fonts.Locate already picks and builds the views with NewFromFonts / NewDiffViewFromFonts. A character the family lacks draws as a box, and a style that sits deeper in a collection draws with its first face, the same ceiling the terminal's rasterizer has.

CI: Windows now runs, not only links

Until now native.yml only cross-linked lich-native.exe, so nothing ever started the window on Windows. That is how this shipped.

  • windows-build also cross-builds one test binary per native package, plus lich.exe, and uploads them.
  • The new windows job (windows-latest) runs each test binary from its package directory and fails if a render test skips for want of a GPU. It then runs .github/scripts/win-native-smoke.sh, the Windows twin of the macOS smoke: a windowless backend, one shell session, and the window has to come up, draw its -shot, stay alive and close on lich quit.

Verified

  • TestTheCodeAndDiffViewsOpenInTheSystemMonospace, cross-built for Windows and run under Wine: fails without the fix with the exact error from the user's log, passes with it.
  • The same smoke flow run by hand under Wine: the build without the fix exits 1 with the user's log lines, and the fixed build connects and draws.
  • The other native test binaries pass under Wine, except three emoji tests in glyphs: the Wine prefix has no emoji font. The real runner is the answer for those.
  • Local native gate (gofmt, go vet, go test ./...) green; actionlint clean on native.yml.

Color emoji on Windows

The new Windows job's first run found that the terminal's rasterizer drew emoji in color only from PNG bitmaps (CBDT, sbix). Segoe UI Emoji stores them as COLR layers, so on Windows they came out as plain outlines (TestMaskDrawsColorEmoji and TestOnlyAColorFontIsParsedForBitmaps failed on windows-latest). native/glyphs/colr.go and colr_fill.go now draw COLR version 0 layers and version 1 paint graphs: solid, linear, radial and sweep fills; every transform; glyph clips; composite modes. Hue, saturation, color and luminosity mix as normal (ponytail: in the code), and variable paints are read at the default instance.

Checked by rendering a row of emoji from Twemoji (COLR v0) and Noto COLRv1 and looking at it, and by running the glyphs tests cross-built for Windows under Wine with a COLRv1 font installed. Then on windows-latest.

Two backend test flakes, fixed at the cause

Neither comes from this PR's changes; both turned up on its CI.

  • copy repository template: open .git/objects/maintenance.lock: no such file or directory (internal/project, ci / test). Since git 2.46 a commit starts git maintenance run --auto --detach, which outlives it holding objects/maintenance.lock (seen with strace). initRepo copying the template right after listed the lock and failed to open it once it was gone. The template now sets maintenance.auto false. Measured with the template's build-then-copy step, 16 processes on two CPUs: 3 failures in 4800 before, 0 in 4800 after.
  • go: unlinkat ...\project.test.exe: Access is denied (os-tests (windows-latest) on fix(native): unfold dropped and attached file paths #829, run 39256549322, no test failing). TestSubprocessBudgetOutlivesAGrandchild left its grandchild, a re-run of project.test, asleep for ten seconds past the test. A package that finished sooner left it running the binary go test then deletes, which Windows refuses. The grandchild now holds the pipe until the test's cleanup releases it, and the cleanup waits for it to exit. Measured by running the two TestSubprocess tests 20 times and checking for a project.test process right after each: 20/20 left one before, 0/20 after. The test still fails without WaitDelay: Output would then wait the grandchild's full 10 s hold.

…ws and macOS

godemirror resolves a family name only through fc-match, which Windows and
macOS do not ship, so the native window exited at startup there. Build the
views from the files fonts.Locate picks on those systems instead.

native.yml now runs the native tests on a real Windows runner and opens the
window once against a backend, as the macOS job does.
…without-fontconfig

# Conflicts:
#	CHANGELOG.md
Segoe UI Emoji stores its glyphs as COLR layers, not the PNG bitmaps
(CBDT, sbix) the terminal's rasterizer drew, so emoji came out as plain
outlines on Windows. Draw COLR version 0 layers and version 1 paint graphs:
solid and gradient fills, transforms, glyph clips and composite modes.

The Windows smoke now runs even when the native tests fail.
…nter test

macOS's Menlo is a .ttc, which ParseTTF refuses.
…template

Since git 2.46 a commit starts `git maintenance run --auto --detach`, which
outlives it holding objects/maintenance.lock. initRepo copying the template
right after listed the lock and failed to open it once it was gone:
"copy repository template: open .git/objects/maintenance.lock: no such file
or directory". Measured with the template's build-then-copy under two CPUs
of contention: 3 failures in 4800 before, 0 in 4800 with maintenance.auto
off, and the commit no longer spawns maintenance at all.
TestSubprocessBudgetOutlivesAGrandchild left its grandchild, a re-run of
project.test, asleep for ten seconds past the test. A package that finished
sooner left it running the binary go test then deletes, which Windows refuses:
"go: unlinkat ...\\project.test.exe: Access is denied" (os-tests on #829,
run 39256549322). The grandchild now holds the pipe until the test's cleanup
releases it, and the cleanup waits for it to exit.

Measured by running the two TestSubprocess tests 20 times and checking for a
project.test process right after each run: 20/20 left one before, 0/20 after.
…without-fontconfig

# Conflicts:
#	CHANGELOG.md
…without-fontconfig

# Conflicts:
#	CHANGELOG.md
@omartelo
omartelo merged commit d4a0d58 into main Oct 19, 2026
10 checks passed
@omartelo
omartelo deleted the fix/native-code-view-without-fontconfig branch October 19, 2026 13:53
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.

1 participant