Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ require("colorizer").setup({
variable_pattern = "^%$([%w_-]+)", -- Lua pattern for variable names
},
xterm = { enable = false }, -- xterm 256-color codes (#xNN, \e[38;5;NNNm)
ls_colors = { enable = false }, -- LS_COLORS/SGR snippets (e.g. =38;5;196, =48;2;0;0;255)
xcolor = { enable = false }, -- LaTeX xcolor expressions (e.g. red!30)
hsluv = { enable = false }, -- hsluv()/hsluvu() functions
css_var_rgb = { enable = false }, -- CSS vars with R,G,B (e.g. --color: 240,198,198)
Expand Down Expand Up @@ -500,6 +501,14 @@ require("colorizer").setup({

Each custom parser supports: `name`, `parse(ctx)`, `prefixes`, `prefix_bytes`, `setup(ctx)`, `teardown(ctx)`, `state_factory()`. See the [full documentation](https://catgoose.github.io/nvim-colorizer.lua/) for details.

> **Tip:** A custom parser can declare `prefixes = { "=" }` (or any other
> trigger) and validate the rest in `parse`. To reuse the xterm 256-color
> palette without copying it, call
> `require("colorizer.parser.xterm").lookup_256(idx)` or
> `require("colorizer.parser.xterm").get_palette()`. The built-in `ls_colors`
> parser already covers `=38;5;NNN` / `=48;5;NNN` and `=38;2;R;G;B` /
> `=48;2;R;G;B` snippets.

## Hooks

`should_highlight_line` is called before each line is parsed. Return `true` to highlight, `false` to skip:
Expand Down
49 changes: 49 additions & 0 deletions doc/colorizer.txt
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ colorizer.ParsersOptions *colorizer.ParsersOptions*
{tailwind} (colorizer.ParsersTailwind) Tailwind CSS color options
{sass} (colorizer.ParsersSass) Sass variable color options
{xterm} (colorizer.ParsersSimple) xterm 256-color code parser
{ls_colors} (colorizer.ParsersSimple) LS_COLORS/SGR snippet parser (e.g. `=38;5;196`, `=48;2;0;0;255`)
{custom} (colorizer.CustomParserDef[]) List of custom parser definitions


Expand Down Expand Up @@ -1358,6 +1359,10 @@ Supported formats:
- \e[38;2;R;G;Bm / \e[48;2;R;G;Bm for 24-bit true-color foreground/background
- \e[X;Ym for 16-color foreground (30-37) and background (40-47) with brightness

Exposes the 256-color palette for reuse:
- `M.lookup_256(idx)` returns the RGB hex for `0..255`, or `nil`
- `M.get_palette()` returns a fresh copy of the full 256-entry palette

M.parser({line}, {i}) *colorizer.parser.xterm.parser*

Parameters: ~
Expand All @@ -1369,4 +1374,48 @@ M.parser({line}, {i}) *colorizer.parser.xterm.parser*
(string|nil) RGB hexadecimal color from the xterm palette, or `nil` if parsing failed


M.lookup_256({idx}) *colorizer.parser.xterm.lookup_256*
Look up an xterm 256-color palette entry.

Parameters: ~
{idx} (number) Palette index, 0..255

Returns: ~
(string|nil) Lowercase 6-digit hex, or `nil` for out-of-range/non-numeric input


M.get_palette() *colorizer.parser.xterm.get_palette*
Return a fresh copy of the full xterm 256-color palette.

Returns: ~
(string[]) 1-indexed list of 256 lowercase 6-digit hex strings


==============================================================================
LS_COLORS Parser *colorizer.parser.ls_colors*

Parses LS_COLORS / SGR color-producing snippets such as:
- `=NN` plain 8-color (30-37 fg, 40-47 bg)
- `=NN` bright 8-color (90-97 fg, 100-107 bg)
- `=01;NN` bold-promoted fg becomes the bright variant
- `=38;5;NNN` / `=48;5;NNN` 256-color
- `=38;2;R;G;B` / `=48;2;R;G;B` truecolor

Walks semicolon-separated codes starting after `=` until a non-digit /
non-semicolon byte (typically `:` or whitespace). Foreground wins when both
are present. 256-color values reuse the xterm palette so users do not need
to duplicate it in custom parsers.

M.parser({line}, {i}) *colorizer.parser.ls_colors.parser*
Parse an LS_COLORS/SGR color snippet starting at `i` in `line`.

Parameters: ~
{line} (string)
{i} (number) 1-indexed start position; must point at `=`

Returns: ~
(number|nil) consumed from `i`
(string|nil) rgb_hex


vim:tw=78:ts=8:noet:ft=help:norl:
1 change: 1 addition & 0 deletions lua/colorizer/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ local plugin_user_default_options = {
---@field tailwind colorizer.ParsersTailwind Tailwind CSS color options
---@field sass colorizer.ParsersSass Sass variable color options
---@field xterm colorizer.ParsersSimple xterm 256-color code parser
---@field ls_colors colorizer.ParsersSimple LS_COLORS/SGR snippet parser (e.g. `=38;5;196`, `=48;2;0;0;255`)
---@field custom colorizer.CustomParserDef[] List of custom parser definitions

---@class colorizer.ParsersNames
Expand Down
2 changes: 2 additions & 0 deletions lua/colorizer/matcher.lua
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ local function read_parser_flags(opts)
hsluv = p.hsluv and p.hsluv.enable,
oklch = p.oklch and p.oklch.enable,
xterm = p.xterm and p.xterm.enable,
ls_colors = p.ls_colors and p.ls_colors.enable,
xcolor = p.xcolor and p.xcolor.enable,
css_var_rgb = p.css_var_rgb and p.css_var_rgb.enable,
css_var = p.css_var and p.css_var.enable,
Expand Down Expand Up @@ -462,6 +463,7 @@ local function calculate_matcher_key(f)
f.tailwind_lsp or false,
f.sass or false,
f.xterm or false,
f.ls_colors or false,
f.xcolor or false,
f.css_var_rgb or false,
f.oklch or false,
Expand Down
1 change: 1 addition & 0 deletions lua/colorizer/parser/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require("colorizer.parser.rgba_hex")
require("colorizer.parser.argb_hex")
require("colorizer.parser.hex_no_hash")
require("colorizer.parser.xterm")
require("colorizer.parser.ls_colors")
require("colorizer.parser.rgb")
require("colorizer.parser.hsl")
require("colorizer.parser.hsluv")
Expand Down
148 changes: 148 additions & 0 deletions lua/colorizer/parser/ls_colors.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---@mod colorizer.parser.ls_colors LS_COLORS Parser
---@brief [[
---Parses LS_COLORS / SGR color-producing snippets such as:
--- - `=NN` plain 8-color (30-37 fg, 40-47 bg)
--- - `=NN` bright 8-color (90-97 fg, 100-107 bg)
--- - `=01;NN` bold-promoted fg becomes the bright variant
--- - `=38;5;NNN` / `=48;5;NNN` 256-color
--- - `=38;2;R;G;B` / `=48;2;R;G;B` truecolor
---
---Walks semicolon-separated codes starting after `=` until a non-digit /
---non-semicolon byte (typically `:` or whitespace). Foreground wins when both
---are present. 256-color values reuse the xterm palette so users do not need
---to duplicate it in custom parsers.
---@brief ]]
local M = {}

local xterm = require("colorizer.parser.xterm")

local function is_digit(byte)
return byte and byte >= 0x30 and byte <= 0x39
end

-- Collect the contiguous [digit;]+ value run starting at `i+1`.
-- Returns (tokens, end_pos) where end_pos is 1 past the last consumed byte.
-- tokens is the array of numeric strings split on ';'.
local function collect_value(line, i)
local n = #line
local j = i + 1
while j <= n do
local b = line:byte(j)
if b == 0x3B or is_digit(b) then -- ';' or digit
j = j + 1
else
break
end
end
if j == i + 1 then
return nil
end
local tokens = {}
for tok in line:sub(i + 1, j - 1):gmatch("([^;]+)") do
tokens[#tokens + 1] = tok
end
return tokens, j
end

-- Walk tokens; track first fg/bg color and any bold (brightness) flag.
-- Color slots hold either a 0-255 palette index (number) or
-- { r = .., g = .., b = .. } for truecolor.
local function resolve_color(tokens)
local fg, bg, brightness
local k, len = 1, #tokens
while k <= len do
local num = tonumber(tokens[k])
if num then
if num == 1 then
brightness = 1
elseif num >= 30 and num <= 37 and not fg then
fg = num - 30
elseif num >= 40 and num <= 47 and not bg then
bg = num - 40
elseif num >= 90 and num <= 97 and not fg then
fg = num - 90 + 8
elseif num >= 100 and num <= 107 and not bg then
bg = num - 100 + 8
elseif num == 38 or num == 48 then
local is_bg = (num == 48)
local sub = tonumber(tokens[k + 1] or "")
if sub == 5 then
local idx = tonumber(tokens[k + 2] or "")
if idx and idx >= 0 and idx <= 255 then
if is_bg and not bg then
bg = idx
elseif not is_bg and not fg then
fg = idx
end
k = k + 2
end
elseif sub == 2 then
local r = tonumber(tokens[k + 2] or "")
local g = tonumber(tokens[k + 3] or "")
local b = tonumber(tokens[k + 4] or "")
if r and g and b and r <= 255 and g <= 255 and b <= 255 then
local rgb = { r = r, g = g, b = b }
if is_bg and not bg then
bg = rgb
elseif not is_bg and not fg then
fg = rgb
end
k = k + 4
end
end
end
end
k = k + 1
end
return fg or bg, brightness
end

---Parse an LS_COLORS/SGR color snippet starting at `i` in `line`.
---@param line string
---@param i number 1-indexed start position; must point at `=`
---@return number|nil length consumed from `i`
---@return string|nil rgb_hex
function M.parser(line, i)
if line:byte(i) ~= 0x3D then -- '='
return nil
end
local tokens, end_pos = collect_value(line, i)
if not tokens then
return nil
end
local color, brightness = resolve_color(tokens)
if color == nil then
return nil
end
-- `end_pos` is 1 past the last consumed byte, so the consumed run from
-- position `i` (the `=`) inclusive is `end_pos - i` bytes long.
local consumed = end_pos - i
if type(color) == "table" then
return consumed, string.format("%02x%02x%02x", color.r, color.g, color.b)
end
-- Bold promotes the 8 plain colors (0-7) to their bright variants (8-15).
if color < 8 and brightness == 1 then
color = color + 8
end
local hex = xterm.lookup_256(color)
if hex then
return consumed, hex
end
end

M.spec = {
name = "ls_colors",
priority = 10,
-- `byte+fallback` (not `byte`) so '=' is not exclusive: when no color
-- resolves, subsequent prefix/fallback parsers (including user custom
-- parsers with prefixes = { "=" }) still get a chance.
dispatch = { kind = "byte+fallback", bytes = { 0x3D } }, -- '='
config_defaults = { enable = false },
parse = function(ctx)
return M.parser(ctx.line, ctx.col)
end,
}

require("colorizer.parser.registry").register(M.spec)

return M
24 changes: 24 additions & 0 deletions lua/colorizer/parser/xterm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
--- - \e[38;5;NNNm / \e[48;5;NNNm for 256-color foreground/background
--- - \e[38;2;R;G;Bm / \e[48;2;R;G;Bm for 24-bit true-color foreground/background
--- - \e[X;Ym for 16-color foreground (30-37) and background (40-47) with brightness
---
---Exposes the 256-color palette for reuse:
--- - `M.lookup_256(idx)` returns the RGB hex for `0..255`, or `nil`
--- - `M.get_palette()` returns a fresh copy of the full 256-entry palette
---@brief ]]
local M = {}

Expand Down Expand Up @@ -173,6 +177,26 @@ function M.parser(line, i)
return nil
end

---Look up an xterm 256-color palette entry.
---@param idx number Palette index, 0..255
---@return string|nil rgb_hex Lowercase 6-digit hex, or `nil` for out-of-range/non-numeric input
function M.lookup_256(idx)
if type(idx) ~= "number" or idx < 0 or idx > 255 then
return nil
end
return xterm_palette[idx + 1]
end

---Return a fresh copy of the full xterm 256-color palette.
---@return string[] palette 1-indexed list of 256 lowercase 6-digit hex strings
function M.get_palette()
local out = {}
for i = 1, 256 do
out[i] = xterm_palette[i]
end
return out
end

--- Parser spec for the registry
M.spec = {
name = "xterm",
Expand Down
1 change: 1 addition & 0 deletions scripts/gen_docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ $LEMMY -f \
"$PROJECT_DIR/lua/colorizer/parser/sass.lua" \
"$PROJECT_DIR/lua/colorizer/parser/css_var.lua" \
"$PROJECT_DIR/lua/colorizer/parser/xterm.lua" \
"$PROJECT_DIR/lua/colorizer/parser/ls_colors.lua" \
>"$OUTPUT"

echo "$OUTPUT created"
Loading
Loading