minimal extui fuzzy finder for neovim

feat: add marks

+82 -8
+7 -8
lua/artio/builtins.lua
··· 10 local config = lzrq("artio.config") 11 local utils = lzrq("artio.utils") 12 13 - local function extend(t1, t2) 14 - return vim.tbl_deep_extend("force", t1, t2) 15 end 16 17 local builtins = {} ··· 39 preview_item = function(item) 40 return vim.fn.bufadd(item) 41 end, 42 - actions = { 43 - setqflist = utils.make_setqflist(function(item) 44 return { filename = item.v } 45 end), 46 - }, 47 - mappings = { 48 - ["<c-q>"] = "setqflist", 49 - }, 50 }, props) 51 ) 52 end
··· 10 local config = lzrq("artio.config") 11 local utils = lzrq("artio.utils") 12 13 + local function extend(t1, ...) 14 + return vim.tbl_deep_extend("force", t1, ...) 15 end 16 17 local builtins = {} ··· 39 preview_item = function(item) 40 return vim.fn.bufadd(item) 41 end, 42 + actions = extend( 43 + {}, 44 + utils.make_setqflistactions(function(item) 45 return { filename = item.v } 46 end), 47 + props.actions or {} 48 + ), 49 }, props) 50 ) 51 end
+5
lua/artio/config.lua
··· 12 ---@field promptprefix string 13 ---@field prompt_title boolean 14 ---@field pointer string 15 ---@field use_icons boolean 16 17 ---@class artio.config.win ··· 32 promptprefix = "", 33 prompt_title = true, 34 pointer = "", 35 use_icons = package.loaded["mini.icons"] and true or false, 36 }, 37 win = { ··· 51 ["<up>"] = "up", 52 ["<cr>"] = "accept", 53 ["<esc>"] = "cancel", 54 ["<c-l>"] = "togglepreview", 55 }, 56 } 57
··· 12 ---@field promptprefix string 13 ---@field prompt_title boolean 14 ---@field pointer string 15 + ---@field marker string 16 ---@field use_icons boolean 17 18 ---@class artio.config.win ··· 33 promptprefix = "", 34 prompt_title = true, 35 pointer = "", 36 + marker = "│", 37 use_icons = package.loaded["mini.icons"] and true or false, 38 }, 39 win = { ··· 53 ["<up>"] = "up", 54 ["<cr>"] = "accept", 55 ["<esc>"] = "cancel", 56 + ["<tab>"] = "mark", 57 ["<c-l>"] = "togglepreview", 58 + ["<c-q>"] = "setqflist", 59 + ["<c-w>"] = "setqflistmark", 60 }, 61 } 62
+28
lua/artio/picker.lua
··· 27 ---@class artio.Picker : artio.Picker.config 28 ---@field idx integer 1-indexed 29 ---@field matches artio.Picker.match[] 30 ---@field actions? artio.Actions 31 local Picker = {} 32 Picker.__index = Picker ··· 55 cancel = function(_, co) 56 coroutine.resume(co, action_enum.cancel) 57 end, 58 togglepreview = function(self, _) 59 self.view:togglepreview() 60 end, ··· 72 idx = 0, 73 items = {}, 74 matches = {}, 75 }, require("artio.config").get(), props) 76 77 if not t.prompttext then ··· 165 table.sort(self.matches, function(a, b) 166 return a[3] > b[3] 167 end) 168 end 169 170 return Picker
··· 27 ---@class artio.Picker : artio.Picker.config 28 ---@field idx integer 1-indexed 29 ---@field matches artio.Picker.match[] 30 + ---@field marked table<integer, true|nil> 31 ---@field actions? artio.Actions 32 local Picker = {} 33 Picker.__index = Picker ··· 56 cancel = function(_, co) 57 coroutine.resume(co, action_enum.cancel) 58 end, 59 + mark = function(self, _) 60 + local match = self.matches[self.idx] 61 + if not match then 62 + return 63 + end 64 + local idx = match[1] 65 + self:mark(idx, not self.marked[idx]) 66 + self.view:showmatches() -- redraw marker 67 + self.view:hlselect() 68 + end, 69 togglepreview = function(self, _) 70 self.view:togglepreview() 71 end, ··· 83 idx = 0, 84 items = {}, 85 matches = {}, 86 + marked = {}, 87 }, require("artio.config").get(), props) 88 89 if not t.prompttext then ··· 177 table.sort(self.matches, function(a, b) 178 return a[3] > b[3] 179 end) 180 + end 181 + 182 + ---@param idx integer 183 + ---@param yes? boolean 184 + function Picker:mark(idx, yes) 185 + self.marked[idx] = yes == nil and true or yes 186 + end 187 + 188 + ---@return integer[] 189 + function Picker:getmarked() 190 + return vim 191 + .iter(pairs(self.marked)) 192 + :map(function(k, v) 193 + return v and k or nil 194 + end) 195 + :totable() 196 end 197 198 return Picker
+30
lua/artio/utils.lua
··· 30 end 31 end 32 33 function utils.make_setqflist(fn) 34 return function(self, co) 35 vim.fn.setqflist(vim ··· 45 end) 46 coroutine.resume(co, 1) 47 end 48 end 49 50 return utils
··· 30 end 31 end 32 33 + ---@param fn fun(item: artio.Picker.item): vim.quickfix.entry 34 + ---@return artio.Picker.action 35 function utils.make_setqflist(fn) 36 return function(self, co) 37 vim.fn.setqflist(vim ··· 47 end) 48 coroutine.resume(co, 1) 49 end 50 + end 51 + 52 + ---@param fn fun(item: artio.Picker.item): vim.quickfix.entry 53 + ---@return artio.Picker.action 54 + function utils.make_setqflistmark(fn) 55 + return function(self, co) 56 + vim.fn.setqflist(vim 57 + .iter(ipairs(self:getmarked())) 58 + :map(function(_, id) 59 + local item = self.items[id] 60 + local qfitem = fn(item) 61 + return qfitem 62 + end) 63 + :totable()) 64 + vim.schedule(function() 65 + vim.cmd.copen() 66 + end) 67 + coroutine.resume(co, 1) 68 + end 69 + end 70 + 71 + ---@param fn fun(item: artio.Picker.item): vim.quickfix.entry 72 + ---@return table<string, artio.Picker.action> 73 + function utils.make_setqflistactions(fn) 74 + return { 75 + setqflist = utils.make_setqflist(fn), 76 + setqflistmark = utils.make_setqflistmark(fn), 77 + } 78 end 79 80 return utils
+12
lua/artio/view.lua
··· 441 local hls = {} 442 local icons = {} ---@type ([string, string]|false)[] 443 local custom_hls = {} ---@type (artio.Picker.hl[]|false)[] 444 for i = 1 + offset, math.min(#self.picker.matches, maxlistheight + offset) do 445 local match = self.picker.matches[i] 446 local item = self.picker.items[match[1]] ··· 459 item.hls = hl 460 end 461 custom_hls[#custom_hls + 1] = hl or false 462 463 lines[#lines + 1] = ("%s%s%s"):format(prefix, icon, item.text) 464 hls[#hls + 1] = match[2] ··· 500 }) 501 ) 502 end 503 end 504 505 if hls[i] then
··· 441 local hls = {} 442 local icons = {} ---@type ([string, string]|false)[] 443 local custom_hls = {} ---@type (artio.Picker.hl[]|false)[] 444 + local marks = {} ---@type boolean[] 445 for i = 1 + offset, math.min(#self.picker.matches, maxlistheight + offset) do 446 local match = self.picker.matches[i] 447 local item = self.picker.items[match[1]] ··· 460 item.hls = hl 461 end 462 custom_hls[#custom_hls + 1] = hl or false 463 + 464 + marks[#marks + 1] = self.picker.marked[item.id] or false 465 466 lines[#lines + 1] = ("%s%s%s"):format(prefix, icon, item.text) 467 hls[#hls + 1] = match[2] ··· 503 }) 504 ) 505 end 506 + end 507 + 508 + if marks[i] then 509 + self:mark(cmdline.srow + i - 1, indent - 1, { 510 + virt_text = { { self.picker.opts.marker, "ArtioMarker" } }, 511 + hl_mode = "combine", 512 + virt_text_pos = "overlay", 513 + invalidate = true, 514 + }) 515 end 516 517 if hls[i] then