Compare commits

...
Sign in to create a new pull request.

13 commits

Author SHA1 Message Date
949da427bf
ci: format
Some checks are pending
quality / changes (push) Waiting to run
quality / Lua Format Check (push) Blocked by required conditions
quality / Lua Lint Check (push) Blocked by required conditions
quality / Lua Type Check (push) Blocked by required conditions
quality / Markdown Format Check (push) Blocked by required conditions
quality / Mapping Sync Check (push) Waiting to run
2026-03-02 20:28:25 -05:00
e9a1612365
ci: systematically pull colors from nvim-web-devicons 2026-03-02 20:27:17 -05:00
ba45790910
feat: highlight 2026-03-02 20:22:07 -05:00
7a655c9919
feat(hl): highlight icons 2026-03-02 19:40:26 -05:00
4335fb8596
doc: note nvim-web-devicions as optional 2026-03-02 19:24:06 -05:00
91609349d2
ci: migrate to nix 2026-02-23 18:14:33 -05:00
92d00b66fc
build(flake): add lua-language-server to devShell
Problem: lua-language-server is not available in the dev shell, making
it impossible to run local diagnostics checks.

Solution: add pkgs.lua-language-server to the devShell packages.
2026-02-23 17:31:50 -05:00
7d35b23ba0
fix: resolve luacats return-type-mismatch and cast-local-type warnings
Problem: lua-language-server reports two diagnostics in override.lua:
char() returns string? but resolve() annotates its return as string,
and fallback_icon is typed as string but assigned a string? value.

Solution: add fallback values so both assignments satisfy the string
type — resolve() falls back to fallback_icon, and fallback_icon falls
back to an empty string.
2026-02-23 17:31:45 -05:00
6f445c30d6
fix: duplicate table index 2026-02-23 17:27:13 -05:00
b6d8733c3e
ci: format 2026-02-23 17:26:04 -05:00
6a8668fab5
fix(ci): luacats support 2026-02-23 17:25:57 -05:00
747e7b6222
fix: make fname resolution more robust; 2026-02-23 17:19:30 -05:00
Barrett Ruth
d07145eeae
feat: add public file-icon API (#10)
* 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.

* 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.

* feat: add get_icon and get_icon_by_filetype API

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:35:45 -05:00
19 changed files with 888 additions and 511 deletions

View file

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

View file

@ -32,10 +32,21 @@ jobs:
echo 'return M' echo 'return M'
} > lua/nonicons/mapping.lua } > 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 - name: Check for changes
id: diff id: diff
run: | run: |
if git diff --quiet lua/nonicons/mapping.lua; then if git diff --quiet lua/nonicons/mapping.lua lua/nonicons/colors.lua; then
echo "changed=false" >> "$GITHUB_OUTPUT" echo "changed=false" >> "$GITHUB_OUTPUT"
else else
echo "changed=true" >> "$GITHUB_OUTPUT" echo "changed=true" >> "$GITHUB_OUTPUT"
@ -45,16 +56,17 @@ jobs:
if: steps.diff.outputs.changed == 'true' if: steps.diff.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v7 uses: peter-evans/create-pull-request@v7
with: with:
branch: sync/upstream-mapping branch: sync/upstream
title: 'fix(mapping): sync with upstream nonicons font' title: 'fix: sync with upstream nonicons font and devicons colors'
body: | body: |
## Problem ## Problem
`mapping.lua` is out of sync with the [ya2s/nonicons](https://github.com/ya2s/nonicons) upstream font. `mapping.lua` or `colors.lua` is out of sync with upstream sources.
## Solution ## Solution
Auto-generated `mapping.lua` from upstream `nonicon.json`. Auto-generated from upstream [ya2s/nonicons](https://github.com/ya2s/nonicons) `nonicon.json`
commit-message: 'fix(mapping): sync with upstream nonicons font' 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'
labels: upstream-sync labels: upstream-sync
delete-branch: true delete-branch: true

1
.gitignore vendored
View file

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

View file

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

View file

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

View file

@ -53,6 +53,33 @@ API *nonicons-api*
Parameters: ~ Parameters: ~
{name} `string` Icon name (e.g. `'lua'`, `'python'`, `'git-branch'`) {name} `string` Icon name (e.g. `'lua'`, `'python'`, `'git-branch'`)
Returns: ~
`string?` The single-character nonicons glyph
*nonicons.get_icon()*
`require('nonicons').get_icon(name, ext)`
Returns the nonicons character for a file, resolved by filename and/or
extension. Returns `nil` if no match is found (caller decides fallback).
Resolution order: exact extension → exact filename → extracted extension.
Parameters: ~
{name} `string?` Filename (e.g. `'init.lua'`, `'Makefile'`)
{ext} `string?` File extension (e.g. `'lua'`, `'py'`)
Returns: ~
`string?` The single-character nonicons glyph
*nonicons.get_icon_by_filetype()*
`require('nonicons').get_icon_by_filetype(ft)`
Returns the nonicons character for a vim filetype. Returns `nil` if no
match is found.
Resolution order: direct mapping key → extension table → filetype table.
Parameters: ~
{ft} `string` Vim filetype (e.g. `'python'`, `'typescriptreact'`)
Returns: ~ Returns: ~
`string?` The single-character nonicons glyph `string?` The single-character nonicons glyph
@ -128,8 +155,9 @@ mason.nvim ~
oil.nvim ~ oil.nvim ~
No configuration needed. oil.nvim reads the devicons extension/filename No configuration needed. oil.nvim detects nonicons.nvim and uses it as a
tables directly, which nonicons.nvim mutates on load. direct icon provider. If devicons is also loaded, the devicons override
still applies to other plugins.
fzf-lua ~ fzf-lua ~
>lua >lua
@ -204,6 +232,16 @@ iTerm2 ~
Preferences > Profiles > Text > Non-ASCII Font > select Nonicons 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* HEALTH CHECK *nonicons-health*

View file

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

69
lua/nonicons/colors.lua Normal file
View file

@ -0,0 +1,69 @@
---@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 if ok and devicons then
vim.health.ok('nvim-web-devicons available') vim.health.ok('nvim-web-devicons available')
else else
vim.health.error('nvim-web-devicons not found') vim.health.info('nvim-web-devicons not found (using built-in highlights)')
end end
local result = vim.fn.system({ 'fc-list', ':family=nonicons' }) local result = vim.fn.system({ 'fc-list', ':family=nonicons' })

104
lua/nonicons/hi-test.lua Normal file
View file

@ -0,0 +1,104 @@
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

@ -0,0 +1,39 @@
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,20 +24,47 @@ local function ensure_initialized()
initialized = true initialized = true
end end
---@type table<string, integer>
M.mapping = require('nonicons.mapping') M.mapping = require('nonicons.mapping')
---@param name string Icon name
---@return string? glyph
---@return string? hl_group
function M.get(name) function M.get(name)
local code = M.mapping[name] local code = M.mapping[name]
if code then if code then
return vim.fn.nr2char(code) local hl = require('nonicons.highlights').get(name)
return vim.fn.nr2char(code), hl
end end
end end
function M.apply() function M.apply()
ensure_initialized() ensure_initialized()
require('nonicons.highlights').setup()
if config.override then if config.override then
require('nonicons.override').apply() require('nonicons.override').apply()
end end
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
return M.get(key)
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
return M.get(key)
end
end
return M return M

View file

@ -1,5 +1,8 @@
local mapping = require('nonicons.mapping') local mapping = require('nonicons.mapping')
local resolve_mod = require('nonicons.resolve')
---@param name string
---@return string?
local function char(name) local function char(name)
local code = mapping[name] local code = mapping[name]
if code then if code then
@ -7,442 +10,16 @@ local function char(name)
end end
end end
---@type string
local fallback_icon local fallback_icon
local ext_map = { ---@param name string?
lua = 'lua', ---@param ext string?
luac = 'lua', ---@return string
luau = 'lua',
js = 'javascript',
cjs = 'javascript',
mjs = 'javascript',
jsx = 'react',
tsx = 'react',
ts = 'typescript',
cts = 'typescript',
mts = 'typescript',
['d.ts'] = 'typescript',
py = 'python',
pyc = 'python',
pyd = 'python',
pyi = 'python',
pyo = 'python',
pyw = 'python',
pyx = 'python',
rb = 'ruby',
rake = 'ruby',
gemspec = 'ruby',
rs = 'rust',
rlib = 'rust',
go = 'go',
c = 'c',
h = 'c',
cpp = 'c-plusplus',
cc = 'c-plusplus',
cxx = 'c-plusplus',
['c++'] = 'c-plusplus',
cp = 'c-plusplus',
cppm = 'c-plusplus',
cxxm = 'c-plusplus',
mpp = 'c-plusplus',
hh = 'c-plusplus',
hpp = 'c-plusplus',
hxx = 'c-plusplus',
ixx = 'c-plusplus',
mm = 'c-plusplus',
cs = 'c-sharp',
cshtml = 'c-sharp',
csproj = 'c-sharp',
java = 'java',
jar = 'java',
kt = 'kotlin',
kts = 'kotlin',
swift = 'swift',
dart = 'dart',
elm = 'elm',
ex = 'elixir',
exs = 'elixir',
eex = 'elixir',
heex = 'elixir',
leex = 'elixir',
vue = 'vue',
svelte = 'svelte',
html = 'html',
htm = 'html',
css = 'css',
scss = 'css',
sass = 'css',
less = 'css',
styl = 'css',
json = 'json',
json5 = 'json',
jsonc = 'json',
cson = 'json',
yaml = 'yaml',
yml = 'yaml',
toml = 'toml',
md = 'markdown',
markdown = 'markdown',
mdx = 'markdown',
php = 'php',
['blade.php'] = 'php',
pl = 'perl',
pm = 'perl',
r = 'r',
R = 'r',
rmd = 'r',
scala = 'scala',
sc = 'scala',
sbt = 'scala',
vim = 'vim',
graphql = 'graphql',
gql = 'graphql',
tf = 'terraform',
tfvars = 'terraform',
Dockerfile = 'docker',
dockerignore = 'docker',
angular = 'angular',
sh = 'terminal',
bash = 'terminal',
zsh = 'terminal',
fish = 'terminal',
ksh = 'terminal',
csh = 'terminal',
terminal = 'terminal',
ps1 = 'terminal',
nix = 'code',
sql = 'database',
sqlite = 'database',
sqlite3 = 'database',
db = 'database',
dump = 'database',
rss = 'rss',
tmux = 'tmux',
nginx = 'nginx',
diff = 'diff',
patch = 'diff',
lock = 'lock',
lck = 'lock',
conf = 'gear',
cfg = 'gear',
ini = 'gear',
env = 'key',
git = 'git-branch',
license = 'law',
log = 'log',
xml = 'code',
xslt = 'code',
tex = 'book',
bib = 'book',
png = 'image',
jpg = 'image',
jpeg = 'image',
gif = 'image',
bmp = 'image',
ico = 'image',
webp = 'image',
avif = 'image',
svg = 'image',
tiff = 'image',
jxl = 'image',
zip = 'file-zip',
gz = 'file-zip',
tgz = 'file-zip',
['7z'] = 'file-zip',
rar = 'file-zip',
bz = 'file-zip',
bz2 = 'file-zip',
bz3 = 'file-zip',
xz = 'file-zip',
zst = 'file-zip',
txz = 'file-zip',
tar = 'file-zip',
bin = 'file-binary',
exe = 'file-binary',
dll = 'file-binary',
so = 'file-binary',
o = 'file-binary',
a = 'file-binary',
elf = 'file-binary',
ko = 'file-binary',
lib = 'file-binary',
out = 'file-binary',
mp3 = 'play',
mp4 = 'play',
mkv = 'play',
mov = 'play',
avi = 'play',
flac = 'play',
ogg = 'play',
wav = 'play',
webm = 'play',
aac = 'play',
m4a = 'play',
m4v = 'play',
ogv = 'play',
wma = 'play',
wmv = 'play',
ttf = 'typography',
otf = 'typography',
woff = 'typography',
woff2 = 'typography',
eot = 'typography',
pdf = 'file',
doc = 'file',
docx = 'file',
ppt = 'file',
pptx = 'file',
xls = 'file',
xlsx = 'file',
csv = 'file',
txt = 'file',
erl = 'code',
hrl = 'code',
hs = 'code',
lhs = 'code',
ml = 'code',
mli = 'code',
clj = 'code',
cljs = 'code',
cljc = 'code',
edn = 'code',
fnl = 'code',
el = 'code',
elc = 'code',
eln = 'code',
nim = 'code',
zig = 'code',
odin = 'code',
gleam = 'code',
cr = 'code',
jl = 'code',
nu = 'code',
pro = 'code',
scm = 'code',
rkt = 'code',
sol = 'code',
wasm = 'code',
ipynb = 'code',
gradle = 'code',
groovy = 'code',
ino = 'code',
prisma = 'code',
astro = 'code',
hx = 'code',
d = 'code',
ada = 'code',
adb = 'code',
ads = 'code',
f90 = 'code',
vala = 'code',
v = 'code',
vh = 'code',
vhd = 'code',
vhdl = 'code',
sv = 'code',
svh = 'code',
mo = 'code',
mojo = 'code',
}
local filename_map = {
['dockerfile'] = 'docker',
['containerfile'] = 'docker',
['docker-compose.yml'] = 'docker',
['docker-compose.yaml'] = 'docker',
['compose.yml'] = 'docker',
['compose.yaml'] = 'docker',
['.dockerignore'] = 'docker',
['.gitignore'] = 'git-branch',
['.gitconfig'] = 'git-branch',
['.gitattributes'] = 'git-branch',
['.gitmodules'] = 'git-branch',
['.git-blame-ignore-revs'] = 'git-branch',
['.mailmap'] = 'git-branch',
['commit_editmsg'] = 'git-commit',
['.bashrc'] = 'terminal',
['.bash_profile'] = 'terminal',
['.zshrc'] = 'terminal',
['.zshenv'] = 'terminal',
['.zprofile'] = 'terminal',
['makefile'] = 'terminal',
['gnumakefile'] = 'terminal',
['.justfile'] = 'terminal',
['justfile'] = 'terminal',
['.eslintrc'] = 'eslint',
['.eslintignore'] = 'eslint',
['eslint.config.js'] = 'eslint',
['eslint.config.cjs'] = 'eslint',
['eslint.config.mjs'] = 'eslint',
['eslint.config.ts'] = 'eslint',
['.prettierrc'] = 'prettier',
['.prettierignore'] = 'prettier',
['.prettierrc.js'] = 'prettier',
['.prettierrc.cjs'] = 'prettier',
['.prettierrc.mjs'] = 'prettier',
['.prettierrc.json'] = 'prettier',
['.prettierrc.json5'] = 'prettier',
['.prettierrc.toml'] = 'prettier',
['.prettierrc.yaml'] = 'prettier',
['.prettierrc.yml'] = 'prettier',
['prettier.config.js'] = 'prettier',
['prettier.config.cjs'] = 'prettier',
['prettier.config.mjs'] = 'prettier',
['prettier.config.ts'] = 'prettier',
['.babelrc'] = 'babel',
['package.json'] = 'npm',
['package-lock.json'] = 'npm',
['.npmrc'] = 'npm',
['.npmignore'] = 'npm',
['pnpm-lock.yaml'] = 'yarn',
['pnpm-workspace.yaml'] = 'package',
['.pnpmfile.cjs'] = 'npm',
['bun.lock'] = 'package',
['bun.lockb'] = 'package',
['tsconfig.json'] = 'typescript',
['license'] = 'law',
['license.md'] = 'law',
['copying'] = 'law',
['copying.lesser'] = 'law',
['unlicense'] = 'law',
['tmux.conf'] = 'tmux',
['tmux.conf.local'] = 'tmux',
['readme'] = 'book',
['readme.md'] = 'book',
['go.mod'] = 'go',
['go.sum'] = 'go',
['go.work'] = 'go',
['.vimrc'] = 'vim',
['.gvimrc'] = 'vim',
['_vimrc'] = 'vim',
['_gvimrc'] = 'vim',
['next.config.js'] = 'next',
['next.config.cjs'] = 'next',
['next.config.ts'] = 'next',
['svelte.config.js'] = 'svelte',
['mix.lock'] = 'elixir',
['.env'] = 'key',
['config'] = 'gear',
['.editorconfig'] = 'gear',
['procfile'] = 'server',
['Gemfile'] = 'ruby',
['rakefile'] = 'ruby',
['Jenkinsfile'] = 'gear',
['.gitlab-ci.yml'] = 'logo-github',
['security'] = 'shield',
['security.md'] = 'shield',
['robots.txt'] = 'globe',
['vite.config.js'] = 'code',
['vite.config.ts'] = 'code',
['vite.config.cjs'] = 'code',
['vite.config.cts'] = 'code',
['vite.config.mjs'] = 'code',
['vite.config.mts'] = 'code',
['build.gradle'] = 'code',
['settings.gradle'] = 'code',
['pom.xml'] = 'code',
['hyprland.conf'] = 'gear',
['hyprlock.conf'] = 'gear',
['hypridle.conf'] = 'gear',
['hyprpaper.conf'] = 'gear',
['cmakelists.txt'] = 'code',
['code_of_conduct'] = 'book',
['code_of_conduct.md'] = 'book',
}
local function resolve(name, ext) local function resolve(name, ext)
if ext and ext_map[ext] then local key = resolve_mod.resolve_name(name, ext)
return char(ext_map[ext]) if key then
end return char(key) or fallback_icon
if name then
local lower = name:lower()
if filename_map[lower] then
return char(filename_map[lower])
end
local dot_ext = lower:match('%.(.+)$')
if dot_ext and ext_map[dot_ext] then
return char(ext_map[dot_ext])
end
end end
return fallback_icon return fallback_icon
end end
@ -455,7 +32,7 @@ function M.apply()
return return
end end
fallback_icon = char('file') fallback_icon = char('file') or ''
local orig_get_icon = devicons.get_icon local orig_get_icon = devicons.get_icon
devicons.get_icon = function(name, ext, opts) devicons.get_icon = function(name, ext, opts)
@ -470,8 +47,8 @@ function M.apply()
devicons.get_icon_by_filetype = function(ft, opts) devicons.get_icon_by_filetype = function(ft, opts)
local icon, hl = orig_get_icon_by_filetype(ft, opts) local icon, hl = orig_get_icon_by_filetype(ft, opts)
if icon then if icon then
local nonicons_name = ext_map[ft] local key = resolve_mod.resolve_filetype(ft)
icon = nonicons_name and char(nonicons_name) or fallback_icon icon = key and char(key) or fallback_icon
end end
return icon, hl return icon, hl
end end
@ -507,8 +84,8 @@ function M.apply()
devicons.get_icon_colors_by_filetype = function(ft, opts) devicons.get_icon_colors_by_filetype = function(ft, opts)
local icon, color, cterm_color = orig_get_icon_colors_by_filetype(ft, opts) local icon, color, cterm_color = orig_get_icon_colors_by_filetype(ft, opts)
if icon then if icon then
local nonicons_name = ext_map[ft] local key = resolve_mod.resolve_filetype(ft)
icon = nonicons_name and char(nonicons_name) or fallback_icon icon = key and char(key) or fallback_icon
end end
return icon, color, cterm_color return icon, color, cterm_color
end end
@ -517,8 +94,8 @@ function M.apply()
devicons.get_icon_color_by_filetype = function(ft, opts) devicons.get_icon_color_by_filetype = function(ft, opts)
local icon, color = orig_get_icon_color_by_filetype(ft, opts) local icon, color = orig_get_icon_color_by_filetype(ft, opts)
if icon then if icon then
local nonicons_name = ext_map[ft] local key = resolve_mod.resolve_filetype(ft)
icon = nonicons_name and char(nonicons_name) or fallback_icon icon = key and char(key) or fallback_icon
end end
return icon, color return icon, color
end end
@ -527,8 +104,8 @@ function M.apply()
devicons.get_icon_cterm_color_by_filetype = function(ft, opts) devicons.get_icon_cterm_color_by_filetype = function(ft, opts)
local icon, cterm_color = orig_get_icon_cterm_color_by_filetype(ft, opts) local icon, cterm_color = orig_get_icon_cterm_color_by_filetype(ft, opts)
if icon then if icon then
local nonicons_name = ext_map[ft] local key = resolve_mod.resolve_filetype(ft)
icon = nonicons_name and char(nonicons_name) or fallback_icon icon = key and char(key) or fallback_icon
end end
return icon, cterm_color return icon, cterm_color
end end
@ -536,7 +113,7 @@ function M.apply()
local function override_tables() local function override_tables()
local by_ext = devicons.get_icons_by_extension() local by_ext = devicons.get_icons_by_extension()
for ext, data in pairs(by_ext) do for ext, data in pairs(by_ext) do
local name = ext_map[ext] local name = resolve_mod.ext_map[ext] or resolve_mod.ext_map[ext:lower()]
if name then if name then
data.icon = char(name) or fallback_icon data.icon = char(name) or fallback_icon
else else
@ -546,7 +123,7 @@ function M.apply()
local by_filename = devicons.get_icons_by_filename() local by_filename = devicons.get_icons_by_filename()
for fname, data in pairs(by_filename) do for fname, data in pairs(by_filename) do
local name = filename_map[fname] local name = resolve_mod.filename_map[fname] or resolve_mod.filename_map[fname:lower()]
if name then if name then
data.icon = char(name) or fallback_icon data.icon = char(name) or fallback_icon
else else

485
lua/nonicons/resolve.lua Normal file
View file

@ -0,0 +1,485 @@
local M = {}
---@type table<string, string>
M.ext_map = {
lua = 'lua',
luac = 'lua',
luau = 'lua',
js = 'javascript',
cjs = 'javascript',
mjs = 'javascript',
jsx = 'react',
tsx = 'react',
ts = 'typescript',
cts = 'typescript',
mts = 'typescript',
['d.ts'] = 'typescript',
py = 'python',
pyc = 'python',
pyd = 'python',
pyi = 'python',
pyo = 'python',
pyw = 'python',
pyx = 'python',
rb = 'ruby',
rake = 'ruby',
gemspec = 'ruby',
rs = 'rust',
rlib = 'rust',
go = 'go',
c = 'c',
h = 'c',
cpp = 'c-plusplus',
cc = 'c-plusplus',
cxx = 'c-plusplus',
['c++'] = 'c-plusplus',
cp = 'c-plusplus',
cppm = 'c-plusplus',
cxxm = 'c-plusplus',
mpp = 'c-plusplus',
hh = 'c-plusplus',
hpp = 'c-plusplus',
hxx = 'c-plusplus',
ixx = 'c-plusplus',
mm = 'c-plusplus',
cs = 'c-sharp',
cshtml = 'c-sharp',
csproj = 'c-sharp',
java = 'java',
jar = 'java',
kt = 'kotlin',
kts = 'kotlin',
swift = 'swift',
dart = 'dart',
elm = 'elm',
ex = 'elixir',
exs = 'elixir',
eex = 'elixir',
heex = 'elixir',
leex = 'elixir',
vue = 'vue',
svelte = 'svelte',
html = 'html',
htm = 'html',
css = 'css',
scss = 'css',
sass = 'css',
less = 'css',
styl = 'css',
json = 'json',
json5 = 'json',
jsonc = 'json',
cson = 'json',
yaml = 'yaml',
yml = 'yaml',
toml = 'toml',
md = 'markdown',
markdown = 'markdown',
mdx = 'markdown',
php = 'php',
['blade.php'] = 'php',
pl = 'perl',
pm = 'perl',
r = 'r',
rmd = 'r',
scala = 'scala',
sc = 'scala',
sbt = 'scala',
vim = 'vim',
graphql = 'graphql',
gql = 'graphql',
tf = 'terraform',
tfvars = 'terraform',
dockerfile = 'docker',
dockerignore = 'docker',
angular = 'angular',
sh = 'terminal',
bash = 'terminal',
zsh = 'terminal',
fish = 'terminal',
ksh = 'terminal',
csh = 'terminal',
terminal = 'terminal',
ps1 = 'terminal',
nix = 'code',
sql = 'database',
sqlite = 'database',
sqlite3 = 'database',
db = 'database',
dump = 'database',
rss = 'rss',
tmux = 'tmux',
nginx = 'nginx',
diff = 'diff',
patch = 'diff',
lock = 'lock',
lck = 'lock',
conf = 'gear',
cfg = 'gear',
ini = 'gear',
env = 'key',
git = 'git-branch',
license = 'law',
log = 'log',
xml = 'code',
xslt = 'code',
tex = 'book',
bib = 'book',
png = 'image',
jpg = 'image',
jpeg = 'image',
gif = 'image',
bmp = 'image',
ico = 'image',
webp = 'image',
avif = 'image',
svg = 'image',
tiff = 'image',
jxl = 'image',
zip = 'file-zip',
gz = 'file-zip',
tgz = 'file-zip',
['7z'] = 'file-zip',
rar = 'file-zip',
bz = 'file-zip',
bz2 = 'file-zip',
bz3 = 'file-zip',
xz = 'file-zip',
zst = 'file-zip',
txz = 'file-zip',
tar = 'file-zip',
bin = 'file-binary',
exe = 'file-binary',
dll = 'file-binary',
so = 'file-binary',
o = 'file-binary',
a = 'file-binary',
elf = 'file-binary',
ko = 'file-binary',
lib = 'file-binary',
out = 'file-binary',
mp3 = 'play',
mp4 = 'play',
mkv = 'play',
mov = 'play',
avi = 'play',
flac = 'play',
ogg = 'play',
wav = 'play',
webm = 'play',
aac = 'play',
m4a = 'play',
m4v = 'play',
ogv = 'play',
wma = 'play',
wmv = 'play',
ttf = 'typography',
otf = 'typography',
woff = 'typography',
woff2 = 'typography',
eot = 'typography',
pdf = 'file',
doc = 'file',
docx = 'file',
ppt = 'file',
pptx = 'file',
xls = 'file',
xlsx = 'file',
csv = 'file',
txt = 'file',
erl = 'code',
hrl = 'code',
hs = 'code',
lhs = 'code',
ml = 'code',
mli = 'code',
clj = 'code',
cljs = 'code',
cljc = 'code',
edn = 'code',
fnl = 'code',
el = 'code',
elc = 'code',
eln = 'code',
nim = 'code',
zig = 'code',
odin = 'code',
gleam = 'code',
cr = 'code',
jl = 'code',
nu = 'code',
pro = 'code',
scm = 'code',
rkt = 'code',
sol = 'code',
wasm = 'code',
ipynb = 'code',
gradle = 'code',
groovy = 'code',
ino = 'code',
prisma = 'code',
astro = 'code',
hx = 'code',
d = 'code',
ada = 'code',
adb = 'code',
ads = 'code',
f90 = 'code',
vala = 'code',
v = 'code',
vh = 'code',
vhd = 'code',
vhdl = 'code',
sv = 'code',
svh = 'code',
mo = 'code',
mojo = 'code',
}
---@type table<string, string>
M.filename_map = {
['dockerfile'] = 'docker',
['containerfile'] = 'docker',
['docker-compose.yml'] = 'docker',
['docker-compose.yaml'] = 'docker',
['compose.yml'] = 'docker',
['compose.yaml'] = 'docker',
['.dockerignore'] = 'docker',
['.gitignore'] = 'git-branch',
['.gitconfig'] = 'git-branch',
['.gitattributes'] = 'git-branch',
['.gitmodules'] = 'git-branch',
['.git-blame-ignore-revs'] = 'git-branch',
['.mailmap'] = 'git-branch',
['commit_editmsg'] = 'git-commit',
['.bashrc'] = 'terminal',
['.bash_profile'] = 'terminal',
['.zshrc'] = 'terminal',
['.zshenv'] = 'terminal',
['.zprofile'] = 'terminal',
['makefile'] = 'terminal',
['gnumakefile'] = 'terminal',
['.justfile'] = 'terminal',
['justfile'] = 'terminal',
['.eslintrc'] = 'eslint',
['.eslintignore'] = 'eslint',
['eslint.config.js'] = 'eslint',
['eslint.config.cjs'] = 'eslint',
['eslint.config.mjs'] = 'eslint',
['eslint.config.ts'] = 'eslint',
['.prettierrc'] = 'prettier',
['.prettierignore'] = 'prettier',
['.prettierrc.js'] = 'prettier',
['.prettierrc.cjs'] = 'prettier',
['.prettierrc.mjs'] = 'prettier',
['.prettierrc.json'] = 'prettier',
['.prettierrc.json5'] = 'prettier',
['.prettierrc.toml'] = 'prettier',
['.prettierrc.yaml'] = 'prettier',
['.prettierrc.yml'] = 'prettier',
['prettier.config.js'] = 'prettier',
['prettier.config.cjs'] = 'prettier',
['prettier.config.mjs'] = 'prettier',
['prettier.config.ts'] = 'prettier',
['.babelrc'] = 'babel',
['package.json'] = 'npm',
['package-lock.json'] = 'npm',
['.npmrc'] = 'npm',
['.npmignore'] = 'npm',
['pnpm-lock.yaml'] = 'yarn',
['pnpm-workspace.yaml'] = 'package',
['.pnpmfile.cjs'] = 'npm',
['bun.lock'] = 'package',
['bun.lockb'] = 'package',
['tsconfig.json'] = 'typescript',
['license'] = 'law',
['license.md'] = 'law',
['copying'] = 'law',
['copying.lesser'] = 'law',
['unlicense'] = 'law',
['tmux.conf'] = 'tmux',
['tmux.conf.local'] = 'tmux',
['readme'] = 'book',
['readme.md'] = 'book',
['go.mod'] = 'go',
['go.sum'] = 'go',
['go.work'] = 'go',
['.vimrc'] = 'vim',
['.gvimrc'] = 'vim',
['_vimrc'] = 'vim',
['_gvimrc'] = 'vim',
['next.config.js'] = 'next',
['next.config.cjs'] = 'next',
['next.config.ts'] = 'next',
['svelte.config.js'] = 'svelte',
['mix.lock'] = 'elixir',
['.env'] = 'key',
['config'] = 'gear',
['.editorconfig'] = 'gear',
['procfile'] = 'server',
['gemfile'] = 'ruby',
['rakefile'] = 'ruby',
['jenkinsfile'] = 'gear',
['.gitlab-ci.yml'] = 'logo-github',
['security'] = 'shield',
['security.md'] = 'shield',
['robots.txt'] = 'globe',
['vite.config.js'] = 'code',
['vite.config.ts'] = 'code',
['vite.config.cjs'] = 'code',
['vite.config.cts'] = 'code',
['vite.config.mjs'] = 'code',
['vite.config.mts'] = 'code',
['build.gradle'] = 'code',
['settings.gradle'] = 'code',
['pom.xml'] = 'code',
['hyprland.conf'] = 'gear',
['hyprlock.conf'] = 'gear',
['hypridle.conf'] = 'gear',
['hyprpaper.conf'] = 'gear',
['cmakelists.txt'] = 'code',
['code_of_conduct'] = 'book',
['code_of_conduct.md'] = 'book',
}
---@type table<string, string>
M.ft_map = {
typescriptreact = 'react',
javascriptreact = 'react',
make = 'terminal',
dockerfile = 'docker',
gitcommit = 'git-commit',
gitrebase = 'git-branch',
help = 'book',
text = 'file',
plaintex = 'book',
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]
end
if name then
local lower = name:lower()
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
end
end
end
---@param ft string? Vim filetype
---@return string? icon_name
function M.resolve_filetype(ft)
if not ft then
return
end
local mapping = require('nonicons.mapping')
if mapping[ft] then
return ft
end
if M.ext_map[ft] then
return M.ext_map[ft]
end
if M.ft_map[ft] then
return M.ft_map[ft]
end
end
return M

View file

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

39
scripts/gen-colors.lua Normal file
View file

@ -0,0 +1,39 @@
#!/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 +1,4 @@
std = 'vim' std = 'vim'
[lints]
bad_string_escape = 'allow'

View file

@ -1,33 +0,0 @@
[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

26
vim.yaml Normal file
View file

@ -0,0 +1,26 @@
---
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