From e1f27710add0febf93009ea1efd9179acab468d2 Mon Sep 17 00:00:00 2001 From: Wiibleyde Date: Mon, 23 Feb 2026 11:53:10 +0100 Subject: [PATCH] feat: add custom decoration color setting for StreamGuard extension Signed-off-by: Wiibleyde --- CHANGELOG.md | 6 ++++ README.md | 4 ++- package.json | 5 ++++ src/config/workspace-config.ts | 3 +- src/constants.ts | 1 + src/extension.ts | 3 +- src/guard/decoration-provider.ts | 50 ++++++++++++++++++++++++-------- src/guard/guard-manager.ts | 6 ++-- src/types.ts | 4 +++ 9 files changed, 64 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e87c5d3..b8b6fbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to the **StreamGuard** extension will be documented in this The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [1.3.0] — 2026-02-23 + +### Added + +- **Custom decoration color** — new `streamGuard.decorationColor` setting lets you pick any CSS hex color (e.g. `#ff0000`) for the masking overlay. Defaults to the editor warning theme color when left empty. + ## [1.0.0] — 2026-02-22 ### Added diff --git a/README.md b/README.md index 8a7a98c..55f665e 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,8 @@ const port = 3000; { "streamGuard.enabled": true, "streamGuard.maskedFilePatterns": ["**/generated.*", "**/build/output.*"], - "streamGuard.maskedFolders": ["**/draft/**"] + "streamGuard.maskedFolders": ["**/draft/**"], + "streamGuard.decorationColor": "#ff0000" } ``` @@ -109,6 +110,7 @@ const port = 3000; | `streamGuard.enabled` | `boolean` | `false` | Toggle the extension globally | | `streamGuard.maskedFilePatterns` | `string[]` | `[]` | Glob patterns — files matching these have their entire content masked | | `streamGuard.maskedFolders` | `string[]` | `[]` | Glob patterns — files inside matching folders are fully masked | +| `streamGuard.decorationColor` | `string` | `""` | Custom color for the overlay (CSS hex, e.g. `"#ff0000"`). Empty = editor warning theme color | | `streamGuard.languageCommentPrefixes` | `object` | `{}` | Custom comment prefixes per language ID (e.g. `{ "lua": ["--"] }`) | --- diff --git a/package.json b/package.json index edaeafb..3c53a3e 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,11 @@ "default": [], "description": "Folder glob patterns to mask in explorer and editor" }, + "streamGuard.decorationColor": { + "type": "string", + "default": "", + "description": "Custom color for the StreamGuard decoration overlay (CSS hex, e.g. '#ff0000'). Leave empty to use the editor warning theme color." + }, "streamGuard.languageCommentPrefixes": { "type": "object", "default": {}, diff --git a/src/config/workspace-config.ts b/src/config/workspace-config.ts index 5c3db7b..5758885 100644 --- a/src/config/workspace-config.ts +++ b/src/config/workspace-config.ts @@ -12,6 +12,7 @@ export function readConfig(): StreamGuardConfig { const enabled = cfg.get(CONFIG_KEYS.ENABLED) ?? false; const maskedFilePatterns = cfg.get(CONFIG_KEYS.MASKED_FILE_PATTERNS) ?? []; const maskedFolders = cfg.get(CONFIG_KEYS.MASKED_FOLDERS) ?? []; + const decorationColor = cfg.get(CONFIG_KEYS.DECORATION_COLOR) ?? ""; // Apply user-defined language comment prefixes (if any) const customPrefixes = cfg.get>(LANGUAGE_CONFIG_KEY) ?? {}; @@ -19,5 +20,5 @@ export function readConfig(): StreamGuardConfig { applyCustomPrefixes(customPrefixes); } - return { enabled, maskedFilePatterns, maskedFolders }; + return { enabled, maskedFilePatterns, maskedFolders, decorationColor }; } diff --git a/src/constants.ts b/src/constants.ts index cf4e54e..0fac896 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -12,6 +12,7 @@ export const CONFIG_KEYS = { ENABLED: "streamGuard.enabled", MASKED_FILE_PATTERNS: "streamGuard.maskedFilePatterns", MASKED_FOLDERS: "streamGuard.maskedFolders", + DECORATION_COLOR: "streamGuard.decorationColor", } as const; /** diff --git a/src/extension.ts b/src/extension.ts index 64d2e09..49c13fb 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -30,7 +30,8 @@ export function activate(context: vscode.ExtensionContext): void { logInfo("StreamGuard activating."); // Eagerly create the decoration type so it's ready before any editor opens - initDecorations(); + const initialConfig = readConfig(); + initDecorations(initialConfig.decorationColor); // Pre-parse all currently open documents so the cache is warm for (const doc of vscode.workspace.textDocuments) { diff --git a/src/guard/decoration-provider.ts b/src/guard/decoration-provider.ts index 8642cc7..573d059 100644 --- a/src/guard/decoration-provider.ts +++ b/src/guard/decoration-provider.ts @@ -4,26 +4,52 @@ import type { DecorationContext } from "../types"; /** Single decoration type used for all guarded ranges (overlay style). */ let maskedDecorationType: vscode.TextEditorDecorationType | undefined; -function getDecorationType(): vscode.TextEditorDecorationType { - if (maskedDecorationType) { - return maskedDecorationType; +/** The color currently baked into the cached decoration type. */ +let currentColor = ""; + +/** + * Resolves the color to use for decorations. + * If the user supplied a custom hex color it is returned as-is; + * otherwise falls back to the `editorWarning.foreground` theme color. + */ +function resolveColor(customColor: string): string | vscode.ThemeColor { + if (customColor.length > 0) { + return customColor; } + return new vscode.ThemeColor("editorWarning.foreground"); +} - maskedDecorationType = vscode.window.createTextEditorDecorationType({ - backgroundColor: new vscode.ThemeColor("editorWarning.foreground"), - color: new vscode.ThemeColor("editorWarning.foreground"), +function createDecorationType(customColor: string): vscode.TextEditorDecorationType { + const color = resolveColor(customColor); + + return vscode.window.createTextEditorDecorationType({ + backgroundColor: color, + color: color, isWholeLine: true, - overviewRulerColor: new vscode.ThemeColor("editorWarning.foreground"), + overviewRulerColor: color, overviewRulerLane: vscode.OverviewRulerLane.Full, before: { contentText: "\u00A0\u00A0\u26A0\uFE0F\u00A0Stream\u00A0Guard\u00A0Active\u00A0\u00A0", color: new vscode.ThemeColor("editor.background"), - backgroundColor: new vscode.ThemeColor("editorWarning.foreground"), + backgroundColor: color, fontWeight: "bold", margin: "0 4px 0 0", }, }); +} + +function getDecorationType(customColor: string): vscode.TextEditorDecorationType { + if (maskedDecorationType && currentColor === customColor) { + return maskedDecorationType; + } + + // Color changed or first call — (re)create the decoration type + if (maskedDecorationType) { + maskedDecorationType.dispose(); + } + maskedDecorationType = createDecorationType(customColor); + currentColor = customColor; return maskedDecorationType; } @@ -31,16 +57,16 @@ function getDecorationType(): vscode.TextEditorDecorationType { * Eagerly creates the decoration type so it is ready before any editor opens. * Call once during extension activation. */ -export function initDecorations(): void { - getDecorationType(); +export function initDecorations(customColor = ""): void { + getDecorationType(customColor); } /** * Applies redaction decorations to the given editor for all masked ranges. */ export function applyDecorations(ctx: DecorationContext): void { - const { editor, maskedRanges } = ctx; - const decorationType = getDecorationType(); + const { editor, maskedRanges, decorationColor } = ctx; + const decorationType = getDecorationType(decorationColor ?? ""); const ranges = maskedRanges.map(({ startLine, endLine }) => { const start = editor.document.lineAt(startLine).range.start; diff --git a/src/guard/guard-manager.ts b/src/guard/guard-manager.ts index fad2738..f5e75aa 100644 --- a/src/guard/guard-manager.ts +++ b/src/guard/guard-manager.ts @@ -100,7 +100,7 @@ export function refreshEditor(editor: vscode.TextEditor): void { // Try the cache first for instant application const cached = rangeCache.get(cacheKey); if (cached && cached.length > 0) { - applyDecorations({ editor, maskedRanges: cached }); + applyDecorations({ editor, maskedRanges: cached, decorationColor: config.decorationColor }); return; } @@ -115,7 +115,7 @@ export function refreshEditor(editor: vscode.TextEditor): void { } const maskedRanges = [{ startLine: 0, endLine: lineCount - 1 }]; rangeCache.set(cacheKey, maskedRanges); - applyDecorations({ editor, maskedRanges }); + applyDecorations({ editor, maskedRanges, decorationColor: config.decorationColor }); logInfo(`masked entire file: ${filePath}`); return; } @@ -135,7 +135,7 @@ export function refreshEditor(editor: vscode.TextEditor): void { } rangeCache.set(cacheKey, maskedRanges); - applyDecorations({ editor, maskedRanges }); + applyDecorations({ editor, maskedRanges, decorationColor: config.decorationColor }); logInfo(`Applied ${maskedRanges.length} masked range(s) to ${filePath}`); } diff --git a/src/types.ts b/src/types.ts index afe63cd..75020cb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -7,6 +7,8 @@ export interface StreamGuardConfig { enabled: boolean; maskedFilePatterns: string[]; maskedFolders: string[]; + /** Custom decoration color (CSS hex string). Empty string means use default theme color. */ + decorationColor: string; } /** @@ -31,6 +33,8 @@ export interface ParseResult { export interface DecorationContext { editor: vscode.TextEditor; maskedRanges: MaskedRange[]; + /** Custom decoration color (CSS hex string). Empty or undefined means use default theme color. */ + decorationColor?: string; } /**