дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 03:05:40 +02:00
fix(editor): match IDEA font weights in the light scheme (#35)
Root cause: the editor had no font weight settings, and the WebView rendering of JetBrains Mono looks lighter than IDEA (#32). Fix (IDEA's Settings › Editor › Font › Typography Settings): configurable Main font weight and Bold font weight (defaults Regular 400 / Bold 700) via --fw-code / --fw-code-bold; colour-scheme bold/regular overrides still win; settings preview updates live. Replaces the Light-only 450/550 hard-coding. Siblings covered: sticky lines, diff/merge, run console (ANSI bold), Find in Files, completion/parameter info/docs, database editor, debugger value popup, matched braces, terminal with "Use editor font". Verified: scoped vitest, ide-core tests, in-app on macOS (settings UI, editor weight follows the setting end to end). Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
29 files changed
+274
-32
No files matched your search
@@ -292,6 +292,10 @@ pub struct EditorSettings {
|
||||
pub ligatures: bool,
|
||||
/// Fallback font for glyphs the main font lacks; empty = none.
|
||||
pub fallback_font: String,
|
||||
/// Typography Settings › Main font weight (CSS 100–900), as IDEA.
|
||||
pub font_weight: u16,
|
||||
/// Typography Settings › Bold font weight: wherever the editor renders bold.
|
||||
pub bold_font_weight: u16,
|
||||
pub general: EditorGeneralSettings,
|
||||
pub appearance: EditorAppearanceSettings,
|
||||
pub breadcrumbs: BreadcrumbsSettings,
|
||||
@@ -327,6 +331,8 @@ impl Default for EditorSettings {
|
||||
color_overrides: BTreeMap::new(),
|
||||
ligatures: false,
|
||||
fallback_font: String::new(),
|
||||
font_weight: 400,
|
||||
bold_font_weight: 700,
|
||||
general: EditorGeneralSettings::default(),
|
||||
appearance: EditorAppearanceSettings::default(),
|
||||
breadcrumbs: BreadcrumbsSettings::default(),
|
||||
@@ -564,6 +570,16 @@ impl Settings {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn editor_font_weights_default_to_regular_and_bold() {
|
||||
let json = serde_json::to_value(Settings::default()).unwrap();
|
||||
assert_eq!(json["editor"]["fontWeight"], 400);
|
||||
assert_eq!(json["editor"]["boldFontWeight"], 700);
|
||||
let s: Settings = serde_json::from_str(r#"{"editor":{"fontWeight":300,"boldFontWeight":600}}"#).unwrap();
|
||||
assert_eq!((s.editor.font_weight, s.editor.bold_font_weight), (300, 600));
|
||||
assert_eq!(s.editor.font_size, 13.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keymap_overrides_are_camel_case_and_default_empty() {
|
||||
let s: Settings =
|
||||
|
||||
@@ -2019,6 +2019,10 @@ export type EditorSettings_Deserialize = {
|
||||
ligatures?: boolean,
|
||||
/** Fallback font for glyphs the main font lacks; empty = none. */
|
||||
fallbackFont?: string,
|
||||
/** Typography Settings › Main font weight (CSS 100–900), as IDEA. */
|
||||
fontWeight?: number,
|
||||
/** Typography Settings › Bold font weight: wherever the editor renders bold. */
|
||||
boldFontWeight?: number,
|
||||
general?: EditorGeneralSettings,
|
||||
appearance?: EditorAppearanceSettings,
|
||||
breadcrumbs?: BreadcrumbsSettings,
|
||||
@@ -2055,6 +2059,10 @@ export type EditorSettings_Serialize = {
|
||||
ligatures: boolean,
|
||||
/** Fallback font for glyphs the main font lacks; empty = none. */
|
||||
fallbackFont: string,
|
||||
/** Typography Settings › Main font weight (CSS 100–900), as IDEA. */
|
||||
fontWeight: number,
|
||||
/** Typography Settings › Bold font weight: wherever the editor renders bold. */
|
||||
boldFontWeight: number,
|
||||
general: EditorGeneralSettings,
|
||||
appearance: EditorAppearanceSettings,
|
||||
breadcrumbs: BreadcrumbsSettings,
|
||||
|
||||
@@ -46,10 +46,10 @@ export const openideaHighlightStyle = HighlightStyle.define([
|
||||
token([t.punctuation, t.bracket, t.separator, t.operator, t.derefOperator], "punct"),
|
||||
token(t.invalid, "invalid"),
|
||||
// Markup (Markdown, docs)
|
||||
token(t.heading, "markup-heading", { bold: "600" }),
|
||||
token(t.heading, "markup-heading", { bold: "var(--fw-code-bold, 700)" }),
|
||||
token(t.processingInstruction, "keyword"),
|
||||
{ tag: t.emphasis, fontStyle: "italic" },
|
||||
{ tag: t.strong, fontWeight: "600" },
|
||||
{ tag: t.strong, fontWeight: "var(--fw-code-bold, 700)" },
|
||||
{ tag: t.strikethrough, textDecoration: "line-through" },
|
||||
// Markdown, as IntelliJ: link destination like a static method, rules like markers.
|
||||
token(t.url, "function", { italic: "italic" }),
|
||||
|
||||
@@ -280,12 +280,14 @@ export function richTextHtml(view: EditorView, from: number, to: number): string
|
||||
const styleOf = new Map<string, string>();
|
||||
const probe = document.createElement("span");
|
||||
view.contentDOM.appendChild(probe);
|
||||
// Bold = heavier than the editor's main weight (Typography Settings).
|
||||
const regular = Number(getComputedStyle(view.contentDOM).fontWeight) || 400;
|
||||
const resolve = (cls: string) => {
|
||||
let s = styleOf.get(cls);
|
||||
if (s === undefined) {
|
||||
probe.className = cls;
|
||||
const cs = getComputedStyle(probe);
|
||||
s = `color:${cs.color};${cs.fontStyle !== "normal" ? `font-style:${cs.fontStyle};` : ""}${Number(cs.fontWeight) >= 600 ? "font-weight:bold;" : ""}`;
|
||||
s = `color:${cs.color};${cs.fontStyle !== "normal" ? `font-style:${cs.fontStyle};` : ""}${Number(cs.fontWeight) > regular ? "font-weight:bold;" : ""}`;
|
||||
styleOf.set(cls, s);
|
||||
}
|
||||
return s;
|
||||
|
||||
@@ -29,6 +29,7 @@ export const openideaTheme = EditorView.theme({
|
||||
// The workbench body uses the arrow cursor; the text area gets the I-beam (also past the last line).
|
||||
cursor: "text",
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
fontSize: "var(--openidea-editor-font-size, var(--fs-code))",
|
||||
lineHeight: "var(--openidea-editor-line-height, var(--lh-code))",
|
||||
fontVariantLigatures: "none",
|
||||
@@ -71,7 +72,7 @@ export const openideaTheme = EditorView.theme({
|
||||
".cm-matchingBracket, &.cm-focused .cm-matchingBracket": {
|
||||
backgroundColor: "var(--editor-brace)",
|
||||
color: "inherit",
|
||||
fontWeight: "var(--matched-brace-weight, 700)",
|
||||
fontWeight: "var(--matched-brace-weight, var(--fw-code-bold, 700))",
|
||||
borderRadius: "1px",
|
||||
},
|
||||
".cm-matchingTag, &.cm-focused .cm-matchingTag": {
|
||||
@@ -241,6 +242,7 @@ export const openideaTheme = EditorView.theme({
|
||||
borderBottom: "1px solid var(--editor-gutter-border)",
|
||||
boxShadow: "0 3px 6px -2px color-mix(in srgb, var(--fg-strong) 12%, transparent)",
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
fontSize: "var(--openidea-editor-font-size, var(--fs-code))",
|
||||
lineHeight: "var(--openidea-editor-line-height, var(--lh-code))",
|
||||
overflow: "hidden",
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
/** Applies Settings › Editor › Color Scheme (scheme + attribute overrides) as one stylesheet. */
|
||||
/**
|
||||
* Applies Settings › Editor › Color Scheme (scheme + attribute overrides) as one
|
||||
* stylesheet, and Editor › Font › Typography Settings as the root's
|
||||
* `--fw-code` / `--fw-code-bold`.
|
||||
*/
|
||||
import { useProject } from "@/platform/project";
|
||||
import { editorPrefs } from "@/platform/editorSettings";
|
||||
import { editorPrefs, fontWeightVars } from "@/platform/editorSettings";
|
||||
import { schemeCss } from "@/platform/colorScheme";
|
||||
|
||||
export function installColorScheme(): () => void {
|
||||
@@ -8,10 +12,14 @@ export function installColorScheme(): () => void {
|
||||
const el = document.createElement("style");
|
||||
el.id = "openidea-editor-color-scheme";
|
||||
document.head.appendChild(el);
|
||||
const root = document.documentElement;
|
||||
const apply = () => {
|
||||
const settings = useProject.getState().settings;
|
||||
const css = schemeCss(settings?.editor?.colorScheme, editorPrefs(settings).colorOverrides);
|
||||
const prefs = editorPrefs(settings);
|
||||
const css = schemeCss(settings?.editor?.colorScheme, prefs.colorOverrides);
|
||||
if (el.textContent !== css) el.textContent = css;
|
||||
for (const [name, value] of Object.entries(fontWeightVars(prefs.fontWeight, prefs.boldFontWeight)))
|
||||
if (root.style.getPropertyValue(name) !== value) root.style.setProperty(name, value);
|
||||
};
|
||||
apply();
|
||||
const unsubscribe = useProject.subscribe((s, prev) => {
|
||||
@@ -23,5 +31,7 @@ export function installColorScheme(): () => void {
|
||||
unsubscribe();
|
||||
observer?.disconnect();
|
||||
el.remove();
|
||||
root.style.removeProperty("--fw-code");
|
||||
root.style.removeProperty("--fw-code-bold");
|
||||
};
|
||||
}
|
||||
@@ -65,6 +65,7 @@
|
||||
margin: 0;
|
||||
padding: 6px 0;
|
||||
font-family: var(--openidea-editor-font, var(--font-mono));
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-small);
|
||||
color: var(--fg-editor);
|
||||
}
|
||||
|
||||
@@ -33,11 +33,13 @@ const markdown = {
|
||||
".cm-lsp-markdown a:hover": { color: "var(--link-hover)", textDecoration: "underline" },
|
||||
".cm-lsp-markdown code": {
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
fontSize: "var(--fs-small)",
|
||||
color: "var(--fg-emphasis)",
|
||||
},
|
||||
".cm-lsp-markdown pre, .cm-lsp-code": {
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
fontSize: "var(--fs-ui)",
|
||||
lineHeight: "1.5",
|
||||
margin: "6px 0",
|
||||
@@ -71,11 +73,13 @@ export const lspTheme = EditorView.theme({
|
||||
".cm-tooltip.cm-tooltip-autocomplete.cm-lsp-completion": {
|
||||
...tooltipSurface,
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
fontSize: "var(--fs-code)",
|
||||
overflow: "hidden",
|
||||
},
|
||||
".cm-tooltip.cm-tooltip-autocomplete.cm-lsp-completion > ul": {
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
minWidth: "420px",
|
||||
maxWidth: "min(720px, 80vw)",
|
||||
maxHeight: "calc(var(--h-list-row) * 11 + 8px)",
|
||||
@@ -123,7 +127,7 @@ export const lspTheme = EditorView.theme({
|
||||
},
|
||||
".cm-lsp-completion .cm-completionMatchedText": {
|
||||
textDecoration: "none",
|
||||
fontWeight: "700",
|
||||
fontWeight: "var(--fw-code-bold, 700)",
|
||||
color: "var(--fg-completion-match, var(--link))",
|
||||
},
|
||||
// A server's label detail (`(a: string)`) hugs the name; template descriptions keep the gap.
|
||||
@@ -138,7 +142,7 @@ export const lspTheme = EditorView.theme({
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
".cm-lsp-completion .cm-lsp-keyword-option .cm-completionLabel": { fontWeight: "700" },
|
||||
".cm-lsp-completion .cm-lsp-keyword-option .cm-completionLabel": { fontWeight: "var(--fw-code-bold, 700)" },
|
||||
".cm-lsp-completion .cm-lsp-deprecated-option .cm-completionLabel": {
|
||||
textDecoration: "line-through",
|
||||
color: "var(--fg-muted)",
|
||||
@@ -211,6 +215,7 @@ export const lspTheme = EditorView.theme({
|
||||
".cm-lsp-link-button:hover": { color: "var(--link-hover)", textDecoration: "underline" },
|
||||
".cm-lsp-signature": {
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
fontSize: "var(--fs-code)",
|
||||
padding: "6px 10px",
|
||||
maxWidth: "640px",
|
||||
@@ -223,7 +228,7 @@ export const lspTheme = EditorView.theme({
|
||||
padding: "0 10px",
|
||||
},
|
||||
".cm-lsp-signature-disabled": { opacity: "0.55" },
|
||||
".cm-lsp-signature b": { fontWeight: "700", color: "var(--fg-strong)" },
|
||||
".cm-lsp-signature b": { fontWeight: "var(--fw-code-bold, 700)", color: "var(--fg-strong)" },
|
||||
".cm-lsp-signature-doc": {
|
||||
borderTop: "1px solid var(--border-popup)",
|
||||
marginTop: "6px",
|
||||
@@ -286,6 +291,7 @@ export const lspTheme = EditorView.theme({
|
||||
margin: "0 -8px 4px",
|
||||
padding: "0",
|
||||
fontFamily: "var(--openidea-editor-font, var(--font-mono))",
|
||||
fontWeight: "var(--fw-code, 400)",
|
||||
fontSize: "var(--fs-ui)",
|
||||
color: "var(--fg)",
|
||||
},
|
||||
@@ -339,7 +345,7 @@ export const lspTheme = EditorView.theme({
|
||||
".cm-sem-property": { color: "var(--syn-property)" },
|
||||
".cm-sem-parameter": { color: "var(--syn-param, var(--syn-variable))" },
|
||||
".cm-sem-self": { color: "var(--syn-self, var(--syn-keyword))" },
|
||||
".cm-sem-keyword": { color: "var(--syn-keyword)" },
|
||||
".cm-sem-keyword": { color: "var(--syn-keyword)", fontWeight: "var(--syn-keyword-bold, inherit)" },
|
||||
".cm-sem-builtin:not(.cm-sem-function)": { color: "var(--syn-builtin, inherit)" },
|
||||
".cm-sem-static": { fontStyle: "italic" },
|
||||
".cm-sem-mutable": { textDecoration: "underline", textDecorationColor: "var(--fg-faint)", textUnderlineOffset: "3px" },
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
}
|
||||
.paramInput {
|
||||
font-family: var(--openidea-editor-font, var(--font-mono));
|
||||
font-weight: var(--fw-code);
|
||||
}
|
||||
.formRow {
|
||||
display: flex;
|
||||
@@ -479,6 +480,7 @@
|
||||
:global(.cm-openidea-db-doc) {
|
||||
margin: 0;
|
||||
font-family: var(--openidea-editor-font, var(--font-mono));
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-code);
|
||||
color: var(--fg-editor, var(--fg-strong));
|
||||
white-space: pre;
|
||||
@@ -498,6 +500,7 @@
|
||||
overflow: auto;
|
||||
padding: 4px 8px;
|
||||
font-family: var(--openidea-editor-font, var(--font-mono));
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-code);
|
||||
line-height: 1.5;
|
||||
color: var(--fg-editor, var(--fg-strong));
|
||||
@@ -648,6 +651,7 @@
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
font-family: var(--openidea-editor-font, var(--font-mono));
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-small);
|
||||
line-height: 1.5;
|
||||
white-space: pre;
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
max-width: 520px;
|
||||
padding: 4px 8px;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-code);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
|
||||
@@ -28,7 +28,7 @@ function spanStyle(s: SpanStyle): CSSProperties | undefined {
|
||||
return {
|
||||
color: s.fg,
|
||||
background: s.bg,
|
||||
fontWeight: s.bold ? 600 : undefined,
|
||||
fontWeight: s.bold ? "var(--fw-code-bold, 700)" : undefined,
|
||||
opacity: s.dim ? 0.6 : undefined,
|
||||
fontStyle: s.italic ? "italic" : undefined,
|
||||
textDecoration: deco || undefined,
|
||||
|
||||
@@ -109,6 +109,7 @@
|
||||
position: relative;
|
||||
padding: 4px 0;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-code);
|
||||
color: var(--fg);
|
||||
background: var(--bg-app);
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
flex-direction: column;
|
||||
padding: 2px 0;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--fw-code);
|
||||
}
|
||||
.results > * {
|
||||
flex: 1;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
overflow: auto;
|
||||
background: var(--bg-app);
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-ui);
|
||||
line-height: 19px;
|
||||
color: var(--fg);
|
||||
|
||||
@@ -25,7 +25,7 @@ import { useProject } from "@/platform/project";
|
||||
import { useKeymap } from "@/platform/keymap";
|
||||
import { ContextMenuHost } from "@/ui";
|
||||
import { DEFAULT_SETTINGS, setAt } from "../model/schema";
|
||||
import { useSettingsDialog } from "../model/staging";
|
||||
import { stage, useSettingsDialog } from "../model/staging";
|
||||
import { SettingsDialog } from "../dialog/SettingsDialog";
|
||||
import { activate } from "..";
|
||||
import { MessagesHost } from "@/workbench/messages";
|
||||
@@ -107,6 +107,27 @@ describe("Settings dialog", () => {
|
||||
expect(within(dialog).getByRole("button", { name: "Apply" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("Typography Settings weights preview live and apply as editor settings", async () => {
|
||||
const dialog = await openDialog("editor.font");
|
||||
expect(within(dialog).getByRole("button", { name: "Main font weight" })).toHaveTextContent("Regular");
|
||||
expect(within(dialog).getByRole("button", { name: "Bold font weight" })).toHaveTextContent("Bold");
|
||||
act(() => {
|
||||
stage("editor.font", "global", "editor.fontWeight", 300);
|
||||
stage("editor.font", "global", "editor.boldFontWeight", 600);
|
||||
});
|
||||
expect(within(dialog).getByRole("button", { name: "Main font weight" })).toHaveTextContent("Light");
|
||||
const preview = within(dialog).getByLabelText("Font preview");
|
||||
expect(preview.style.getPropertyValue("--fw-code")).toBe("300");
|
||||
expect(preview.style.getPropertyValue("--fw-code-bold")).toBe("600");
|
||||
await act(async () => {
|
||||
fireEvent.click(within(dialog).getByRole("button", { name: "Apply" }));
|
||||
});
|
||||
expect(settingsSet.mock.calls).toEqual([
|
||||
["global", "editor.boldFontWeight", "600"],
|
||||
["global", "editor.fontWeight", "300"],
|
||||
]);
|
||||
});
|
||||
|
||||
it("Cancel discards staged edits", async () => {
|
||||
const dialog = await openDialog("editor.codeStyle.other");
|
||||
fireEvent.change(within(dialog).getByLabelText("Tab size"), { target: { value: "2" } });
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
color: var(--fg-strong);
|
||||
}
|
||||
.previewArea {
|
||||
font-weight: var(--fw-code);
|
||||
width: 100%;
|
||||
min-height: 240px;
|
||||
resize: vertical;
|
||||
@@ -213,6 +214,7 @@
|
||||
background: var(--bg-editor, var(--bg-island));
|
||||
color: var(--fg-editor, var(--fg));
|
||||
font-family: var(--openidea-editor-font, var(--font-mono));
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-code);
|
||||
line-height: var(--lh-code);
|
||||
overflow: auto;
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
*/
|
||||
import { useMemo, useState, type CSSProperties } from "react";
|
||||
import { Button, Checkbox, Input, Tree, type SelectOption, T } from "@/ui";
|
||||
import type { TextAttributesOverride } from "@/platform/editorSettings";
|
||||
import { fontWeightVars, type TextAttributesOverride } from "@/platform/editorSettings";
|
||||
import {
|
||||
ATTRIBUTES,
|
||||
SCHEMES,
|
||||
boldWeight,
|
||||
effectiveScheme,
|
||||
schemeTokens,
|
||||
type AttributeDef,
|
||||
@@ -72,7 +73,7 @@ function previewVars(id: SchemeId, o: Record<string, TextAttributesOverride> | u
|
||||
if (ov.foreground && attr.fg) vars[attr.fg] = ov.foreground;
|
||||
if (ov.background && attr.bg) vars[attr.bg] = ov.background;
|
||||
if (attr.token) {
|
||||
if (ov.bold !== undefined) vars[`--syn-${attr.token}-bold`] = ov.bold ? "700" : "400";
|
||||
if (ov.bold !== undefined) vars[`--syn-${attr.token}-bold`] = boldWeight(ov.bold);
|
||||
if (ov.italic !== undefined) vars[`--syn-${attr.token}-italic`] = ov.italic ? "italic" : "normal";
|
||||
if (ov.background) vars[`--syn-${attr.token}-bg`] = ov.background;
|
||||
if (ov.effect && ov.effect !== "none")
|
||||
@@ -255,11 +256,16 @@ function ColorRow({
|
||||
function ColorSchemePage() {
|
||||
const [scheme, setScheme] = useSetting<string>("editor.colorScheme");
|
||||
const [overrides, setOverrides] = useSetting<Overrides>("editor.colorOverrides");
|
||||
const [weight] = useSetting<number>("editor.fontWeight");
|
||||
const [boldFontWeight] = useSetting<number>("editor.boldFontWeight");
|
||||
const [selected, setSelected] = useState<string>("keyword");
|
||||
const id = effectiveScheme(scheme);
|
||||
const mine = overrides?.[id];
|
||||
const attr = ATTRIBUTES.find((a) => a.id === selected);
|
||||
const vars = useMemo(() => previewVars(id, mine), [id, mine]);
|
||||
const vars = useMemo(
|
||||
() => ({ ...previewVars(id, mine), ...fontWeightVars(weight, boldFontWeight) }) as CSSProperties,
|
||||
[id, mine, weight, boldFontWeight],
|
||||
);
|
||||
const update = (attrId: string, v: TextAttributesOverride | undefined) => {
|
||||
const next: Overrides = { ...(overrides ?? {}) };
|
||||
const forScheme = { ...(next[id] ?? {}) };
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
/** Editor › Font: family (installed fonts, monospaced filter), size, line height, ligatures, fallback, editable preview. */
|
||||
/**
|
||||
* Editor › Font: family (installed fonts, monospaced filter), size, line height,
|
||||
* ligatures, Typography Settings (main / bold weight, fallback font), editable preview.
|
||||
*/
|
||||
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 { FONT_WEIGHTS, fontWeightOf, fontWeightVars } from "@/platform/editorSettings";
|
||||
import { LIMITS } from "../../model/schema";
|
||||
import { editorPage } from "./kit";
|
||||
import styles from "./Editor.module.css";
|
||||
@@ -131,6 +135,38 @@ function FallbackRow() {
|
||||
);
|
||||
}
|
||||
|
||||
const WEIGHT_OPTIONS: SelectOption[] = FONT_WEIGHTS.map(([w, name]) => ({ value: String(w), label: name, detail: String(w) }));
|
||||
|
||||
/**
|
||||
* IDEA's Main / Bold font weight. IDEA lists the weights the family ships; the
|
||||
* bundled JetBrains Mono is variable (100–900), and other families get the
|
||||
* standard names (the WebView picks the nearest face or synthesises bold).
|
||||
*/
|
||||
function WeightRows() {
|
||||
const [main, setMain] = useSetting<number>("editor.fontWeight");
|
||||
const [bold, setBold] = useSetting<number>("editor.boldFontWeight");
|
||||
return (
|
||||
<>
|
||||
<Row label="Main font weight">
|
||||
<Select
|
||||
value={String(fontWeightOf(main, 400))}
|
||||
options={WEIGHT_OPTIONS}
|
||||
onChange={(v) => setMain(Number(v))}
|
||||
aria-label="Main font weight"
|
||||
/>
|
||||
</Row>
|
||||
<Row label="Bold font weight" hint="Used wherever the color scheme shows text in bold">
|
||||
<Select
|
||||
value={String(fontWeightOf(bold, 700))}
|
||||
options={WEIGHT_OPTIONS}
|
||||
onChange={(v) => setBold(Number(v))}
|
||||
aria-label="Bold font weight"
|
||||
/>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const SAMPLE = `OpenIDEA is a full-featured IDE
|
||||
with a high level of usability and outstanding
|
||||
advanced code editing and refactoring support.
|
||||
@@ -149,6 +185,8 @@ function Preview() {
|
||||
const [size] = useSetting<number>("editor.fontSize");
|
||||
const [line] = useSetting<number>("editor.lineHeight");
|
||||
const [ligatures] = useSetting<boolean>("editor.ligatures");
|
||||
const [weight] = useSetting<number>("editor.fontWeight");
|
||||
const [boldWeight] = useSetting<number>("editor.boldFontWeight");
|
||||
const [text, setText] = useState(SAMPLE);
|
||||
const fam = monoFontFamily(family, fallback, "var(--font-mono)");
|
||||
return (
|
||||
@@ -163,6 +201,7 @@ function Preview() {
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
style={{
|
||||
...fontWeightVars(weight, boldWeight),
|
||||
fontFamily: fam,
|
||||
fontSize: `${size}px`,
|
||||
lineHeight: `${line}px`,
|
||||
@@ -178,7 +217,7 @@ export const FONT_PAGE = editorPage({
|
||||
title: "Font",
|
||||
order: 22,
|
||||
projectScope: true,
|
||||
keywords: ["typeface", "monospace", "ligatures", "font size", "line spacing"],
|
||||
keywords: ["typeface", "monospace", "ligatures", "font size", "line spacing", "font weight", "bold"],
|
||||
sections: [
|
||||
{
|
||||
items: [
|
||||
@@ -187,7 +226,13 @@ export const FONT_PAGE = editorPage({
|
||||
{ t: "check", key: "editor.ligatures", label: "Enable ligatures" },
|
||||
],
|
||||
},
|
||||
{ title: "Typography Settings", items: [{ t: "custom", label: "Fallback font", component: FallbackRow }] },
|
||||
{
|
||||
title: "Typography Settings",
|
||||
items: [
|
||||
{ t: "custom", label: "Main font weight", keywords: ["Bold font weight"], component: WeightRows },
|
||||
{ t: "custom", label: "Fallback font", component: FallbackRow },
|
||||
],
|
||||
},
|
||||
],
|
||||
footer: Preview,
|
||||
});
|
||||
@@ -30,6 +30,8 @@ export function createEmbeddedTerminal(host: HTMLElement, opts: EmbeddedTerminal
|
||||
fontSize: font.size,
|
||||
lineHeight: font.lineHeight,
|
||||
letterSpacing: font.letterSpacing,
|
||||
fontWeight: font.weight,
|
||||
fontWeightBold: font.boldWeight,
|
||||
cursorBlink: settings.cursorBlink,
|
||||
cursorWidth: 2,
|
||||
cursorInactiveStyle: "outline",
|
||||
|
||||
@@ -15,7 +15,7 @@ import { splitCommandLine } from "@/platform/commandLine";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { workflowSettings, type TerminalSettingsValid } from "@/platform/workflowSettings";
|
||||
import { editorPrefs } from "@/platform/editorSettings";
|
||||
import { readSearchDecorations, readTerminalTheme, remeasureWhenFontsLoad, terminalFont, type TerminalFontSettings } from "./theme";
|
||||
import { readSearchDecorations, readTerminalTheme, remeasureWhenFontsLoad, terminalFont, type EditorFont, type TerminalFontSettings } from "./theme";
|
||||
|
||||
export interface TerminalSearchOptions {
|
||||
caseSensitive: boolean;
|
||||
@@ -80,7 +80,7 @@ function beep() {
|
||||
|
||||
/** The terminal font for `t`, resolved against the current editor font (also used by embedded terminals). */
|
||||
export function fontFor(t: TerminalFontSettings) {
|
||||
const e = useProject.getState().settings?.editor as { fontFamily?: string; fontSize?: number; lineHeight?: number } | undefined;
|
||||
const e = useProject.getState().settings?.editor as EditorFont | undefined;
|
||||
return terminalFont(t, { ...e, fallbackFont: editorPrefs().fallbackFont });
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ export class TerminalSession {
|
||||
fontSize: font.size,
|
||||
lineHeight: font.lineHeight,
|
||||
letterSpacing: font.letterSpacing,
|
||||
fontWeight: font.weight,
|
||||
fontWeightBold: font.boldWeight,
|
||||
cursorBlink: settings.cursorBlink,
|
||||
cursorStyle: CURSOR[settings.cursorShape],
|
||||
cursorWidth: 2,
|
||||
@@ -246,6 +248,8 @@ export class TerminalSession {
|
||||
if (o.fontSize !== font.size) o.fontSize = font.size;
|
||||
if (o.lineHeight !== font.lineHeight) o.lineHeight = font.lineHeight;
|
||||
if (o.letterSpacing !== font.letterSpacing) o.letterSpacing = font.letterSpacing;
|
||||
if (o.fontWeight !== font.weight) o.fontWeight = font.weight;
|
||||
if (o.fontWeightBold !== font.boldWeight) o.fontWeightBold = font.boldWeight;
|
||||
o.cursorStyle = CURSOR[t.cursorShape];
|
||||
o.cursorBlink = t.cursorBlink;
|
||||
o.macOptionIsMeta = t.optionAsMeta;
|
||||
|
||||
@@ -235,6 +235,18 @@ describe("terminalFont", () => {
|
||||
expect(font.family).not.toContain("PingFang SC");
|
||||
});
|
||||
|
||||
it("follows the editor's Typography Settings weights with Use editor font", () => {
|
||||
const f = terminalFont(t, { ...editor, fontWeight: 300, boldFontWeight: 600 }, BASE);
|
||||
expect([f.weight, f.boldWeight]).toEqual([300, 600]);
|
||||
const d = terminalFont(t, editor, BASE);
|
||||
expect([d.weight, d.boldWeight]).toEqual([400, 700]);
|
||||
});
|
||||
|
||||
it("keeps regular / bold for the terminal's own font", () => {
|
||||
const f = terminalFont({ ...t, useEditorFont: false }, { ...editor, fontWeight: 300, boldFontWeight: 600 }, BASE);
|
||||
expect([f.weight, f.boldWeight]).toEqual([400, 700]);
|
||||
});
|
||||
|
||||
it("turns the column width into letter spacing", () => {
|
||||
expect(terminalFont({ ...t, useEditorFont: false }, editor, BASE).letterSpacing).toBe(0);
|
||||
expect(terminalFont({ ...t, useEditorFont: false, columnWidth: 1.5 }, editor, BASE).letterSpacing).toBe(3);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* derived from the status + syntax tokens.
|
||||
*/
|
||||
import type { ITheme, Terminal } from "@xterm/xterm";
|
||||
import { fontWeightOf } from "@/platform/editorSettings";
|
||||
import { BUNDLED_MONO_FAMILY, fontsLoaded, monoFontFamily, monoStack } from "@/platform/fonts";
|
||||
|
||||
export type Rgba = [number, number, number, number];
|
||||
@@ -192,6 +193,8 @@ export interface EditorFont {
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fallbackFont?: string;
|
||||
fontWeight?: number;
|
||||
boldFontWeight?: number;
|
||||
}
|
||||
|
||||
export interface TerminalFont {
|
||||
@@ -199,6 +202,9 @@ export interface TerminalFont {
|
||||
size: number;
|
||||
lineHeight: number;
|
||||
letterSpacing: number;
|
||||
/** xterm `fontWeight` / `fontWeightBold`. */
|
||||
weight: number;
|
||||
boldWeight: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,7 +222,7 @@ function cssNumber(name: string): number {
|
||||
|
||||
/**
|
||||
* Font from Settings › Tools › Terminal: the editor font (Editor › Font, with its
|
||||
* fallback) or the terminal's own family / size / line height (a multiple of the
|
||||
* fallback and Typography Settings weights) or the terminal's own family / size / line height (a multiple of the
|
||||
* cell height). The family resolves like the editor's (`monoFontFamily`), so the
|
||||
* bundled JetBrains Mono is used where it isn't installed; `base` is the resolved
|
||||
* `--font-mono` stack, since xterm measures on a canvas and can't use `var()`.
|
||||
@@ -225,6 +231,8 @@ function cssNumber(name: string): number {
|
||||
*/
|
||||
export function terminalFont(t: TerminalFontSettings, editor: EditorFont | undefined, base = monoStack()): TerminalFont {
|
||||
let family: string, fallback: string, size: number, lineHeight: number;
|
||||
let weight = 400,
|
||||
boldWeight = 700;
|
||||
if (!t.useEditorFont) {
|
||||
family = t.fontFamily;
|
||||
fallback = t.fallbackFontFamily;
|
||||
@@ -237,9 +245,11 @@ export function terminalFont(t: TerminalFontSettings, editor: EditorFont | undef
|
||||
const lh = editor?.lineHeight ?? (editor?.fontSize ? size * 1.6 : cssNumber("--lh-code") || 21);
|
||||
// xterm's lineHeight is a multiplier of the cell height (≈ 1.2 × font size); aim for the editor's line height.
|
||||
lineHeight = Math.max(1, Math.round((lh / (size * 1.2)) * 100) / 100);
|
||||
weight = fontWeightOf(editor?.fontWeight, weight);
|
||||
boldWeight = fontWeightOf(editor?.boldFontWeight, boldWeight);
|
||||
}
|
||||
const letterSpacing = Math.round((t.columnWidth - 1) * size * 0.6 * 10) / 10;
|
||||
return { family: withNerdFonts(monoFontFamily(family, fallback, base)), size, lineHeight, letterSpacing };
|
||||
return { family: withNerdFonts(monoFontFamily(family, fallback, base)), size, lineHeight, letterSpacing, weight, boldWeight };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,7 +83,7 @@ export function todoDecorations(view: EditorView, settings: TodoSettings): Decor
|
||||
|
||||
const theme = EditorView.baseTheme({
|
||||
".cm-openidea-todo, .cm-openidea-todo *": { color: "var(--todo-color) !important", fontStyle: "italic" },
|
||||
".cm-openidea-todo-keyword, .cm-openidea-todo-keyword *": { fontWeight: "600", fontStyle: "normal" },
|
||||
".cm-openidea-todo-keyword, .cm-openidea-todo-keyword *": { fontWeight: "var(--fw-code-bold, 700)", fontStyle: "normal" },
|
||||
});
|
||||
|
||||
export function todoEditorExtension(ctx: EditorDocContext): Extension {
|
||||
|
||||
@@ -22,7 +22,7 @@ export const viewerHighlight = HighlightStyle.define([
|
||||
{ tag: [t.propertyName], color: "var(--syn-property)" },
|
||||
{ tag: [t.variableName], color: "var(--syn-variable)" },
|
||||
{ tag: t.invalid, color: "var(--syn-invalid)" },
|
||||
{ tag: t.heading, fontWeight: "600", color: "var(--fg-strong)" },
|
||||
{ tag: t.heading, fontWeight: "var(--fw-code-bold, 700)", color: "var(--fg-strong)" },
|
||||
{ tag: t.link, color: "var(--link)", textDecoration: "underline" },
|
||||
]);
|
||||
|
||||
@@ -34,7 +34,7 @@ export const viewerTheme = EditorView.theme(
|
||||
color: "var(--fg)",
|
||||
fontSize: "var(--fs-code)",
|
||||
},
|
||||
".cm-scroller": { fontFamily: "var(--font-mono)", lineHeight: "var(--lh-code)" },
|
||||
".cm-scroller": { fontFamily: "var(--font-mono)", fontWeight: "var(--fw-code, 400)", lineHeight: "var(--lh-code)" },
|
||||
".cm-content": { caretColor: "var(--accent)" },
|
||||
".cm-cursor, .cm-dropCursor": { borderLeftColor: "var(--accent)" },
|
||||
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection": {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { beforeAll, describe, expect, it } from "vitest";
|
||||
import { _setSchemeSource, schemeCss, schemeTokens } from "./colorScheme";
|
||||
import { validateEditorExtras } from "./editorSettings";
|
||||
import { fontWeightVars, validateEditorExtras } from "./editorSettings";
|
||||
import type { Settings } from "./ipc";
|
||||
import { useProject } from "./project";
|
||||
import { installColorScheme } from "@/editor/core/colorSchemeRuntime";
|
||||
|
||||
describe("color schemes", () => {
|
||||
beforeAll(() => _setSchemeSource(readFileSync("src/styles/tokens.css", "utf8")));
|
||||
@@ -19,7 +22,43 @@ describe("color schemes", () => {
|
||||
expect(dark).toContain("--syn-keyword: #cf8e6d !important");
|
||||
const over = schemeCss("theme-default", { light: { keyword: { foreground: "#ff0000", bold: true } } }, root);
|
||||
expect(over).toContain("--syn-keyword: #ff0000 !important");
|
||||
expect(over).toContain("--syn-keyword-bold: 700 !important");
|
||||
// Explicit Bold / regular follow Typography Settings' bold / main weight.
|
||||
expect(over).toContain("--syn-keyword-bold: var(--fw-code-bold, 700) !important");
|
||||
const regular = schemeCss("theme-default", { light: { keyword: { bold: false } } }, root);
|
||||
expect(regular).toContain("--syn-keyword-bold: var(--fw-code, 400) !important");
|
||||
});
|
||||
|
||||
it("keeps editor weights out of the schemes", () => {
|
||||
for (const id of ["light", "dark", "dark-hc"] as const) {
|
||||
expect(schemeTokens(id).has("--fw-code")).toBe(false);
|
||||
expect(schemeTokens(id).has("--syn-keyword-bold")).toBe(false);
|
||||
}
|
||||
// Dark schemes bold matched braces with the configured bold weight, even under the light theme.
|
||||
expect(schemeTokens("dark").get("--matched-brace-weight")).toBe("var(--fw-code-bold)");
|
||||
expect(schemeTokens("light").get("--matched-brace-weight")).toBe("inherit");
|
||||
const root = document.documentElement;
|
||||
root.dataset.theme = "light";
|
||||
expect(schemeCss("dark", undefined, root)).toContain("--matched-brace-weight: var(--fw-code-bold) !important");
|
||||
});
|
||||
|
||||
it("sets the configured weights on the root and follows settings changes", () => {
|
||||
const root = document.documentElement;
|
||||
useProject.setState({ settings: { editor: { fontWeight: 300, boldFontWeight: 800 } } as unknown as Settings });
|
||||
const dispose = installColorScheme();
|
||||
expect([root.style.getPropertyValue("--fw-code"), root.style.getPropertyValue("--fw-code-bold")]).toEqual(["300", "800"]);
|
||||
useProject.setState({ settings: { editor: {} } as unknown as Settings });
|
||||
expect([root.style.getPropertyValue("--fw-code"), root.style.getPropertyValue("--fw-code-bold")]).toEqual(["400", "700"]);
|
||||
dispose();
|
||||
expect(root.style.getPropertyValue("--fw-code")).toBe("");
|
||||
});
|
||||
|
||||
it("validates Typography Settings weights", () => {
|
||||
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(validateEditorExtras({ fontWeight: "bold" }).fontWeight).toBe(400);
|
||||
expect(fontWeightVars(500, undefined)).toEqual({ "--fw-code": "500", "--fw-code-bold": "700" });
|
||||
});
|
||||
|
||||
it("coerces editor settings to their defaults' shape", () => {
|
||||
|
||||
@@ -214,6 +214,11 @@ export function schemeValue(id: SchemeId, cssVar: string): string | undefined {
|
||||
return schemeTokens(id).get(cssVar);
|
||||
}
|
||||
|
||||
/** An explicit Bold / regular attribute: the editor's configured bold or main weight. */
|
||||
export function boldWeight(bold: boolean): string {
|
||||
return bold ? "var(--fw-code-bold, 700)" : "var(--fw-code, 400)";
|
||||
}
|
||||
|
||||
const EFFECT_CSS: Record<string, (c: string) => string> = {
|
||||
underline: (c) => `underline ${c}`,
|
||||
wave: (c) => `underline wavy ${c}`,
|
||||
@@ -237,7 +242,7 @@ export function overrideDeclarations(overrides: Record<string, TextAttributesOve
|
||||
}
|
||||
if (attr.token) {
|
||||
const t = attr.token;
|
||||
if (o.bold !== undefined) out.push([`--syn-${t}-bold`, o.bold ? "700" : "400"]);
|
||||
if (o.bold !== undefined) out.push([`--syn-${t}-bold`, boldWeight(o.bold)]);
|
||||
if (o.italic !== undefined) {
|
||||
out.push([`--syn-${t}-italic`, o.italic ? "italic" : "normal"]);
|
||||
if (t === "comment") out.push(["--syn-comment-style", o.italic ? "italic" : "normal"]);
|
||||
|
||||
@@ -54,6 +54,9 @@ export const EDITOR_EXTRA_DEFAULTS = {
|
||||
colorOverrides: {} as Record<string, Record<string, TextAttributesOverride>>,
|
||||
ligatures: false,
|
||||
fallbackFont: "",
|
||||
/** Typography Settings › Main / Bold font weight (CSS weights, IDEA's Regular / Bold). */
|
||||
fontWeight: 400,
|
||||
boldFontWeight: 700,
|
||||
general: {
|
||||
wheelZoom: false,
|
||||
dragAndDrop: true,
|
||||
@@ -185,8 +188,38 @@ export function coerceLike<T>(raw: unknown, def: T): T {
|
||||
return def;
|
||||
}
|
||||
|
||||
/** IDEA's named font weights (Typography Settings). */
|
||||
export const FONT_WEIGHTS: ReadonlyArray<readonly [number, string]> = [
|
||||
[100, "Thin"],
|
||||
[200, "ExtraLight"],
|
||||
[300, "Light"],
|
||||
[400, "Regular"],
|
||||
[500, "Medium"],
|
||||
[600, "SemiBold"],
|
||||
[700, "Bold"],
|
||||
[800, "ExtraBold"],
|
||||
[900, "Black"],
|
||||
];
|
||||
|
||||
/** Nearest CSS weight step (100–900); `def` for anything that is not a number. */
|
||||
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));
|
||||
}
|
||||
|
||||
/** `--fw-code` / `--fw-code-bold`: what editor-font text and its bold runs render with. */
|
||||
export function fontWeightVars(main: unknown, bold: unknown): Record<"--fw-code" | "--fw-code-bold", string> {
|
||||
return {
|
||||
"--fw-code": String(fontWeightOf(main, EDITOR_EXTRA_DEFAULTS.fontWeight)),
|
||||
"--fw-code-bold": String(fontWeightOf(bold, EDITOR_EXTRA_DEFAULTS.boldFontWeight)),
|
||||
};
|
||||
}
|
||||
|
||||
export function validateEditorExtras(raw: unknown): EditorExtras {
|
||||
return coerceLike(raw, EDITOR_EXTRA_DEFAULTS);
|
||||
const out = coerceLike(raw, EDITOR_EXTRA_DEFAULTS);
|
||||
out.fontWeight = fontWeightOf(out.fontWeight, EDITOR_EXTRA_DEFAULTS.fontWeight);
|
||||
out.boldFontWeight = fontWeightOf(out.boldFontWeight, EDITOR_EXTRA_DEFAULTS.boldFontWeight);
|
||||
return out;
|
||||
}
|
||||
|
||||
let cache: { raw: unknown; powerSave: boolean; value: EditorExtras } | null = null;
|
||||
|
||||
@@ -5,6 +5,15 @@
|
||||
* (+ expUI parents) and the Islands Dark / Light editor schemes; metrics are the
|
||||
* theme JSON values at 100% zoom. Components use only these variables.
|
||||
*/
|
||||
/*
|
||||
* Editor font weights (Settings › Editor › Font › Typography Settings; set on
|
||||
* the root at runtime). Outside `:root` so scheme tokens such as
|
||||
* `--matched-brace-weight` keep referring to them instead of a resolved value.
|
||||
*/
|
||||
html {
|
||||
--fw-code: 400;
|
||||
--fw-code-bold: 700;
|
||||
}
|
||||
:root {
|
||||
/* surfaces */
|
||||
--bg-app: #191a1c;
|
||||
@@ -121,7 +130,7 @@
|
||||
--bg-text-search-result: #114957;
|
||||
--bg-text-search-result-current: #165e70;
|
||||
--bg-matched-brace: #43454a;
|
||||
--matched-brace-weight: 700;
|
||||
--matched-brace-weight: var(--fw-code-bold);
|
||||
--bg-matched-tag: #3f4045;
|
||||
--bg-fold-placeholder: #393b40;
|
||||
--fg-fold-placeholder: #868991;
|
||||
@@ -455,7 +464,7 @@
|
||||
--bg-text-search-result: #fcd47e;
|
||||
--bg-text-search-result-current: #f2bf57;
|
||||
--bg-matched-brace: #93d9d9;
|
||||
--matched-brace-weight: normal;
|
||||
--matched-brace-weight: inherit;
|
||||
--bg-matched-tag: #c9ecec;
|
||||
--bg-fold-placeholder: #e9f5e6;
|
||||
--fg-fold-placeholder: #414d41;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
overflow: auto;
|
||||
background: var(--bg-app);
|
||||
font-family: var(--font-mono);
|
||||
font-weight: var(--fw-code);
|
||||
font-size: var(--fs-ui);
|
||||
line-height: 19px;
|
||||
color: var(--fg);
|
||||
|
||||
Reference in new issue
Заблокувати користувача