Skip to content

Commit 51cf7c9

Browse files
authored
Support xterm 16-color highlighting (#157)
* feat(xterm): support xterm 16-color highlighting * chore(xterm): align code when tonumber() returns nil * fix(test): xterm test case
1 parent 1659718 commit 51cf7c9

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

lua/colorizer/parser/xterm.lua

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,11 @@ for i = 0, 23 do
2828
end
2929

3030
--- Parses xterm color codes and converts them to RGB hex format.
31-
-- This function matches both #xNN format (decimal, 0-255) and ANSI escape sequences \e[38;5;NNNm
32-
-- for xterm 256-color palette. It returns the corresponding RGB hex string from the xterm color palette.
31+
-- This function matches following color codes:
32+
-- 1. #xNN format (decimal, 0-255).
33+
-- 2. ANSI escape sequences \e[38;5;NNNm for xterm 256-color palette.
34+
-- 3. ANSI escape sequences \e[X;Ym for xterm 16-color palette.
35+
-- It returns the corresponding RGB hex string from the xterm color palette.
3336
---@param line string: The line of text to parse for xterm color codes
3437
---@param i number: The starting index within the line where parsing should begin
3538
---@return number|nil: The end index of the parsed xterm color code within the line, or `nil` if parsing failed
@@ -40,8 +43,8 @@ function M.parser(line, i)
4043
if hash_x == "#x" then
4144
local num = line:sub(i + 2):match("^(%d?%d?%d)")
4245
if num then
43-
local idx = tonumber(num)
44-
if idx and idx >= 0 and idx <= 255 then
46+
local idx = tonumber(num) or -1
47+
if idx >= 0 and idx <= 255 then
4548
local next_char = line:sub(i + 2 + #num, i + 2 + #num)
4649
if next_char == "" or not next_char:match("%w") then
4750
return 2 + #num, xterm_palette[idx + 1]
@@ -50,16 +53,16 @@ function M.parser(line, i)
5053
end
5154
end
5255
-- \e[38;5;NNNm (decimal, 0-255), support both literal '\e' and actual escape char
53-
local ansi_patterns = {
56+
local ansi_256_patterns = {
5457
"^\\e%[38;5;(%d?%d?%d)m", -- literal '\e'
5558
"^\27%[38;5;(%d?%d?%d)m", -- ASCII 27
5659
"^\x1b%[38;5;(%d?%d?%d)m", -- hex escape
5760
}
58-
for _, esc_pat in ipairs(ansi_patterns) do
61+
for _, esc_pat in ipairs(ansi_256_patterns) do
5962
local esc_match = line:sub(i):match(esc_pat)
6063
if esc_match then
61-
local idx = tonumber(esc_match)
62-
if idx and idx >= 0 and idx <= 255 then
64+
local idx = tonumber(esc_match) or -1
65+
if idx >= 0 and idx <= 255 then
6366
-- Use string.find to get the end index of the match
6467
local _, end_idx = line:sub(i):find(esc_pat)
6568
if end_idx then
@@ -70,7 +73,26 @@ function M.parser(line, i)
7073
end
7174
end
7275
end
76+
-- \e[X;Ym for xterm 16-color palette, support foreground colors (30-37) with brightness (0-1)
77+
-- TODO: Support background colors and other patterns
78+
local ansi_16_patterns = {
79+
"^\\e%[(%d+);(%d+)m", -- literal '\e'
80+
"^\27%[(%d+);(%d+)m", -- ASCII 27
81+
"^\x1b%[(%d+);(%d+)m", -- hex escape
82+
}
83+
for _, esc_pat in ipairs(ansi_16_patterns) do
84+
local match_x, match_y = line:sub(i):match(esc_pat)
85+
if match_x and match_y then
86+
local x, y = tonumber(match_x) or -1, tonumber(match_y) or -1
87+
-- Color and brightness positions are interchangeable
88+
local color, brightness = math.max(x, y), math.min(x, y)
89+
if color >= 30 and color <= 37 and brightness >= 0 and brightness <= 1 then
90+
color = color - 30
91+
return 5 + #match_x + #match_y, xterm_palette[color + 1 + brightness * 8]
92+
end
93+
end
94+
end
7395
return nil
7496
end
7597

76-
return M
98+
return M

test/expect.lua

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ SUCCESS CASES:
103103
#x16 -- start of color cube #000000
104104
#x17 -- color cube #00005f
105105
#x21 -- color cube #0000ff
106-
#x51 -- color cube #00ff00
106+
#x51 -- color cube #00ffff
107107
#x88 -- color cube #870000
108108
#x160 -- color cube #d70000
109109
#x231 -- color cube #ffffff
@@ -157,6 +157,25 @@ SUCCESS CASES:
157157
158158
 #00d75f
159159
160+
\e[30;0m #000000
161+
\e[31;0m #800000
162+
\e[32;0m #008000
163+
\e[33;0m #808000
164+
\e[34;0m #000080
165+
\e[35;0m #800080
166+
\e[36;0m #008080
167+
\e[37;0m #c0c0c0
168+
\e[30;1m #808080
169+
\e[31;1m #ff0000
170+
\e[32;1m #00ff00
171+
\e[33;1m #ffff00
172+
\e[34;1m #0000ff
173+
\e[35;1m #ff00ff
174+
\e[36;1m #00ffff
175+
\e[37;1m #ffffff
176+
177+
\e[1;37m #ffffff
178+
160179
CSS Named Colors:
161180
olive -- do not remove
162181
cyan magenta gold chartreuse lightgreen pink violet orange

0 commit comments

Comments
 (0)