Compare commits

..

3 commits
main ... v0.2.0

Author SHA1 Message Date
6c3e72cf33
feat: add get_icon and get_icon_by_filetype API
Some checks are pending
luarocks / quality (push) Waiting to run
luarocks / publish (push) Blocked by required conditions
Problem: plugins that don't use devicons have no way to get nonicons
glyphs for files. The only integration path is the devicons
monkey-patch via apply().

Solution: add get_icon(name, ext) and get_icon_by_filetype(ft) to
the public API in init.lua. Both use lazy require of the resolve
module and return nil on miss so callers decide fallback. Document
both functions in vimdoc and update the oil.nvim recipe.
2026-02-22 21:31:35 -05:00
b9a310dc37
refactor: wire override.lua to resolve module
Problem: override.lua still has its own copies of ext_map,
filename_map, and resolve(), duplicating the newly extracted
resolve module.

Solution: replace local tables and resolve() with imports from
nonicons.resolve. Update all *_by_filetype overrides to use
resolve_filetype() instead of raw ext_map[ft], gaining ft_map
fallback and direct mapping key lookup.
2026-02-22 21:31:25 -05:00
defe9287d5
refactor: extract resolution tables into resolve module
Problem: ext_map, filename_map, and resolve() are locked inside
override.lua as locals, making them inaccessible to any code path
that doesn't go through the devicons monkey-patch.

Solution: extract all resolution tables and logic into a new
nonicons.resolve module. Fix Gemfile/Jenkinsfile case bug (keys
were uppercase but resolve() lowercases input before lookup). Add
ft_map for vim filetypes that don't match extension or mapping keys.
2026-02-22 21:31:17 -05:00
19 changed files with 74 additions and 369 deletions

View file

@ -25,7 +25,6 @@ jobs:
- '*.lua'
- '.luarc.json'
- '*.toml'
- 'vim.yaml'
markdown:
- '*.md'
@ -36,8 +35,11 @@ jobs:
if: ${{ needs.changes.outputs.lua == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v31
- run: nix develop --command stylua --check .
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: 2.1.0
args: --check .
lua-lint:
name: Lua Lint Check
@ -46,8 +48,11 @@ jobs:
if: ${{ needs.changes.outputs.lua == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v31
- run: nix develop --command selene --display-style quiet .
- name: Lint with Selene
uses: NTBBloodbath/selene-action@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --display-style quiet .
lua-typecheck:
name: Lua Type Check
@ -70,8 +75,18 @@ jobs:
if: ${{ needs.changes.outputs.markdown == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v31
- run: nix develop --command prettier --check .
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 8
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install prettier
run: pnpm add -g prettier@3.1.0
- name: Check markdown formatting with prettier
run: prettier --check .
mapping-sync:
name: Mapping Sync Check

View file

@ -32,21 +32,10 @@ jobs:
echo 'return M'
} > lua/nonicons/mapping.lua
- name: Generate colors from nvim-web-devicons
run: |
sudo apt-get install -y -qq lua5.4 > /dev/null
curl -sL https://raw.githubusercontent.com/nvim-tree/nvim-web-devicons/master/lua/nvim-web-devicons/default/icons_by_file_extension.lua \
-o /tmp/devicons_ext.lua
curl -sL https://raw.githubusercontent.com/nvim-tree/nvim-web-devicons/master/lua/nvim-web-devicons/default/icons_by_filename.lua \
-o /tmp/devicons_fname.lua
lua5.4 scripts/gen-colors.lua > lua/nonicons/colors.lua
- name: Check for changes
id: diff
run: |
if git diff --quiet lua/nonicons/mapping.lua lua/nonicons/colors.lua; then
if git diff --quiet lua/nonicons/mapping.lua; then
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
@ -56,17 +45,16 @@ jobs:
if: steps.diff.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7
with:
branch: sync/upstream
title: 'fix: sync with upstream nonicons font and devicons colors'
branch: sync/upstream-mapping
title: 'fix(mapping): sync with upstream nonicons font'
body: |
## Problem
`mapping.lua` or `colors.lua` is out of sync with upstream sources.
`mapping.lua` is out of sync with the [ya2s/nonicons](https://github.com/ya2s/nonicons) upstream font.
## Solution
Auto-generated from upstream [ya2s/nonicons](https://github.com/ya2s/nonicons) `nonicon.json`
and [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons) color definitions.
commit-message: 'fix: sync with upstream nonicons font and devicons colors'
Auto-generated `mapping.lua` from upstream `nonicon.json`.
commit-message: 'fix(mapping): sync with upstream nonicons font'
labels: upstream-sync
delete-branch: true

1
.gitignore vendored
View file

@ -10,4 +10,3 @@ node_modules/
result
result-*
.direnv/
.envrc

View file

@ -1,5 +1,5 @@
{
"runtime.version": "LuaJIT",
"runtime.version": "Lua 5.1",
"runtime.path": ["lua/?.lua", "lua/?/init.lua"],
"diagnostics.globals": ["vim", "jit"],
"workspace.library": ["$VIMRUNTIME/lua", "${3rd}/luv/library"],

View file

@ -12,8 +12,8 @@
## Requirements
- [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
- [nonicons font](https://github.com/ya2s/nonicons/releases) installed
- (Optionally) [nvim-web-devicons](https://github.com/nvim-tree/nvim-web-devicons)
## Installation

View file

@ -232,16 +232,6 @@ iTerm2 ~
Preferences > Profiles > Text > Non-ASCII Font > select Nonicons
==============================================================================
COMMANDS *nonicons-commands*
*:NoniconsHiTest*
`:NoniconsHiTest`
Open a scratch buffer listing every icon in the nonicons font alongside
its name, Unicode codepoint, and which extensions / filenames / filetypes
map to it. Use this to visually verify that glyphs render correctly and
that resolution tables point to the intended icons.
==============================================================================
HEALTH CHECK *nonicons-health*

View file

@ -22,7 +22,6 @@
pkgs.prettier
pkgs.stylua
pkgs.selene
pkgs.lua-language-server
];
};
});

View file

@ -1,69 +0,0 @@
---@type table<string, { [1]: string, [2]: integer }>
local M = {
['babel'] = { '#CBCB41', 185 },
['book'] = { '#3D6117', 22 },
['c'] = { '#A074C4', 140 },
['c-plusplus'] = { '#A074C4', 140 },
['c-sharp'] = { '#596706', 58 },
['code'] = { '#3882D2', 32 },
['css'] = { '#563D7C', 54 },
['dart'] = { '#03589C', 25 },
['database'] = { '#DAD8D8', 188 },
['diff'] = { '#41535B', 239 },
['docker'] = { '#458EE6', 68 },
['elixir'] = { '#A074C4', 140 },
['elm'] = { '#519ABA', 74 },
['eslint'] = { '#4B32C3', 56 },
['file'] = { '#89E051', 113 },
['file-binary'] = { '#4D2C0B', 52 },
['file-zip'] = { '#ECA517', 214 },
['gear'] = { '#6D8086', 66 },
['git-branch'] = { '#F14C28', 196 },
['git-commit'] = { '#F54D27', 196 },
['globe'] = { '#5D7096', 60 },
['go'] = { '#00ADD8', 38 },
['graphql'] = { '#E535AB', 199 },
['html'] = { '#E34C26', 196 },
['image'] = { '#A074C4', 140 },
['java'] = { '#ffaf67', 215 },
['javascript'] = { '#CBCB41', 185 },
['json'] = { '#CBCB41', 185 },
['key'] = { '#FAF743', 227 },
['kotlin'] = { '#7F52FF', 99 },
['law'] = { '#CBCB41', 185 },
['lock'] = { '#BBBBBB', 250 },
['log'] = { '#DDDDDD', 253 },
['logo-github'] = { '#E24329', 196 },
['lua'] = { '#00A2FF', 75 },
['markdown'] = { '#519ABA', 74 },
['next'] = { '#FFFFFF', 231 },
['npm'] = { '#E8274B', 197 },
['package'] = { '#EADCD1', 253 },
['perl'] = { '#519ABA', 74 },
['php'] = { '#F05340', 203 },
['play'] = { '#0075AA', 24 },
['prettier'] = { '#4285F4', 33 },
['python'] = { '#5AA7E4', 39 },
['r'] = { '#519ABA', 74 },
['react'] = { '#1354BF', 26 },
['rss'] = { '#FB9D3B', 215 },
['ruby'] = { '#701516', 52 },
['rust'] = { '#DEA584', 216 },
['scala'] = { '#CC3E44', 167 },
['server'] = { '#A074C4', 140 },
['shield'] = { '#BEC4C9', 251 },
['svelte'] = { '#FF3E00', 196 },
['swift'] = { '#E37933', 166 },
['terminal'] = { '#4273CA', 68 },
['terraform'] = { '#5F43E9', 93 },
['tmux'] = { '#14BA19', 34 },
['toml'] = { '#9C4221', 124 },
['typescript'] = { '#519ABA', 74 },
['typography'] = { '#ECECEC', 255 },
['vim'] = { '#019833', 28 },
['vue'] = { '#8DC149', 113 },
['yaml'] = { '#6D8086', 66 },
['yarn'] = { '#F9AD02', 214 },
}
return M

View file

@ -7,7 +7,7 @@ function M.check()
if ok and devicons then
vim.health.ok('nvim-web-devicons available')
else
vim.health.info('nvim-web-devicons not found (using built-in highlights)')
vim.health.error('nvim-web-devicons not found')
end
local result = vim.fn.system({ 'fc-list', ':family=nonicons' })

View file

@ -1,104 +0,0 @@
local mapping = require('nonicons.mapping')
local resolve = require('nonicons.resolve')
---@param name string
---@return string
local function char(name)
local code = mapping[name]
if code then
return vim.fn.nr2char(code)
end
return '?'
end
---@param t table<string, any>
---@return string[]
local function sorted_keys(t)
local keys = {}
for k in pairs(t) do
keys[#keys + 1] = k
end
table.sort(keys)
return keys
end
---@param t table<string, string>
---@return table<string, string[]>
local function invert(t)
local inv = {}
for k, v in pairs(t) do
inv[v] = inv[v] or {}
inv[v][#inv[v] + 1] = k
end
for _, list in pairs(inv) do
table.sort(list)
end
return inv
end
return function()
local lines = {} ---@type string[]
local ext_by_icon = invert(resolve.ext_map)
local fname_by_icon = invert(resolve.filename_map)
local ft_by_icon = invert(resolve.ft_map)
local icon_names = sorted_keys(mapping)
lines[#lines + 1] = 'nonicons.nvim — icon reference'
lines[#lines + 1] = string.rep('=', 72)
lines[#lines + 1] = ''
lines[#lines + 1] =
string.format(' %-4s %-28s %-7s %s', 'ICON', 'NAME', 'CODE', 'MAPPED FROM')
lines[#lines + 1] = string.rep('-', 72)
local highlights = require('nonicons.highlights')
local icon_line_start = #lines
local icon_glyphs = {} ---@type string[]
for _, name in ipairs(icon_names) do
local sources = {} ---@type string[]
if ext_by_icon[name] then
for _, ext in ipairs(ext_by_icon[name]) do
sources[#sources + 1] = 'ext:' .. ext
end
end
if fname_by_icon[name] then
for _, fname in ipairs(fname_by_icon[name]) do
sources[#sources + 1] = 'file:' .. fname
end
end
if ft_by_icon[name] then
for _, ft in ipairs(ft_by_icon[name]) do
sources[#sources + 1] = 'ft:' .. ft
end
end
local glyph = char(name)
local src_str = #sources > 0 and table.concat(sources, ', ') or ''
lines[#lines + 1] =
string.format(' %s %-28s U+%04X %s', glyph, name, mapping[name], src_str)
icon_glyphs[#icon_glyphs + 1] = glyph
end
lines[#lines + 1] = ''
lines[#lines + 1] = string.format('%d icons total', #icon_names)
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
local ns = vim.api.nvim_create_namespace('nonicons_hitest')
for i, name in ipairs(icon_names) do
local col_start = 2
local col_end = col_start + #icon_glyphs[i]
vim.api.nvim_buf_set_extmark(buf, ns, icon_line_start + i - 1, col_start, {
end_col = col_end,
hl_group = highlights.get(name),
})
end
vim.bo[buf].modifiable = false
vim.bo[buf].buftype = 'nofile'
vim.bo[buf].bufhidden = 'wipe'
vim.api.nvim_set_current_buf(buf)
end

View file

@ -1,39 +0,0 @@
local colors = require('nonicons.colors')
local M = {}
---@param icon_name string
---@return string
local function to_hl_group(icon_name)
local parts = {}
for part in icon_name:gmatch('[^-]+') do
parts[#parts + 1] = part:sub(1, 1):upper() .. part:sub(2)
end
return 'Nonicons' .. table.concat(parts)
end
local function apply()
for name, data in pairs(colors) do
vim.api.nvim_set_hl(0, to_hl_group(name), { fg = data[1], ctermfg = data[2] })
end
end
function M.setup()
apply()
local group = vim.api.nvim_create_augroup('NoniconsHighlights', { clear = true })
vim.api.nvim_create_autocmd('ColorScheme', {
group = group,
callback = apply,
})
end
---@param icon_name string
---@return string? hl_group
function M.get(icon_name)
if colors[icon_name] then
return to_hl_group(icon_name)
end
end
return M

View file

@ -24,32 +24,22 @@ local function ensure_initialized()
initialized = true
end
---@type table<string, integer>
M.mapping = require('nonicons.mapping')
---@param name string Icon name
---@return string? glyph
---@return string? hl_group
function M.get(name)
local code = M.mapping[name]
if code then
local hl = require('nonicons.highlights').get(name)
return vim.fn.nr2char(code), hl
return vim.fn.nr2char(code)
end
end
function M.apply()
ensure_initialized()
require('nonicons.highlights').setup()
if config.override then
require('nonicons.override').apply()
end
end
---@param name string? Filename (e.g. `'init.lua'`)
---@param ext string? File extension (e.g. `'lua'`)
---@return string? glyph
---@return string? hl_group
function M.get_icon(name, ext)
local key = require('nonicons.resolve').resolve_name(name, ext)
if key then
@ -57,9 +47,6 @@ function M.get_icon(name, ext)
end
end
---@param ft string Vim filetype
---@return string? glyph
---@return string? hl_group
function M.get_icon_by_filetype(ft)
local key = require('nonicons.resolve').resolve_filetype(ft)
if key then

View file

@ -1,8 +1,6 @@
local mapping = require('nonicons.mapping')
local resolve_mod = require('nonicons.resolve')
---@param name string
---@return string?
local function char(name)
local code = mapping[name]
if code then
@ -10,16 +8,12 @@ local function char(name)
end
end
---@type string
local fallback_icon
---@param name string?
---@param ext string?
---@return string
local function resolve(name, ext)
local key = resolve_mod.resolve_name(name, ext)
if key then
return char(key) or fallback_icon
return char(key)
end
return fallback_icon
end
@ -32,7 +26,7 @@ function M.apply()
return
end
fallback_icon = char('file') or ''
fallback_icon = char('file')
local orig_get_icon = devicons.get_icon
devicons.get_icon = function(name, ext, opts)
@ -113,7 +107,7 @@ function M.apply()
local function override_tables()
local by_ext = devicons.get_icons_by_extension()
for ext, data in pairs(by_ext) do
local name = resolve_mod.ext_map[ext] or resolve_mod.ext_map[ext:lower()]
local name = resolve_mod.ext_map[ext]
if name then
data.icon = char(name) or fallback_icon
else
@ -123,7 +117,7 @@ function M.apply()
local by_filename = devicons.get_icons_by_filename()
for fname, data in pairs(by_filename) do
local name = resolve_mod.filename_map[fname] or resolve_mod.filename_map[fname:lower()]
local name = resolve_mod.filename_map[fname]
if name then
data.icon = char(name) or fallback_icon
else

View file

@ -1,6 +1,5 @@
local M = {}
---@type table<string, string>
M.ext_map = {
lua = 'lua',
luac = 'lua',
@ -107,6 +106,7 @@ M.ext_map = {
pm = 'perl',
r = 'r',
R = 'r',
rmd = 'r',
scala = 'scala',
@ -121,7 +121,7 @@ M.ext_map = {
tf = 'terraform',
tfvars = 'terraform',
dockerfile = 'docker',
Dockerfile = 'docker',
dockerignore = 'docker',
angular = 'angular',
@ -287,7 +287,6 @@ M.ext_map = {
mojo = 'code',
}
---@type table<string, string>
M.filename_map = {
['dockerfile'] = 'docker',
['containerfile'] = 'docker',
@ -422,7 +421,6 @@ M.filename_map = {
['code_of_conduct.md'] = 'book',
}
---@type table<string, string>
M.ft_map = {
typescriptreact = 'react',
javascriptreact = 'react',
@ -436,9 +434,6 @@ M.ft_map = {
latex = 'book',
}
---@param name string? Filename (e.g. `'init.lua'`)
---@param ext string? File extension (e.g. `'lua'`)
---@return string? icon_name
function M.resolve_name(name, ext)
if ext and M.ext_map[ext] then
return M.ext_map[ext]
@ -448,24 +443,13 @@ function M.resolve_name(name, ext)
if M.filename_map[lower] then
return M.filename_map[lower]
end
local last_ext = lower:match('%.([^%.]+)$')
if last_ext and M.ext_map[last_ext] then
return M.ext_map[last_ext]
end
local compound = lower:match('%.(.+)$')
if compound and compound ~= last_ext then
while compound do
if M.ext_map[compound] then
return M.ext_map[compound]
end
compound = compound:match('%.(.+)$')
end
local dot_ext = lower:match('%.(.+)$')
if dot_ext and M.ext_map[dot_ext] then
return M.ext_map[dot_ext]
end
end
end
---@param ft string? Vim filetype
---@return string? icon_name
function M.resolve_filetype(ft)
if not ft then
return

View file

@ -4,7 +4,3 @@ end
vim.g.loaded_nonicons = 1
require('nonicons').apply()
vim.api.nvim_create_user_command('NoniconsHiTest', function()
require('nonicons.hi-test')()
end, { desc = 'nonicons: display all icons' })

View file

@ -1,39 +0,0 @@
#!/usr/bin/env lua
local resolve = dofile('lua/nonicons/resolve.lua')
local ext_icons = dofile('/tmp/devicons_ext.lua')
local fname_icons = dofile('/tmp/devicons_fname.lua')
local colors = {}
for ext, icon_name in pairs(resolve.ext_map) do
if not colors[icon_name] and ext_icons[ext] then
colors[icon_name] = {
ext_icons[ext].color,
tonumber(ext_icons[ext].cterm_color),
}
end
end
for fname, icon_name in pairs(resolve.filename_map) do
if not colors[icon_name] and fname_icons[fname] then
colors[icon_name] = {
fname_icons[fname].color,
tonumber(fname_icons[fname].cterm_color),
}
end
end
local names = {}
for name in pairs(colors) do
names[#names + 1] = name
end
table.sort(names)
io.write('---@type table<string, { [1]: string, [2]: integer }>\n')
io.write('local M = {\n')
for _, name in ipairs(names) do
local c = colors[name]
io.write(string.format(" ['%s'] = { '%s', %d },\n", name, c[1], c[2]))
end
io.write('}\n\nreturn M\n')

View file

@ -1,4 +1 @@
std = 'vim'
[lints]
bad_string_escape = 'allow'

33
vim.toml Normal file
View file

@ -0,0 +1,33 @@
[selene]
base = "lua51"
name = "vim"
[vim]
any = true
[jit]
any = true
[bit]
any = true
[assert]
any = true
[describe]
any = true
[it]
any = true
[before_each]
any = true
[after_each]
any = true
[spy]
any = true
[stub]
any = true

View file

@ -1,26 +0,0 @@
---
base: lua51
name: vim
lua_versions:
- luajit
globals:
vim:
any: true
jit:
any: true
assert:
any: true
describe:
any: true
it:
any: true
before_each:
any: true
after_each:
any: true
spy:
any: true
stub:
any: true
bit:
any: true