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 69 ["<cr>"] = "accept", 70 70 ["<esc>"] = "cancel", 71 71 ["<tab>"] = "mark", 72 + ["<c-g>"] = "togglelive", 72 73 ["<c-l>"] = "togglepreview", 73 74 ["<c-q>"] = "setqflist", 74 75 ["<m-q>"] = "setqflistmark",
+1
lua/artio/config.lua
··· 49 49 ["<cr>"] = "accept", 50 50 ["<esc>"] = "cancel", 51 51 ["<tab>"] = "mark", 52 + ["<c-g>"] = "togglelive", 52 53 ["<c-l>"] = "togglepreview", 53 54 ["<c-q>"] = "setqflist", 54 55 ["<m-q>"] = "setqflistmark",
+50 -3
lua/artio/picker.lua
··· 17 17 ---@field get_icon? fun(item: artio.Picker.item): string, string 18 18 ---@field hl_item? fun(item: artio.Picker.item): artio.Picker.hl[] 19 19 ---@field on_quit? fun() 20 + ---@field live? boolean 20 21 ---@field prompt? string 21 22 ---@field defaulttext? string 22 23 ---@field prompttext? string ··· 28 29 ---@class artio.Picker : artio.Picker.config 29 30 ---@field co thread|nil 30 31 ---@field input string 32 + ---@field liveinput? string 31 33 ---@field idx integer 1-indexed 32 34 ---@field matches artio.Picker.match[] 33 35 ---@field marked table<integer, true|nil> 36 + ---@field live boolean 34 37 local Picker = {} 35 38 Picker.__index = Picker 36 39 Picker.active_picker = nil ··· 45 48 closed = false, 46 49 prompt = "", 47 50 input = nil, 51 + liveinput = nil, 48 52 idx = 0, 49 53 items = {}, 50 54 matches = {}, ··· 55 59 t.prompttext = t.opts.prompt_title and ("%s %s"):format(t.prompt, t.opts.promptprefix) or t.opts.promptprefix 56 60 end 57 61 58 - t.input = t.defaulttext or "" 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 59 71 60 72 Picker.getitems(t, "") 61 73 ··· 204 216 end 205 217 206 218 function Picker:getitems(input) 207 - self.items = self.get_items and self.get_items(input) or self.items 219 + if self.live then 220 + self.items = self.get_items and self.get_items(input) or self.items 221 + end 222 + 208 223 if #self.items > 0 and not item_is_structured(self.items[1]) then 209 224 self.items = vim 210 225 .iter(ipairs(self.items)) ··· 226 241 227 242 ---@param input? string 228 243 function Picker:getmatches(input) 229 - input = input or self.input 244 + if not input then 245 + input = self.live and self.liveinput or self.input 246 + end 230 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 + 231 255 self.matches = vim.tbl_values(self.fn(self.items, input)) 232 256 table.sort(self.matches, function(a, b) 233 257 return a[3] > b[3] 234 258 end) 235 259 end 236 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 + 237 271 ---@param idx integer 238 272 ---@param yes? boolean 239 273 function Picker:mark(idx, yes) ··· 262 296 end 263 297 264 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 265 312 end 266 313 267 314 return Picker
+12 -2
lua/artio/view.lua
··· 394 394 395 395 function View:trigger_show() 396 396 logdebug("trigger_show") 397 - self:show({ { 0, self.picker.input } }, -1, "", self.picker.prompttext, cmdline.indent, cmdline.level, prompt_hl_id) 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) 398 404 end 399 405 400 406 ---@param force? boolean ··· 409 415 local text = vim.api.nvim_get_current_line() 410 416 text = text:sub(promptlen + 1) 411 417 412 - self.picker.input = text 418 + if self.picker.live then 419 + self.picker.liveinput = text 420 + else 421 + self.picker.input = text 422 + end 413 423 414 424 vim.schedule(coroutine.wrap(function() 415 425 logdebug("getmatches")
+8
plugin/artio.lua
··· 159 159 self.view:togglepreview() 160 160 end) 161 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 + )