🐻 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.

refactor: use plug keymaps for actions

robin ed1e03fc 30d45b45

+164 -141
-58
lua/artio/actions.lua
··· 1 - ---@class artio.Actions.proto 2 - ---@field actions table<string, function> 3 - 4 - ---@class artio.Actions : artio.Actions.proto 5 - ---@field picker artio.Picker 6 - ---@field co thread 7 - ---@field key_ns integer 8 - local Actions = {} 9 - Actions.__index = Actions 10 - 11 - ---@param props artio.Actions.proto 12 - function Actions:new(props) 13 - return setmetatable( 14 - vim.tbl_extend("force", { 15 - actions = {}, 16 - }, props), 17 - Actions 18 - ) 19 - end 20 - 21 - function Actions:init(picker) 22 - self.picker = picker 23 - 24 - local co, ismain = coroutine.running() 25 - assert(not ismain, "must be called from a coroutine") 26 - self.co = co 27 - 28 - self.key_ns = vim.on_key(function(key, typed) 29 - return self:on_key(key, typed) 30 - end) 31 - 32 - local result = coroutine.yield() 33 - 34 - vim.on_key(nil, self.key_ns) 35 - 36 - return result 37 - end 38 - 39 - function Actions:on_key(_, typed) 40 - if self.picker.view.closed then 41 - coroutine.resume(self.co) 42 - return 43 - end 44 - 45 - typed = string.lower(vim.fn.keytrans(typed)) 46 - 47 - local _, actionname = vim.iter(pairs(self.picker.mappings)):find(function(key, _) 48 - return key == typed 49 - end) 50 - 51 - local action = self.actions[actionname] 52 - if action and vim.is_callable(action) then 53 - action(self.picker, self.co) 54 - return "" 55 - end 56 - end 57 - 58 - return Actions
+16 -23
lua/artio/builtins.lua
··· 43 43 {}, 44 44 utils.make_setqflistactions(function(item) 45 45 return { filename = item.v } 46 - end), 47 - props.actions or {} 46 + end) 48 47 ), 49 48 }, props) 50 49 ) ··· 101 100 get_icon = config.get().opts.use_icons and function(item) 102 101 return require("mini.icons").get("file", item.v[1]) 103 102 end or nil, 104 - actions = { 105 - setqflist = utils.make_setqflist(function(item) 103 + actions = extend( 104 + {}, 105 + utils.make_setqflistactions(function(item) 106 106 return { filename = item.v[1], lnum = item.v[2], col = item.v[3], text = item.text } 107 - end), 108 - }, 109 - mappings = { 110 - ["<c-q>"] = "setqflist", 111 - }, 107 + end) 108 + ), 112 109 }, props)) 113 110 end 114 111 ··· 140 137 preview_item = function(item) 141 138 return vim.fn.bufadd(item) 142 139 end, 143 - actions = { 144 - setqflist = utils.make_setqflist(function(item) 140 + actions = extend( 141 + {}, 142 + utils.make_setqflistactions(function(item) 145 143 return { filename = item.v } 146 - end), 147 - }, 148 - mappings = { 149 - ["<c-q>"] = "setqflist", 150 - }, 144 + end) 145 + ), 151 146 }, props) 152 147 ) 153 148 end ··· 323 318 preview_item = function(item) 324 319 return vim.fn.bufadd(item) 325 320 end, 326 - actions = { 327 - setqflist = utils.make_setqflist(function(item) 321 + actions = extend( 322 + {}, 323 + utils.make_setqflistactions(function(item) 328 324 return { filename = item.v } 329 - end), 330 - }, 331 - mappings = { 332 - ["<c-q>"] = "setqflist", 333 - }, 325 + end) 326 + ), 334 327 }, props)) 335 328 end 336 329
+1 -1
lua/artio/config.lua
··· 56 56 ["<tab>"] = "mark", 57 57 ["<c-l>"] = "togglepreview", 58 58 ["<c-q>"] = "setqflist", 59 - ["<c-w>"] = "setqflistmark", 59 + ["<m-q>"] = "setqflistmark", 60 60 }, 61 61 } 62 62
+26
lua/artio/init.lua
··· 149 149 return Picker:new(...):open() 150 150 end 151 151 152 + ---@param fn artio.Picker.action 153 + ---@param scheduled_fn? artio.Picker.action 154 + artio.wrap = function(fn, scheduled_fn) 155 + return function() 156 + local Picker = require("artio.picker") 157 + local current = Picker.active_picker 158 + if not current or current.closed then 159 + return 160 + end 161 + 162 + -- whether to accept key inputs 163 + if coroutine.status(current.co) ~= "suspended" then 164 + return 165 + end 166 + 167 + pcall(fn, current) 168 + 169 + if scheduled_fn == nil then 170 + return 171 + end 172 + vim.schedule(function() 173 + pcall(scheduled_fn, current) 174 + end) 175 + end 176 + end 177 + 152 178 return artio
+47 -47
lua/artio/picker.lua
··· 1 - local Actions = require("artio.actions") 2 1 local View = require("artio.view") 3 2 4 3 ---@alias artio.Picker.item { id: integer, v: any, text: string, icon?: string, icon_hl?: string, hls?: artio.Picker.hl[] } 5 4 ---@alias artio.Picker.match [integer, integer[], integer] [item, pos[], score] 6 5 ---@alias artio.Picker.sorter fun(lst: artio.Picker.item[], input: string): artio.Picker.match[] 7 6 ---@alias artio.Picker.hl [[integer, integer], string] 8 - ---@alias artio.Picker.action fun(self: artio.Picker, co: thread) 7 + ---@alias artio.Picker.action fun(self: artio.Picker) 9 8 10 9 ---@class artio.Picker.config 11 10 ---@field items artio.Picker.item[]|string[] ··· 16 15 ---@field preview_item? fun(item: any): integer, fun(win: integer) 17 16 ---@field get_icon? fun(item: artio.Picker.item): string, string 18 17 ---@field hl_item? fun(item: artio.Picker.item): artio.Picker.hl[] 19 - ---@field actions? table<string, artio.Picker.action> 20 18 ---@field prompt? string 21 19 ---@field defaulttext? string 22 20 ---@field prompttext? string 23 21 ---@field opts? artio.config.opts 24 22 ---@field win? artio.config.win 23 + ---@field actions? table<string, artio.Picker.action> 25 24 ---@field mappings? table<string, 'up'|'down'|'accept'|'cancel'|'togglepreview'|string> 26 25 27 26 ---@class artio.Picker : artio.Picker.config 27 + ---@field co thread|nil 28 28 ---@field idx integer 1-indexed 29 29 ---@field matches artio.Picker.match[] 30 30 ---@field marked table<integer, true|nil> 31 - ---@field actions? artio.Actions 32 31 local Picker = {} 33 32 Picker.__index = Picker 34 33 Picker.active_picker = nil 35 34 36 - local action_enum = { 37 - accept = 0, 38 - cancel = 1, 39 - } 40 - 41 - ---@type table<string, fun(self: artio.Picker, co: thread)> 42 - local default_actions = { 43 - down = function(self, _) 44 - self.idx = self.idx + 1 45 - self.view:showmatches() -- adjust for scrolling 46 - self.view:hlselect() 47 - end, 48 - up = function(self, _) 49 - self.idx = self.idx - 1 50 - self.view:showmatches() -- adjust for scrolling 51 - self.view:hlselect() 52 - end, 53 - accept = function(_, co) 54 - coroutine.resume(co, action_enum.accept) 55 - end, 56 - cancel = function(_, co) 57 - coroutine.resume(co, action_enum.cancel) 58 - end, 59 - mark = function(self, _) 60 - local match = self.matches[self.idx] 61 - if not match then 62 - return 63 - end 64 - local idx = match[1] 65 - self:mark(idx, not self.marked[idx]) 66 - self.view:showmatches() -- redraw marker 67 - self.view:hlselect() 68 - end, 69 - togglepreview = function(self, _) 70 - self.view:togglepreview() 71 - end, 72 - } 73 - 74 35 ---@param props artio.Picker.config 75 36 function Picker:new(props) 76 37 vim.validate("Picker.items", props.items, "table") ··· 92 53 93 54 Picker.getitems(t, "") 94 55 95 - t.actions = Actions:new({ 96 - actions = vim.tbl_extend("force", default_actions, t.actions or {}), 97 - }) 98 - 99 56 return setmetatable(t, Picker) 100 57 end 58 + 59 + local action_enum = { 60 + accept = 0, 61 + cancel = 1, 62 + } 101 63 102 64 function Picker:open() 103 65 if Picker.active_picker then ··· 110 72 coroutine.wrap(function() 111 73 self.view:open() 112 74 113 - local result = self.actions:init(self) 75 + self:initkeymaps() 76 + 77 + local co, ismain = coroutine.running() 78 + assert(not ismain, "must be called from a coroutine") 79 + self.co = co 80 + 81 + local result = coroutine.yield() 114 82 115 83 self:close() 116 84 ··· 139 107 self.view:close() 140 108 end 141 109 110 + self:delkeymaps() 111 + 142 112 self.closed = true 113 + end 114 + 115 + function Picker:initkeymaps() 116 + local ext = require("vim._extui.shared") 117 + 118 + ---@type vim.keymap.set.Opts 119 + local opts = { buffer = ext.bufs.cmd } 120 + 121 + if self.actions then 122 + vim.iter(pairs(self.actions)):each(function(k, v) 123 + vim.keymap.set("i", ("<Plug>(artio-action-%s)"):format(k), v, opts) 124 + end) 125 + end 126 + if self.mappings then 127 + vim.iter(pairs(self.mappings)):each(function(k, v) 128 + vim.keymap.set("i", k, ("<Plug>(artio-action-%s)"):format(v), opts) 129 + end) 130 + end 131 + end 132 + 133 + function Picker:delkeymaps() 134 + local ext = require("vim._extui.shared") 135 + 136 + local keymaps = vim.api.nvim_buf_get_keymap(ext.bufs.cmd, "i") 137 + 138 + vim.iter(ipairs(keymaps)):each(function(_, v) 139 + if v.lhs:match("^<Plug>(artio-action-") or (v.rhs and v.rhs:match("^<Plug>(artio-action-")) then 140 + vim.api.nvim_buf_del_keymap(ext.bufs.cmd, "i", v.lhs) 141 + end 142 + end) 143 143 end 144 144 145 145 function Picker:fix()
+10 -12
lua/artio/utils.lua
··· 33 33 ---@param fn fun(item: artio.Picker.item): vim.quickfix.entry 34 34 ---@return artio.Picker.action 35 35 function utils.make_setqflist(fn) 36 - return function(self, co) 36 + return require("artio").wrap(function(self) 37 37 vim.fn.setqflist(vim 38 38 .iter(ipairs(self.matches)) 39 39 :map(function(_, match) ··· 42 42 return qfitem 43 43 end) 44 44 :totable()) 45 - vim.schedule(function() 46 - vim.cmd.copen() 47 - end) 48 - coroutine.resume(co, 1) 49 - end 45 + coroutine.resume(self.co, 1) 46 + end, function(_) 47 + vim.cmd.copen() 48 + end) 50 49 end 51 50 52 51 ---@param fn fun(item: artio.Picker.item): vim.quickfix.entry 53 52 ---@return artio.Picker.action 54 53 function utils.make_setqflistmark(fn) 55 - return function(self, co) 54 + return require("artio").wrap(function(self) 56 55 vim.fn.setqflist(vim 57 56 .iter(ipairs(self:getmarked())) 58 57 :map(function(_, id) ··· 61 60 return qfitem 62 61 end) 63 62 :totable()) 64 - vim.schedule(function() 65 - vim.cmd.copen() 66 - end) 67 - coroutine.resume(co, 1) 68 - end 63 + coroutine.resume(self.co, 1) 64 + end, function(_) 65 + vim.cmd.copen() 66 + end) 69 67 end 70 68 71 69 ---@param fn fun(item: artio.Picker.item): vim.quickfix.entry
+64
plugin/artio.lua
··· 29 29 end, 30 30 }) 31 31 32 + -- == pickers == 33 + 32 34 vim.keymap.set("n", "<Plug>(artio-files)", function() 33 35 return require("artio.builtins").files() 34 36 end) ··· 50 52 vim.keymap.set("n", "<Plug>(artio-smart)", function() 51 53 return require("artio.builtins").smart() 52 54 end) 55 + 56 + -- == actions == 57 + 58 + local function wrap(fn) 59 + return function() 60 + (require("artio").wrap(fn))() 61 + end 62 + end 63 + 64 + vim.keymap.set( 65 + "i", 66 + "<Plug>(artio-action-down)", 67 + wrap(function(self) 68 + self.idx = self.idx + 1 69 + self.view:showmatches() -- adjust for scrolling 70 + self.view:hlselect() 71 + end) 72 + ) 73 + vim.keymap.set( 74 + "i", 75 + "<Plug>(artio-action-up)", 76 + wrap(function(self) 77 + self.idx = self.idx - 1 78 + self.view:showmatches() -- adjust for scrolling 79 + self.view:hlselect() 80 + end) 81 + ) 82 + vim.keymap.set( 83 + "i", 84 + "<Plug>(artio-action-accept)", 85 + wrap(function(self) 86 + coroutine.resume(self.co, 0) 87 + end) 88 + ) 89 + vim.keymap.set( 90 + "i", 91 + "<Plug>(artio-action-cancel)", 92 + wrap(function(self) 93 + coroutine.resume(self.co, 1) 94 + end) 95 + ) 96 + vim.keymap.set( 97 + "i", 98 + "<Plug>(artio-action-mark)", 99 + wrap(function(self) 100 + local match = self.matches[self.idx] 101 + if not match then 102 + return 103 + end 104 + local idx = match[1] 105 + self:mark(idx, not self.marked[idx]) 106 + self.view:showmatches() -- redraw marker 107 + self.view:hlselect() 108 + end) 109 + ) 110 + vim.keymap.set( 111 + "i", 112 + "<Plug>(artio-action-togglepreview)", 113 + wrap(function(self) 114 + self.view:togglepreview() 115 + end) 116 + )