mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
feat: statuscolumn
This commit is contained in:
parent
cc5353f6b3
commit
99b1700015
1 changed files with 208 additions and 0 deletions
208
lua/snacks/statuscolumn.lua
Normal file
208
lua/snacks/statuscolumn.lua
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
---@class snacks.statuscolumn
|
||||
---@overload fun(): string
|
||||
local M = setmetatable({}, {
|
||||
__call = function(t)
|
||||
return t.get()
|
||||
end,
|
||||
})
|
||||
|
||||
---@class snacks.statuscolumn.Config
|
||||
local defaults = {
|
||||
left = { "mark", "sign" },
|
||||
right = { "fold", "git" },
|
||||
folds = {
|
||||
open = false, -- show open fold icons
|
||||
git_hl = false, -- use Git Signs hl for fold icons
|
||||
},
|
||||
git = {
|
||||
patterns = { "GitSign", "MiniDiffSign" },
|
||||
},
|
||||
refresh = 50, -- refresh at most every 50ms
|
||||
}
|
||||
|
||||
local config = Snacks.config.get("statuscolumn", defaults)
|
||||
|
||||
---@alias snacks.statuscolumn.Sign.type "mark"|"sign"|"fold"|"git"
|
||||
---@alias snacks.statuscolumn.Sign {name:string, text:string, texthl:string, priority:number, type:snacks.statuscolumn.Sign.type}
|
||||
|
||||
-- Cache for signs per buffer and line
|
||||
---@type table<number,table<number,snacks.statuscolumn.Sign[]>>
|
||||
local cache = {}
|
||||
|
||||
local did_setup = false
|
||||
|
||||
function M.setup()
|
||||
if did_setup then
|
||||
return
|
||||
end
|
||||
did_setup = true
|
||||
local timer = assert((vim.uv or vim.loop).new_timer())
|
||||
timer:start(config.refresh, config.refresh, function()
|
||||
cache = {}
|
||||
end)
|
||||
end
|
||||
|
||||
---@param name string
|
||||
function M.is_git_sign(name)
|
||||
for _, pattern in ipairs(config.git.patterns) do
|
||||
if name:find(pattern) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Returns a list of regular and extmark signs sorted by priority (low to high)
|
||||
---@return table<number, snacks.statuscolumn.Sign[]>
|
||||
---@param buf number
|
||||
function M.buf_signs(buf)
|
||||
if cache[buf] then
|
||||
return cache[buf]
|
||||
end
|
||||
|
||||
-- Get regular signs
|
||||
---@type table<number, snacks.statuscolumn.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 = "*" })[1].signs) do
|
||||
local ret = vim.fn.sign_getdefined(sign.name)[1] --[[@as snacks.statuscolumn.Sign]]
|
||||
if ret then
|
||||
ret.priority = sign.priority
|
||||
ret.type = M.is_git_sign(sign.name) and "git" or "sign"
|
||||
signs[sign.lnum] = signs[sign.lnum] or {}
|
||||
table.insert(signs[sign.lnum], ret)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Get extmark signs
|
||||
local extmarks = vim.api.nvim_buf_get_extmarks(buf, -1, 0, -1, { details = true, type = "sign" })
|
||||
for _, extmark in pairs(extmarks) do
|
||||
local lnum = extmark[2] + 1
|
||||
signs[lnum] = signs[lnum] or {}
|
||||
local name = extmark[4].sign_hl_group or extmark[4].sign_name or ""
|
||||
table.insert(signs[lnum], {
|
||||
name = name,
|
||||
type = M.is_git_sign(name) and "git" or "sign",
|
||||
text = extmark[4].sign_text,
|
||||
texthl = extmark[4].sign_hl_group,
|
||||
priority = extmark[4].priority,
|
||||
})
|
||||
end
|
||||
|
||||
-- Add marks
|
||||
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.mark:match("[a-zA-Z]") then
|
||||
local lnum = mark.pos[2]
|
||||
signs[lnum] = signs[lnum] or {}
|
||||
table.insert(signs[lnum], { text = mark.mark:sub(2), texthl = "DiagnosticHint", type = "mark" })
|
||||
end
|
||||
end
|
||||
|
||||
cache[buf] = signs
|
||||
return signs
|
||||
end
|
||||
|
||||
-- Returns a list of regular and extmark signs sorted by priority (high to low)
|
||||
---@return snacks.statuscolumn.Sign[]
|
||||
---@param win number
|
||||
---@param buf number
|
||||
---@param lnum number
|
||||
function M.line_signs(win, buf, lnum)
|
||||
local signs = M.buf_signs(buf)[lnum] or {}
|
||||
|
||||
-- Get fold signs
|
||||
vim.api.nvim_win_call(win, function()
|
||||
if vim.fn.foldclosed(vim.v.lnum) >= 0 then
|
||||
signs[#signs + 1] = { text = vim.opt.fillchars:get().foldclose or "", texthl = "Folded", type = "fold" }
|
||||
elseif config.folds.open and tostring(vim.treesitter.foldexpr(vim.v.lnum)):sub(1, 1) == ">" then
|
||||
signs[#signs + 1] = { text = vim.opt.fillchars:get().foldopen or "", type = "fold" }
|
||||
end
|
||||
end)
|
||||
|
||||
-- Sort by priority
|
||||
table.sort(signs, function(a, b)
|
||||
return (a.priority or 0) > (b.priority or 0)
|
||||
end)
|
||||
return signs
|
||||
end
|
||||
|
||||
---@param sign? snacks.statuscolumn.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.get()
|
||||
M.setup()
|
||||
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
|
||||
local signs = M.line_signs(win, buf, vim.v.lnum)
|
||||
|
||||
---@param types snacks.statuscolumn.Sign.type[]
|
||||
local function find(types)
|
||||
for _, t in ipairs(types) do
|
||||
for _, s in ipairs(signs) do
|
||||
if s.type == t then
|
||||
return s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local left = find(config.left)
|
||||
local right = find(config.right)
|
||||
|
||||
if config.folds.git_hl then
|
||||
local git = find({ "git" })
|
||||
if git and left and left.type == "fold" then
|
||||
left.texthl = git.texthl
|
||||
end
|
||||
if git and right and right.type == "fold" then
|
||||
right.texthl = git.texthl
|
||||
end
|
||||
end
|
||||
|
||||
components[1] = M.icon(left) -- left
|
||||
components[3] = is_file and M.icon(right) or "" -- right
|
||||
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.fn.has("nvim-0.11") == 1 then
|
||||
components[2] = "%l" -- 0.11 handles both the current and other lines with %l
|
||||
else
|
||||
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
|
||||
end
|
||||
components[2] = "%=" .. components[2] .. " " -- right align
|
||||
end
|
||||
|
||||
if vim.v.virtnum ~= 0 then
|
||||
components[2] = "%= "
|
||||
end
|
||||
|
||||
return table.concat(components, "")
|
||||
end
|
||||
|
||||
return M
|
||||
Loading…
Add table
Add a link
Reference in a new issue