From f35b13d669867209427449840ff0930a732591dc Mon Sep 17 00:00:00 2001 From: Squibid Date: Fri, 24 Nov 2023 21:38:31 -0500 Subject: more stuff too lazy to seperate --- lua/core/statusbar/components.lua | 315 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 lua/core/statusbar/components.lua (limited to 'lua/core/statusbar') diff --git a/lua/core/statusbar/components.lua b/lua/core/statusbar/components.lua new file mode 100644 index 0000000..cce3127 --- /dev/null +++ b/lua/core/statusbar/components.lua @@ -0,0 +1,315 @@ +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 + +function M.mode(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 + +function M.git_branch(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 function git_changes_formatter(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 +function M.git_changes_buf(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 + +function M.git_changes_all(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 + +function M.lsp_srvname(opts) + local fmt = opts.fmt or "%s" + local icon = opts.icon or "" + local buf_clients = vim.lsp.buf_get_clients(0) + if not buf_clients or #buf_clients == 0 then + return "" + end + local names = "" + for i, c in ipairs(buf_clients) do + if i > 1 then names = names .. ", " end + names = names .. c.name + end + return icon..fmt:format(names) +end + +local function diag_formatter(opts) + return function(_, buffer, counts) + local items = {} + local icons = { + ["errors"] = { opts.icon_err or "x", opts.hl_err or "DiagnosticError"}, + ["warnings"] = { opts.icon_warn or "!", opts.hl_warn or "DiagnosticWarn"}, + ["infos"] = { opts.icon_info or "i", opts.hl_info or "DiagnosticInfo"}, + ["hints"] = { opts.icon_hint or "h", opts.hl_hint or "DiagnosticHint"}, + } + 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" + if vim.tbl_isempty(items) then + return "" + else + return fmt:format(table.concat(items, " ")) + end + end +end + +local function get_buffer_counts(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 + +function M.diagnostics(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 + +function M.line(opts) + opts = opts or {} + local fmt = opts.fmt or "%s" + return wrap_fnc(opts, function(_, _) + local al = vim.api.nvim_buf_line_count(0) + local cl = vim.api.nvim_win_get_cursor(0)[1] + return (fmt):format(cl.."/"..al.." "..math.floor((cl / al) * 100).."%%") + end) +end + +return M -- cgit v1.2.1