[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
178
infrastructure/config/fonts.ts
Normal file
178
infrastructure/config/fonts.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* Terminal Fonts Configuration
|
||||
*
|
||||
* `family` is the raw CSS font-family string for the Latin glyphs only.
|
||||
* CJK and icon fallbacks are composed at runtime by composeFontFamilyStack()
|
||||
* in cjkFonts.ts, which lets users pick the CJK font independently or have
|
||||
* one chosen automatically per Latin font.
|
||||
*/
|
||||
|
||||
export interface TerminalFont {
|
||||
id: string;
|
||||
name: string;
|
||||
family: string;
|
||||
description: string;
|
||||
category: 'monospace' | 'proportional';
|
||||
}
|
||||
|
||||
const BASE_TERMINAL_FONTS: TerminalFont[] = [
|
||||
// Existing Latin monospace fonts (ids unchanged for sync compatibility)
|
||||
{ id: 'menlo', name: 'Menlo', family: 'Menlo, monospace', description: 'macOS system font, clean and professional', category: 'monospace' },
|
||||
{ id: 'monaco', name: 'Monaco', family: 'Monaco, monospace', description: 'Classic monospace, excellent readability', category: 'monospace' },
|
||||
{ id: 'consolas', name: 'Consolas', family: 'Consolas, monospace', description: 'Windows-style monospace, clear and compact', category: 'monospace' },
|
||||
{ id: 'courier-new', name: 'Courier New', family: '"Courier New", monospace', description: 'Classic typewriter style, universal support', category: 'monospace' },
|
||||
{ id: 'source-code-pro', name: 'Source Code Pro', family: '"Source Code Pro", monospace', description: "Adobe's professional programming font", category: 'monospace' },
|
||||
{ id: 'fira-code', name: 'Fira Code', family: '"Fira Code", monospace', description: 'Monospace font with programming ligatures', category: 'monospace' },
|
||||
{ id: 'fira-mono', name: 'Fira Mono', family: '"Fira Mono", monospace', description: 'Clean monospace without ligatures', category: 'monospace' },
|
||||
{ id: 'inconsolata', name: 'Inconsolata', family: 'Inconsolata, monospace', description: 'Elegant and readable monospace font', category: 'monospace' },
|
||||
{ id: 'dejavu-sans-mono', name: 'DejaVu Sans Mono', family: '"DejaVu Sans Mono", monospace', description: 'Wide character support, very readable', category: 'monospace' },
|
||||
{ id: 'liberation-mono', name: 'Liberation Mono', family: '"Liberation Mono", monospace', description: 'Open source monospace font, Courier alternative', category: 'monospace' },
|
||||
{ id: 'jetbrains-mono', name: 'JetBrains Mono', family: '"JetBrains Mono", monospace', description: 'Professional font designed for IDEs', category: 'monospace' },
|
||||
{ id: 'victor-mono', name: 'Victor Mono', family: '"Victor Mono", monospace', description: 'Stylish monospace with italic support', category: 'monospace' },
|
||||
{ id: 'cascadia-code', name: 'Cascadia Code', family: '"Cascadia Code", monospace', description: "Microsoft's modern monospace font", category: 'monospace' },
|
||||
{ id: 'cascadia-mono', name: 'Cascadia Mono', family: '"Cascadia Mono", monospace', description: 'Cascadia without ligatures', category: 'monospace' },
|
||||
{ id: 'droid-sans-mono', name: 'Droid Sans Mono', family: '"Droid Sans Mono", monospace', description: "Google's Droid monospace font", category: 'monospace' },
|
||||
{ id: 'ubuntu-mono', name: 'Ubuntu Mono', family: '"Ubuntu Mono", monospace', description: "Ubuntu's official monospace font", category: 'monospace' },
|
||||
{ id: 'roboto-mono', name: 'Roboto Mono', family: '"Roboto Mono", monospace', description: "Google's Roboto monospace variant", category: 'monospace' },
|
||||
{ id: 'ibm-plex-mono', name: 'IBM Plex Mono', family: '"IBM Plex Mono", monospace', description: "IBM's professional monospace font", category: 'monospace' },
|
||||
{ id: 'space-mono', name: 'Space Mono', family: '"Space Mono", monospace', description: 'Geometric monospace with strong personality', category: 'monospace' },
|
||||
{ id: 'input-mono', name: 'Input Mono', family: '"Input Mono", monospace', description: 'Designed specifically for coding', category: 'monospace' },
|
||||
{ id: 'hack', name: 'Hack', family: 'Hack, monospace', description: 'Designed for source code, excellent in terminals', category: 'monospace' },
|
||||
{ id: 'anonymous-pro', name: 'Anonymous Pro', family: '"Anonymous Pro", monospace', description: 'Designed for coding and terminal use', category: 'monospace' },
|
||||
{ id: 'programmer-fonts', name: 'Programmer Fonts', family: '"Programmer Fonts", monospace', description: 'Optimized for programming with clear glyphs', category: 'monospace' },
|
||||
{ id: 'pt-mono', name: 'PT Mono', family: '"PT Mono", monospace', description: "ParaType's monospace font", category: 'monospace' },
|
||||
{ id: 'iosevka', name: 'Iosevka', family: 'Iosevka, monospace', description: 'Highly customizable monospace font', category: 'monospace' },
|
||||
{ id: 'ioskeley-mono', name: 'Ioskeley Mono', family: '"Ioskeley Mono", monospace', description: 'Iosevka variant mimicking Berkeley Mono style', category: 'monospace' },
|
||||
{ id: 'mononoki', name: 'Mononoki', family: 'Mononoki, monospace', description: 'Crisp and clear monospace with ligatures', category: 'monospace' },
|
||||
{ id: 'go-mono', name: 'Go Mono', family: '"Go Mono", monospace', description: "Google Go's monospace font", category: 'monospace' },
|
||||
{ id: 'overpass-mono', name: 'Overpass Mono', family: '"Overpass Mono", monospace', description: 'Open source monospace with good coverage', category: 'monospace' },
|
||||
|
||||
// True monospace CJK-coverage fonts only. PingFang SC and Microsoft
|
||||
// YaHei UI (the OS system fonts) are deliberately omitted — they are
|
||||
// proportional sans-serif designs whose Latin glyphs render with
|
||||
// variable widths and whose CJK glyphs don't fit a terminal's 2x cell
|
||||
// grid. Picking one as the primary font produced visibly bloated
|
||||
// spacing for ASCII characters in #931.
|
||||
{ id: 'sarasa-mono-sc', name: 'Sarasa Mono SC', family: '"Sarasa Mono SC", monospace', description: 'Iosevka + Source Han Sans (Simplified Chinese), 2:1 monospace', category: 'monospace' },
|
||||
{ id: 'sarasa-mono-tc', name: 'Sarasa Mono TC', family: '"Sarasa Mono TC", monospace', description: 'Iosevka + Source Han Sans (Traditional Chinese), 2:1 monospace', category: 'monospace' },
|
||||
{ id: 'maple-mono-cn', name: 'Maple Mono CN', family: '"Maple Mono CN", monospace', description: 'Maple Mono with unified Latin + Simplified Chinese metrics', category: 'monospace' },
|
||||
{ id: 'lxgw-wenkai-mono', name: 'LXGW WenKai Mono', family: '"LXGW WenKai Mono", monospace', description: 'Monospace Kaishu (regular-script) derived from Fontworks Klee One', category: 'monospace' },
|
||||
];
|
||||
|
||||
export const TERMINAL_FONTS: TerminalFont[] = BASE_TERMINAL_FONTS;
|
||||
|
||||
export const DEFAULT_FONT_SIZE = 14;
|
||||
export const MIN_FONT_SIZE = 10;
|
||||
export const MAX_FONT_SIZE = 32;
|
||||
|
||||
export type FontPlatform = 'darwin' | 'win32' | 'linux' | (string & {});
|
||||
|
||||
/**
|
||||
* The default terminal font id for a given OS. Chosen so the primary
|
||||
* Latin glyphs render with a font that ships with the OS — avoiding the
|
||||
* cold-start cell-misalignment in #1647, where a bundled webfont
|
||||
* (JetBrains Mono, font-display: swap) swapped in *after* xterm measured
|
||||
* the cell grid and left ASCII garbled until a manual window resize.
|
||||
*
|
||||
* - darwin: Menlo is a macOS system font (instant, no swap).
|
||||
* - win32: Consolas ships with every Windows since Vista.
|
||||
* - linux: DejaVu Sans Mono is the most widely pre-installed Linux
|
||||
* monospace; if a given distro lacks it, the runtime font-ready
|
||||
* remeasure (useTerminalEffects) still recovers any swap.
|
||||
*
|
||||
* Unknown platforms fall back to the macOS default.
|
||||
*/
|
||||
export function getDefaultTerminalFontIdForPlatform(platform: FontPlatform): string {
|
||||
if (platform === 'win32') return 'consolas';
|
||||
if (platform === 'linux') return 'dejavu-sans-mono';
|
||||
return 'menlo';
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a `navigator.platform` string to the coarse OS buckets used
|
||||
* by getDefaultTerminalFontIdForPlatform. Mirrors the detection already
|
||||
* used for the CJK fallback stack in Terminal.tsx (Mac/Win regex on
|
||||
* navigator.platform); anything else — including Linux — is `linux`.
|
||||
*/
|
||||
export function detectFontPlatform(navigatorPlatform: string): FontPlatform {
|
||||
if (/Win/i.test(navigatorPlatform)) return 'win32';
|
||||
if (/Mac|iPhone|iPad|iPod/i.test(navigatorPlatform)) return 'darwin';
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
/**
|
||||
* The stored sentinel meaning "follow the OS default font". It is
|
||||
* persisted and cloud-synced as-is (platform-neutral), so each device
|
||||
* resolves it to its own locally-installed default at render time —
|
||||
* unlike a concrete platform id, which would leak one OS's font to
|
||||
* another via sync and reintroduce #1647 on the receiving machine.
|
||||
*/
|
||||
export const TERMINAL_FONT_AUTO = 'auto';
|
||||
|
||||
/**
|
||||
* Resolve a stored terminal font id to a concrete font id. The `auto`
|
||||
* sentinel (and any empty/nullish value) resolves to the per-platform
|
||||
* default; an explicit user choice is returned unchanged.
|
||||
*/
|
||||
export function resolveTerminalFontFamilyId(
|
||||
id: string | null | undefined,
|
||||
navigatorPlatform: string,
|
||||
): string {
|
||||
if (id && id !== TERMINAL_FONT_AUTO) return id;
|
||||
return getDefaultTerminalFontIdForPlatform(detectFontPlatform(navigatorPlatform));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether two terminal font ids resolve to the same concrete font on this
|
||||
* platform. Font pickers display the resolved concrete font for an `auto`
|
||||
* value, so a click on the shown default arrives as that concrete id while
|
||||
* the stored value is still `auto`; comparing raw would treat it as a
|
||||
* change and pin a per-OS font (which then syncs across devices). Comparing
|
||||
* resolved ids makes that click a correct no-op.
|
||||
*/
|
||||
export function isSameResolvedTerminalFont(
|
||||
a: string | null | undefined,
|
||||
b: string | null | undefined,
|
||||
navigatorPlatform: string,
|
||||
): boolean {
|
||||
return (
|
||||
resolveTerminalFontFamilyId(a, navigatorPlatform) ===
|
||||
resolveTerminalFontFamilyId(b, navigatorPlatform)
|
||||
);
|
||||
}
|
||||
|
||||
// Font ids that earlier versions of netcatty exposed in the primary font
|
||||
// dropdown but that are proportional (non-monospace) and produce broken
|
||||
// cell-grid alignment when used as a terminal font. Reads should migrate
|
||||
// these to a sane default.
|
||||
const DEPRECATED_PRIMARY_FONT_IDS = new Set<string>([
|
||||
'pingfang-sc',
|
||||
'microsoft-yahei',
|
||||
'comic-sans-ms',
|
||||
]);
|
||||
|
||||
export function isDeprecatedPrimaryFontId(fontId: string | null | undefined): boolean {
|
||||
return !!fontId && DEPRECATED_PRIMARY_FONT_IDS.has(fontId);
|
||||
}
|
||||
|
||||
/**
|
||||
* In-place migration for any object carrying `fontFamily` /
|
||||
* `fontFamilyOverride` (Host, GroupConfig). When the saved id is one
|
||||
* we've since removed from TERMINAL_FONTS, drop the override so the
|
||||
* record inherits the global default rather than silently rendering
|
||||
* "fallback to fonts[0]" while still claiming an override is active.
|
||||
*
|
||||
* Returns the (possibly new) value to assign back. Caller decides
|
||||
* whether to mutate or copy; both are safe with this shape.
|
||||
*/
|
||||
export function migrateDeprecatedFontOverride<
|
||||
T extends { fontFamily?: string; fontFamilyOverride?: boolean },
|
||||
>(record: T): T {
|
||||
if (!isDeprecatedPrimaryFontId(record.fontFamily)) return record;
|
||||
const next = { ...record };
|
||||
delete next.fontFamily;
|
||||
if (next.fontFamilyOverride === true) {
|
||||
next.fontFamilyOverride = false;
|
||||
}
|
||||
return next;
|
||||
}
|
||||
Reference in New Issue
Block a user