190 lines
5.2 KiB
Lua
190 lines
5.2 KiB
Lua
---@class lazyvim.util.ui
|
|
local M = {}
|
|
|
|
---@alias Sign {name:string, text:string, texthl:string, priority:number}
|
|
|
|
-- Returns a list of regular and extmark signs sorted by priority (low to high)
|
|
---@return Sign[]
|
|
---@param buf number
|
|
---@param lnum number
|
|
function M.get_signs(buf, lnum)
|
|
-- Get regular signs
|
|
---@type Sign[]
|
|
local signs = {}
|
|
|
|
if vim.fn.has("nvim-0.10") == 0 then
|
|
-- Only needed for Neovim <0.10
|
|
-- Newer versions include legacy signs in nvim_buf_get_extmarks
|
|
for _, sign in ipairs(vim.fn.sign_getplaced(buf, { group = "*", lnum = lnum })[1].signs) do
|
|
local ret = vim.fn.sign_getdefined(sign.name)[1] --[[@as Sign]]
|
|
if ret then
|
|
ret.priority = sign.priority
|
|
signs[#signs + 1] = ret
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Get extmark signs
|
|
local extmarks = vim.api.nvim_buf_get_extmarks(
|
|
buf,
|
|
-1,
|
|
{ lnum - 1, 0 },
|
|
{ lnum - 1, -1 },
|
|
{ details = true, type = "sign" }
|
|
)
|
|
for _, extmark in pairs(extmarks) do
|
|
signs[#signs + 1] = {
|
|
name = extmark[4].sign_hl_group or "",
|
|
text = extmark[4].sign_text,
|
|
texthl = extmark[4].sign_hl_group,
|
|
priority = extmark[4].priority,
|
|
}
|
|
end
|
|
|
|
-- Sort by priority
|
|
table.sort(signs, function(a, b)
|
|
return (a.priority or 0) < (b.priority or 0)
|
|
end)
|
|
|
|
return signs
|
|
end
|
|
|
|
---@return Sign?
|
|
---@param buf number
|
|
---@param lnum number
|
|
function M.get_mark(buf, lnum)
|
|
local marks = vim.fn.getmarklist(buf)
|
|
vim.list_extend(marks, vim.fn.getmarklist())
|
|
for _, mark in ipairs(marks) do
|
|
if mark.pos[1] == buf and mark.pos[2] == lnum and mark.mark:match("[a-zA-Z]") then
|
|
return { text = mark.mark:sub(2), texthl = "DiagnosticHint" }
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param sign? Sign
|
|
---@param len? number
|
|
function M.icon(sign, len)
|
|
sign = sign or {}
|
|
len = len or 2
|
|
local text = vim.fn.strcharpart(sign.text or "", 0, len) ---@type string
|
|
text = text .. string.rep(" ", len - vim.fn.strchars(text))
|
|
return sign.texthl and ("%#" .. sign.texthl .. "#" .. text .. "%*") or text
|
|
end
|
|
|
|
function M.foldtext()
|
|
local ok = pcall(vim.treesitter.get_parser, vim.api.nvim_get_current_buf())
|
|
local ret = ok and vim.treesitter.foldtext and vim.treesitter.foldtext()
|
|
if not ret or type(ret) == "string" then
|
|
ret = { { vim.api.nvim_buf_get_lines(0, vim.v.lnum - 1, vim.v.lnum, false)[1], {} } }
|
|
end
|
|
table.insert(ret, { " " .. require("lazyvim.config").icons.misc.dots })
|
|
|
|
if not vim.treesitter.foldtext then
|
|
return table.concat(
|
|
vim.tbl_map(function(line)
|
|
return line[1]
|
|
end, ret),
|
|
" "
|
|
)
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.statuscolumn()
|
|
local win = vim.g.statusline_winid
|
|
local buf = vim.api.nvim_win_get_buf(win)
|
|
local is_file = vim.bo[buf].buftype == ""
|
|
local show_signs = vim.wo[win].signcolumn ~= "no"
|
|
|
|
local components = { "", "", "" } -- left, middle, right
|
|
|
|
if show_signs then
|
|
---@type Sign?,Sign?,Sign?
|
|
local left, right, fold
|
|
for _, s in ipairs(M.get_signs(buf, vim.v.lnum)) do
|
|
if s.name and s.name:find("GitSign") then
|
|
right = s
|
|
else
|
|
left = s
|
|
end
|
|
end
|
|
if vim.v.virtnum ~= 0 then
|
|
left = nil
|
|
end
|
|
vim.api.nvim_win_call(win, function()
|
|
if vim.fn.foldclosed(vim.v.lnum) >= 0 then
|
|
fold = { text = vim.opt.fillchars:get().foldclose or "", texthl = "Folded" }
|
|
end
|
|
end)
|
|
-- Left: mark or non-git sign
|
|
components[1] = M.icon(M.get_mark(buf, vim.v.lnum) or left)
|
|
-- Right: fold icon or git sign (only if file)
|
|
components[3] = is_file and M.icon(fold or right) or ""
|
|
end
|
|
|
|
-- Numbers in Neovim are weird
|
|
-- They show when either number or relativenumber is true
|
|
local is_num = vim.wo[win].number
|
|
local is_relnum = vim.wo[win].relativenumber
|
|
if (is_num or is_relnum) and vim.v.virtnum == 0 then
|
|
if vim.v.relnum == 0 then
|
|
components[2] = is_num and "%l" or "%r" -- the current line
|
|
else
|
|
components[2] = is_relnum and "%r" or "%l" -- other lines
|
|
end
|
|
components[2] = "%=" .. components[2] .. " " -- right align
|
|
end
|
|
|
|
return table.concat(components, "")
|
|
end
|
|
|
|
function M.fg(name)
|
|
---@type {foreground?:number}?
|
|
---@diagnostic disable-next-line: deprecated
|
|
local hl = vim.api.nvim_get_hl and vim.api.nvim_get_hl(0, { name = name }) or vim.api.nvim_get_hl_by_name(name, true)
|
|
---@diagnostic disable-next-line: undefined-field
|
|
local fg = hl and (hl.fg or hl.foreground)
|
|
return fg and { fg = string.format("#%06x", fg) } or nil
|
|
end
|
|
|
|
M.skip_foldexpr = {} ---@type table<number,boolean>
|
|
local skip_check = assert(vim.loop.new_check())
|
|
|
|
function M.foldexpr()
|
|
local buf = vim.api.nvim_get_current_buf()
|
|
|
|
-- still in the same tick and no parser
|
|
if M.skip_foldexpr[buf] then
|
|
return "0"
|
|
end
|
|
|
|
-- don't use treesitter folds for non-file buffers
|
|
if vim.bo[buf].buftype ~= "" then
|
|
return "0"
|
|
end
|
|
|
|
-- as long as we don't have a filetype, don't bother
|
|
-- checking if treesitter is available (it won't)
|
|
if vim.bo[buf].filetype == "" then
|
|
return "0"
|
|
end
|
|
|
|
local ok = pcall(vim.treesitter.get_parser, buf)
|
|
|
|
if ok then
|
|
return vim.treesitter.foldexpr()
|
|
end
|
|
|
|
-- no parser available, so mark it as skip
|
|
-- in the next tick, all skip marks will be reset
|
|
M.skip_foldexpr[buf] = true
|
|
skip_check:start(function()
|
|
M.skip_foldexpr = {}
|
|
skip_check:stop()
|
|
end)
|
|
return "0"
|
|
end
|
|
|
|
return M
|