-
-
Notifications
You must be signed in to change notification settings - Fork 110
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(interactive):
live_colors()
for internal development
Add a new, experimental function `live_colors()` to the `interactive` module. It displays colors live, inline in the current buffer and refreshes on every write. Works with palette files and module files at the moment. Module files display using the current theme/colorscheme. This can be used to assist with internal development. Simply run `=require('github-theme.interactive').live_colors(true)` from the cmdline to try it out. Pass `false` to disable (or restart neovim).
- Loading branch information
Showing
5 changed files
with
324 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/CHANGELOG | ||
/CHANGELOG.md | ||
*.lua |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,328 @@ | ||
local util = require('github-theme.util') | ||
local api = vim.api | ||
local cmd = util.is_nvim and vim.cmd or vim.command | ||
local fmt = string.format | ||
|
||
local M = {} | ||
|
||
local function get_filetype() | ||
return vim['bo'] and vim.bo.filetype or vim.eval('&filetype') | ||
-- TODO: move to util module? | ||
---@param tbl table the target search in | ||
---@param keypath string[]|string | ||
---@return any? value # value in `tbl` at `keypath`, or `nil` if unsuccessful | ||
---@return any? last # the last/deepest value reached if unsuccessful, otherwise `nil` | ||
local function keypath_get(tbl, keypath) | ||
if type(keypath) == 'string' then | ||
keypath = vim.split(keypath, '.', { plain = true, trimempty = true }) | ||
end | ||
|
||
local current = tbl | ||
for _, k in ipairs(keypath) do | ||
if type(current) ~= 'table' then | ||
return nil, current | ||
end | ||
|
||
current = current[k] | ||
end | ||
|
||
return current | ||
end | ||
|
||
---@return integer id autocmd id | ||
function M.attach() | ||
vim.g.github_theme_debug = true | ||
cmd([[ | ||
augroup GithubThemeInteractiveAugroup | ||
autocmd! | ||
autocmd BufWritePost <buffer> lua require("github-theme.interactive").execute() | ||
augroup END | ||
]]) | ||
end | ||
|
||
function M.execute() | ||
local source_method = get_filetype() == 'lua' and 'luafile' or 'source' | ||
local name = vim['g'] and vim.g.colors_name or vim.eval('g:colors_name') | ||
return api.nvim_create_autocmd('BufWritePost', { | ||
group = api.nvim_create_augroup('github-theme.interactive', { clear = true }), | ||
buffer = 0, | ||
desc = 'Reloads user config when the buffer is written', | ||
nested = true, | ||
callback = require('github-theme.interactive').execute(), | ||
}) | ||
end | ||
|
||
function M.execute(info) | ||
require('github-theme.config').reset() | ||
require('github-theme.override').reset() | ||
cmd(fmt( | ||
[[ | ||
%s %% | ||
colorscheme %s | ||
doautoall ColorScheme | ||
]], | ||
source_method, | ||
name | ||
)) | ||
cmd(([[ | ||
%s %% | ||
colorscheme %s | ||
]]):format(vim.bo[info.buf].ft == 'lua' and 'luafile' or 'source', vim.g.colors_name)) | ||
end | ||
|
||
---**EXPERIMENTAL** | ||
--- | ||
---For internal development. Displays colors live, inline in the current buffer and | ||
---refreshes on write. Works with palette files and module files at the moment, although | ||
---there may be some bugs. Module files display in/with the current theme/colorscheme. | ||
--- | ||
---TODO: support other files as well, expose via cmd?, etc. | ||
---@param enable? boolean | ||
function M.live_colors(enable) | ||
local ts = vim.treesitter | ||
local ns = api.nvim_create_namespace('github-theme.interactive') | ||
local augroup = api.nvim_create_augroup('github-theme.interactive', { clear = true }) | ||
|
||
local function get_nodes(node, src, typ, cb) | ||
local stack = {} | ||
|
||
-- {{{ Queries | ||
|
||
local q1 = ts.query.parse( | ||
'lua', | ||
[[ | ||
(assignment_statement | ||
. | ||
(variable_list) @k (#vim-match? @k "\\m^\\%(palette\\|pal\\|spec\\)\\%(\\.\\w\\+\\)*$") | ||
. | ||
"=" | ||
. | ||
(expression_list | ||
(table_constructor) @defs | ||
) | ||
) | ||
]] | ||
) | ||
local q2 = ts.query.parse( | ||
'lua', | ||
[[ | ||
(field | ||
name: [ | ||
(_ content: (_) @k) | ||
(_ !content) @k | ||
] | ||
value: (_) @v | ||
) | ||
(table_constructor | ||
"}" @tbl_end | ||
. | ||
) | ||
]] | ||
) | ||
|
||
local modquery = ts.query.parse( | ||
'lua', | ||
[[ | ||
(function_declaration | ||
name: (_) @fname (#match? @fname "^M[.:]get$") | ||
body: (_ | ||
(return_statement | ||
(expression_list | ||
(table_constructor | ||
(field | ||
name: [ | ||
(_ content: (_) @k) | ||
(_ !content) @k | ||
] | ||
value: (table_constructor) @v | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
]] | ||
) | ||
|
||
-- }}} | ||
|
||
-- Map capture names to their ID | ||
local cap1, cap2, cap3 = {}, {}, {} | ||
for _, v in ipairs({ | ||
{ q1.captures, cap1 }, | ||
{ q2.captures, cap2 }, | ||
{ modquery.captures, cap3 }, | ||
}) do | ||
for id, name in pairs(v[1]) do | ||
v[2][name] = id | ||
end | ||
end | ||
|
||
local function matched_node(match, cap_id) | ||
vim.validate({ | ||
match = { match, { 'table', 'userdata' } }, | ||
cap_id = { cap_id, 'number' }, | ||
}) | ||
|
||
local nodes = match[cap_id] | ||
if nodes then | ||
assert(#nodes == 1) | ||
return nodes[1] | ||
end | ||
end | ||
|
||
---@param node TSNode | ||
---@diagnostic disable-next-line: redefined-local | ||
local function inner(node) | ||
for _pat, match, meta in q2:iter_matches(node, src, nil, nil, { all = true }) do | ||
if matched_node(match, cap2.tbl_end) then | ||
table.remove(stack) | ||
else | ||
local knode = assert(matched_node(match, cap2.k)) | ||
local knode_text = ts.get_node_text(knode, src, { metadata = meta[cap2.k] }) | ||
local vnode = assert(matched_node(match, cap2.v)) | ||
|
||
if vnode:type() == 'table_constructor' then | ||
table.insert(stack, knode_text) | ||
else | ||
cb({ | ||
knode = knode, | ||
vnode = vnode, | ||
keypath = table.concat(stack, '.') .. '.' .. knode_text, | ||
}) | ||
end | ||
end | ||
end | ||
end | ||
|
||
if typ == 'mod' then | ||
for _pat, match, meta in modquery:iter_matches(node, src, nil, nil, { all = true }) do | ||
local knode = assert(matched_node(match, cap3.k)) | ||
local vnode = assert(matched_node(match, cap3.v)) | ||
cb({ | ||
knode = knode, | ||
vnode = vnode, | ||
keypath = ts.get_node_text(knode, src, { metadata = meta[cap3.k] }), | ||
}) | ||
end | ||
else | ||
for _pat, match, meta in q1:iter_matches(node, src, nil, nil, { all = true }) do | ||
---@diagnostic disable-next-line: redefined-local | ||
local node = assert(matched_node(match, cap1.defs)) | ||
local k = matched_node(match, cap1.k) | ||
and ts.get_node_text( | ||
matched_node(match, cap1.k), | ||
src, | ||
{ metadata = meta[cap1.k] } | ||
) | ||
or '' | ||
|
||
for part in k:gmatch('[^.]+') do | ||
table.insert(stack, part) | ||
end | ||
|
||
inner(node) | ||
|
||
for _ in k:gmatch('[^.]+') do | ||
table.remove(stack) | ||
end | ||
end | ||
end | ||
end | ||
|
||
local function disp_colors(buf, nodes, colors, typ, theme) | ||
for _, v in ipairs(nodes) do | ||
local lnum, col = v.vnode:end_() | ||
local color = typ == 'mod' and '' or keypath_get(colors, v.keypath) | ||
|
||
if type(color) == 'string' then | ||
local def, grp | ||
|
||
if typ == 'mod' then | ||
grp, def = 'github.' .. v.keypath, vim.deepcopy(colors[v.keypath], true) | ||
if (def.style or 'NONE') ~= 'NONE' then | ||
for s in def.style:gmatch('[^,]+') do | ||
def[s] = true | ||
end | ||
end | ||
def.style = nil | ||
else | ||
if v.keypath:find('bg') or v.keypath:find('sel%d+$') then | ||
def, grp = { bg = color }, theme .. v.keypath .. '-bg' | ||
else | ||
def, grp = { fg = color }, theme .. v.keypath .. '-fg' | ||
end | ||
end | ||
|
||
def.force = true | ||
api.nvim_set_hl(0, grp, def) | ||
api.nvim_buf_set_extmark(buf, ns, lnum, 0, { | ||
end_row = lnum, | ||
end_col = 0, | ||
strict = false, | ||
undo_restore = true, | ||
invalidate = true, | ||
spell = false, | ||
right_gravity = true, | ||
end_right_gravity = true, | ||
hl_mode = 'combine', | ||
virt_text_pos = 'inline', | ||
virt_text = { { ' ' }, { ' Example ', grp } }, | ||
}) | ||
end | ||
end | ||
end | ||
|
||
local function refresh(opts) | ||
opts = opts or {} | ||
local buf = opts.buf or 0 | ||
local fname = api.nvim_buf_get_name(buf) | ||
local reqname = fname:gsub('%.lua$', ''):gsub('[/\\]', '.'):gsub('^.-%.lua%.', '') | ||
local typ = reqname:find([[^github[-_]*theme%.palette%.github[^.]+$]]) and 'pal' | ||
or (reqname:find([[^github[-_]*theme%.group%.modules%.[^.]+$]]) and 'mod') | ||
|
||
if not typ then | ||
return | ||
end | ||
|
||
api.nvim_buf_clear_namespace(buf, ns, 0, -1) | ||
local env | ||
|
||
do | ||
local reloaded = {} | ||
env = setmetatable({ | ||
require = function(...) | ||
if not reloaded[...] then | ||
reloaded[...] = ... | ||
package.loaded[...] = nil | ||
end | ||
return _G.require(...) | ||
end, | ||
}, { __index = _G }) | ||
|
||
setfenv(env.require, env) | ||
end | ||
|
||
-- Be quiet about syntax errors and just return | ||
local ok, mod = pcall(env.require, reqname) | ||
|
||
if ok then | ||
local defs, theme | ||
if typ == 'pal' then | ||
theme = mod.meta.name | ||
defs = { spec = mod.generate_spec(mod.palette), palette = mod.palette } | ||
else | ||
theme = vim.g.colors_name | ||
local pal = env.require('github-theme.palette.' .. theme) | ||
local spec = pal.generate_spec(pal.palette) | ||
pal.palette.meta, spec.palette = pal.meta, pal.palette | ||
defs = mod.get(spec, require('github-theme.config').options, { enable = true }) | ||
end | ||
|
||
local nodes, parser = {}, ts.get_parser(buf) | ||
get_nodes(parser:parse(false)[1]:root(), buf, typ, function(node_info) | ||
table.insert(nodes, node_info) | ||
end) | ||
|
||
disp_colors(buf, nodes, defs, typ, theme) | ||
end | ||
end | ||
|
||
if enable == false then | ||
for _, buf in ipairs(api.nvim_list_bufs()) do | ||
api.nvim_buf_clear_namespace(buf, ns, 0, -1) | ||
end | ||
|
||
return | ||
end | ||
|
||
api.nvim_create_autocmd({ 'ColorScheme', 'BufNew', 'BufWritePost' }, { | ||
group = augroup, | ||
pattern = '*.lua', | ||
desc = 'Refresh live-displayed colors', | ||
nested = true, | ||
callback = vim.schedule_wrap(refresh), | ||
}) | ||
|
||
refresh() | ||
end | ||
|
||
return M |