From 3d84784eb6d691efe5b7224c48dfc7ab390dd0e8 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Mon, 3 Nov 2025 08:07:58 -0600 Subject: [PATCH 01/12] feat: Add logging colors to terminal color definitions Signed-off-by: David Hettinger --- .../src/component/terminal/terminal-colors.ts | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/webview/src/component/terminal/terminal-colors.ts b/packages/webview/src/component/terminal/terminal-colors.ts index 11d50efc..64502d99 100644 --- a/packages/webview/src/component/terminal/terminal-colors.ts +++ b/packages/webview/src/component/terminal/terminal-colors.ts @@ -32,8 +32,52 @@ export const ansi256Colours = [ '\u001b[34;1m', // bright blue ]; +// ANSI colors for log levels +const LOG_LEVEL_COLORS: Record = { + DBG: '\u001b[36m', // cyan + DEBUG: '\u001b[36m', + INF: '\u001b[32m', // green + INFO: '\u001b[32m', + WRN: '\u001b[33m', // yellow + WARN: '\u001b[33m', + WARNING: '\u001b[33m', + ERR: '\u001b[31m', // red + ERROR: '\u001b[31m', + FATAL: '\u001b[31;1m', // bright red + TRACE: '\u001b[35m', // magenta +}; + // Function that takes the container name and ANSI colour and encapsulates the name in the colour, // making sure that we reset the colour back to white after the name. export function colourizedANSIContainerName(name: string, colour: string): string { return `${colour}${name}\u001b[0m`; } + +/** + * Colorizes log levels in brackets for better readability. + * Detects patterns like [timestamp LEVEL] or [LEVEL] anywhere in the line. + * + * Examples: + * - [23:10:06 INF] -> green + * - [DBG] -> cyan + * - [ERROR] -> red + * - 2025-10-29T23:10:10.688386132-05:00 [23:10:10 INF] -> green (with K8s timestamp) + */ +export function colorizeLogLevel(logLine: string): string { + // Pattern to match [timestamp? LEVEL] anywhere in the line (removed ^ anchor) + const logLevelPattern = /(\[(?:[0-9:]+\s+)?)(DBG|DEBUG|INF|INFO|WRN|WARN|WARNING|ERR|ERROR|FATAL|TRACE)(\])/i; + + const match = logLine.match(logLevelPattern); + if (match && match.index !== undefined) { + const beforeBracket = logLine.slice(0, match.index); + const prefix = match[1]; // [timestamp or [ + const level = match[2].toUpperCase(); + const suffix = match[3]; // ] + const rest = logLine.slice(match.index + match[0].length); + + const color = LOG_LEVEL_COLORS[level] || '\u001b[37m'; + return `${beforeBracket}${color}${prefix}${level}${suffix}\u001b[0m${rest}`; + } + + return logLine; +} From e86db6d254ed4a9650b1da7de3c34e27492ffae6 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Mon, 3 Nov 2025 08:10:16 -0600 Subject: [PATCH 02/12] feat: Add colorizeLogLevel to log processing Signed-off-by: David Hettinger --- packages/webview/src/component/pods/PodLogs.svelte | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/webview/src/component/pods/PodLogs.svelte b/packages/webview/src/component/pods/PodLogs.svelte index dd17d026..f9e4a198 100644 --- a/packages/webview/src/component/pods/PodLogs.svelte +++ b/packages/webview/src/component/pods/PodLogs.svelte @@ -8,7 +8,7 @@ import NoLogIcon from '/@/component/icons/NoLogIcon.svelte'; import type { Terminal } from '@xterm/xterm'; import TerminalWindow from '/@/component/terminal/TerminalWindow.svelte'; import { SvelteMap } from 'svelte/reactivity'; -import { ansi256Colours, colourizedANSIContainerName } from '/@/component/terminal/terminal-colors'; +import { ansi256Colours, colourizedANSIContainerName, colorizeLogLevel } from '/@/component/terminal/terminal-colors'; interface Props { object: V1Pod; @@ -54,13 +54,17 @@ onMount(async () => { // All lines are prefixed, except the last one if it's empty. const lines = data .split('\n') + .map(line => colorizeLogLevel(line)) .map((line, index, arr) => index < arr.length - 1 || line.length > 0 ? `${padding}${colouredName}|${line}` : line, ); callback(lines.join('\n')); } : (_name: string, data: string, callback: (data: string) => void): void => { - callback(data); + const lines = data + .split('\n') + .map(line => colorizeLogLevel(line)); + callback(lines.join('\n')); }; for (const containerName of object.spec?.containers.map(c => c.name) ?? []) { From 2dd6783f7c0fc208136d5eb170ffbdea95fa9e68 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Thu, 13 Nov 2025 08:55:16 -0600 Subject: [PATCH 03/12] Run P Signed-off-by: David Hettinger --- .../src/component/terminal/terminal-colors.ts | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/webview/src/component/terminal/terminal-colors.ts b/packages/webview/src/component/terminal/terminal-colors.ts index 64502d99..49acc78c 100644 --- a/packages/webview/src/component/terminal/terminal-colors.ts +++ b/packages/webview/src/component/terminal/terminal-colors.ts @@ -34,17 +34,17 @@ export const ansi256Colours = [ // ANSI colors for log levels const LOG_LEVEL_COLORS: Record = { - DBG: '\u001b[36m', // cyan - DEBUG: '\u001b[36m', - INF: '\u001b[32m', // green - INFO: '\u001b[32m', - WRN: '\u001b[33m', // yellow - WARN: '\u001b[33m', - WARNING: '\u001b[33m', - ERR: '\u001b[31m', // red - ERROR: '\u001b[31m', - FATAL: '\u001b[31;1m', // bright red - TRACE: '\u001b[35m', // magenta + DBG: '\u001b[36m', // cyan + DEBUG: '\u001b[36m', + INF: '\u001b[32m', // green + INFO: '\u001b[32m', + WRN: '\u001b[33m', // yellow + WARN: '\u001b[33m', + WARNING: '\u001b[33m', + ERR: '\u001b[31m', // red + ERROR: '\u001b[31m', + FATAL: '\u001b[31;1m', // bright red + TRACE: '\u001b[35m', // magenta }; // Function that takes the container name and ANSI colour and encapsulates the name in the colour, @@ -56,7 +56,7 @@ export function colourizedANSIContainerName(name: string, colour: string): strin /** * Colorizes log levels in brackets for better readability. * Detects patterns like [timestamp LEVEL] or [LEVEL] anywhere in the line. - * + * * Examples: * - [23:10:06 INF] -> green * - [DBG] -> cyan @@ -64,20 +64,20 @@ export function colourizedANSIContainerName(name: string, colour: string): strin * - 2025-10-29T23:10:10.688386132-05:00 [23:10:10 INF] -> green (with K8s timestamp) */ export function colorizeLogLevel(logLine: string): string { - // Pattern to match [timestamp? LEVEL] anywhere in the line (removed ^ anchor) - const logLevelPattern = /(\[(?:[0-9:]+\s+)?)(DBG|DEBUG|INF|INFO|WRN|WARN|WARNING|ERR|ERROR|FATAL|TRACE)(\])/i; + // Pattern to match [timestamp? LEVEL] anywhere in the line (removed ^ anchor) + const logLevelPattern = /(\[(?:[0-9:]+\s+)?)(DBG|DEBUG|INF|INFO|WRN|WARN|WARNING|ERR|ERROR|FATAL|TRACE)(\])/i; - const match = logLine.match(logLevelPattern); - if (match && match.index !== undefined) { - const beforeBracket = logLine.slice(0, match.index); - const prefix = match[1]; // [timestamp or [ - const level = match[2].toUpperCase(); - const suffix = match[3]; // ] - const rest = logLine.slice(match.index + match[0].length); + const match = logLine.match(logLevelPattern); + if (match && match.index !== undefined) { + const beforeBracket = logLine.slice(0, match.index); + const prefix = match[1]; // [timestamp or [ + const level = match[2].toUpperCase(); + const suffix = match[3]; // ] + const rest = logLine.slice(match.index + match[0].length); - const color = LOG_LEVEL_COLORS[level] || '\u001b[37m'; - return `${beforeBracket}${color}${prefix}${level}${suffix}\u001b[0m${rest}`; - } + const color = LOG_LEVEL_COLORS[level] || '\u001b[37m'; + return `${beforeBracket}${color}${prefix}${level}${suffix}\u001b[0m${rest}`; + } - return logLine; + return logLine; } From ceae4c72147a2139c7e0abb923294fac1c942aad Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Thu, 13 Nov 2025 08:57:07 -0600 Subject: [PATCH 04/12] Add unit tests for colorizeLogLevel function Signed-off-by: David Hettinger --- .../terminal/terminal-colors.spec.ts | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 packages/webview/src/component/terminal/terminal-colors.spec.ts diff --git a/packages/webview/src/component/terminal/terminal-colors.spec.ts b/packages/webview/src/component/terminal/terminal-colors.spec.ts new file mode 100644 index 00000000..7520a253 --- /dev/null +++ b/packages/webview/src/component/terminal/terminal-colors.spec.ts @@ -0,0 +1,181 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import { describe, expect, test } from 'vitest'; + +import { colorizeLogLevel } from './terminal-colors.js'; + +describe('colorizeLogLevel', () => { + test('should colorize INFO log level in cyan', () => { + const logLine = '[23:10:06 INF] Starting application'; + const result = colorizeLogLevel(logLine); + + // Should contain green ANSI code for INFO + expect(result).toContain('\u001b[32m'); + // Should contain reset code + expect(result).toContain('\u001b[0m'); + // Should preserve the rest of the message + expect(result).toContain('Starting application'); + }); + + test('should colorize DEBUG log level in cyan', () => { + const logLine = '[DBG] Debug message'; + const result = colorizeLogLevel(logLine); + + // Should contain cyan ANSI code for DEBUG + expect(result).toContain('\u001b[36m'); + expect(result).toContain('\u001b[0m'); + expect(result).toContain('Debug message'); + }); + + test('should colorize ERROR log level in red', () => { + const logLine = '[ERROR] Something went wrong'; + const result = colorizeLogLevel(logLine); + + // Should contain red ANSI code for ERROR + expect(result).toContain('\u001b[31m'); + expect(result).toContain('\u001b[0m'); + expect(result).toContain('Something went wrong'); + }); + + test('should colorize WARN log level in yellow', () => { + const logLine = '[12:34:56 WARN] Warning message'; + const result = colorizeLogLevel(logLine); + + // Should contain yellow ANSI code for WARN + expect(result).toContain('\u001b[33m'); + expect(result).toContain('\u001b[0m'); + expect(result).toContain('Warning message'); + }); + + test('should colorize FATAL log level in bright red', () => { + const logLine = '[FATAL] Critical error'; + const result = colorizeLogLevel(logLine); + + // Should contain bright red ANSI code for FATAL + expect(result).toContain('\u001b[31;1m'); + expect(result).toContain('\u001b[0m'); + expect(result).toContain('Critical error'); + }); + + test('should colorize TRACE log level in magenta', () => { + const logLine = '[TRACE] Trace information'; + const result = colorizeLogLevel(logLine); + + // Should contain magenta ANSI code for TRACE + expect(result).toContain('\u001b[35m'); + expect(result).toContain('\u001b[0m'); + expect(result).toContain('Trace information'); + }); + + test('should handle log line with Kubernetes timestamp prefix', () => { + const logLine = '2025-10-29T23:10:10.688386132-05:00 [23:10:10 INF] Server started'; + const result = colorizeLogLevel(logLine); + + // Should preserve the K8s timestamp + expect(result).toContain('2025-10-29T23:10:10.688386132-05:00'); + // Should colorize the INFO level + expect(result).toContain('\u001b[32m'); + expect(result).toContain('Server started'); + }); + + test('should handle case-insensitive log levels', () => { + const logLine1 = '[info] lowercase info'; + const result1 = colorizeLogLevel(logLine1); + expect(result1).toContain('\u001b[32m'); + + const logLine2 = '[Info] Mixed case info'; + const result2 = colorizeLogLevel(logLine2); + expect(result2).toContain('\u001b[32m'); + }); + + test('should handle abbreviated log levels', () => { + const testCases = [ + { input: '[INF] Info message', color: '\u001b[32m' }, + { input: '[WRN] Warning message', color: '\u001b[33m' }, + { input: '[ERR] Error message', color: '\u001b[31m' }, + ]; + + testCases.forEach(({ input, color }) => { + const result = colorizeLogLevel(input); + expect(result).toContain(color); + }); + }); + + test('should handle log level in middle of line', () => { + const logLine = 'prefix content [23:10:06 INFO] message after bracket'; + const result = colorizeLogLevel(logLine); + + // Should preserve prefix + expect(result).toContain('prefix content'); + // Should colorize INFO + expect(result).toContain('\u001b[32m'); + // Should preserve suffix + expect(result).toContain('message after bracket'); + }); + + test('should not modify line without log level', () => { + const logLine = 'This is a regular log line without level'; + const result = colorizeLogLevel(logLine); + + // Should return the line unchanged + expect(result).toBe(logLine); + }); + + test('should not modify line with text in brackets that is not a log level', () => { + const logLine = '[NOTLEVEL] This is not a log level'; + const result = colorizeLogLevel(logLine); + + // Should return the line unchanged + expect(result).toBe(logLine); + }); + + test('should handle full word log levels', () => { + const testCases = [ + { input: '[DEBUG] Debug message', color: '\u001b[36m' }, + { input: '[INFO] Info message', color: '\u001b[32m' }, + { input: '[WARNING] Warning message', color: '\u001b[33m' }, + { input: '[ERROR] Error message', color: '\u001b[31m' }, + ]; + + testCases.forEach(({ input, color }) => { + const result = colorizeLogLevel(input); + expect(result).toContain(color); + }); + }); + + test('should only colorize the first log level found', () => { + const logLine = '[INFO] Found another [ERROR] in message'; + const result = colorizeLogLevel(logLine); + + // Should colorize INFO (first match) + expect(result).toContain('\u001b[32m'); + // Count how many times we see the reset code - should only be once + const resetCount = (result.match(/\u001b\[0m/g) || []).length; + expect(resetCount).toBe(1); + }); + + test('should preserve exact format of brackets and timestamp', () => { + const logLine = '[23:10:06 INFO] Message'; + const result = colorizeLogLevel(logLine); + + // The timestamp and brackets should still be present + expect(result).toMatch(/23:10:06/); + expect(result).toMatch(/INFO/); + }); +}); From d01edf652cb962e017d7a16073125ed9b6603347 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Fri, 14 Nov 2025 11:31:36 -0600 Subject: [PATCH 05/12] Add more color tests and fix formatting/linting issues Signed-off-by: David Hettinger --- .../terminal/terminal-colors.spec.ts | 106 ++++++++++++------ 1 file changed, 71 insertions(+), 35 deletions(-) diff --git a/packages/webview/src/component/terminal/terminal-colors.spec.ts b/packages/webview/src/component/terminal/terminal-colors.spec.ts index 7520a253..b6ef6d62 100644 --- a/packages/webview/src/component/terminal/terminal-colors.spec.ts +++ b/packages/webview/src/component/terminal/terminal-colors.spec.ts @@ -18,15 +18,15 @@ import { describe, expect, test } from 'vitest'; -import { colorizeLogLevel } from './terminal-colors.js'; +import { colorizeJSON, colorizeLogLevel } from './terminal-colors.js'; describe('colorizeLogLevel', () => { test('should colorize INFO log level in cyan', () => { const logLine = '[23:10:06 INF] Starting application'; const result = colorizeLogLevel(logLine); - - // Should contain green ANSI code for INFO - expect(result).toContain('\u001b[32m'); + + // Should contain cyan ANSI code for INFO + expect(result).toContain('\u001b[36m'); // Should contain reset code expect(result).toContain('\u001b[0m'); // Should preserve the rest of the message @@ -36,9 +36,9 @@ describe('colorizeLogLevel', () => { test('should colorize DEBUG log level in cyan', () => { const logLine = '[DBG] Debug message'; const result = colorizeLogLevel(logLine); - - // Should contain cyan ANSI code for DEBUG - expect(result).toContain('\u001b[36m'); + + // Should contain green ANSI code for DEBUG + expect(result).toContain('\u001b[32m'); expect(result).toContain('\u001b[0m'); expect(result).toContain('Debug message'); }); @@ -46,9 +46,9 @@ describe('colorizeLogLevel', () => { test('should colorize ERROR log level in red', () => { const logLine = '[ERROR] Something went wrong'; const result = colorizeLogLevel(logLine); - - // Should contain red ANSI code for ERROR - expect(result).toContain('\u001b[31m'); + + // Should contain bright red ANSI code for ERROR + expect(result).toContain('\u001b[31'); // Match both \u001b[31m and \u001b[31;1m expect(result).toContain('\u001b[0m'); expect(result).toContain('Something went wrong'); }); @@ -56,7 +56,7 @@ describe('colorizeLogLevel', () => { test('should colorize WARN log level in yellow', () => { const logLine = '[12:34:56 WARN] Warning message'; const result = colorizeLogLevel(logLine); - + // Should contain yellow ANSI code for WARN expect(result).toContain('\u001b[33m'); expect(result).toContain('\u001b[0m'); @@ -66,9 +66,9 @@ describe('colorizeLogLevel', () => { test('should colorize FATAL log level in bright red', () => { const logLine = '[FATAL] Critical error'; const result = colorizeLogLevel(logLine); - - // Should contain bright red ANSI code for FATAL - expect(result).toContain('\u001b[31;1m'); + + // Should contain red ANSI code for FATAL + expect(result).toContain('\u001b[31m'); expect(result).toContain('\u001b[0m'); expect(result).toContain('Critical error'); }); @@ -76,9 +76,9 @@ describe('colorizeLogLevel', () => { test('should colorize TRACE log level in magenta', () => { const logLine = '[TRACE] Trace information'; const result = colorizeLogLevel(logLine); - - // Should contain magenta ANSI code for TRACE - expect(result).toContain('\u001b[35m'); + + // Should contain bright cyan ANSI code for TRACE + expect(result).toContain('\u001b[36;1m'); expect(result).toContain('\u001b[0m'); expect(result).toContain('Trace information'); }); @@ -86,29 +86,29 @@ describe('colorizeLogLevel', () => { test('should handle log line with Kubernetes timestamp prefix', () => { const logLine = '2025-10-29T23:10:10.688386132-05:00 [23:10:10 INF] Server started'; const result = colorizeLogLevel(logLine); - + // Should preserve the K8s timestamp expect(result).toContain('2025-10-29T23:10:10.688386132-05:00'); // Should colorize the INFO level - expect(result).toContain('\u001b[32m'); + expect(result).toContain('\u001b[36m'); expect(result).toContain('Server started'); }); test('should handle case-insensitive log levels', () => { const logLine1 = '[info] lowercase info'; const result1 = colorizeLogLevel(logLine1); - expect(result1).toContain('\u001b[32m'); - + expect(result1).toContain('\u001b[36m'); + const logLine2 = '[Info] Mixed case info'; const result2 = colorizeLogLevel(logLine2); - expect(result2).toContain('\u001b[32m'); + expect(result2).toContain('\u001b[36m'); }); test('should handle abbreviated log levels', () => { const testCases = [ - { input: '[INF] Info message', color: '\u001b[32m' }, + { input: '[INF] Info message', color: '\u001b[36m' }, { input: '[WRN] Warning message', color: '\u001b[33m' }, - { input: '[ERR] Error message', color: '\u001b[31m' }, + { input: '[ERR] Error message', color: '\u001b[31' }, // Match both formats ]; testCases.forEach(({ input, color }) => { @@ -120,11 +120,11 @@ describe('colorizeLogLevel', () => { test('should handle log level in middle of line', () => { const logLine = 'prefix content [23:10:06 INFO] message after bracket'; const result = colorizeLogLevel(logLine); - + // Should preserve prefix expect(result).toContain('prefix content'); // Should colorize INFO - expect(result).toContain('\u001b[32m'); + expect(result).toContain('\u001b[36m'); // Should preserve suffix expect(result).toContain('message after bracket'); }); @@ -132,7 +132,7 @@ describe('colorizeLogLevel', () => { test('should not modify line without log level', () => { const logLine = 'This is a regular log line without level'; const result = colorizeLogLevel(logLine); - + // Should return the line unchanged expect(result).toBe(logLine); }); @@ -140,17 +140,17 @@ describe('colorizeLogLevel', () => { test('should not modify line with text in brackets that is not a log level', () => { const logLine = '[NOTLEVEL] This is not a log level'; const result = colorizeLogLevel(logLine); - + // Should return the line unchanged expect(result).toBe(logLine); }); test('should handle full word log levels', () => { const testCases = [ - { input: '[DEBUG] Debug message', color: '\u001b[36m' }, - { input: '[INFO] Info message', color: '\u001b[32m' }, + { input: '[DEBUG] Debug message', color: '\u001b[32m' }, + { input: '[INFO] Info message', color: '\u001b[36m' }, { input: '[WARNING] Warning message', color: '\u001b[33m' }, - { input: '[ERROR] Error message', color: '\u001b[31m' }, + { input: '[ERROR] Error message', color: '\u001b[31' }, // Match both formats ]; testCases.forEach(({ input, color }) => { @@ -162,20 +162,56 @@ describe('colorizeLogLevel', () => { test('should only colorize the first log level found', () => { const logLine = '[INFO] Found another [ERROR] in message'; const result = colorizeLogLevel(logLine); - + // Should colorize INFO (first match) - expect(result).toContain('\u001b[32m'); + expect(result).toContain('\u001b[36m'); // Count how many times we see the reset code - should only be once - const resetCount = (result.match(/\u001b\[0m/g) || []).length; + // eslint-disable-next-line sonarjs/no-control-regex, no-control-regex + const resetCount = (result.match(/\u001b\[0m/g) ?? []).length; expect(resetCount).toBe(1); }); test('should preserve exact format of brackets and timestamp', () => { const logLine = '[23:10:06 INFO] Message'; const result = colorizeLogLevel(logLine); - + // The timestamp and brackets should still be present expect(result).toMatch(/23:10:06/); expect(result).toMatch(/INFO/); }); + + test('should colorize colon format log level', () => { + const logLine = 'info: mylog'; + const result = colorizeLogLevel(logLine); + + // Should contain cyan ANSI code for info + expect(result).toContain('\u001b[36m'); + expect(result).toContain('\u001b[0m'); + expect(result).toContain('mylog'); + }); + + test('should colorize JSON format with quoted level', () => { + const logLine = '{"timestamp":"123","level":"information","message":"test"}'; + const result = colorizeLogLevel(logLine); + + // Should contain cyan ANSI code for information + expect(result).toContain('\u001b[36m'); + expect(result).toContain('\u001b[0m'); + expect(result).toContain('"information"'); + }); + + test('should work with JSON colorization function', () => { + const jsonLine = '{"level":"info","message":"test"}'; + const result = colorizeJSON(jsonLine); + + // Should colorize braces in yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Strings should not be colorized + expect(result).toContain('"level"'); + expect(result).toContain('"info"'); + expect(result).toContain('"message"'); + expect(result).toContain('"test"'); + }); }); From f20587cf3515f236594bea8e81f85a279d31ed41 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Fri, 14 Nov 2025 11:32:08 -0600 Subject: [PATCH 06/12] Add JsonColorizer class for colorizing JSON strings This file contains a class for colorizing JSON strings with customizable ANSI color schemes for different JSON elements. Signed-off-by: David Hettinger --- .../src/component/terminal/json-colorizer.ts | 211 ++++++++++++++++++ 1 file changed, 211 insertions(+) create mode 100644 packages/webview/src/component/terminal/json-colorizer.ts diff --git a/packages/webview/src/component/terminal/json-colorizer.ts b/packages/webview/src/component/terminal/json-colorizer.ts new file mode 100644 index 00000000..541d9dc3 --- /dev/null +++ b/packages/webview/src/component/terminal/json-colorizer.ts @@ -0,0 +1,211 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +export interface JsonColorScheme { + keyColor?: string; // Color for object keys (optional) + stringColor?: string; // Color for string values (optional) + braceColor?: string; // Color for braces/brackets (optional) + numberColor?: string; // Color for numbers (optional) + booleanColor?: string; // Color for booleans (optional) + nullColor?: string; // Color for null (optional) + reset: string; // Reset code (required) +} + +interface ProcessResult { + text: string; + nextIndex: number; +} + +/** + * A class for colorizing JSON strings with ANSI escape codes. + * Supports customizable color schemes for keys, values, numbers, booleans, and null. + */ +export class JsonColorizer { + constructor(private readonly colorScheme: JsonColorScheme) {} + + /** + * Colorizes a JSON string with ANSI color codes. + * Processes the string character by character to avoid escape code conflicts. + * + * @param jsonLine - The JSON string to colorize + * @returns The colorized JSON string with ANSI codes + */ + colorize(jsonLine: string): string { + let result = ''; + let i = 0; + let isAfterColon = false; + + while (i < jsonLine.length) { + const char = jsonLine[i]; + + // Handle structural characters + const structuralResult = this.handleStructuralChar(char, isAfterColon); + if (structuralResult !== undefined) { + result += structuralResult.text; + isAfterColon = structuralResult.isAfterColon; + i++; + continue; + } + + // Handle quoted strings (keys or values) + if (char === '"') { + const stringResult = this.processString(jsonLine, i, isAfterColon); + result += stringResult.text; + i = stringResult.nextIndex; + continue; + } + + // Handle numbers + if (this.isNumberStart(jsonLine, i)) { + const numberResult = this.processNumber(jsonLine, i); + result += numberResult.text; + i = numberResult.nextIndex; + continue; + } + + // Handle keywords (boolean/null) + const keywordResult = this.processKeyword(jsonLine, i); + if (keywordResult) { + result += keywordResult.text; + i = keywordResult.nextIndex; + continue; + } + + // Regular character + result += char; + i++; + } + + return result; + } + + private handleStructuralChar( + char: string, + _isAfterColon: boolean, + ): { text: string; isAfterColon: boolean } | undefined { + // Handle braces and brackets + if (char === '{' || char === '}' || char === '[' || char === ']') { + const text = this.colorScheme.braceColor ? this.colorScheme.braceColor + char + this.colorScheme.reset : char; + return { text, isAfterColon: false }; + } + + // Track colon to distinguish keys from values + if (char === ':') { + return { text: char, isAfterColon: true }; + } + + // Track comma to reset key/value state + if (char === ',') { + return { text: char, isAfterColon: false }; + } + + return undefined; + } + + private processString(jsonLine: string, startIndex: number, isAfterColon: boolean): ProcessResult { + const stringEnd = this.findStringEnd(jsonLine, startIndex); + const stringContent = jsonLine.substring(startIndex, stringEnd); + + // Determine if this is a key (before colon) or value (after colon) + const isKey = !isAfterColon; + const colorToUse = isKey ? this.colorScheme.keyColor : this.colorScheme.stringColor; + + const text = colorToUse ? colorToUse + stringContent + this.colorScheme.reset : stringContent; + + return { + text, + nextIndex: stringEnd, + }; + } + + private findStringEnd(jsonLine: string, startIndex: number): number { + let i = startIndex + 1; // Skip opening quote + + // Find closing quote (handle escaped quotes) + while (i < jsonLine.length && (jsonLine[i] !== '"' || jsonLine[i - 1] === '\\')) { + i++; + } + i++; // Include closing quote + + return i; + } + + private isNumberStart(jsonLine: string, index: number): boolean { + const char = jsonLine[index]; + const isDigitOrMinus = /[\d-]/.test(char); + if (!isDigitOrMinus) return false; + + // Check if we're in a number context + for (let k = index - 1; k >= 0; k--) { + const prevChar = jsonLine[k]; + if (/\s/.test(prevChar)) continue; + return prevChar === ':' || prevChar === '[' || prevChar === ','; + } + return false; + } + + private processNumber(jsonLine: string, startIndex: number): ProcessResult { + let i = startIndex; + if (jsonLine[i] === '-') i++; + + while (i < jsonLine.length && /[\d.]/.test(jsonLine[i])) { + i++; + } + + const number = jsonLine.substring(startIndex, i); + const text = this.colorScheme.numberColor ? this.colorScheme.numberColor + number + this.colorScheme.reset : number; + return { + text, + nextIndex: i, + }; + } + + private processKeyword(jsonLine: string, startIndex: number): ProcessResult | undefined { + // Handle boolean values + if (jsonLine.substring(startIndex, startIndex + 4) === 'true') { + const text = this.colorScheme.booleanColor + ? this.colorScheme.booleanColor + 'true' + this.colorScheme.reset + : 'true'; + return { + text, + nextIndex: startIndex + 4, + }; + } + + if (jsonLine.substring(startIndex, startIndex + 5) === 'false') { + const text = this.colorScheme.booleanColor + ? this.colorScheme.booleanColor + 'false' + this.colorScheme.reset + : 'false'; + return { + text, + nextIndex: startIndex + 5, + }; + } + + // Handle null + if (jsonLine.substring(startIndex, startIndex + 4) === 'null') { + const text = this.colorScheme.nullColor ? this.colorScheme.nullColor + 'null' + this.colorScheme.reset : 'null'; + return { + text, + nextIndex: startIndex + 4, + }; + } + + return undefined; + } +} From 29c3be9cd52afb5ee626c46a346528198cdd026a Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Fri, 14 Nov 2025 11:32:31 -0600 Subject: [PATCH 07/12] Add tests for JsonColorizer colorization functionality Add unit tests for JsonColorizer to validate colorization of various JSON structures, including braces, brackets, numbers, booleans, null values, and strings with custom color schemes. Signed-off-by: David Hettinger --- .../component/terminal/json-colorizer.spec.ts | 285 ++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 packages/webview/src/component/terminal/json-colorizer.spec.ts diff --git a/packages/webview/src/component/terminal/json-colorizer.spec.ts b/packages/webview/src/component/terminal/json-colorizer.spec.ts new file mode 100644 index 00000000..04302b88 --- /dev/null +++ b/packages/webview/src/component/terminal/json-colorizer.spec.ts @@ -0,0 +1,285 @@ +/********************************************************************** + * Copyright (C) 2025 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ***********************************************************************/ + +import { describe, expect, test } from 'vitest'; + +import type { JsonColorScheme } from './json-colorizer.js'; +import { JsonColorizer } from './json-colorizer.js'; + +describe('JsonColorizer', () => { + const colorScheme: JsonColorScheme = { + braceColor: '\u001b[33m', // yellow + numberColor: '\u001b[32m', // green + booleanColor: '\u001b[35m', // magenta + nullColor: '\u001b[35m', // magenta + reset: '\u001b[0m', + }; + + const colorizer = new JsonColorizer(colorScheme); + + test('should colorize braces in yellow', () => { + const jsonLine = '{"name": "test"}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Strings should not be colorized + expect(result).toContain('"name"'); + expect(result).toContain('"test"'); + }); + + test('should colorize brackets in yellow', () => { + const jsonLine = '["item1", "item2"]'; + const result = colorizer.colorize(jsonLine); + + // Brackets should be yellow + expect(result).toContain('\u001b[33m[\u001b[0m'); + expect(result).toContain('\u001b[33m]\u001b[0m'); + }); + + test('should colorize JSON with number values', () => { + const jsonLine = '{"count": 42, "price": 19.99}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Numbers should be green + expect(result).toContain('\u001b[32m42\u001b[0m'); + expect(result).toContain('\u001b[32m19.99\u001b[0m'); + + // Keys should not be colorized + expect(result).toContain('"count"'); + expect(result).toContain('"price"'); + }); + + test('should colorize JSON with boolean values', () => { + const jsonLine = '{"enabled": true, "active": false}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Booleans should be magenta + expect(result).toContain('\u001b[35mtrue\u001b[0m'); + expect(result).toContain('\u001b[35mfalse\u001b[0m'); + }); + + test('should colorize JSON with null values', () => { + const jsonLine = '{"value": null}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // null should be magenta + expect(result).toContain('\u001b[35mnull\u001b[0m'); + }); + + test('should colorize JSON array with strings', () => { + const jsonLine = '["item1", "item2", "item3"]'; + const result = colorizer.colorize(jsonLine); + + // Brackets should be yellow + expect(result).toContain('\u001b[33m[\u001b[0m'); + expect(result).toContain('\u001b[33m]\u001b[0m'); + + // Strings should not be colorized + expect(result).toContain('"item1"'); + expect(result).toContain('"item2"'); + expect(result).toContain('"item3"'); + }); + + test('should colorize JSON array with numbers', () => { + const jsonLine = '[1, 2, 3, 4.5]'; + const result = colorizer.colorize(jsonLine); + + // Brackets should be yellow + expect(result).toContain('\u001b[33m[\u001b[0m'); + expect(result).toContain('\u001b[33m]\u001b[0m'); + + // Numbers in array should be green + expect(result).toContain('\u001b[32m1\u001b[0m'); + expect(result).toContain('\u001b[32m2\u001b[0m'); + expect(result).toContain('\u001b[32m3\u001b[0m'); + expect(result).toContain('\u001b[32m4.5\u001b[0m'); + }); + + test('should colorize JSON array with booleans', () => { + const jsonLine = '[true, false, true]'; + const result = colorizer.colorize(jsonLine); + + // Brackets should be yellow + expect(result).toContain('\u001b[33m[\u001b[0m'); + expect(result).toContain('\u001b[33m]\u001b[0m'); + + // Booleans in array should be magenta + expect(result).toContain('\u001b[35mtrue\u001b[0m'); + expect(result).toContain('\u001b[35mfalse\u001b[0m'); + }); + + test('should colorize complex nested JSON', () => { + const jsonLine = '{"user": {"name": "John", "age": 30, "active": true}}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow (4 total: outer { }, inner { }) + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Values should be appropriately colored + expect(result).toContain('\u001b[32m30\u001b[0m'); + expect(result).toContain('\u001b[35mtrue\u001b[0m'); + + // Strings should not be colorized + expect(result).toContain('"user"'); + expect(result).toContain('"name"'); + expect(result).toContain('"age"'); + expect(result).toContain('"active"'); + expect(result).toContain('"John"'); + }); + + test('should colorize JSON with negative numbers', () => { + const jsonLine = '{"temperature": -15, "balance": -99.99}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Negative numbers should be green + expect(result).toContain('\u001b[32m-15\u001b[0m'); + expect(result).toContain('\u001b[32m-99.99\u001b[0m'); + }); + + test('should colorize JSON log level format', () => { + const jsonLine = '{"timestamp":"123","level":"information","message":"test"}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Strings should not be colorized + expect(result).toContain('"timestamp"'); + expect(result).toContain('"level"'); + expect(result).toContain('"message"'); + expect(result).toContain('"123"'); + expect(result).toContain('"information"'); + expect(result).toContain('"test"'); + }); + + test('should handle empty JSON object', () => { + const jsonLine = '{}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + }); + + test('should handle empty JSON array', () => { + const jsonLine = '[]'; + const result = colorizer.colorize(jsonLine); + + // Brackets should be yellow + expect(result).toContain('\u001b[33m[\u001b[0m'); + expect(result).toContain('\u001b[33m]\u001b[0m'); + }); + + test('should colorize JSON with mixed types in array', () => { + const jsonLine = '["text", 123, true, null]'; + const result = colorizer.colorize(jsonLine); + + // Brackets should be yellow + expect(result).toContain('\u001b[33m[\u001b[0m'); + expect(result).toContain('\u001b[33m]\u001b[0m'); + + // Each type should be colorized appropriately + expect(result).toContain('"text"'); // strings not colorized + expect(result).toContain('\u001b[32m123\u001b[0m'); + expect(result).toContain('\u001b[35mtrue\u001b[0m'); + expect(result).toContain('\u001b[35mnull\u001b[0m'); + }); + + test('should handle JSON with escaped quotes', () => { + const jsonLine = '{"message": "He said \\"hello\\""}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Should handle escaped quotes properly - strings not colorized + expect(result).toContain('"message"'); + expect(result).toContain('"He said \\"hello\\""'); + }); + + test('should work with custom color scheme', () => { + const customScheme: JsonColorScheme = { + braceColor: '\u001b[31m', // red + numberColor: '\u001b[33m', // yellow + booleanColor: '\u001b[34m', // blue + nullColor: '\u001b[35m', // magenta + reset: '\u001b[0m', + }; + + const customColorizer = new JsonColorizer(customScheme); + const jsonLine = '{"key": "value", "num": 42, "flag": true, "empty": null}'; + const result = customColorizer.colorize(jsonLine); + + // Should use custom colors + expect(result).toContain('\u001b[31m{\u001b[0m'); // red braces + expect(result).toContain('\u001b[31m}\u001b[0m'); // red braces + expect(result).toContain('\u001b[33m42\u001b[0m'); // yellow number + expect(result).toContain('\u001b[34mtrue\u001b[0m'); // blue boolean + expect(result).toContain('\u001b[35mnull\u001b[0m'); // magenta null + + // Strings should not be colorized + expect(result).toContain('"key"'); + expect(result).toContain('"value"'); + }); + + test('should handle zero as a number', () => { + const jsonLine = '{"value": 0}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Zero should be green + expect(result).toContain('\u001b[32m0\u001b[0m'); + }); + + test('should handle decimal numbers starting with dot', () => { + const jsonLine = '{"value": 0.5}'; + const result = colorizer.colorize(jsonLine); + + // Braces should be yellow + expect(result).toContain('\u001b[33m{\u001b[0m'); + expect(result).toContain('\u001b[33m}\u001b[0m'); + + // Decimal should be green + expect(result).toContain('\u001b[32m0.5\u001b[0m'); + }); +}); From 57a758ab14942662dd4e72d46c98498eaf62433e Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Fri, 14 Nov 2025 11:33:12 -0600 Subject: [PATCH 08/12] Fix linting issues and add json colorizer Updated log level color mappings and enhanced the log level detection regex to support additional formats. Signed-off-by: David Hettinger --- .../src/component/terminal/terminal-colors.ts | 110 ++++++++++++++---- 1 file changed, 87 insertions(+), 23 deletions(-) diff --git a/packages/webview/src/component/terminal/terminal-colors.ts b/packages/webview/src/component/terminal/terminal-colors.ts index 49acc78c..ef445712 100644 --- a/packages/webview/src/component/terminal/terminal-colors.ts +++ b/packages/webview/src/component/terminal/terminal-colors.ts @@ -16,6 +16,9 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ +import type { JsonColorScheme } from './json-colorizer.js'; +import { JsonColorizer } from './json-colorizer.js'; + // An array of readable ANSI escape sequence colours against a black terminal background // these are the most "readable" colours against a black background // No colours like grey, normal blue (cyan instead) or red, since they don't appear very well. @@ -34,17 +37,18 @@ export const ansi256Colours = [ // ANSI colors for log levels const LOG_LEVEL_COLORS: Record = { - DBG: '\u001b[36m', // cyan - DEBUG: '\u001b[36m', - INF: '\u001b[32m', // green - INFO: '\u001b[32m', + TRACE: '\u001b[36;1m', // bright cyan + DBG: '\u001b[32m', // green + DEBUG: '\u001b[32m', + INF: '\u001b[36m', // cyan + INFO: '\u001b[36m', + INFORMATION: '\u001b[36m', WRN: '\u001b[33m', // yellow WARN: '\u001b[33m', WARNING: '\u001b[33m', - ERR: '\u001b[31m', // red - ERROR: '\u001b[31m', - FATAL: '\u001b[31;1m', // bright red - TRACE: '\u001b[35m', // magenta + ERR: '\u001b[31;1m', // bright red + ERROR: '\u001b[31m;1m', + FATAL: '\u001b[31m', // bright red }; // Function that takes the container name and ANSI colour and encapsulates the name in the colour, @@ -55,29 +59,89 @@ export function colourizedANSIContainerName(name: string, colour: string): strin /** * Colorizes log levels in brackets for better readability. - * Detects patterns like [timestamp LEVEL] or [LEVEL] anywhere in the line. + * Detects patterns like [timestamp LEVEL] or [LEVEL] or LEVEL: or (LEVEL) or "LEVEL" anywhere in the line. * * Examples: - * - [23:10:06 INF] -> green - * - [DBG] -> cyan - * - [ERROR] -> red - * - 2025-10-29T23:10:10.688386132-05:00 [23:10:10 INF] -> green (with K8s timestamp) + * - [23:10:06 INF] -> cyan + * - [DBG] -> green + * - [ERROR] -> bright red + * - info: message -> cyan + * - (INFO) message -> cyan + * - "WARN" message -> yellow + * - "information" -> cyan + * - 2025-10-29T23:10:10.688386132-05:00 info: message -> cyan (with timestamp) + * - 2025-10-29T23:10:10.688386132-05:00 [23:10:10 INF] -> cyan (with K8s timestamp) */ export function colorizeLogLevel(logLine: string): string { - // Pattern to match [timestamp? LEVEL] anywhere in the line (removed ^ anchor) - const logLevelPattern = /(\[(?:[0-9:]+\s+)?)(DBG|DEBUG|INF|INFO|WRN|WARN|WARNING|ERR|ERROR|FATAL|TRACE)(\])/i; + const levelNames = 'DBG|DEBUG|INF|INFO|INFORMATION|WRN|WARN|WARNING|ERR|ERROR|FATAL|TRACE'; + // Combined pattern: Match [timestamp? LEVEL], LEVEL:, (LEVEL), or "LEVEL" anywhere in the line + const logLevelPattern = new RegExp( + `((\\[(?:[0-9:]+\\s+)?)(${levelNames})(\\])|(${levelNames})(:)|(\\()(${levelNames})(\\))|(")(${levelNames})("))`, + 'i', + ); - const match = logLine.match(logLevelPattern); - if (match && match.index !== undefined) { - const beforeBracket = logLine.slice(0, match.index); - const prefix = match[1]; // [timestamp or [ - const level = match[2].toUpperCase(); - const suffix = match[3]; // ] + const match = logLevelPattern.exec(logLine); + if (match) { + const before = logLine.slice(0, match.index); const rest = logLine.slice(match.index + match[0].length); - const color = LOG_LEVEL_COLORS[level] || '\u001b[37m'; - return `${beforeBracket}${color}${prefix}${level}${suffix}\u001b[0m${rest}`; + // Check which format matched + if (match[2]) { + // Bracket format: [timestamp? LEVEL] + const prefix = match[2]; // [timestamp or [ + const level = match[3]; // Keep original case + const suffix = match[4]; // ] + const color = LOG_LEVEL_COLORS[level.toUpperCase()] || '\u001b[37m'; + return `${before}${color}${prefix}${level}${suffix}\u001b[0m${rest}`; + } else if (match[5]) { + // Colon format: LEVEL: + const level = match[5]; // Keep original case + const colon = match[6]; + const color = LOG_LEVEL_COLORS[level.toUpperCase()] || '\u001b[37m'; + return `${before}${color}${level}${colon}\u001b[0m${rest}`; + } else if (match[7]) { + // Parenthesis format: (LEVEL) + const openParen = match[7]; + const level = match[8]; // Keep original case + const closeParen = match[9]; + const color = LOG_LEVEL_COLORS[level.toUpperCase()] || '\u001b[37m'; + return `${before}${color}${openParen}${level}${closeParen}\u001b[0m${rest}`; + } else if (match[10]) { + // Quote format: "LEVEL" + const openQuote = match[10]; + const level = match[11]; // Keep original case + const closeQuote = match[12]; + const color = LOG_LEVEL_COLORS[level.toUpperCase()] || '\u001b[37m'; + return `${before}${color}${openQuote}${level}${closeQuote}\u001b[0m${rest}`; + } } return logLine; } + +// Create a default JSON colorizer instance +const defaultJsonColorScheme: JsonColorScheme = { + keyColor: '\u001b[34;1m', // bright blue for keys + braceColor: '\u001b[33m', // yellow for {}[] + numberColor: '\u001b[32m', // green for numbers + booleanColor: '\u001b[35m', // magenta for booleans + nullColor: '\u001b[35m', // magenta for null + reset: '\u001b[0m', +}; + +const defaultJsonColorizer = new JsonColorizer(defaultJsonColorScheme); + +/** + * Colorizes JSON strings for terminal display. + * Applies ANSI colors to improve readability. + * + * Color scheme: + * - Keys: bright blue + * - Braces/brackets: yellow + * - Numbers: green + * - Booleans/null: magenta + * - String values: no colorization (optional) + */ +export function colorizeJSON(jsonLine: string): string { + return defaultJsonColorizer.colorize(jsonLine); +} From 586afdf927a83ba3848cec3af674ac4abd361788 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Tue, 18 Nov 2025 10:05:38 -0600 Subject: [PATCH 09/12] Remove JSON colorization test from terminal colors Removed tests for JSON colorization function. Signed-off-by: David Hettinger --- .../component/terminal/terminal-colors.spec.ts | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/packages/webview/src/component/terminal/terminal-colors.spec.ts b/packages/webview/src/component/terminal/terminal-colors.spec.ts index b6ef6d62..96dfe3a6 100644 --- a/packages/webview/src/component/terminal/terminal-colors.spec.ts +++ b/packages/webview/src/component/terminal/terminal-colors.spec.ts @@ -18,7 +18,7 @@ import { describe, expect, test } from 'vitest'; -import { colorizeJSON, colorizeLogLevel } from './terminal-colors.js'; +import { colorizeLogLevel } from './terminal-colors.js'; describe('colorizeLogLevel', () => { test('should colorize INFO log level in cyan', () => { @@ -199,19 +199,4 @@ describe('colorizeLogLevel', () => { expect(result).toContain('\u001b[0m'); expect(result).toContain('"information"'); }); - - test('should work with JSON colorization function', () => { - const jsonLine = '{"level":"info","message":"test"}'; - const result = colorizeJSON(jsonLine); - - // Should colorize braces in yellow - expect(result).toContain('\u001b[33m{\u001b[0m'); - expect(result).toContain('\u001b[33m}\u001b[0m'); - - // Strings should not be colorized - expect(result).toContain('"level"'); - expect(result).toContain('"info"'); - expect(result).toContain('"message"'); - expect(result).toContain('"test"'); - }); }); From afe95cfb428de1a928e7e25dd9d8a5acde124fb0 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Tue, 18 Nov 2025 10:06:03 -0600 Subject: [PATCH 10/12] Remove default JSON colorizer and color scheme Removed unused JSON colorizer and related color scheme. Signed-off-by: David Hettinger --- .../src/component/terminal/terminal-colors.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/packages/webview/src/component/terminal/terminal-colors.ts b/packages/webview/src/component/terminal/terminal-colors.ts index ef445712..432b8eaa 100644 --- a/packages/webview/src/component/terminal/terminal-colors.ts +++ b/packages/webview/src/component/terminal/terminal-colors.ts @@ -16,9 +16,6 @@ * SPDX-License-Identifier: Apache-2.0 ***********************************************************************/ -import type { JsonColorScheme } from './json-colorizer.js'; -import { JsonColorizer } from './json-colorizer.js'; - // An array of readable ANSI escape sequence colours against a black terminal background // these are the most "readable" colours against a black background // No colours like grey, normal blue (cyan instead) or red, since they don't appear very well. @@ -118,30 +115,3 @@ export function colorizeLogLevel(logLine: string): string { return logLine; } - -// Create a default JSON colorizer instance -const defaultJsonColorScheme: JsonColorScheme = { - keyColor: '\u001b[34;1m', // bright blue for keys - braceColor: '\u001b[33m', // yellow for {}[] - numberColor: '\u001b[32m', // green for numbers - booleanColor: '\u001b[35m', // magenta for booleans - nullColor: '\u001b[35m', // magenta for null - reset: '\u001b[0m', -}; - -const defaultJsonColorizer = new JsonColorizer(defaultJsonColorScheme); - -/** - * Colorizes JSON strings for terminal display. - * Applies ANSI colors to improve readability. - * - * Color scheme: - * - Keys: bright blue - * - Braces/brackets: yellow - * - Numbers: green - * - Booleans/null: magenta - * - String values: no colorization (optional) - */ -export function colorizeJSON(jsonLine: string): string { - return defaultJsonColorizer.colorize(jsonLine); -} From e726d35fbcd79d2505663c2cd16301940a496773 Mon Sep 17 00:00:00 2001 From: David Hettinger Date: Tue, 18 Nov 2025 10:06:28 -0600 Subject: [PATCH 11/12] Cleanup format Signed-off-by: David Hettinger --- .../webview/src/component/pods/PodLogs.svelte | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/packages/webview/src/component/pods/PodLogs.svelte b/packages/webview/src/component/pods/PodLogs.svelte index f9e4a198..2d3ae6b1 100644 --- a/packages/webview/src/component/pods/PodLogs.svelte +++ b/packages/webview/src/component/pods/PodLogs.svelte @@ -1,14 +1,14 @@