From 4d6ad3922e67e61e1f7810ef2072620533efadea Mon Sep 17 00:00:00 2001 From: Jona Date: Sat, 17 Oct 2026 00:11:21 -0400 Subject: [PATCH 1/4] Restore readability in high-contrast themes (#92) --- README.md | 2 +- media/high-contrast.css | 117 +++++++++++++++++++++++++++++++ src/CsvEditorProvider.ts | 14 +++- src/extension.ts | 4 ++ src/test/extension-theme.test.ts | 34 +++++++++ src/test/high-contrast.test.ts | 100 ++++++++++++++++++++++++++ 6 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 media/high-contrast.css create mode 100644 src/test/extension-theme.test.ts create mode 100644 src/test/high-contrast.test.ts diff --git a/README.md b/README.md index 8f7a614..bae56e3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Working with CSV files shouldn’t be a chore. With CSV, you get: - **Clipboard Actions:** Use Ctrl/Cmd + C or X, or right-click a selection and choose Copy or Cut, to copy properly quoted data to the system clipboard. Right-click Paste inserts clipboard data into the grid. Clipboard shortcuts in Find, Replace, and cell text edits keep their normal text-editing behavior. - **Grid Paste Support:** Paste copied ranges directly into the selected cell/range with `Ctrl/Cmd + V` in selection mode. - **Robust Data Handling:** Leveraging the power of [Papa Parse](https://www.papaparse.com/), the extension handles complex CSV structures, special characters, and various data types gracefully. -- **Theme-Optimized Interface:** Whether you prefer light or dark mode, CSV automatically adapts its styles for an optimal viewing experience. +- **Theme-Optimized Interface:** CSV adapts to light, dark, and high-contrast themes. High-contrast themes use your theme’s foreground colors for cells, selection, Find/Replace, and menus automatically. --- diff --git a/media/high-contrast.css b/media/high-contrast.css new file mode 100644 index 0000000..6622a86 --- /dev/null +++ b/media/high-contrast.css @@ -0,0 +1,117 @@ +/* VS Code updates these body classes and color variables on theme changes. + Override legacy inline table colors for high-contrast themes, including chunks. */ +body:is(.vscode-high-contrast, .vscode-high-contrast-light) { + color: var(--vscode-editor-foreground); + background-color: var(--vscode-editor-background); + --csv-contrast-border: var(--vscode-contrastBorder, var(--vscode-editor-foreground)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th) { + color: var(--vscode-editor-foreground) !important; + border-color: var(--csv-contrast-border) !important; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) th { + background-color: var(--vscode-editor-background) !important; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).selected { + color: var(--vscode-editor-selectionForeground, var(--vscode-editor-foreground)) !important; + background-color: var(--vscode-editor-selectionBackground, var(--vscode-editor-background)) !important; + outline: 1px solid var(--vscode-focusBorder, var(--csv-contrast-border)); + outline-offset: -1px; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).highlight { + background-color: var(--vscode-editor-findMatchHighlightBackground, var(--vscode-editor-background)) !important; + outline: 1px solid var(--vscode-editor-findMatchHighlightBorder, var(--csv-contrast-border)); + outline-offset: -1px; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).active-match { + color: var(--vscode-editor-findMatchForeground, var(--vscode-editor-foreground)) !important; + background-color: var(--vscode-editor-findMatchBackground, var(--vscode-editor-background)) !important; + outline: 2px solid var(--vscode-editor-findMatchBorder, var(--vscode-focusBorder)); + outline-offset: -2px; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) .csv-link { + color: var(--vscode-textLink-foreground, var(--vscode-editor-foreground)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) .csv-link:hover { + color: var(--vscode-textLink-activeForeground, var(--vscode-textLink-foreground)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) .selected .csv-link { + color: inherit; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #contextMenu { + color: var(--vscode-menu-foreground, var(--vscode-editor-foreground)); + background-color: var(--vscode-menu-background, var(--vscode-editor-background)); + border-color: var(--csv-contrast-border); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #contextMenu div { + border-color: var(--csv-contrast-border) !important; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #contextMenu div:hover { + color: var(--vscode-menu-selectionForeground, var(--vscode-editor-foreground)); + background-color: var(--vscode-menu-selectionBackground, var(--vscode-editor-background)); + outline: 1px solid var(--vscode-menu-selectionBorder, var(--csv-contrast-border)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget { + color: var(--vscode-editorWidget-foreground, var(--vscode-editor-foreground)); + background-color: var(--vscode-editorWidget-background, var(--vscode-editor-background)); + border-color: var(--csv-contrast-border); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(.fr-gutter, .fr-overflow-menu) { + background-color: var(--vscode-editorWidget-background, var(--vscode-editor-background)); + border-color: var(--csv-contrast-border); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-input { + color: var(--vscode-input-foreground, var(--vscode-editor-foreground)); + background-color: var(--vscode-input-background, var(--vscode-editor-background)); + border-color: var(--vscode-input-border, var(--csv-contrast-border)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-input::placeholder { + color: var(--vscode-input-placeholderForeground, var(--vscode-editor-foreground)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(.fr-status, button) { + color: var(--vscode-editorWidget-foreground, var(--vscode-editor-foreground)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget button:hover { + color: var(--vscode-toolbar-hoverForeground, var(--vscode-editor-foreground)); + background-color: var(--vscode-toolbar-hoverBackground, var(--vscode-editor-background)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-toggle-btn[aria-pressed="true"] { + color: var(--vscode-inputOption-activeForeground, var(--vscode-editor-foreground)); + background-color: var(--vscode-inputOption-activeBackground, var(--vscode-editor-background)); + box-shadow: inset 0 -2px 0 var(--vscode-inputOption-activeBorder, var(--csv-contrast-border)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget button[disabled] { + color: var(--vscode-disabledForeground, var(--vscode-editor-foreground)); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(button, input):focus-visible { + outline: 2px solid var(--vscode-focusBorder, var(--csv-contrast-border)); + outline-offset: -2px; +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-divider { + background-color: var(--csv-contrast-border); +} + +body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-inline-toggles { + border-color: var(--csv-contrast-border); +} diff --git a/src/CsvEditorProvider.ts b/src/CsvEditorProvider.ts index a1484e4..5630901 100644 --- a/src/CsvEditorProvider.ts +++ b/src/CsvEditorProvider.ts @@ -1482,7 +1482,8 @@ class CsvEditorController { const totalRows = data.length; const offset = Math.min(Math.max(0, hiddenRows), totalRows); - const isDark = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark; + const themeKind = vscode.window.activeColorTheme.kind; + const isDark = themeKind === vscode.ColorThemeKind.Dark || themeKind === vscode.ColorThemeKind.HighContrast; let headerRow: string[] = []; let bodyData: string[][] = []; if (totalRows === 0 || offset >= totalRows) { @@ -1500,7 +1501,8 @@ class CsvEditorController { const columnData = Array.from({ length: numColumns }, (_, i) => bodyData.map(row => row[i] || '')); const columnTypes = columnData.map(col => this.estimateColumnDataType(col)); - const useThemeForeground = columnColorMode === 'theme'; + const highContrast = themeKind === vscode.ColorThemeKind.HighContrast || themeKind === vscode.ColorThemeKind.HighContrastLight; + const useThemeForeground = columnColorMode === 'theme' || highContrast; const palette = columnColorPalette === 'cool' ? 'cool' : (columnColorPalette === 'warm' ? 'warm' : 'default'); @@ -1712,12 +1714,17 @@ class CsvEditorController { columnSizes: Record; }): string { const { webview, nonce, fontFamily, fontSize, cellPadding, separator, tableHtml, chunksJson, extraColumnColorCss, nextChunkStart, hasRemoteChunks, mouseWheelZoomEnabled, mouseWheelZoomInvert, columnSizes } = args; - const isDark = vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark; + const themeKind = vscode.window.activeColorTheme.kind; + const isDark = themeKind === vscode.ColorThemeKind.Dark || themeKind === vscode.ColorThemeKind.HighContrast; // Build script URI using file path for compatibility (older APIs may lack Uri.joinPath) const scriptUri = webview.asWebviewUri( vscode.Uri.file(path.join(this.context.extensionPath, 'media', 'main.js')) ); + const contrastStyleUri = webview.asWebviewUri( + vscode.Uri.file(path.join(this.context.extensionPath, 'media', 'high-contrast.css')) + ); + // Safe separator transport (assumes single character; see assumptions) const sepCode = (separator && separator.length > 0) ? separator.codePointAt(0)! : ','.codePointAt(0)!; @@ -1921,6 +1928,7 @@ class CsvEditorController { /* Per-column computed colors */ ${extraColumnColorCss} +
diff --git a/src/extension.ts b/src/extension.ts index 50ec42b..abaff87 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -104,6 +104,10 @@ export function activate(context: vscode.ExtensionContext) { } }); context.subscriptions.push(cfgListener); + // Recompute type colors when entering/leaving high contrast, including open chunks. + context.subscriptions.push(vscode.window.onDidChangeActiveColorTheme(() => { + CsvEditorProvider.editors.forEach(editor => editor.refresh()); + })); // Register the custom editor provider for CSV/TSV context.subscriptions.push( diff --git a/src/test/extension-theme.test.ts b/src/test/extension-theme.test.ts new file mode 100644 index 0000000..f44573f --- /dev/null +++ b/src/test/extension-theme.test.ts @@ -0,0 +1,34 @@ +import assert from 'assert'; +import Module from 'module'; +import { it } from 'node:test'; + +let themeChanged: () => void = () => {}; +const themeListener = { dispose() {} }; +const originalRequire = Module.prototype.require; +Module.prototype.require = function (id: string) { + if (id === 'vscode') { + return { + commands: { registerCommand: () => ({ dispose() {} }) }, + workspace: { onDidChangeConfiguration: () => ({ dispose() {} }) }, + window: { + onDidChangeActiveColorTheme: (listener: () => void) => { themeChanged = listener; return themeListener; }, + registerCustomEditorProvider: () => ({ dispose() {} }) + } + }; + } + return originalRequire.apply(this, arguments as any); +}; +import { activate } from '../extension'; +import { CsvEditorProvider } from '../CsvEditorProvider'; +Module.prototype.require = originalRequire; + +it('refreshes every open editor when the color theme changes and disposes the listener', () => { + const context = { subscriptions: [] as any[] }; + activate(context as any); + let refreshed = 0; + CsvEditorProvider.editors = [{ refresh: () => refreshed++ }, { refresh: () => refreshed++ }] as any; + themeChanged(); + assert.strictEqual(refreshed, 2); + assert.ok(context.subscriptions.includes(themeListener)); + CsvEditorProvider.editors = []; +}); diff --git a/src/test/high-contrast.test.ts b/src/test/high-contrast.test.ts new file mode 100644 index 0000000..ef2f3f0 --- /dev/null +++ b/src/test/high-contrast.test.ts @@ -0,0 +1,100 @@ +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import Module from 'module'; +import { describe, it } from 'node:test'; + +const theme = { kind: 1 }; +const kinds = { Light: 1, Dark: 2, HighContrast: 3, HighContrastLight: 4 }; +const originalRequire = Module.prototype.require; +Module.prototype.require = function (id: string) { + if (id === 'vscode') { + return { + ColorThemeKind: kinds, + window: { activeColorTheme: theme }, + Uri: { file: (filePath: string) => ({ fsPath: filePath, toString: () => `test-resource:${filePath}` }) }, + workspace: { + getConfiguration: () => ({ get: (key: string, fallback: any) => key === 'maxFileSizeMB' ? 0 : fallback }), + onDidChangeTextDocument: () => ({ dispose() {} }), + onWillSaveTextDocument: () => ({ dispose() {} }) + } + }; + } + return originalRequire.apply(this, arguments as any); +}; +import { CsvEditorProvider } from '../CsvEditorProvider'; +Module.prototype.require = originalRequire; + +describe('High-contrast rendering', () => { + it('uses theme foreground for header, body and virtual cells in both high-contrast variants', () => { + for (const kind of [kinds.HighContrast, kinds.HighContrastLight]) { + theme.kind = kind; + const { tableHtml } = CsvEditorProvider.__test.generateTableAndChunksRaw([ + ['Name', 'Value'], ['hello', '123'] + ], true, true, 0); + const dataCells = tableHtml.match(/<(?:td|th)\b[^>]*data-col="[0-9]+"[^>]*>/g) || []; + assert.strictEqual(dataCells.length, 6); + for (const cell of dataCells) { assert.ok(cell.includes('color: var(--vscode-editor-foreground)')); } + } + }); + + it('applies high-contrast foreground to serialized and on-demand chunks', () => { + for (const kind of [kinds.HighContrast, kinds.HighContrastLight]) { + theme.kind = kind; + const rows = [['Header'], ...Array.from({ length: 1100 }, (_, index) => [String(index)])]; + const serialized = CsvEditorProvider.__test.generateTableAndChunksRaw(rows, true, false, 0); + assert.ok(serialized.chunks.length > 0); + assert.ok(serialized.chunks[0].includes('color:var(--vscode-editor-foreground)')); + const remote = CsvEditorProvider.__test.generateRuntimeChunkTransport(rows, true, false, 0, 1000); + assert.ok(remote.response?.html.includes('color:var(--vscode-editor-foreground)')); + } + }); + + it('retains type colors in ordinary light and dark themes', () => { + for (const kind of [kinds.Light, kinds.Dark]) { + theme.kind = kind; + const { tableHtml } = CsvEditorProvider.__test.generateTableAndChunksRaw([['Name'], ['hello']], true, false, 0); + assert.ok(!tableHtml.includes('var(--vscode-editor-foreground)')); + assert.ok(tableHtml.includes('color: #')); + } + }); + + it('loads the packaged high-contrast stylesheet after generated table styles', async () => { + const webview = { + html: '', options: {}, cspSource: 'test-resource:', + asWebviewUri: (uri: any) => uri.toString(), postMessage() {}, onDidReceiveMessage() {} + }; + const panel = { title: 'fixture', active: false, webview, onDidChangeViewState() {}, onDidDispose() {} }; + const context = { extensionPath: process.cwd(), workspaceState: { get: (_key: string, fallback: any) => fallback } }; + const uri = { fsPath: '/fixture.csv', toString: () => 'file:///fixture.csv' }; + await new CsvEditorProvider(context as any).resolveCustomTextEditor({ uri, version: 1, getText: () => 'Name\nhello' } as any, panel as any, {} as any); + const link = ``; + assert.ok(webview.html.includes(link)); + assert.ok(webview.html.indexOf(link) > webview.html.indexOf('')); + assert.ok(fs.existsSync(path.join(process.cwd(), 'media', 'high-contrast.css'))); + }); + + it('scopes accessibility styles to high-contrast themes and covers interactive states with theme tokens', () => { + const css = fs.readFileSync(path.join(process.cwd(), 'media', 'high-contrast.css'), 'utf8'); + const rules = css.replace(/\/\*[\s\S]*?\*\//g, '').split('}').filter(rule => rule.trim()).map(rule => { + const [selector, declarations] = rule.split('{'); + return { selector: selector.trim(), declarations: declarations.trim() }; + }); + for (const rule of rules) { + assert.ok(rule.selector.startsWith('body:is(.vscode-high-contrast, .vscode-high-contrast-light)')); + assert.ok(!/#[0-9a-f]{3,8}\b/i.test(rule.declarations), 'high-contrast colors must come from the active theme'); + } + const expectedStates = new Map([ + ['.selected', ['selectionForeground', 'selectionBackground', 'outline']], + ['.active-match', ['findMatchForeground', 'findMatchBackground', 'outline']], + ['.fr-input {', ['input-foreground', 'input-background']], + ['#contextMenu {', ['menu-foreground', 'menu-background']], + [':focus-visible', ['focusBorder', 'outline']] + ]); + for (const [state, tokens] of expectedStates) { + const rule = rules.find(item => `${item.selector} {`.includes(state)); + assert.ok(rule, state); + for (const token of tokens) { assert.ok(rule!.declarations.includes(token), `${state}: ${token}`); } + } + }); +}); From 7a91fc520df27a1936cafc849378c3ba83a8f5e9 Mon Sep 17 00:00:00 2001 From: Jona Date: Sat, 17 Oct 2026 00:13:07 -0400 Subject: [PATCH 2/4] Switch theme colors without reloading active cell drafts --- src/CsvEditorProvider.ts | 11 +++++++++-- src/extension.ts | 4 ---- src/test/extension-theme.test.ts | 34 -------------------------------- src/test/high-contrast.test.ts | 8 +++++++- 4 files changed, 16 insertions(+), 41 deletions(-) delete mode 100644 src/test/extension-theme.test.ts diff --git a/src/CsvEditorProvider.ts b/src/CsvEditorProvider.ts index 5630901..f892c65 100644 --- a/src/CsvEditorProvider.ts +++ b/src/CsvEditorProvider.ts @@ -1561,9 +1561,16 @@ class CsvEditorController { } } - const colorCss = useThemeForeground + // VS Code changes these classes in place. Keep both palettes available so + // entering/leaving high contrast never needs to reload a live cell edit. + const colorCss = columnColorMode === 'theme' ? '' - : columnColors.map((hex, i) => `td[data-col="${i}"], th[data-col="${i}"] { color: ${hex}; }`).join(''); + : columnTypes.map((type, i) => { + const light = this.getColumnColor(type, false, i, palette); + const dark = this.getColumnColor(type, true, i, palette); + return `body.vscode-light td[data-col="${i}"], body.vscode-light th[data-col="${i}"] { color: ${light} !important; }` + + `body.vscode-dark td[data-col="${i}"], body.vscode-dark th[data-col="${i}"] { color: ${dark} !important; }`; + }).join(''); let tableHtml = ``; if (headerFlag) { diff --git a/src/extension.ts b/src/extension.ts index abaff87..50ec42b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -104,10 +104,6 @@ export function activate(context: vscode.ExtensionContext) { } }); context.subscriptions.push(cfgListener); - // Recompute type colors when entering/leaving high contrast, including open chunks. - context.subscriptions.push(vscode.window.onDidChangeActiveColorTheme(() => { - CsvEditorProvider.editors.forEach(editor => editor.refresh()); - })); // Register the custom editor provider for CSV/TSV context.subscriptions.push( diff --git a/src/test/extension-theme.test.ts b/src/test/extension-theme.test.ts deleted file mode 100644 index f44573f..0000000 --- a/src/test/extension-theme.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import assert from 'assert'; -import Module from 'module'; -import { it } from 'node:test'; - -let themeChanged: () => void = () => {}; -const themeListener = { dispose() {} }; -const originalRequire = Module.prototype.require; -Module.prototype.require = function (id: string) { - if (id === 'vscode') { - return { - commands: { registerCommand: () => ({ dispose() {} }) }, - workspace: { onDidChangeConfiguration: () => ({ dispose() {} }) }, - window: { - onDidChangeActiveColorTheme: (listener: () => void) => { themeChanged = listener; return themeListener; }, - registerCustomEditorProvider: () => ({ dispose() {} }) - } - }; - } - return originalRequire.apply(this, arguments as any); -}; -import { activate } from '../extension'; -import { CsvEditorProvider } from '../CsvEditorProvider'; -Module.prototype.require = originalRequire; - -it('refreshes every open editor when the color theme changes and disposes the listener', () => { - const context = { subscriptions: [] as any[] }; - activate(context as any); - let refreshed = 0; - CsvEditorProvider.editors = [{ refresh: () => refreshed++ }, { refresh: () => refreshed++ }] as any; - themeChanged(); - assert.strictEqual(refreshed, 2); - assert.ok(context.subscriptions.includes(themeListener)); - CsvEditorProvider.editors = []; -}); diff --git a/src/test/high-contrast.test.ts b/src/test/high-contrast.test.ts index ef2f3f0..2b7f889 100644 --- a/src/test/high-contrast.test.ts +++ b/src/test/high-contrast.test.ts @@ -67,11 +67,17 @@ describe('High-contrast rendering', () => { const panel = { title: 'fixture', active: false, webview, onDidChangeViewState() {}, onDidDispose() {} }; const context = { extensionPath: process.cwd(), workspaceState: { get: (_key: string, fallback: any) => fallback } }; const uri = { fsPath: '/fixture.csv', toString: () => 'file:///fixture.csv' }; - await new CsvEditorProvider(context as any).resolveCustomTextEditor({ uri, version: 1, getText: () => 'Name\nhello' } as any, panel as any, {} as any); + await new CsvEditorProvider(context as any).resolveCustomTextEditor({ uri, version: 1, getText: () => 'Name,Empty\nhello,' } as any, panel as any, {} as any); const link = ``; assert.ok(webview.html.includes(link)); assert.ok(webview.html.indexOf(link) > webview.html.indexOf('')); assert.ok(fs.existsSync(path.join(process.cwd(), 'media', 'high-contrast.css'))); + assert.ok(webview.html.includes('body.vscode-light td[data-col="0"]')); + assert.ok(webview.html.includes('body.vscode-dark td[data-col="0"]')); + assert.ok(webview.html.includes('color: #444 !important')); + assert.ok(webview.html.includes('color: #BBB !important')); + const extensionSource = fs.readFileSync(path.join(process.cwd(), 'src', 'extension.ts'), 'utf8'); + assert.ok(!extensionSource.includes('onDidChangeActiveColorTheme'), 'theme changes must not force-reload an active cell draft'); }); it('scopes accessibility styles to high-contrast themes and covers interactive states with theme tokens', () => { From 1a3d74dd834ca1d78a5de1aea3b00e914d85fc85 Mon Sep 17 00:00:00 2001 From: Jona Date: Sat, 17 Oct 2026 00:16:21 -0400 Subject: [PATCH 3/4] Keep chrome colors coherent across live theme transitions --- media/high-contrast.css | 53 ++++++++++++++++--------------- src/test/high-contrast.test.ts | 5 +-- src/test/theme-transition.test.ts | 33 +++++++++++++++++++ 3 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 src/test/theme-transition.test.ts diff --git a/media/high-contrast.css b/media/high-contrast.css index 6622a86..b22ead2 100644 --- a/media/high-contrast.css +++ b/media/high-contrast.css @@ -1,117 +1,120 @@ /* VS Code updates these body classes and color variables on theme changes. - Override legacy inline table colors for high-contrast themes, including chunks. */ -body:is(.vscode-high-contrast, .vscode-high-contrast-light) { + Keep chrome coherent across every transition; override cell type colors only in high contrast. */ +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) { color: var(--vscode-editor-foreground); background-color: var(--vscode-editor-background); - --csv-contrast-border: var(--vscode-contrastBorder, var(--vscode-editor-foreground)); + --csv-contrast-border: var(--vscode-contrastBorder, var(--vscode-editorWidget-border, var(--vscode-panel-border, var(--vscode-editor-foreground)))); +} + +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) :is(td, th) { + border-color: var(--csv-contrast-border) !important; } body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th) { color: var(--vscode-editor-foreground) !important; - border-color: var(--csv-contrast-border) !important; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) th { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) th { background-color: var(--vscode-editor-background) !important; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).selected { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).selected { color: var(--vscode-editor-selectionForeground, var(--vscode-editor-foreground)) !important; background-color: var(--vscode-editor-selectionBackground, var(--vscode-editor-background)) !important; outline: 1px solid var(--vscode-focusBorder, var(--csv-contrast-border)); outline-offset: -1px; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).highlight { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).highlight { background-color: var(--vscode-editor-findMatchHighlightBackground, var(--vscode-editor-background)) !important; outline: 1px solid var(--vscode-editor-findMatchHighlightBorder, var(--csv-contrast-border)); outline-offset: -1px; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).active-match { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) :is(td, th).active-match { color: var(--vscode-editor-findMatchForeground, var(--vscode-editor-foreground)) !important; background-color: var(--vscode-editor-findMatchBackground, var(--vscode-editor-background)) !important; outline: 2px solid var(--vscode-editor-findMatchBorder, var(--vscode-focusBorder)); outline-offset: -2px; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) .csv-link { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) .csv-link { color: var(--vscode-textLink-foreground, var(--vscode-editor-foreground)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) .csv-link:hover { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) .csv-link:hover { color: var(--vscode-textLink-activeForeground, var(--vscode-textLink-foreground)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) .selected .csv-link { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) .selected .csv-link { color: inherit; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #contextMenu { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #contextMenu { color: var(--vscode-menu-foreground, var(--vscode-editor-foreground)); background-color: var(--vscode-menu-background, var(--vscode-editor-background)); border-color: var(--csv-contrast-border); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #contextMenu div { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #contextMenu div { border-color: var(--csv-contrast-border) !important; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #contextMenu div:hover { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #contextMenu div:hover { color: var(--vscode-menu-selectionForeground, var(--vscode-editor-foreground)); background-color: var(--vscode-menu-selectionBackground, var(--vscode-editor-background)); outline: 1px solid var(--vscode-menu-selectionBorder, var(--csv-contrast-border)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget { color: var(--vscode-editorWidget-foreground, var(--vscode-editor-foreground)); background-color: var(--vscode-editorWidget-background, var(--vscode-editor-background)); border-color: var(--csv-contrast-border); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(.fr-gutter, .fr-overflow-menu) { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(.fr-gutter, .fr-overflow-menu) { background-color: var(--vscode-editorWidget-background, var(--vscode-editor-background)); border-color: var(--csv-contrast-border); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-input { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-input { color: var(--vscode-input-foreground, var(--vscode-editor-foreground)); background-color: var(--vscode-input-background, var(--vscode-editor-background)); border-color: var(--vscode-input-border, var(--csv-contrast-border)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-input::placeholder { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vscode-editor-foreground)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(.fr-status, button) { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(.fr-status, button) { color: var(--vscode-editorWidget-foreground, var(--vscode-editor-foreground)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget button:hover { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget button:hover { color: var(--vscode-toolbar-hoverForeground, var(--vscode-editor-foreground)); background-color: var(--vscode-toolbar-hoverBackground, var(--vscode-editor-background)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-toggle-btn[aria-pressed="true"] { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-toggle-btn[aria-pressed="true"] { color: var(--vscode-inputOption-activeForeground, var(--vscode-editor-foreground)); background-color: var(--vscode-inputOption-activeBackground, var(--vscode-editor-background)); box-shadow: inset 0 -2px 0 var(--vscode-inputOption-activeBorder, var(--csv-contrast-border)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget button[disabled] { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget button[disabled] { color: var(--vscode-disabledForeground, var(--vscode-editor-foreground)); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(button, input):focus-visible { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget :is(button, input):focus-visible { outline: 2px solid var(--vscode-focusBorder, var(--csv-contrast-border)); outline-offset: -2px; } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-divider { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-divider { background-color: var(--csv-contrast-border); } -body:is(.vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-inline-toggles { +body:is(.vscode-light, .vscode-dark, .vscode-high-contrast, .vscode-high-contrast-light) #findReplaceWidget .fr-inline-toggles { border-color: var(--csv-contrast-border); } diff --git a/src/test/high-contrast.test.ts b/src/test/high-contrast.test.ts index 2b7f889..89883da 100644 --- a/src/test/high-contrast.test.ts +++ b/src/test/high-contrast.test.ts @@ -80,14 +80,15 @@ describe('High-contrast rendering', () => { assert.ok(!extensionSource.includes('onDidChangeActiveColorTheme'), 'theme changes must not force-reload an active cell draft'); }); - it('scopes accessibility styles to high-contrast themes and covers interactive states with theme tokens', () => { + it('scopes theme styles and covers interactive states with theme tokens', () => { const css = fs.readFileSync(path.join(process.cwd(), 'media', 'high-contrast.css'), 'utf8'); const rules = css.replace(/\/\*[\s\S]*?\*\//g, '').split('}').filter(rule => rule.trim()).map(rule => { const [selector, declarations] = rule.split('{'); return { selector: selector.trim(), declarations: declarations.trim() }; }); for (const rule of rules) { - assert.ok(rule.selector.startsWith('body:is(.vscode-high-contrast, .vscode-high-contrast-light)')); + assert.ok(rule.selector.startsWith('body:is(')); + assert.ok(rule.selector.includes('.vscode-high-contrast-light')); assert.ok(!/#[0-9a-f]{3,8}\b/i.test(rule.declarations), 'high-contrast colors must come from the active theme'); } const expectedStates = new Map([ diff --git a/src/test/theme-transition.test.ts b/src/test/theme-transition.test.ts new file mode 100644 index 0000000..d0f28d9 --- /dev/null +++ b/src/test/theme-transition.test.ts @@ -0,0 +1,33 @@ +import assert from 'assert'; +import fs from 'fs'; +import path from 'path'; +import { it } from 'node:test'; +const { JSDOM } = require('jsdom'); + +it('updates dark/light chrome after leaving either high-contrast variant without replacing an active multiline draft', () => { + const css = fs.readFileSync(path.join(process.cwd(), 'media', 'high-contrast.css'), 'utf8'); + for (const [initial, ordinary] of [['vscode-high-contrast', 'vscode-light'], ['vscode-high-contrast-light', 'vscode-dark']]) { + const dom = new JSDOM(`
Header
line one\nline two
`); + const { document } = dom.window; + const cell = document.querySelector('td'); + cell.focus(); + const range = document.createRange(); + range.setStart(cell.firstChild, 5); + range.collapse(true); + dom.window.getSelection().removeAllRanges(); + dom.window.getSelection().addRange(range); + document.body.className = ordinary; + const rules = Array.from(document.styleSheets[0].cssRules) as any[]; + const declarationsFor = (element: any, property: string) => rules.filter(rule => element.matches(rule.selectorText)).map(rule => rule.style.getPropertyValue(property)).filter(Boolean); + for (const [element, token] of [[document.body, '--vscode-editor-background'], [document.querySelector('th'), '--vscode-editor-background'], [document.querySelector('#contextMenu'), '--vscode-menu-background'], [document.querySelector('.fr-input'), '--vscode-input-background']] as any[]) { + assert.ok(declarationsFor(element, 'background-color').some(value => value.includes(token)), `${ordinary} ${element.tagName} background follows live theme`); + } + assert.strictEqual(document.querySelector('td'), cell); + assert.strictEqual(cell.textContent, 'line one\nline two'); + assert.strictEqual(document.activeElement, cell); + assert.strictEqual(dom.window.getSelection().anchorNode, cell.firstChild); + assert.strictEqual(dom.window.getSelection().anchorOffset, 5); + assert.ok(!declarationsFor(cell, 'color').some(value => value.includes('--vscode-editor-foreground')), 'ordinary cells retain their type palette'); + dom.window.close(); + } +}); From 66c7d53f47292029f1544f20d7162ef7dc554c94 Mon Sep 17 00:00:00 2001 From: Jona Date: Sat, 17 Oct 2026 09:50:08 -0400 Subject: [PATCH 4/4] Preserve datatype palettes and limit accessibility styles to high contrast (#92) --- README.md | 2 +- images/high-contrast-narrow.jpg | Bin 0 -> 92277 bytes media/high-contrast.css | 64 ++++--- src/CsvEditorProvider.ts | 96 ++++++---- src/test/high-contrast.test.ts | 284 +++++++++++++++++++++++------- src/test/theme-transition.test.ts | 139 ++++++++++++--- 6 files changed, 426 insertions(+), 159 deletions(-) create mode 100644 images/high-contrast-narrow.jpg diff --git a/README.md b/README.md index 425baa0..94faab5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Working with CSV files shouldn’t be a chore. With CSV, you get: - **Clipboard Actions:** Use Ctrl/Cmd + C or X, or right-click a selection and choose Copy or Cut, to copy properly quoted data to the system clipboard. Right-click Paste inserts clipboard data into the grid. Ordinary cell text is copied without surrounding quotes. CSV escaping still preserves separators, line breaks, literal quotes, surrounding whitespace, and empty cells. Clipboard shortcuts in Find, Replace, and cell text edits keep their normal text-editing behavior. - **Grid Paste Support:** Paste copied ranges directly into the selected cell/range with `Ctrl/Cmd + V` in selection mode. - **Robust Data Handling:** Leveraging the power of [Papa Parse](https://www.papaparse.com/), the extension handles complex CSV structures, special characters, and various data types gracefully. -- **Theme-Optimized Interface:** CSV adapts to light, dark, and high-contrast themes. High-contrast themes use your theme’s foreground colors for cells, selection, Find/Replace, and menus automatically. +- **Theme-Optimized Interface:** CSV adapts to light, dark, and high-contrast themes. High-contrast themes add clear borders, selection and Find outlines, and theme-aware menus and Find/Replace controls. Cell colors keep the selected datatype palette or explicit theme-foreground mode in every theme. --- diff --git a/images/high-contrast-narrow.jpg b/images/high-contrast-narrow.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d71b1fc4b2e48036332c93a0638edfce85746f0 GIT binary patch literal 92277 zcmc$_cT`i|w>BE2N=G^bMCm0+??pszW~QcQ7U5#!68V4Kes%zus4k6> z14v1(11>R;~}sGV&5B$*%_Z-N-3O$tW+8P+k7@^Ib*&2`T9%5;B_0G*ndN zWF+MO)t!u-iQ>vNW@)W-N)}eO>lW5Fw&O%0nR~AAh>W5Mc7FL=+JUKQ1)Y-|vI>^< zuNzhcto#Ea|3TGJ$=ydp2`U!8)rH>fIz0NNfb+jp|7yRL|2mWWmx5nE-C+9VLDEZP zWaQ+(JCIyrxGy&f?1u5BnZ`D$fen<`82#TvqKtxs|5*k_e(-N} z@@_O#nX7Os%0^eYs&R&j4K*t}b2TX_YzWd7Cw5HR$1zMk&Qpz1qM_ zhD4@*;w!t}YOl%-kqkPUb`tkKRXTSc>s}~EMt>TiFapfOktleN_na!r9ZB47Xmnt8 zc75FhE;0r-DsI<@5||5jOpJ;&<9*^IqP!-Vq&J)W!N#Sm2D#(TJh`w0kBla^nhAKH zb#cQ}KmGV_U>n5mb)sU4Bs?n2!=31Q00j|Iy3@)V!(HlIQAjoasIePB1+ z@j>y#al0rt?E~fIRk`ShWhz<$IXHJ8AT=T*Ccp0S)I8)2kN@D!;#lSdx7Odx%yI%B zHObYL`SDRIxlxIT)I3i4Y{HgV51ZdrZI!Qgs&JG`ZODm4tjc!sQtI(4+hRa8Mdf0j zNAf3SAxlr+Sm`KLlp@IFs?$@s6;d6j|9;-FzQSSg# zIv1>$3sSY7A!_ZYCC>Cqgd14ZszM*p3k&-WFpTcCC04-X zO5{g20{?O14%>(5a67?Q-axa6+~2oUn~xgH)END{A$K7`Lgj@mQ!OSo=F^tJCg@E| zJ>`Hg4YLpd0TM!4GQ&h-LvHl$>ZEOgQPq6h0%mtf2c#qY#6cG91SJ{b6d;XC1oFB7 zlh`evLSwDLg2naqF{|Lc+aSM1P8+_#P
5INip;b#AE8F1`q-PD;p(Y%j; z$P?yp54S6ha4w5TYo1CD78}Br!Jv+rO8b3ul%2i$*0~lX)nJ6pXc;DJZeO}B727Zu zlHPi0DM!X1s03A~;!AZ><%UfJeT>(JHZ8o6Lk0|?krKEOm*m%-tCn?=QKT49DSJq^7|ddnUU4Ploj|mFS{EeEiKk0C+AmD`!$@PK zWP*Kslmd^{(@6AHtywG96lfVSg`nnc5Tk-L-&j_k7`+V?hukDK;o=k-%0A@dNL4B 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 q$yW8hG}D!TT3aeMQ${safe}`; + cells += `${safe}`; } const idxCell = state.addSerialIndex - ? `${displayIdx}` + ? `${displayIdx}` : ''; return `${idxCell}${cells}`; }).join(''); @@ -812,9 +812,9 @@ class CsvEditorController { const virtualAbs = state.startAbs + state.allRowsCount; const displayIdx = state.allRowsCount + 1; const idxCell = state.addSerialIndex - ? `${displayIdx}` + ? `${displayIdx}` : ''; - const dataCells = Array.from({ length: state.numColumns }, (_, i) => ``).join(''); + const dataCells = Array.from({ length: state.numColumns }, (_, i) => ``).join(''); return { html: `${idxCell}${dataCells}`, nextStart: -1, done: true }; } @@ -1501,14 +1501,13 @@ class CsvEditorController { const columnData = Array.from({ length: numColumns }, (_, i) => bodyData.map(row => row[i] || '')); const columnTypes = columnData.map(col => this.estimateColumnDataType(col)); - const highContrast = themeKind === vscode.ColorThemeKind.HighContrast || themeKind === vscode.ColorThemeKind.HighContrastLight; - const useThemeForeground = columnColorMode === 'theme' || highContrast; + const useThemeForeground = columnColorMode === 'theme'; const palette = columnColorPalette === 'cool' ? 'cool' : (columnColorPalette === 'warm' ? 'warm' : 'default'); const columnColors = useThemeForeground ? Array.from({ length: numColumns }, () => 'var(--vscode-editor-foreground)') - : columnTypes.map((type, i) => this.getColumnColor(type, isDark, i, palette)); + : columnTypes.map((type, i) => `var(--csv-column-${i}, ${this.getColumnColor(type, isDark, i, palette)})`); const columnWidths = this.computeColumnWidths(visibleForWidth); const BASE_CHUNK_ROWS = 1000; @@ -1548,11 +1547,11 @@ class CsvEditorController { const rawValue = row[cIdx] || ''; const safe = this.formatCellContent(rawValue, clickableLinks); const titleAttr = this.getMultilineCellTitleAttr(rawValue); - cells += `${safe}`; + cells += `${safe}`; } return `${ - addSerialIndex ? `${displayIdx}` : '' + addSerialIndex ? `${displayIdx}` : '' }${cells}`; }).join(''); @@ -1561,48 +1560,44 @@ class CsvEditorController { } } - // VS Code changes these classes in place. Keep both palettes available so - // entering/leaving high contrast never needs to reload a live cell edit. - const colorCss = columnColorMode === 'theme' + // Body classes change in place; inherited variables update every mounted or + // later-loaded cell without overriding the user's mode or reloading a draft. + const colorCss = useThemeForeground ? '' - : columnTypes.map((type, i) => { - const light = this.getColumnColor(type, false, i, palette); - const dark = this.getColumnColor(type, true, i, palette); - return `body.vscode-light td[data-col="${i}"], body.vscode-light th[data-col="${i}"] { color: ${light} !important; }` + - `body.vscode-dark td[data-col="${i}"], body.vscode-dark th[data-col="${i}"] { color: ${dark} !important; }`; - }).join(''); + : `body:is(.vscode-light, .vscode-high-contrast-light) { ${columnTypes.map((type, i) => `--csv-column-${i}: ${this.getColumnColor(type, false, i, palette)};`).join('')} }` + + `body:is(.vscode-dark, .vscode-high-contrast) { ${columnTypes.map((type, i) => `--csv-column-${i}: ${this.getColumnColor(type, true, i, palette)};`).join('')} }`; let tableHtml = ``; if (headerFlag) { tableHtml += `${ addSerialIndex - ? `` + ? `` : '' }`; for (let i = 0; i < numColumns; i++) { const safe = this.formatCellContent(headerRow[i] || '', clickableLinks); - tableHtml += ``; + tableHtml += ``; } tableHtml += ``; const initialBodyRows = chunked ? allRows.slice(0, chunkRows) : allRows; initialBodyRows.forEach((row, r) => { tableHtml += `${ addSerialIndex - ? `` + ? `` : '' }`; for (let i = 0; i < numColumns; i++) { const rawValue = row[i] || ''; const safe = this.formatCellContent(rawValue, clickableLinks); const titleAttr = this.getMultilineCellTitleAttr(rawValue); - tableHtml += ``; + tableHtml += ``; } tableHtml += ``; }); if (!chunked && includeTrailingEmptyRow) { const virtualAbs = offset + 1 + initialBodyRows.length; - const idxCell = addSerialIndex ? `` : ''; - const dataCells = Array.from({ length: numColumns }, (_, i) => ``).join(''); + const idxCell = addSerialIndex ? `` : ''; + const dataCells = Array.from({ length: numColumns }, (_, i) => ``).join(''); tableHtml += `${idxCell}${dataCells}`; } tableHtml += ``; @@ -1612,22 +1607,22 @@ class CsvEditorController { nonHeaderRows.forEach((row, r) => { tableHtml += `${ addSerialIndex - ? `` + ? `` : '' }`; for (let i = 0; i < numColumns; i++) { const rawValue = row[i] || ''; const safe = this.formatCellContent(rawValue, clickableLinks); const titleAttr = this.getMultilineCellTitleAttr(rawValue); - tableHtml += ``; + tableHtml += ``; } tableHtml += ``; }); if (!chunked && includeTrailingEmptyRow) { const virtualAbs = offset + nonHeaderRows.length; const displayIdx = nonHeaderRows.length + 1; - const idxCell = addSerialIndex ? `` : ''; - const dataCells = Array.from({ length: numColumns }, (_, i) => ``).join(''); + const idxCell = addSerialIndex ? `` : ''; + const dataCells = Array.from({ length: numColumns }, (_, i) => ``).join(''); tableHtml += `${idxCell}${dataCells}`; } tableHtml += ``; @@ -1639,8 +1634,8 @@ class CsvEditorController { const startAbs = headerFlag ? offset + 1 : offset; const virtualAbs = startAbs + allRowsCount; const displayIdx = allRowsCount + 1; - const idxCell = addSerialIndex ? `` : ''; - const dataCells = Array.from({ length: numColumns }, (_, i) => ``).join(''); + const idxCell = addSerialIndex ? `` : ''; + const dataCells = Array.from({ length: numColumns }, (_, i) => ``).join(''); const vrow = `${idxCell}${dataCells}`; chunks.push(vrow); } else if (nextChunkStart === -1) { @@ -1744,18 +1739,41 @@ class CsvEditorController { CSV')); + assert.ok(view.html.includes(link)); + assert.ok(view.html.indexOf(link) > view.html.indexOf('')); assert.ok(fs.existsSync(path.join(process.cwd(), 'media', 'high-contrast.css'))); - assert.ok(webview.html.includes('body.vscode-light td[data-col="0"]')); - assert.ok(webview.html.includes('body.vscode-dark td[data-col="0"]')); - assert.ok(webview.html.includes('color: #444 !important')); - assert.ok(webview.html.includes('color: #BBB !important')); const extensionSource = fs.readFileSync(path.join(process.cwd(), 'src', 'extension.ts'), 'utf8'); - assert.ok(!extensionSource.includes('onDidChangeActiveColorTheme'), 'theme changes must not force-reload an active cell draft'); + assert.ok(!extensionSource.includes('onDidChangeActiveColorTheme'), 'theme changes must not force-reload an active draft'); + view.dom.window.close(); }); - it('scopes theme styles and covers interactive states with theme tokens', () => { - const css = fs.readFileSync(path.join(process.cwd(), 'media', 'high-contrast.css'), 'utf8'); - const rules = css.replace(/\/\*[\s\S]*?\*\//g, '').split('}').filter(rule => rule.trim()).map(rule => { - const [selector, declarations] = rule.split('{'); - return { selector: selector.trim(), declarations: declarations.trim() }; - }); - for (const rule of rules) { - assert.ok(rule.selector.startsWith('body:is(')); - assert.ok(rule.selector.includes('.vscode-high-contrast-light')); - assert.ok(!/#[0-9a-f]{3,8}\b/i.test(rule.declarations), 'high-contrast colors must come from the active theme'); + it('leaves ordinary parent112 chrome colors unchanged and scopes accessibility overrides to HC', async () => { + const view = await render(); + const document = view.dom.window.document; + const th = document.querySelector('th[data-col="0"]'); + const cell = document.querySelector('td[data-col="0"]'); + const menu = document.querySelector('#contextMenu'); + const link = document.createElement('a'); + link.className = 'csv-link'; + cell.append(link); + const cases = [ + { name: 'vscode-light', header: '#ffffff', border: '#ccc', selected: '#cce0ff', highlight: '#fefefe', active: '#ffffcc', link: '#0066cc', menu: '#ffffff' }, + { name: 'vscode-dark', header: '#1e1e1e', border: '#555', selected: '#333333', highlight: '#2a2a2a', active: '#444444', link: '#6cb6ff', menu: '#2d2d2d' } + ]; + const externalRules = Array.from(document.styleSheets[document.styleSheets.length - 1].cssRules) as any[]; + const elements = [document.body, th, cell, link, menu, ...Array.from(document.querySelectorAll('#findReplaceWidget, #findReplaceWidget *'))]; + for (const expected of cases) { + document.body.className = expected.name; + for (const state of ['', 'selected', 'highlight', 'active-match']) { + cell.className = state; + for (const element of elements) { + assert.ok(!externalRules.some(rule => element.matches(rule.selectorText)), `${expected.name} ${element.tagName}.${state} must not match accessibility CSS`); + } + } + cell.className = ''; + assert.strictEqual(resolved(th, declared(th, 'background-color')), expected.header); + assert.ok(resolved(cell, declared(cell, 'border')).endsWith(expected.border)); + assert.strictEqual(color(link), expected.link); + assert.strictEqual(resolved(menu, declared(menu, 'background')), expected.menu); + for (const [state, background] of [['selected', expected.selected], ['highlight', expected.highlight], ['active-match', expected.active]]) { + cell.className = state; + assert.strictEqual(resolved(cell, declared(cell, 'background-color')), background); + } + assert.strictEqual(declared(document.querySelector('#findReplaceWidget'), 'background'), '#171717'); + assert.strictEqual(declared(document.querySelector('.fr-input'), 'background'), '#1c1c1c'); } - const expectedStates = new Map([ - ['.selected', ['selectionForeground', 'selectionBackground', 'outline']], - ['.active-match', ['findMatchForeground', 'findMatchBackground', 'outline']], - ['.fr-input {', ['input-foreground', 'input-background']], - ['#contextMenu {', ['menu-foreground', 'menu-background']], - [':focus-visible', ['focusBorder', 'outline']] - ]); - for (const [state, tokens] of expectedStates) { - const rule = rules.find(item => `${item.selector} {`.includes(state)); - assert.ok(rule, state); - for (const token of tokens) { assert.ok(rule!.declarations.includes(token), `${state}: ${token}`); } + for (const kind of ['vscode-high-contrast', 'vscode-high-contrast-light']) { + document.body.className = kind; + for (const target of [th, cell]) { + for (const state of ['', 'selected', 'highlight', 'active-match']) { + target.className = state; + const matching = externalRules.filter(rule => target.matches(rule.selectorText)); + assert.ok(matching.length > 0); + assert.ok(matching.every(rule => !rule.style.getPropertyValue('color')), `${kind} ${target.tagName}.${state} retains its configured foreground`); + if (state) { + assert.ok(matching.some(rule => rule.style.getPropertyValue('outline')), `${state} remains visibly distinguishable`); + assert.strictEqual(resolved(target, declared(target, 'background-color')), '#102030'); + } + } + } } + view.dom.window.close(); }); }); diff --git a/src/test/theme-transition.test.ts b/src/test/theme-transition.test.ts index d0f28d9..9bc1787 100644 --- a/src/test/theme-transition.test.ts +++ b/src/test/theme-transition.test.ts @@ -1,33 +1,124 @@ import assert from 'assert'; import fs from 'fs'; import path from 'path'; +import Module from 'module'; import { it } from 'node:test'; const { JSDOM } = require('jsdom'); -it('updates dark/light chrome after leaving either high-contrast variant without replacing an active multiline draft', () => { - const css = fs.readFileSync(path.join(process.cwd(), 'media', 'high-contrast.css'), 'utf8'); - for (const [initial, ordinary] of [['vscode-high-contrast', 'vscode-light'], ['vscode-high-contrast-light', 'vscode-dark']]) { - const dom = new JSDOM(`
${safe}${safe}
${r + 1}${r + 1}${safe}${safe}
${initialBodyRows.length + 1}${initialBodyRows.length + 1}
${r + 1}${r + 1}${safe}${safe}
${displayIdx}${displayIdx}
${displayIdx}${displayIdx}
Header
line one\nline two
`); - const { document } = dom.window; - const cell = document.querySelector('td'); - cell.focus(); - const range = document.createRange(); - range.setStart(cell.firstChild, 5); - range.collapse(true); - dom.window.getSelection().removeAllRanges(); - dom.window.getSelection().addRange(range); - document.body.className = ordinary; - const rules = Array.from(document.styleSheets[0].cssRules) as any[]; - const declarationsFor = (element: any, property: string) => rules.filter(rule => element.matches(rule.selectorText)).map(rule => rule.style.getPropertyValue(property)).filter(Boolean); - for (const [element, token] of [[document.body, '--vscode-editor-background'], [document.querySelector('th'), '--vscode-editor-background'], [document.querySelector('#contextMenu'), '--vscode-menu-background'], [document.querySelector('.fr-input'), '--vscode-input-background']] as any[]) { - assert.ok(declarationsFor(element, 'background-color').some(value => value.includes(token)), `${ordinary} ${element.tagName} background follows live theme`); +const kinds = { Light: 1, Dark: 2, HighContrast: 3, HighContrastLight: 4 }; +const theme = { kind: kinds.HighContrast }; +const settings: Record = {}; +const originalRequire = Module.prototype.require; +Module.prototype.require = function (id: string) { + if (id === 'vscode') { + return { + ColorThemeKind: kinds, window: { activeColorTheme: theme }, + Uri: { file: (filePath: string) => ({ fsPath: filePath, toString: () => `test:${filePath}` }) }, + workspace: { + getConfiguration: () => ({ get: (key: string, fallback: unknown) => key === 'maxFileSizeMB' ? 0 : (settings[key] ?? fallback) }), + onDidChangeTextDocument: () => ({ dispose() {} }), onWillSaveTextDocument: () => ({ dispose() {} }) + } + }; + } + return originalRequire.apply(this, arguments as any); +}; +import { CsvEditorProvider } from '../CsvEditorProvider'; +Module.prototype.require = originalRequire; + +async function createLiveView(mode: string, palette: string) { + settings.columnColorMode = mode; + settings.columnColorPalette = palette; + const uri = { fsPath: '/draft.csv', toString: () => 'file:///draft.csv' }; + const values: Record = {}; + const context = { + extensionPath: process.cwd(), workspaceState: { + get: (key: string, fallback: unknown) => values[key] ?? fallback, + update: async (key: string, value: unknown) => { values[key] = value; } + } + }; + await CsvEditorProvider.setHeaderForUri(context as any, uri as any, true); + const webview = { html: '', options: {}, cspSource: 'test:', asWebviewUri: (resource: any) => resource.toString(), postMessage() {}, onDidReceiveMessage() {} }; + const panel = { title: 'draft.csv', active: true, webview, onDidChangeViewState() {}, onDidDispose() {} }; + const text = 'Name,Count\nalpha,12\nbeta,24'; + const document = { uri, version: 1, getText: () => text }; + await new CsvEditorProvider(context as any).resolveCustomTextEditor(document as any, panel as any, {} as any); + const dom = new JSDOM(webview.html, { runScripts: 'outside-only', pretendToBeVisual: true }); + const style = dom.window.document.createElement('style'); + style.textContent = fs.readFileSync(path.join(process.cwd(), 'media', 'high-contrast.css'), 'utf8'); + dom.window.document.head.append(style); + const messages: any[] = []; + let state = {}; + dom.window.acquireVsCodeApi = () => ({ postMessage: (message: unknown) => messages.push(message), getState: () => state, setState: (value: object) => { state = value; } }); + dom.window.HTMLElement.prototype.scrollIntoView = () => {}; + dom.window.IntersectionObserver = class { observe() {} }; + dom.window.eval(`${fs.readFileSync(path.join(process.cwd(), 'media', 'main.js'), 'utf8')}\nwindow.themeTestApi = { editCell, setSingleSelection };`); + return { dom, messages, document }; +} + +// Resolve only inherited custom-property values: jsdom leaves CSS var() colors +// unevaluated, while the declarations and real browser DOM remain executable. +function cellColor(cell: any): string { + const document = cell.ownerDocument; + const variables = new Map([['--vscode-editor-foreground', '#13579b']]); + for (const sheet of Array.from(document.styleSheets) as any[]) { + for (const rule of Array.from(sheet.cssRules) as any[]) { + if (rule.selectorText && document.body.matches(rule.selectorText)) { + for (let index = 0; index < rule.style.length; index++) { + const property = rule.style[index]; + if (property.startsWith('--')) { variables.set(property, rule.style.getPropertyValue(property)); } + } + } + } + } + return cell.style.color.replace(/var\((--[\w-]+)(?:,\s*([^()]*))?\)/g, (_: string, name: string, fallback = '') => variables.get(name) ?? fallback).trim().toLowerCase(); +} + +it('changes all four theme classes without replacing, committing or recoloring an active multiline draft outside the chosen mode', async () => { + for (const palette of ['default', 'cool', 'warm']) { + for (const mode of ['type', 'theme']) { + const { dom, messages, document: owner } = await createLiveView(mode, palette); + const { document } = dom.window; + const cell = document.querySelector('[data-row="1"][data-col="0"]'); + const header = document.querySelector('th[data-col="0"]'); + const append = document.querySelector('[data-row="3"][data-col="0"]'); + dom.window.themeTestApi.setSingleSelection(cell); + dom.window.themeTestApi.editCell(cell); + cell.textContent = 'unsaved first line\nsecond line'; + const selection = dom.window.getSelection(); + const range = document.createRange(); + range.setStart(cell.firstChild, 8); + range.collapse(true); + selection.removeAllRanges(); + selection.addRange(range); + assert.strictEqual(document.activeElement, cell); + const colors = new Map(); + messages.length = 0; + for (const themeClass of ['vscode-high-contrast', 'vscode-light', 'vscode-high-contrast-light', 'vscode-dark', 'vscode-light']) { + document.body.className = themeClass; + const visibleColor = cellColor(cell); + colors.set(themeClass, visibleColor); + assert.strictEqual(cellColor(header), visibleColor); + assert.strictEqual(cellColor(append), visibleColor); + assert.strictEqual(document.querySelector('[data-row="1"][data-col="0"]'), cell); + assert.strictEqual(cell.textContent, 'unsaved first line\nsecond line'); + assert.strictEqual(cell.getAttribute('contenteditable'), 'true'); + assert.strictEqual(document.activeElement, cell); + assert.strictEqual(selection.anchorNode, cell.firstChild); + assert.strictEqual(selection.anchorOffset, 8); + assert.ok(cell.classList.contains('editing')); + assert.ok(!messages.some(message => ['editCell', 'save', 'saveAs'].includes(message.type)), 'class changes do not commit or save a draft'); + assert.strictEqual(owner.getText(), 'Name,Count\nalpha,12\nbeta,24'); + assert.strictEqual(owner.version, 1); + } + assert.strictEqual(colors.get('vscode-high-contrast'), colors.get('vscode-dark')); + assert.strictEqual(colors.get('vscode-high-contrast-light'), colors.get('vscode-light')); + if (mode === 'type') { + assert.notStrictEqual(colors.get('vscode-light'), colors.get('vscode-dark')); + assert.ok(Array.from(colors.values()).every(value => /^#[0-9a-f]{6}$/.test(value))); + } else { + assert.ok(Array.from(colors.values()).every(value => value === '#13579b')); + } + dom.window.close(); } - assert.strictEqual(document.querySelector('td'), cell); - assert.strictEqual(cell.textContent, 'line one\nline two'); - assert.strictEqual(document.activeElement, cell); - assert.strictEqual(dom.window.getSelection().anchorNode, cell.firstChild); - assert.strictEqual(dom.window.getSelection().anchorOffset, 5); - assert.ok(!declarationsFor(cell, 'color').some(value => value.includes('--vscode-editor-foreground')), 'ordinary cells retain their type palette'); - dom.window.close(); } });