1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
|
---Helper utils
--@module utils
local bit, ffi = require "bit", require "ffi"
local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift
local uv = vim.loop
-- -- TODO use rgb as the return value from the matcher functions
-- -- instead of the rgb_hex. Can be the highlight key as well
-- -- when you shift it left 8 bits. Use the lower 8 bits for
-- -- indicating which highlight mode to use.
-- ffi.cdef [[
-- typedef struct { uint8_t r, g, b; } colorizer_rgb;
-- ]]
-- local rgb_t = ffi.typeof 'colorizer_rgb'
-- Create a lookup table where the bottom 4 bits are used to indicate the
-- category and the top 4 bits are the hex value of the ASCII byte.
local BYTE_CATEGORY = ffi.new "uint8_t[256]"
local CATEGORY_DIGIT = lshift(1, 0)
local CATEGORY_ALPHA = lshift(1, 1)
local CATEGORY_HEX = lshift(1, 2)
local CATEGORY_ALPHANUM = bor(CATEGORY_ALPHA, CATEGORY_DIGIT)
-- do not run the loop multiple times
local b = string.byte
local byte_values = { ["0"] = b "0", ["9"] = b "9", ["a"] = b "a", ["f"] = b "f", ["z"] = b "z" }
local extra_char = { [b "-"] = true }
for i = 0, 255 do
local v = 0
local lowercase = bor(i, 0x20)
-- Digit is bit 1
if i >= byte_values["0"] and i <= byte_values["9"] then
v = bor(v, lshift(1, 0))
v = bor(v, lshift(1, 2))
v = bor(v, lshift(i - byte_values["0"], 4))
elseif lowercase >= byte_values["a"] and lowercase <= byte_values["z"] then
-- Alpha is bit 2
v = bor(v, lshift(1, 1))
if lowercase <= byte_values["f"] then
v = bor(v, lshift(1, 2))
v = bor(v, lshift(lowercase - byte_values["a"] + 10, 4))
end
elseif extra_char[i] then
v = i
end
BYTE_CATEGORY[i] = v
end
---Obvious.
---@param byte number
---@return boolean
local function byte_is_alphanumeric(byte)
local category = BYTE_CATEGORY[byte]
return band(category, CATEGORY_ALPHANUM) ~= 0
end
---Obvious.
---@param byte number
---@return boolean
local function byte_is_hex(byte)
return band(BYTE_CATEGORY[byte], CATEGORY_HEX) ~= 0
end
---Merge two tables.
--
-- todo: Remove this and use `vim.tbl_deep_extend`
---@return table
local function merge(...)
local res = {}
for i = 1, select("#", ...) do
local o = select(i, ...)
if type(o) ~= "table" then
return {}
end
for k, v in pairs(o) do
res[k] = v
end
end
return res
end
--- Obvious.
---@param byte number
---@return number
local function parse_hex(byte)
return rshift(BYTE_CATEGORY[byte], 4)
end
local b_percent = string.byte "%"
--- Obvious.
---@param v string
---@return number|nil
local function percent_or_hex(v)
if v:byte(-1) == b_percent then
return tonumber(v:sub(1, -2)) / 100 * 255
end
local x = tonumber(v)
if x > 255 then
return
end
return x
end
--- Get last modified time of a file
---@param path string: file path
---@return number|nil: modified time
local function get_last_modified(path)
local fd = uv.fs_open(path, "r", 438)
if not fd then
return
end
local stat = uv.fs_fstat(fd)
uv.fs_close(fd)
if stat then
return stat.mtime.nsec
else
return
end
end
--- Watch a file for changes and execute callback
---@param path string: File path
---@param callback function: Callback to execute
---@param ... array: params for callback
---@return function|nil
local function watch_file(path, callback, ...)
if not path or type(callback) ~= "function" then
return
end
local fullpath = uv.fs_realpath(path)
if not fullpath then
return
end
local start
local args = { ... }
local handle = uv.new_fs_event()
local function on_change(err, filename, _)
-- Do work...
callback(filename, unpack(args))
-- Debounce: stop/start.
handle:stop()
if not err or not get_last_modified(filename) then
start()
end
end
function start()
uv.fs_event_start(
handle,
fullpath,
{},
vim.schedule_wrap(function(...)
on_change(...)
end)
)
end
start()
return handle
end
--- @export
return {
byte_is_alphanumeric = byte_is_alphanumeric,
byte_is_hex = byte_is_hex,
merge = merge,
parse_hex = parse_hex,
percent_or_hex = percent_or_hex,
get_last_modified = get_last_modified,
watch_file = watch_file,
}
|