From 192dcc20ec2c36f44a67e2ed1dd33dda622418e0 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasyura Date: Fri, 19 Dec 2025 03:21:25 -0800 Subject: [PATCH] Add tests and CSS 4 syntax support for hsl/hsla colors --- .../defaultDocumentColorsComputer.ts | 7 +-- .../defaultDocumentColorsComputer.test.ts | 48 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts b/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts index 7e19cd3dd37..e6ba5053181 100644 --- a/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts +++ b/src/vs/editor/common/languages/defaultDocumentColorsComputer.ts @@ -100,8 +100,8 @@ function _findMatches(model: IDocumentColorComputerTarget | string, regex: RegEx function computeColors(model: IDocumentColorComputerTarget): IColorInformation[] { const result: IColorInformation[] = []; - // Early validation for RGB and HSL - const initialValidationRegex = /\b(rgb|rgba|hsl|hsla)(\([0-9\s,.\%]*\))|^(#)([A-Fa-f0-9]{3})\b|^(#)([A-Fa-f0-9]{4})\b|^(#)([A-Fa-f0-9]{6})\b|^(#)([A-Fa-f0-9]{8})\b|(?<=['"\s])(#)([A-Fa-f0-9]{3})\b|(?<=['"\s])(#)([A-Fa-f0-9]{4})\b|(?<=['"\s])(#)([A-Fa-f0-9]{6})\b|(?<=['"\s])(#)([A-Fa-f0-9]{8})\b/gm; + // Early validation for RGB and HSL (including CSS Level 4 syntax with / separator) + const initialValidationRegex = /\b(rgb|rgba|hsl|hsla)(\([0-9\s,.\%\/]*\))|^(#)([A-Fa-f0-9]{3})\b|^(#)([A-Fa-f0-9]{4})\b|^(#)([A-Fa-f0-9]{6})\b|^(#)([A-Fa-f0-9]{8})\b|(?<=['"\s])(#)([A-Fa-f0-9]{3})\b|(?<=['"\s])(#)([A-Fa-f0-9]{4})\b|(?<=['"\s])(#)([A-Fa-f0-9]{6})\b|(?<=['"\s])(#)([A-Fa-f0-9]{8})\b/gm; const initialValidationMatches = _findMatches(model, initialValidationRegex); // Potential colors have been found, validate the parameters @@ -124,7 +124,8 @@ function computeColors(model: IDocumentColorComputerTarget): IColorInformation[] const regexParameters = /^\(\s*((?:360(?:\.0+)?|(?:36[0]|3[0-5][0-9]|[12][0-9][0-9]|[1-9]?[0-9])(?:\.\d+)?))\s*[\s,]\s*(100(?:\.0+)?|\d{1,2}[.]\d*|\d{1,2})%\s*[\s,]\s*(100(?:\.0+)?|\d{1,2}[.]\d*|\d{1,2})%\s*\)$/gm; colorInformation = _findHSLColorInformation(_findRange(model, initialMatch), _findMatches(colorParameters, regexParameters), false); } else if (colorScheme === 'hsla') { - const regexParameters = /^\(\s*((?:360(?:\.0+)?|(?:36[0]|3[0-5][0-9]|[12][0-9][0-9]|[1-9]?[0-9])(?:\.\d+)?))\s*[\s,]\s*(100(?:\.0+)?|\d{1,2}[.]\d*|\d{1,2})%\s*[\s,]\s*(100(?:\.0+)?|\d{1,2}[.]\d*|\d{1,2})%\s*[\s,]\s*(0[.][0-9]+|[.][0-9]+|[01][.]0*|[01])\s*\)$/gm; + // Supports both comma-separated (hsla(253, 100%, 50%, 0.5)) and CSS Level 4 syntax (hsla(253 100% 50% / 0.5)) + const regexParameters = /^\(\s*((?:360(?:\.0+)?|(?:36[0]|3[0-5][0-9]|[12][0-9][0-9]|[1-9]?[0-9])(?:\.\d+)?))\s*[\s,]\s*(100(?:\.0+)?|\d{1,2}[.]\d*|\d{1,2})%\s*[\s,]\s*(100(?:\.0+)?|\d{1,2}[.]\d*|\d{1,2})%\s*(?:[\s,]|[\s]*\/)\s*(0[.][0-9]+|[.][0-9]+|[01][.]0*|[01])\s*\)$/gm; colorInformation = _findHSLColorInformation(_findRange(model, initialMatch), _findMatches(colorParameters, regexParameters), true); } else if (colorScheme === '#') { colorInformation = _findHexColorInformation(_findRange(model, initialMatch), colorScheme + colorParameters); diff --git a/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts b/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts index a86b6b103ef..0f1679e9088 100644 --- a/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts +++ b/src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts @@ -120,4 +120,52 @@ suite('Default Document Colors Computer', () => { assert.strictEqual(colors.length, 1, 'Should detect one hsl color'); }); + + test('hsl with decimal hue values should work', () => { + // Test case from issue #180436 comment + const testCases = [ + { content: 'hsl(253.5, 100%, 50%)', name: 'decimal hue' }, + { content: 'hsl(360.0, 50%, 50%)', name: '360.0 hue' }, + { content: 'hsl(100.5, 50.5%, 50.5%)', name: 'all decimals' }, + { content: 'hsl(0.5, 50%, 50%)', name: 'small decimal hue' }, + { content: 'hsl(359.9, 100%, 50%)', name: 'near-max decimal hue' } + ]; + + testCases.forEach(testCase => { + const model = new TestDocumentModel(`const color = ${testCase.content};`); + const colors = computeDefaultDocumentColors(model); + assert.strictEqual(colors.length, 1, `Should detect hsl color with ${testCase.name}: ${testCase.content}`); + }); + }); + + test('hsla with decimal values should work', () => { + const testCases = [ + { content: 'hsla(253.5, 100%, 50%, 0.5)', name: 'decimal hue with alpha' }, + { content: 'hsla(360.0, 50.5%, 50.5%, 1)', name: 'all decimals with alpha 1' }, + { content: 'hsla(0.5, 50%, 50%, 0.25)', name: 'small decimal hue with alpha' } + ]; + + testCases.forEach(testCase => { + const model = new TestDocumentModel(`const color = ${testCase.content};`); + const colors = computeDefaultDocumentColors(model); + assert.strictEqual(colors.length, 1, `Should detect hsla color with ${testCase.name}: ${testCase.content}`); + }); + }); + + test('hsl with space separator (CSS Level 4 syntax) should work', () => { + // CSS Level 4 allows space-separated values instead of comma-separated + const testCases = [ + { content: 'hsl(253 100% 50%)', name: 'space-separated' }, + { content: 'hsl(253.5 100% 50%)', name: 'space-separated with decimal hue' }, + { content: 'hsla(253 100% 50% / 0.5)', name: 'hsla with slash separator for alpha' }, + { content: 'hsla(253.5 100% 50% / 0.5)', name: 'hsla with decimal hue and slash separator' }, + { content: 'hsla(253 100% 50% / 1)', name: 'hsla with slash and alpha 1' } + ]; + + testCases.forEach(testCase => { + const model = new TestDocumentModel(`const color = ${testCase.content};`); + const colors = computeDefaultDocumentColors(model); + assert.strictEqual(colors.length, 1, `Should detect hsl color with ${testCase.name}: ${testCase.content}`); + }); + }); });