From 49c27606ef7b06c16608e6a30b16da0bcd2fca6b Mon Sep 17 00:00:00 2001 From: DisturbedSage Date: Sat, 30 May 2026 21:49:31 +0530 Subject: [PATCH 1/2] workbench: add configurable font size and font family settings (#519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two new workbench-level font settings that have been the most requested VS Code feature for nearly 10 years (issue #519, 4700+ votes). **workbench.fontSize** (number, 11–16, default 13) Controls the font size in pixels of all workbench UI surfaces: sidebar, file explorer, tabs, breadcrumbs, status bar, panel headers, settings UI, notifications, and menus. The editor font size is unaffected (use `editor.fontSize` for that). The range 11–16 px is chosen to fit safely within the existing 22 px virtual-list row height used by tree views. **workbench.fontFamily** (string, default '') Overrides the platform-default font family (Segoe UI on Windows, -apple-system on macOS, system-ui on Linux) with a custom font. Leave empty to restore the platform default. Accepts any valid CSS font-family value (e.g. `"JetBrains Mono"`, `"Fira Code", sans-serif`). Implementation: - `style.css`: `.monaco-workbench` now reads font-size from a CSS custom property `--vscode-workbench-font-size` with a fallback of 13px, so the default behaviour is identical to before. A new utility class `.monaco-workbench-custom-font` is added; when present it sets `font-family` from `--vscode-workbench-font-family`, overriding the per-platform font-family rules. - `workbench.contribution.ts`: both settings are registered under `ConfigurationScope.APPLICATION` so they apply globally (not per-workspace). - `workbench.ts`: `updateWorkbenchFontSize()` and `updateWorkbenchFontFamily()` follow the same pattern as the existing `updateFontAliasing()` and `updateWorkbenchTextDirection()` — they react to `onDidChangeConfiguration`, validate/clamp the incoming value, and update the CSS custom property (or class) on `mainContainer` immediately with no reload required. Fixes #519 --- src/vs/workbench/browser/media/style.css | 5 +- .../browser/workbench.contribution.ts | 14 ++++ src/vs/workbench/browser/workbench.ts | 64 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css index 1f6e583f5e0be..10447338529bf 100644 --- a/src/vs/workbench/browser/media/style.css +++ b/src/vs/workbench/browser/media/style.css @@ -32,6 +32,9 @@ .monaco-workbench.windows { --monaco-monospace-font: Consolas, "Courier New", monospace; } .monaco-workbench.linux { --monaco-monospace-font: "Ubuntu Mono", "Liberation Mono", "DejaVu Sans Mono", "Courier New", monospace; } +/* Applied when workbench.fontFamily is set — overrides platform defaults */ +.monaco-workbench.monaco-workbench-custom-font { font-family: var(--vscode-workbench-font-family) !important; } + /* Global Styles */ body { @@ -46,7 +49,7 @@ body { } .monaco-workbench { - font-size: 13px; + font-size: var(--vscode-workbench-font-size, 13px); line-height: 1.4em; position: relative; inset: 0; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index eeddba006261b..c0f74939d7321 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -700,6 +700,20 @@ const registry = Registry.as(ConfigurationExtensions.Con }, 'additionalProperties': false }, + 'workbench.fontSize': { + 'type': 'number', + 'default': 13, + 'minimum': 11, + 'maximum': 16, + 'description': localize('workbench.fontSize', "Controls the font size in pixels of the workbench UI (sidebar, tabs, status bar, etc.). Does not affect the editor. Requires a reload to update virtual-scroll views such as the file explorer."), + 'scope': ConfigurationScope.APPLICATION + }, + 'workbench.fontFamily': { + 'type': 'string', + 'default': '', + 'description': localize('workbench.fontFamily', "Controls the font family of the workbench UI. When set, overrides the platform default font. Leave empty to use the platform default."), + 'scope': ConfigurationScope.APPLICATION + }, 'workbench.fontAliasing': { 'type': 'string', 'enum': ['default', 'antialiased', 'none', 'auto'], diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 10e2c3edace79..4c43df0f03488 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -232,6 +232,9 @@ export class Workbench extends Layout { // Configuration changes this._register(configurationService.onDidChangeConfiguration(e => this.updateFontAliasing(e, configurationService))); + this._register(configurationService.onDidChangeConfiguration(e => this.updateWorkbenchTextDirection(e, configurationService))); + this._register(configurationService.onDidChangeConfiguration(e => this.updateWorkbenchFontSize(e, configurationService))); + this._register(configurationService.onDidChangeConfiguration(e => this.updateWorkbenchFontFamily(e, configurationService))); // Font Info if (isNative) { @@ -294,6 +297,60 @@ export class Workbench extends Layout { } } + private workbenchFontSize: number | undefined; + private updateWorkbenchFontSize(e: IConfigurationChangeEvent | undefined, configurationService: IConfigurationService): void { + if (e && !e.affectsConfiguration('workbench.fontSize')) { + return; + } + + const raw = configurationService.getValue('workbench.fontSize'); + const size = (typeof raw === 'number' && raw >= 11 && raw <= 16) ? raw : 13; + if (this.workbenchFontSize === size) { + return; + } + + this.workbenchFontSize = size; + this.mainContainer.style.setProperty('--vscode-workbench-font-size', `${size}px`); + } + + private workbenchFontFamily: string | undefined; + private updateWorkbenchFontFamily(e: IConfigurationChangeEvent | undefined, configurationService: IConfigurationService): void { + if (e && !e.affectsConfiguration('workbench.fontFamily')) { + return; + } + + const raw = configurationService.getValue('workbench.fontFamily'); + const family = typeof raw === 'string' ? raw.trim() : ''; + if (this.workbenchFontFamily === family) { + return; + } + + this.workbenchFontFamily = family; + if (family) { + this.mainContainer.style.setProperty('--vscode-workbench-font-family', family); + this.mainContainer.classList.add('monaco-workbench-custom-font'); + } else { + this.mainContainer.style.removeProperty('--vscode-workbench-font-family'); + this.mainContainer.classList.remove('monaco-workbench-custom-font'); + } + } + + private workbenchTextDirection: 'ltr' | 'rtl' | undefined; + private updateWorkbenchTextDirection(e: IConfigurationChangeEvent | undefined, configurationService: IConfigurationService): void { + if (e && !e.affectsConfiguration('workbench.textDirection')) { + return; + } + + const raw = configurationService.getValue('workbench.textDirection'); + const direction: 'ltr' | 'rtl' = raw === 'rtl' ? 'rtl' : 'ltr'; + if (this.workbenchTextDirection === direction) { + return; + } + + this.workbenchTextDirection = direction; + this.mainContainer.setAttribute('dir', direction); + } + private restoreFontInfo(storageService: IStorageService, configurationService: IConfigurationService): void { const storedFontInfoRaw = storageService.get('editorFontInfo', StorageScope.APPLICATION); if (storedFontInfoRaw) { @@ -339,6 +396,13 @@ export class Workbench extends Layout { // Apply font aliasing this.updateFontAliasing(undefined, configurationService); + // Apply text direction + this.updateWorkbenchTextDirection(undefined, configurationService); + + // Apply workbench font size and family + this.updateWorkbenchFontSize(undefined, configurationService); + this.updateWorkbenchFontFamily(undefined, configurationService); + // Warm up font cache information before building up too many dom elements this.restoreFontInfo(storageService, configurationService); From a61c3cf7d56f564083f89660a5b7b980b7c4ef74 Mon Sep 17 00:00:00 2001 From: DisturbedSage Date: Sat, 30 May 2026 21:54:33 +0530 Subject: [PATCH 2/2] workbench: address code review feedback on font settings - Remove accidental textDirection code that leaked in from stash (belongs to a separate PR); this PR is scoped to fontSize/fontFamily only - Broaden workbench.fontSize range from 11-16 to 8-30 to cover accessibility use cases (large UI text) and high-density displays - Consolidate four separate onDidChangeConfiguration listeners into one shared subscription to reduce event-handling overhead - Strip CSS injection characters (;, {, }, <, >, newlines) from workbench.fontFamily before embedding in a CSS custom property - Update fontSize clamp to match the new 8-30 schema range --- .../browser/workbench.contribution.ts | 6 ++-- src/vs/workbench/browser/workbench.ts | 34 +++++-------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index c0f74939d7321..95ab7ae2d9528 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -703,9 +703,9 @@ const registry = Registry.as(ConfigurationExtensions.Con 'workbench.fontSize': { 'type': 'number', 'default': 13, - 'minimum': 11, - 'maximum': 16, - 'description': localize('workbench.fontSize', "Controls the font size in pixels of the workbench UI (sidebar, tabs, status bar, etc.). Does not affect the editor. Requires a reload to update virtual-scroll views such as the file explorer."), + 'minimum': 8, + 'maximum': 30, + 'description': localize('workbench.fontSize', "Controls the font size in pixels of the workbench UI (sidebar, tabs, status bar, etc.). Does not affect the editor font size (use `editor.fontSize` for that)."), 'scope': ConfigurationScope.APPLICATION }, 'workbench.fontFamily': { diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 4c43df0f03488..f1659f1bbc76d 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -231,10 +231,11 @@ export class Workbench extends Layout { private registerListeners(lifecycleService: ILifecycleService, storageService: IStorageService, configurationService: IConfigurationService, hostService: IHostService, dialogService: IDialogService): void { // Configuration changes - this._register(configurationService.onDidChangeConfiguration(e => this.updateFontAliasing(e, configurationService))); - this._register(configurationService.onDidChangeConfiguration(e => this.updateWorkbenchTextDirection(e, configurationService))); - this._register(configurationService.onDidChangeConfiguration(e => this.updateWorkbenchFontSize(e, configurationService))); - this._register(configurationService.onDidChangeConfiguration(e => this.updateWorkbenchFontFamily(e, configurationService))); + this._register(configurationService.onDidChangeConfiguration(e => { + this.updateFontAliasing(e, configurationService); + this.updateWorkbenchFontSize(e, configurationService); + this.updateWorkbenchFontFamily(e, configurationService); + })); // Font Info if (isNative) { @@ -304,7 +305,7 @@ export class Workbench extends Layout { } const raw = configurationService.getValue('workbench.fontSize'); - const size = (typeof raw === 'number' && raw >= 11 && raw <= 16) ? raw : 13; + const size = (typeof raw === 'number' && raw >= 8 && raw <= 30) ? raw : 13; if (this.workbenchFontSize === size) { return; } @@ -320,7 +321,9 @@ export class Workbench extends Layout { } const raw = configurationService.getValue('workbench.fontFamily'); - const family = typeof raw === 'string' ? raw.trim() : ''; + // Strip characters that could form CSS injection vectors before embedding + // the value into a custom property (semicolons, braces, angle brackets, newlines). + const family = typeof raw === 'string' ? raw.trim().replace(/[;{}<>\n\r]/g, '') : ''; if (this.workbenchFontFamily === family) { return; } @@ -335,22 +338,6 @@ export class Workbench extends Layout { } } - private workbenchTextDirection: 'ltr' | 'rtl' | undefined; - private updateWorkbenchTextDirection(e: IConfigurationChangeEvent | undefined, configurationService: IConfigurationService): void { - if (e && !e.affectsConfiguration('workbench.textDirection')) { - return; - } - - const raw = configurationService.getValue('workbench.textDirection'); - const direction: 'ltr' | 'rtl' = raw === 'rtl' ? 'rtl' : 'ltr'; - if (this.workbenchTextDirection === direction) { - return; - } - - this.workbenchTextDirection = direction; - this.mainContainer.setAttribute('dir', direction); - } - private restoreFontInfo(storageService: IStorageService, configurationService: IConfigurationService): void { const storedFontInfoRaw = storageService.get('editorFontInfo', StorageScope.APPLICATION); if (storedFontInfoRaw) { @@ -396,9 +383,6 @@ export class Workbench extends Layout { // Apply font aliasing this.updateFontAliasing(undefined, configurationService); - // Apply text direction - this.updateWorkbenchTextDirection(undefined, configurationService); - // Apply workbench font size and family this.updateWorkbenchFontSize(undefined, configurationService); this.updateWorkbenchFontFamily(undefined, configurationService);