Compare commits

..

19 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
5426ec037f
doc: improve q&a format 2026-02-21 23:02:31 -05:00
Barrett Ruth
62549ecb99
feat(ci): add scheduled upstream mapping sync workflow (#9)
Problem: mapping.lua can silently drift from the ya2s/nonicons upstream
font. The existing CI check only runs on PRs/pushes and just fails
without any actionable follow-up.

Solution: add a daily scheduled workflow that generates mapping.lua from
upstream nonicon.json and opens (or updates) a PR via
peter-evans/create-pull-request. Reuses the same branch to avoid spam.

Closes #7
2026-02-18 13:25:39 -05:00
Barrett Ruth
e6d3a78eb3
fix: ghostty install instructions (#8)
* fix: ghostty install isntructions

* fix(docs): lazy = false
2026-02-18 12:51:22 -05:00
Barrett Ruth
6f7670d1a7
Update README.md 2026-02-17 21:18:36 -05:00
Barrett Ruth
17aa14259a
feat: add mapping sync check and improve vimdoc (#6)
Some checks are pending
luarocks / quality (push) Waiting to run
luarocks / publish (push) Blocked by required conditions
* feat(doc): fix phrasing

* ci: add upstream mapping sync check to quality workflow

Problem: mapping.lua can silently fall behind the ya2s/nonicons font
repo with no indication that new icons are available.

Solution: add a mapping-sync job that fetches nonicon.json from
upstream, generates the expected mapping.lua, and fails the check
if they differ.

* fix: sort alphabetically

* fix(ci): dont respect alphabetical order in font checker
2026-02-17 21:07:32 -05:00
Barrett Ruth
6b4f5cf150
feat(doc): fix phrasing (#5) 2026-02-17 20:46:28 -05:00
19 changed files with 953 additions and 519 deletions

View file

@ -25,6 +25,7 @@ jobs:
- '*.lua'
- '.luarc.json'
- '*.toml'
- 'vim.yaml'
markdown:
- '*.md'
@ -35,11 +36,8 @@ jobs:
if: ${{ needs.changes.outputs.lua == 'true' }}
steps:
- uses: actions/checkout@v4
- uses: JohnnyMorganz/stylua-action@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
version: 2.1.0
args: --check .
- uses: cachix/install-nix-action@v31
- run: nix develop --command stylua --check .
lua-lint:
name: Lua Lint Check
@ -48,11 +46,8 @@ jobs:
if: ${{ needs.changes.outputs.lua == 'true' }}
steps:
- uses: actions/checkout@v4
- name: Lint with Selene
uses: NTBBloodbath/selene-action@v1.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --display-style quiet .
- uses: cachix/install-nix-action@v31
- run: nix develop --command selene --display-style quiet .
lua-typecheck:
name: Lua Type Check
@ -75,18 +70,8 @@ jobs:
if: ${{ needs.changes.outputs.markdown == 'true' }}
steps:
- uses: actions/checkout@v4
- 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 .
- uses: cachix/install-nix-action@v31
- run: nix develop --command prettier --check .
mapping-sync:
name: Mapping Sync Check
@ -96,20 +81,17 @@ jobs:
- name: Check mapping against upstream
run: |
curl -sL https://raw.githubusercontent.com/ya2s/nonicons/main/src/template/nonicon.json \
| jq -S 'to_entries | map({key: (.key | sub("-16$"; "")), value: .value}) | from_entries' \
> /tmp/upstream.json
| jq -r 'to_entries[] | "\(.key | sub("-16$"; "")) \(.value)"' \
| sort > /tmp/upstream.txt
{
echo '---@type table<string, integer>'
echo 'local M = {'
jq -r 'to_entries | sort_by(.key) | .[] | " ['"'"'\(.key)'"'"'] = \(.value),"' /tmp/upstream.json
echo '}'
echo 'return M'
} > /tmp/expected.lua
grep -oP "^\s*\['\K[^']+(?='\]\s*=\s*\d)" lua/nonicons/mapping.lua \
| while read -r key; do
val=$(grep -oP "^\s*\['${key}'\]\s*=\s*\K\d+" lua/nonicons/mapping.lua)
echo "$key $val"
done | sort > /tmp/local.txt
if ! diff -u lua/nonicons/mapping.lua /tmp/expected.lua; then
if ! diff -u /tmp/local.txt /tmp/upstream.txt; then
echo ''
echo '::warning::mapping.lua is out of sync with ya2s/nonicons upstream'
echo 'Run the sync script or update lua/nonicons/mapping.lua manually.'
exit 1
fi

72
.github/workflows/sync.yaml vendored Normal file
View file

@ -0,0 +1,72 @@
name: sync
on:
schedule:
- cron: '0 8 * * *'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
sync-mapping:
name: Sync Upstream Mapping
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate mapping from upstream
run: |
curl -sL https://raw.githubusercontent.com/ya2s/nonicons/main/src/template/nonicon.json \
| jq -r 'to_entries | sort_by(.key) | .[] | "\(.key | sub("-16$"; "")) \(.value)"' \
> /tmp/upstream.txt
{
echo '---@type table<string, integer>'
echo 'local M = {'
while IFS=' ' read -r name code; do
printf " ['%s'] = %s,\n" "$name" "$code"
done < /tmp/upstream.txt
echo '}'
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
echo "changed=false" >> "$GITHUB_OUTPUT"
else
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Create pull request
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'
body: |
## Problem
`mapping.lua` or `colors.lua` is out of sync with upstream sources.
## 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'
labels: upstream-sync
delete-branch: true

1
.gitignore vendored
View file

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

View file

@ -1,5 +1,5 @@
{
"runtime.version": "Lua 5.1",
"runtime.version": "LuaJIT",
"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
@ -32,7 +32,7 @@ luarocks install nonicons.nvim
## FAQ
**How do I integrate with plugin \<X\>?**
**Q: How do I integrate with plugin `X`?**
See `:help nonicons-recipes`.

View file

@ -23,6 +23,7 @@ Load nvim-web-devicons before nonicons.nvim. For example, with lazy.nvim: >lua
{
'barrettruth/nonicons.nvim',
dependencies = { 'nvim-tree/nvim-web-devicons' },
lazy = false,
}
<
The plugin works automatically with no configuration required. For
@ -52,6 +53,33 @@ API *nonicons-api*
Parameters: ~
{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: ~
`string?` The single-character nonicons glyph
@ -127,8 +155,9 @@ mason.nvim ~
oil.nvim ~
No configuration needed. oil.nvim reads the devicons extension/filename
tables directly, which nonicons.nvim mutates on load.
No configuration needed. oil.nvim detects nonicons.nvim and uses it as a
direct icon provider. If devicons is also loaded, the devicons override
still applies to other plugins.
fzf-lua ~
>lua
@ -183,8 +212,7 @@ Download the font from: https://github.com/ya2s/nonicons/releases
ghostty ~
>
font-family = Nonicons
font-family = YourMainFont
font_codepoint_map = "U+f101-U+f25c=nonicons"
<
kitty ~
@ -204,6 +232,16 @@ 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,6 +22,7 @@
pkgs.prettier
pkgs.stylua
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
vim.health.ok('nvim-web-devicons available')
else
vim.health.error('nvim-web-devicons not found')
vim.health.info('nvim-web-devicons not found (using built-in highlights)')
end
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
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
return vim.fn.nr2char(code)
local hl = require('nonicons.highlights').get(name)
return vim.fn.nr2char(code), hl
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
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

View file

@ -1,5 +1,8 @@
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
@ -7,442 +10,16 @@ local function char(name)
end
end
---@type string
local fallback_icon
local 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',
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',
}
---@param name string?
---@param ext string?
---@return string
local function resolve(name, ext)
if ext and ext_map[ext] then
return char(ext_map[ext])
end
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
local key = resolve_mod.resolve_name(name, ext)
if key then
return char(key) or fallback_icon
end
return fallback_icon
end
@ -455,7 +32,7 @@ function M.apply()
return
end
fallback_icon = char('file')
fallback_icon = char('file') or ''
local orig_get_icon = devicons.get_icon
devicons.get_icon = function(name, ext, opts)
@ -470,8 +47,8 @@ function M.apply()
devicons.get_icon_by_filetype = function(ft, opts)
local icon, hl = orig_get_icon_by_filetype(ft, opts)
if icon then
local nonicons_name = ext_map[ft]
icon = nonicons_name and char(nonicons_name) or fallback_icon
local key = resolve_mod.resolve_filetype(ft)
icon = key and char(key) or fallback_icon
end
return icon, hl
end
@ -507,8 +84,8 @@ function M.apply()
devicons.get_icon_colors_by_filetype = function(ft, opts)
local icon, color, cterm_color = orig_get_icon_colors_by_filetype(ft, opts)
if icon then
local nonicons_name = ext_map[ft]
icon = nonicons_name and char(nonicons_name) or fallback_icon
local key = resolve_mod.resolve_filetype(ft)
icon = key and char(key) or fallback_icon
end
return icon, color, cterm_color
end
@ -517,8 +94,8 @@ function M.apply()
devicons.get_icon_color_by_filetype = function(ft, opts)
local icon, color = orig_get_icon_color_by_filetype(ft, opts)
if icon then
local nonicons_name = ext_map[ft]
icon = nonicons_name and char(nonicons_name) or fallback_icon
local key = resolve_mod.resolve_filetype(ft)
icon = key and char(key) or fallback_icon
end
return icon, color
end
@ -527,8 +104,8 @@ function M.apply()
devicons.get_icon_cterm_color_by_filetype = function(ft, opts)
local icon, cterm_color = orig_get_icon_cterm_color_by_filetype(ft, opts)
if icon then
local nonicons_name = ext_map[ft]
icon = nonicons_name and char(nonicons_name) or fallback_icon
local key = resolve_mod.resolve_filetype(ft)
icon = key and char(key) or fallback_icon
end
return icon, cterm_color
end
@ -536,7 +113,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 = ext_map[ext]
local name = resolve_mod.ext_map[ext] or resolve_mod.ext_map[ext:lower()]
if name then
data.icon = char(name) or fallback_icon
else
@ -546,7 +123,7 @@ function M.apply()
local by_filename = devicons.get_icons_by_filename()
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
data.icon = char(name) or fallback_icon
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
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'
[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