The Cardputer draws its path lines as chips
The Cardputer's panel is drawn in MeshTerm's own Terminus, which has the powerline chevrons, yet its paths came out as arrows: the chips-or-arrows verdict asked the terminal that started the app about its font, and on the device that terminal is no one's, so the answer was unknown. In the emulator it was worse, since the verdict was whatever the host terminal could draw, a question about the wrong glass. A handheld names its glyph inventory (Platform.font), and every frame is folded down to that font anyway, so the inventory now answers: after the MESHTERM_POWERLINE override, before any terminal probe. Terminus has U+E0B0 and U+E0B1, so the Cardputer draws chips. The PicoCalc's 512-glyph console font has neither, so its paths stay arrows, now said outright rather than reached by not knowing. Diagnostics reads "core (platform:cardputer-zero)". The verdict is bound on a platform switch and its memo cleared there. Checked by rasterizing gallery screens through the emulator's VT and Terminus at 53x14; not yet on the device. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b9585e9b59
commit
c07f15f4ec
4 files changed
+122
-11
No files matched your search
@@ -764,7 +764,9 @@ branch on the platform per frame, and never `from meshterm.platforms import PLAT
|
||||
(`platforms.drawn_by_meshterm`), so nothing offers to move to another terminal. Its glyph contract is
|
||||
`fontset.CARDPUTER_ZERO_CODEPOINTS`, named by `Platform.font` as the PicoCalc's is; the fold
|
||||
derives everything from the platform's font (accents, emoji, the `?` net) and quantizes
|
||||
colour only where `truecolor` is off.
|
||||
colour only where `truecolor` is off. The path line's chips-or-arrows verdict reads the
|
||||
same font (`termfont`, after `MESHTERM_POWERLINE` and before any terminal probe): Terminus
|
||||
has the chevrons, so the Cardputer draws chips; the PicoCalc's 512 glyphs don't, so arrows.
|
||||
- **The Cardputer's exit must fit its launcher's grace.** A held Esc there is the
|
||||
launcher's own way out (SIGTERM at 3 s, SIGKILL 3 s later, letting go or not), which is
|
||||
where hold-to-quit's timing comes from (see **Navigation**). MeshTerm's whole exit was
|
||||
|
||||
+61
-9
@@ -33,11 +33,18 @@ nothing — which makes this an *out-of-band* detection problem: identify the te
|
||||
read its configuration, and match the configured face against fonts known to carry the
|
||||
powerline block.
|
||||
|
||||
Three sources of truth, in confidence order:
|
||||
Four sources of truth, in confidence order:
|
||||
|
||||
* **An explicit override** — ``MESHTERM_POWERLINE`` (``1``/``full``, ``core``,
|
||||
``0``/``off``) always wins, the same gate pattern as ``MESHTERM_FULL_WIDTH``. The
|
||||
user knows their glass better than any probe.
|
||||
* **A handheld's own font.** Where the platform names a glyph contract
|
||||
(:attr:`~meshterm.platforms.Platform.font`), every frame is folded down to that font
|
||||
at the render boundary, whatever terminal started the app — so the inventory is the
|
||||
answer, and asking the terminal would be asking the wrong glass. The Cardputer's panel
|
||||
is Terminus, which carries the core chevrons, so its paths are chips; the PicoCalc's
|
||||
512-glyph console font has no room for them, so its paths stay arrows. Nothing is
|
||||
probed: :data:`~meshterm.ui.fontset.FONTS` already says what each font can draw.
|
||||
* **The configured font**, resolved per terminal: Windows Terminal (``WT_SESSION`` +
|
||||
``WT_PROFILE_ID`` → the profile's ``font.face`` in ``settings.json``), VS Code's
|
||||
integrated terminal (``TERM_PROGRAM=vscode`` → ``terminal.integrated.fontFamily``,
|
||||
@@ -80,6 +87,8 @@ from functools import lru_cache
|
||||
from pathlib import Path
|
||||
|
||||
from ..core import win32dll
|
||||
from ..platforms import Platform, on_platform
|
||||
from .fontset import FONTS
|
||||
|
||||
#: Coverage levels a verdict (or a recommended font) can carry, strongest first.
|
||||
FULL = "full"
|
||||
@@ -196,7 +205,8 @@ class PowerlineSupport:
|
||||
|
||||
Attributes:
|
||||
level: :data:`FULL`, :data:`CORE`, :data:`NONE`, or :data:`UNKNOWN`.
|
||||
source: What decided it — ``"env"`` (the override), ``"font:<terminal>"``
|
||||
source: What decided it — ``"env"`` (the override), ``"platform:<name>"`` (a
|
||||
handheld's own font inventory), ``"font:<terminal>"``
|
||||
(a configured face matched, or definitively didn't), ``"renderer:<terminal>"``
|
||||
(the terminal draws the glyphs itself), ``"ssh"`` (the font lives on the far
|
||||
client, unknowable), or ``"unknown"``.
|
||||
@@ -576,13 +586,37 @@ def _renderer_backed(environ: Mapping[str, str]) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
#: The glyphs the path widget draws at ``core``: the solid triangle every seam is, and the
|
||||
#: thin one that joins two fills the eye can't tell apart (:mod:`~meshterm.ui.pathline`).
|
||||
_CORE_GLYPHS = (0xE0B0, 0xE0B1)
|
||||
|
||||
#: What ``full`` adds that the widget uses: the rounded caps a path's square ends become.
|
||||
_FULL_GLYPHS = (0xE0B4, 0xE0B6)
|
||||
|
||||
|
||||
def _inventory_coverage(inventory: frozenset[int]) -> str:
|
||||
"""The coverage level a known glyph inventory earns — no guess, it is all listed."""
|
||||
if not all(code in inventory for code in _CORE_GLYPHS):
|
||||
return NONE
|
||||
return FULL if all(code in inventory for code in _FULL_GLYPHS) else CORE
|
||||
|
||||
|
||||
def _powerline_support(
|
||||
environ: Mapping[str, str] | None = None,
|
||||
*,
|
||||
cwd: Path | None = None,
|
||||
conhost_probe: Callable[[], str | None] = _conhost_face,
|
||||
handheld: tuple[str, frozenset[int]] | None = None,
|
||||
) -> PowerlineSupport:
|
||||
"""The uncached verdict (see :func:`powerline_support` for the ladder)."""
|
||||
"""The uncached verdict (see :func:`powerline_support` for the ladder).
|
||||
|
||||
Args:
|
||||
environ: The environment to inspect (defaults to ``os.environ``).
|
||||
cwd: Where the VS Code workspace walk starts (defaults to the process cwd).
|
||||
conhost_probe: The classic-console face reader (injectable for tests).
|
||||
handheld: The platform's name and glyph inventory where a handheld's own font
|
||||
draws the screen, or ``None`` on a terminal.
|
||||
"""
|
||||
env = os.environ if environ is None else environ
|
||||
override = (env.get("MESHTERM_POWERLINE") or "").strip().lower()
|
||||
if override in {"0", "off", "no", "none", "false"}:
|
||||
@@ -591,6 +625,9 @@ def _powerline_support(
|
||||
return PowerlineSupport(FULL, "env")
|
||||
if override == "core":
|
||||
return PowerlineSupport(CORE, "env")
|
||||
if handheld is not None:
|
||||
name, inventory = handheld
|
||||
return PowerlineSupport(_inventory_coverage(inventory), f"platform:{name}")
|
||||
|
||||
detected = detect_terminal_font(env, cwd=cwd, conhost_probe=conhost_probe)
|
||||
matched = match_recommended(detected.face) if detected else None
|
||||
@@ -610,16 +647,17 @@ def _powerline_support(
|
||||
def powerline_support() -> PowerlineSupport:
|
||||
"""The session's powerline verdict, decided once and cached.
|
||||
|
||||
The ladder: the ``MESHTERM_POWERLINE`` override, then the configured font matched
|
||||
against :data:`RECOMMENDED_FONTS`, then a renderer that draws the glyphs itself,
|
||||
then an honest ``none`` (face read, no match, no fallback) or ``unknown`` (ssh, or
|
||||
a terminal we can't identify). Cached because the environment and settings files
|
||||
don't change mid-session; tests exercise :func:`_powerline_support` directly.
|
||||
The ladder: the ``MESHTERM_POWERLINE`` override, then a handheld platform's own font
|
||||
inventory, then the configured font matched against :data:`RECOMMENDED_FONTS`, then
|
||||
a renderer that draws the glyphs itself, then an honest ``none`` (face read, no
|
||||
match, no fallback) or ``unknown`` (ssh, or a terminal we can't identify). Cached
|
||||
because the environment and settings files don't change mid-session, and cleared on
|
||||
a platform switch (:func:`_bind`); tests exercise :func:`_powerline_support` directly.
|
||||
|
||||
Returns:
|
||||
The :class:`PowerlineSupport` verdict.
|
||||
"""
|
||||
return _powerline_support(os.environ)
|
||||
return _powerline_support(os.environ, handheld=_HANDHELD_FONT)
|
||||
|
||||
|
||||
def powerline_enabled() -> bool:
|
||||
@@ -640,6 +678,20 @@ def powerline_full() -> bool:
|
||||
return powerline_support().level == FULL
|
||||
|
||||
|
||||
#: The active handheld's name and glyph inventory, or ``None`` where a terminal draws
|
||||
#: whatever it is sent. Bound at platform-switch time (:func:`_bind`).
|
||||
_HANDHELD_FONT: tuple[str, frozenset[int]] | None = None
|
||||
|
||||
|
||||
@on_platform
|
||||
def _bind(platform: Platform) -> None:
|
||||
"""Bind the handheld font the verdict reads, and drop the verdict the last one made."""
|
||||
global _HANDHELD_FONT
|
||||
inventory = FONTS.get(platform.font)
|
||||
_HANDHELD_FONT = (platform.name, inventory) if inventory is not None else None
|
||||
powerline_support.cache_clear()
|
||||
|
||||
|
||||
# --- installed-font scan ----------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
+19
-1
@@ -14,6 +14,7 @@ import random
|
||||
import pytest
|
||||
|
||||
import meshterm.ui.pathline as pathline
|
||||
from meshterm.platforms import CARDPUTER_ZERO, set_platform
|
||||
from meshterm.ui import oklab
|
||||
from meshterm.ui.pathline import (
|
||||
_DIM_BG,
|
||||
@@ -44,7 +45,7 @@ from meshterm.ui.pathline import (
|
||||
path_line,
|
||||
with_action_mark,
|
||||
)
|
||||
from meshterm.ui.theme import node_style
|
||||
from meshterm.ui.theme import fold_text, node_style
|
||||
from meshterm.ui.widgets import path_text
|
||||
|
||||
|
||||
@@ -158,6 +159,23 @@ def test_auto_mode_follows_the_terminal_verdict(monkeypatch: pytest.MonkeyPatch)
|
||||
assert PathLine(hops).text().plain == "a → b"
|
||||
|
||||
|
||||
def test_the_cardputer_draws_chips_in_glyphs_its_font_has(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""The Cardputer's own font has the chevrons, so its paths are chips that fold clean.
|
||||
|
||||
Its verdict is read off its font (:mod:`~meshterm.ui.termfont`), and the render
|
||||
boundary folds whatever that font lacks to ``?`` — so every mark the chip language
|
||||
spends (both seams, both cracks, the elision, our star) must come through unchanged.
|
||||
"""
|
||||
monkeypatch.delenv("MESHTERM_POWERLINE")
|
||||
set_platform(CARDPUTER_ZERO)
|
||||
hops = [PathHop(SELF_GLYPH, you=True), PathHop("Hub", key="3d"), elision_hop()]
|
||||
line = PathLine([*hops, PathHop("a"), PathHop("b")]).text() # auto: the font decides
|
||||
drawn = [line, cut_to(line, 12), cut_mark(line, 1, ELIDE_HEAD)]
|
||||
ink = "".join(text.plain for text in drawn)
|
||||
assert {POWERLINE_SEP, POWERLINE_THIN, CRACK_TAIL, CRACK_HEAD, "⋯", SELF_GLYPH} <= set(ink)
|
||||
assert fold_text(ink) == ink
|
||||
|
||||
|
||||
def test_the_cursor_is_a_hop_of_its_own_in_either_mode() -> None:
|
||||
"""The composer's insertion point is a slot *in* the route, not a gap between hops.
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ from __future__ import annotations
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from meshterm.platforms import CARDPUTER_ZERO, PICOCALC_LYRA, REGULAR, set_platform
|
||||
from meshterm.ui.fontset import CARDPUTER_ZERO_CODEPOINTS, FONT_CODEPOINTS
|
||||
from meshterm.ui.termfont import (
|
||||
CORE,
|
||||
FULL,
|
||||
@@ -30,6 +34,7 @@ from meshterm.ui.termfont import (
|
||||
installed_recommended,
|
||||
match_recommended,
|
||||
normalize_face,
|
||||
powerline_support,
|
||||
primary_family,
|
||||
)
|
||||
|
||||
@@ -248,6 +253,40 @@ def test_powerline_support_kitty_ssh_and_unknown() -> None:
|
||||
assert (lost.level, lost.source) == (UNKNOWN, "unknown")
|
||||
|
||||
|
||||
def test_powerline_support_a_handheld_answers_from_its_own_font() -> None:
|
||||
"""A handheld's font draws the screen, so its inventory answers and no terminal does.
|
||||
|
||||
The Cardputer's Terminus carries the core chevrons and no rounded caps; the PicoCalc's
|
||||
512-glyph console font has neither, so a kitty that started it changes nothing. The
|
||||
override still outranks the font, as it outranks every probe.
|
||||
"""
|
||||
kitty = {"TERM": "xterm-kitty"} # a terminal that would otherwise earn core
|
||||
cardputer = _powerline_support(kitty, handheld=("cardputer-zero", CARDPUTER_ZERO_CODEPOINTS))
|
||||
assert (cardputer.level, cardputer.source) == (CORE, "platform:cardputer-zero")
|
||||
picocalc = _powerline_support(kitty, handheld=("picocalc-lyra", FONT_CODEPOINTS))
|
||||
assert (picocalc.level, picocalc.source) == (NONE, "platform:picocalc-lyra")
|
||||
capped = CARDPUTER_ZERO_CODEPOINTS | {0xE0B4, 0xE0B6}
|
||||
assert _powerline_support({}, handheld=("capped", capped)).level == FULL
|
||||
pinned = {"MESHTERM_POWERLINE": "0"}
|
||||
assert _powerline_support(pinned, handheld=("cardputer-zero", CARDPUTER_ZERO_CODEPOINTS)) == (
|
||||
_powerline_support(pinned)
|
||||
)
|
||||
|
||||
|
||||
def test_powerline_support_follows_a_platform_switch(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""The memoized verdict is dropped on a switch, so it never answers for the last glass."""
|
||||
monkeypatch.delenv("MESHTERM_POWERLINE", raising=False)
|
||||
monkeypatch.setenv("TERM", "xterm-kitty")
|
||||
monkeypatch.delenv("WT_SESSION", raising=False)
|
||||
monkeypatch.delenv("TERM_PROGRAM", raising=False)
|
||||
set_platform(CARDPUTER_ZERO)
|
||||
assert powerline_support().source == "platform:cardputer-zero"
|
||||
set_platform(PICOCALC_LYRA)
|
||||
assert powerline_support().source == "platform:picocalc-lyra"
|
||||
set_platform(REGULAR)
|
||||
assert powerline_support().source == "renderer:kitty"
|
||||
|
||||
|
||||
def test_installed_recommended_never_raises() -> None:
|
||||
"""Scanning the machine for installed fonts never raises.
|
||||
|
||||
|
||||
Reference in new issue
Block a user