Document color
textDocument/documentColor and colorPresentation for #hex, color.*, and literal color calls.
This page
Abstract
textDocument/documentColor reports editor color swatches for Pine color literals. Each hit is a 0–1 RGBA ColorInformation covering the AST node. textDocument/colorPresentation rewrites the requested range as #rrggbb (or #rrggbbaa when alpha ≠ 1), including when the original text was a color.* name. Empty source or parse failure returns [].
Conceptual model
Rendering…
Only literal values become swatches. A color.rgb(x, 0, 0) whose x is a name is skipped; a nested color.blue inside a skipped call still gets its own swatch.
Interface surface
| Method | Handler |
|---|---|
textDocument/documentColor | handle_document_color |
textDocument/colorPresentation | handle_color_presentation |
Capability: color_provider=True (config.py). handle_document_color receives the version-matched AST (tree=doc.valid_ast).
Returns [] for empty source, parse failure, or tree=None. Never None.
Reported swatches
| Form | Example | Notes |
|---|---|---|
Lexer COLOR token | #ff0000, #00ff00aa, #f0a | Constant with kind="#"; #rgb expands each nibble (#f0a → ff00aaff) |
| Quoted hex string | "#ff0000", "#00ff00aa" | Constant with kind is None; range includes the quotes |
Named color.* | color.red, color.green | Attribute on Name("color"); palette from _NAMED_COLORS |
color.rgb | color.rgb(255, 0, 0[, transp]) | Numeric literals only; optional Pine transp 0–100 (0 = opaque) |
color.new | color.new(color.white, 50) | Both args literals (named / #hex / quoted hex + numeric transp) |
color() | color(#ff0000, 25) | Pine alias of color.new |
color.from_gradient | color.from_gradient(5, 0, 10, #000000, #ffffff) | All five args literals; interpolation matches interpret color.from_gradient |
#rrggbb is opaque (alpha 1). #rrggbbaa sets alpha from the last byte. Channels are 0–255, divided by 255 for the LSP Color.
Pine transparency
color.rgb / color.new / color() take transp 0–100, where 0 is opaque. Alpha byte = round(255 * (1 - transp / 100)). Example: transp 50 → alpha 128; transp 95 → alpha 13.
Keyword aliases on color.rgb: r/g/b/t for red/green/blue/transp. from_gradient accepts evaluator aliases (min / max / color1 / color2, …).
colorPresentation
Rewrites params.range as hex. source is unused — the client already sent the color and range.
| Alpha | Label / newText |
|---|---|
1 (255) | #rrggbb (lowercase) |
| otherwise | #rrggbbaa |
Picking a color on color.red therefore replaces the name with #f23645. There is no presentation that keeps the color.* spelling.
Internals
| Path | Role |
|---|---|
src/pynescript/langserver/features/document_color.py | Walk + RGBA conversion + presentation |
src/pynescript/ast/evaluator/builtins/color.py | _NAMED_COLORS, color_from_gradient |
src/pynescript/langserver/config.py | color_provider=True |
src/pynescript/langserver/server.py | TEXT_DOCUMENT_DOCUMENT_COLOR / TEXT_DOCUMENT_COLOR_PRESENTATION; color list cached on doc.feature_cache["document_color"] |
Named palette (hex as stored): red #F23645, green #22AB94, blue #2962FF, black, white, gray/grey, orange, purple, yellow, aqua, fuchsia, lime, maroon, navy, olive, silver, teal.
from_gradient lerp matches interpret: RGBA interpolate, clamp value to [min, max], equal min/max → color1.
Tests: tests/test_lsp_features.py (TestDocumentColorHandler).
Invariants and edge cases
- Empty / unparsed →
[]. Missing source, parse exception, ortree=None. - Non-literal args skip the call, not nested literals.
color.rgb(x, 0, 0)is not a swatch;color.blueinside a skippedcolor.rgb(color.r(color.blue), …)still is. - Unknown
color.foo/color.rare not swatches. Only names in_NAMED_COLORS. color.new/color()need both args. One-argcolor.new(color.white)is skipped. Extra args skip the call. Nestedcolor.rgb(...)as the color arg is not a literal color.- Quoted non-hex strings are ignored.
"not-a-color"and"ff0000"(no#) are not swatches. - Presentation always hex. Named ranges become
#rrggbb/#rrggbbaa; the handler does not emitcolor.redas a presentation label. - Hash comments vs colors.
#ff0000is a color token;# hash …is a comment (see document links).
Worked example
//@version=6
indicator("T")
c1 = #ff0000
c2 = #00ff00aa
plot(close, color=#2962FF)
bgcolor(#22AB94)
plot(close, title="#ff00ff")
col = color.red
c = color.rgb(255, 0, 0)
d = color.rgb(33, 150, 243, 95)
n = color.new(color.white, 50)
g = color.from_gradient(5, 0, 10, #000000, #ffffff)
Expected swatches (0–1 RGBA, 8-bit shown):
| Text | RGB (alpha) |
|---|---|
#ff0000 | 255, 0, 0 (255) |
#00ff00aa | 0, 255, 0 (170) |
#2962FF | 41, 98, 255 |
#22AB94 | 34, 171, 148 |
"#ff00ff" | 255, 0, 255 (range includes quotes) |
color.red | 242, 54, 69 (#F23645) |
color.rgb(255, 0, 0) | 255, 0, 0 |
color.rgb(33, 150, 243, 95) | 33, 150, 243 (13) |
color.new(color.white, 50) | 255, 255, 255 (128) |
color.from_gradient(5, 0, 10, #000000, #ffffff) | 127, 127, 127 (midpoint) |
colorPresentation on #ff0000 with opaque red yields label #ff0000. On color.red with the palette red, the edit replaces the name with #f23645.
Not swatches: color.rgb(x, 0, 0), color.from_gradient(close, 0, 100, color.red, color.green), color.new(color.white) (missing transp).
Failure modes
| Symptom | Cause |
|---|---|
| No swatches on file | Parse error → [] |
color.rgb(close, 0, 0) has no picker | Non-literal args — expected skip |
Picking a color on color.red rewrites hex | Expected: presentation is always #rrggbb / #rrggbbaa |
Quoted "#ff0000" picker covers the quotes | Expected: range is the whole string Constant |
| Gradient swatch disagrees with chart | Call args were not all literals, or min/max equal (then color1) |
See also
- Document links — hash comments vs
#hextokens - Inlay hints — parameter-name labels in calls
- Architecture —
color_provider=True