mirror of
https://github.com/nyakang/nyaterm.git
synced 2026-10-05 08:01:01 +00:00
`Svg::paint` does `self.path.zip(style.text.color)`, and `compute_style` starts from `Style::default()`, whose `text.color` is `None`. A parent's `.text_color(..)` cascades to real text but never to an `svg()` child, so a glyph without a colour of its own was skipped entirely at paint time — no error, no fallback, just an invisible icon still taking up layout. 59 call sites were affected: the window minimise/maximise/close controls, tab close buttons, the title bar, the quick-command toolbar, the transfer browser and queue, tunnels, settings and the AI panels. Shared button helpers are fixed first, so most sites are covered by ~10 functions. Where the parent brightened the icon on hover, the pair becomes `.group(..)` plus `.group_hover(..)`, since a `.hover()` refinement cannot reach a child either. `scripts/check-icon-references.sh` now fails on an `svg()` whose chain has a `.path(..)` but no `.text_color(..)`. It checks depth 0 only: a colour that lives solely inside `.group_hover(.., |s| s.text_color(..))` still leaves the icon invisible until the pointer is over it.
114 lines
4.2 KiB
Bash
114 lines
4.2 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Verify that every icon path the UI references is actually bundled, and that it
|
|
# is painted with the matching element.
|
|
#
|
|
# GPUI fails silently here: a missing asset makes `svg()`/`img()` draw nothing,
|
|
# with no error and no log line. Painting a full-color logo through `svg()` is
|
|
# just as quiet — it renders as a flat silhouette.
|
|
#
|
|
set -euo pipefail
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
ASSET_DIR="crates/nyaterm-app/assets"
|
|
SRC_DIRS=(crates/nyaterm-desktop/src crates/nyaterm-ui/src)
|
|
|
|
failures=0
|
|
fail() {
|
|
printf 'icon-reference: %s\n' "$*" >&2
|
|
failures=$((failures + 1))
|
|
}
|
|
|
|
# 1. Every referenced asset exists.
|
|
referenced="$(grep -rEoh '"(icons|color)/[A-Za-z0-9._/-]+\.(svg|png)"' "${SRC_DIRS[@]}" |
|
|
tr -d '"' | sort -u)"
|
|
|
|
if [[ -z "$referenced" ]]; then
|
|
fail "no icon references found; the search pattern is probably stale"
|
|
fi
|
|
|
|
while IFS= read -r asset; do
|
|
[[ -z "$asset" ]] && continue
|
|
[[ -f "$ASSET_DIR/$asset" ]] || fail "$asset is referenced but not bundled"
|
|
done <<< "$referenced"
|
|
|
|
# 2. The prefix and the element agree. `icons/**` is an alpha mask, `color/**` is
|
|
# a raster; swapping them is silent but wrong.
|
|
while IFS= read -r hit; do
|
|
[[ -z "$hit" ]] && continue
|
|
fail "svg() is given a full-color asset (use color_icon): $hit"
|
|
done <<< "$(grep -rn '\.path("color/' "${SRC_DIRS[@]}" || true)"
|
|
|
|
while IFS= read -r hit; do
|
|
[[ -z "$hit" ]] && continue
|
|
fail "img() is given a monochrome asset (use mono_icon): $hit"
|
|
done <<< "$(grep -rn 'img("icons/' "${SRC_DIRS[@]}" || true)"
|
|
|
|
# 3. Every svg() declares its own color. GPUI resolves an element's style from
|
|
# Style::default(), so `text.color` is None unless the element sets it: a
|
|
# parent's `.text_color(..)` reaches text but never an svg() child, and
|
|
# `Svg::paint` skips a glyph with no color entirely. The result is an
|
|
# invisible icon that still occupies layout — no error, no log line.
|
|
#
|
|
# Needs to follow the builder chain across newlines, which grep cannot do.
|
|
while IFS= read -r hit; do
|
|
[[ -z "$hit" ]] && continue
|
|
fail "svg() has no text_color, so it is never painted: $hit"
|
|
done <<< "$(python3 - "${SRC_DIRS[@]}" <<'PY' || true
|
|
import pathlib, re, sys
|
|
|
|
# The color has to be on the svg's own chain, not nested inside a closure: an
|
|
# icon whose only color lives in `.group_hover(.., |s| s.text_color(..))` is
|
|
# invisible until the pointer is over it. So both markers are counted at depth 0.
|
|
def chain_flags(source, start):
|
|
index, depth = start, 0
|
|
has_path = has_color = False
|
|
while index < len(source):
|
|
char = source[index]
|
|
if char == "(":
|
|
if depth == 0:
|
|
if source.startswith(".path(", index - 5):
|
|
has_path = True
|
|
elif source.startswith(".text_color(", index - 11):
|
|
has_color = True
|
|
depth += 1
|
|
elif char == ")":
|
|
if depth == 0:
|
|
break
|
|
depth -= 1
|
|
elif depth == 0 and char in ",;":
|
|
break
|
|
index += 1
|
|
return has_path, has_color
|
|
|
|
|
|
for root in (pathlib.Path(arg) for arg in sys.argv[1:]):
|
|
for path in sorted(root.rglob("*.rs")):
|
|
source = path.read_text(encoding="utf-8", errors="ignore")
|
|
for match in re.finditer(r"\bsvg\(\)", source):
|
|
has_path, has_color = chain_flags(source, match.end())
|
|
if has_path and not has_color:
|
|
line = source.count("\n", 0, match.start()) + 1
|
|
print(f"{path.as_posix()}:{line}")
|
|
PY
|
|
)"
|
|
|
|
# 4. Nothing bundled is orphaned. A warning, not a failure: an asset reached only
|
|
# through a lookup table is still referenced by literal, but a future
|
|
# indirection could legitimately hide one.
|
|
while IFS= read -r asset; do
|
|
[[ -z "$asset" ]] && continue
|
|
if ! grep -qxF "$asset" <<< "$referenced"; then
|
|
printf 'icon-reference: warning: %s is bundled but never referenced\n' "$asset" >&2
|
|
fi
|
|
done <<< "$(cd "$ASSET_DIR" && find icons color -type f \( -name '*.svg' -o -name '*.png' \) 2>/dev/null | sort)"
|
|
|
|
if (( failures )); then
|
|
printf 'icon-reference: %d problem(s)\n' "$failures" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'icon-reference: all referenced icons are bundled and correctly painted\n'
|