Add document color support for format color-hex - #83
Conversation
| const color = colorFromHex(node.value as string); | ||
| if (!color) { | ||
| continue; | ||
| } | ||
|
|
||
| const annotations = await jsonDocument.getAnnotations(node); |
There was a problem hiding this comment.
here we Parse first, then check annotations. As regex is cheap and rules out the vast majority of strings before we do the async lookup.
| }); | ||
| }; | ||
|
|
||
| const HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i; |
There was a problem hiding this comment.
CSS-style hex colors: #rgb, #rgba, #rrggbb, #rrggbbaa.
|
|
||
| const HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i; | ||
|
|
||
| const colorFromHex = (text: string): Color | undefined => { |
There was a problem hiding this comment.
This helper colorsFromHex converts a hex color string into LSP's Color, whose channels are 0-1 floats rather than 0-255 integers.
- Shorthand forms (
#rgb,#rgba) use one digit per channel, which expands by doubling the digit for eg."f"becomes"ff". - Alpha defaults to fully opaque when the hex doesn't carry one, it is basically an additional parameter that determines if the color has some transparency.
| const toTwoDigitHex = (channel: number): string => { | ||
| return Math.round(channel * 255).toString(16) | ||
| .padStart(2, "0"); |
There was a problem hiding this comment.
This converts one 0-1 channel back to a zero-padded hex byte, so 1 becomes "ff" and 0 becomes "00" rather than "f"/"0", which wouldn't be valid hex.
| const hexFromColor = (color: Color): string => { | ||
| const hex = `#${toTwoDigitHex(color.red)}${toTwoDigitHex(color.green)}${toTwoDigitHex(color.blue)}`; | ||
| return color.alpha === 1 ? hex : `${hex}${toTwoDigitHex(color.alpha)}`; |
There was a problem hiding this comment.
this converts a color picked in the editor back into a hex string to write into the document. Alpha is omitted when fully opaque, so we produce #rrggbb rather than a needlessly long #rrggbbff.
| const digits = text.slice(1); | ||
| const isShorthand = digits.length < 6; | ||
| const width = isShorthand ? 1 : 2; | ||
| const channel = (index: number): number => { | ||
| const digit = digits.slice(index * width, index * width + width); | ||
| return parseInt(isShorthand ? digit + digit : digit, 16) / 255; | ||
| }; |
There was a problem hiding this comment.
we first strip the leading # and then read one channel at a time. Hex colors come in two groups
- shorthand (#rgb, #rgba) which uses a single digit per channel
- full form (#rrggbb, #rrggbbaa) which uses two digit per channel
so width picks how many digits to slice per channel, and anything under 6 digits total is shorthand. For shorthand, each digit is doubled before parsing, since CSS defines #f0a as equivalent to #ff00aa . Finally, dividing by 255 converts the 0–255 byte into the 0–1 float that LSP's Color type expects.
jdesrosiers
left a comment
There was a problem hiding this comment.
This one is a little weird because 'color-hex' isn't a format defined by JSON Schema. I'm guessing this is behavior we're trying to match in the vscode json language server, so it's fine.
Description
Implements Document Color support, so strings that are marked by a schema as "format": "color-hex" render an inline color swatch and open the editor's color picker. Editing through the picker writes the new hex back into the document.
Screen.Recording.2026-09-03.094002.mp4