feat(picker.icons): make it easier to add custom icon sources

This commit is contained in:
Folke Lemaitre 2025-11-05 14:01:36 +01:00
parent a4176301e3
commit 82e69661cd
No known key found for this signature in database
GPG key ID: 9B52594D560070AB
3 changed files with 70 additions and 6 deletions

View file

@ -535,9 +535,14 @@ M.highlights = {
}
---@class snacks.picker.icons.Config: snacks.picker.Config
---@field icon_sources? string[]
---@field icon_sources? string[] list of sources to use
--- Custom icon sources can be added here. The key is the source name,
--- and the value is the file path or URL to load icons from.
--- The file should be a JSON array of:
--- `{[1]:string, [2]:string}|{icon:string, name:string, category:string}`
--- The format is compatible with https://github.com/nvim-telescope/telescope-symbols.nvim
---@field custom_sources? table<string,string> additional icon sources `table<source,file|url>`
M.icons = {
icon_sources = { "nerd_fonts", "emoji" },
main = { current = true },
finder = "icons",
format = "icon",

View file

@ -709,7 +709,8 @@ function M.icon(item, picker)
---@cast item snacks.picker.Icon
local ret = {} ---@type snacks.picker.Highlight[]
ret[#ret + 1] = { a(item.icon, 2), "SnacksPickerIcon" }
local icon_width = vim.api.nvim_strwidth(item.icon)
ret[#ret + 1] = { a(item.icon, icon_width > 3 and 15 or 3), "SnacksPickerIcon" }
ret[#ret + 1] = { " " }
ret[#ret + 1] = { a(item.source, 10), "SnacksPickerIconSource" }
ret[#ret + 1] = { " " }

View file

@ -1,5 +1,13 @@
local M = {}
---@class snacks.picker.icons.Source
---@field url string
---@field v? number
---@field priority? number
---@field build fun(data:table):snacks.picker.Icon[]
---@alias snacks.picker.icons.source.Item {[1]:string, [2]:string}|{icon:string, name:string, category:string}
local NERDFONTS_SETS = {
cod = "Codicons",
dev = "Devicons",
@ -16,9 +24,32 @@ local NERDFONTS_SETS = {
md = "Material Design Icons",
}
---@type table<string, {url: string, v?:number, build: fun(data:table):snacks.picker.Icon[]}>
---@param source string
local function custom_source(source, url)
---@type snacks.picker.icons.Source
return {
v = 3,
url = url,
build = function(data)
---@cast data snacks.picker.icons.source.Item[]
local ret = {} ---@type snacks.picker.Icon[]
for _, info in ipairs(data) do
table.insert(ret, {
name = vim.trim(info.name or info[2] or ""),
icon = vim.trim(info.icon or info[1] or ""),
category = info.category,
source = source,
})
end
return ret
end,
}
end
---@type table<string, snacks.picker.icons.Source>
M.sources = {
nerd_fonts = {
priority = 10,
url = "https://github.com/ryanoasis/nerd-fonts/raw/refs/heads/master/glyphnames.json",
v = 4,
build = function(data)
@ -43,6 +74,7 @@ M.sources = {
},
emoji = {
url = "https://raw.githubusercontent.com/muan/unicode-emoji-json/refs/heads/main/data-by-emoji.json",
priority = 20,
v = 4,
build = function(data)
---@cast data table<string, {name:string, slug:string, group:string}>
@ -70,7 +102,21 @@ M.sources = {
---@param source_name string
local function load(source_name)
local source = M.sources[source_name]
local file = vim.fn.stdpath("cache") .. "/snacks/picker/icons/" .. source_name .. "-v" .. (source.v or 1) .. ".json"
if not source then
Snacks.notify.error("Unknown icon source: " .. source_name)
return {}
end
-- Load from local file if not a URL
if not source.url:find("^https?://") then
local fd = assert(io.open(source.url, "r"))
local data = fd:read("*a")
fd:close()
return source.build(vim.json.decode(data))
end
local parts = { source_name, "v" .. (source.v or 1), "-", vim.fn.sha256(source.url):sub(1, 8), ".json" }
local file = vim.fn.stdpath("cache") .. "/snacks/picker/icons/" .. table.concat(parts, "")
vim.fn.mkdir(vim.fn.fnamemodify(file, ":h"), "p")
if vim.fn.filereadable(file) == 1 then
local fd = assert(io.open(file, "r"))
@ -100,7 +146,19 @@ end
---@type snacks.picker.finder
function M.icons(opts)
local ret = {} ---@type snacks.picker.Icon[]
for _, source in ipairs(opts.icon_sources or { "nerd_fonts", "emoji" }) do
for source, url in pairs(opts.custom_sources or {}) do
M.sources[source] = custom_source(source, url)
end
local sources = opts.icon_sources or vim.tbl_keys(M.sources)
table.sort(sources, function(a, b)
local sa = M.sources[a] and M.sources[a].priority or 0
local sb = M.sources[b] and M.sources[b].priority or 0
return sa > sb
end)
for _, source in ipairs(sources) do
vim.list_extend(ret, load(source))
end
for _, icon in ipairs(ret) do