diff options
Diffstat (limited to 'lua/colorizer/parser')
-rw-r--r-- | lua/colorizer/parser/argb_hex.lua | 58 | ||||
-rw-r--r-- | lua/colorizer/parser/hsl.lua | 110 | ||||
-rw-r--r-- | lua/colorizer/parser/names.lua | 84 | ||||
-rw-r--r-- | lua/colorizer/parser/rgb.lua | 114 | ||||
-rw-r--r-- | lua/colorizer/parser/rgba_hex.lua | 68 |
5 files changed, 434 insertions, 0 deletions
diff --git a/lua/colorizer/parser/argb_hex.lua b/lua/colorizer/parser/argb_hex.lua new file mode 100644 index 0000000..f664dc3 --- /dev/null +++ b/lua/colorizer/parser/argb_hex.lua @@ -0,0 +1,58 @@ +---Helper function to parse argb + +local bit = require "bit" +local floor, min = math.floor, math.min +local band, rshift, lshift = bit.band, bit.rshift, bit.lshift + +local utils = require "colorizer.utils" +local byte_is_alphanumeric = utils.byte_is_alphanumeric +local byte_is_hex = utils.byte_is_hex +local parse_hex = utils.parse_hex + +local parser = {} + +local ARGB_MINIMUM_LENGTH = #"0xAARRGGBB" - 1 +---parse for 0xaarrggbb and return rgb hex. +-- a format used in android apps +---@param line string: line to parse +---@param i number: index of line from where to start parsing +---@return number|nil: index of line where the hex value ended +---@return string|nil: rgb hex value +function parser.argb_hex_parser(line, i) + if #line < i + ARGB_MINIMUM_LENGTH then + return + end + + local j = i + 2 + + local n = j + 8 + local alpha + local v = 0 + while j <= min(n, #line) do + local b = line:byte(j) + if not byte_is_hex(b) then + break + end + if j - i <= 3 then + alpha = parse_hex(b) + lshift(alpha or 0, 4) + else + v = parse_hex(b) + lshift(v, 4) + end + j = j + 1 + end + if #line >= j and byte_is_alphanumeric(line:byte(j)) then + return + end + local length = j - i + if length ~= 10 then + return + end + alpha = tonumber(alpha) / 255 + local r = floor(band(rshift(v, 16), 0xFF) * alpha) + local g = floor(band(rshift(v, 8), 0xFF) * alpha) + local b = floor(band(v, 0xFF) * alpha) + local rgb_hex = string.format("%02x%02x%02x", r, g, b) + return length, rgb_hex +end + +return parser.argb_hex_parser diff --git a/lua/colorizer/parser/hsl.lua b/lua/colorizer/parser/hsl.lua new file mode 100644 index 0000000..cb6a19d --- /dev/null +++ b/lua/colorizer/parser/hsl.lua @@ -0,0 +1,110 @@ +---Helper function to parse argb +local count = require("colorizer.utils").count +local floor = math.floor + +local hsl_to_rgb = require("colorizer.color").hsl_to_rgb + +local parser = {} + +local CSS_HSLA_FN_MINIMUM_LENGTH = #"hsla(0,0%,0%)" - 1 +local CSS_HSL_FN_MINIMUM_LENGTH = #"hsl(0,0%,0%)" - 1 +---Parse for hsl() hsla() css function and return rgb hex. +-- For more info: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl +---@param line string: Line to parse +---@param i number: Index of line from where to start parsing +---@param opts table: Values passed from matchers like prefix +---@return number|nil: Index of line where the hsla/hsl function ended +---@return string|nil: rgb hex value +function parser.hsl_function_parser(line, i, opts) + local min_len = CSS_HSLA_FN_MINIMUM_LENGTH + local min_commas, min_spaces = 2, 2 + local pattern = "^" + .. opts.prefix + .. "%(%s*([.%d]+)([deg]*)([turn]*)(%s?)%s*(,?)%s*(%d+)%%(%s?)%s*(,?)%s*(%d+)%%%s*(/?,?)%s*([.%d]*)([%%]?)%s*%)()" + + if opts.prefix == "hsl" then + min_len = CSS_HSL_FN_MINIMUM_LENGTH + end + + if #line < i + min_len then + return + end + + local h, deg, turn, ssep1, csep1, s, ssep2, csep2, l, sep3, a, percent_sign, match_end = line:sub(i):match(pattern) + if not match_end then + return + end + if a == "" then + a = nil + else + min_commas = min_commas + 1 + end + + -- the text after hue should be either deg or empty + if not ((deg == "") or (deg == "deg") or (turn == "turn")) then + return + end + + local c_seps = ("%s%s%s"):format(csep1, csep2, sep3) + local s_seps = ("%s%s"):format(ssep1, ssep2) + -- comma separator syntax + if c_seps:match "," then + if not (count(c_seps, ",") == min_commas) then + return + end + -- space separator syntax with decimal or percentage alpha + elseif count(s_seps, "%s") >= min_spaces then + if a then + if not (c_seps == "/") then + return + end + end + else + return + end + + if not a then + a = 1 + else + a = tonumber(a) + -- if percentage, then convert to decimal + if percent_sign == "%" then + a = a / 100 + end + -- although alpha doesn't support larger values than 1, css anyways renders it at 1 + if a > 1 then + a = 1 + end + end + + h = tonumber(h) or 1 + -- single turn is 360 + if turn == "turn" then + h = 360 * h + end + + -- if hue angle if greater than 360, then calculate the hue within 360 + if h > 360 then + local turns = h / 360 + h = 360 * (turns - floor(turns)) + end + + -- if saturation or luminance percentage is greater than 100 then reset it to 100 + s = tonumber(s) + if s > 100 then + s = 100 + end + l = tonumber(l) + if l > 100 then + l = 100 + end + + local r, g, b = hsl_to_rgb(h / 360, s / 100, l / 100) + if r == nil or g == nil or b == nil then + return + end + local rgb_hex = string.format("%02x%02x%02x", r * a, g * a, b * a) + return match_end - 1, rgb_hex +end + +return parser.hsl_function_parser diff --git a/lua/colorizer/parser/names.lua b/lua/colorizer/parser/names.lua new file mode 100644 index 0000000..6fc7043 --- /dev/null +++ b/lua/colorizer/parser/names.lua @@ -0,0 +1,84 @@ +---Helper function to parse argb +local api = vim.api + +local bit = require "bit" +local tohex = bit.tohex + +local min, max = math.min, math.max + +local Trie = require "colorizer.trie" + +local utils = require "colorizer.utils" +local byte_is_valid_colorchar = utils.byte_is_valid_colorchar + +local parser = {} + +local COLOR_MAP +local COLOR_TRIE +local COLOR_NAME_MINLEN, COLOR_NAME_MAXLEN +local COLOR_NAME_SETTINGS = { lowercase = true, strip_digits = false } +local TAILWIND_ENABLED = false +--- Grab all the colour values from `vim.api.nvim_get_color_map` and create a lookup table. +-- COLOR_MAP is used to store the colour values +---@param line string: Line to parse +---@param i number: Index of line from where to start parsing +---@param opts table: Currently contains whether tailwind is enabled or not +function parser.name_parser(line, i, opts) + --- Setup the COLOR_MAP and COLOR_TRIE + if not COLOR_TRIE or opts.tailwind ~= TAILWIND_ENABLED then + COLOR_MAP = {} + COLOR_TRIE = Trie() + for k, v in pairs(api.nvim_get_color_map()) do + if not (COLOR_NAME_SETTINGS.strip_digits and k:match "%d+$") then + COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#k, COLOR_NAME_MINLEN) or #k + COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#k, COLOR_NAME_MAXLEN) or #k + local rgb_hex = tohex(v, 6) + COLOR_MAP[k] = rgb_hex + COLOR_TRIE:insert(k) + if COLOR_NAME_SETTINGS.lowercase then + local lowercase = k:lower() + COLOR_MAP[lowercase] = rgb_hex + COLOR_TRIE:insert(lowercase) + end + end + end + if opts and opts.tailwind then + if opts.tailwind == true or opts.tailwind == "normal" or opts.tailwind == "both" then + local tailwind = require "colorizer.tailwind_colors" + -- setup tailwind colors + for k, v in pairs(tailwind.colors) do + for _, pre in ipairs(tailwind.prefixes) do + local name = pre .. "-" .. k + COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#name, COLOR_NAME_MINLEN) or #name + COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#name, COLOR_NAME_MAXLEN) or #name + COLOR_MAP[name] = v + COLOR_TRIE:insert(name) + end + end + end + end + TAILWIND_ENABLED = opts.tailwind + end + + if #line < i + COLOR_NAME_MINLEN - 1 then + return + end + + if i > 1 and byte_is_valid_colorchar(line:byte(i - 1)) then + return + end + + local prefix = COLOR_TRIE:longest_prefix(line, i) + if prefix then + -- Check if there is a letter here so as to disallow matching here. + -- Take the Blue out of Blueberry + -- Line end or non-letter. + local next_byte_index = i + #prefix + if #line >= next_byte_index and byte_is_valid_colorchar(line:byte(next_byte_index)) then + return + end + return #prefix, COLOR_MAP[prefix] + end +end + +return parser.name_parser diff --git a/lua/colorizer/parser/rgb.lua b/lua/colorizer/parser/rgb.lua new file mode 100644 index 0000000..2786a60 --- /dev/null +++ b/lua/colorizer/parser/rgb.lua @@ -0,0 +1,114 @@ +---Helper function to parse argb +local count = require("colorizer.utils").count + +local parser = {} +local CSS_RGBA_FN_MINIMUM_LENGTH = #"rgba(0,0,0)" - 1 +local CSS_RGB_FN_MINIMUM_LENGTH = #"rgb(0,0,0)" - 1 +---Parse for rgb() rgba() css function and return rgb hex. +-- For more info: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb +---@param line string: Line to parse +---@param i number: Index of line from where to start parsing +---@param opts table: Values passed from matchers like prefix +---@return number|nil: Index of line where the rgb/rgba function ended +---@return string|nil: rgb hex value +function parser.rgb_function_parser(line, i, opts) + local min_len = CSS_RGBA_FN_MINIMUM_LENGTH + local min_commas, min_spaces, min_percent = 2, 2, 3 + local pattern = "^" + .. opts.prefix + .. "%(%s*([.%d]+)([%%]?)(%s?)%s*(,?)%s*([.%d]+)([%%]?)(%s?)%s*(,?)%s*([.%d]+)([%%]?)%s*(/?,?)%s*([.%d]*)([%%]?)%s*%)()" + + if opts.prefix == "rgb" then + min_len = CSS_RGB_FN_MINIMUM_LENGTH + end + + if #line < i + min_len then + return + end + + local r, unit1, ssep1, csep1, g, unit2, ssep2, csep2, b, unit3, sep3, a, unit_a, match_end = + line:sub(i):match(pattern) + if not match_end then + return + end + + if a == "" then + a = nil + else + min_commas = min_commas + 1 + end + + local units = ("%s%s%s"):format(unit1, unit2, unit3) + if units:match "%%" then + if not ((count(units, "%%")) == min_percent) then + return + end + end + + local c_seps = ("%s%s%s"):format(csep1, csep2, sep3) + local s_seps = ("%s%s"):format(ssep1, ssep2) + -- comma separator syntax + if c_seps:match "," then + if not (count(c_seps, ",") == min_commas) then + return + end + -- space separator syntax with decimal or percentage alpha + elseif count(s_seps, "%s") >= min_spaces then + if a then + if not (c_seps == "/") then + return + end + end + else + return + end + + if not a then + a = 1 + else + a = tonumber(a) + -- if percentage, then convert to decimal + if unit_a == "%" then + a = a / 100 + end + -- although alpha doesn't support larger values than 1, css anyways renders it at 1 + if a > 1 then + a = 1 + end + end + + r = tonumber(r) + if not r then + return + end + g = tonumber(g) + if not g then + return + end + b = tonumber(b) + if not b then + return + end + + if unit1 == "%" then + r = r / 100 * 255 + g = g / 100 * 255 + b = b / 100 * 255 + else + -- although r,g,b doesn't support larger values than 255, css anyways renders it at 255 + if r > 255 then + r = 255 + end + if g > 255 then + g = 255 + end + if b > 255 then + b = 255 + end + end + + local rgb_hex = string.format("%02x%02x%02x", r * a, g * a, b * a) + return match_end - 1, rgb_hex +end + +return parser.rgb_function_parser diff --git a/lua/colorizer/parser/rgba_hex.lua b/lua/colorizer/parser/rgba_hex.lua new file mode 100644 index 0000000..0f16fbe --- /dev/null +++ b/lua/colorizer/parser/rgba_hex.lua @@ -0,0 +1,68 @@ +---Helper function to parse argb +local bit = require "bit" +local floor, min = math.floor, math.min +local band, rshift, lshift = bit.band, bit.rshift, bit.lshift + +local utils = require "colorizer.utils" +local byte_is_alphanumeric = utils.byte_is_alphanumeric +local byte_is_hex = utils.byte_is_hex +local parse_hex = utils.parse_hex + +local parser = {} + +---parse for #rrggbbaa and return rgb hex. +-- a format used in android apps +---@param line string: line to parse +---@param i number: index of line from where to start parsing +---@param opts table: Containing minlen, maxlen, valid_lengths +---@return number|nil: index of line where the hex value ended +---@return string|nil: rgb hex value +function parser.rgba_hex_parser(line, i, opts) + local minlen, maxlen, valid_lengths = opts.minlen, opts.maxlen, opts.valid_lengths + local j = i + 1 + if #line < j + minlen - 1 then + return + end + + if i > 1 and byte_is_alphanumeric(line:byte(i - 1)) then + return + end + + local n = j + maxlen + local alpha + local v = 0 + + while j <= min(n, #line) do + local b = line:byte(j) + if not byte_is_hex(b) then + break + end + if j - i >= 7 then + alpha = parse_hex(b) + lshift(alpha or 0, 4) + else + v = parse_hex(b) + lshift(v, 4) + end + j = j + 1 + end + + if #line >= j and byte_is_alphanumeric(line:byte(j)) then + return + end + + local length = j - i + if length ~= 4 and length ~= 7 and length ~= 9 then + return + end + + if alpha then + alpha = tonumber(alpha) / 255 + local r = floor(band(rshift(v, 16), 0xFF) * alpha) + local g = floor(band(rshift(v, 8), 0xFF) * alpha) + local b = floor(band(v, 0xFF) * alpha) + local rgb_hex = string.format("%02x%02x%02x", r, g, b) + return 9, rgb_hex + end + return (valid_lengths[length - 1] and length), line:sub(i + 1, i + length - 1) +end + +return parser.rgba_hex_parser |