Skip to content

Commit 83f180c

Browse files
authored
Merge pull request microsoft#284423 from microsoft/dev/dmitriv/hsla-colors-regex
Add tests and CSS 4 syntax support
2 parents 20b1485 + 2957efd commit 83f180c

2 files changed

Lines changed: 76 additions & 5 deletions

File tree

src/vs/editor/common/languages/defaultDocumentColorsComputer.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ function _findMatches(model: IDocumentColorComputerTarget | string, regex: RegEx
100100

101101
function computeColors(model: IDocumentColorComputerTarget): IColorInformation[] {
102102
const result: IColorInformation[] = [];
103-
// Early validation for RGB and HSL
104-
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;
103+
// Early validation for RGB and HSL (including CSS Level 4 syntax with / separator)
104+
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;
105105
const initialValidationMatches = _findMatches(model, initialValidationRegex);
106106

107107
// Potential colors have been found, validate the parameters
@@ -115,16 +115,19 @@ function computeColors(model: IDocumentColorComputerTarget): IColorInformation[]
115115
}
116116
let colorInformation;
117117
if (colorScheme === 'rgb') {
118-
const regexParameters = /^\(\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*,\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*,\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*\)$/gm;
118+
// Supports both comma-separated (rgb(255, 0, 0)) and CSS Level 4 space-separated syntax (rgb(255 0 0))
119+
const regexParameters = /^\(\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*[\s,]\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*[\s,]\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*\)$/gm;
119120
colorInformation = _findRGBColorInformation(_findRange(model, initialMatch), _findMatches(colorParameters, regexParameters), false);
120121
} else if (colorScheme === 'rgba') {
121-
const regexParameters = /^\(\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*,\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*,\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*,\s*(0[.][0-9]+|[.][0-9]+|[01][.]|[01])\s*\)$/gm;
122+
// Supports both comma-separated (rgba(255, 0, 0, 0.5)) and CSS Level 4 syntax (rgba(255 0 0 / 0.5))
123+
const regexParameters = /^\(\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*[\s,]\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*[\s,]\s*(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])\s*(?:[\s,]|[\s]*\/)\s*(0[.][0-9]+|[.][0-9]+|[01][.]|[01])\s*\)$/gm;
122124
colorInformation = _findRGBColorInformation(_findRange(model, initialMatch), _findMatches(colorParameters, regexParameters), true);
123125
} else if (colorScheme === 'hsl') {
124126
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;
125127
colorInformation = _findHSLColorInformation(_findRange(model, initialMatch), _findMatches(colorParameters, regexParameters), false);
126128
} else if (colorScheme === 'hsla') {
127-
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;
129+
// Supports both comma-separated (hsla(253, 100%, 50%, 0.5)) and CSS Level 4 syntax (hsla(253 100% 50% / 0.5))
130+
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;
128131
colorInformation = _findHSLColorInformation(_findRange(model, initialMatch), _findMatches(colorParameters, regexParameters), true);
129132
} else if (colorScheme === '#') {
130133
colorInformation = _findHexColorInformation(_findRange(model, initialMatch), colorScheme + colorParameters);

src/vs/editor/test/common/languages/defaultDocumentColorsComputer.test.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,72 @@ suite('Default Document Colors Computer', () => {
120120

121121
assert.strictEqual(colors.length, 1, 'Should detect one hsl color');
122122
});
123+
124+
test('hsl with decimal hue values should work', () => {
125+
// Test case from issue #180436 comment
126+
const testCases = [
127+
{ content: 'hsl(253.5, 100%, 50%)', name: 'decimal hue' },
128+
{ content: 'hsl(360.0, 50%, 50%)', name: '360.0 hue' },
129+
{ content: 'hsl(100.5, 50.5%, 50.5%)', name: 'all decimals' },
130+
{ content: 'hsl(0.5, 50%, 50%)', name: 'small decimal hue' },
131+
{ content: 'hsl(359.9, 100%, 50%)', name: 'near-max decimal hue' }
132+
];
133+
134+
testCases.forEach(testCase => {
135+
const model = new TestDocumentModel(`const color = ${testCase.content};`);
136+
const colors = computeDefaultDocumentColors(model);
137+
assert.strictEqual(colors.length, 1, `Should detect hsl color with ${testCase.name}: ${testCase.content}`);
138+
});
139+
});
140+
141+
test('hsla with decimal values should work', () => {
142+
const testCases = [
143+
{ content: 'hsla(253.5, 100%, 50%, 0.5)', name: 'decimal hue with alpha' },
144+
{ content: 'hsla(360.0, 50.5%, 50.5%, 1)', name: 'all decimals with alpha 1' },
145+
{ content: 'hsla(0.5, 50%, 50%, 0.25)', name: 'small decimal hue with alpha' }
146+
];
147+
148+
testCases.forEach(testCase => {
149+
const model = new TestDocumentModel(`const color = ${testCase.content};`);
150+
const colors = computeDefaultDocumentColors(model);
151+
assert.strictEqual(colors.length, 1, `Should detect hsla color with ${testCase.name}: ${testCase.content}`);
152+
});
153+
});
154+
155+
test('hsl with space separator (CSS Level 4 syntax) should work', () => {
156+
// CSS Level 4 allows space-separated values instead of comma-separated
157+
const testCases = [
158+
{ content: 'hsl(253 100% 50%)', name: 'space-separated' },
159+
{ content: 'hsl(253.5 100% 50%)', name: 'space-separated with decimal hue' },
160+
{ content: 'hsla(253 100% 50% / 0.5)', name: 'hsla with slash separator for alpha' },
161+
{ content: 'hsla(253.5 100% 50% / 0.5)', name: 'hsla with decimal hue and slash separator' },
162+
{ content: 'hsla(253 100% 50% / 1)', name: 'hsla with slash and alpha 1' }
163+
];
164+
165+
testCases.forEach(testCase => {
166+
const model = new TestDocumentModel(`const color = ${testCase.content};`);
167+
const colors = computeDefaultDocumentColors(model);
168+
assert.strictEqual(colors.length, 1, `Should detect hsl color with ${testCase.name}: ${testCase.content}`);
169+
});
170+
});
171+
172+
test('rgb and rgba with CSS Level 4 space-separated syntax should work', () => {
173+
// CSS Level 4 allows space-separated values for RGB/RGBA
174+
const testCases = [
175+
{ content: 'rgb(255 0 0)', name: 'rgb space-separated' },
176+
{ content: 'rgb(128 128 128)', name: 'rgb space-separated gray' },
177+
{ content: 'rgba(255 0 0 / 0.5)', name: 'rgba with slash separator for alpha' },
178+
{ content: 'rgba(128 128 128 / 0.8)', name: 'rgba gray with slash separator' },
179+
{ content: 'rgba(255 0 0 / 1)', name: 'rgba with slash and alpha 1' },
180+
// Traditional comma syntax should still work
181+
{ content: 'rgb(255, 0, 0)', name: 'rgb comma-separated (traditional)' },
182+
{ content: 'rgba(255, 0, 0, 0.5)', name: 'rgba comma-separated (traditional)' }
183+
];
184+
185+
testCases.forEach(testCase => {
186+
const model = new TestDocumentModel(`const color = ${testCase.content};`);
187+
const colors = computeDefaultDocumentColors(model);
188+
assert.strictEqual(colors.length, 1, `Should detect rgb/rgba color with ${testCase.name}: ${testCase.content}`);
189+
});
190+
});
123191
});

0 commit comments

Comments
 (0)