From 81a5f96569533185ddc98c5c6a8bf14cabfb8d55 Mon Sep 17 00:00:00 2001 From: Baptiste Augrain Date: Sat, 7 Mar 2026 23:27:25 +0100 Subject: [PATCH] fix(font-size): detect and apply padding and margin at 0 (#2737) --- font-size/generate-css.ts | 99 +++++++++++++++++----------- patches/feat-experimental-font.patch | 77 ++++++++++++++++++---- 2 files changed, 124 insertions(+), 52 deletions(-) diff --git a/font-size/generate-css.ts b/font-size/generate-css.ts index 3bc4730..7056c42 100755 --- a/font-size/generate-css.ts +++ b/font-size/generate-css.ts @@ -4,7 +4,7 @@ import path from 'node:path'; import process from 'node:process'; import fse from '@zokugun/fs-extra-plus/async'; import { err, OK, type Result, stringifyError, xtry } from '@zokugun/xtry'; -import postcss, { type Rule } from 'postcss'; +import postcss, { Root, type Rule } from 'postcss'; type Area = { name: string; @@ -16,6 +16,7 @@ type Area = { const PX_REGEX = /(-?\d+(\.\d+)?)px\b/g; const COEFF_PRECISION = 6; const HEADER = '/*** Generated for Custom Font Size ***/'; +const ZEROS = ['margin', 'padding']; const AREAS: Record = { activitybar: { @@ -27,7 +28,7 @@ const AREAS: Record = { bottompane: { name: 'bottompane', defaultSize: 13, - files: ['src/vs/workbench/browser/parts/panel/media/panelpart.css'], + files: ['src/vs/workbench/browser/parts/panel/media/panelpart.css', 'src/vs/base/browser/ui/actionbar/actionbar.css'], prefixes: ['.monaco-workbench .part.panel'], }, statusbar: { @@ -50,12 +51,12 @@ const AREAS: Record = { }, }; -function formatCoefficient(n: number): string { +function formatCoefficient(n: number): string { // {{{ const fixed = n.toFixed(COEFF_PRECISION); return fixed.replace(/\.?0+$/, ''); -} +} // }}} -function replacePx(area: Area) { +function replacePx(area: Area) { // {{{ return (match: string, numStr: string): string => { const pxValue = Number.parseFloat(numStr); @@ -67,13 +68,13 @@ function replacePx(area: Area) { return `calc(var(--vscode-workbench-${area.name}-font-size) * ${coeff})`; }; -} +} // }}} -function transformPxValue(value: string, area: Area): string { +function transformPxValue(value: string, area: Area): string { // {{{ return value.replaceAll(PX_REGEX, replacePx(area)); -} +} // }}} -async function processFile(filePath: string, area: Area): Promise> { +async function processFile(filePath: string, areas: Area[]): Promise> { // {{{ const readResult = await fse.readFile(filePath, 'utf8'); if(readResult.fails) { return err(stringifyError(readResult.error)); @@ -88,7 +89,27 @@ async function processFile(filePath: string, area: Area): Promise { + for(const area of areas) { + processFileArea(postcssResult.value, generatedRoot, area) + } + + if(generatedRoot.nodes && generatedRoot.nodes.length > 0) { + const writeResult = await fse.writeFile(filePath, content + `\n\n\n${HEADER}\n\n` + generatedRoot.toString(), 'utf8'); + if(writeResult.fails) { + return err(stringifyError(readResult.error)); + } + + console.log(`Generated: ${filePath}`); + } + else { + console.log(`No px sizes found in: ${filePath}`); + } + + return OK; +} // }}} + +function processFileArea(postcssResult: Root, generatedRoot: Root, area: Area): void { // {{{ + postcssResult.walkRules((rule: Rule) => { const declarationsToAdd: Array<{ prop: string; value: string }> = []; rule.walkDecls((declaration) => { @@ -100,6 +121,9 @@ async function processFile(filePath: string, area: Area): Promise 0) { @@ -129,23 +153,9 @@ async function processFile(filePath: string, area: Area): Promise 0) { - const writeResult = await fse.writeFile(filePath, content + `\n\n\n${HEADER}\n\n` + generatedRoot.toString(), 'utf8'); - if(writeResult.fails) { - return err(stringifyError(readResult.error)); - } - - console.log(`Generated: ${filePath}`); - } - else { - console.log(`No px sizes found in: ${filePath}`); - } - - return OK; -} - -function extractOriginal(content: string): string { +function extractOriginal(content: string): string { // {{{ const index = content.indexOf(HEADER); if(index === -1) { @@ -153,15 +163,15 @@ function extractOriginal(content: string): string { } return content.slice(0, Math.max(0, index - 3)); -} +} // }}} -function extractStyle(selector: string): string { +function extractStyle(selector: string): string { // {{{ const match = /^(\.[\w-]+)/.exec(selector); return match?.[1] ?? ''; -} +} // }}} -function mergeSelector(selectors: string[], prefixes: string[], index: number): void { +function mergeSelector(selectors: string[], prefixes: string[], index: number): void { // {{{ if(index >= prefixes.length) { return; } @@ -186,9 +196,9 @@ function mergeSelector(selectors: string[], prefixes: string[], index: number): else { selectors.splice(index + 1, 0, ...prefixes.slice(index)); } -} +} // }}} -function prefixSelector(selector: string, prefixParts: string[]): string { +function prefixSelector(selector: string, prefixParts: string[]): string { // {{{ const parts = selector.split(' '); if(parts[0] === '.mac' || parts[0] === '.linux' || parts[0] === '.windows') { @@ -201,27 +211,38 @@ function prefixSelector(selector: string, prefixParts: string[]): string { } return parts.join(' '); -} +} // }}} -async function main(): Promise { +async function main(): Promise { // {{{ const name = process.argv[2]; const area = AREAS[name]; if(area) { for(const file of area.files) { - const result = await processFile(path.join('..', 'vscode', file), area); + const result = await processFile(path.join('..', 'vscode', file), [area]); if(result.fails) { console.error(`Error processing ${file}:`, result.error); } } } else if(name === 'all') { + const files: Record = {}; + for(const area of Object.values(AREAS)) { for(const file of area.files) { - const result = await processFile(path.join('..', 'vscode', file), area); - if(result.fails) { - console.error(`Error processing ${file}:`, result.error); + if(files[file]) { + files[file].push(area) } + else { + files[file] = [area] + } + } + } + + for(const [file, areas] of Object.entries(files)) { + const result = await processFile(path.join('..', 'vscode', file), areas); + if(result.fails) { + console.error(`Error processing ${file}:`, result.error); } } } @@ -230,6 +251,6 @@ async function main(): Promise { console.log(`\nAvailable areas:\n- ${Object.keys(AREAS).join('\n- ')}`); return; } -} +} // }}} await main(); diff --git a/patches/feat-experimental-font.patch b/patches/feat-experimental-font.patch index f325149..b461dce 100644 --- a/patches/feat-experimental-font.patch +++ b/patches/feat-experimental-font.patch @@ -1,14 +1,50 @@ diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css -index 467b1ff..16e6d69 100644 +index 467b1ff..f3c5130 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css -@@ -127 +127,35 @@ +@@ -127 +127,72 @@ } + + + +/*** Generated for Custom Font Size ***/ + ++.monaco-workbench .part.panel .monaco-action-bar .actions-container { ++ padding: 0 ++} ++.monaco-workbench .part.panel .monaco-action-bar .action-item .codicon { ++ width: calc(var(--vscode-workbench-bottompane-font-size) * 1.230769); ++ height: calc(var(--vscode-workbench-bottompane-font-size) * 1.230769) ++} ++.monaco-workbench .part.panel .monaco-action-bar .action-label, .monaco-workbench .part.panel .monaco-action-bar .action-item .keybinding { ++ font-size: calc(var(--vscode-workbench-bottompane-font-size) * 0.846154); ++ padding: calc(var(--vscode-workbench-bottompane-font-size) * 0.230769) ++} ++.monaco-workbench .part.panel .monaco-action-bar.vertical .action-item .action-label.separator { ++ padding-top: 1px; ++ margin: calc(var(--vscode-workbench-bottompane-font-size) * 0.307692) .8em ++} ++.monaco-workbench .part.panel .monaco-action-bar .action-item .action-label.separator { ++ width: 1px; ++ height: calc(var(--vscode-workbench-bottompane-font-size) * 1.230769); ++ margin: calc(var(--vscode-workbench-bottompane-font-size) * 0.384615) calc(var(--vscode-workbench-bottompane-font-size) * 0.307692); ++ min-width: 1px; ++ padding: 0 ++} ++.monaco-workbench .part.panel .secondary-actions .monaco-action-bar .action-label { ++ margin-left: calc(var(--vscode-workbench-bottompane-font-size) * 0.461538) ++} ++.monaco-workbench .part.panel .monaco-action-bar .action-item.select-container { ++ max-width: calc(var(--vscode-workbench-bottompane-font-size) * 13.076923); ++ min-width: calc(var(--vscode-workbench-bottompane-font-size) * 4.615385); ++ margin-right: calc(var(--vscode-workbench-bottompane-font-size) * 0.769231) ++} ++.monaco-workbench .part.panel .monaco-action-bar .action-item.action-dropdown-item > .action-dropdown-item-separator > div { ++ width: 1px ++} ++.monaco-workbench .part.sidebar .monaco-action-bar .actions-container, .monaco-workbench .part.auxiliarybar .monaco-action-bar .actions-container { ++ padding: 0 ++} +.monaco-workbench .part.sidebar .monaco-action-bar .action-item .codicon, .monaco-workbench .part.auxiliarybar .monaco-action-bar .action-item .codicon { + width: calc(var(--vscode-workbench-sidebar-font-size) * 1.230769); + height: calc(var(--vscode-workbench-sidebar-font-size) * 1.230769) @@ -25,7 +61,8 @@ index 467b1ff..16e6d69 100644 + width: 1px; + height: calc(var(--vscode-workbench-sidebar-font-size) * 1.230769); + margin: calc(var(--vscode-workbench-sidebar-font-size) * 0.384615) calc(var(--vscode-workbench-sidebar-font-size) * 0.307692); -+ min-width: 1px ++ min-width: 1px; ++ padding: 0 +} +.monaco-workbench .part.sidebar .secondary-actions .monaco-action-bar .action-label, .monaco-workbench .part.auxiliarybar .secondary-actions .monaco-action-bar .action-label { + margin-left: calc(var(--vscode-workbench-sidebar-font-size) * 0.461538) @@ -1179,10 +1216,10 @@ index 0307cab..5e5f6f3 100644 + getPinnedPaneCompositeIds(): string[] { diff --git a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css -index a40a351..aeae132 100644 +index a40a351..51eb067 100644 --- a/src/vs/workbench/browser/parts/activitybar/media/activityaction.css +++ b/src/vs/workbench/browser/parts/activitybar/media/activityaction.css -@@ -230 +230,59 @@ +@@ -230 +230,60 @@ } + + @@ -1240,7 +1277,8 @@ index a40a351..aeae132 100644 + padding: calc(var(--vscode-workbench-activitybar-font-size) * 0.125) calc(var(--vscode-workbench-activitybar-font-size) * 0.125) +} +.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .badge .codicon.badge-content { -+ font-size: calc(var(--vscode-workbench-activitybar-font-size) * 0.8125) ++ font-size: calc(var(--vscode-workbench-activitybar-font-size) * 0.8125); ++ padding: 0 +} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css b/src/vs/workbench/browser/parts/activitybar/media/activitybarpart.css @@ -1488,7 +1526,7 @@ index a24f761..4f3bc89 100644 +} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css -index 924d9b3..07b29cc 100644 +index 924d9b3..7c987f7 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -168,4 +168,4 @@ @@ -1503,7 +1541,7 @@ index 924d9b3..07b29cc 100644 - min-width: calc(var(--tab-sizing-current-width, var(--tab-sizing-fixed-min-width, 50px)) - 1px); + min-width: 50px - 1px; } -@@ -560 +560,112 @@ +@@ -560 +560,113 @@ } + + @@ -1567,6 +1605,7 @@ index 924d9b3..07b29cc 100644 +} +.monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-shrink > .tab-label > .monaco-icon-label-container::after, .monaco-workbench .part.editor > .content .editor-group-container > .title .tabs-container > .tab.sizing-fixed > .tab-label > .monaco-icon-label-container::after { + width: calc(var(--vscode-workbench-tabs-font-size) * 0.384615); ++ padding: 0; + top: 1px; + bottom: 1px; + height: calc(100% - calc(var(--vscode-workbench-tabs-font-size) * 0.153846)) @@ -2982,10 +3021,10 @@ index 8454447..733b9a6 100644 +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css -index 6326d45..8e9a0f1 100644 +index 6326d45..cc5242a 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css -@@ -166 +166,23 @@ +@@ -166 +166,26 @@ } + + @@ -2995,6 +3034,9 @@ index 6326d45..8e9a0f1 100644 +.monaco-workbench .part.sidebar .monaco-action-bar .action-item > .action-label.extension-action.label, .monaco-workbench .part.sidebar .monaco-action-bar .action-dropdown-item > .action-label.extension-action.label, .monaco-workbench .part.auxiliarybar .monaco-action-bar .action-item > .action-label.extension-action.label, .monaco-workbench .part.auxiliarybar .monaco-action-bar .action-dropdown-item > .action-label.extension-action.label { + padding: 0 calc(var(--vscode-workbench-sidebar-font-size) * 0.384615) +} ++.monaco-workbench .part.sidebar .monaco-action-bar .action-dropdown-item > .monaco-dropdown .action-label, .monaco-workbench .part.auxiliarybar .monaco-action-bar .action-dropdown-item > .monaco-dropdown .action-label { ++ padding: 0 ++} +.monaco-workbench .part.sidebar .monaco-action-bar .action-item .action-label.extension-action.label, .monaco-workbench .part.auxiliarybar .monaco-action-bar .action-item .action-label.extension-action.label { + outline-offset: 1px +} @@ -3238,10 +3280,10 @@ index 530b0c4..b3c979d 100644 + return FONT.sidebarSize22; } diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css -index 20c78c3..54b982d 100644 +index 20c78c3..e83c322 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css -@@ -799 +799,211 @@ +@@ -799 +799,215 @@ } + + @@ -3376,6 +3418,10 @@ index 20c78c3..54b982d 100644 +.monaco-workbench .part.sidebar .scm-editor-validation, .monaco-workbench .part.auxiliarybar .scm-editor-validation { + padding: 1px calc(var(--vscode-workbench-sidebar-font-size) * 0.230769) +} ++.monaco-workbench .part.sidebar .scm-editor-validation p, .monaco-workbench .part.auxiliarybar .scm-editor-validation p { ++ margin: 0; ++ padding: 0 ++} +.monaco-workbench .part.sidebar .scm-editor-validation-actions, .monaco-workbench .part.auxiliarybar .scm-editor-validation-actions { + margin-top: 1px +} @@ -3711,7 +3757,7 @@ index e9c0fcd..7ee6d39 100644 + this.replaceInput.width = width - FONT.sidebarSize28; this.replaceInput.inputBox.layout(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts -index 21118dd..9c788d8 100644 +index 21118dd..b5e53a1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabsList.ts @@ -58,2 +58,3 @@ import { TerminalStorageKeys } from '../common/terminalStorageKeys.js'; @@ -3732,6 +3778,11 @@ index 21118dd..9c788d8 100644 - paddingBottom: TerminalTabsListSizes.TabHeight, + paddingBottom: FONT.bottomPaneSize22, dnd: instantiationService.createInstance(TerminalTabsDragAndDrop), +@@ -458,3 +458,3 @@ class TerminalTabsRenderer implements IListRenderer