From b4742b730b656ccbc26a0ce6933ea063b356778c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=83=A1=E7=BF=80?= <1020327414@qq.com>
Date: Tue, 27 Oct 2026 14:38:42 +0800
Subject: [PATCH] Support custom editor font weights in steps of 50 (#74)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
JetBrains Mono at 500 looks heavier than PyCharm's rendering, and 450 was rounded up to 500. Adds a Custom option with an input to the Main and Bold font weight rows; weights are clamped to 100–900 and rounded to the nearest 50 (the terminal, which follows the editor weights, gets the same steps).
Verified in the app: Custom → 450 shows the input and the preview renders at 450. Maintainer follow-up: formatted the dialog test.
Related: #32
https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
---
.../settings/__tests__/dialog.test.tsx | 20 ++++++++
src/features/settings/pages/editor/font.tsx | 48 ++++++++++++-------
src/platform/colorScheme.test.ts | 3 +-
src/platform/editorSettings.ts | 4 +-
4 files changed, 54 insertions(+), 21 deletions(-)
diff --git a/src/features/settings/__tests__/dialog.test.tsx b/src/features/settings/__tests__/dialog.test.tsx
index 42591fcd..32ceed56 100644
--- a/src/features/settings/__tests__/dialog.test.tsx
+++ b/src/features/settings/__tests__/dialog.test.tsx
@@ -128,6 +128,26 @@ describe("Settings dialog", () => {
]);
});
+ it("edits and saves independent custom font weights", async () => {
+ const dialog = await openDialog("editor.font");
+ for (const [label, value] of [
+ ["Main font weight", "449"],
+ ["Bold font weight", "650"],
+ ]) {
+ fireEvent.click(within(dialog).getByRole("button", { name: label }));
+ fireEvent.click(screen.getByRole("option", { name: "Custom" }));
+ fireEvent.change(within(dialog).getByLabelText(`${label} custom value`), { target: { value } });
+ }
+ const preview = within(dialog).getByLabelText("Font preview");
+ expect(preview.style.getPropertyValue("--fw-code")).toBe("450");
+ expect(preview.style.getPropertyValue("--fw-code-bold")).toBe("650");
+ await act(async () => fireEvent.click(within(dialog).getByRole("button", { name: "Apply" })));
+ expect(settingsSet.mock.calls).toEqual([
+ ["global", "editor.boldFontWeight", "650"],
+ ["global", "editor.fontWeight", "450"],
+ ]);
+ });
+
it("Cancel discards staged edits", async () => {
const dialog = await openDialog("editor.codeStyle.other");
fireEvent.change(within(dialog).getByLabelText("Tab size"), { target: { value: "2" } });
diff --git a/src/features/settings/pages/editor/font.tsx b/src/features/settings/pages/editor/font.tsx
index 3c422d86..25e0eb20 100644
--- a/src/features/settings/pages/editor/font.tsx
+++ b/src/features/settings/pages/editor/font.tsx
@@ -6,7 +6,7 @@ import { useMemo, useState } from "react";
import { Checkbox, type SelectOption, T } from "@/ui";
import { monoFontFamily } from "@/platform/fonts";
import { NumberInput, Row, SearchLabel, Select } from "../../components/Form";
-import { useLabelMatch, useSetting } from "../../components/context";
+import { useLabelMatch, usePageContext, useSetting } from "../../components/context";
import { FONT_WEIGHTS, fontWeightOf, fontWeightVars } from "@/platform/editorSettings";
import { LIMITS } from "../../model/schema";
import { editorPage } from "./kit";
@@ -135,7 +135,32 @@ function FallbackRow() {
);
}
-const WEIGHT_OPTIONS: SelectOption[] = FONT_WEIGHTS.map(([w, name]) => ({ value: String(w), label: name, detail: String(w) }));
+const WEIGHT_OPTIONS: SelectOption[] = [
+ ...FONT_WEIGHTS.map(([w, name]) => ({ value: String(w), label: name, detail: String(w) })),
+ { value: "custom", label: "Custom" },
+];
+
+function WeightRow({ label, value, onChange, defaultWeight, hint }: { label: string; value: number; onChange: (value: number) => void; defaultWeight: number; hint?: string }) {
+ const [customSelected, setCustomSelected] = useState(false);
+ const weight = fontWeightOf(value, defaultWeight);
+ const custom = customSelected || !FONT_WEIGHTS.some(([w]) => w === weight);
+ return (
+
+
+ );
+}
/**
* IDEA's Main / Bold font weight. IDEA lists the weights the family ships; the
@@ -143,26 +168,13 @@ const WEIGHT_OPTIONS: SelectOption[] = FONT_WEIGHTS.map(([w, name]) => ({ value:
* standard names (the WebView picks the nearest face or synthesises bold).
*/
function WeightRows() {
+ const { scope } = usePageContext();
const [main, setMain] = useSetting("editor.fontWeight");
const [bold, setBold] = useSetting("editor.boldFontWeight");
return (
<>
-
-
-
-
+
+
>
);
}
diff --git a/src/platform/colorScheme.test.ts b/src/platform/colorScheme.test.ts
index d41867cc..1b6f3f5e 100644
--- a/src/platform/colorScheme.test.ts
+++ b/src/platform/colorScheme.test.ts
@@ -60,9 +60,10 @@ describe("color schemes", () => {
const d = validateEditorExtras({});
expect([d.fontWeight, d.boldFontWeight]).toEqual([400, 700]);
const v = validateEditorExtras({ fontWeight: 349, boldFontWeight: 1200 });
- expect([v.fontWeight, v.boldFontWeight]).toEqual([300, 900]);
+ expect([v.fontWeight, v.boldFontWeight]).toEqual([350, 900]);
expect(validateEditorExtras({ fontWeight: "bold" }).fontWeight).toBe(400);
expect(fontWeightVars(500, undefined)).toEqual({ "--fw-code": "500", "--fw-code-bold": "700" });
+ expect(fontWeightVars(450, 650)).toEqual({ "--fw-code": "450", "--fw-code-bold": "650" });
});
it("coerces editor settings to their defaults' shape", () => {
diff --git a/src/platform/editorSettings.ts b/src/platform/editorSettings.ts
index a51d2fad..07e8ac12 100644
--- a/src/platform/editorSettings.ts
+++ b/src/platform/editorSettings.ts
@@ -205,10 +205,10 @@ export const FONT_WEIGHTS: ReadonlyArray = [
[900, "Black"],
];
-/** Nearest CSS weight step (100–900); `def` for anything that is not a number. */
+/** CSS weight from 100 to 900, rounded to the nearest 50. */
export function fontWeightOf(raw: unknown, def: number): number {
if (typeof raw !== "number" || !Number.isFinite(raw)) return def;
- return Math.min(900, Math.max(100, Math.round(raw / 100) * 100));
+ return Math.min(900, Math.max(100, Math.round(raw / 50) * 50));
}
/** `--fw-code` / `--fw-code-bold`: what editor-font text and its bold runs render with. */