minimal extui fuzzy finder for neovim

feat: allow toggling between live and fuzzy search

use `<c-g>` by default to toggle between live and fuzzy search (inspired
by fzf-lua).

uses `togglelive` action.

+72 -5
+1
README.md
··· 69 ["<cr>"] = "accept", 70 ["<esc>"] = "cancel", 71 ["<tab>"] = "mark", 72 ["<c-l>"] = "togglepreview", 73 ["<c-q>"] = "setqflist", 74 ["<m-q>"] = "setqflistmark",
··· 69 ["<cr>"] = "accept", 70 ["<esc>"] = "cancel", 71 ["<tab>"] = "mark", 72 + ["<c-g>"] = "togglelive", 73 ["<c-l>"] = "togglepreview", 74 ["<c-q>"] = "setqflist", 75 ["<m-q>"] = "setqflistmark",
+1
lua/artio/config.lua
··· 49 ["<cr>"] = "accept", 50 ["<esc>"] = "cancel", 51 ["<tab>"] = "mark", 52 ["<c-l>"] = "togglepreview", 53 ["<c-q>"] = "setqflist", 54 ["<m-q>"] = "setqflistmark",
··· 49 ["<cr>"] = "accept", 50 ["<esc>"] = "cancel", 51 ["<tab>"] = "mark", 52 + ["<c-g>"] = "togglelive", 53 ["<c-l>"] = "togglepreview", 54 ["<c-q>"] = "setqflist", 55 ["<m-q>"] = "setqflistmark",
+50 -3
lua/artio/picker.lua
··· 17 ---@field get_icon? fun(item: artio.Picker.item): string, string 18 ---@field hl_item? fun(item: artio.Picker.item): artio.Picker.hl[] 19 ---@field on_quit? fun() 20 ---@field prompt? string 21 ---@field defaulttext? string 22 ---@field prompttext? string ··· 28 ---@class artio.Picker : artio.Picker.config 29 ---@field co thread|nil 30 ---@field input string 31 ---@field idx integer 1-indexed 32 ---@field matches artio.Picker.match[] 33 ---@field marked table<integer, true|nil> 34 local Picker = {} 35 Picker.__index = Picker 36 Picker.active_picker = nil ··· 45 closed = false, 46 prompt = "", 47 input = nil, 48 idx = 0, 49 items = {}, 50 matches = {}, ··· 55 t.prompttext = t.opts.prompt_title and ("%s %s"):format(t.prompt, t.opts.promptprefix) or t.opts.promptprefix 56 end 57 58 - t.input = t.defaulttext or "" 59 60 Picker.getitems(t, "") 61 ··· 204 end 205 206 function Picker:getitems(input) 207 - self.items = self.get_items and self.get_items(input) or self.items 208 if #self.items > 0 and not item_is_structured(self.items[1]) then 209 self.items = vim 210 .iter(ipairs(self.items)) ··· 226 227 ---@param input? string 228 function Picker:getmatches(input) 229 - input = input or self.input 230 self:getitems(input) 231 self.matches = vim.tbl_values(self.fn(self.items, input)) 232 table.sort(self.matches, function(a, b) 233 return a[3] > b[3] 234 end) 235 end 236 237 ---@param idx integer 238 ---@param yes? boolean 239 function Picker:mark(idx, yes) ··· 262 end 263 264 return self.items[idx] 265 end 266 267 return Picker
··· 17 ---@field get_icon? fun(item: artio.Picker.item): string, string 18 ---@field hl_item? fun(item: artio.Picker.item): artio.Picker.hl[] 19 ---@field on_quit? fun() 20 + ---@field live? boolean 21 ---@field prompt? string 22 ---@field defaulttext? string 23 ---@field prompttext? string ··· 29 ---@class artio.Picker : artio.Picker.config 30 ---@field co thread|nil 31 ---@field input string 32 + ---@field liveinput? string 33 ---@field idx integer 1-indexed 34 ---@field matches artio.Picker.match[] 35 ---@field marked table<integer, true|nil> 36 + ---@field live boolean 37 local Picker = {} 38 Picker.__index = Picker 39 Picker.active_picker = nil ··· 48 closed = false, 49 prompt = "", 50 input = nil, 51 + liveinput = nil, 52 idx = 0, 53 items = {}, 54 matches = {}, ··· 59 t.prompttext = t.opts.prompt_title and ("%s %s"):format(t.prompt, t.opts.promptprefix) or t.opts.promptprefix 60 end 61 62 + t.live = vim.F.if_nil(t.live, t.get_items ~= nil) 63 + 64 + if t.live then 65 + t.input = "" 66 + t.liveinput = t.defaulttext or "" 67 + else 68 + t.input = t.defaulttext or "" 69 + t.liveinput = "" 70 + end 71 72 Picker.getitems(t, "") 73 ··· 216 end 217 218 function Picker:getitems(input) 219 + if self.live then 220 + self.items = self.get_items and self.get_items(input) or self.items 221 + end 222 + 223 if #self.items > 0 and not item_is_structured(self.items[1]) then 224 self.items = vim 225 .iter(ipairs(self.items)) ··· 241 242 ---@param input? string 243 function Picker:getmatches(input) 244 + if not input then 245 + input = self.live and self.liveinput or self.input 246 + end 247 self:getitems(input) 248 + 249 + -- if live, ignore sorting 250 + if self.live then 251 + self.matches = self:getallmatches() 252 + return 253 + end 254 + 255 self.matches = vim.tbl_values(self.fn(self.items, input)) 256 table.sort(self.matches, function(a, b) 257 return a[3] > b[3] 258 end) 259 end 260 261 + ---@return artio.Picker.match[] 262 + function Picker:getallmatches() 263 + return vim 264 + .iter(ipairs(self.items)) 265 + :map(function(_, v) 266 + return { v.id, {}, 0 } 267 + end) 268 + :totable() 269 + end 270 + 271 ---@param idx integer 272 ---@param yes? boolean 273 function Picker:mark(idx, yes) ··· 296 end 297 298 return self.items[idx] 299 + end 300 + 301 + function Picker:togglelive() 302 + -- check if live can be toggled 303 + if not self.get_items then 304 + return 305 + end 306 + 307 + -- reset fuzzy search when enabling live search 308 + if not self.live then 309 + self.input = "" 310 + end 311 + self.live = not self.live 312 end 313 314 return Picker
+12 -2
lua/artio/view.lua
··· 394 395 function View:trigger_show() 396 logdebug("trigger_show") 397 - self:show({ { 0, self.picker.input } }, -1, "", self.picker.prompttext, cmdline.indent, cmdline.level, prompt_hl_id) 398 end 399 400 ---@param force? boolean ··· 409 local text = vim.api.nvim_get_current_line() 410 text = text:sub(promptlen + 1) 411 412 - self.picker.input = text 413 414 vim.schedule(coroutine.wrap(function() 415 logdebug("getmatches")
··· 394 395 function View:trigger_show() 396 logdebug("trigger_show") 397 + local input 398 + if self.picker.live then 399 + input = self.picker.liveinput 400 + else 401 + input = self.picker.input 402 + end 403 + self:show({ { 0, input } }, -1, "", self.picker.prompttext, cmdline.indent, cmdline.level, prompt_hl_id) 404 end 405 406 ---@param force? boolean ··· 415 local text = vim.api.nvim_get_current_line() 416 text = text:sub(promptlen + 1) 417 418 + if self.picker.live then 419 + self.picker.liveinput = text 420 + else 421 + self.picker.input = text 422 + end 423 424 vim.schedule(coroutine.wrap(function() 425 logdebug("getmatches")
+8
plugin/artio.lua
··· 159 self.view:togglepreview() 160 end) 161 )
··· 159 self.view:togglepreview() 160 end) 161 ) 162 + vim.keymap.set( 163 + "i", 164 + "<Plug>(artio-action-togglelive)", 165 + wrap(function(self) 166 + self:togglelive() 167 + self.view:trigger_show() -- update input 168 + end) 169 + )