Skip to content

Commit 310bfd5

Browse files
committed
feat: add csv.fontSize override with editor fallback and row-height scaling
1 parent 6542d8d commit 310bfd5

6 files changed

Lines changed: 73 additions & 14 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Working with CSV files shouldn’t be a chore. With CSV, you get:
3737
- **Add/Delete Rows:** Insert above/below or remove the selected row via context menu.
3838
- **Edit Empty CSVs:** Create or open an empty CSV file and start typing immediately.
3939
- **Column Sorting:** Right-click a header and choose A–Z or Z–A.
40-
- **Custom Font Selection:** Choose a font from a dropdown or inherit VS Code's default.
40+
- **Custom Font Controls:** Choose a font family and optional font-size override, or inherit VS Code defaults.
4141
- **Find & Replace Overlay:** Built-in find/replace bar with match options (case, whole-word, regex), keyboard navigation, and single/all replace actions across the full file (including chunked rows).
4242
- **Multiline Cell Display:** Cells with embedded newlines render as wrapped multi-line content (with preserved line breaks and matching row height).
4343
- **Clickable Links:** URLs in cells are automatically detected and displayed as clickable links. Ctrl/Cmd+click to open them in your browser.
@@ -98,6 +98,7 @@ Global (Settings UI or `settings.json`):
9898

9999
- `csv.enabled` (boolean, default `true`): Enable/disable the custom editor.
100100
- `csv.fontFamily` (string, default empty): Override font family; falls back to `editor.fontFamily`.
101+
- `csv.fontSize` (number, default `0`): Override font size in px; set to `0` to inherit `editor.fontSize`.
101102
- `csv.cellPadding` (number, default `4`): Vertical cell padding in pixels.
102103
- `csv.columnColorMode` (string, default `type`): `type` keeps CSV’s type-based column colors; `theme` uses your theme foreground color for all columns.
103104
- `csv.columnColorPalette` (string, default `default`): Type-color palette when `csv.columnColorMode` is `type`. `cool` biases colors toward greens/blues; `warm` biases colors toward oranges/reds.

media/main.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ const vscode = acquireVsCodeApi();
77

88
const root = document.getElementById('csv-root');
99
const CSV_SEPARATOR = String.fromCodePoint(parseInt(root?.dataset?.sepcode || '44', 10)); // default ','
10+
const parsePositiveNumber = value => {
11+
const parsed = typeof value === 'string' ? Number.parseFloat(value) : Number(value);
12+
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
13+
return parsed;
14+
};
15+
const configuredFontSizePx = parsePositiveNumber(root?.dataset?.fontsize);
16+
const computedFontSizePx = parsePositiveNumber(window.getComputedStyle(document.body).fontSize);
17+
const BASE_FONT_SIZE_PX = configuredFontSizePx ?? computedFontSizePx ?? 14;
18+
const MIN_ROW_HEIGHT = Math.max(22, Math.round(BASE_FONT_SIZE_PX * 1.6));
1019

1120
let lastContextIsHeader = false; // remembers whether we right-clicked a <th>
1221
let isUpdating = false, isSelecting = false, anchorCell = null, rangeEndCell = null, currentSelection = [];
@@ -56,7 +65,7 @@ const applySizeStateToRenderedCells = () => {
5665
});
5766
}
5867
for (const [row, height] of Object.entries(rowSizeState)) {
59-
const px = Math.max(22, Math.round(Number(height)));
68+
const px = Math.max(MIN_ROW_HEIGHT, Math.round(Number(height)));
6069
table.querySelectorAll(`[data-row="${row}"]`).forEach(cell => {
6170
cell.style.height = `${px}px`;
6271
cell.style.minHeight = `${px}px`;
@@ -96,7 +105,7 @@ const restoreState = () => {
96105
try {
97106
const st = vscode.getState() || {};
98107
columnSizeState = normalizeSizeState(st.columnSizes, 40);
99-
rowSizeState = normalizeSizeState(st.rowSizes, 22);
108+
rowSizeState = normalizeSizeState(st.rowSizes, MIN_ROW_HEIGHT);
100109
applySizeStateToRenderedCells();
101110
if (typeof st.scrollX === 'number' && scrollContainer) {
102111
scrollContainer.scrollLeft = st.scrollX;
@@ -524,7 +533,7 @@ const resetColumnWidth = col => {
524533
});
525534
};
526535
const applyRowHeight = (row, heightPx) => {
527-
const height = Math.max(22, Math.round(heightPx));
536+
const height = Math.max(MIN_ROW_HEIGHT, Math.round(heightPx));
528537
rowSizeState[String(row)] = height;
529538
table.querySelectorAll(`[data-row="${row}"]`).forEach(cell => {
530539
cell.style.height = `${height}px`;

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@
116116
"description": "Font family used by the CSV custom editor. Leave empty to inherit ‘editor.fontFamily’.",
117117
"scope": "application"
118118
},
119+
"csv.fontSize": {
120+
"type": "number",
121+
"default": 0,
122+
"minimum": 0,
123+
"description": "Font size in pixels for the CSV custom editor. Set to 0 to inherit 'editor.fontSize'.",
124+
"scope": "application"
125+
},
119126
"csv.cellPadding": {
120127
"type": "number",
121128
"default": 4,

src/CsvEditorProvider.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,22 @@ class CsvEditorController {
370370
return normalizedBase;
371371
}
372372

373+
private static normalizeFontSize(value: unknown): number | undefined {
374+
const parsed = Number(value);
375+
if (!Number.isFinite(parsed) || parsed <= 0) {
376+
return undefined;
377+
}
378+
return Math.round(parsed * 100) / 100;
379+
}
380+
381+
private static resolveEffectiveFontSize(csvFontSize: unknown, editorFontSize: unknown): number {
382+
return (
383+
CsvEditorController.normalizeFontSize(csvFontSize) ??
384+
CsvEditorController.normalizeFontSize(editorFontSize) ??
385+
14
386+
);
387+
}
388+
373389
public getDocumentUri(): vscode.Uri {
374390
return this.document.uri;
375391
}
@@ -1362,6 +1378,10 @@ class CsvEditorController {
13621378
const fontFamily =
13631379
config.get<string>('fontFamily') ||
13641380
vscode.workspace.getConfiguration('editor').get<string>('fontFamily', 'Menlo');
1381+
const fontSize = CsvEditorController.resolveEffectiveFontSize(
1382+
config.get<number>('fontSize', 0),
1383+
vscode.workspace.getConfiguration('editor').get<number>('fontSize', 14)
1384+
);
13651385

13661386
const cellPadding = config.get<number>('cellPadding', 4);
13671387
const data = this.trimTrailingEmptyRows((parsed.data || []) as string[][]);
@@ -1397,6 +1417,7 @@ class CsvEditorController {
13971417
webview,
13981418
nonce,
13991419
fontFamily,
1420+
fontSize,
14001421
cellPadding,
14011422
separator,
14021423
tableHtml,
@@ -1646,6 +1667,7 @@ class CsvEditorController {
16461667
webview: vscode.Webview;
16471668
nonce: string;
16481669
fontFamily: string;
1670+
fontSize: number;
16491671
cellPadding: number;
16501672
separator: string;
16511673
tableHtml: string;
@@ -1654,7 +1676,7 @@ class CsvEditorController {
16541676
nextChunkStart: number;
16551677
hasRemoteChunks: boolean;
16561678
}): string {
1657-
const { webview, nonce, fontFamily, cellPadding, separator, tableHtml, chunksJson, extraColumnColorCss, nextChunkStart, hasRemoteChunks } = args;
1679+
const { webview, nonce, fontFamily, fontSize, cellPadding, separator, tableHtml, chunksJson, extraColumnColorCss, nextChunkStart, hasRemoteChunks } = args;
16581680
const isDark = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark;
16591681
// Build script URI using file path for compatibility (older APIs may lack Uri.joinPath)
16601682
const scriptUri = webview.asWebviewUri(
@@ -1673,10 +1695,10 @@ class CsvEditorController {
16731695
<meta name="viewport" content="width=device-width, initial-scale=1.0">
16741696
<title>CSV</title>
16751697
<style nonce="${nonce}">
1676-
body { font-family: ${this.escapeCss(fontFamily)}; margin: 0; padding: 0; user-select: none; }
1698+
body { font-family: ${this.escapeCss(fontFamily)}; font-size: ${fontSize}px; margin: 0; padding: 0; user-select: none; }
16771699
.table-container { overflow: auto; height: 100vh; }
16781700
table { border-collapse: collapse; width: max-content; }
1679-
th, td { padding: ${cellPadding}px 8px; border: 1px solid ${isDark ? '#555' : '#ccc'}; }
1701+
th, td { padding: ${cellPadding}px 8px; border: 1px solid ${isDark ? '#555' : '#ccc'}; font-size: inherit; }
16801702
th { position: sticky; top: 0; background-color: ${isDark ? '#1e1e1e' : '#ffffff'}; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
16811703
td { overflow: visible; white-space: pre-wrap; overflow-wrap: anywhere; }
16821704
td.selected, th.selected { background-color: ${isDark ? '#333333' : '#cce0ff'} !important; }
@@ -1702,6 +1724,7 @@ class CsvEditorController {
17021724
align-items: stretch;
17031725
color: #d4d4d4;
17041726
font-family: ${this.escapeCss(fontFamily)};
1727+
font-size: inherit;
17051728
}
17061729
#findReplaceWidget.open { display: flex; }
17071730
#findReplaceWidget .fr-gutter {
@@ -1750,7 +1773,7 @@ class CsvEditorController {
17501773
background: #1c1c1c;
17511774
color: #d4d4d4;
17521775
padding-left: 10px;
1753-
font-size: 14px;
1776+
font-size: inherit;
17541777
outline: none;
17551778
}
17561779
#findReplaceWidget .fr-input::placeholder { color: #6a6a6a; }
@@ -1778,7 +1801,7 @@ class CsvEditorController {
17781801
border-radius: 4px;
17791802
background: transparent;
17801803
color: rgba(189,189,189,0.8);
1781-
font-size: 12px;
1804+
font-size: 0.86em;
17821805
cursor: pointer;
17831806
padding: 0 4px;
17841807
}
@@ -1791,7 +1814,7 @@ class CsvEditorController {
17911814
min-width: 84px;
17921815
text-align: right;
17931816
color: #d0d0d0;
1794-
font-size: 14px;
1817+
font-size: inherit;
17951818
}
17961819
#findReplaceWidget .fr-divider {
17971820
width: 1px;
@@ -1853,10 +1876,10 @@ class CsvEditorController {
18531876
text-align: left;
18541877
padding: 6px 8px;
18551878
cursor: pointer;
1856-
font-size: 13px;
1879+
font-size: inherit;
18571880
}
18581881
#findReplaceWidget .fr-overflow-item:hover { background: rgba(255,255,255,0.05); }
1859-
#contextMenu { position: absolute; display: none; background: ${isDark ? '#2d2d2d' : '#ffffff'}; border: 1px solid ${isDark ? '#555' : '#ccc'}; z-index: 10000; font-family: ${this.escapeCss(fontFamily)}; }
1882+
#contextMenu { position: absolute; display: none; background: ${isDark ? '#2d2d2d' : '#ffffff'}; border: 1px solid ${isDark ? '#555' : '#ccc'}; z-index: 10000; font-family: ${this.escapeCss(fontFamily)}; font-size: inherit; }
18601883
#contextMenu div { padding: 4px 12px; cursor: pointer; }
18611884
#contextMenu div:hover { background: ${isDark ? '#3d3d3d' : '#eeeeee'}; }
18621885
@@ -1865,7 +1888,7 @@ class CsvEditorController {
18651888
</style>
18661889
</head>
18671890
<body>
1868-
<div id="csv-root" class="table-container" data-sepcode="${sepCode}" data-nextchunkstart="${nextChunkStart >= 0 ? nextChunkStart : ''}" data-hasmorechunks="${hasRemoteChunks ? '1' : '0'}">
1891+
<div id="csv-root" class="table-container" data-sepcode="${sepCode}" data-fontsize="${fontSize}" data-nextchunkstart="${nextChunkStart >= 0 ? nextChunkStart : ''}" data-hasmorechunks="${hasRemoteChunks ? '1' : '0'}">
18691892
${tableHtml}
18701893
</div>
18711894
@@ -2666,6 +2689,9 @@ export class CsvEditorProvider implements vscode.CustomTextEditorProvider {
26662689
resolveEffectiveColumnColorMode(baseMode: string, isDiffContext: boolean, diffUseThemeForeground: boolean): 'type' | 'theme' {
26672690
return (CsvEditorController as any).resolveEffectiveColumnColorMode(baseMode, isDiffContext, diffUseThemeForeground);
26682691
},
2692+
resolveEffectiveFontSize(csvFontSize: unknown, editorFontSize: unknown): number {
2693+
return (CsvEditorController as any).resolveEffectiveFontSize(csvFontSize, editorFontSize);
2694+
},
26692695
hslToHex(h: number, s: number, l: number): string {
26702696
const c: any = new (CsvEditorController as any)({} as any);
26712697
return c.hslToHex(h, s, l);

src/test/provider-utils.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,16 @@ describe('CsvEditorProvider utility methods', () => {
151151
assert.strictEqual(resolveMode('invalid', false, false), 'type');
152152
});
153153

154+
it('resolves effective font size using csv override or editor fallback', () => {
155+
const resolveFontSize = CsvEditorProvider.__test.resolveEffectiveFontSize;
156+
assert.strictEqual(resolveFontSize(18, 14), 18);
157+
assert.strictEqual(resolveFontSize(0, 14), 14);
158+
assert.strictEqual(resolveFontSize(undefined, 15), 15);
159+
assert.strictEqual(resolveFontSize(-2, 15), 15);
160+
assert.strictEqual(resolveFontSize('abc', 15), 15);
161+
assert.strictEqual(resolveFontSize(undefined, undefined), 14);
162+
});
163+
154164
it('computes paste plan to fill rectangular selection for single-cell clipboard value', () => {
155165
const plan = CsvEditorProvider.__test.computePastePlan(
156166
[['X']],

src/test/webview-size-persistence.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,19 @@ describe('Webview size persistence', () => {
1313

1414
it('restores and reapplies size state after render/chunk loads', () => {
1515
assert.ok(source.includes('columnSizeState = normalizeSizeState(st.columnSizes, 40);'));
16-
assert.ok(source.includes('rowSizeState = normalizeSizeState(st.rowSizes, 22);'));
16+
assert.ok(source.includes('rowSizeState = normalizeSizeState(st.rowSizes, MIN_ROW_HEIGHT);'));
1717
assert.ok(source.includes('applySizeStateToRenderedCells();'));
1818
});
1919

2020
it('updates in-memory size maps when resizing', () => {
2121
assert.ok(source.includes('columnSizeState[String(col)] = width;'));
2222
assert.ok(source.includes('rowSizeState[String(row)] = height;'));
23+
assert.ok(source.includes('Math.max(MIN_ROW_HEIGHT, Math.round(heightPx))'));
24+
});
25+
26+
it('derives a dynamic minimum row height from configured font size', () => {
27+
assert.ok(source.includes('const BASE_FONT_SIZE_PX ='));
28+
assert.ok(source.includes('const MIN_ROW_HEIGHT = Math.max(22, Math.round(BASE_FONT_SIZE_PX * 1.6));'));
2329
});
2430

2531
it('removes size overrides from state when reset to defaults', () => {

0 commit comments

Comments
 (0)