minimal extui fuzzy finder for neovim
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