auto cover style: use sextants when fontconfig has a font with the glyphs

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 23:08:52 +02:00
co-authored by Claude Sonnet 5.5
parent 108541af91
commit b3275f6a81
7 changed files with 62 additions and 5 deletions
+2 -2
View File
@@ -43,8 +43,8 @@ type UI struct {
// block, e.g. kitty, WezTerm, foot, Ghostty), "kitty" (real images through
// the Kitty graphics protocol with Unicode placeholders: kitty and
// Ghostty) or "auto" (default: the Kitty protocol where the terminal is
// known to support it, else sextant on terminals known to draw them, else
// quadrant). The block styles are also the fallback for "kitty".
// known to support it, else sextant on terminals known to draw them or when
// fontconfig finds a font with the glyphs, else quadrant). The block styles are also the fallback for "kitty".
CoverStyle string `json:"cover_style,omitempty"`
}
+21
View File
@@ -8,6 +8,7 @@ import (
"image/color"
_ "image/jpeg" // poster formats
_ "image/png"
"os/exec"
"strings"
"time"
@@ -138,9 +139,29 @@ func coverStyleFor(setting string, env func(string) string) coverStyle {
if env("KITTY_WINDOW_ID") != "" || env("WEZTERM_PANE") != "" {
return sextantStyle
}
// Other terminals (GNOME Terminal, Konsole, Alacritty...) draw glyphs their
// font lacks from a fallback font, so sextants work if some installed font
// has them. Over SSH the installed fonts are the remote machine's, which
// says nothing about the terminal, so keep the safe style there.
if env("SSH_CONNECTION") == "" && env("SSH_TTY") == "" && sextantFontAvailable() {
return sextantStyle
}
return quadrantStyle
}
// sextantFontAvailable reports whether fontconfig knows a font with the
// sextant characters. It is a variable so tests can replace it.
var sextantFontAvailable = func() bool {
path, err := exec.LookPath("fc-list")
if err != nil {
return false
}
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
out, err := exec.CommandContext(ctx, path, ":charset=1fb00", "family").Output()
return err == nil && len(bytes.TrimSpace(out)) > 0
}
// resolveCovers decides how posters are drawn: as real images through the
// Kitty graphics protocol (graphics) or with block characters in the given
// style. "kitty" asks for the protocol explicitly; "auto" uses it when the
+32
View File
@@ -223,7 +223,39 @@ func TestSextantCell(t *testing.T) {
}
}
func TestCoverStyleForFontCheck(t *testing.T) {
defer func(old func() bool) { sextantFontAvailable = old }(sextantFontAvailable)
env := func(m map[string]string) func(string) string { return func(k string) string { return m[k] } }
sextantFontAvailable = func() bool { return true }
for _, tc := range []struct {
name string
env map[string]string
want string
}{
{"unknown terminal with a suitable font", map[string]string{"TERM": "xterm-256color"}, "sextant"},
{"over ssh the local fonts are unknown", map[string]string{"TERM": "xterm-256color", "SSH_CONNECTION": "1 2 3 4"}, "quadrant"},
{"over ssh with a tty", map[string]string{"SSH_TTY": "/dev/pts/1"}, "quadrant"},
} {
if got := coverStyleFor("auto", env(tc.env)).name; got != tc.want {
t.Errorf("%s: %s, want %s", tc.name, got, tc.want)
}
}
// An explicit setting never asks fontconfig.
sextantFontAvailable = func() bool { t.Error("fontconfig consulted for an explicit style"); return true }
if coverStyleFor("quadrant", env(nil)).name != "quadrant" || coverStyleFor("sextant", env(nil)).name != "sextant" {
t.Error("explicit styles must win")
}
// No suitable font: quadrants.
sextantFontAvailable = func() bool { return false }
if got := coverStyleFor("auto", env(map[string]string{"TERM": "xterm-256color"})).name; got != "quadrant" {
t.Errorf("no font: %s", got)
}
}
func TestCoverStyleFor(t *testing.T) {
defer func(old func() bool) { sextantFontAvailable = old }(sextantFontAvailable)
sextantFontAvailable = func() bool { return false } // the table below is about the terminal checks
env := func(m map[string]string) func(string) string { return func(k string) string { return m[k] } }
for _, tc := range []struct {
setting string
+2
View File
@@ -143,6 +143,8 @@ func TestPlaceholderCell(t *testing.T) {
}
func TestKittyTerminalDetection(t *testing.T) {
defer func(old func() bool) { sextantFontAvailable = old }(sextantFontAvailable)
sextantFontAvailable = func() bool { return false } // not what these cases are about
env := func(m map[string]string) func(string) string { return func(k string) string { return m[k] } }
for _, tc := range []struct {
name string