mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
## Description
Currently in the picker you cant filter git log pickers by author, the
only option to do that right now is to pass it into the opts when you
call it like lua Snacks.picker.git_log({ author="test" }) but most of
the time I would like to filter interactively and also use the field
filtering offered by snacks like file:lua$ and with this new change,
author:test.
## Related Issue(s)
<!--
If this PR fixes any issues, please link to the issue here.
- Fixes #<issue_number>
-->
## Screenshots
<img width="760" height="842" alt="Screenshot 2025-10-13 at 11 03 28 PM"
src="https://github.com/user-attachments/assets/e57278aa-0fcd-4513-981d-fe8cfe078c64"
/>
<!-- Add screenshots of the changes if applicable. -->
---------
Co-authored-by: Folke Lemaitre <folke.lemaitre@gmail.com>
707 lines
22 KiB
Lua
707 lines
22 KiB
Lua
---@class snacks.picker.formatters
|
|
---@field [string] snacks.picker.format
|
|
local M = {}
|
|
|
|
local uv = vim.uv or vim.loop
|
|
|
|
function M.severity(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local severity = item.severity
|
|
severity = type(severity) == "number" and vim.diagnostic.severity[severity] or severity
|
|
if not severity or type(severity) == "number" then
|
|
return ret
|
|
end
|
|
---@cast severity string
|
|
local lower = severity:lower()
|
|
local cap = severity:sub(1, 1):upper() .. lower:sub(2)
|
|
|
|
if picker.opts.formatters.severity.pos == "right" then
|
|
return {
|
|
{
|
|
col = 0,
|
|
virt_text = { { picker.opts.icons.diagnostics[cap], "Diagnostic" .. cap } },
|
|
virt_text_pos = "right_align",
|
|
hl_mode = "combine",
|
|
},
|
|
}
|
|
end
|
|
|
|
if picker.opts.formatters.severity.icons then
|
|
ret[#ret + 1] = { picker.opts.icons.diagnostics[cap], "Diagnostic" .. cap, virtual = true }
|
|
ret[#ret + 1] = { " ", virtual = true }
|
|
end
|
|
|
|
if picker.opts.formatters.severity.level then
|
|
ret[#ret + 1] = { lower:upper(), "Diagnostic" .. cap, virtual = true }
|
|
ret[#ret + 1] = { " ", virtual = true }
|
|
end
|
|
|
|
return ret
|
|
end
|
|
|
|
function M.filename(item, picker)
|
|
---@type snacks.picker.Highlight[]
|
|
local ret = {}
|
|
if not item.file then
|
|
return ret
|
|
end
|
|
local path = Snacks.picker.util.path(item) or item.file
|
|
local name, cat = path, "file"
|
|
if item.buf and vim.api.nvim_buf_is_loaded(item.buf) then
|
|
name = vim.bo[item.buf].filetype
|
|
cat = "filetype"
|
|
elseif item.dir then
|
|
cat = "directory"
|
|
end
|
|
|
|
if picker.opts.icons.files.enabled ~= false then
|
|
local icon, hl = Snacks.util.icon(name, cat, {
|
|
fallback = picker.opts.icons.files,
|
|
})
|
|
if item.dir and item.open then
|
|
icon = picker.opts.icons.files.dir_open
|
|
end
|
|
icon = Snacks.picker.util.align(icon, picker.opts.formatters.file.icon_width or 2)
|
|
ret[#ret + 1] = { icon, hl, virtual = true }
|
|
end
|
|
|
|
local base_hl = item.dir and "SnacksPickerDirectory" or "SnacksPickerFile"
|
|
local function is(prop)
|
|
local it = item
|
|
while it do
|
|
if it[prop] then
|
|
return true
|
|
end
|
|
it = it.parent
|
|
end
|
|
end
|
|
|
|
if is("ignored") then
|
|
base_hl = "SnacksPickerPathIgnored"
|
|
elseif item.filename_hl then
|
|
base_hl = item.filename_hl
|
|
elseif is("hidden") then
|
|
base_hl = "SnacksPickerPathHidden"
|
|
end
|
|
local dir_hl = "SnacksPickerDir"
|
|
|
|
if picker.opts.formatters.file.filename_only then
|
|
path = vim.fn.fnamemodify(item.file, ":t")
|
|
ret[#ret + 1] = { path, base_hl, field = "file" }
|
|
else
|
|
ret[#ret + 1] = {
|
|
"",
|
|
resolve = function(max_width)
|
|
local truncpath = Snacks.picker.util.truncpath(
|
|
path,
|
|
math.max(max_width, picker.opts.formatters.file.min_width or 20),
|
|
{ cwd = picker:cwd(), kind = picker.opts.formatters.file.truncate }
|
|
)
|
|
local dir, base = truncpath:match("^(.*)/(.+)$")
|
|
local resolved = {} ---@type snacks.picker.Highlight[]
|
|
if base and dir then
|
|
if picker.opts.formatters.file.filename_first then
|
|
resolved[#resolved + 1] = { base, base_hl, field = "file" }
|
|
resolved[#resolved + 1] = { " " }
|
|
resolved[#resolved + 1] = { dir, dir_hl, field = "file" }
|
|
else
|
|
resolved[#resolved + 1] = { dir .. "/", dir_hl, field = "file" }
|
|
resolved[#resolved + 1] = { base, base_hl, field = "file" }
|
|
end
|
|
else
|
|
resolved[#resolved + 1] = { truncpath, base_hl, field = "file" }
|
|
end
|
|
return resolved
|
|
end,
|
|
}
|
|
end
|
|
if item.pos and item.pos[1] > 0 then
|
|
ret[#ret + 1] = { ":", "SnacksPickerDelim" }
|
|
ret[#ret + 1] = { tostring(item.pos[1]), "SnacksPickerRow" }
|
|
if item.pos[2] > 0 then
|
|
ret[#ret + 1] = { ":", "SnacksPickerDelim" }
|
|
ret[#ret + 1] = { tostring(item.pos[2]), "SnacksPickerCol" }
|
|
end
|
|
end
|
|
ret[#ret + 1] = { " " }
|
|
if item.type == "link" then
|
|
local real = uv.fs_realpath(item.file)
|
|
local broken = not real
|
|
real = real or uv.fs_readlink(item.file)
|
|
if real then
|
|
ret[#ret + 1] = { "-> ", "SnacksPickerDelim" }
|
|
ret[#ret + 1] =
|
|
{ Snacks.picker.util.truncpath(real, 20), broken and "SnacksPickerLinkBroken" or "SnacksPickerLink" }
|
|
ret[#ret + 1] = { " " }
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.file(item, picker)
|
|
---@type snacks.picker.Highlight[]
|
|
local ret = {}
|
|
|
|
if item.label then
|
|
ret[#ret + 1] = { item.label, "SnacksPickerLabel" }
|
|
ret[#ret + 1] = { " ", virtual = true }
|
|
end
|
|
|
|
if item.parent then
|
|
vim.list_extend(ret, M.tree(item, picker))
|
|
end
|
|
|
|
if item.status then
|
|
vim.list_extend(ret, M.file_git_status(item, picker))
|
|
end
|
|
|
|
if item.severity then
|
|
vim.list_extend(ret, M.severity(item, picker))
|
|
end
|
|
|
|
vim.list_extend(ret, M.filename(item, picker))
|
|
|
|
if item.comment then
|
|
table.insert(ret, { item.comment, "SnacksPickerComment" })
|
|
table.insert(ret, { " " })
|
|
end
|
|
|
|
if item.line then
|
|
if item.positions then
|
|
local offset = Snacks.picker.highlight.offset(ret)
|
|
Snacks.picker.highlight.matches(ret, item.positions, offset)
|
|
end
|
|
Snacks.picker.highlight.format(item, item.line, ret)
|
|
table.insert(ret, { " " })
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.git_log(item, picker)
|
|
local a = Snacks.picker.util.align
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { picker.opts.icons.git.commit, "SnacksPickerGitCommit" }
|
|
local c = item.commit or item.branch or "HEAD"
|
|
ret[#ret + 1] = { a(c, 8, { truncate = true }), "SnacksPickerGitCommit" }
|
|
|
|
ret[#ret + 1] = { " " }
|
|
if item.date then
|
|
ret[#ret + 1] = { a(item.date, 16), "SnacksPickerGitDate" }
|
|
end
|
|
ret[#ret + 1] = { " " }
|
|
|
|
local msg = item.msg ---@type string
|
|
local type, scope, breaking, body = msg:match("^(%S+)%s*(%(.-%))(!?):%s*(.*)$")
|
|
if not type then
|
|
type, breaking, body = msg:match("^(%S+)(!?):%s*(.*)$")
|
|
end
|
|
local msg_hl = "SnacksPickerGitMsg"
|
|
if type and body then
|
|
local dimmed = vim.tbl_contains({ "chore", "bot", "build", "ci", "style", "test" }, type)
|
|
msg_hl = dimmed and "SnacksPickerDimmed" or "SnacksPickerGitMsg"
|
|
ret[#ret + 1] =
|
|
{ type, breaking ~= "" and "SnacksPickerGitBreaking" or dimmed and "SnacksPickerBold" or "SnacksPickerGitType" }
|
|
if scope and scope ~= "" then
|
|
ret[#ret + 1] = { scope, "SnacksPickerGitScope" }
|
|
end
|
|
if breaking ~= "" then
|
|
ret[#ret + 1] = { "!", "SnacksPickerGitBreaking" }
|
|
end
|
|
ret[#ret + 1] = { ":", "SnacksPickerDelim" }
|
|
ret[#ret + 1] = { " " }
|
|
msg = body
|
|
end
|
|
ret[#ret + 1] = { msg, msg_hl }
|
|
if item.author then
|
|
ret[#ret + 1] = { " <" .. item.author .. ">", "SnacksPickerGitAuthor" }
|
|
end
|
|
Snacks.picker.highlight.markdown(ret)
|
|
Snacks.picker.highlight.highlight(ret, {
|
|
["#%d+"] = "SnacksPickerGitIssue",
|
|
})
|
|
return ret
|
|
end
|
|
|
|
function M.git_branch(item, picker)
|
|
local a = Snacks.picker.util.align
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
if item.current then
|
|
ret[#ret + 1] = { a("", 2), "SnacksPickerGitBranchCurrent" }
|
|
else
|
|
ret[#ret + 1] = { a("", 2) }
|
|
end
|
|
if item.detached then
|
|
ret[#ret + 1] = { a("(detached HEAD)", 30, { truncate = true }), "SnacksPickerGitDetached" }
|
|
else
|
|
ret[#ret + 1] = { a(item.branch, 30, { truncate = true }), "SnacksPickerGitBranch" }
|
|
end
|
|
ret[#ret + 1] = { " " }
|
|
local offset = Snacks.picker.highlight.offset(ret)
|
|
local log = M.git_log(item, picker)
|
|
Snacks.picker.highlight.fix_offset(log, offset)
|
|
vim.list_extend(ret, log)
|
|
return ret
|
|
end
|
|
|
|
function M.git_stash(item, picker)
|
|
local a = Snacks.picker.util.align
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { a(item.stash, 10), "SnacksPickerIdx" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(item.branch, 10, { truncate = true }), "SnacksPickerGitBranch" }
|
|
ret[#ret + 1] = { " " }
|
|
local offset = Snacks.picker.highlight.offset(ret)
|
|
local log = M.git_log(item, picker)
|
|
Snacks.picker.highlight.fix_offset(log, offset)
|
|
vim.list_extend(ret, log)
|
|
return ret
|
|
end
|
|
|
|
function M.tree(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local icons = picker.opts.icons.tree
|
|
local indent = {} ---@type string[]
|
|
local node = item
|
|
while node and node.parent do
|
|
local is_last, icon = node.last, ""
|
|
if node ~= item then
|
|
icon = is_last and " " or icons.vertical
|
|
else
|
|
icon = is_last and icons.last or icons.middle
|
|
end
|
|
table.insert(indent, 1, icon)
|
|
node = node.parent
|
|
end
|
|
ret[#ret + 1] = { table.concat(indent), "SnacksPickerTree" }
|
|
return ret
|
|
end
|
|
|
|
function M.undo(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local entry = item.item ---@type vim.fn.undotree.entry
|
|
local a = Snacks.picker.util.align
|
|
if item.current then
|
|
ret[#ret + 1] = { a("", 2), "SnacksPickerUndoCurrent" }
|
|
else
|
|
ret[#ret + 1] = { a("", 2) }
|
|
end
|
|
vim.list_extend(ret, M.tree(item, picker))
|
|
local w = vim.api.nvim_strwidth(ret[#ret][1])
|
|
|
|
ret[#ret + 1] = { tostring(entry.seq), "SnacksPickerIdx" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(" ", 8 - w - #tostring(entry.seq)) }
|
|
ret[#ret + 1] = { a(Snacks.picker.util.reltime(entry.time), 15), "SnacksPickerTime" }
|
|
ret[#ret + 1] = { " " }
|
|
local function num(v, prefix)
|
|
v = v or 0
|
|
return a((v and v > 0 and prefix .. v or ""), 4)
|
|
end
|
|
ret[#ret + 1] = { num(item.added, "+"), "SnacksPickerUndoAdded" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { num(item.removed, "-"), "SnacksPickerUndoRemoved" }
|
|
if entry.save then
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(picker.opts.icons.undo.saved, 2), "SnacksPickerUndoSaved" }
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.lsp_symbol(item, picker)
|
|
local opts = picker.opts --[[@as snacks.picker.lsp.symbols.Config]]
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
if item.tree and not opts.workspace then
|
|
vim.list_extend(ret, M.tree(item, picker))
|
|
end
|
|
local kind = item.kind or "Unknown" ---@type string
|
|
local kind_hl = "SnacksPickerIcon" .. kind
|
|
ret[#ret + 1] = { picker.opts.icons.kinds[kind], kind_hl }
|
|
ret[#ret + 1] = { " " }
|
|
local name = vim.trim(item.name:gsub("\r?\n", " "))
|
|
name = name == "" and item.detail or name
|
|
Snacks.picker.highlight.format(item, name, ret)
|
|
|
|
if opts.workspace then
|
|
local offset = Snacks.picker.highlight.offset(ret, { char_idx = true })
|
|
ret[#ret + 1] = { Snacks.picker.util.align(" ", 40 - offset) }
|
|
vim.list_extend(ret, M.filename(item, picker))
|
|
end
|
|
return ret
|
|
end
|
|
|
|
---@param opts snacks.picker.ui_select.Opts
|
|
---@return snacks.picker.format
|
|
function M.ui_select(opts)
|
|
return function(item, picker)
|
|
local count = picker:count()
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local idx = tostring(item.idx)
|
|
idx = (" "):rep(#tostring(count) - #idx) .. idx
|
|
ret[#ret + 1] = { idx .. ".", "SnacksPickerIdx" }
|
|
ret[#ret + 1] = { " " }
|
|
|
|
if opts.kind == "codeaction" then
|
|
---@type lsp.Command|lsp.CodeAction, lsp.HandlerContext
|
|
local action, ctx = item.item.action, item.item.ctx
|
|
local client = vim.lsp.get_client_by_id(ctx.client_id)
|
|
ret[#ret + 1] = { action.title }
|
|
if client then
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { ("[%s]"):format(client.name), "SnacksPickerSpecial" }
|
|
end
|
|
elseif opts.kind == "snacks" and opts.format_item then
|
|
local t = opts.format_item(item.item, true)
|
|
if type(t) == "string" then
|
|
ret[#ret + 1] = { t }
|
|
elseif type(t) == "table" then
|
|
vim.list_extend(ret, t)
|
|
end
|
|
else
|
|
ret[#ret + 1] = { item.formatted }
|
|
end
|
|
return ret
|
|
end
|
|
end
|
|
|
|
function M.lines(item)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local line_count = vim.api.nvim_buf_line_count(item.buf)
|
|
local idx = Snacks.picker.util.align(tostring(item.idx), #tostring(line_count), { align = "right" })
|
|
ret[#ret + 1] = { idx, "LineNr", virtual = true }
|
|
ret[#ret + 1] = { " ", virtual = true }
|
|
ret[#ret + 1] = { item.text }
|
|
|
|
local offset = #idx + 2
|
|
|
|
for _, extmark in ipairs(item.highlights or {}) do
|
|
extmark = vim.deepcopy(extmark)
|
|
if type(extmark[1]) ~= "string" then
|
|
---@cast extmark snacks.picker.Extmark
|
|
extmark.col = extmark.col + offset
|
|
if extmark.end_col then
|
|
extmark.end_col = extmark.end_col + offset
|
|
end
|
|
end
|
|
ret[#ret + 1] = extmark
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.text(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local ft = item.ft or picker.opts.formatters.text.ft
|
|
if ft then
|
|
Snacks.picker.highlight.format(item, item.text, ret, { lang = ft })
|
|
else
|
|
ret[#ret + 1] = { item.text, item.text_hl }
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.command(item)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { item.cmd, "SnacksPickerCmd" .. (item.cmd:find("^[a-z]") and "Builtin" or "") }
|
|
if item.desc then
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { item.desc, "SnacksPickerDesc" }
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.diagnostic(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local diag = item.item ---@type vim.Diagnostic
|
|
if item.severity then
|
|
vim.list_extend(ret, M.severity(item, picker))
|
|
end
|
|
|
|
local message = diag.message
|
|
ret[#ret + 1] = { message }
|
|
Snacks.picker.highlight.markdown(ret)
|
|
ret[#ret + 1] = { " " }
|
|
|
|
if diag.source then
|
|
ret[#ret + 1] = { diag.source, "SnacksPickerDiagnosticSource" }
|
|
ret[#ret + 1] = { " " }
|
|
end
|
|
|
|
if diag.code then
|
|
ret[#ret + 1] = { ("(%s)"):format(diag.code), "SnacksPickerDiagnosticCode" }
|
|
ret[#ret + 1] = { " " }
|
|
end
|
|
vim.list_extend(ret, M.filename(item, picker))
|
|
return ret
|
|
end
|
|
|
|
function M.autocmd(item)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
---@type vim.api.keyset.get_autocmds.ret
|
|
local au = item.item
|
|
local a = Snacks.picker.util.align
|
|
ret[#ret + 1] = { a(au.event, 15), "SnacksPickerAuEvent" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(au.pattern, 10), "SnacksPickerAuPattern" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(tostring(au.group_name or ""), 15), "SnacksPickerAuGroup" }
|
|
ret[#ret + 1] = { " " }
|
|
if au.command ~= "" then
|
|
Snacks.picker.highlight.format(item, au.command, ret, { lang = "vim" })
|
|
else
|
|
ret[#ret + 1] = { "callback", "Function" }
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.hl(item)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { item.hl_group, item.hl_group }
|
|
return ret
|
|
end
|
|
|
|
function M.man(item)
|
|
local a = Snacks.picker.util.align
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { a(item.page, 20), "SnacksPickerManPage" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { ("(%s)"):format(item.section), "SnacksPickerManSection" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { item.desc, "SnacksPickerManDesc" }
|
|
return ret
|
|
end
|
|
|
|
-- Pretty keymaps using which-key icons when available
|
|
function M.keymap(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
---@type vim.api.keyset.get_keymap
|
|
local k = item.item
|
|
local a = Snacks.picker.util.align
|
|
|
|
if package.loaded["which-key"] then
|
|
local Icons = require("which-key.icons")
|
|
local icon, hl = Icons.get({ keymap = k, desc = k.desc })
|
|
if icon then
|
|
ret[#ret + 1] = { a(icon, 3), hl }
|
|
else
|
|
ret[#ret + 1] = { " " }
|
|
end
|
|
end
|
|
local lhs = Snacks.util.normkey(k.lhs)
|
|
ret[#ret + 1] = { k.mode, "SnacksPickerKeymapMode" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(lhs, 15), "SnacksPickerKeymapLhs" }
|
|
ret[#ret + 1] = { " " }
|
|
local icon_nowait = picker.opts.icons.keymaps.nowait
|
|
|
|
if k.nowait == 1 then
|
|
ret[#ret + 1] = { icon_nowait, "SnacksPickerKeymapNowait" }
|
|
else
|
|
ret[#ret + 1] = { (" "):rep(vim.api.nvim_strwidth(icon_nowait)) }
|
|
end
|
|
ret[#ret + 1] = { " " }
|
|
|
|
if k.buffer and k.buffer > 0 then
|
|
ret[#ret + 1] = { a("buf:" .. k.buffer, 6), "SnacksPickerBufNr" }
|
|
else
|
|
ret[#ret + 1] = { a("", 6) }
|
|
end
|
|
ret[#ret + 1] = { " " }
|
|
|
|
local rhs_len = 0
|
|
if k.rhs and k.rhs ~= "" then
|
|
local rhs = k.rhs or ""
|
|
rhs_len = #rhs
|
|
local cmd = rhs:lower():find("<cmd>")
|
|
if cmd then
|
|
ret[#ret + 1] = { rhs:sub(1, cmd + 4), "NonText" }
|
|
rhs = rhs:sub(cmd + 5)
|
|
local cr = rhs:lower():find("<cr>$")
|
|
if cr then
|
|
rhs = rhs:sub(1, cr - 1)
|
|
end
|
|
Snacks.picker.highlight.format(item, rhs, ret, { lang = "vim" })
|
|
if cr then
|
|
ret[#ret + 1] = { "<CR>", "NonText" }
|
|
end
|
|
elseif rhs:lower():find("^<plug>") then
|
|
ret[#ret + 1] = { "<Plug>", "NonText" }
|
|
local plug = rhs:sub(7):gsub("^%(", ""):gsub("%)$", "")
|
|
ret[#ret + 1] = { "(", "SnacksPickerDelim" }
|
|
Snacks.picker.highlight.format(item, plug, ret, { lang = "vim" })
|
|
ret[#ret + 1] = { ")", "SnacksPickerDelim" }
|
|
elseif rhs:find("v:lua%.") then
|
|
ret[#ret + 1] = { "v:lua", "NonText" }
|
|
ret[#ret + 1] = { ".", "SnacksPickerDelim" }
|
|
Snacks.picker.highlight.format(item, rhs:sub(7), ret, { lang = "lua" })
|
|
else
|
|
ret[#ret + 1] = { k.rhs, "SnacksPickerKeymapRhs" }
|
|
end
|
|
else
|
|
ret[#ret + 1] = { "callback", "Function" }
|
|
rhs_len = 8
|
|
end
|
|
|
|
if rhs_len < 15 then
|
|
ret[#ret + 1] = { (" "):rep(15 - rhs_len) }
|
|
end
|
|
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(k.desc or "", 20) }
|
|
|
|
if item.file then
|
|
ret[#ret + 1] = { " " }
|
|
vim.list_extend(ret, M.filename(item, picker))
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.git_status(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local a = Snacks.picker.util.align
|
|
local s = vim.trim(item.status):sub(1, 1)
|
|
local hls = {
|
|
["A"] = "SnacksPickerGitStatusAdded",
|
|
["M"] = "SnacksPickerGitStatusModified",
|
|
["D"] = "SnacksPickerGitStatusDeleted",
|
|
["R"] = "SnacksPickerGitStatusRenamed",
|
|
["C"] = "SnacksPickerGitStatusCopied",
|
|
["?"] = "SnacksPickerGitStatusUntracked",
|
|
}
|
|
local hl = hls[s] or "SnacksPickerGitStatus"
|
|
ret[#ret + 1] = { a(item.status, 2, { align = "right" }), hl }
|
|
ret[#ret + 1] = { " " }
|
|
if item.rename then
|
|
local file = item.file
|
|
item.file = item.rename
|
|
item._path = nil
|
|
vim.list_extend(ret, M.filename(item, picker))
|
|
item.file = file
|
|
item._path = nil
|
|
ret[#ret + 1] = { "-> ", "SnacksPickerDelim" }
|
|
ret[#ret + 1] = { " " }
|
|
end
|
|
vim.list_extend(ret, M.filename(item, picker))
|
|
return ret
|
|
end
|
|
|
|
function M.file_git_status(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local status = require("snacks.picker.source.git").git_status(item.status)
|
|
|
|
local hl = "SnacksPickerGitStatus"
|
|
if status.unmerged then
|
|
hl = "SnacksPickerGitStatusUnmerged"
|
|
elseif status.staged then
|
|
hl = "SnacksPickerGitStatusStaged"
|
|
else
|
|
hl = "SnacksPickerGitStatus" .. status.status:sub(1, 1):upper() .. status.status:sub(2)
|
|
end
|
|
|
|
if picker.opts.formatters.file.git_status_hl then
|
|
item.filename_hl = hl
|
|
end
|
|
|
|
local icon = status.status:sub(1, 1):upper()
|
|
icon = status.status == "untracked" and "?" or status.status == "ignored" and "!" or icon
|
|
if picker.opts.icons.git.enabled then
|
|
icon = picker.opts.icons.git[status.unmerged and "unmerged" or status.status] or icon --[[@as string]]
|
|
if status.staged then
|
|
icon = picker.opts.icons.git.staged
|
|
end
|
|
end
|
|
|
|
ret[#ret + 1] = {
|
|
col = 0,
|
|
virt_text = { { icon, hl }, { " " } },
|
|
virt_text_pos = "right_align",
|
|
hl_mode = "combine",
|
|
}
|
|
return ret
|
|
end
|
|
|
|
function M.register(item)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { "[", "SnacksPickerDelim" }
|
|
ret[#ret + 1] = { item.reg, "SnacksPickerRegister" }
|
|
ret[#ret + 1] = { "]", "SnacksPickerDelim" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { item.value }
|
|
return ret
|
|
end
|
|
|
|
function M.buffer(item, picker)
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { Snacks.picker.util.align(tostring(item.buf), 3), "SnacksPickerBufNr" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { Snacks.picker.util.align(item.flags, 2, { align = "right" }), "SnacksPickerBufFlags" }
|
|
ret[#ret + 1] = { " " }
|
|
vim.list_extend(ret, M.filename(item, picker))
|
|
return ret
|
|
end
|
|
|
|
function M.selected(item, picker)
|
|
local a = Snacks.picker.util.align
|
|
local selected = picker.opts.icons.ui.selected
|
|
local unselected = picker.opts.icons.ui.unselected
|
|
local width = math.max(vim.api.nvim_strwidth(selected), vim.api.nvim_strwidth(unselected))
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
if picker.list:is_selected(item) then
|
|
ret[#ret + 1] = { a(selected, width), "SnacksPickerSelected", virtual = true }
|
|
elseif picker.opts.formatters.selected.unselected then
|
|
ret[#ret + 1] = { a(unselected, width), "SnacksPickerUnselected", virtual = true }
|
|
else
|
|
ret[#ret + 1] = { a("", width) }
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.debug(item, picker)
|
|
local score = item.score
|
|
if not picker.matcher.sorting then
|
|
score = picker.matcher.DEFAULT_SCORE
|
|
if item.score_add then
|
|
score = score + item.score_add
|
|
end
|
|
if item.score_mul then
|
|
score = score * item.score_mul
|
|
end
|
|
end
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
ret[#ret + 1] = { ("%.2f "):format(score), "Number" }
|
|
return ret
|
|
end
|
|
|
|
function M.icon(item, picker)
|
|
local a = Snacks.picker.util.align
|
|
---@cast item snacks.picker.Icon
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
|
|
ret[#ret + 1] = { a(item.icon, 2), "SnacksPickerIcon" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(item.source, 10), "SnacksPickerIconSource" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(item.name, 30), "SnacksPickerIconName" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(item.category, 8), "SnacksPickerIconCategory" }
|
|
return ret
|
|
end
|
|
|
|
function M.notification(item, picker)
|
|
local a = Snacks.picker.util.align
|
|
local ret = {} ---@type snacks.picker.Highlight[]
|
|
local notif = item.item ---@type snacks.notifier.Notif
|
|
ret[#ret + 1] = { a(os.date("%R", notif.added), 5), "SnacksPickerTime" }
|
|
ret[#ret + 1] = { " " }
|
|
if item.severity then
|
|
vim.list_extend(ret, M.severity(item, picker))
|
|
end
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { a(notif.title or "", 15), "SnacksNotifierHistoryTitle" }
|
|
ret[#ret + 1] = { " " }
|
|
ret[#ret + 1] = { notif.msg, "SnacksPickerNotificationMessage" }
|
|
Snacks.picker.highlight.markdown(ret)
|
|
-- ret[#ret + 1] = { " " }
|
|
return ret
|
|
end
|
|
|
|
return M
|