馃惢 minimal ui2 fuzzy finder for Neovim codeberg.org/comfysage/artio.nvim
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 750 lines 20 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") 11local utils = lzrq("artio.utils") 12 13local function extend(t1, ...) 14 return vim.tbl_deep_extend("force", t1, ...) 15end 16 17local builtins = {} 18 19builtins.builtins = function(props) 20 props = props or {} 21 22 return artio.generic( 23 vim.tbl_keys(builtins), 24 extend({ 25 prompt = "builtins", 26 on_close = function(fname, _) 27 if not builtins[fname] then 28 return 29 end 30 31 artio.schedule(builtins[fname]) 32 end, 33 }, props) 34 ) 35end 36 37---@class artio.picker.generic.fs.Props : artio.Picker.config 38---@field base_dir? string 39 40local findprg = vim.fn.executable("fd") == 1 and "fd -H -p -a -t f --color=never --" 41 or "find . -type f -iregex '.*$*.*'" 42 43---@class artio.picker.files.Props : artio.picker.generic.fs.Props 44---@field findprg? string 45 46---@param props? artio.picker.files.Props 47builtins.files = function(props) 48 props = props or {} 49 props.findprg = props.findprg or findprg 50 51 local base_dir = props.base_dir or vim.fn.getcwd(0) 52 local lst = utils.make_cmd(props.findprg, { 53 cwd = base_dir, 54 })() 55 56 return artio.generic( 57 lst, 58 extend({ 59 prompt = "files", 60 on_close = function(text, _) 61 vim.schedule(function() 62 vim.cmd.edit(text) 63 end) 64 end, 65 format_item = function(item) 66 return vim.fs.relpath(base_dir, item) or item 67 end, 68 get_icon = config.get().opts.use_icons and function(item) 69 return require("mini.icons").get("file", item.v) 70 end or nil, 71 preview_item = function(item) 72 return { buf = vim.fn.bufadd(item) } 73 end, 74 actions = extend( 75 {}, 76 utils.make_setqflistactions(function(item) 77 return { filename = item.v } 78 end), 79 utils.make_fileactions(function(item) 80 return vim.fn.bufnr(item.v, true) 81 end) 82 ), 83 }, props) 84 ) 85end 86 87---@class artio.picker.grep.Props : artio.picker.generic.fs.Props 88---@field grepprg? string 89 90---@param props? artio.picker.grep.Props 91builtins.grep = function(props) 92 props = props or {} 93 props.grepprg = props.grepprg or vim.o.grepprg 94 95 local base_dir = props.base_dir or vim.fn.getcwd(0) 96 local ui2 = require("vim._core.ui2") 97 local grepcmd = utils.make_cmd(props.grepprg, { 98 cwd = base_dir, 99 }) 100 101 return artio.pick(extend({ 102 items = {}, 103 prompt = "grep", 104 get_items = function(input) 105 if input == "" then 106 return {} 107 end 108 109 local lines = grepcmd(input) 110 111 vim.fn.setloclist(ui2.wins.cmd, {}, " ", { 112 title = "grep[" .. input .. "]", 113 lines = lines, 114 efm = vim.o.grepformat, 115 nr = "$", 116 }) 117 118 return vim 119 .iter(ipairs(vim.fn.getloclist(ui2.wins.cmd))) 120 :map(function(i, locitem) 121 local name = vim.fs.abspath(vim.fn.bufname(locitem.bufnr)) 122 return { 123 id = i, 124 v = { name, locitem.lnum, locitem.col }, 125 text = ("%s:%d:%d:%s"):format(vim.fs.relpath(base_dir, name), locitem.lnum, locitem.col, locitem.text), 126 } 127 end) 128 :totable() 129 end, 130 fn = artio.sorter, 131 on_close = function(item, _) 132 vim.schedule(function() 133 vim.cmd.edit(item[1]) 134 vim.api.nvim_win_set_cursor(0, { item[2], item[3] }) 135 end) 136 end, 137 preview_item = function(item) 138 return { buf = vim.fn.bufadd(item[1]), pos = { item[2], 0 } } 139 end, 140 get_icon = config.get().opts.use_icons and function(item) 141 return require("mini.icons").get("file", item.v[1]) 142 end or nil, 143 hl_item = utils.hl_qfitem, 144 actions = extend( 145 {}, 146 utils.make_setqflistactions(function(item) 147 return { filename = item.v[1], lnum = item.v[2], col = item.v[3], text = item.text } 148 end) 149 ), 150 }, props)) 151end 152 153local function find_oldfiles() 154 return vim 155 .iter(vim.v.oldfiles) 156 :filter(function(v) 157 return vim.uv.fs_stat(v) --[[@as boolean]] 158 end) 159 :totable() 160end 161 162builtins.oldfiles = function(props) 163 props = props or {} 164 local lst = find_oldfiles() 165 166 return artio.generic( 167 lst, 168 extend({ 169 prompt = "oldfiles", 170 on_close = function(text, _) 171 vim.schedule(function() 172 vim.cmd.edit(text) 173 end) 174 end, 175 get_icon = config.get().opts.use_icons and function(item) 176 return require("mini.icons").get("file", item.v) 177 end or nil, 178 preview_item = function(item) 179 return { buf = vim.fn.bufadd(item) } 180 end, 181 actions = extend( 182 {}, 183 utils.make_setqflistactions(function(item) 184 return { filename = item.v } 185 end) 186 ), 187 }, props) 188 ) 189end 190 191builtins.buffergrep = function(props) 192 props = props or {} 193 local win = vim.api.nvim_get_current_win() 194 local buf = vim.api.nvim_win_get_buf(win) 195 local n = vim.api.nvim_buf_line_count(buf) 196 local lst = {} ---@type integer[] 197 for i = 1, n do 198 lst[#lst + 1] = i 199 end 200 201 local pad = #tostring(lst[#lst]) 202 203 return artio.generic( 204 lst, 205 extend({ 206 prompt = "buffergrep", 207 on_close = function(row, _) 208 vim.schedule(function() 209 vim.api.nvim_win_set_cursor(win, { row, 0 }) 210 end) 211 end, 212 format_item = function(row) 213 return vim.api.nvim_buf_get_lines(buf, row - 1, row, true)[1] 214 end, 215 preview_item = function(row) 216 return { buf = buf, pos = { row, 0 } } 217 end, 218 get_icon = function(row) 219 local v = tostring(row.v) 220 return ("%s%s"):format((" "):rep(pad - #v), v) 221 end, 222 actions = extend( 223 {}, 224 utils.make_setqflistactions(function(item) 225 return { filename = vim.api.nvim_buf_get_name(buf), lnum = item.v } 226 end) 227 ), 228 }, props) 229 ) 230end 231 232local function find_helptags() 233 local buf = vim.api.nvim_create_buf(false, true) 234 vim.bo[buf].buftype = "help" 235 local tags = vim.api.nvim_buf_call(buf, function() 236 return vim.fn.taglist(".*") 237 end) 238 vim.api.nvim_buf_delete(buf, { force = true }) 239 return vim.tbl_map(function(t) 240 return t.name 241 end, tags) 242end 243 244builtins.helptags = function(props) 245 props = props or {} 246 local lst = find_helptags() 247 248 return artio.generic( 249 lst, 250 extend({ 251 prompt = "helptags", 252 on_close = function(text, _) 253 vim.schedule(function() 254 vim.cmd.help(text) 255 end) 256 end, 257 preview_item = function(tag) 258 local buf = vim.api.nvim_create_buf(false, true) 259 260 vim.bo[buf].bufhidden = "wipe" 261 vim.bo[buf].buftype = "help" 262 263 vim._with({ buf = buf }, function() 264 vim.cmd.help(tag) 265 end) 266 267 return { buf = buf } 268 end, 269 }, props) 270 ) 271end 272 273local function find_buffers() 274 return vim 275 .iter(vim.api.nvim_list_bufs()) 276 :filter(function(bufnr) 277 return vim.api.nvim_buf_is_valid(bufnr) and vim.bo[bufnr].buflisted 278 end) 279 :totable() 280end 281 282builtins.buffers = function(props) 283 props = props or {} 284 local lst = find_buffers() 285 286 return artio.generic( 287 lst, 288 vim.tbl_extend("force", { 289 prompt = "buffers", 290 format_item = function(bufnr) 291 return vim.api.nvim_buf_get_name(bufnr) 292 end, 293 on_close = function(bufnr, _) 294 vim.schedule(function() 295 vim.cmd.buffer(bufnr) 296 end) 297 end, 298 get_icon = config.get().opts.use_icons and function(item) 299 return require("mini.icons").get("file", vim.api.nvim_buf_get_name(item.v)) 300 end or nil, 301 preview_item = function(item) 302 return { buf = item } 303 end, 304 }, props) 305 ) 306end 307 308---@param currentfile string 309---@param item string 310---@return integer score 311local function matchproximity(currentfile, item) 312 currentfile = vim.fs.abspath(currentfile) 313 local parts = vim.split(currentfile, "/", { trimempty = true }) 314 item = vim.fs.abspath(item) 315 316 return vim.iter(ipairs(vim.split(item, "/", { trimempty = true }))):fold(0, function(score, i, part) 317 if part == parts[i] then 318 return score + 50 319 end 320 return score 321 end) 322end 323 324--- uses the regular files picker as a base 325--- - boosts items in the bufferlist 326--- - proportionally boosts items that match closely to the current file in proximity within the filesystem 327builtins.smart = function(props) 328 props = props or {} 329 local currentbuf = vim.fn.bufnr("%") 330 local currentfile = vim.api.nvim_buf_get_name(currentbuf) 331 currentfile = vim.fs.abspath(currentfile) 332 local alternatebuf = vim.fn.bufnr("#") 333 local lbufnr = vim.fn.bufnr("$") 334 local bufnr_len = #(string.format("%d", lbufnr)) 335 336 props.findprg = props.findprg or findprg 337 local base_dir = vim.fn.getcwd(0) 338 local lst = utils.make_cmd(props.findprg, { 339 cwd = base_dir, 340 })() 341 342 local recentlst = vim 343 .iter(find_buffers()) 344 :map(function(buf) 345 local v = vim.api.nvim_buf_get_name(buf) 346 return { path = vim.fs.abspath(v), buf = buf } 347 end) 348 :totable() 349 350 local items = vim.list.unique( 351 vim 352 .iter({ recentlst, lst }) 353 :flatten(1) 354 :map(function(x) 355 if type(x) == "string" then 356 x = { path = x } 357 end 358 return x 359 end) 360 :map(function(x) 361 if x.buf and x.buf == currentbuf then 362 x.current = true 363 elseif x.buf and x.buf == alternatebuf then 364 x.alt = true 365 end 366 return x 367 end) 368 :totable(), 369 function(x) 370 return x.path 371 end 372 ) 373 374 return artio.pick(extend({ 375 prompt = "smart", 376 items = items, 377 fn = artio.mergesorters( 378 "base", 379 -- use default sorter but only display buffer files if input is empty 380 function(l, input) 381 if #input == 0 then 382 return vim 383 .iter(l) 384 :map(function(item) 385 if not item.v.buf then 386 return 387 end 388 return { item.id, {}, 0 } 389 end) 390 :totable() 391 end 392 393 return artio.sorter(l, input) 394 end, 395 -- boost files that are open as buffers 396 function(l, _) 397 return vim 398 .iter(l) 399 :map(function(item) 400 if not item.v.buf then 401 return 402 end 403 return { item.id, {}, 100 } 404 end) 405 :totable() 406 end, 407 -- boost files that are close in proximity to the current file 408 function(l, _) 409 return vim 410 .iter(l) 411 :map(function(item) 412 return { item.id, {}, matchproximity(currentfile, item.v.path) } 413 end) 414 :totable() 415 end 416 ), 417 on_close = function(v, _) 418 vim.schedule(function() 419 vim.cmd.edit(v.path) 420 end) 421 end, 422 format_item = function(v) 423 local path = vim.fs.relpath(base_dir, v.path) or v.path 424 local ind = v.current and "%" or v.alt and "#" or " " 425 return v.buf and string.format("%s (%0" .. bufnr_len .. "d) %s", ind, v.buf, path) 426 or string.format("%s " .. string.rep(" ", bufnr_len + 2) .. " %s", ind, path) 427 end, 428 hl_item = function(_) 429 return { 430 { { 0, 2 }, "Special" }, 431 { { 2, 3 }, "Delimiter" }, 432 { { 3, 3 + bufnr_len }, "Number" }, 433 { { 3 + bufnr_len, 3 + bufnr_len + 1 }, "NonText" }, 434 } 435 end, 436 get_icon = config.get().opts.use_icons and function(item) 437 return require("mini.icons").get("file", item.v.path) 438 end or nil, 439 preview_item = function(v) 440 return { buf = vim.fn.bufadd(v.path) } 441 end, 442 actions = extend( 443 {}, 444 utils.make_setqflistactions(function(item) 445 return { filename = item.v.path } 446 end) 447 ), 448 }, props)) 449end 450 451builtins.colorschemes = function(props) 452 props = props or {} 453 local files = vim.api.nvim_get_runtime_file("colors/*.{vim,lua}", true) 454 local lst = vim.tbl_map(function(f) 455 return vim.fs.basename(f):gsub("%.[^.]+$", "") 456 end, files) 457 458 local current = vim.g.colors_name 459 local bg = vim.o.background 460 461 return artio.generic( 462 lst, 463 extend({ 464 prompt = "colorschemes", 465 on_close = function(text, _) 466 vim.schedule(function() 467 vim.cmd.colorscheme(text) 468 end) 469 end, 470 on_quit = function() 471 -- reset colorscheme 472 vim.schedule(function() 473 if vim.g.colors_name ~= current then 474 vim.cmd.colorscheme(current) 475 end 476 end) 477 end, 478 preview_item = function(item) 479 vim.cmd.colorscheme(item) 480 vim.o.background = bg 481 482 return {} 483 end, 484 }, props) 485 ) 486end 487 488builtins.highlights = function(props) 489 props = props or {} 490 local hlout = vim.split(vim.api.nvim_exec2([[ highlight ]], { output = true }).output, "\n", { trimempty = true }) 491 492 local maxw = 0 493 494 local hls = vim 495 .iter(hlout) 496 :map(function(hl) 497 local sp = string.find(hl, "%s", 1) 498 maxw = sp > maxw and sp or maxw 499 return { hl:sub(1, sp - 1), hl } 500 end) 501 :fold({}, function(t, hl) 502 local pad = math.max(1, math.min(20, maxw) - #hl[1] + 1) 503 t[hl[1]] = string.gsub(hl[2], "%s+", (" "):rep(pad), 1) 504 return t 505 end) 506 507 return artio.generic( 508 vim.tbl_keys(hls), 509 extend({ 510 prompt = "highlights", 511 on_close = function(hlname, _) 512 vim.schedule(function() 513 vim.cmd(("verbose hi %s"):format(hlname)) 514 end) 515 end, 516 format_item = function(hlname) 517 return hls[hlname] 518 end, 519 hl_item = function(hlname) 520 local x_start, x_end = string.find(hlname.text, "%sxxx") 521 522 return { 523 { { 0, #hlname.v }, hlname.v }, 524 { { x_start, x_end }, hlname.v }, 525 } 526 end, 527 }, props) 528 ) 529end 530 531---@private 532---@param severity vim.diagnostic.Severity 533---@return string 534local function get_severity_hl(severity) 535 if severity == vim.diagnostic.severity.ERROR then 536 return "DiagnosticError" 537 elseif severity == vim.diagnostic.severity.WARN then 538 return "DiagnosticWarn" 539 elseif severity == vim.diagnostic.severity.INFO then 540 return "DiagnosticInfo" 541 elseif severity == vim.diagnostic.severity.HINT then 542 return "DiagnosticHint" 543 end 544 return "" 545end 546 547---@class artio.picker.diagnostics.Props : artio.Picker.config 548---@field buf? integer defaults to workspace 549 550---@param props? artio.picker.diagnostics.Props 551builtins.diagnostics = function(props) 552 props = props or {} 553 local lst = vim.diagnostic.get(props.buf) 554 555 return artio.generic( 556 lst, 557 extend({ 558 prompt = "diagnostics", 559 format_item = function(item) 560 local text = item.message 561 if item.code then 562 text = ("%s [%s]"):format(text, item.code) 563 end 564 return ("%d:%d :: %s"):format(item.end_lnum, item.end_col, text) 565 end, 566 on_close = function(item, _) 567 vim.schedule(function() 568 local win = vim.fn.bufwinid(item.bufnr) 569 if win < 0 then 570 vim.api.nvim_win_set_buf(0, item.bufnr) 571 win = 0 572 end 573 vim.api.nvim_set_current_win(win) 574 vim.api.nvim_win_set_cursor(win, { item.end_lnum + 1, item.end_col }) 575 end) 576 end, 577 hl_item = function(item) 578 return { 579 { { 0, #item.text }, get_severity_hl(item.v.severity) }, 580 } 581 end, 582 get_icon = function(item) 583 if item.v.severity == vim.diagnostic.severity.ERROR then 584 return "E", get_severity_hl(item.v.severity) 585 elseif item.v.severity == vim.diagnostic.severity.WARN then 586 return "W", get_severity_hl(item.v.severity) 587 elseif item.v.severity == vim.diagnostic.severity.INFO then 588 return "I", get_severity_hl(item.v.severity) 589 elseif item.v.severity == vim.diagnostic.severity.HINT then 590 return "H", get_severity_hl(item.v.severity) 591 end 592 return " " 593 end, 594 }, props) 595 ) 596end 597 598---@param props? artio.picker.diagnostics.Props 599builtins.diagnostics_buffer = function(props) 600 props = props or {} 601 props.buf = props.buf or vim.api.nvim_get_current_buf() 602 return builtins.diagnostics(props) 603end 604 605---@class artio.picker.keymaps.Props : artio.Picker.config 606---@field modes? string[] defaults to all 607 608---@param props? artio.picker.keymaps.Props 609builtins.keymaps = function(props) 610 props = props or {} 611 props.modes = props.modes or { "n", "i", "c", "v", "x", "s", "o", "t", "l" } 612 613 ---@type vim.api.keyset.get_keymap[] 614 local lst = vim.iter(props.modes):fold({}, function(keymaps, mode) 615 vim.iter(vim.api.nvim_get_keymap(mode)):each(function(km) 616 keymaps[#keymaps + 1] = km 617 end) 618 return keymaps 619 end) 620 621 return artio.generic( 622 lst, 623 extend({ 624 prompt = "keymaps", 625 format_item = function(km) 626 return ("%s %s %s | %s"):format(km.mode, km.lhs, km.rhs, km.desc) 627 end, 628 ---@param km vim.api.keyset.get_keymap 629 on_close = function(km, _) 630 vim.schedule(function() 631 local out = vim.api.nvim_cmd({ 632 cmd = ("%smap"):format(km.mode), 633 args = { km.lhs }, 634 }, { 635 output = true, 636 }) 637 vim.print(out) 638 end) 639 end, 640 }, props) 641 ) 642end 643 644builtins.commands = function(props) 645 props = props or {} 646 local lst = vim.api.nvim_get_commands({}) 647 648 return artio.generic( 649 vim.tbl_values(lst), 650 extend({ 651 prompt = "commands", 652 ---@param item vim.api.keyset.command_info 653 format_item = function(item) 654 return item.name 655 end, 656 ---@param cmd vim.api.keyset.command_info 657 on_close = function(cmd, _) 658 local nargs = vim.F.npcall(tonumber, cmd.nargs) 659 local fmt = (nargs and nargs > 0) and ":%s " or ":%s" 660 661 artio.schedule(function() 662 vim.api.nvim_feedkeys(string.format(fmt, cmd.name), "n", false) 663 end) 664 end, 665 hl_item = function(item) 666 return { 667 { { 0, #item.v.name }, "@function.macro.vim" }, 668 } 669 end, 670 }, props) 671 ) 672end 673 674builtins.quickfix = function(props) 675 props = props or {} 676 677 local qfid = vim.fn.getqflist({ id = 0 }).id 678 local qflist = vim.fn.getqflist({ id = qfid, items = 0 }).items 679 680 return artio.generic( 681 vim 682 .iter(ipairs(qflist)) 683 :map(function(_, item) 684 item.name = vim.fn.bufname(item.bufnr) 685 return item 686 end) 687 :totable(), 688 extend({ 689 prompt = "quickfix", 690 on_close = function(_, idx) 691 artio.schedule(function() 692 vim.cmd([[copen]]) 693 local win = vim.fn.getqflist({ id = qfid, winid = 0 }).winid 694 vim.api.nvim_win_set_cursor(win, { idx, 0 }) 695 end) 696 end, 697 format_item = function(item) 698 return string.format("%s:%d:%d:%s", item.name, item.lnum, item.col, item.text) 699 end, 700 preview_item = function(item) 701 return { buf = item.bufnr, pos = { item.lnum, item.col }, pos_end = { item.end_lnum, item.end_col } } 702 end, 703 get_icon = config.get().opts.use_icons and function(item) 704 return require("mini.icons").get("file", item.v.name) 705 end or nil, 706 hl_item = utils.hl_qfitem, 707 actions = extend( 708 {}, 709 utils.make_setqflistactions(function(item) 710 return item.v 711 end) 712 ), 713 }, props) 714 ) 715end 716 717builtins.document_symbols = function(props) 718 props = props or {} 719 720 local buf = vim.api.nvim_get_current_buf() 721 local win = vim.api.nvim_get_current_win() 722 723 vim.lsp.buf.document_symbol({ 724 on_list = function(what) 725 local lst = what.items 726 727 return artio.select(lst, { 728 prompt = "document_symbols", 729 format_item = function(v) 730 return v.text 731 end, 732 preview_item = function(v) 733 return { 734 buf = buf, 735 pos = { v.lnum, v.col }, 736 pos_end = { v.end_lnum, v.end_col }, 737 } 738 end, 739 }, function(v, _) 740 if not v then 741 return 742 end 743 744 vim.api.nvim_win_set_cursor(win, { v.lnum, v.col }) 745 end, props) 746 end, 747 }) 748end 749 750return builtins