minimal extui fuzzy finder for neovim
at 3ccbd8cfef81e04868a4df1d155fed08bf870c3d 303 lines 7.4 kB view raw
1local function lzrq(modname) 2 return setmetatable({}, { 3 __index = function(_, key) 4 return require(modname)[key] 5 end, 6 }) 7end 8 9local artio = lzrq("artio") 10local config = lzrq("artio.config") 11 12local builtins = {} 13 14local findprg = "fd -H -p -t f --color=never" 15 16local function find_files(match) 17 if not findprg then 18 return {} 19 end 20 local farg = string.format("'%s'", match or "") 21 local findcmd, n = findprg:gsub("%$%*", farg) 22 if n == 0 then 23 findcmd = findcmd .. " " .. farg 24 end 25 local fn = function(o) 26 local src = o.stderr 27 if o.code == 0 then 28 src = o.stdout 29 end 30 src = src 31 local lines = vim.split(src, "\n", { trimempty = true }) 32 return lines 33 end 34 return fn(vim 35 .system({ vim.o.shell, "-c", findcmd }, { 36 text = true, 37 }) 38 :wait()) 39end 40 41builtins.files = function() 42 local lst = find_files() 43 44 return artio.generic(lst, { 45 prompt = "files", 46 on_close = function(text, _) 47 vim.schedule(function() 48 vim.cmd.edit(text) 49 end) 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, 54 preview_item = function(item) 55 return vim.fn.bufadd(item) 56 end, 57 }) 58end 59 60local function find_oldfiles() 61 return vim 62 .iter(vim.v.oldfiles) 63 :filter(function(v) 64 return vim.uv.fs_stat(v) --[[@as boolean]] 65 end) 66 :totable() 67end 68 69builtins.oldfiles = function() 70 local lst = find_oldfiles() 71 72 return artio.generic(lst, { 73 prompt = "oldfiles", 74 on_close = function(text, _) 75 vim.schedule(function() 76 vim.cmd.edit(text) 77 end) 78 end, 79 get_icon = config.get().opts.use_icons and function(item) 80 return require("mini.icons").get("file", item.v) 81 end or nil, 82 preview_item = function(item) 83 return vim.fn.bufadd(item) 84 end, 85 }) 86end 87 88builtins.livegrep = function() 89 local win = vim.api.nvim_get_current_win() 90 local buf = vim.api.nvim_win_get_buf(win) 91 local n = vim.api.nvim_buf_line_count(buf) 92 local lst = {} ---@type integer[] 93 for i = 1, n do 94 lst[#lst + 1] = i 95 end 96 97 local pad = #tostring(lst[#lst]) 98 99 return artio.generic(lst, { 100 prompt = "livegrep", 101 on_close = function(row, _) 102 vim.schedule(function() 103 vim.api.nvim_win_set_cursor(win, { row, 0 }) 104 end) 105 end, 106 format_item = function(row) 107 return vim.api.nvim_buf_get_lines(buf, row - 1, row, true)[1] 108 end, 109 preview_item = function(row) 110 return buf, 111 function(w) 112 vim.api.nvim_set_option_value("cursorline", true, { scope = "local", win = w }) 113 vim.api.nvim_win_set_cursor(w, { row, 0 }) 114 end 115 end, 116 get_icon = function(row) 117 local v = tostring(row.v) 118 return ("%s%s"):format((" "):rep(pad - #v), v) 119 end, 120 }) 121end 122 123local function find_helptags() 124 local buf = vim.api.nvim_create_buf(false, true) 125 vim.bo[buf].buftype = "help" 126 local tags = vim.api.nvim_buf_call(buf, function() 127 return vim.fn.taglist(".*") 128 end) 129 vim.api.nvim_buf_delete(buf, { force = true }) 130 return vim.tbl_map(function(t) 131 return t.name 132 end, tags) 133end 134 135builtins.helptags = function() 136 local lst = find_helptags() 137 138 return artio.generic(lst, { 139 prompt = "helptags", 140 on_close = function(text, _) 141 vim.schedule(function() 142 vim.cmd.help(text) 143 end) 144 end, 145 }) 146end 147 148local function find_buffers() 149 return vim 150 .iter(vim.api.nvim_list_bufs()) 151 :filter(function(bufnr) 152 return vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted 153 end) 154 :totable() 155end 156 157builtins.buffers = function() 158 local lst = find_buffers() 159 160 return artio.select(lst, { 161 prompt = "buffers", 162 format_item = function(bufnr) 163 return vim.api.nvim_buf_get_name(bufnr) 164 end, 165 }, function(bufnr, _) 166 vim.schedule(function() 167 vim.cmd.buffer(bufnr) 168 end) 169 end, { 170 get_icon = config.get().opts.use_icons and function(item) 171 return require("mini.icons").get("file", vim.api.nvim_buf_get_name(item.v)) 172 end or nil, 173 preview_item = function(item) 174 return item 175 end, 176 }) 177end 178 179---@param currentfile string 180---@param item string 181---@return integer score 182local function matchproximity(currentfile, item) 183 item = vim.fs.abspath(item) 184 185 return vim.iter(ipairs(vim.split(item, "/", { trimempty = true }))):fold(0, function(score, i, part) 186 if part == currentfile[i] then 187 return score + 50 188 end 189 return score 190 end) 191end 192 193--- uses the regular files picker as a base 194--- - boosts items in the bufferlist 195--- - proportionally boosts items that match closely to the current file in proximity within the filesystem 196builtins.smart = function() 197 local currentfile = vim.api.nvim_buf_get_name(0) 198 currentfile = vim.fs.abspath(currentfile) 199 200 local lst = find_files() 201 202 local pwd = vim.fn.getcwd() 203 local recentlst = vim 204 .iter(find_buffers()) 205 :map(function(buf) 206 local v = vim.api.nvim_buf_get_name(buf) 207 return vim.fs.relpath(pwd, v) or v 208 end) 209 :totable() 210 211 return artio.pick({ 212 prompt = "smart", 213 items = vim.tbl_keys(vim.iter({ lst, recentlst }):fold({}, function(items, l) 214 for i = 1, #l do 215 items[l[i]] = true 216 end 217 return items 218 end)), 219 fn = artio.mergesorters("base", artio.sorter, function(l, _) 220 return vim 221 .iter(l) 222 :map(function(v) 223 if not vim.tbl_contains(recentlst, v.text) then 224 return 225 end 226 return { v.id, {}, 100 } 227 end) 228 :totable() 229 end, function(l, _) 230 return vim 231 .iter(l) 232 :map(function(v) 233 return { v.id, {}, matchproximity(currentfile, v.text) } 234 end) 235 :totable() 236 end), 237 on_close = function(text, _) 238 vim.schedule(function() 239 vim.cmd.edit(text) 240 end) 241 end, 242 get_icon = config.get().opts.use_icons and function(item) 243 return require("mini.icons").get("file", item.v) 244 end or nil, 245 preview_item = function(item) 246 return vim.fn.bufadd(item) 247 end, 248 }) 249end 250 251builtins.colorschemes = function() 252 local files = vim.api.nvim_get_runtime_file("colors/*.{vim,lua}", true) 253 local lst = vim.tbl_map(function(f) 254 return vim.fs.basename(f):gsub("%.[^.]+$", "") 255 end, files) 256 257 return artio.generic(lst, { 258 prompt = "colorschemes", 259 on_close = function(text, _) 260 vim.schedule(function() 261 vim.cmd.colorscheme(text) 262 end) 263 end, 264 }) 265end 266 267builtins.highlights = function() 268 local hlout = vim.split(vim.api.nvim_exec2([[ highlight ]], { output = true }).output, "\n", { trimempty = true }) 269 270 local maxw = 0 271 272 local hls = vim 273 .iter(hlout) 274 :map(function(hl) 275 local sp = string.find(hl, "%s", 1) 276 maxw = sp > maxw and sp or maxw 277 return { hl:sub(1, sp - 1), hl } 278 end) 279 :fold({}, function(t, hl) 280 local pad = math.max(0, math.min(20, maxw) - #hl[1] + 1) 281 t[hl[1]] = string.gsub(hl[2], "%s+", (" "):rep(pad), 1) 282 return t 283 end) 284 285 return artio.generic(vim.tbl_keys(hls), { 286 prompt = "highlights", 287 on_close = function(line, _) 288 vim.schedule(function() 289 vim.print(line) 290 end) 291 end, 292 format_item = function(hlname) 293 return hls[hlname] 294 end, 295 hl_item = function(hlname) 296 return { 297 { { 0, #hlname.v }, hlname.v }, 298 } 299 end, 300 }) 301end 302 303return builtins