From d07145eeaef78ac1f298d4d759a75a6df031b8fd Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Sun, 22 Feb 2026 21:35:45 -0500 Subject: [PATCH 01/13] 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. --- doc/nonicons.nvim.txt | 32 ++- lua/nonicons/init.lua | 14 ++ lua/nonicons/override.lua | 457 ++----------------------------------- lua/nonicons/resolve.lua | 469 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 527 insertions(+), 445 deletions(-) create mode 100644 lua/nonicons/resolve.lua diff --git a/doc/nonicons.nvim.txt b/doc/nonicons.nvim.txt index aca5336..0fd5043 100644 --- a/doc/nonicons.nvim.txt +++ b/doc/nonicons.nvim.txt @@ -53,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 @@ -128,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 diff --git a/lua/nonicons/init.lua b/lua/nonicons/init.lua index dbdb186..42c1539 100644 --- a/lua/nonicons/init.lua +++ b/lua/nonicons/init.lua @@ -40,4 +40,18 @@ function M.apply() end end +function M.get_icon(name, ext) + local key = require('nonicons.resolve').resolve_name(name, ext) + if key then + return M.get(key) + end +end + +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 diff --git a/lua/nonicons/override.lua b/lua/nonicons/override.lua index 9e18a9b..7c0e8b5 100644 --- a/lua/nonicons/override.lua +++ b/lua/nonicons/override.lua @@ -1,4 +1,5 @@ local mapping = require('nonicons.mapping') +local resolve_mod = require('nonicons.resolve') local function char(name) local code = mapping[name] @@ -9,440 +10,10 @@ end 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', -} - 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) end return fallback_icon end @@ -470,8 +41,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 +78,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 +88,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 +98,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 +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 = ext_map[ext] + local name = resolve_mod.ext_map[ext] if name then data.icon = char(name) or fallback_icon else @@ -546,7 +117,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] if name then data.icon = char(name) or fallback_icon else diff --git a/lua/nonicons/resolve.lua b/lua/nonicons/resolve.lua new file mode 100644 index 0000000..97f8738 --- /dev/null +++ b/lua/nonicons/resolve.lua @@ -0,0 +1,469 @@ +local M = {} + +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', + 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', +} + +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', +} + +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', +} + +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 dot_ext = lower:match('%.(.+)$') + if dot_ext and M.ext_map[dot_ext] then + return M.ext_map[dot_ext] + end + end +end + +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 From 747e7b62224af2dbdfc4152f008bd32badecff18 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 23 Feb 2026 17:19:30 -0500 Subject: [PATCH 02/13] fix: make fname resolution more robust; --- lua/nonicons/override.lua | 4 ++-- lua/nonicons/resolve.lua | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/lua/nonicons/override.lua b/lua/nonicons/override.lua index 7c0e8b5..64cccc1 100644 --- a/lua/nonicons/override.lua +++ b/lua/nonicons/override.lua @@ -107,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] + 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 @@ -117,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] + 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 diff --git a/lua/nonicons/resolve.lua b/lua/nonicons/resolve.lua index 97f8738..42a881a 100644 --- a/lua/nonicons/resolve.lua +++ b/lua/nonicons/resolve.lua @@ -106,7 +106,7 @@ M.ext_map = { pm = 'perl', r = 'r', - 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', @@ -443,9 +443,18 @@ function M.resolve_name(name, ext) if M.filename_map[lower] then return M.filename_map[lower] end - local dot_ext = lower:match('%.(.+)$') - if dot_ext and M.ext_map[dot_ext] then - return M.ext_map[dot_ext] + 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 From 6a8668fab5bba176f19c40776aaab5b393b11263 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 23 Feb 2026 17:25:57 -0500 Subject: [PATCH 03/13] fix(ci): luacats support --- doc/nonicons.nvim.txt | 10 +++++ lua/nonicons/hi-test.lua | 85 +++++++++++++++++++++++++++++++++++++++ lua/nonicons/init.lua | 8 ++++ lua/nonicons/override.lua | 6 +++ lua/nonicons/resolve.lua | 8 ++++ plugin/nonicons.lua | 4 ++ 6 files changed, 121 insertions(+) create mode 100644 lua/nonicons/hi-test.lua diff --git a/doc/nonicons.nvim.txt b/doc/nonicons.nvim.txt index 0fd5043..8b43865 100644 --- a/doc/nonicons.nvim.txt +++ b/doc/nonicons.nvim.txt @@ -232,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* diff --git a/lua/nonicons/hi-test.lua b/lua/nonicons/hi-test.lua new file mode 100644 index 0000000..ed22827 --- /dev/null +++ b/lua/nonicons/hi-test.lua @@ -0,0 +1,85 @@ +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 +---@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 +---@return table +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) + + 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 src_str = #sources > 0 and table.concat(sources, ', ') or '' + lines[#lines + 1] = string.format(' %s %-28s U+%04X %s', char(name), name, mapping[name], src_str) + 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) + vim.bo[buf].modifiable = false + vim.bo[buf].buftype = 'nofile' + vim.bo[buf].bufhidden = 'wipe' + vim.api.nvim_set_current_buf(buf) +end diff --git a/lua/nonicons/init.lua b/lua/nonicons/init.lua index 42c1539..e80afae 100644 --- a/lua/nonicons/init.lua +++ b/lua/nonicons/init.lua @@ -24,8 +24,11 @@ local function ensure_initialized() initialized = true end +---@type table M.mapping = require('nonicons.mapping') +---@param name string Icon name +---@return string? glyph function M.get(name) local code = M.mapping[name] if code then @@ -40,6 +43,9 @@ function M.apply() end end +---@param name string? Filename (e.g. `'init.lua'`) +---@param ext string? File extension (e.g. `'lua'`) +---@return string? glyph function M.get_icon(name, ext) local key = require('nonicons.resolve').resolve_name(name, ext) if key then @@ -47,6 +53,8 @@ function M.get_icon(name, ext) end end +---@param ft string Vim filetype +---@return string? glyph function M.get_icon_by_filetype(ft) local key = require('nonicons.resolve').resolve_filetype(ft) if key then diff --git a/lua/nonicons/override.lua b/lua/nonicons/override.lua index 64cccc1..bd44c8b 100644 --- a/lua/nonicons/override.lua +++ b/lua/nonicons/override.lua @@ -1,6 +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 @@ -8,8 +10,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 diff --git a/lua/nonicons/resolve.lua b/lua/nonicons/resolve.lua index 42a881a..c7906ef 100644 --- a/lua/nonicons/resolve.lua +++ b/lua/nonicons/resolve.lua @@ -1,5 +1,6 @@ local M = {} +---@type table M.ext_map = { lua = 'lua', luac = 'lua', @@ -287,6 +288,7 @@ M.ext_map = { mojo = 'code', } +---@type table M.filename_map = { ['dockerfile'] = 'docker', ['containerfile'] = 'docker', @@ -421,6 +423,7 @@ M.filename_map = { ['code_of_conduct.md'] = 'book', } +---@type table M.ft_map = { typescriptreact = 'react', javascriptreact = 'react', @@ -434,6 +437,9 @@ 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] @@ -459,6 +465,8 @@ function M.resolve_name(name, ext) end end +---@param ft string? Vim filetype +---@return string? icon_name function M.resolve_filetype(ft) if not ft then return diff --git a/plugin/nonicons.lua b/plugin/nonicons.lua index 7219658..e381004 100644 --- a/plugin/nonicons.lua +++ b/plugin/nonicons.lua @@ -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' }) From b6d8733c3e19c113d2d00c4d024613a1b83cc298 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 23 Feb 2026 17:26:04 -0500 Subject: [PATCH 04/13] ci: format --- lua/nonicons/hi-test.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lua/nonicons/hi-test.lua b/lua/nonicons/hi-test.lua index ed22827..47de1f0 100644 --- a/lua/nonicons/hi-test.lua +++ b/lua/nonicons/hi-test.lua @@ -48,7 +48,8 @@ return function() 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.format(' %-4s %-28s %-7s %s', 'ICON', 'NAME', 'CODE', 'MAPPED FROM') lines[#lines + 1] = string.rep('-', 72) for _, name in ipairs(icon_names) do @@ -70,7 +71,8 @@ return function() end local src_str = #sources > 0 and table.concat(sources, ', ') or '' - lines[#lines + 1] = string.format(' %s %-28s U+%04X %s', char(name), name, mapping[name], src_str) + lines[#lines + 1] = + string.format(' %s %-28s U+%04X %s', char(name), name, mapping[name], src_str) end lines[#lines + 1] = '' From 6f445c30d66234618633d4f8ad8e8c347b089695 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 23 Feb 2026 17:27:13 -0500 Subject: [PATCH 05/13] fix: duplicate table index --- lua/nonicons/resolve.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/lua/nonicons/resolve.lua b/lua/nonicons/resolve.lua index c7906ef..c3e1960 100644 --- a/lua/nonicons/resolve.lua +++ b/lua/nonicons/resolve.lua @@ -106,7 +106,6 @@ M.ext_map = { pl = 'perl', pm = 'perl', - r = 'r', r = 'r', rmd = 'r', From 7d35b23ba09cd152d3c7db1d47520ef2a57e5af9 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 23 Feb 2026 17:31:45 -0500 Subject: [PATCH 06/13] fix: resolve luacats return-type-mismatch and cast-local-type warnings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- lua/nonicons/override.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/nonicons/override.lua b/lua/nonicons/override.lua index bd44c8b..d861524 100644 --- a/lua/nonicons/override.lua +++ b/lua/nonicons/override.lua @@ -19,7 +19,7 @@ local fallback_icon local function resolve(name, ext) local key = resolve_mod.resolve_name(name, ext) if key then - return char(key) + return char(key) or fallback_icon end return fallback_icon end @@ -32,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) From 92d00b66fc669d3df43708bc58f0070ecfabf66e Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 23 Feb 2026 17:31:50 -0500 Subject: [PATCH 07/13] 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. --- flake.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/flake.nix b/flake.nix index 5d6a2f4..c375a80 100644 --- a/flake.nix +++ b/flake.nix @@ -22,6 +22,7 @@ pkgs.prettier pkgs.stylua pkgs.selene + pkgs.lua-language-server ]; }; }); From 91609349d2bda36338eb730c9c9383d313317b0a Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 23 Feb 2026 18:14:33 -0500 Subject: [PATCH 08/13] ci: migrate to nix --- .github/workflows/quality.yaml | 29 +++++++---------------------- .gitignore | 1 + .luarc.json | 2 +- selene.toml | 3 +++ vim.toml | 33 --------------------------------- vim.yaml | 24 ++++++++++++++++++++++++ 6 files changed, 36 insertions(+), 56 deletions(-) delete mode 100644 vim.toml create mode 100644 vim.yaml diff --git a/.github/workflows/quality.yaml b/.github/workflows/quality.yaml index 70b168f..4a4795a 100644 --- a/.github/workflows/quality.yaml +++ b/.github/workflows/quality.yaml @@ -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 diff --git a/.gitignore b/.gitignore index eabe60d..93ac2c5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ node_modules/ result result-* .direnv/ +.envrc diff --git a/.luarc.json b/.luarc.json index b438cce..23646d3 100644 --- a/.luarc.json +++ b/.luarc.json @@ -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"], diff --git a/selene.toml b/selene.toml index 96cf5ab..f2ada4b 100644 --- a/selene.toml +++ b/selene.toml @@ -1 +1,4 @@ std = 'vim' + +[lints] +bad_string_escape = 'allow' diff --git a/vim.toml b/vim.toml deleted file mode 100644 index 3a84ac2..0000000 --- a/vim.toml +++ /dev/null @@ -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 diff --git a/vim.yaml b/vim.yaml new file mode 100644 index 0000000..401fce6 --- /dev/null +++ b/vim.yaml @@ -0,0 +1,24 @@ +--- +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 From 4335fb859687724dfe594057afc85b9728bd9e42 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Mar 2026 19:24:06 -0500 Subject: [PATCH 09/13] doc: note nvim-web-devicions as optional --- README.md | 2 +- vim.yaml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dc1759f..c604091 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/vim.yaml b/vim.yaml index 401fce6..3821d25 100644 --- a/vim.yaml +++ b/vim.yaml @@ -22,3 +22,5 @@ globals: any: true stub: any: true + bit: + any: true From 7a655c9919a8f18247a59d6d0fe0bd897e069433 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Mar 2026 19:40:26 -0500 Subject: [PATCH 10/13] feat(hl): highlight icons --- lua/nonicons/colors.lua | 71 +++++++++++++++++++++++++++++++++++++ lua/nonicons/health.lua | 2 +- lua/nonicons/hi-test.lua | 16 ++++++++- lua/nonicons/highlights.lua | 37 +++++++++++++++++++ lua/nonicons/init.lua | 7 +++- 5 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 lua/nonicons/colors.lua create mode 100644 lua/nonicons/highlights.lua diff --git a/lua/nonicons/colors.lua b/lua/nonicons/colors.lua new file mode 100644 index 0000000..8927e07 --- /dev/null +++ b/lua/nonicons/colors.lua @@ -0,0 +1,71 @@ +---@type table +local M = { + ['angular'] = { '#e23237', 161 }, + ['babel'] = { '#f9dc3e', 185 }, + ['book'] = { '#a8b1c7', 146 }, + ['c'] = { '#599eff', 111 }, + ['c-plusplus'] = { '#f34b7d', 204 }, + ['c-sharp'] = { '#68217a', 55 }, + ['code'] = { '#a9b7c6', 145 }, + ['css'] = { '#563d7c', 60 }, + ['dart'] = { '#00b4ab', 37 }, + ['database'] = { '#dad8d8', 188 }, + ['diff'] = { '#41535b', 59 }, + ['docker'] = { '#0db7ed', 39 }, + ['elixir'] = { '#a074c4', 140 }, + ['elm'] = { '#60b5cc', 74 }, + ['eslint'] = { '#4b32c3', 56 }, + ['file'] = { '#6d8086', 66 }, + ['file-binary'] = { '#9f0500', 124 }, + ['file-zip'] = { '#eca517', 214 }, + ['gear'] = { '#6d8086', 66 }, + ['git-branch'] = { '#f14e32', 196 }, + ['git-commit'] = { '#f14e32', 196 }, + ['globe'] = { '#519aba', 74 }, + ['go'] = { '#00add8', 38 }, + ['graphql'] = { '#e535ab', 199 }, + ['html'] = { '#e34c26', 166 }, + ['image'] = { '#a074c4', 140 }, + ['java'] = { '#cc3e44', 167 }, + ['javascript'] = { '#f1e05a', 185 }, + ['json'] = { '#fbc02d', 220 }, + ['key'] = { '#e6c419', 220 }, + ['kotlin'] = { '#7f52ff', 99 }, + ['law'] = { '#cbcb41', 185 }, + ['lock'] = { '#bbbbbb', 250 }, + ['log'] = { '#afb42b', 142 }, + ['logo-github'] = { '#f0eff1', 255 }, + ['lua'] = { '#51a0cf', 74 }, + ['markdown'] = { '#519aba', 74 }, + ['next'] = { '#a9b7c6', 145 }, + ['nginx'] = { '#009639', 28 }, + ['npm'] = { '#cb3837', 160 }, + ['package'] = { '#cb3837', 160 }, + ['perl'] = { '#39457e', 61 }, + ['php'] = { '#4f5d95', 61 }, + ['play'] = { '#e37933', 208 }, + ['prettier'] = { '#56b3b4', 73 }, + ['python'] = { '#3776ab', 68 }, + ['r'] = { '#276dc3', 68 }, + ['react'] = { '#61dafb', 75 }, + ['rss'] = { '#f26522', 208 }, + ['ruby'] = { '#cc342d', 160 }, + ['rust'] = { '#dea584', 180 }, + ['scala'] = { '#cc3e44', 167 }, + ['server'] = { '#6d8086', 66 }, + ['shield'] = { '#43853d', 35 }, + ['svelte'] = { '#ff3e00', 202 }, + ['swift'] = { '#f05138', 203 }, + ['terminal'] = { '#4d4d4d', 239 }, + ['terraform'] = { '#844fba', 135 }, + ['tmux'] = { '#1bb91f', 34 }, + ['toml'] = { '#9c4221', 130 }, + ['typescript'] = { '#3178c6', 68 }, + ['typography'] = { '#e34c26', 166 }, + ['vim'] = { '#019833', 28 }, + ['vue'] = { '#41b883', 35 }, + ['yaml'] = { '#cb171e', 160 }, + ['yarn'] = { '#2c8ebb', 68 }, +} + +return M diff --git a/lua/nonicons/health.lua b/lua/nonicons/health.lua index e78e2ae..275ba8c 100644 --- a/lua/nonicons/health.lua +++ b/lua/nonicons/health.lua @@ -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' }) diff --git a/lua/nonicons/hi-test.lua b/lua/nonicons/hi-test.lua index 47de1f0..79041ef 100644 --- a/lua/nonicons/hi-test.lua +++ b/lua/nonicons/hi-test.lua @@ -52,6 +52,10 @@ return function() 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 @@ -70,9 +74,11 @@ return function() 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', char(name), name, mapping[name], src_str) + string.format(' %s %-28s U+%04X %s', glyph, name, mapping[name], src_str) + icon_glyphs[#icon_glyphs + 1] = glyph end lines[#lines + 1] = '' @@ -80,6 +86,14 @@ return function() local buf = vim.api.nvim_create_buf(false, true) vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + + for i, name in ipairs(icon_names) do + local hl = highlights.get(name) + local col_start = 2 + local col_end = col_start + #icon_glyphs[i] + vim.api.nvim_buf_add_highlight(buf, -1, hl, icon_line_start + i - 1, col_start, col_end) + end + vim.bo[buf].modifiable = false vim.bo[buf].buftype = 'nofile' vim.bo[buf].bufhidden = 'wipe' diff --git a/lua/nonicons/highlights.lua b/lua/nonicons/highlights.lua new file mode 100644 index 0000000..fbf42e2 --- /dev/null +++ b/lua/nonicons/highlights.lua @@ -0,0 +1,37 @@ +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) + return to_hl_group(icon_name) +end + +return M diff --git a/lua/nonicons/init.lua b/lua/nonicons/init.lua index e80afae..526bf18 100644 --- a/lua/nonicons/init.lua +++ b/lua/nonicons/init.lua @@ -29,15 +29,18 @@ 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 @@ -46,6 +49,7 @@ 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 @@ -55,6 +59,7 @@ 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 From ba457909100d155883ce87de0b09ed095c599e5d Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Mar 2026 20:22:07 -0500 Subject: [PATCH 11/13] feat: highlight --- lua/nonicons/hi-test.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/nonicons/hi-test.lua b/lua/nonicons/hi-test.lua index 79041ef..8df09cd 100644 --- a/lua/nonicons/hi-test.lua +++ b/lua/nonicons/hi-test.lua @@ -87,11 +87,14 @@ return function() 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 hl = highlights.get(name) local col_start = 2 local col_end = col_start + #icon_glyphs[i] - vim.api.nvim_buf_add_highlight(buf, -1, hl, icon_line_start + i - 1, col_start, col_end) + 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 From e9a161236540c26eef187b7e744125c0439518c3 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Mar 2026 20:27:17 -0500 Subject: [PATCH 12/13] ci: systematically pull colors from nvim-web-devicons --- .github/workflows/sync.yaml | 24 +++++-- lua/nonicons/colors.lua | 128 ++++++++++++++++++------------------ lua/nonicons/highlights.lua | 6 +- scripts/gen-colors.lua | 39 +++++++++++ 4 files changed, 124 insertions(+), 73 deletions(-) create mode 100644 scripts/gen-colors.lua diff --git a/.github/workflows/sync.yaml b/.github/workflows/sync.yaml index b80393a..08758bc 100644 --- a/.github/workflows/sync.yaml +++ b/.github/workflows/sync.yaml @@ -32,10 +32,21 @@ 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; then + if git diff --quiet lua/nonicons/mapping.lua lua/nonicons/colors.lua; then echo "changed=false" >> "$GITHUB_OUTPUT" else echo "changed=true" >> "$GITHUB_OUTPUT" @@ -45,16 +56,17 @@ jobs: if: steps.diff.outputs.changed == 'true' uses: peter-evans/create-pull-request@v7 with: - branch: sync/upstream-mapping - title: 'fix(mapping): sync with upstream nonicons font' + branch: sync/upstream + title: 'fix: sync with upstream nonicons font and devicons colors' body: | ## 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 - Auto-generated `mapping.lua` from upstream `nonicon.json`. - commit-message: 'fix(mapping): sync with upstream nonicons font' + 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 diff --git a/lua/nonicons/colors.lua b/lua/nonicons/colors.lua index 8927e07..7092f12 100644 --- a/lua/nonicons/colors.lua +++ b/lua/nonicons/colors.lua @@ -1,71 +1,69 @@ ---@type table local M = { - ['angular'] = { '#e23237', 161 }, - ['babel'] = { '#f9dc3e', 185 }, - ['book'] = { '#a8b1c7', 146 }, - ['c'] = { '#599eff', 111 }, - ['c-plusplus'] = { '#f34b7d', 204 }, - ['c-sharp'] = { '#68217a', 55 }, - ['code'] = { '#a9b7c6', 145 }, - ['css'] = { '#563d7c', 60 }, - ['dart'] = { '#00b4ab', 37 }, - ['database'] = { '#dad8d8', 188 }, - ['diff'] = { '#41535b', 59 }, - ['docker'] = { '#0db7ed', 39 }, - ['elixir'] = { '#a074c4', 140 }, - ['elm'] = { '#60b5cc', 74 }, - ['eslint'] = { '#4b32c3', 56 }, - ['file'] = { '#6d8086', 66 }, - ['file-binary'] = { '#9f0500', 124 }, - ['file-zip'] = { '#eca517', 214 }, - ['gear'] = { '#6d8086', 66 }, - ['git-branch'] = { '#f14e32', 196 }, - ['git-commit'] = { '#f14e32', 196 }, - ['globe'] = { '#519aba', 74 }, - ['go'] = { '#00add8', 38 }, - ['graphql'] = { '#e535ab', 199 }, - ['html'] = { '#e34c26', 166 }, - ['image'] = { '#a074c4', 140 }, - ['java'] = { '#cc3e44', 167 }, - ['javascript'] = { '#f1e05a', 185 }, - ['json'] = { '#fbc02d', 220 }, - ['key'] = { '#e6c419', 220 }, - ['kotlin'] = { '#7f52ff', 99 }, - ['law'] = { '#cbcb41', 185 }, - ['lock'] = { '#bbbbbb', 250 }, - ['log'] = { '#afb42b', 142 }, - ['logo-github'] = { '#f0eff1', 255 }, - ['lua'] = { '#51a0cf', 74 }, - ['markdown'] = { '#519aba', 74 }, - ['next'] = { '#a9b7c6', 145 }, - ['nginx'] = { '#009639', 28 }, - ['npm'] = { '#cb3837', 160 }, - ['package'] = { '#cb3837', 160 }, - ['perl'] = { '#39457e', 61 }, - ['php'] = { '#4f5d95', 61 }, - ['play'] = { '#e37933', 208 }, - ['prettier'] = { '#56b3b4', 73 }, - ['python'] = { '#3776ab', 68 }, - ['r'] = { '#276dc3', 68 }, - ['react'] = { '#61dafb', 75 }, - ['rss'] = { '#f26522', 208 }, - ['ruby'] = { '#cc342d', 160 }, - ['rust'] = { '#dea584', 180 }, - ['scala'] = { '#cc3e44', 167 }, - ['server'] = { '#6d8086', 66 }, - ['shield'] = { '#43853d', 35 }, - ['svelte'] = { '#ff3e00', 202 }, - ['swift'] = { '#f05138', 203 }, - ['terminal'] = { '#4d4d4d', 239 }, - ['terraform'] = { '#844fba', 135 }, - ['tmux'] = { '#1bb91f', 34 }, - ['toml'] = { '#9c4221', 130 }, - ['typescript'] = { '#3178c6', 68 }, - ['typography'] = { '#e34c26', 166 }, + ['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'] = { '#41b883', 35 }, - ['yaml'] = { '#cb171e', 160 }, - ['yarn'] = { '#2c8ebb', 68 }, + ['vue'] = { '#8DC149', 113 }, + ['yaml'] = { '#6D8086', 66 }, + ['yarn'] = { '#F9AD02', 214 }, } return M diff --git a/lua/nonicons/highlights.lua b/lua/nonicons/highlights.lua index fbf42e2..0398c49 100644 --- a/lua/nonicons/highlights.lua +++ b/lua/nonicons/highlights.lua @@ -29,9 +29,11 @@ function M.setup() end ---@param icon_name string ----@return string hl_group +---@return string? hl_group function M.get(icon_name) - return to_hl_group(icon_name) + if colors[icon_name] then + return to_hl_group(icon_name) + end end return M diff --git a/scripts/gen-colors.lua b/scripts/gen-colors.lua new file mode 100644 index 0000000..8895e81 --- /dev/null +++ b/scripts/gen-colors.lua @@ -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\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") From 949da427bf267c6caaa4c0f9e70ce9b937a8436c Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Mon, 2 Mar 2026 20:28:25 -0500 Subject: [PATCH 13/13] ci: format --- scripts/gen-colors.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/gen-colors.lua b/scripts/gen-colors.lua index 8895e81..f26c28c 100644 --- a/scripts/gen-colors.lua +++ b/scripts/gen-colors.lua @@ -30,10 +30,10 @@ for name in pairs(colors) do end table.sort(names) -io.write("---@type table\n") -io.write("local M = {\n") +io.write('---@type table\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") +io.write('}\n\nreturn M\n')