new config old version is now on v1 branch
This commit is contained in:
340
lua/components.lua
Normal file
340
lua/components.lua
Normal file
@ -0,0 +1,340 @@
|
||||
if not pcall(require, "el") then
|
||||
return
|
||||
end
|
||||
|
||||
local Job = require "plenary.job"
|
||||
local el_sub = require "el.subscribe"
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.extract_hl(spec)
|
||||
if not spec or vim.tbl_isempty(spec) then return end
|
||||
local hl_name, hl_opts = { "El" }, {}
|
||||
for attr, val in pairs(spec) do
|
||||
if type(val) == "table" then
|
||||
table.insert(hl_name, attr)
|
||||
assert(vim.tbl_count(val) == 1)
|
||||
local hl, what = next(val)
|
||||
local hlID = vim.fn.hlID(hl)
|
||||
if hlID > 0 then
|
||||
table.insert(hl_name, hl)
|
||||
local col = vim.fn.synIDattr(hlID, what)
|
||||
if col and #col > 0 then
|
||||
table.insert(hl_name, what)
|
||||
hl_opts[attr] = col
|
||||
end
|
||||
end
|
||||
else
|
||||
-- bold, underline, etc
|
||||
hl_opts[attr] = val
|
||||
end
|
||||
end
|
||||
hl_name = table.concat(hl_name, "_")
|
||||
-- if highlight exists, verify it has
|
||||
-- the correct colorscheme highlights
|
||||
local newID = vim.fn.hlID(hl_name)
|
||||
if newID > 0 then
|
||||
for what, expected in pairs(hl_opts) do
|
||||
local res = vim.fn.synIDattr(newID, what)
|
||||
if type(expected) == "boolean" then
|
||||
-- synIDattr returns '1' for boolean
|
||||
res = res and res == "1" and true
|
||||
end
|
||||
if res ~= expected then
|
||||
-- need to regen the highlight
|
||||
-- print("color mismatch", hl_name, what, "e:", expected, "c:", res)
|
||||
newID = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
if newID == 0 then
|
||||
vim.api.nvim_set_hl(0, hl_name, hl_opts)
|
||||
end
|
||||
return hl_name
|
||||
end
|
||||
|
||||
local function set_hl(hls, s)
|
||||
if not hls or not s then return s end
|
||||
hls = type(hls) == "string" and { hls } or hls
|
||||
for _, hl in ipairs(hls) do
|
||||
if vim.fn.hlID(hl) > 0 then
|
||||
return ("%%#%s#%s%%0*"):format(hl, s)
|
||||
end
|
||||
end
|
||||
return s
|
||||
end
|
||||
|
||||
local function wrap_fnc(opts, fn)
|
||||
return function(window, buffer)
|
||||
-- buf_autocmd doesn't send win
|
||||
if not window and buffer then
|
||||
window = { win_id = vim.fn.bufwinid(buffer.bufnr) }
|
||||
end
|
||||
if opts.hide_inactive and window and
|
||||
window.win_id ~= vim.api.nvim_get_current_win() then
|
||||
return ""
|
||||
end
|
||||
return fn(window, buffer)
|
||||
end
|
||||
end
|
||||
|
||||
M.mode = function(opts)
|
||||
opts = opts or {}
|
||||
return wrap_fnc(opts, function(_, _)
|
||||
local fmt = opts.fmt or "%s%s"
|
||||
local mode = vim.api.nvim_get_mode().mode
|
||||
local mode_data = opts.modes and opts.modes[mode]
|
||||
local hls = mode_data and mode_data[3]
|
||||
local icon = opts.hl_icon_only and set_hl(hls, opts.icon) or opts.icon
|
||||
mode = mode_data and mode_data[1]:upper() or mode
|
||||
mode = (fmt):format(icon or "", mode)
|
||||
return not opts.hl_icon_only and set_hl(hls, mode) or mode
|
||||
end)
|
||||
end
|
||||
|
||||
M.try_devicons = function()
|
||||
if not M._has_devicons then
|
||||
M._has_devicons, M._devicons = pcall(require, "nvim-web-devicons")
|
||||
end
|
||||
return M._devicons
|
||||
end
|
||||
|
||||
M.file_icon = function(opts)
|
||||
opts = opts or {}
|
||||
return el_sub.buf_autocmd("el_file_icon", "BufRead",
|
||||
wrap_fnc(opts, function(_, buffer)
|
||||
if not M.try_devicons() then return "" end
|
||||
local fmt = opts.fmt or "%s"
|
||||
local ext = vim.fn.fnamemodify(buffer.name, ":p:e")
|
||||
local icon, hl = M._devicons.get_icon(buffer.name, ext:lower(), { default = true })
|
||||
-- local icon = extensions.file_icon(_, bufnr)
|
||||
if icon then
|
||||
if opts.hl_icon then
|
||||
local hlgroup = M.extract_hl({
|
||||
bg = { StatusLine = "bg" },
|
||||
fg = { [hl] = "fg" },
|
||||
})
|
||||
icon = set_hl(hlgroup, icon)
|
||||
end
|
||||
return (fmt):format(icon)
|
||||
end
|
||||
return ""
|
||||
end))
|
||||
end
|
||||
|
||||
M.git_branch = function(opts)
|
||||
opts = opts or {}
|
||||
return el_sub.buf_autocmd("el_git_branch", "BufEnter",
|
||||
wrap_fnc(opts, function(_, buffer)
|
||||
-- Try fugitive first as it's most reliable
|
||||
local branch = vim.g.loaded_fugitive == 1 and
|
||||
vim.fn.FugitiveHead() or nil
|
||||
-- buffer can be null and code will crash with:
|
||||
-- E5108: Error executing lua ... 'attempt to index a nil value'
|
||||
if not buffer or not (buffer.bufnr > 0) then
|
||||
return
|
||||
end
|
||||
-- fugitive is empty or not loaded, try gitsigns
|
||||
if not branch or #branch == 0 then
|
||||
local ok, res = pcall(vim.api.nvim_buf_get_var,
|
||||
buffer.bufnr, "gitsigns_head")
|
||||
if ok then branch = res end
|
||||
end
|
||||
-- last resort run git command
|
||||
if not branch then
|
||||
local j = Job:new {
|
||||
command = "git",
|
||||
args = { "branch", "--show-current" },
|
||||
cwd = vim.fn.fnamemodify(buffer.name, ":h"),
|
||||
}
|
||||
|
||||
local ok, result = pcall(function()
|
||||
return vim.trim(j:sync()[1])
|
||||
end)
|
||||
if ok then
|
||||
branch = result
|
||||
end
|
||||
end
|
||||
|
||||
if branch and #branch > 0 then
|
||||
local fmt = opts.fmt or "%s %s"
|
||||
local icon = opts.icon or ""
|
||||
return set_hl(opts.hl, (fmt):format(icon, branch))
|
||||
end
|
||||
end))
|
||||
end
|
||||
|
||||
local git_changes_formatter = function(opts)
|
||||
local specs = {
|
||||
insert = {
|
||||
regex = "(%d+) insertions?",
|
||||
icon = opts.icon_insert or "+",
|
||||
hl = opts.hl_insert,
|
||||
},
|
||||
change = {
|
||||
regex = "(%d+) files? changed",
|
||||
icon = opts.icon_change or "~",
|
||||
hl = opts.hl_change,
|
||||
},
|
||||
delete = {
|
||||
regex = "(%d+) deletions?",
|
||||
icon = opts.icon_delete or "-",
|
||||
hl = opts.hl_delete,
|
||||
},
|
||||
}
|
||||
return function(_, _, s)
|
||||
local result = {}
|
||||
for k, v in pairs(specs) do
|
||||
local count = nil
|
||||
if type(s) == "string" then
|
||||
-- 'git diff --shortstat' output
|
||||
-- from 'git_changes_all'
|
||||
count = tonumber(string.match(s, v.regex))
|
||||
else
|
||||
-- map from 'git_changes_buf'
|
||||
count = s[k]
|
||||
end
|
||||
if count and count > 0 then
|
||||
table.insert(result, set_hl(v.hl, ("%s%d"):format(v.icon, count)))
|
||||
end
|
||||
end
|
||||
return table.concat(result, ", ")
|
||||
end
|
||||
end
|
||||
|
||||
-- requires gitsigns
|
||||
M.git_changes_buf = function(opts)
|
||||
opts = opts or {}
|
||||
local formatter = opts.formatter or git_changes_formatter(opts)
|
||||
return wrap_fnc(opts, function(window, buffer)
|
||||
local stats = {}
|
||||
if buffer and buffer.bufnr > 0 then
|
||||
local ok, res = pcall(vim.api.nvim_buf_get_var,
|
||||
buffer.bufnr, "vgit_status")
|
||||
if ok then stats = res end
|
||||
end
|
||||
if buffer and buffer.bufnr > 0 then
|
||||
local ok, res = pcall(vim.api.nvim_buf_get_var,
|
||||
buffer.bufnr, "gitsigns_status_dict")
|
||||
if ok then stats = res end
|
||||
end
|
||||
local counts = {
|
||||
insert = stats.added > 0 and stats.added or nil,
|
||||
change = stats.changed > 0 and stats.changed or nil,
|
||||
delete = stats.removed > 0 and stats.removed or nil,
|
||||
}
|
||||
if not vim.tbl_isempty(counts) then
|
||||
local fmt = opts.fmt or "%s"
|
||||
local out = formatter(window, buffer, counts)
|
||||
return out and fmt:format(out) or nil
|
||||
else
|
||||
-- el functions that return a
|
||||
-- string must not return nil
|
||||
return ""
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
M.git_changes_all = function(opts)
|
||||
opts = opts or {}
|
||||
local formatter = opts.formatter or git_changes_formatter(opts)
|
||||
return el_sub.buf_autocmd("el_git_changes", "BufWritePost",
|
||||
wrap_fnc(opts, function(window, buffer)
|
||||
if not buffer or
|
||||
not (buffer.bufnr > 0) or
|
||||
vim.bo[buffer.bufnr].bufhidden ~= "" or
|
||||
vim.bo[buffer.bufnr].buftype == "nofile" or
|
||||
vim.fn.filereadable(buffer.name) ~= 1 then
|
||||
return
|
||||
end
|
||||
|
||||
local j = Job:new {
|
||||
command = "git",
|
||||
args = { "diff", "--shortstat" },
|
||||
-- makes no sense to run for one file as
|
||||
-- 'file(s) changed' will always be 1
|
||||
-- args = { "diff", "--shortstat", buffer.name },
|
||||
cwd = vim.fn.fnamemodify(buffer.name, ":h"),
|
||||
}
|
||||
|
||||
local ok, git_changes = pcall(function()
|
||||
return formatter(window, buffer, vim.trim(j:sync()[1]))
|
||||
end)
|
||||
|
||||
if ok then
|
||||
local fmt = opts.fmt or "%s"
|
||||
return git_changes and fmt:format(git_changes) or nil
|
||||
end
|
||||
end))
|
||||
end
|
||||
|
||||
local function lsp_srvname(bufnr)
|
||||
local buf_clients = vim.lsp.buf_get_clients(bufnr)
|
||||
if not buf_clients or #buf_clients == 0 then
|
||||
return nil
|
||||
end
|
||||
local names = ""
|
||||
for i, c in ipairs(buf_clients) do
|
||||
if i > 1 then names = names .. ", " end
|
||||
names = names .. c.name
|
||||
end
|
||||
return names
|
||||
end
|
||||
|
||||
local function diag_formatter(opts)
|
||||
return function(_, buffer, counts)
|
||||
local items = {}
|
||||
local icons = {
|
||||
["errors"] = { opts.icon_err or "E", opts.hl_err },
|
||||
["warnings"] = { opts.icon_warn or "W", opts.hl_warn },
|
||||
["infos"] = { opts.icon_info or "I", opts.hl_info },
|
||||
["hints"] = { opts.icon_hint or "H", opts.hl_hint },
|
||||
}
|
||||
for _, k in ipairs({ "errors", "warnings", "infos", "hints" }) do
|
||||
if counts[k] > 0 then
|
||||
table.insert(items,
|
||||
set_hl(icons[k][2], ("%s:%s"):format(icons[k][1], counts[k])))
|
||||
end
|
||||
end
|
||||
local fmt = opts.fmt or "%s"
|
||||
local lsp_name = opts.lsp and lsp_srvname(buffer.bufnr)
|
||||
if not lsp_name and vim.tbl_isempty(items) then
|
||||
return ""
|
||||
else
|
||||
local contents = lsp_name
|
||||
if not vim.tbl_isempty(items) then
|
||||
contents = ("%s %s"):format(lsp_name, table.concat(items, " "))
|
||||
end
|
||||
return fmt:format(contents)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local get_buffer_counts = function(diagnostic, _, buffer)
|
||||
local counts = { 0, 0, 0, 0 }
|
||||
local diags = diagnostic.get(buffer.bufnr)
|
||||
if diags and not vim.tbl_isempty(diags) then
|
||||
for _, d in ipairs(diags) do
|
||||
if tonumber(d.severity) then
|
||||
counts[d.severity] = counts[d.severity] + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return {
|
||||
errors = counts[1],
|
||||
warnings = counts[2],
|
||||
infos = counts[3],
|
||||
hints = counts[4],
|
||||
}
|
||||
end
|
||||
|
||||
M.diagnostics = function(opts)
|
||||
opts = opts or {}
|
||||
local formatter = opts.formatter or diag_formatter(opts)
|
||||
return el_sub.buf_autocmd("el_buf_diagnostic", "LspAttach,DiagnosticChanged",
|
||||
wrap_fnc(opts, function(window, buffer)
|
||||
return formatter(window, buffer, get_buffer_counts(vim.diagnostic, window, buffer))
|
||||
end))
|
||||
end
|
||||
|
||||
return M
|
Reference in New Issue
Block a user