minimal extui fuzzy finder for neovim

feat: add icon support

+50 -7
+2
README.md
··· 12 12 - Lightweight picker window built on Neovim's extui 13 13 - Prompt + list UI components - minimal and focused 14 14 - Fuzzy filtering using matchfuzzy (built-in) 15 + - Icon support for common filetypes through [mini.icons](https://github.com/echasnovski/mini.nvim) _(optional)_ 15 16 - No heavy dependencies - pure Lua 16 17 17 18 ## installation ··· 39 40 bottom = true, 40 41 promptprefix = "", 41 42 pointer = "", 43 + use_icons = true, -- requires mini.icons 42 44 }, 43 45 win = { 44 46 height = 12,
+9 -1
lua/artio/builtins.lua
··· 7 7 end 8 8 9 9 local artio = lzrq("artio") 10 + local config = lzrq("artio.config") 10 11 11 12 local builtins = {} 12 13 ··· 47 48 vim.cmd.edit(text) 48 49 end) 49 50 end, 51 + get_icon = config.get().opts.use_icons and function(item) 52 + return require("mini.icons").get("file", item.v) 53 + end or nil, 50 54 }) 51 55 end 52 56 ··· 92 96 vim.schedule(function() 93 97 vim.cmd.buffer(bufnr) 94 98 end) 95 - end) 99 + end, { 100 + get_icon = config.get().opts.use_icons and function(item) 101 + return require("mini.icons").get("file", vim.api.nvim_buf_get_name(item.v)) 102 + end or nil, 103 + }) 96 104 end 97 105 98 106 return builtins
+2
lua/artio/config.lua
··· 9 9 ---@field bottom boolean 10 10 ---@field promptprefix string 11 11 ---@field pointer string 12 + ---@field use_icons boolean 12 13 13 14 ---@class artio.config.win 14 15 ---@field height? integer|number ··· 25 26 bottom = true, 26 27 promptprefix = "", 27 28 pointer = "", 29 + use_icons = package.loaded['mini.icons'] and true or false, 28 30 }, 29 31 win = { 30 32 height = 12,
+2 -1
lua/artio/picker.lua
··· 1 1 local View = require("artio.view") 2 2 3 - ---@alias artio.Picker.item { id: integer, v: string, text: string } 3 + ---@alias artio.Picker.item { id: integer, v: string, text: string, icon?: string, icon_hl?: string } 4 4 ---@alias artio.Picker.match [integer, integer[], integer] [item, pos[], score] 5 5 6 6 ---@class artio.Picker.proto ··· 8 8 ---@field fn? fun(lst: artio.Picker.item[], input: string): artio.Picker.match[] 9 9 ---@field on_close? fun(text: string, idx: integer) 10 10 ---@field format_item? fun(item: string): string 11 + ---@field get_icon? fun(item: artio.Picker.item): string, string 11 12 ---@field opts? artio.config.opts 12 13 ---@field win? artio.config.win 13 14 ---@field prompt? string
+35 -5
lua/artio/view.lua
··· 361 361 offset = math.min(math.max(0, offset), math.max(0, #self.picker.matches - maxlistheight)) 362 362 end 363 363 364 + local icon_pad = 2 365 + 364 366 function View:showmatches() 365 367 local indent = vim.fn.strdisplaywidth(self.picker.opts.pointer) + 1 366 368 local prefix = (" "):rep(indent) 369 + local icon_pad_str = (" "):rep(icon_pad) 367 370 368 371 self:updateoffset() 369 372 370 373 local lines = {} ---@type string[] 371 374 local hls = {} 375 + local icons = {} ---@type ([string, string]|false)[] 372 376 for i = 1 + offset, math.min(#self.picker.matches, maxlistheight + offset) do 373 377 local match = self.picker.matches[i] 374 - lines[#lines + 1] = ("%s%s"):format(prefix, self.picker.items[match[1]].text) 378 + local item = self.picker.items[match[1]] 379 + 380 + local icon, icon_hl = item.icon, item.icon_hl 381 + if not (icon and icon_hl) and vim.is_callable(self.picker.get_icon) then 382 + icon, icon_hl = self.picker.get_icon(item) 383 + item.icon, item.icon_hl = icon, icon_hl 384 + end 385 + icons[#icons + 1] = icon and { icon, icon_hl } or false 386 + icon = icon and ("%s%s"):format(item.icon, icon_pad_str) or "" 387 + 388 + lines[#lines + 1] = ("%s%s%s"):format(prefix, icon, item.text) 375 389 hls[#hls + 1] = match[2] 376 390 end 377 391 cmdline.erow = cmdline.srow + #lines 378 392 vim.api.nvim_buf_set_lines(ext.bufs.cmd, cmdline.srow, cmdline.erow, false, lines) 379 393 380 - for i = 1, #hls do 381 - for j = 1, #hls[i] do 382 - local col = indent + hls[i][j] 383 - self:mark(cmdline.srow + i - 1, col, vim.tbl_extend("force", ext_match_opts, { end_col = col + 1 })) 394 + for i = 1, #lines do 395 + local has_icon = icons[i] and icons[i][1] and true 396 + local icon_indent = has_icon and (#icons[i][1] + icon_pad) or 0 397 + 398 + if has_icon and icons[i][2] then 399 + self:mark( 400 + cmdline.srow + i - 1, 401 + indent, 402 + vim.tbl_extend("force", ext_match_opts, { 403 + end_col = indent + icon_indent, 404 + hl_group = icons[i][2], 405 + }) 406 + ) 407 + end 408 + 409 + if hls[i] then 410 + for j = 1, #hls[i] do 411 + local col = indent + icon_indent + hls[i][j] 412 + self:mark(cmdline.srow + i - 1, col, vim.tbl_extend("force", ext_match_opts, { end_col = col + 1 })) 413 + end 384 414 end 385 415 end 386 416 end