├── plugin └── fzf-lua-extra.lua ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── stylua.toml ├── .gitignore ├── lua ├── fzf-lua-extra │ ├── providers │ │ ├── repl.lua │ │ ├── cmd.lua │ │ ├── ex.lua │ │ ├── grep_project_globs.lua │ │ ├── visits.lua │ │ ├── rtp.lua │ │ ├── scriptnames.lua │ │ ├── cliphist.lua │ │ ├── node_lines.lua │ │ ├── function.lua │ │ ├── plocate.lua │ │ ├── gitignore.lua │ │ ├── license.lua │ │ ├── lua_registry.lua │ │ ├── file_decor.lua │ │ ├── icons.lua │ │ ├── runtime.lua │ │ ├── lazy.lua │ │ ├── hunks.lua │ │ ├── aerial.lua │ │ ├── swiper_blines.lua │ │ ├── git_log.lua │ │ ├── ps.lua │ │ └── serverlist2.lua │ ├── compat │ │ └── async.lua │ ├── previewers.lua │ └── utils.lua └── fzf-lua-extra.lua ├── README.md ├── .emmyrc.json ├── LICENSE ├── doc └── fzf-lua-extra.txt ├── scripts └── gen_readme.lua ├── Makefile ├── test └── main_spec.lua └── CHANGELOG.md /plugin/fzf-lua-extra.lua: -------------------------------------------------------------------------------- 1 | require('fzf-lua-extra') 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /stylua.toml: -------------------------------------------------------------------------------- 1 | column_width = 100 2 | line_endings = "Unix" 3 | indent_type = "Spaces" 4 | indent_width = 2 5 | quote_style = "AutoPreferSingle" 6 | call_parentheses = "Input" 7 | collapse_simple_statement = 'Always' 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc 2 | deps 3 | # Compiled Lua sources 4 | luac.out 5 | 6 | # luarocks build files 7 | *.src.rock 8 | *.zip 9 | *.tar.gz 10 | 11 | # Object files 12 | *.o 13 | *.os 14 | *.ko 15 | *.obj 16 | *.elf 17 | 18 | # Precompiled Headers 19 | *.gch 20 | *.pch 21 | 22 | # Libraries 23 | *.lib 24 | *.a 25 | *.la 26 | *.lo 27 | *.def 28 | *.exp 29 | 30 | # Shared objects (inc. Windows DLLs) 31 | *.dll 32 | *.so 33 | *.so.* 34 | *.dylib 35 | 36 | # Executables 37 | *.exe 38 | *.out 39 | *.app 40 | *.i*86 41 | *.x86_64 42 | *.hex 43 | 44 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/repl.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Repl: fzf-lua.config.Base 2 | local __DEFAULT__ = { 3 | preview = 'true', 4 | winopts = { preview = { hidden = true } }, 5 | } 6 | 7 | return function(opts) 8 | assert(__DEFAULT__) 9 | FzfLua.fzf_live(function(s) 10 | return function(cb) 11 | local f, err = loadstring('return ' .. s[1]) 12 | if not f then return cb(err) end 13 | local ret = vim.F.pack_len(pcall(f)) 14 | if not ret[1] then return cb(ret[2]) end 15 | for i = 2, ret.n do 16 | cb(vim.inspect(ret[i])) 17 | end 18 | cb(nil) 19 | end 20 | end, opts) 21 | end 22 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/cmd.lua: -------------------------------------------------------------------------------- 1 | local c 2 | ---@class fle.config.Cmd: fzf-lua.config.Base 3 | local __DEFAULT__ = { 4 | allow = { 5 | journalctl = true, 6 | curl = true, 7 | }, 8 | fzf_opts = { 9 | ['--raw'] = true, 10 | }, 11 | preview = { 12 | fn = function(...) return c(...) end, 13 | type = 'cmd', 14 | field_index = '{q}', 15 | }, 16 | } 17 | 18 | c = function(s, opts) 19 | -- TODO: callback should have way to access cmd 20 | opts = type(opts) == 'table' and opts or __DEFAULT__ 21 | local q = s[1] 22 | local cmd = q:match('^%s*(%S+)') 23 | if not cmd or not opts.allow[cmd] then return vim.tbl_keys(opts.allow) end 24 | return q 25 | end 26 | return function(opts) 27 | assert(__DEFAULT__) 28 | FzfLua.fzf_live(c, opts) 29 | end 30 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/ex.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Ex: fzf-lua.config.Base 2 | local __DEFAULT__ = { 3 | preview = 'true', 4 | line_query = function(q) return nil, (q:match('%S+$') or '') end, 5 | winopts = { preview = { hidden = true } }, 6 | keymap = { 7 | fzf = { 8 | start = 'toggle-search', 9 | tab = 'transform:' .. FzfLua.shell.stringify_data(function(s, _, _) 10 | if not s[1] or not s[2] then return end 11 | return 'change-query:' .. (s[1]:gsub('%S+$', s[2])) 12 | end, {}, '{q} {}'), 13 | }, 14 | }, 15 | actions = { 16 | enter = { fn = function(s) vim.cmd(s[1]) end, field_index = '{q}' }, 17 | }, 18 | } 19 | 20 | return function(opts) 21 | assert(__DEFAULT__) 22 | FzfLua.fzf_live(function(s) 23 | return function(cb) 24 | vim.iter(vim.fn.getcompletion(s[1], 'cmdline')):each(cb) 25 | cb(nil) 26 | end 27 | end, opts) 28 | end 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fzf-lua-extra 2 | [![CI](https://github.com/phanen/fzf-lua-overlay/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/phanen/fzf-lua-overlay/actions/workflows/ci.yml) 3 | 4 | Extra pickers for [fzf-lua](https://github.com/ibhagwan/fzf-lua). 5 | 6 | * ~aerial~ `require("aerial").fzf_lua_picker()` (https://github.com/stevearc/aerial.nvim/issues/472) 7 | * cliphist 8 | * cmd 9 | * ex 10 | * file_decor 11 | * function 12 | * git_log 13 | * gitignore 14 | * grep_project_globs 15 | * hunks 16 | * icons 17 | * lazy 18 | * license 19 | * lua_registry 20 | * node_lines 21 | * plocate 22 | * ps 23 | * repl 24 | * rtp 25 | * runtime 26 | * scriptnames 27 | * ~serverlist2~ `FzfLua serverlist` (https://github.com/ibhagwan/fzf-lua/pull/2320) 28 | * ~swiper_blines~ `FzfLua blines profile=ivy` (https://github.com/ibhagwan/fzf-lua/issues/2261) 29 | * visits 30 | 31 | 32 | ## credit 33 | * 34 | * 35 | * 36 | -------------------------------------------------------------------------------- /.emmyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/EmmyLuaLs/emmylua-analyzer-rust/refs/heads/main/crates/emmylua_code_analysis/resources/schema.json", 3 | "runtime": { 4 | "version": "LuaJIT", 5 | "requirePattern": [ 6 | "lua/?.lua", 7 | "lua/?/init.lua" 8 | ] 9 | }, 10 | "workspace": { 11 | "library": [ 12 | "$VIMRUNTIME", 13 | "${3rd}/luv/library", 14 | "${3rd}/busted/library", 15 | "${3rd}/luassert/library", 16 | "deps/nvim-test", 17 | "deps/.data/nvim/site/pack/core/opt/fzf-lua", 18 | "deps/.data/nvim/site/pack/core/opt/aerial.nvim", 19 | "deps/.data/nvim/site/pack/core/opt/mini.nvim", 20 | "deps/.data/nvim/site/pack/core/opt/lazy.nvim", 21 | "deps/.data/nvim/site/pack/core/opt/gitsigns.nvim" 22 | ] 23 | }, 24 | "diagnostics": { 25 | "disable": [ 26 | "unnecessary-if", 27 | "unnecessary-assert", 28 | "missing-fields", 29 | "access-invisible" 30 | ], 31 | "enables": [] 32 | }, 33 | "strict": { 34 | "typeCall": true, 35 | "arrayIndex": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/grep_project_globs.lua: -------------------------------------------------------------------------------- 1 | local last_gq ---@type string? 2 | 3 | ---@class fle.config.GrepProjectGlobs: fzf-lua.config.Base 4 | local __DEFAULT__ = { 5 | cmd = 'rg --column --line-number --no-heading --color=always --smart-case', 6 | _actions = function() return require('fzf-lua-extra.utils').fix_actions() end, 7 | previewer = 'builtin', 8 | keymap = function() 9 | return { 10 | fzf = { 11 | change = 'transform:' .. (FzfLua.shell.stringify_data(function(sel) 12 | if not sel[1] then return end 13 | local sq, gq = unpack(vim.split(sel[1], '%s%-%-%s')) 14 | local gq_changed = gq ~= last_gq 15 | last_gq = gq 16 | if gq_changed then 17 | local cmd = assert(FzfLua.utils.fzf_winobj())._o.cmd 18 | return ('reload(%s "" --iglob %q)+search:%s'):format(cmd, gq, sq) 19 | end 20 | return ('+search:%s'):format(sq) 21 | end, {}, '{q}')), 22 | }, 23 | } 24 | end, 25 | } 26 | 27 | return function(opts) 28 | assert(__DEFAULT__) 29 | FzfLua.fzf_exec(('%s ""'):format(opts.cmd), opts) 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 phanium 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/visits.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Visits: fzf-lua.config.Base 2 | local __DEFAULT__ = { 3 | silent = true, 4 | previewer = 'builtin', 5 | file_icons = 1, 6 | color_icons = true, 7 | fzf_opts = { ['--no-sort'] = true }, 8 | ---@diagnostic disable-next-line: undefined-field 9 | _actions = function() return require('fzf-lua-extra.utils').fix_actions() end, 10 | filter = function(e) return (vim.uv.fs_stat(e.path) or {}).type == 'file' end, 11 | } 12 | ---@diagnostic disable-next-line: no-unknown 13 | return function(opts) 14 | assert(__DEFAULT__) 15 | local contents = function(cb) 16 | coroutine.wrap(function() 17 | local co = coroutine.running() 18 | ---@type string[] 19 | ---@diagnostic disable-next-line: no-unknown 20 | local paths = require('mini.visits').list_paths(opts.cwd or '', { filter = opts.filter }) 21 | for _, file in ipairs(paths) do 22 | cb(FzfLua.make_entry.file(file, opts), function() coroutine.resume(co) end) 23 | coroutine.yield() 24 | end 25 | cb(nil) 26 | end)() 27 | end 28 | FzfLua.fzf_exec(contents, opts) 29 | end 30 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/rtp.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Rtp: fzf-lua.config.Base 2 | local __DEFAULT__ = { 3 | previewer = { 4 | cmd = 'eza --color=always --tree --level=3 --icons=always', 5 | _ctor = require('fzf-lua.previewer').fzf.cmd, 6 | }, 7 | _fmt = { from = function(e, _) return vim.fn.expand(e) end }, 8 | actions = { 9 | ['enter'] = function(sel) require('fzf-lua-extra.utils').chdir(sel[1]) end, 10 | ['ctrl-l'] = function(sel) FzfLua.files { cwd = sel[1] } end, 11 | ['ctrl-n'] = function(sel) FzfLua.live_grep_native { cwd = sel[1] } end, 12 | }, 13 | } 14 | 15 | return function(opts) 16 | assert(__DEFAULT__) 17 | local clear = FzfLua.utils.ansi_escseq.clear 18 | local clear_pat = vim.pesc(clear) 19 | local contents = vim 20 | .iter(vim.api.nvim_list_runtime_paths()) 21 | :map(require('fzf-lua-extra.utils').format_env) 22 | :map( 23 | ---@param path string 24 | ---@return string 25 | function(path) -- hack... 26 | local cleared = (path:gsub(clear_pat, '')) 27 | return cleared and cleared .. clear or path 28 | end 29 | ) 30 | :totable() 31 | 32 | return FzfLua.fzf_exec(contents, opts) 33 | end 34 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/scriptnames.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Scriptnames: fzf-lua.config.Base 2 | local __DEFAULT__ = { 3 | previewer = 'builtin', 4 | _fmt = { from = function(e, _) return vim.fn.expand(e) end }, 5 | winopts = { preview = { hidden = 'nohidden' } }, 6 | file_icons = true, 7 | actions = { ['enter'] = require('fzf-lua.actions').file_sel_to_qf }, 8 | } 9 | 10 | return function(opts) 11 | assert(__DEFAULT__) 12 | local utils = require('fzf-lua.utils') 13 | local green = utils.ansi_codes.green 14 | local blue = utils.ansi_codes.blue 15 | local clear_pat = vim.pesc(utils.ansi_escseq.clear) 16 | local contents = vim 17 | .iter(vim.fn.getscriptinfo()) 18 | :map(function(s) return s.name end) 19 | :map(require('fzf-lua-extra.utils').format_env) 20 | :map( 21 | ---@param path string 22 | ---@return string 23 | function(path) 24 | local _, off = path:find(clear_pat) 25 | off = off or 0 26 | return path:match('%.vim$') and (path:sub(0, off) .. green(path:sub(off + 1, -1))) 27 | or path:match('%.lua$') and (path:sub(0, off) .. blue(path:sub(off + 1, -1))) 28 | or path 29 | end 30 | ) 31 | :totable() 32 | return FzfLua.fzf_exec(contents, opts) 33 | end 34 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra.lua: -------------------------------------------------------------------------------- 1 | local curdir = assert(debug.getinfo(1, 'S').source:sub(2):match('(.*/)')) 2 | 3 | local getdefault = function(f, name) 4 | local i = 1 5 | while true do 6 | local n, opts = debug.getupvalue(f, i) 7 | if not n then break end 8 | if n == name then return vim.is_callable(opts) and opts() or opts end 9 | i = i + 1 10 | end 11 | end 12 | 13 | local register = function(name, mod) 14 | require('fzf-lua')[name] = function(opts) 15 | local f = require(mod) 16 | local default = getdefault(f, '__DEFAULT__') 17 | if default then 18 | FzfLua.defaults[name] = default 19 | opts = require('fzf-lua.config').normalize_opts(opts or {}, name) 20 | end 21 | FzfLua.set_info({ cmd = name, fnc = name, mod = mod }) 22 | return f(opts) 23 | end 24 | end 25 | 26 | return vim.iter(vim.fs.dir(vim.fs.joinpath(curdir, 'fzf-lua-extra/providers'))):fold( 27 | {}, 28 | ---@param M table 29 | ---@param name string 30 | ---@return table 31 | function(M, name) 32 | ---@type string 33 | name = assert(name:match('(.*)%.lua$')) 34 | local mod = 'fzf-lua-extra.providers.' .. name 35 | register(name, mod) 36 | ---@type fun(...: table): any 37 | M[name] = require('fzf-lua')[name] 38 | return M 39 | end 40 | ) 41 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/cliphist.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Cliphist: fzf-lua.config.Base 2 | local __DEFAULT__ = { 3 | fzf_opts = { 4 | ['--no-sort'] = true, 5 | ['--with-nth'] = '2..', 6 | }, 7 | preview = { 8 | -- TODO: 9 | fn = function(sel) 10 | ---@diagnostic disable-next-line: no-unknown 11 | sel = sel[1] 12 | if not sel then return end 13 | return vim.system({ 'cliphist', 'decode', sel }):wait().stdout 14 | end, 15 | field_index = '{1}', 16 | }, 17 | -- TODO: handle binary? 18 | actions = { 19 | enter = { 20 | fn = function(sel) 21 | ---@diagnostic disable-next-line: no-unknown 22 | sel = sel[1] 23 | if not sel then return end 24 | local data = vim.fn.systemlist({ 'cliphist', 'decode', sel }) 25 | ---@type string[], string 26 | local regs, cb = {}, vim.o.clipboard 27 | if cb:match('unnamed') then regs[#regs + 1] = [[*]] end 28 | if cb:match('unnamedplus') then regs[#regs + 1] = [[+]] end 29 | if #regs == 0 then regs[#regs + 1] = [["]] end 30 | for _, reg in ipairs(regs) do 31 | vim.fn.setreg(reg, data) 32 | end 33 | end, 34 | field_index = '{1}', 35 | }, 36 | }, 37 | } 38 | 39 | return function(opts) 40 | assert(__DEFAULT__) 41 | FzfLua.fzf_exec('cliphist list', opts) 42 | end 43 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/node_lines.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.NodeLines: fzf-lua.config.Base 2 | local __DEFAULT__ = {} 3 | 4 | local api, fn = vim.api, vim.fn 5 | ---@param opts { query: string?, node: TSNode? } 6 | local function nodelines(opts) 7 | assert(__DEFAULT__) 8 | opts = opts or {} 9 | local stop = { function_definition = true } 10 | local node = opts.node or vim.treesitter.get_node() 11 | while node and not stop[node:type()] do 12 | node = node:parent() ---@type TSNode? 13 | end 14 | if not node then return end 15 | local start_line, end_line ---@type integer, integer 16 | if api.nvim_get_mode().mode:match('[vV\022]') then 17 | start_line, end_line = fn.line '.', fn.line 'v' 18 | if start_line > end_line then 19 | start_line, end_line = end_line, start_line ---@type integer, integer 20 | end 21 | else 22 | start_line, _, end_line, _ = vim.treesitter.get_node_range(node) 23 | start_line = start_line + 1 24 | end_line = end_line + 1 25 | end 26 | FzfLua.blines({ 27 | start_line = start_line, 28 | end_line = end_line, 29 | query = opts.query, 30 | actions = { 31 | ['alt-g'] = { 32 | fn = function(sel) return nodelines({ node = node:parent(), query = unpack(sel) }) end, 33 | field_index = '{q}', 34 | exec_silent = true, 35 | }, 36 | }, 37 | }) 38 | end 39 | 40 | return nodelines 41 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/function.lua: -------------------------------------------------------------------------------- 1 | local funcinfo = function(s) 2 | if not s then return end 3 | ---@type string? 4 | local name = s:match('function (.*)%(') 5 | if not name then return end 6 | local content = 7 | vim.split(vim.api.nvim_exec2('verb function ' .. name, { output = true }).output, '\n') 8 | if not content[1] or not content[2] then return end 9 | local path, lnum = content[2]:match('Last set from (.*) line (%d+)') 10 | return path and vim.fs.normalize(path) or nil, tonumber(lnum) 11 | end 12 | 13 | local format = function(e) return ('%s:%s:'):format(funcinfo(e)) end 14 | 15 | ---@class fle.config.Function: fzf-lua.config.Base 16 | local __DEFAULT__ = { 17 | _treesitter = function(line) return 'foo.vim', nil, line end, 18 | fzf_colors = { ['hl'] = '-1:reverse', ['hl+'] = '-1:reverse' }, 19 | _actions = function() return require('fzf-lua-extra.utils').fix_actions(format) end, 20 | previewer = { 21 | _ctor = function() 22 | local p = require('fzf-lua.previewer.builtin').buffer_or_file:extend() 23 | function p:parse_entry(sel) 24 | local path, lnum = funcinfo(sel) 25 | return { path = path, line = lnum } 26 | end 27 | return p 28 | end, 29 | }, 30 | } 31 | 32 | return function(opts) 33 | assert(__DEFAULT__) 34 | FzfLua.fzf_exec(vim.split(vim.api.nvim_exec2('function', { output = true }).output, '\n'), opts) 35 | end 36 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/plocate.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Plocate: fzf-lua.config.Base 2 | local __DEFAULT__ = {} 3 | 4 | ---TODO: 5 | ---@diagnostic disable-next-line: no-unknown 6 | return function(opts) 7 | assert(__DEFAULT__) 8 | opts = opts or {} 9 | local search = FzfLua.utils.input('Grep > ') 10 | if not search then return end 11 | local last_gq ---@type string? 12 | local cmd = 'plocate -r' 13 | local actions = require('fzf-lua.actions') 14 | opts = vim.tbl_deep_extend('force', opts or {}, { 15 | raw_cmd = cmd .. ' ' .. search, 16 | search = search, 17 | previewer = 'builtin', 18 | actions = { 19 | ['enter'] = actions.file_edit_or_qf, 20 | ['ctrl-s'] = actions.file_split, 21 | ['ctrl-v'] = actions.file_vsplit, 22 | ['ctrl-t'] = actions.file_tabedit, 23 | ['alt-q'] = actions.file_sel_to_qf, 24 | ['alt-Q'] = actions.file_sel_to_ll, 25 | change = { 26 | fn = function() end, 27 | exec_silent = true, 28 | postfix = 'transform:' .. FzfLua.shell.stringify_data(function(sel) 29 | local sq, gq = unpack(vim.split(unpack(sel) --[[@as string]], '%s%-%-%s')) 30 | local gq_changed = gq ~= last_gq 31 | last_gq = gq 32 | if gq_changed then return ('reload(%s %q)+search:%s'):format(cmd, gq, sq) end 33 | return ('+search:%s'):format(sq) 34 | end, {}, '{q}'), 35 | }, 36 | }, 37 | }) 38 | FzfLua.grep(opts) 39 | end 40 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/gitignore.lua: -------------------------------------------------------------------------------- 1 | local endpoint = 'gitignore/templates' 2 | 3 | ---@class fle.config.Gitignore: fzf-lua.config.Base 4 | local __DEFAULT__ = { 5 | previewer = { _ctor = function() return require('fzf-lua-extra.previewers').gitignore end }, 6 | endpoint = endpoint, 7 | json_key = 'source', 8 | filetype = 'gitignore', 9 | winopts = { preview = { hidden = true } }, 10 | _actions = function() 11 | local utils = require('fzf-lua-extra.utils') 12 | ---@type fzf-lua.config.Actions 13 | return { 14 | -- TODO: 15 | ['enter'] = function(selected) 16 | local root = vim.fs.root(0, '.git') 17 | if not root then error('Not in a git repo') end 18 | local path = root .. '/.gitignore' 19 | if vim.uv.fs_stat(path) then 20 | local confirm = vim.fn.confirm('Override?', '&Yes\n&No') 21 | if confirm ~= 1 then return end 22 | end 23 | ---@type string 24 | local filetype = assert(selected[1]) 25 | utils.arun(function() 26 | local json = utils.gh({ endpoint = vim.fs.joinpath(endpoint, filetype) }) 27 | local content = assert(json.source) 28 | utils.write_file(path, content) 29 | vim.cmd.edit(path) 30 | end) 31 | end, 32 | } 33 | end, 34 | } 35 | 36 | return function(opts) 37 | assert(__DEFAULT__) 38 | local utils = require('fzf-lua-extra.utils') 39 | local contents = function(fzf_cb) 40 | utils.arun(function() 41 | local json = utils.gh({ endpoint = opts.endpoint }) 42 | vim.iter(json):each(fzf_cb) 43 | fzf_cb() 44 | end) 45 | end 46 | return FzfLua.fzf_exec(contents, opts) 47 | end 48 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/license.lua: -------------------------------------------------------------------------------- 1 | local utils = require('fzf-lua-extra.utils') 2 | local fn, uv = vim.fn, vim.uv 3 | local endpoint = 'licenses' 4 | 5 | local license_path = function(root) 6 | local path = vim 7 | .iter({ 'LICENSE', 'license', 'License' }) 8 | :map(function(license) return vim.fs.joinpath(root, license) end) 9 | :find(uv.fs_stat) 10 | if path and fn.confirm('Override?', '&Yes\n&No') ~= 1 then return end 11 | return path or vim.fs.joinpath(root, 'LICENSE') 12 | end 13 | 14 | ---@class fle.config.License: fzf-lua.config.Base 15 | local __DEFAULT__ = { 16 | previewer = { _ctor = function() return require('fzf-lua-extra.previewers').gitignore end }, 17 | endpoint = endpoint, 18 | json_key = 'body', 19 | filetype = 'text', 20 | ---@type fzf-lua.config.Actions 21 | actions = { 22 | enter = function(selected) 23 | local root = vim.fs.root(0, '.git') 24 | if not root then error('Not in a git repo') end 25 | if not selected[1] then return end 26 | local path = license_path(root) 27 | if not path then return end 28 | local license = assert(selected[1]) 29 | utils.arun(function() 30 | local json = utils.gh({ endpoint = vim.fs.joinpath(endpoint, license) }) 31 | ---@type string 32 | local content = assert(json.body) 33 | utils.write_file(path, content) 34 | vim.cmd.edit(path) 35 | end) 36 | end, 37 | }, 38 | } 39 | 40 | return function(opts) 41 | assert(__DEFAULT__) 42 | opts = vim.tbl_deep_extend('force', __DEFAULT__, opts or {}) 43 | local contents = function(fzf_cb) 44 | utils.arun(function() 45 | local json = utils.gh({ endpoint = opts.endpoint }) 46 | vim.iter(json):each(function(item) fzf_cb(item.key) end) 47 | fzf_cb() 48 | end) 49 | end 50 | return FzfLua.fzf_exec(contents, opts) 51 | end 52 | -------------------------------------------------------------------------------- /doc/fzf-lua-extra.txt: -------------------------------------------------------------------------------- 1 | *fzf-lua-extra.txt* For Neovim >= 0.10.0 Last change: 2025 December 14 2 | 3 | ============================================================================== 4 | Table of Contents *fzf-lua-extra-table-of-contents* 5 | 6 | 1. fzf-lua-extra |fzf-lua-extra-fzf-lua-extra| 7 | - credit |fzf-lua-extra-fzf-lua-extra-credit| 8 | 2. Links |fzf-lua-extra-links| 9 | 10 | ============================================================================== 11 | 1. fzf-lua-extra *fzf-lua-extra-fzf-lua-extra* 12 | 13 | 14 | 15 | Extra pickers for fzf-lua . _aerial 16 | `require("aerial").fzf_lua_picker()` 17 | cliphist cmd ex file_decor 18 | function git_log gitignore grep_project_globs hunks icons lazy license 19 | lua_registry node_lines plocate ps repl rtp runtime scriptnames _serverlist2 20 | `FzfLua serverlist` 21 | _swiper_blines `FzfLua blines profile=ivy` 22 | visits 23 | 24 | 25 | CREDIT *fzf-lua-extra-fzf-lua-extra-credit* 26 | 27 | - 28 | - 29 | - 30 | 31 | ============================================================================== 32 | 2. Links *fzf-lua-extra-links* 33 | 34 | 1. *CI*: https://github.com/phanen/fzf-lua-overlay/actions/workflows/ci.yml/badge.svg?branch=master 35 | 36 | Generated by panvimdoc 37 | 38 | vim:tw=78:ts=8:noet:ft=help:norl: 39 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/lua_registry.lua: -------------------------------------------------------------------------------- 1 | ---@class fle..License: fzf-lua.config.Base 2 | local __DEFAULT__ = { 3 | previewer = 'builtin', 4 | _actions = function() return require('fzf-lua-extra.utils').fix_actions() end, 5 | } 6 | 7 | local function format(v) 8 | local ac = FzfLua.utils.ansi_codes 9 | local info = v.info 10 | local file_col = ('%s:%d:0'):format(FzfLua.make_entry.file(v.src, v.opts), info.linedefined) 11 | local param_str = info.nparams > 0 and (' param=%s'):format(ac.yellow(tostring(info.nparams))) 12 | or '' 13 | local upv_str = info.nups > 0 and (' upv=%s'):format(ac.cyan(tostring(info.nups))) or '' 14 | local vararg_str = info.isvararg and (' ' .. ac.red('vararg')) or '' 15 | local ref_str = v.nref > 1 and (' ref=' .. ac.magenta(tostring(v.nref))) or '' 16 | local fold_str = v.fold_count > 1 and (' fold=' .. ac.magenta(tostring(v.fold_count))) or '' 17 | return ('%s\t%s%s%s%s%s'):format(file_col, param_str, upv_str, vararg_str, ref_str, fold_str) 18 | end 19 | 20 | return function(opts) 21 | assert(__DEFAULT__) 22 | local content = function(cb) 23 | local co = coroutine.running() 24 | local seen = {} 25 | for _, v in pairs(debug.getregistry()) do 26 | if type(v) == 'function' then 27 | local info = debug.getinfo(v) 28 | local src = info.source:sub(1, 1) == '@' and info.source:sub(2) or info.source 29 | seen[v] = seen[v] or { nref = 0 } 30 | seen[v].nref = seen[v].nref + 1 31 | seen[v].info = info 32 | seen[v].src = src 33 | seen[v].opts = opts 34 | end 35 | end 36 | local folded = {} 37 | for _, v in pairs(seen) do 38 | local info = v.info 39 | local key = ('%s:%d:%d:%d'):format( 40 | info.source, 41 | info.linedefined, 42 | info.lastlinedefined, 43 | v.nref 44 | ) 45 | folded[key] = folded[key] or v 46 | folded[key].fold_count = (folded[key].fold_count or 0) + 1 47 | end 48 | for _, v in pairs(folded) do 49 | cb(format(v), function() coroutine.resume(co) end) 50 | coroutine.yield() 51 | end 52 | cb() 53 | end 54 | FzfLua.fzf_exec(function(...) return coroutine.wrap(content)(...) end, opts) 55 | end 56 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/file_decor.lua: -------------------------------------------------------------------------------- 1 | local ns = vim.api.nvim_create_namespace('fzf-lua-extra.decor') 2 | local lmarks = {} ---@type [integer, string, string][] -- line number -> { extmark id, icon, hl } 3 | 4 | local overlay = true 5 | 6 | ---@class fle.config.FileDecor: fzf-lua.config.Base 7 | local __DEFAULT__ = { 8 | file_icons = false, 9 | cmd = 'fd --color=never --hidden --type f --type l --exclude .git', 10 | git_icons = false, 11 | fn_transform = overlay and function(e) return ' ' .. e end or nil, 12 | _fmt = overlay and { from = function(e) return e:match('%S.*') end } or nil, 13 | _actions = function() return require('fzf-lua-extra.utils').fix_actions() end, 14 | previewer = 'builtin', 15 | winopts = { 16 | on_create = function(e) 17 | vim.api.nvim_set_decoration_provider(ns, { 18 | on_line = function(_, _, buf, lnum) 19 | -- skip prompt 0, maybe not the first line though 20 | if buf ~= e.bufnr or lnum == 0 then return end 21 | local line = vim.api.nvim_buf_get_lines(buf, lnum, lnum + 1, true)[1] 22 | if not line then return end 23 | local content = line:match('^%s*(%S+)') 24 | if not content then 25 | if lmarks[lnum] then 26 | vim.api.nvim_buf_del_extmark(buf, ns, lmarks[lnum][1]) 27 | lmarks[lnum] = nil 28 | end 29 | return 30 | end 31 | -- FIXME: scroll... 32 | ---@type string?, string? 33 | local icon, hl = require('mini.icons').get('file', content) 34 | if icon and (not lmarks[lnum] or (lmarks[lnum][2] ~= icon and lmarks[lnum][3] ~= hl)) then 35 | local id = vim.api.nvim_buf_set_extmark(buf, ns, lnum, 0, { 36 | id = (lmarks[lnum] or {})[1], 37 | -- virt_text_pos = 'inline', -- this break fzf match hl 38 | virt_text_pos = overlay and 'overlay' or 'right_align', 39 | virt_text = { { icon .. ' ', hl or 'Error' } }, 40 | }) 41 | lmarks[lnum] = lmarks[lnum] or {} 42 | lmarks[lnum][1] = id 43 | lmarks[lnum][2] = icon 44 | lmarks[lnum][3] = hl 45 | end 46 | end, 47 | }) 48 | end, 49 | on_close = function() vim.api.nvim_set_decoration_provider(ns, { on_line = nil }) end, 50 | }, 51 | } 52 | 53 | return function(opts) 54 | assert(__DEFAULT__) 55 | lmarks = {} 56 | -- require('fzf-lua.devicons').load() 57 | FzfLua.fzf_exec(opts.cmd, opts) 58 | end 59 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/icons.lua: -------------------------------------------------------------------------------- 1 | local utils = require('fzf-lua-extra.utils') 2 | 3 | ---@class fle.config.Icons: fzf-lua.config.Base 4 | local __DEFAULT__ = { 5 | glyphnames_url = 'https://github.com/ryanoasis/nerd-fonts/raw/refs/heads/master/glyphnames.json', 6 | emojis_url = 'https://raw.githubusercontent.com/muan/unicode-emoji-json/refs/heads/main/data-by-emoji.json', 7 | cache_invalid = utils.month_invalid, 8 | -- no complete.{fn, field_index} 9 | complete = function(sel, _o, line, col) 10 | local s = sel[1] 11 | if not s then return '' end 12 | if _o.__CTX.mode == 'i' then col = col - 1 end 13 | local cur_end = (line:len() == 0 or col == 0) and 0 or col + vim.str_utf_end(line, col) 14 | local icon = s:match(('^(.-)' .. FzfLua.utils.nbsp)) 15 | local newline = line:sub(1, cur_end) .. icon .. line:sub(cur_end + 1) 16 | return newline, cur_end 17 | end, 18 | winopts = { relative = 'cursor', height = 0.2, width = 0.3, border = 'none' }, 19 | actions = { 20 | ['ctrl-r'] = { 21 | fn = function(_, o) 22 | o.cache_invalid = function() return true end 23 | end, 24 | reload = true, 25 | }, 26 | }, 27 | } 28 | 29 | ---@diagnostic disable-next-line: no-unknown 30 | return function(opts) 31 | assert(__DEFAULT__) 32 | local contents = function(cb) 33 | utils.arun(function() 34 | local nerds = vim.json.decode( 35 | ---@diagnostic disable-next-line: param-type-mismatch 36 | utils.run( 37 | { 'curl', '-sL', opts.glyphnames_url }, 38 | { cache_path = utils.path('glyphnames.json'), cache_invalid = opts.cache_invalid } 39 | ).stdout 40 | ) 41 | local emojis = vim.json.decode( 42 | ---@diagnostic disable-next-line: param-type-mismatch 43 | utils.run( 44 | { 'curl', '-sL', opts.emojis_url }, 45 | { cache_path = utils.path('emojis.json'), cache_invalid = opts.cache_invalid } 46 | ).stdout 47 | ) 48 | local fu = FzfLua.utils 49 | local nbsp = fu.nbsp 50 | local cyan = fu.ansi_codes.cyan 51 | local yellow = fu.ansi_codes.yellow 52 | local magenta = fu.ansi_codes.magenta 53 | local co = coroutine.running() -- this work now anyway 54 | for k, v in pairs(nerds) do 55 | local str = ('%s%s%s%s(%s)'):format(cyan(v.char), nbsp, yellow(k), nbsp, magenta(v.code)) 56 | cb(str, function() coroutine.resume(co) end) 57 | coroutine.yield() 58 | end 59 | for k, v in pairs(emojis) do 60 | local str = ('%s%s%s%s(%s)'):format(cyan(k), nbsp, yellow(v.name), nbsp, magenta('emoji')) 61 | cb(str, function() coroutine.resume(co) end) 62 | coroutine.yield() 63 | end 64 | cb(nil) 65 | end) 66 | end 67 | FzfLua.fzf_exec(contents, opts) 68 | end 69 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/runtime.lua: -------------------------------------------------------------------------------- 1 | ---@class fle.config.Runtime: fzf-lua.config.Base 2 | local __DEFAULT__ = {} 3 | 4 | local glob_regex = '(.*)%s%-%-%s(.*)' 5 | local api = vim.api 6 | 7 | local test = function() 8 | assert(__DEFAULT__) 9 | local port ---@type string? 10 | local glob ---@type string? 11 | local f ---@type string[] 12 | FzfLua.fzf_live(function(q) 13 | if not q[1] then return end 14 | ---@type string, string 15 | local sq, gq = q[1]:match(glob_regex) 16 | if port then 17 | sq = sq or q[1] ---@type string 18 | vim.system { 'curl', '-XPOST', ('localhost:%s'):format(port), '-d', ('search:%s'):format(sq) } 19 | end 20 | local new_glob = gq or '*' 21 | if new_glob == glob then return f end 22 | glob = new_glob 23 | -- sometimes we don't reload? 24 | f = api.nvim_get_runtime_file(glob, true) 25 | return f 26 | ---@diagnostic disable-next-line: param-type-mismatch 27 | end, { 28 | fzf_opts = { ['--listen'] = true }, 29 | previewer = 'builtin', 30 | -- live_field_index = '{q} $FZF_PORT', 31 | actions = { 32 | start = { 33 | fn = function(s) port = unpack(s) end, 34 | field_index = '$FZF_PORT', 35 | exec_silent = true, 36 | }, 37 | }, 38 | }) 39 | end 40 | 41 | local dedup = function(paths) 42 | table.sort(paths, function(a, b) return #a < #b end) 43 | local res = {} 44 | for _, path in ipairs(paths) do 45 | if not vim.iter(res):any(function(p) return vim.fs.relpath(p, path) end) then 46 | res[#res + 1] = path 47 | end 48 | end 49 | return res 50 | end 51 | 52 | ---@return string[] 53 | local get_rtp = function() 54 | ---@type string[] 55 | local rtp = vim.opt.runtimepath:get() 56 | -- If using lazy.nvim, get all the lazy loaded plugin paths (#1296) 57 | local lazy = package.loaded['lazy.core.util'] 58 | if lazy and lazy.get_unloaded_rtp then vim.list_extend(rtp, (lazy.get_unloaded_rtp(''))) end 59 | return dedup(rtp) 60 | end 61 | 62 | local _ = {} 63 | local file_state = true 64 | 65 | local make_opts = function() 66 | file_state = not file_state 67 | return { query = FzfLua.get_last_query(), resume = true } 68 | end 69 | 70 | _.lgrep = function(opts) 71 | FzfLua.live_grep(vim.tbl_deep_extend('keep', opts or {}, { 72 | search_paths = get_rtp(), 73 | actions = { 74 | ['ctrl-g'] = function() _.files(make_opts()) end, 75 | ['alt-g'] = FzfLua.actions.grep_lgrep, 76 | }, 77 | })) 78 | end 79 | 80 | _.files = function(opts) 81 | FzfLua.files(vim.tbl_deep_extend('keep', opts or {}, { 82 | search_paths = get_rtp(), 83 | actions = { 84 | ['ctrl-g'] = function() _.lgrep(make_opts()) end, 85 | }, 86 | })) 87 | end 88 | 89 | return function(...) return file_state and _.lgrep(...) or _.files(...) end 90 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/lazy.lua: -------------------------------------------------------------------------------- 1 | -- tbh lazy load is not necessary now, just use alias here 2 | local utils = require('fzf-lua-extra.utils') 3 | 4 | ---@param cb fun(plugin: LazyPlugin) 5 | local p_do = function(cb) 6 | return function(selected) 7 | vim.iter(selected):each(function(sel) 8 | local bs_parts = vim.split(sel, '/') 9 | local name = bs_parts[#bs_parts] 10 | cb(utils.get_lazy_plugins()[name]) 11 | end) 12 | end 13 | end 14 | 15 | local State = {} 16 | State.state = { 17 | all_name = function() 18 | State.encode = function(p) return p.name end 19 | end, 20 | all_repo = function() 21 | ---@param p LazyPlugin 22 | ---@return string 23 | State.encode = function(p) 24 | local fullname = p[1] 25 | if not fullname then 26 | local url = p.url 27 | if not url then 28 | fullname = 'unknown/' .. p.name -- dummy name 29 | else 30 | local url_slice = vim.split(url, '/') 31 | local username = url_slice[#url_slice - 1] 32 | local repo = url_slice[#url_slice] 33 | fullname = username .. '/' .. repo 34 | end 35 | end 36 | return fullname 37 | end 38 | end, 39 | } 40 | State.cycle = function() 41 | State.key = next(State.state, State.key) 42 | if not State.key then State.key = next(State.state, State.key) end 43 | State.state[State.key]() 44 | end 45 | 46 | ---@return function, function 47 | State.get = function() return State.filter, State.encode end 48 | State.cycle() 49 | 50 | ---@class fle.config.Lazy: fzf-lua.config.Base 51 | local __DEFAULT__ = { 52 | previewer = { _ctor = function() return require('fzf-lua-extra.previewers').lazy end }, 53 | actions = { 54 | ['enter'] = p_do(function(p) 55 | if p.dir and vim.uv.fs_stat(p.dir) then utils.chdir(p.dir) end 56 | end), 57 | ['ctrl-y'] = p_do(function(p) vim.fn.setreg('+', p.url) end), 58 | ['ctrl-o'] = p_do(function(p) -- search cleaned plugins 59 | vim.ui.open(p.url or ('https://github.com/search?q=%s'):format(p.name)) 60 | end), 61 | ['ctrl-l'] = p_do(function(p) 62 | if p.dir and vim.uv.fs_stat(p.dir) then FzfLua.files { cwd = p.dir } end 63 | end), 64 | ['ctrl-n'] = p_do(function(p) 65 | if p.dir then FzfLua.live_grep_native { cwd = p.dir } end 66 | end), 67 | ['ctrl-r'] = p_do( 68 | function(p) require('lazy.core.loader')[p._ and p._.loaded and 'reload' or 'load'](p) end 69 | ), 70 | ['ctrl-g'] = { fn = State.cycle, reload = true }, 71 | }, 72 | } 73 | 74 | return function(opts) 75 | assert(__DEFAULT__) 76 | local contents = function(fzf_cb) 77 | local filter, encode = State.get() 78 | vim 79 | .iter(utils.get_lazy_plugins()) 80 | :filter(filter or function() return true end) 81 | :each(function(_, p) fzf_cb(encode(p)) end) 82 | fzf_cb() 83 | end 84 | return FzfLua.fzf_exec(contents, opts) 85 | end 86 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/hunks.lua: -------------------------------------------------------------------------------- 1 | local uv = vim.uv 2 | 3 | ---@class fle.config.Hunks: fzf-lua.config.Base 4 | local __DEFAULT__ = { 5 | previewer = 'builtin', 6 | _treesitter = function(line) return line:match('(.-):?(%d+)[: ].-:(.+)$') end, 7 | _actions = function() return require('fzf-lua-extra.utils').fix_actions() end, 8 | } 9 | 10 | --- @param buf_or_filename string|integer 11 | --- @param hunks Gitsigns.Hunk.Hunk[] 12 | --- @param cb function 13 | --- @param opts table 14 | local function cb_hunks(buf_or_filename, hunks, cb, opts) 15 | local utils = require('fzf-lua.utils') 16 | for _, hunk in ipairs(hunks) do 17 | local hl = hunk.type == 'add' and 'Added' or hunk.type == 'delete' and 'Removed' or 'Changed' 18 | local kind = hunk.type == 'add' and '+' or hunk.type == 'delete' and '-' or '~' 19 | local header = ('-%s%s/+%s%s'):format( 20 | hunk.removed.start, 21 | hunk.removed.count ~= 1 and ',' .. tostring(hunk.removed.count) or '', 22 | hunk.added.start, 23 | hunk.added.count ~= 1 and ',' .. tostring(hunk.added.count) or '' 24 | ) 25 | local text = ('%s(%s): %s'):format( 26 | utils.ansi_from_hl(hl, kind), 27 | utils.ansi_from_hl(hl, header), 28 | hunk.added.lines[1] or hunk.removed.lines[1] 29 | ) 30 | 31 | cb(require('fzf-lua.make_entry').lcol({ 32 | bufnr = type(buf_or_filename) == 'number' and buf_or_filename or nil, 33 | filename = type(buf_or_filename) == 'string' and buf_or_filename or nil, 34 | lnum = hunk.added.start, 35 | text = text, 36 | }, opts)) 37 | end 38 | end 39 | 40 | return function(opts) 41 | assert(__DEFAULT__) 42 | if not pcall(require, 'gitsigns') then return end 43 | local config = require('gitsigns.config').config 44 | local git = require('gitsigns.git') 45 | local async = require('gitsigns.async') 46 | local run_diff = require('gitsigns.diff') 47 | local util = require('gitsigns.util') 48 | ---@async 49 | local a = function() 50 | local repo = git.Repo.get((assert(uv.cwd()))) 51 | if not repo then return end 52 | local func = async.create(1, function(cb) 53 | for _, f in ipairs(repo:files_changed(config.base)) do 54 | local f_abs = repo.toplevel .. '/' .. f 55 | local stat = uv.fs_stat(f_abs) 56 | if stat and stat.type == 'file' then 57 | ---@type string 58 | local obj 59 | if config.base and config.base ~= ':0' then 60 | obj = config.base .. ':' .. f 61 | else 62 | obj = ':0:' .. f 63 | end 64 | async.schedule() 65 | local hunks = run_diff(repo:get_show_text(obj), util.file_lines(f_abs)) 66 | async.schedule() 67 | cb_hunks(f_abs, hunks, cb, opts) 68 | end 69 | end 70 | cb() 71 | end) 72 | async.schedule() 73 | FzfLua.fzf_exec(func, opts) 74 | end 75 | async.run(a):raise_on_error() 76 | end 77 | -------------------------------------------------------------------------------- /scripts/gen_readme.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable 2 | -- Update README.md providers section with generated list 3 | -- Usage: nvim -l scripts/gen_readme.lua 4 | 5 | local luv = vim.uv 6 | local dir = 'lua/fzf-lua-extra/providers/' 7 | local readme = 'README.md' 8 | local section_start = '' 9 | local section_end = '' 10 | 11 | local function read_file(path) 12 | local f = io.open(path, 'r') 13 | if not f then 14 | io.stderr:write('Could not open ' .. path .. '\n') 15 | os.exit(1) 16 | end 17 | local content = f:read('*a') 18 | f:close() 19 | return content 20 | end 21 | 22 | local function write_file(path, content) 23 | local f = io.open(path, 'w') 24 | if not f then 25 | io.stderr:write('Could not open ' .. path .. ' for writing\n') 26 | os.exit(1) 27 | end 28 | f:write(content) 29 | f:close() 30 | end 31 | 32 | local function gen_providers() 33 | local extra_info = { 34 | aerial = { 35 | desc = '`require("aerial").fzf_lua_picker()`', 36 | upstream_url = 'https://github.com/stevearc/aerial.nvim/issues/472', 37 | }, 38 | serverlist2 = { 39 | desc = '`FzfLua serverlist`', 40 | upstream_url = 'https://github.com/ibhagwan/fzf-lua/pull/2320', 41 | }, 42 | swiper_blines = { 43 | desc = '`FzfLua blines profile=ivy`', 44 | upstream_url = 'https://github.com/ibhagwan/fzf-lua/issues/2261', 45 | }, 46 | } 47 | local handle = luv.fs_scandir(dir) 48 | if not handle then 49 | io.stderr:write('Could not open directory: ' .. dir .. '\n') 50 | os.exit(1) 51 | end 52 | local entries = {} ---@type string[] 53 | while true do 54 | local name, t = luv.fs_scandir_next(handle) 55 | if not name then break end 56 | if t == 'file' then 57 | local base = name:gsub('%.lua$', '') 58 | table.insert(entries, base) 59 | end 60 | end 61 | table.sort(entries) 62 | local lines = {} 63 | for _, base in ipairs(entries) do 64 | local info = extra_info[base] 65 | if info then 66 | table.insert(lines, string.format('* ~%s~ %s (%s)', base, info.desc, info.upstream_url)) 67 | else 68 | table.insert(lines, '* ' .. base) 69 | end 70 | end 71 | return table.concat(lines, '\n') 72 | end 73 | 74 | local function update_readme() 75 | local f = io.open(readme, 'r') 76 | if not f then 77 | io.stderr:write('Could not open ' .. readme .. '\n') 78 | os.exit(1) 79 | end 80 | local content = read_file(readme) ---@type string 81 | -- Use vim.pesc to escape section markers for robust pattern matching 82 | local pattern = vim.pesc(section_start) .. '(.-)' .. vim.pesc(section_end) 83 | local new_section = gen_providers() 84 | local replaced, n = content:gsub( 85 | pattern, 86 | function(_) return section_start .. '\n' .. new_section .. '\n' .. section_end end 87 | ) 88 | if n == 0 then 89 | io.stderr:write('Section markers not found in ' .. readme .. '\n') 90 | os.exit(1) 91 | end 92 | write_file(readme, replaced) 93 | end 94 | 95 | update_readme() 96 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/compat/async.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable 2 | local M = {} 3 | 4 | local max_timeout = 30000 5 | local copcall = package.loaded.jit and pcall or require('coxpcall').pcall 6 | 7 | --- @param thread thread 8 | --- @param on_finish fun(err: string?, ...:any) 9 | --- @param ... any 10 | local function resume(thread, on_finish, ...) 11 | --- @type {n: integer, [1]:boolean, [2]:string|function} 12 | local ret = vim.F.pack_len(coroutine.resume(thread, ...)) 13 | local stat = ret[1] 14 | 15 | if not stat then 16 | -- Coroutine had error 17 | on_finish(ret[2] --[[@as string]]) 18 | elseif coroutine.status(thread) == 'dead' then 19 | -- Coroutine finished 20 | on_finish(nil, unpack(ret, 2, ret.n)) 21 | else 22 | local fn = ret[2] 23 | --- @cast fn -string 24 | 25 | --- @type boolean, string? 26 | local ok, err = copcall(fn, function(...) resume(thread, on_finish, ...) end) 27 | 28 | if not ok then on_finish(err) end 29 | end 30 | end 31 | 32 | --- @param func async fun(): ...:any 33 | --- @param on_finish? fun(err: string?, ...:any) 34 | function M.run(func, on_finish) 35 | local res --- @type {n:integer, [integer]:any}? 36 | resume(coroutine.create(func), function(err, ...) 37 | res = vim.F.pack_len(err, ...) 38 | if on_finish then on_finish(err, ...) end 39 | end) 40 | 41 | return { 42 | --- @param timeout? integer 43 | --- @return any ... return values of `func` 44 | wait = function(_self, timeout) 45 | vim.wait(timeout or max_timeout, function() return res ~= nil end) 46 | assert(res, 'timeout') 47 | if res[1] then error(res[1]) end 48 | return unpack(res, 2, res.n) 49 | end, 50 | } 51 | end 52 | 53 | --- Asynchronous blocking wait 54 | --- @async 55 | --- @param argc integer 56 | --- @param fun function 57 | --- @param ... any func arguments 58 | --- @return any ... 59 | function M.await(argc, fun, ...) 60 | assert(coroutine.running(), 'Async.await() must be called from an async function') 61 | local args = vim.F.pack_len(...) --- @type {n:integer, [integer]:any} 62 | 63 | --- @param callback fun(...:any) 64 | return coroutine.yield(function(callback) 65 | args[argc] = assert(callback) 66 | fun(unpack(args, 1, math.max(argc, args.n))) 67 | end) 68 | end 69 | 70 | --- @async 71 | --- @param max_jobs integer 72 | --- @param funs (async fun())[] 73 | function M.join(max_jobs, funs) 74 | if #funs == 0 then return end 75 | 76 | max_jobs = math.min(max_jobs, #funs) 77 | 78 | --- @type (async fun())[] 79 | local remaining = { select(max_jobs + 1, unpack(funs)) } 80 | local to_go = #funs 81 | 82 | M.await(1, function(on_finish) 83 | local function run_next() 84 | to_go = to_go - 1 85 | if to_go == 0 then 86 | on_finish() 87 | elseif #remaining > 0 then 88 | local next_fun = table.remove(remaining) 89 | M.run(next_fun, run_next) 90 | end 91 | end 92 | 93 | for i = 1, max_jobs do 94 | M.run(funs[i], run_next) 95 | end 96 | end) 97 | end 98 | 99 | return M 100 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/aerial.lua: -------------------------------------------------------------------------------- 1 | require('aerial').sync_load() -- ensure aerial config loaded 2 | local config = require('aerial.config') 3 | local backends = require('aerial.backends') 4 | local data = require('aerial.data') 5 | local highlight = require('aerial.highlight') 6 | local api = vim.api 7 | local ns = api.nvim_create_namespace('fzf-lua-extra.aerial') 8 | local utils = require('fzf-lua-extra.utils') 9 | 10 | local items = {} ---@type aerial.Symbol[] 11 | local bufnr, filename ---@type integer, string 12 | 13 | ---@param entry_str string 14 | ---@return fzf-lua.buffer_or_file.Entry 15 | local parse_entry = function(entry_str) 16 | local idx0, _ = entry_str:match('^(%d+)\t(.*)$') 17 | local idx = assert(utils.tointeger(idx0)) 18 | local item = assert(items[idx], entry_str) 19 | return { 20 | bufnr = utils.tointeger(bufnr), 21 | bufname = filename, 22 | path = filename, 23 | line = item.lnum or 0, 24 | col = item.col or 0, 25 | -- end_line = item.end_lnum or 0, 26 | -- end_col = item.end_col or 0, 27 | end_line = (item.selection_range or {}).end_lnum or 0, 28 | end_col = (item.selection_range or {}).end_col or 0, 29 | item = item, 30 | } 31 | end 32 | 33 | local format = function(e) 34 | e = parse_entry(e) 35 | return ('%s:%s:%s'):format(e.bufname, e.line, e.col) 36 | end 37 | 38 | ---@class fle.config.Aerial: fzf-lua.config.Base 39 | local __DEFAULT__ = { 40 | fzf_opts = { ['--ansi'] = true, ['--with-nth'] = '2..' }, 41 | _actions = function() return utils.fix_actions(format) end, 42 | previewer = { 43 | _ctor = function() 44 | local base = require 'fzf-lua.previewer.builtin'.buffer_or_file 45 | local previewer = base:extend() 46 | ---@diagnostic disable-next-line: unused 47 | function previewer:parse_entry(entry_str) return parse_entry(entry_str) end 48 | function previewer:set_cursor_hl(entry) 49 | pcall(api.nvim_win_call, self.win.preview_winid, function() 50 | api.nvim_buf_call(self.preview_bufnr, function() 51 | api.nvim_buf_clear_namespace(0, ns, 0, -1) 52 | api.nvim_win_set_cursor(0, { entry.line, entry.col }) 53 | vim.hl.range( 54 | 0, 55 | ns, 56 | self.win.hls.search, 57 | { entry.line - 1, entry.col }, 58 | { entry.end_line - 1, entry.end_col }, 59 | {} 60 | ) 61 | self.orig_pos = api.nvim_win_get_cursor(0) 62 | FzfLua.utils.zz() 63 | end) 64 | end) 65 | end 66 | return previewer 67 | end, 68 | }, 69 | } 70 | 71 | return function(opts) 72 | assert(__DEFAULT__) 73 | bufnr = api.nvim_get_current_buf() 74 | filename = api.nvim_buf_get_name(bufnr) 75 | items = {} 76 | local backend = backends.get() 77 | 78 | if not backend then 79 | backends.log_support_err() 80 | return 81 | elseif not data.has_symbols(bufnr) then 82 | backend.fetch_symbols_sync(bufnr, {}) 83 | end 84 | 85 | if not data.has_symbols(bufnr) then 86 | vim.notify('No symbols found in buffer', vim.log.levels.WARN) 87 | return 88 | end 89 | 90 | local bufdata = data.get_or_create(bufnr) 91 | 92 | FzfLua.fzf_exec(function(fzf_cb) 93 | for i, item in bufdata:iter({ skip_hidden = false }) do 94 | local icon = config.get_icon(bufnr, item.kind) 95 | local icon_hl = highlight.get_highlight(item, true, false) or 'NONE' 96 | icon = FzfLua.utils.ansi_from_hl(icon_hl, icon) 97 | fzf_cb(('%s\t%s%s%s'):format(i, icon, FzfLua.utils.nbsp, item.name)) 98 | items[#items + 1] = item 99 | end 100 | fzf_cb() 101 | end, opts) 102 | end 103 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | pull_request: 5 | 6 | jobs: 7 | tests: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 10 10 | 11 | strategy: 12 | fail-fast: false 13 | 14 | matrix: 15 | neovim_branch: 16 | - "nightly" 17 | 18 | env: 19 | NVIM_TEST_VERSION: ${{ matrix.neovim_branch }} 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v5 24 | 25 | - uses: lewis6991/gh-actions-lua@master 26 | with: 27 | luaVersion: "5.1.5" 28 | 29 | - uses: leafo/gh-actions-luarocks@v6 30 | 31 | - name: Download nvim-test 32 | run: make nvim-test 33 | 34 | - name: Download fzf 35 | run: | 36 | wget -O - https://github.com/junegunn/fzf/releases/download/v0.66.0/fzf-0.66.0-linux_amd64.tar.gz | tar zxfv - 37 | sudo mv ./fzf /bin 38 | 39 | - name: Run Test 40 | env: # we use "gh" 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | run: make test 43 | 44 | docs: 45 | runs-on: ubuntu-latest 46 | needs: tests 47 | if: ${{ github.ref == 'refs/heads/master' }} 48 | steps: 49 | - uses: actions/checkout@v5 50 | - name: Install Neovim 51 | uses: rhysd/action-setup-vim@v1 52 | with: 53 | neovim: true 54 | version: "stable" 55 | - name: Generate README providers section 56 | run: nvim -l scripts/gen_readme.lua 57 | - name: panvimdoc 58 | uses: kdheepak/panvimdoc@main 59 | with: 60 | vimdoc: fzf-lua-extra 61 | version: "Neovim >= 0.10.0" 62 | demojify: true 63 | treesitter: true 64 | - name: Check doc changes 65 | id: check_doc_changes 66 | run: | 67 | set -e 68 | git fetch 69 | changed_files=$(git diff --name-only HEAD doc/) 70 | abort_commit=true 71 | for file in $changed_files; do 72 | # Check if only the first line is changed 73 | diff_output=$(git diff HEAD "$file") 74 | # Count changed lines not in the first line 75 | if echo "$diff_output" | grep -E '^@@' | grep -v '@@ -1,'; then 76 | abort_commit=false 77 | break 78 | fi 79 | done 80 | echo "abort_commit=$abort_commit" >> $GITHUB_OUTPUT 81 | - name: Push changes 82 | if: steps.check_doc_changes.outputs.abort_commit == 'false' 83 | uses: stefanzweifel/git-auto-commit-action@v7 84 | with: 85 | commit_message: "chore(build): auto-generate vimdoc" 86 | commit_user_name: "github-actions[bot]" 87 | commit_user_email: "github-actions[bot]@users.noreply.github.com" 88 | commit_author: "github-actions[bot] " 89 | release: 90 | name: release 91 | if: ${{ github.ref == 'refs/heads/master' }} 92 | needs: 93 | - docs 94 | - tests 95 | runs-on: ubuntu-latest 96 | steps: 97 | - uses: googleapis/release-please-action@v4 98 | id: release 99 | with: 100 | release-type: simple 101 | package-name: fzf-lua-extra 102 | - uses: actions/checkout@v5 103 | - name: tag stable versions 104 | if: ${{ steps.release.outputs.release_created }} 105 | run: | 106 | git config user.name github-actions[bot] 107 | git config user.email github-actions[bot]@users.noreply.github.com 108 | git remote add gh-token "https://${{ secrets.GITHUB_TOKEN }}@github.com/google-github-actions/release-please-action.git" 109 | git tag -d stable || true 110 | git push origin :stable || true 111 | git tag -a stable -m "Last Stable Release" 112 | git push origin stable 113 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/swiper_blines.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable 2 | ---@class fle.config.SwiperBlines: fzf-lua.config.Base 3 | local __DEFAULT__ = {} 4 | 5 | ---@class swiper_state 6 | ---@field lnum integer? 7 | ---@field parsing_lnum integer? 8 | ---@field in_matched boolean? 9 | ---@field start_col integer? 10 | ---@field text string[]? 11 | 12 | return function() 13 | assert(__DEFAULT__) 14 | local off = vim.o.cmdheight + (vim.o.laststatus and 1 or 0) 15 | local height = math.ceil(vim.o.lines / 4) 16 | local ns = vim.api.nvim_create_namespace('swiper') 17 | local buf = vim.api.nvim_get_current_buf() 18 | local hl = function(start_row, start_col, end_row, end_col) 19 | assert(start_col >= 0 and end_col >= 0, 'start_col and end_col must be non-negative') 20 | vim.hl.range(buf, ns, 'IncSearch', { start_row, start_col }, { end_row, end_col }, {}) 21 | end 22 | local on_buf_change = function() 23 | vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1) 24 | local lines = vim.o.lines 25 | local l_s = lines - height - off + 1 26 | local l_e = lines - off - 1 27 | local max_columns = vim.o.columns 28 | for r = l_s, l_e do 29 | local state = {} ---@type swiper_state 30 | for c = 1, max_columns do 31 | local ok, ret = pcall(vim.api.nvim__inspect_cell, 1, r, c) 32 | if not ok or not ret[1] then break end 33 | (function() 34 | if not state.lnum then -- parsing lnum 35 | local d = tonumber(ret[1]) 36 | if not state.parsing_lnum and not d then return end 37 | if not state.parsing_lnum then 38 | state.parsing_lnum = d 39 | return 40 | end 41 | if d then 42 | state.parsing_lnum = state.parsing_lnum * 10 + d 43 | return 44 | end 45 | state.lnum, state.parsing_lnum = assert(state.parsing_lnum), nil 46 | return 47 | end 48 | ---TODO: neovim upstream type 49 | ---@diagnostic disable-next-line: no-unknown 50 | local in_matched = ret[2] and ret[2].reverse 51 | if in_matched and not state.in_matched then 52 | state.start_col = math.max(c - 8, 0) 53 | state.text = { ret[1] } 54 | state.in_matched = in_matched 55 | return 56 | end 57 | if in_matched then 58 | state.text[#state.text + 1] = ret[1] 59 | return 60 | end 61 | if state.in_matched then 62 | hl(state.lnum - 1, state.start_col, state.lnum - 1, c - 8) 63 | state.in_matched = nil 64 | end 65 | end)() 66 | end 67 | end 68 | end 69 | 70 | FzfLua.blines { 71 | silent = true, 72 | _treesitter = function(line) return 'foo.lua', nil, line:sub(2) end, 73 | start = 'cursor', 74 | fzf_colors = { ['hl'] = '-1:reverse', ['hl+'] = '-1:reverse' }, 75 | fzf_opts = { 76 | ['--with-nth'] = '4..', 77 | ['--nth'] = '1..', 78 | ['--exact'] = true, 79 | }, 80 | fzf_args = '--pointer=', 81 | winopts = { 82 | split = ('botright %snew +set\\ nobl'):format(height), 83 | preview = { hidden = true }, 84 | on_create = function(e) 85 | vim.api.nvim_create_autocmd('TextChangedT', { buffer = e.bufnr, callback = on_buf_change }) 86 | end, 87 | on_close = function() 88 | vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1) 89 | vim.api.nvim_win_set_cursor(0, assert(FzfLua.utils.__CTX()).cursor) 90 | FzfLua.utils.zz() 91 | end, 92 | }, 93 | actions = { 94 | focus = { 95 | fn = function(sel, opts) 96 | if not sel[1] then return end 97 | local entry = FzfLua.path.entry_to_file(sel[1], opts) 98 | if not entry.line then return end 99 | local ctx = FzfLua.utils.CTX() 100 | ---@diagnostic disable-next-line: assign-type-mismatch 101 | pcall(vim.api.nvim_win_set_cursor, ctx.winid, { entry.line, entry.col }) 102 | end, 103 | field_index = '{}', 104 | exec_silent = true, 105 | }, 106 | }, 107 | } 108 | end 109 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/git_log.lua: -------------------------------------------------------------------------------- 1 | local pager ---@type string? 2 | local q ---@type string? 3 | 4 | ---@class fle.config.GitLog: fzf-lua.config.Base 5 | local __DEFAULT__ = { 6 | winopts = { 7 | on_create = function(e) 8 | vim.keymap.set('t', '#', function() 9 | local altfile = vim.fn.expand('#') 10 | local root = assert(vim.fs.root(vim.fn.expand('#'), '.git')) 11 | altfile = assert(vim.fs.relpath(root, altfile)) 12 | local bpstart = '\27[200~' 13 | local bpend = '\27[201~' 14 | vim.api.nvim_chan_send(vim.bo.channel, bpstart .. altfile .. bpend) 15 | end, { buffer = e.bufnr }) 16 | -- vim.keymap.set({ 't', 'n' }, '', function() 17 | -- if not insert then FzfLua.hide() end 18 | -- vim.api.nvim_chan_send(vim.bo[e.bufnr].channel, '\x1b') 19 | -- end, { buffer = e.bufnr, nowait = true }) 20 | end, 21 | }, 22 | query = ' --grep=""', 23 | exec_empty_query = true, 24 | preview = { 25 | fn = function(s) 26 | local h = vim.split(s[1], '%s')[1] 27 | if not h then return end 28 | local S_args = q and q:match('%-S(%S+)') 29 | local cmd ---@type string 30 | if S_args then 31 | cmd = 'git grep --color ' .. S_args .. ' ' .. h 32 | else 33 | cmd = 'git show --color ' .. h 34 | end 35 | cmd = not pager and cmd or (cmd .. ' | ' .. pager) 36 | return cmd 37 | end, 38 | type = 'cmd', 39 | }, 40 | keymap = function(opts) 41 | local s = FzfLua.shell.stringify_data2 42 | local insert = true 43 | local iOrN = function(a, b) 44 | return s( 45 | function(...) 46 | return insert and (type(a) == 'function' and a(...) or a) 47 | or (type(b) == 'function' and b(...) or b) 48 | end, 49 | opts, 50 | '' 51 | ) 52 | end 53 | local toggle = function() 54 | insert = not insert 55 | return 'change-prompt:' .. (insert and '❯ ' or '[N]❯ ') 56 | end 57 | return { 58 | fzf = { 59 | j = ('transform:%s'):format(iOrN('put:j', 'down')), 60 | k = ('transform:%s'):format(iOrN('put:k', 'up')), 61 | u = ('transform:%s'):format(iOrN('put:u', 'half-page-down')), 62 | d = ('transform:%s'):format(iOrN('put:d', 'half-page-up')), 63 | i = ('transform:%s'):format(iOrN('put:i', toggle)), 64 | ['ctrl-/'] = ('transform:%s'):format(s(toggle, opts, '')), 65 | -- start = 'beginning-of-line', 66 | start = 'end-of-line+backward-char+change-prompt:' .. (insert and '❯ ' or '[N]❯ '), 67 | ['ctrl-j'] = ('transform:%s'):format(iOrN('down', 'down-match')), 68 | ['ctrl-k'] = ('transform:%s'):format(iOrN('up', 'up-match')), 69 | ['alt-j'] = 'down-match', 70 | ['alt-k'] = 'up-match', 71 | ['ctrl-x'] = 'toggle-raw', 72 | ['ctrl-w'] = 'backward-kill-word', 73 | }, 74 | } 75 | end, 76 | actions = { 77 | enter = function(s) 78 | local h = vim.split(s[1], '%s')[1] 79 | if not h then return end 80 | vim.cmd('Gedit ' .. h) 81 | end, 82 | ['ctrl-t'] = function(s) 83 | local h = vim.split(s[1], '%s')[1] 84 | if not h then return end 85 | vim.cmd('Gtabedit ' .. h) 86 | end, 87 | }, 88 | fzf_opts = { ['--raw'] = true }, 89 | } 90 | 91 | -- --grep=a -S=b 92 | return function(opts) 93 | assert(__DEFAULT__) 94 | pager = vim.fn.executable('delta') == 1 and ('delta --%s'):format(vim.o.bg) or nil 95 | opts._fzf_cli_args = opts._fzf_cli_args or {} 96 | ---@diagnostic disable-next-line: no-unknown 97 | opts._fzf_cli_args[#opts._fzf_cli_args + 1] = '--bind=' 98 | .. require('fzf-lua.libuv').shellescape( 99 | 'start,change:+transform:' 100 | .. FzfLua.shell.stringify_data(function(s, _, _) 101 | if not s[1] then return end 102 | q = s[1] 103 | local cmd = 104 | [[git log --color --pretty=format:"%C(yellow)%h%Creset %Cgreen(%cs)%Creset %s %Cblue<%an>%Creset" ]] 105 | return ('reload(%s)+search:%s'):format( 106 | cmd .. (q:match('%-%-grep=.*$') or ''), 107 | q:match('(.*)%-%-grep=.*') or q or '' 108 | ) 109 | end, opts, '{q}') 110 | ) 111 | 112 | return FzfLua.fzf_exec('true', opts) 113 | end 114 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PJ_ROOT=$(PWD) 2 | 3 | FILTER ?= .* 4 | 5 | export NVIM_RUNNER_VERSION := nightly 6 | export NVIM_TEST_VERSION ?= nightly 7 | 8 | ifeq ($(shell uname -s),Darwin) 9 | UNAME ?= MACOS 10 | else 11 | UNAME ?= LINUX 12 | endif 13 | 14 | .DEFAULT_GOAL := build 15 | 16 | # ------------------------------------------------------------------------------ 17 | # Nvim-test 18 | # ------------------------------------------------------------------------------ 19 | 20 | NVIM_TEST := deps/nvim-test 21 | 22 | .PHONY: nvim-test 23 | nvim-test: $(NVIM_TEST) 24 | 25 | $(NVIM_TEST): 26 | git clone --depth 1 --branch v1.3.0 https://github.com/lewis6991/nvim-test $@ 27 | $@/bin/nvim-test --init 28 | 29 | .PHONY: test 30 | test: $(NVIM_TEST) 31 | NVIM_TEST_VERSION=$(NVIM_TEST_VERSION) \ 32 | $(NVIM_TEST)/bin/nvim-test test \ 33 | --lpath=$(PWD)/lua/?.lua \ 34 | --verbose \ 35 | --filter="$(FILTER)" 36 | 37 | -@stty sane 38 | 39 | .PHONY: test-all 40 | test-all: test-095 test-010 test-nightly 41 | 42 | .PHONY: test-010 43 | test-010: 44 | $(MAKE) $(MAKEFLAGS) test NVIM_TEST_VERSION=v0.10.4 45 | 46 | .PHONY: test-011 47 | test-011: 48 | $(MAKE) $(MAKEFLAGS) test NVIM_TEST_VERSION=v0.11.0 49 | 50 | .PHONY: test-nightly 51 | test-nightly: 52 | $(MAKE) $(MAKEFLAGS) test NVIM_TEST_VERSION=nightly 53 | 54 | export XDG_DATA_HOME ?= $(HOME)/.data 55 | 56 | NVIM := $(XDG_DATA_HOME)/nvim-test/nvim-runner-$(NVIM_RUNNER_VERSION)/bin/nvim 57 | 58 | .PHONY: gen_help 59 | gen_help: $(NVIM_TEST) 60 | $(NVIM) -l ./gen_help.lua 61 | @echo Updated help 62 | 63 | STYLUA_PLATFORM_MACOS := macos-aarch64 64 | STYLUA_PLATFORM_LINUX := linux-x86_64 65 | STYLUA_PLATFORM := $(STYLUA_PLATFORM_$(UNAME)) 66 | 67 | STYLUA_VERSION := v2.0.2 68 | STYLUA_ZIP := stylua-$(STYLUA_PLATFORM).zip 69 | STYLUA_URL_BASE := https://github.com/JohnnyMorganz/StyLua/releases/download 70 | STYLUA_URL := $(STYLUA_URL_BASE)/$(STYLUA_VERSION)/$(STYLUA_ZIP) 71 | STYLUA := deps/stylua 72 | 73 | .INTERMEDIATE: $(STYLUA_ZIP) 74 | $(STYLUA_ZIP): 75 | wget $(STYLUA_URL) 76 | 77 | .PHONY: stylua 78 | stylua: $(STYLUA) 79 | 80 | $(STYLUA): $(STYLUA_ZIP) 81 | unzip $< -d $(dir $@) 82 | 83 | LUA_FILES := $(shell git ls-files lua test) 84 | 85 | .PHONY: stylua-check 86 | stylua-check: $(STYLUA) 87 | $(STYLUA) --check $(LUA_FILES) 88 | 89 | .PHONY: stylua-run 90 | stylua-run: $(STYLUA) 91 | $(STYLUA) $(LUA_FILES) 92 | 93 | .PHONY: build 94 | # build: gen_help stylua-run 95 | build: stylua-run 96 | 97 | .PHONY: doc-check 98 | doc-check: gen_help 99 | git diff --exit-code -- doc 100 | 101 | ################################################################################ 102 | # Emmylua 103 | ################################################################################ 104 | 105 | ifeq ($(shell uname -m),arm64) 106 | EMMYLUA_ARCH ?= arm64 107 | else 108 | EMMYLUA_ARCH ?= x64 109 | endif 110 | 111 | EMMYLUA_REF := 0.16.0 112 | EMMYLUA_OS ?= $(shell uname -s | tr '[:upper:]' '[:lower:]') 113 | EMMYLUA_RELEASE_URL := https://github.com/EmmyLuaLs/emmylua-analyzer-rust/releases/download/$(EMMYLUA_REF)/emmylua_check-$(EMMYLUA_OS)-$(EMMYLUA_ARCH).tar.gz 114 | EMMYLUA_RELEASE_TAR := deps/emmylua_check-$(EMMYLUA_REF)-$(EMMYLUA_OS)-$(EMMYLUA_ARCH).tar.gz 115 | EMMYLUA_DIR := deps/emmylua 116 | EMMYLUA_BIN := $(EMMYLUA_DIR)/emmylua_check 117 | 118 | .PHONY: emmylua 119 | emmylua: $(EMMYLUA_BIN) 120 | 121 | ifeq ($(shell echo $(EMMYLUA_REF) | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$$'),$(EMMYLUA_REF)) 122 | 123 | $(EMMYLUA_BIN): 124 | mkdir -p $(EMMYLUA_DIR) 125 | curl -L $(EMMYLUA_RELEASE_URL) -o $(EMMYLUA_RELEASE_TAR) 126 | tar -xzf $(EMMYLUA_RELEASE_TAR) -C $(EMMYLUA_DIR) 127 | 128 | else 129 | 130 | $(EMMYLUA_BIN): 131 | git clone --filter=blob:none https://github.com/EmmyLuaLs/emmylua-analyzer-rust.git $(EMMYLUA_DIR) 132 | git -C $(EMMYLUA_DIR) checkout $(EMMYLUA_SHA) 133 | cd $(EMMYLUA_DIR) && cargo build --release --package emmylua_check 134 | 135 | endif 136 | 137 | NVIM_TEST_RUNTIME=$(XDG_DATA_HOME)/nvim-test/nvim-test-$(NVIM_TEST_VERSION)/share/nvim/runtime 138 | 139 | $(NVIM_TEST_RUNTIME): $(NVIM_TEST) 140 | $^/bin/nvim-test --init 141 | 142 | # make emmylua-check | grep -o '\[[a-z-]\{6,\}\]$' | sort | uniq | sed 's/^\[\(.*\)\]$/\1/' 143 | 144 | .PHONY: emmylua-check 145 | emmylua-check: $(EMMYLUA_BIN) $(NVIM_TEST_RUNTIME) 146 | VIMRUNTIME=$(NVIM_TEST_RUNTIME) \ 147 | $(EMMYLUA_BIN) . \ 148 | --ignore 'deps/**/*' 149 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/ps.lua: -------------------------------------------------------------------------------- 1 | local utils = require 'fzf-lua.utils' 2 | local exec_lua = function(_) return _ end 3 | 4 | ---@class fle.config.Ps: fzf-lua.config.Base 5 | local __DEFAULT__ = { 6 | cmd = 'ps --sort=-pid -eo pid,ppid,cmd', 7 | ps_preview_cmd = 'ps --no-headers -wwo cmd', 8 | requires_processing = true, 9 | -- debug = true, 10 | multiprocess = true, 11 | fn_preprocess = exec_lua [[return function(e) 12 | local utils = FzfLua.utils 13 | _G.bold = utils.ansi_codes.bold 14 | _G.red = utils.ansi_codes.red 15 | _G.green = utils.ansi_codes.green 16 | _G.magenta = utils.ansi_codes.magenta 17 | _G.hl_cmd = function(cmd) return cmd end 18 | end]], 19 | fn_transform = exec_lua [[return function(e) 20 | if e:match('^%s*PID') then 21 | local sep1, pid, sep2, ppid, sep3, cmd = e:match('^(%s*)(%S+)(%s*)(%S+)(%s*)(%S+)$') 22 | return ('%s%s%s%s%s%s'):format(sep1, bold(pid), sep2, bold(ppid), sep3, bold(cmd)) 23 | end 24 | local sep1, pid, sep2, ppid, sep3, cmd = e:match('^(%s*)(%d+)(%s*)(%d+)(%s*)(.*)$') 25 | return ('%s%s%s%s%s%s'):format(sep1, magenta(pid), sep2, red(ppid), sep3, hl_cmd(cmd)) 26 | end]], 27 | fzf_opts = { 28 | ['--ansi'] = true, 29 | ['--header-lines'] = 1, 30 | ['--multi'] = true, 31 | ['--no-multi'] = false, 32 | -- ['--nth'] = '-1', 33 | -- ['--track'] = true, 34 | -- ['--no-hscroll'] = true, 35 | }, 36 | fzf_colors = { 37 | ['fg'] = 'dim', 38 | ['nth'] = 'regular', 39 | ['hl+'] = '-1:reverse', 40 | ['hl'] = '-1:reverse', 41 | }, 42 | keymap = function(opts) 43 | return { -- TODO: emmylua bug, code action applied on wrong pos 44 | ---@diagnostic disable-next-line: assign-type-mismatch 45 | fzf = { 46 | ['click-header'] = utils.has(opts, 'fzf', { 0, 60 }) 47 | and [[transform-nth(echo "$FZF_CLICK_HEADER_NTH")+transform-prompt(echo "$FZF_CLICK_HEADER_WORD> ")]] 48 | or nil, 49 | }, 50 | } 51 | end, 52 | -- inject treesitter? but often truncated... seems useless 53 | -- upstream currently can only handle file entry 54 | _treesitter = function(line) return 'foo.sh', nil, line:match('%d+%s+%d+%s+(.*)') end, 55 | winopts = { 56 | preview = { wrap = true }, 57 | -- treesitter = true, 58 | }, 59 | previewer = { 60 | _ctor = function() 61 | ---@class fle.previewer.Ps: fzf-lua.previewer.Fzf 62 | ---@field opts fle.config.Ps 63 | local p = require('fzf-lua.previewer.fzf').cmd_async:extend() 64 | ---@diagnostic disable-next-line: unused 65 | function p:fzf_delimiter() return '\\s+' end 66 | function p:cmdline(_) 67 | return ( 68 | FzfLua.shell.stringify_cmd(function(items) 69 | if not items[1] then return FzfLua.utils.shell_nop() end 70 | local pid = items[1]:match('^%s*(%d+)') 71 | if not pid then return 'echo no preview' end 72 | return self.opts.ps_preview_cmd .. ' ' .. pid 73 | end, self.opts, '{}') 74 | ) 75 | end 76 | return p 77 | end, 78 | }, 79 | ---@type fzf-lua.config.Actions 80 | actions = { 81 | -- cursorhold? top? https://github.com/junegunn/fzf/issues/1211 82 | ['ctrl-r'] = { fn = function() end, reload = true }, 83 | change = { fn = function() end, reload = true }, 84 | ['ctrl-x'] = { 85 | fn = function(selected) 86 | ---@type integer[] 87 | local pids = vim.tbl_map(function(s) return tonumber(s:match('^%s*(%d+)')) end, selected) 88 | local sig = require('fzf-lua.utils').input('signal: ', 'sigkill') 89 | if not sig then return end 90 | vim.tbl_map(function(_pid) FzfLua.libuv.process_kill(_pid, sig) end, pids) 91 | end, 92 | field_index = '{+}', 93 | reload = true, 94 | }, 95 | ['ctrl-s'] = { -- man ps | nvim +Man! +'norm! G' +'?STANDARD FORMAT SPECIFIERS' 96 | fn = function(_, opts) 97 | local ps_preview_cmd = require('fzf-lua.utils').input('preview: ', opts.ps_preview_cmd) 98 | if not ps_preview_cmd then return end 99 | opts.ps_preview_cmd = ps_preview_cmd 100 | end, 101 | exec_silent = true, 102 | postfix = 'refresh-preview', 103 | }, 104 | }, 105 | } 106 | 107 | return function(opts) 108 | assert(__DEFAULT__) 109 | if vim.fn.executable('ps') ~= 1 then 110 | utils.warn("No executable 'ps' (https://gitlab.com/procps-ng/procps)") 111 | return 112 | end 113 | return FzfLua.fzf_exec(opts.cmd, opts) 114 | end 115 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/providers/serverlist2.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable 2 | local uv = vim.uv 3 | 4 | local spawn = function() 5 | local cmd = { vim.fn.exepath('nvim'), '--headless' } 6 | uv.spawn( 7 | cmd[1], 8 | ---@diagnostic disable-next-line: missing-fields, missing-parameter 9 | { 10 | args = vim.list_slice(cmd, 2), 11 | env = (function() 12 | -- uv.spawn will override all env when table provided? 13 | -- steal from $VIMRUNTIME/lua/vim/_system.lua 14 | local env = vim.fn.environ() --- @type table 15 | env['NVIM'] = nil 16 | env['NVIM_LISTEN_ADDRESS'] = nil 17 | local renv = {} --- @type string[] 18 | ---@diagnostic disable-next-line: no-unknown 19 | for k, v in pairs(env) do 20 | renv[#renv + 1] = string.format('%s=%s', k, tostring(v)) 21 | end 22 | return renv 23 | end)(), 24 | }, 25 | function(_) end 26 | ) 27 | end 28 | 29 | local parse_entry = function(e) return e and e:match('%((.-)%)') or nil end 30 | 31 | local remote_exec = function(path, method, ...) 32 | local chan = vim.fn.sockconnect('pipe', path, { rpc = true }) 33 | if chan == 0 then return end 34 | local ret = { vim.rpcrequest(chan, method, ...) } 35 | vim.fn.chanclose(chan) 36 | return unpack(ret) 37 | end 38 | 39 | -- generate screenshot 40 | -- spawn a temporary tui for non-tui/headless client 41 | local make_screenshot = function(screenshot, addr, lines, columns) 42 | local closing = false 43 | local utils = require('fzf-lua-extra.utils') 44 | vim.fn.writefile( 45 | utils.center_message({ 'Failed to generate screenshot' }, lines, columns), 46 | screenshot 47 | ) 48 | local uis = vim.F.npcall(remote_exec, addr, 'nvim_list_uis') 49 | if not uis then return end 50 | local has_tui = vim.iter(uis):find(function(info) return info.stdout_tty end) 51 | if has_tui then 52 | -- TODO: lines/columns don't fit in fzf preview... 53 | pcall(remote_exec, addr, 'nvim__screenshot', screenshot) 54 | return 55 | end 56 | local chan = vim.fn.jobstart({ vim.fn.exepath('nvim'), '--server', addr, '--remote-ui' }, { 57 | pty = true, 58 | height = lines, 59 | width = columns, 60 | env = { TERM = 'xterm-256color' }, 61 | on_stdout = function(chan0) 62 | if closing then return end 63 | closing = true 64 | -- TODO: we can loop check line1? (https://github.com/neovim/neovim/blob/460738e02de0b018c5caf1a2abe66441897ae5c8/src/nvim/tui/tui.c#L1692) 65 | vim.defer_fn(function() pcall(remote_exec, addr, 'nvim__screenshot', screenshot) end, 10) 66 | vim.defer_fn(function() vim.fn.jobstop(chan0) end, 20) 67 | end, 68 | }) 69 | return chan 70 | end 71 | 72 | ---@class fle.config.Serverlist2: fzf-lua.config.Base 73 | local __DEFAULT__ = { 74 | screenshot = true and '/tmp/screenshot' or vim.fn.tempname(), 75 | previewer = { 76 | _ctor = function() 77 | local p = require('fzf-lua.previewer.fzf').cmd_async:extend() 78 | function p:cmdline(_) 79 | return FzfLua.shell.stringify_cmd(function(items, lines, columns) 80 | self._last_query = items[2] or '' 81 | local path = parse_entry(items[1]) 82 | if not path then return 'true' end 83 | local screenshot = assert(self.opts.screenshot) ---@type string 84 | local chan = make_screenshot(screenshot, path, lines, columns) 85 | local wait = chan 86 | and vim.fn.executable('waitpid') == 1 87 | and ('waitpid %s;'):format(vim.fn.jobpid(chan)) 88 | or ('sleep %s;'):format(50 / 1000) 89 | local pager = vim.fn.executable('tail') == 1 and 'tail -n+2 %s' or 'cat %s' 90 | return wait .. pager:format(screenshot) 91 | end, self.opts, '{} {q}') 92 | end 93 | return p 94 | end, 95 | }, 96 | _resume_reload = true, -- avoid list contain killed server unhide 97 | keymap = { 98 | fzf = { resize = 'refresh-preview' }, 99 | }, 100 | actions = { 101 | ['enter'] = function(sel) 102 | local path = parse_entry(sel[1]) 103 | if not path then return end 104 | vim.cmd.connect(path) 105 | end, 106 | ['alt-n'] = { fn = function() spawn() end, reload = true }, 107 | ['ctrl-x'] = { 108 | fn = function(sel) 109 | local exec = function(path) 110 | local ok, err = pcall(remote_exec, path, 'nvim_exec2', 'qa!', {}) 111 | assert(ok or err and err:match('Invalid channel'), err) 112 | end 113 | vim.iter(sel):map(parse_entry):each(exec) 114 | end, 115 | reload = true, 116 | }, 117 | }, 118 | } 119 | 120 | return function(opts) 121 | assert(__DEFAULT__) 122 | local f = function(cb) 123 | vim 124 | .iter(vim.fn.serverlist({ peer = true })) 125 | :filter( 126 | function(path) 127 | return not path:match('fzf%-lua') and not vim.tbl_contains(vim.fn.serverlist(), path) 128 | end 129 | ) 130 | :map(function(path) 131 | local cwd = remote_exec(path, 'nvim_exec_lua', 'return vim.uv.cwd()', {}) 132 | if not cwd then return end 133 | cwd = FzfLua.path.normalize(cwd) 134 | return ('%s (%s)'):format(cwd, path) 135 | end) 136 | :each(cb) 137 | cb(nil) 138 | end 139 | FzfLua.fzf_exec(function(cb) 140 | vim.defer_fn(function() f(cb) end, 50) -- wait for spawn/remote_exec? 141 | end, opts) 142 | end 143 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/previewers.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local previewer = require('fzf-lua.previewer.builtin') 4 | local utils = require('fzf-lua-extra.utils') 5 | ---@diagnostic disable-next-line: unused 6 | local api, fn, fs, uv = vim.api, vim.fn, vim.fs, vim.uv 7 | 8 | ---@param self fzf-lua.previewer.BufferOrFile 9 | ---@param entry table 10 | ---@param content string[] 11 | local preview_with = vim.schedule_wrap(function(self, entry, content) 12 | local bufnr = self:get_tmp_buffer() 13 | api.nvim_buf_set_lines(bufnr, 0, -1, false, content) 14 | self:set_preview_buf(bufnr) 15 | self:preview_buf_post(entry) 16 | end) 17 | 18 | ---@enum plugin_type 19 | local p_type = { 20 | LOCAL = 1, -- local module 21 | UNINS_GH = 2, -- uninstall, url is github 22 | UNINS_NO_GH = 3, -- uninstall, not github 23 | INS_MD = 4, -- installed, readme found 24 | INS_NO_MD = 5, -- installed, readme not found 25 | } 26 | 27 | -- Helper to check for README variants 28 | local function find_readme(dir) 29 | local readme_names = { 30 | 'README.md', 31 | 'readme.md', 32 | 'Readme.md', 33 | 'README.markdown', 34 | 'readme.markdown', 35 | 'Readme.markdown', 36 | 'README', 37 | 'readme', 38 | 'Readme', 39 | } 40 | for name, type in fs.dir(dir) do 41 | if type == 'file' then 42 | for _, rname in ipairs(readme_names) do 43 | if name:lower() == rname:lower() then return fs.joinpath(dir, name) end 44 | end 45 | end 46 | end 47 | return nil 48 | end 49 | 50 | -- item can be a fullname or just a plugin name 51 | ---@param plugin LazyPlugin|{} plugin spec 52 | ---@return plugin_type, string? 53 | local parse_plugin_type = function(_, plugin) 54 | local dir = assert(plugin.dir) 55 | 56 | -- clear preview buf? 57 | if not uv.fs_stat(dir) then 58 | if not plugin.url then return p_type.LOCAL end 59 | if plugin.url:match('github') then return p_type.UNINS_GH end 60 | return p_type.UNINS_NO_GH 61 | end 62 | 63 | -- README check 64 | local readme_path = find_readme(dir) 65 | if readme_path then return p_type.INS_MD, readme_path end 66 | 67 | return p_type.INS_NO_MD 68 | end 69 | 70 | ---@class fle.previewer.Lazy: fzf-lua.previewer.BufferOrFile 71 | ---@field super fzf-lua.previewer.BufferOrFile 72 | M.lazy = previewer.buffer_or_file:extend() 73 | 74 | function M.lazy:new(...) 75 | self.super.new(self, ...) 76 | self.bcache = {} 77 | return self 78 | end 79 | 80 | ---@diagnostic disable-next-line: unused 81 | ---@param entry_str string 82 | ---@return LazyPlugin 83 | function M.lazy:parse_entry(entry_str) 84 | local slices = vim.split(entry_str, '/') 85 | local name = assert(slices[#slices]) 86 | return assert(utils.get_lazy_plugins()[name]) 87 | end 88 | 89 | ---@diagnostic disable-next-line: unused 90 | ---@param entry LazyPlugin 91 | function M.lazy:key_from_entry(entry) return entry.name end 92 | 93 | function M.lazy:populate_preview_buf(entry_str) 94 | local plugin = self:parse_entry(entry_str) 95 | local t, data = parse_plugin_type(self, plugin) 96 | local win = api.nvim_win_get_config(self.win.preview_winid) 97 | local center = function(msg) return utils.center_message(msg, win.height, win.width) end 98 | ---@type table 99 | local handlers = { 100 | [p_type.UNINS_GH] = function() 101 | local repo = assert(plugin[1] or plugin.url) 102 | repo = assert(repo:match('[^/]+/.+')) 103 | local res = utils.gh({ endpoint = fs.joinpath('repos', repo, 'readme') }) 104 | local content = res.content or '' 105 | content = (content:gsub('[\n\r]', '')) 106 | if res.encoding == 'base64' then -- TODO: error now never bubble up in vim._async.. 107 | content = vim.F.npcall(vim.base64.decode, content) 108 | else 109 | error('unimplemented encoding: ' .. res.encoding) 110 | end 111 | if not content then return center({ 'Failed to decode base64 content!' }), 'markdown' end 112 | local lines = vim.split(content, '\n') 113 | lines = vim.list_extend({ 'Not Installed (fetch from github)' }, lines) 114 | return lines, 'markdown' 115 | end, 116 | [p_type.UNINS_NO_GH] = function() 117 | return center({ 'echo "Not Installed (not github)"!' }), 'markdown' 118 | end, 119 | [p_type.INS_MD] = function() 120 | local content = utils.read_file(assert(data)) 121 | local lines = content and vim.split(content, '\n') or center({ 'Failed to read README!' }) 122 | return lines, 'markdown' 123 | end, 124 | ('cat %s'):format(data), 125 | [p_type.INS_NO_MD] = function() 126 | return vim.split(utils.run({ 'ls', '-lh', plugin.dir }).stdout or '', '\n'), 'dirpager' 127 | end, 128 | } 129 | local handler = handlers[t] 130 | if not handler then return end 131 | utils.arun(function() 132 | local lines, filetype = handler() 133 | local entry = vim.deepcopy(plugin) ---@type LazyPlugin|{} 134 | entry.path, entry.filetype = '', filetype 135 | preview_with(self, entry, lines) 136 | end) 137 | end 138 | 139 | ---@class fle.previewer.Gitignore: fzf-lua.previewer.BufferOrFile 140 | ---@field super fzf-lua.previewer.BufferOrFile 141 | ---@field endpoint string 142 | ---@field filetype string 143 | ---@field json_key string 144 | M.gitignore = previewer.buffer_or_file:extend() 145 | 146 | function M.gitignore:new(o, opts) 147 | M.gitignore.super.new(self, o, opts) 148 | self.endpoint = opts.endpoint 149 | self.filetype = opts.filetype 150 | self.json_key = opts.json_key 151 | return self 152 | end 153 | 154 | function M.gitignore:populate_preview_buf(entry_str) 155 | utils.arun(function() 156 | local endpoint = fs.joinpath(self.endpoint, entry_str) 157 | local json = utils.gh({ endpoint = endpoint }) 158 | local content = assert(json[self.json_key]) ---@type string 159 | preview_with(self, { path = '', filetype = self.filetype }, vim.split(content, '\n')) 160 | end) 161 | end 162 | 163 | return M 164 | -------------------------------------------------------------------------------- /test/main_spec.lua: -------------------------------------------------------------------------------- 1 | ---@diagnostic disable: invisible, no-unknown, assign-type-mismatch, param-type-mismatch, need-check-nil 2 | local n = require('nvim-test.helpers') 3 | local Screen = require('nvim-test.screen') 4 | local exec_lua = n.exec_lua 5 | 6 | local scale = (os.getenv('CI') and 10 or 1) 7 | local row_expr_no_attr = function(self, gridnr, rownr, cursor) 8 | local rv = {} 9 | local i = 1 10 | local has_windows = self._options.ext_multigrid and gridnr == 1 11 | local row = self._grids[gridnr].rows[rownr] 12 | if has_windows and self.msg_grid and self.msg_grid_pos < rownr then 13 | return '[' .. self.msg_grid .. ':' .. string.rep('-', #row) .. ']' 14 | end 15 | while i <= #row do 16 | local did_window = false 17 | if has_windows then 18 | for id, pos in pairs(self.win_position) do 19 | if 20 | i - 1 == pos.startcol 21 | and pos.startrow <= rownr - 1 22 | and rownr - 1 < pos.startrow + pos.height 23 | then 24 | table.insert(rv, '[' .. id .. ':' .. string.rep('-', pos.width) .. ']') 25 | i = i + pos.width 26 | did_window = true 27 | end 28 | end 29 | end 30 | 31 | if not did_window then 32 | if not self._busy and cursor and self._cursor.col == i then table.insert(rv, '^') end 33 | table.insert(rv, row[i].text) 34 | i = i + 1 35 | end 36 | end 37 | -- trailing whitespace 38 | return table.concat(rv, '') --:gsub('%s+$', '') 39 | end 40 | 41 | ---@param self test.screen 42 | local function render_no_attr(self) 43 | local rv = {} 44 | for igrid, grid in pairs(self._grids) do 45 | local height = grid.height 46 | if igrid == self.msg_grid then height = self._grids[1].height - self.msg_grid_pos end 47 | for i = 1, height do 48 | -- local cursor = self._cursor.grid == igrid and self._cursor.row == i 49 | local cursor = false 50 | table.insert(rv, row_expr_no_attr(self, igrid, i, cursor) .. '|') 51 | end 52 | end 53 | print(table.concat(rv, '\n')) 54 | end 55 | 56 | describe('main', function() 57 | local screen --- @type test.screen 58 | local red = '\27[0;31m' 59 | local green = '\27[0;32m' 60 | local clear = '\27[0m' 61 | local color = red 62 | local prompt_mark = '\27]133;A;\a\27' 63 | before_each(function() 64 | n.clear() 65 | screen = Screen.new(80, 24) 66 | screen:attach({ ext_messages = true }) 67 | ---@diagnostic disable-next-line: inject-field 68 | screen._handle_screenshot = function() end 69 | -- screen:set_default_attr_ids(nil) 70 | exec_lua(function() ---@diagnostic disable-next-line: duplicate-set-field 71 | -- for plocated 72 | ---@diagnostic disable-next-line: duplicate-set-field 73 | vim.fn.input = function() return 'input' end 74 | -- vim.fn.confirm = function() return 1 end 75 | -- cannot print https://github.com/neovim/neovim/blob/12689c73d882a29695d3fff4f6f5af642681f0a6/runtime/lua/vim/pack.lua#L370 76 | ---@diagnostic disable-next-line: duplicate-set-field 77 | -- _G.save_print, _G.print = _G.print, function() end 78 | vim.env.XDG_DATA_HOME = vim.fs.abspath('./deps/.data') 79 | vim.env.XDG_CONFIG_HOME = vim.fs.abspath('./deps/.config') 80 | vim.opt.pp:append( 81 | -- TODO: we don't need lockfile, modify HOME+NVIM_APPNAME? 82 | vim.fs.joinpath(vim.env.XDG_DATA_HOME, vim.env.NVIM_APPNAME or 'nvim', 'site') 83 | ) 84 | vim.pack.add(os.getenv('CI') and { 85 | { src = 'https://github.com/ibhagwan/fzf-lua' }, 86 | { src = 'https://github.com/stevearc/aerial.nvim' }, 87 | { src = 'https://github.com/echasnovski/mini.nvim' }, 88 | { src = 'https://github.com/folke/lazy.nvim' }, 89 | { src = 'https://github.com/lewis6991/gitsigns.nvim' }, 90 | } or { 91 | { src = 'file://' .. vim.fs.joinpath(vim.env.HOME, 'b/fzf-lua'), version = 'cli' }, 92 | { src = 'file://' .. vim.fs.joinpath(vim.env.HOME, 'lazy/aerial.nvim') }, 93 | { src = 'file://' .. vim.fs.joinpath(vim.env.HOME, 'lazy/mini.nvim') }, 94 | { src = 'file://' .. vim.fs.joinpath(vim.env.HOME, 'lazy/lazy.nvim') }, 95 | { src = 'file://' .. vim.fs.joinpath(vim.env.HOME, 'lazy/gitsigns.nvim') }, 96 | }, { confirm = false }) 97 | vim.pack.update(nil, { force = true }) 98 | -- pass spec to let lazy konw it's not a plugins... 99 | require('lazy').setup({ spec = {}, performance = { rtp = { reset = false } } }) 100 | require('aerial').setup({}) 101 | require('fzf-lua').setup({ 'hide' }) 102 | require('mini.visits').setup() 103 | require('mini.icons').setup() 104 | require('gitsigns').setup() 105 | vim.opt.rtp:append('.') 106 | vim.cmd.runtime { 'plugin/fzf-lua-extra.lua', bang = true } 107 | end) 108 | if os.getenv('update_only') then os.exit(0) end 109 | -- print('SERVERNAME:', n.api.nvim_get_vvar('servername')) 110 | -- n.feed('y') 111 | -- screen:print_snapshot() 112 | -- exec_lua(function() _G.print = _G.save_print end) 113 | end) 114 | 115 | after_each(function() n.eq(n.api.nvim_get_vvar('errmsg'), '') end) 116 | 117 | local curdir = debug.getinfo(1, 'S').source:sub(2):match('(.*/)') 118 | for name, _ in vim.fs.dir(vim.fs.joinpath(curdir, '../lua/fzf-lua-extra/providers')) do 119 | name = name:match('(.*)%.lua$') 120 | it(('%s no error'):format(name), function() 121 | color = color == red and green or red 122 | print(clear, prompt_mark, color) 123 | n.api.nvim_command('edit test/main_spec.lua') 124 | n.fn.search('function(') 125 | exec_lua(function(name0, scale0) 126 | local opts = { 127 | cmd = { query = 'journalctl --user -u kanata' }, 128 | ex = { query = 'ls' }, 129 | repl = { query = 'vim.api.nvim_list_uis()' }, 130 | } 131 | assert(xpcall(function() require('fzf-lua-extra')[name0](opts[name0]) end, debug.traceback)) 132 | -- vim.api.nvim_command('sleep 100m') wait jobstart, check callback codepath 133 | vim.uv.sleep(100 * scale0) 134 | vim.defer_fn(function() vim.api.nvim_input((''):rep(4)) end, 100 * scale0) 135 | end, name, scale) 136 | n.sleep(200 * scale) 137 | -- screen:sleep(200 * scale) 138 | ---@diagnostic disable-next-line: redundant-parameter 139 | n.run_session(screen._session, nil, function(method, args) 140 | if method == 'nvim_print_event' then return end 141 | screen:_redraw(args) 142 | end, 200 * scale) 143 | render_no_attr(screen) 144 | -- screen:expect({ messages = {} }) 145 | end) 146 | end 147 | end) 148 | -------------------------------------------------------------------------------- /lua/fzf-lua-extra/utils.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---@diagnostic disable-next-line: assign-type-mismatch 4 | ---@module 'vim._async' 5 | local async = vim.F.npcall(require, 'vim._async') or require('fzf-lua-extra.compat.async') 6 | 7 | ---@diagnostic disable-next-line: unused-local 8 | local api, fn, uv, fs = vim.api, vim.fn, vim.uv, vim.fs 9 | 10 | local state_path ---@type string 11 | ---@param ... string 12 | ---@return string 13 | M.path = function(...) 14 | state_path = state_path or M.stdpath('state') 15 | return fs.joinpath(state_path, 'fzf-lua-extra', ...) 16 | end 17 | 18 | ---@param s string 19 | ---@return string 20 | M.stdpath = function(s) ---@diagnostic disable-next-line: param-type-mismatch, return-type-mismatch 21 | return fn.stdpath(s) 22 | end 23 | 24 | M.arun = async.run 25 | 26 | --- @param x any 27 | --- @return integer? 28 | function M.tointeger(x) 29 | local nx = tonumber(x) 30 | if nx and nx == math.floor(nx) then 31 | --- @cast nx integer 32 | return nx 33 | end 34 | end 35 | 36 | ---@param path string 37 | M.chdir = function(path) 38 | if fn.executable('zoxide') == 1 then vim.system { 'zoxide', 'add', path } end 39 | api.nvim_set_current_dir(path) 40 | end 41 | 42 | ---@type fun(fmt: string, ...: any) 43 | M.errf = function(fmt, ...) error(fmt:format(...)) end 44 | 45 | ---@param path string 46 | ---@param flag iolib.OpenMode? 47 | ---@return string? 48 | M.read_file = function(path, flag) 49 | local fd = io.open(path, flag or 'r') 50 | if not fd then return nil end 51 | local content = fd:read('*a') 52 | fd:close() 53 | return content or '' 54 | end 55 | 56 | -- mkdir for file 57 | local fs_file_mkdir = function(path) 58 | ---@type string[] 59 | local parents = {} 60 | vim.iter(fs.parents(path)):all(function(dir) 61 | local fs_stat = uv.fs_stat(dir) 62 | if not fs_stat then 63 | parents[#parents + 1] = dir 64 | return true 65 | end 66 | return false 67 | end) 68 | vim.iter(parents):rev():each(function(p) return uv.fs_mkdir(p, 493) end) 69 | end 70 | 71 | -- path should be normalized 72 | ---@param path string 73 | ---@param content string 74 | ---@param flag iolib.OpenMode? 75 | ---@return boolean 76 | M.write_file = function(path, content, flag) 77 | if not uv.fs_stat(path) then fs_file_mkdir(path) end 78 | local fd = io.open(path, flag or 'w') 79 | if not fd then return false end 80 | if content then fd:write(content) end 81 | fd:close() 82 | return true 83 | end 84 | 85 | -- https://github.com/folke/lazy.nvim/blob/d3974346b6cef2116c8e7b08423256a834cb7cbc/lua/lazy/view/render.lua#L38-L40 86 | ---@return table 87 | M.get_lazy_plugins = function() 88 | ---@module 'lazy.core.config' 89 | local cfg = package.loaded['lazy.core.config'] 90 | if not cfg or not cfg.plugins then 91 | error('lazy.nvim is not loaded') 92 | return {} 93 | end 94 | local plugins = vim.tbl_deep_extend('force', cfg.plugins, cfg.spec.disabled) 95 | for _, p in ipairs(cfg.to_clean) do -- kind="clean" seems not named in table 96 | plugins[p.name] = p 97 | end 98 | return plugins 99 | end 100 | 101 | ---@class fle.SystemOpts: vim.SystemOpts 102 | ---@field cache_path? string 103 | ---@field cache_invalid? fun(cache_path: string): boolean 104 | 105 | ---@param path string 106 | ---@return boolean 107 | M.month_invalid = function(path) 108 | local stat = uv.fs_stat(path) 109 | return not stat or (os.time() - stat.ctime.sec) > 30 * 24 * 60 * 60 110 | end 111 | 112 | ---run with optional cache 113 | ---@async 114 | ---@param cmd string[] 115 | ---@param opts? fle.SystemOpts 116 | ---@return vim.SystemCompleted 117 | M.run = function(cmd, opts) 118 | opts = opts or {} ---@type fle.SystemOpts 119 | opts.cache_invalid = opts.cache_invalid or function(_) return false end 120 | local path = opts.cache_path 121 | if path then 122 | local res = M.read_file(path) 123 | if res and #res > 0 and not opts.cache_invalid(path) then 124 | return { code = 0, stdout = res, signal = 0 } 125 | end 126 | end 127 | local obj = async.await(3, vim.system, cmd, opts) ---@type vim.SystemCompleted 128 | if obj.code ~= 0 then M.errf('Fail %q (%s %s)', table.concat(cmd, ' '), obj.code, obj.stderr) end 129 | if path and obj.stdout then assert(M.write_file(path, obj.stdout or '')) end 130 | return obj 131 | end 132 | 133 | ---@class fle.gh.Opts 134 | ---@field endpoint string 135 | ---@field method? string 136 | ---@field headers? table 137 | ---@field data? any 138 | 139 | ---github restful api with cache 140 | ---@async 141 | ---@param opts fle.gh.Opts 142 | ---@return table 143 | M.gh = function(opts) 144 | local method = opts.method or 'GET' 145 | local headers = opts.headers or {} 146 | local data = opts.data 147 | 148 | local cmd = { 'gh', 'api', opts.endpoint, '--method', method } 149 | 150 | -- Add headers if provided 151 | for k, v in pairs(headers) do 152 | table.insert(cmd, '-H') 153 | table.insert(cmd, string.format('%s: %s', k, v)) 154 | end 155 | 156 | -- Add data if provided 157 | if data then 158 | table.insert(cmd, '--input') 159 | table.insert(cmd, '-') 160 | end 161 | 162 | ---@param str string 163 | ---@return table 164 | local parse_gh_result = function(str) 165 | local ok, tbl = pcall(vim.json.decode, str) 166 | if not ok then error('Fail to parse json: ' .. tostring(tbl)) end ---@cast tbl table 167 | if tbl.message and tbl.message:match('API rate limit exceeded') then M.errf(tbl.message) end 168 | return tbl 169 | end 170 | 171 | local sopts = {} ---@type fle.SystemOpts 172 | if data then sopts.stdin = vim.json.encode(data) end 173 | sopts.cache_invalid = function(_) return false end 174 | sopts.cache_path = M.path(opts.endpoint .. '.json') 175 | 176 | local obj = M.run(cmd, sopts) 177 | local stdout = obj.stdout or '' 178 | -- local stderr = obj.stderr or '' 179 | -- if obj.code ~= 0 then M.errf('gh api failed: %s', stderr) end 180 | return parse_gh_result(stdout) 181 | end 182 | 183 | ---@param name string 184 | ---@return string 185 | M.format_env = function(name) 186 | local env_vars = { 187 | { 188 | key = 'LAZY', 189 | color = 'cyan', 190 | get = function(env) 191 | local v = vim.tbl_get(package.loaded['lazy.core.config'] or {}, 'options', 'root') 192 | env.LAZY = v 193 | return v 194 | end, 195 | }, 196 | { key = 'XDG_CONFIG_HOME', color = 'yellow' }, 197 | { key = 'XDG_STATE_HOME', color = 'red' }, 198 | { key = 'XDG_CACHE_HOME', color = 'grey' }, 199 | { key = 'XDG_DATA_HOME', color = 'green' }, 200 | { key = 'VIMFILE', color = 'red', get = function() return '/usr/share/vim/vimfiles' end }, 201 | { key = 'VIMRUNTIME', color = 'red' }, 202 | } 203 | 204 | local ac = require('fzf-lua.utils').ansi_codes 205 | 206 | for _, env in ipairs(env_vars) do 207 | local value = env.get and env.get(vim.env) or vim.env[env.key] 208 | if value and name:match('^' .. vim.pesc(value)) then 209 | local color_fn = ac[env.color] or function(x) return x end 210 | name = name:gsub('^' .. vim.pesc(value), color_fn('$' .. env.key)) 211 | break 212 | end 213 | end 214 | return name 215 | end 216 | 217 | ---@param format? function 218 | ---@return fzf-lua.config.Actions 219 | M.fix_actions = function(format) 220 | local actions = vim.deepcopy(FzfLua.config.globals.actions.files) ---@type fzf-lua.config.Actions 221 | for a, f in pairs(actions) do 222 | if type(a) == 'string' then 223 | if type(f) == 'function' then actions[a] = { fn = f } end 224 | local old_fn = actions[a].fn 225 | if old_fn then 226 | actions[a].fn = function(s, ...) 227 | if s[1] and vim.startswith(s[1], '/tmp/fzf-temp-') then -- {+f} is used as field_index 228 | ---@diagnostic disable-next-line: need-check-nil 229 | s = vim.split(io.open(s[1], 'r'):read('*a'), '\n') 230 | s[#s] = nil 231 | end 232 | s = format and vim.tbl_map(format, s) or s 233 | return old_fn(s, ...) 234 | end 235 | end 236 | end 237 | end 238 | return actions 239 | end 240 | 241 | ---@param messages string[] 242 | ---@param lines integer 243 | ---@param columns integer 244 | ---@return string[] 245 | M.center_message = function(messages, lines, columns) 246 | -- messages: array of strings (each is a line) 247 | local msg_count = #messages 248 | local top = math.floor((lines - msg_count) / 2) 249 | local bottom = lines - top - msg_count 250 | 251 | -- Center each message line horizontally 252 | local centered_lines = {} ---@type string[] 253 | for _, line in ipairs(messages) do 254 | local pad = math.max(0, columns - #line) 255 | local left = math.floor(pad / 2) 256 | local right = pad - left 257 | table.insert(centered_lines, string.rep(' ', left) .. line .. string.rep(' ', right)) 258 | end 259 | 260 | -- Build the array 261 | local result = {} 262 | for _ = 1, top do 263 | table.insert(result, string.rep(' ', columns)) 264 | end 265 | for _, line in ipairs(centered_lines) do 266 | table.insert(result, line) 267 | end 268 | for _ = 1, bottom do 269 | table.insert(result, string.rep(' ', columns)) 270 | end 271 | return result 272 | end 273 | 274 | return M 275 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [3.8.0](https://github.com/phanen/fzf-lua-extra/compare/v3.7.0...v3.8.0) (2025-11-05) 4 | 5 | 6 | ### Features 7 | 8 | * treesitter hl ps ([a04601c](https://github.com/phanen/fzf-lua-extra/commit/a04601c1b7e6ba66ea0e09a445eabac6b28424ff)) 9 | 10 | 11 | ### Bug Fixes 12 | 13 | * _G.FzfLua give types ([83e563a](https://github.com/phanen/fzf-lua-extra/commit/83e563ad8712d7fa7c0230272494f92b1d32f085)) 14 | * actions in global config ([6873166](https://github.com/phanen/fzf-lua-extra/commit/6873166adab69013c53b5ffd6e33d99aae2ce4b1)) 15 | * config types ([4a2c0e3](https://github.com/phanen/fzf-lua-extra/commit/4a2c0e307a67e967c0d1c27343f53daa339692bd)) 16 | * connect error ([81cc1e5](https://github.com/phanen/fzf-lua-extra/commit/81cc1e53e252b92297ec185448645c5abff996bd)) 17 | * deepcopy global opts ([6640dc5](https://github.com/phanen/fzf-lua-extra/commit/6640dc55995ca286cfb36dff465ec241fb7404e4)) 18 | * force update without confirm buf ([9ae0ccd](https://github.com/phanen/fzf-lua-extra/commit/9ae0ccd0f447a51f0d6123f027f999e2d73e5b51)) 19 | * hunk stream early truncated ([432fd58](https://github.com/phanen/fzf-lua-extra/commit/432fd5851f1accde5fb1ccc111670700170c7c47)) 20 | * **hunks:** weird coredump in test ([253789c](https://github.com/phanen/fzf-lua-extra/commit/253789c04e9e01aec10074cef9470a61167b6396)) 21 | * ignore luals warnings ([508aeb4](https://github.com/phanen/fzf-lua-extra/commit/508aeb46ea144b41907fef33c9a231bda493deae)) 22 | * rename serverlist to serverlist2 ([8d1891b](https://github.com/phanen/fzf-lua-extra/commit/8d1891bde7dba1db151d786a5aa6705d5c08c594)) 23 | * sigabort in compose_line again ([cc244b8](https://github.com/phanen/fzf-lua-extra/commit/cc244b80b36d3242ff40885d1f4a454b08f4b349)) 24 | * use absolute path for xdg_{config,data}_path ([80cce0c](https://github.com/phanen/fzf-lua-extra/commit/80cce0c3413e5902254bb837042b02f5cf92cac2)) 25 | * verbose json decode error ([a874c20](https://github.com/phanen/fzf-lua-extra/commit/a874c20486896e4651099a055f43daa859bbef5d)) 26 | * wtf the table is ([14ecd15](https://github.com/phanen/fzf-lua-extra/commit/14ecd15a236b910d09f6f5461116ad9ffe44e1d5)) 27 | 28 | ## [3.7.0](https://github.com/phanen/fzf-lua-extra/compare/v3.6.0...v3.7.0) (2025-10-25) 29 | 30 | 31 | ### Features 32 | 33 | * ex cmd mvp ([ae39760](https://github.com/phanen/fzf-lua-extra/commit/ae39760e53531d30b8618e6a53b16b26e7e051f3)) 34 | * **gitlog:** normal mode ([8d9bb63](https://github.com/phanen/fzf-lua-extra/commit/8d9bb630fff013dde8e2789a196511191805f70e)) 35 | * hunks picker ([1e2971f](https://github.com/phanen/fzf-lua-extra/commit/1e2971f489b177916f76b96c6f077c36f425a310)) 36 | 37 | 38 | ### Bug Fixes 39 | 40 | * feed nil to close pipe ([3f83885](https://github.com/phanen/fzf-lua-extra/commit/3f838855399caf6b6b75fa003b878c10fc7dd473)) 41 | * hunk test ([2798909](https://github.com/phanen/fzf-lua-extra/commit/2798909c54755d5b3c4565d8f50136aea03fee6b)) 42 | * improve tui detection ([9f7b943](https://github.com/phanen/fzf-lua-extra/commit/9f7b943c7505a13b23747c0c5ac6446524af7a6a)) 43 | * openpty to get screenshot data ([56c3197](https://github.com/phanen/fzf-lua-extra/commit/56c3197d8a8d8603f39135f48bae5198f910ef8a)) 44 | * **repl:** feed nil to close pipe ([af76957](https://github.com/phanen/fzf-lua-extra/commit/af76957e592079c82eb05ed861a7c883e022584c)) 45 | * show info when instance is headless ([d961f8f](https://github.com/phanen/fzf-lua-extra/commit/d961f8f4c58d06814b711118e7d6e65cf3f6d2a1)) 46 | * spawn a tui client to generate screenshot when needed ([8af803b](https://github.com/phanen/fzf-lua-extra/commit/8af803b5286b7dee7b26e437f3f1d8807d9ec06d)) 47 | * when child seems exited ([b002cb0](https://github.com/phanen/fzf-lua-extra/commit/b002cb017709c4aaab136389468172db84c7f547)) 48 | 49 | ## [3.6.0](https://github.com/phanen/fzf-lua-extra/compare/v3.5.0...v3.6.0) (2025-10-21) 50 | 51 | 52 | ### Features 53 | 54 | * aerial actions ([e863b91](https://github.com/phanen/fzf-lua-extra/commit/e863b9119c2c54997de1c3e5e19a26a130f3ffe0)) 55 | * **gitlog:** raw mode ([84fbad5](https://github.com/phanen/fzf-lua-extra/commit/84fbad5bfd1d9e886cd9ad30d5ea4c12d1aacc76)) 56 | * **serverlist:** create new instance ([9d5ba52](https://github.com/phanen/fzf-lua-extra/commit/9d5ba52640c38a08d8c8aea51859a57dce88b9e3)) 57 | 58 | 59 | ### Bug Fixes 60 | 61 | * aerial now has builtin picker ([0bca3fe](https://github.com/phanen/fzf-lua-extra/commit/0bca3fec62c97e0b7308b5f1b4f043a7a8b2459f)) 62 | * avoid indirect dep on mini.icons ([c3e096f](https://github.com/phanen/fzf-lua-extra/commit/c3e096fb2255045d64c0e6869f1dd2e8b3af9c63)) 63 | * disable new keymap ([c3e096f](https://github.com/phanen/fzf-lua-extra/commit/c3e096fb2255045d64c0e6869f1dd2e8b3af9c63)) 64 | * **file_decor:** padding space to make ansi color happy ([de0121a](https://github.com/phanen/fzf-lua-extra/commit/de0121a94fe589b29cbdc5106aa89a60615c654b)) 65 | * grep rtp ([de35955](https://github.com/phanen/fzf-lua-extra/commit/de35955f48ac5f67e1f54ac6b6d41b3b4aba7e13)) 66 | * nil check ([18d3f16](https://github.com/phanen/fzf-lua-extra/commit/18d3f161166cfe989452aa5e45b4e2ac84e7188e)) 67 | * nil orig_pos ([91ac0c9](https://github.com/phanen/fzf-lua-extra/commit/91ac0c9f872ce380c29922bd56af327d012c1cd4)) 68 | * pad empty preview ([518df81](https://github.com/phanen/fzf-lua-extra/commit/518df8155b55d3e061e678a781c0cf29b82fb256)) 69 | * schedule_wrap ([c3e096f](https://github.com/phanen/fzf-lua-extra/commit/c3e096fb2255045d64c0e6869f1dd2e8b3af9c63)) 70 | * **serverlist:** refresh live server list when possilbe ([d84d0df](https://github.com/phanen/fzf-lua-extra/commit/d84d0df635ae928049ad877c8278d6caa921615c)) 71 | 72 | ## [3.5.0](https://github.com/phanen/fzf-lua-extra/compare/v3.4.0...v3.5.0) (2025-08-25) 73 | 74 | 75 | ### Features 76 | 77 | * **gitlog:** git log -Sx --grep=y ([ff552f4](https://github.com/phanen/fzf-lua-extra/commit/ff552f4583ffb7191b427027054bcd1f65759e7c)) 78 | * **repl:** init ([00f5cbd](https://github.com/phanen/fzf-lua-extra/commit/00f5cbd67f462e16af98d85f7f2ef65e4ffda98e)) 79 | * **serverlist:** init ([64bdde1](https://github.com/phanen/fzf-lua-extra/commit/64bdde16aef4b6233b97a23a97dd2d0070689bcf)) 80 | 81 | 82 | ### Bug Fixes 83 | 84 | * **aerial:** nil check in manpage outline ([5c1130a](https://github.com/phanen/fzf-lua-extra/commit/5c1130adf4b74f00af7a70bdcce9369dd3d9aa86)) 85 | * ctx ([7dd893c](https://github.com/phanen/fzf-lua-extra/commit/7dd893c93e8db7c887564ffc5d31d6ccb430c062)) 86 | * **gitlog:** preset git args ([9daacfe](https://github.com/phanen/fzf-lua-extra/commit/9daacfe1565751f21aad90e7201ba80fb425b299)) 87 | * **gitlog:** wrong bracketed paste.. ([9f942b1](https://github.com/phanen/fzf-lua-extra/commit/9f942b1207310056356d72a2bc0b91c666be4315)) 88 | * has ([7dd893c](https://github.com/phanen/fzf-lua-extra/commit/7dd893c93e8db7c887564ffc5d31d6ccb430c062)) 89 | * **upstream:** fzf_exec now shell.stringify all the contents ([93d5f60](https://github.com/phanen/fzf-lua-extra/commit/93d5f60f8f99ba370e12809790aee7a088aa7de3)) 90 | 91 | ## [3.4.0](https://github.com/phanen/fzf-lua-extra/compare/v3.3.0...v3.4.0) (2025-06-29) 92 | 93 | 94 | ### Features 95 | 96 | * add icons with extamrk ([8aaa897](https://github.com/phanen/fzf-lua-extra/commit/8aaa897c019524967bda7c3c94415f9ab66bddb1)) 97 | * **function:** init ([98917fa](https://github.com/phanen/fzf-lua-extra/commit/98917fa27320075ab2309a7cdda20591310357d1)) 98 | 99 | 100 | ### Bug Fixes 101 | 102 | * padding but looks weird in kitty ([709fa6c](https://github.com/phanen/fzf-lua-extra/commit/709fa6c135527ffb4de71d7a83b9e0e782c3ee38)) 103 | 104 | ## [3.3.0](https://github.com/phanen/fzf-lua-extra/compare/v3.2.0...v3.3.0) (2025-06-06) 105 | 106 | 107 | ### Features 108 | 109 | * register `:FzfLua {cmd}` ([1b54637](https://github.com/phanen/fzf-lua-extra/commit/1b54637debf86b5959703e4c9dc8927f18d366d0)) 110 | * **swiper:** init ([7e1066d](https://github.com/phanen/fzf-lua-extra/commit/7e1066d7128c8e6ac085aa2fe2c97bf6814f378e)) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * handle lazy load ([47a9103](https://github.com/phanen/fzf-lua-extra/commit/47a9103435f983215d98c74bf5438db53bcf2f64)) 116 | * **icons:** 😿 when at the begin of line ([d6eaab8](https://github.com/phanen/fzf-lua-extra/commit/d6eaab8280ef2f9cfccc770ab0b126491c8b5e6f)) 117 | * **icons:** relative to cursor ([4582e31](https://github.com/phanen/fzf-lua-extra/commit/4582e31166cadb3250597253c7869a30b0a9e567)) 118 | * **swiper:** force default flags ([265329c](https://github.com/phanen/fzf-lua-extra/commit/265329c066cfd8003d2acec11f3a1ebe8b4d8cb0)) 119 | 120 | ## [3.2.0](https://github.com/phanen/fzf-lua-extra/compare/v3.1.1...v3.2.0) (2025-05-27) 121 | 122 | 123 | ### Features 124 | 125 | * cliphist ([fae3e57](https://github.com/phanen/fzf-lua-extra/commit/fae3e57b516a18aa7436c89a3b77732f072b01eb)) 126 | * **grep_project_globs:** init ([b9ff6d6](https://github.com/phanen/fzf-lua-extra/commit/b9ff6d650e77bef3812520e61b15d1e2e61765a1)) 127 | * **icons:** force update cache ([353c5ff](https://github.com/phanen/fzf-lua-extra/commit/353c5ff9b52aeac195848e34333db7b6e992b771)) 128 | * nerd icons, emojis ([f9bb5d3](https://github.com/phanen/fzf-lua-extra/commit/f9bb5d30f9284cd2dbd600e235ff81c0957c5c7c)) 129 | * **node_lines:** init ([13a2a07](https://github.com/phanen/fzf-lua-extra/commit/13a2a07d18dadb3895422f787f3299b876364428)) 130 | * **plocate:** init ([1e9650a](https://github.com/phanen/fzf-lua-extra/commit/1e9650a76bce51315b2be9eb0979bab86eeefdfc)) 131 | * **ps:** colorize columns ([6480de0](https://github.com/phanen/fzf-lua-extra/commit/6480de0ac90882411d2455833c87689046a1b2f7)) 132 | * **runtime:** runtime file ([77c6bc0](https://github.com/phanen/fzf-lua-extra/commit/77c6bc0d6876e5fa75888ec9d326b4743655f32d)) 133 | * use mini.visits ([3a311f5](https://github.com/phanen/fzf-lua-extra/commit/3a311f5ad795289d6187d95d433c4e60d39a22ed)) 134 | 135 | 136 | ### Bug Fixes 137 | 138 | * **aerial:** when id >= 10 ([81a9b1d](https://github.com/phanen/fzf-lua-extra/commit/81a9b1d458ebf8e57c2a1beced9249fd31d63765)) 139 | * **icons:** 😼😼😼 ([b04c97e](https://github.com/phanen/fzf-lua-extra/commit/b04c97e957f0caf8432e74aabd24b8c64deda8ea)) 140 | * **icons:** byte col ([7aa5421](https://github.com/phanen/fzf-lua-extra/commit/7aa542120f9a3d7595c84425e7b2eaf68875c382)) 141 | * **icons:** insert mode 🐱😾😺 ([19bc4a8](https://github.com/phanen/fzf-lua-extra/commit/19bc4a86914e955fe0d65e616f200192334b3ab8)) 142 | 143 | ## [3.1.1](https://github.com/phanen/fzf-lua-extra/compare/v3.1.0...v3.1.1) (2025-04-13) 144 | 145 | 146 | ### Bug Fixes 147 | 148 | * **ps:** wrong whitespace delimiter for click-header ([cf3e8fd](https://github.com/phanen/fzf-lua-extra/commit/cf3e8fd9975481b388735b0e2280ffa835260433)) 149 | 150 | ## [3.1.0](https://github.com/phanen/fzf-lua-extra/compare/v3.0.0...v3.1.0) (2025-04-13) 151 | 152 | 153 | ### Features 154 | 155 | * **aerial:** add symbols picker ([230d56c](https://github.com/phanen/fzf-lua-extra/commit/230d56c01f0207acb6f3cd6f2e644552f47ea2c9)) 156 | * make all pickers reloadable ([8c18dc4](https://github.com/phanen/fzf-lua-extra/commit/8c18dc482ee814186885aa3db79013791f5d31ce)) 157 | * process picker/inspector ([760e5fe](https://github.com/phanen/fzf-lua-extra/commit/760e5fee9457382e90c7855a6b888c51ff838750)) 158 | 159 | 160 | ### Bug Fixes 161 | 162 | * expand env variable by `_fmt.from` ([14d8ed4](https://github.com/phanen/fzf-lua-extra/commit/14d8ed45afa03b00de513ae47b1551dc1ab6a96c)) 163 | * fzf_exec api would override info ([3b3ccb0](https://github.com/phanen/fzf-lua-extra/commit/3b3ccb081f0acf7cc24f79f2e8071cada71f89c3)) 164 | * upsteram change '' to nil when no selected ([1856d69](https://github.com/phanen/fzf-lua-extra/commit/1856d697c99344cac45018584a4dd8f9eb2af946)) 165 | 166 | ## [3.0.0](https://github.com/phanen/fzf-lua-extra/compare/v2.4.0...v3.0.0) (2025-01-16) 167 | 168 | 169 | ### ⚠ BREAKING CHANGES 170 | 171 | * remove overlay, plugins renaming 172 | 173 | ### Bug Fixes 174 | 175 | * curbuf is not fzf term ([b7cdd2b](https://github.com/phanen/fzf-lua-extra/commit/b7cdd2b9669daa1e35d06a4dc0bacdb16a675aba)) 176 | * legacy showcase ([a4419d8](https://github.com/phanen/fzf-lua-extra/commit/a4419d81dfe485157a71e9f600ed86e281902469)) 177 | * upstream renamed ([f9d073d](https://github.com/phanen/fzf-lua-extra/commit/f9d073da50fba971c2638122b31990510c1e7368)) 178 | * with `ex_run_cr` already ([47a9f23](https://github.com/phanen/fzf-lua-extra/commit/47a9f23c187c0e58e009168193dce49011dae2f2)) 179 | 180 | 181 | ### Code Refactoring 182 | 183 | * remove overlay, plugins renaming ([8b0cf45](https://github.com/phanen/fzf-lua-extra/commit/8b0cf4547fad8e209d654e0f94b7fc4bdb528441)) 184 | 185 | ## [2.4.0](https://github.com/phanen/fzf-lua-overlay/compare/v2.3.2...v2.4.0) (2024-10-21) 186 | 187 | 188 | ### Features 189 | 190 | * **action:** ex_run no confirm ([929a98a](https://github.com/phanen/fzf-lua-overlay/commit/929a98a4a32a240af75f62075d3bfcf5c9c6a4e4)) 191 | * **builtin:** inject by `extends_builtin` ([6423ad7](https://github.com/phanen/fzf-lua-overlay/commit/6423ad7dadc47ef46cc29abd596be72e61bd0fef)) 192 | * make opts optional ([5e57a41](https://github.com/phanen/fzf-lua-overlay/commit/5e57a4138889b96603c0e22e66d25c4e88f71d51)) 193 | 194 | ## [2.3.2](https://github.com/phanen/fzf-lua-overlay/compare/v2.3.1...v2.3.2) (2024-09-26) 195 | 196 | 197 | ### Bug Fixes 198 | 199 | * when `__recent_hlist` is nil ([e9554d0](https://github.com/phanen/fzf-lua-overlay/commit/e9554d0bee07fef35192c5fe04806eeae15cf477)) 200 | 201 | ## [2.3.1](https://github.com/phanen/fzf-lua-overlay/compare/v2.3.0...v2.3.1) (2024-09-24) 202 | 203 | 204 | ### Bug Fixes 205 | 206 | * typos ([f6dea82](https://github.com/phanen/fzf-lua-overlay/commit/f6dea82ed4c3ec742595c19620e105dada64f4a5)) 207 | 208 | ## [2.3.0](https://github.com/phanen/fzf-lua-overlay/compare/v2.2.0...v2.3.0) (2024-09-24) 209 | 210 | 211 | ### Features 212 | 213 | * mimic builtin ([1bcc93d](https://github.com/phanen/fzf-lua-overlay/commit/1bcc93dfb7bae776f8f6804e12c94ef766f04122)) 214 | 215 | 216 | ### Bug Fixes 217 | 218 | * drop hashlist init ([e4970f9](https://github.com/phanen/fzf-lua-overlay/commit/e4970f92b763c88bb65e80f8da7a84d12c83d423)) 219 | 220 | ## [2.2.0](https://github.com/phanen/fzf-lua-overlay/compare/v2.1.0...v2.2.0) (2024-09-17) 221 | 222 | 223 | ### Features 224 | 225 | * ls, bcommits ([d2e47b3](https://github.com/phanen/fzf-lua-overlay/commit/d2e47b396bfccd3e2b3618adca915dc84982804d)) 226 | 227 | 228 | ### Bug Fixes 229 | 230 | * async preview ([d86cf8e](https://github.com/phanen/fzf-lua-overlay/commit/d86cf8e877a31683494885b3b402af4a60f81375)) 231 | * correct inherit ([532effd](https://github.com/phanen/fzf-lua-overlay/commit/532effdf24b309306d833e2333ab7b7490f5f30b)) 232 | * decorate with md syntax ([b3b7869](https://github.com/phanen/fzf-lua-overlay/commit/b3b78690f7319b411da82004d139c8c30313c841)) 233 | * don't pass query ([ef2c5ef](https://github.com/phanen/fzf-lua-overlay/commit/ef2c5efef92f7219117bd2b1a699782c3999f18f)) 234 | * force create dir ([007e55a](https://github.com/phanen/fzf-lua-overlay/commit/007e55acf6cf0ab7679ce057655411c197fd5406)) 235 | * lazy loading ([71849a9](https://github.com/phanen/fzf-lua-overlay/commit/71849a99a8933991c616b3eae6f058665280a62e)) 236 | * multiplex ([de221b4](https://github.com/phanen/fzf-lua-overlay/commit/de221b48e86027ae81d666b412c54c851fe35daf)) 237 | * no fzf-lua deps in utils ([148808a](https://github.com/phanen/fzf-lua-overlay/commit/148808ac0bcf8114283109016e162b73ac4f73e1)) 238 | * not resume after enter (regression) ([7bcab42](https://github.com/phanen/fzf-lua-overlay/commit/7bcab42d273b5e145b48063b1ae5ba3281ac0ace)) 239 | * **recent:** buf/closed/shada ([4a1c757](https://github.com/phanen/fzf-lua-overlay/commit/4a1c75785ccc748c60a99b4a1affca476bdcf67e)) 240 | * remove state file ([53beb83](https://github.com/phanen/fzf-lua-overlay/commit/53beb837b9fdcaa187dba2c954a7ede8118d4773)) 241 | * typing ([6b4336d](https://github.com/phanen/fzf-lua-overlay/commit/6b4336d11b58701912fa99280608b2943a8d5625)) 242 | * typo ([5c6bc69](https://github.com/phanen/fzf-lua-overlay/commit/5c6bc6997e1788a09f7cda48a9e68c3eaa9a286b)) 243 | * when no match ([ef9d906](https://github.com/phanen/fzf-lua-overlay/commit/ef9d906e056d7d3f7fb5e287fc69643a69ea6b9b)) 244 | * workaroud for some potential circle require ([abceb7f](https://github.com/phanen/fzf-lua-overlay/commit/abceb7f393cfe013ae3d0f5b5d8eb7bce434ee95)) 245 | 246 | ## [2.1.1](https://github.com/phanen/fzf-lua-overlay/compare/v2.1.0...v2.1.1) (2024-09-01) 247 | 248 | 249 | ### Bug Fixes 250 | 251 | * lazy loading ([71849a9](https://github.com/phanen/fzf-lua-overlay/commit/71849a99a8933991c616b3eae6f058665280a62e)) 252 | 253 | ## [2.1.0](https://github.com/phanen/fzf-lua-overlay/compare/v2.0.0...v2.1.0) (2024-08-14) 254 | 255 | 256 | ### Features 257 | 258 | * colorful rtp (wip: previewer) ([df66d72](https://github.com/phanen/fzf-lua-overlay/commit/df66d723eb47ff441131eaccdcabb960955677c2)) 259 | * **scriptnames:** add file icons ([d72fb8d](https://github.com/phanen/fzf-lua-overlay/commit/d72fb8d03faf75832606cfa60d1f4a46828c3db9)) 260 | * use `opt_name` to inhert config from fzf-lua ([64fb0ab](https://github.com/phanen/fzf-lua-overlay/commit/64fb0abc780434e42868e9c45a1d4f3ab30bb061)) 261 | 262 | 263 | ### Bug Fixes 264 | 265 | * handle icons in oldfiles ([64fb0ab](https://github.com/phanen/fzf-lua-overlay/commit/64fb0abc780434e42868e9c45a1d4f3ab30bb061)) 266 | * remove nonsense (override by top `prompt = false`) ([94f8f95](https://github.com/phanen/fzf-lua-overlay/commit/94f8f952aff42a7e4988b679cf423b715430a0dd)) 267 | 268 | ## [2.0.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.11.0...v2.0.0) (2024-08-01) 269 | 270 | 271 | ### ⚠ BREAKING CHANGES 272 | 273 | * bug fixes 274 | 275 | ### release 276 | 277 | * bug fixes ([80984eb](https://github.com/phanen/fzf-lua-overlay/commit/80984ebec5eb3557b1c849b362bdf26c430227cd)) 278 | 279 | 280 | ### Bug Fixes 281 | 282 | * **json:** tbl or str ([7ce78c3](https://github.com/phanen/fzf-lua-overlay/commit/7ce78c359e5010438dd29682af9928e1c51dcd8c)) 283 | * log on api limited ([105bcdb](https://github.com/phanen/fzf-lua-overlay/commit/105bcdbab1d48d5cc1668e7a84663db80e168a3f)) 284 | 285 | ## [1.11.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.10.0...v1.11.0) (2024-05-21) 286 | 287 | 288 | ### Features 289 | 290 | * a new action to open file in background ([0b9d69c](https://github.com/phanen/fzf-lua-overlay/commit/0b9d69c2c58babf16d8fe9b1c2f720b4599c52ef)) 291 | * cache plugin lists ([b1232b2](https://github.com/phanen/fzf-lua-overlay/commit/b1232b2c084734d72c8f801cd9d9c51cbe3f3a71)) 292 | * plugins do ([9383e8d](https://github.com/phanen/fzf-lua-overlay/commit/9383e8d6b3c789e922990bb08e34f6ec31373e7e)) 293 | 294 | 295 | ### Bug Fixes 296 | 297 | * annoy repeat ([24cfd1d](https://github.com/phanen/fzf-lua-overlay/commit/24cfd1ddb4235caeaf46a64985ecce7a4187a478)) 298 | * disable ui then bulk edit ([8ecd1d5](https://github.com/phanen/fzf-lua-overlay/commit/8ecd1d52bc0452dfaf7dc60d1e87a89d0d1090c8)) 299 | * passtrough resume query ([309c517](https://github.com/phanen/fzf-lua-overlay/commit/309c51757757f428961ea089028d6b54eaed6513)) 300 | * typos ([9383e8d](https://github.com/phanen/fzf-lua-overlay/commit/9383e8d6b3c789e922990bb08e34f6ec31373e7e)) 301 | 302 | ## [1.10.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.9.0...v1.10.0) (2024-05-05) 303 | 304 | 305 | ### Features 306 | 307 | * show all plugins and better actions fallback ([c06d639](https://github.com/phanen/fzf-lua-overlay/commit/c06d639492adc3a1208063b83e7d8bb9b3285013)) 308 | 309 | ## [1.9.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.8.2...v1.9.0) (2024-05-05) 310 | 311 | 312 | ### Features 313 | 314 | * add todos in notes query ([777c4fd](https://github.com/phanen/fzf-lua-overlay/commit/777c4fda6cefe034ffa425acee8a2fef0a07e737)) 315 | * vscode-like display for dotfiles ([6383536](https://github.com/phanen/fzf-lua-overlay/commit/6383536474db95bcb58f132569940b40164b21c8)) 316 | * zoxide delete path ([a3b5a00](https://github.com/phanen/fzf-lua-overlay/commit/a3b5a00940424ed636d33d53326adb2a4c5b4b32)) 317 | 318 | ## [1.8.2](https://github.com/phanen/fzf-lua-overlay/compare/v1.8.1...v1.8.2) (2024-04-27) 319 | 320 | 321 | ### Bug Fixes 322 | 323 | * avoid spam ([469e0f1](https://github.com/phanen/fzf-lua-overlay/commit/469e0f1cc4e89171f5fd334d820e937ddbe2a5c9)) 324 | 325 | ## [1.8.1](https://github.com/phanen/fzf-lua-overlay/compare/v1.8.0...v1.8.1) (2024-04-21) 326 | 327 | 328 | ### Bug Fixes 329 | 330 | * write nil should create file ([08404cd](https://github.com/phanen/fzf-lua-overlay/commit/08404cd310d8d022cc775bfc368651a0d0e56fcd)) 331 | 332 | ## [1.8.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.7.0...v1.8.0) (2024-04-19) 333 | 334 | 335 | ### Features 336 | 337 | * allow other exts ([23319f9](https://github.com/phanen/fzf-lua-overlay/commit/23319f9abd7d95b91db8bf967800f40d56baf74c)) 338 | * multiple dirs ([c75d1f3](https://github.com/phanen/fzf-lua-overlay/commit/c75d1f353f58ed0f23ecd68a5128e4830743773b)) 339 | * passthrough opts ([a9e0656](https://github.com/phanen/fzf-lua-overlay/commit/a9e0656a58c23c53b21c3b735930e2d6804f5f91)) 340 | * show README if exist for lazy plugins ([9beb358](https://github.com/phanen/fzf-lua-overlay/commit/9beb35861fcc1c566e1acd24da021dceaef0ebb8)) 341 | * todos manager ([6c476e4](https://github.com/phanen/fzf-lua-overlay/commit/6c476e48fef78162d5ec8e9738a3d0756da88329)) 342 | * toggle between find/grep ([c75d1f3](https://github.com/phanen/fzf-lua-overlay/commit/c75d1f353f58ed0f23ecd68a5128e4830743773b)) 343 | 344 | 345 | ### Bug Fixes 346 | 347 | * correct way to get last query ([6c476e4](https://github.com/phanen/fzf-lua-overlay/commit/6c476e48fef78162d5ec8e9738a3d0756da88329)) 348 | * nil actions ([6c476e4](https://github.com/phanen/fzf-lua-overlay/commit/6c476e48fef78162d5ec8e9738a3d0756da88329)) 349 | * revert git buf local opts ([c18aee1](https://github.com/phanen/fzf-lua-overlay/commit/c18aee1034ae2a35639a1f8743c017082f5f14ef)) 350 | * typos ([c18aee1](https://github.com/phanen/fzf-lua-overlay/commit/c18aee1034ae2a35639a1f8743c017082f5f14ef)) 351 | 352 | ## [1.7.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.6.0...v1.7.0) (2024-04-15) 353 | 354 | 355 | ### Features 356 | 357 | * add toggle author actions for lazy.nvim ([41ba5ea](https://github.com/phanen/fzf-lua-overlay/commit/41ba5ea15424eace25e0dec9bfa8b7a819a063c2)) 358 | * inject default-title style ([8197f62](https://github.com/phanen/fzf-lua-overlay/commit/8197f62071b8c21ada17455a751e96b7b9041075)) 359 | * prefer buf's root for `git*` picker ([84e2260](https://github.com/phanen/fzf-lua-overlay/commit/84e226012903e154390e5adfdd0ed7c3ca0c453f)) 360 | 361 | 362 | ### Bug Fixes 363 | 364 | * parse generic url when missing plugin name ([5289af9](https://github.com/phanen/fzf-lua-overlay/commit/5289af9afee10de49b09d84b69e00b7f2fb793db)) 365 | * shebang ([ee879a9](https://github.com/phanen/fzf-lua-overlay/commit/ee879a9a8208914b534155632f5ad2db169b59bf)) 366 | 367 | ## [1.6.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.5.1...v1.6.0) (2024-04-09) 368 | 369 | 370 | ### Features 371 | 372 | * use lru for recent closed files ([772a858](https://github.com/phanen/fzf-lua-overlay/commit/772a858e364304a60ce47cff0c353e5419febd45)) 373 | 374 | ## [1.5.1](https://github.com/phanen/fzf-lua-overlay/compare/v1.5.0...v1.5.1) (2024-03-31) 375 | 376 | 377 | ### Bug Fixes 378 | 379 | * santinize ([94d97a4](https://github.com/phanen/fzf-lua-overlay/commit/94d97a44252a15d440bd9d1c8b323faf9065c5d7)) 380 | 381 | ## [1.5.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.4.0...v1.5.0) (2024-03-31) 382 | 383 | 384 | ### Features 385 | 386 | * add recentfiles picker ([0bf7165](https://github.com/phanen/fzf-lua-overlay/commit/0bf7165601575c780c77c7c97101df4d92855930)) 387 | * don't show opened buffers as entry ([270c558](https://github.com/phanen/fzf-lua-overlay/commit/270c558a0d1e74f60771fa8f5f90bba92622b9be)) 388 | * gitignore picker ([a21a9e7](https://github.com/phanen/fzf-lua-overlay/commit/a21a9e7165b2df1213c6c6779dedfea506df2ad5)) 389 | * license picker ([edf4c10](https://github.com/phanen/fzf-lua-overlay/commit/edf4c10ac84093f0689ffeab93a3ef39cbce5fd8)) 390 | * optional commands ([f76b6f5](https://github.com/phanen/fzf-lua-overlay/commit/f76b6f583133876a7bb13f88eba4596f79f4206c)) 391 | * reload plugins ([764eb7d](https://github.com/phanen/fzf-lua-overlay/commit/764eb7d6ddb119ae1413f78e4765c6241a76fc24)) 392 | 393 | 394 | ### Bug Fixes 395 | 396 | * disable custom global ([b1b3d39](https://github.com/phanen/fzf-lua-overlay/commit/b1b3d39a4663b6edc270012bb1d928155ed0ef02)) 397 | * error path ([cde0f95](https://github.com/phanen/fzf-lua-overlay/commit/cde0f95b87f3516189c4485337ea2adaf4f36565)) 398 | * missing actions again... ([106fb79](https://github.com/phanen/fzf-lua-overlay/commit/106fb799146f0073828776644d748e4ceb15bfd1)) 399 | * store abs path ([db567dd](https://github.com/phanen/fzf-lua-overlay/commit/db567dd82cee72e541a387ae045f098464600854)) 400 | 401 | ## [1.4.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.3.0...v1.4.0) (2024-03-29) 402 | 403 | 404 | ### Features 405 | 406 | * add actions ([5a4872c](https://github.com/phanen/fzf-lua-overlay/commit/5a4872c02c613bf0daef9acc656bb332593204ba)) 407 | 408 | 409 | ### Bug Fixes 410 | 411 | * open in browser ([14c545c](https://github.com/phanen/fzf-lua-overlay/commit/14c545c565b71fb78982dff36146b7adba789c84)) 412 | 413 | ## [1.3.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.2.0...v1.3.0) (2024-03-29) 414 | 415 | 416 | ### Features 417 | 418 | * custom dot_dir ([2a80a11](https://github.com/phanen/fzf-lua-overlay/commit/2a80a11e5570f30678b3c80434fc6046cfc0b7b3)) 419 | * use setup opts in fzf_exec ([d8f2d0a](https://github.com/phanen/fzf-lua-overlay/commit/d8f2d0a6ed0ff113b8d5170f4e6113c7266e7854)) 420 | 421 | 422 | ### Bug Fixes 423 | 424 | * path ([6e287fe](https://github.com/phanen/fzf-lua-overlay/commit/6e287fe310685ba2de64a83d10b978e678e0f9c5)) 425 | * previewer for plugins ([624eb7d](https://github.com/phanen/fzf-lua-overlay/commit/624eb7ddd2184686edc6e3a38e634d55ae57fda4)) 426 | 427 | ## [1.2.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.1.0...v1.2.0) (2024-03-25) 428 | 429 | 430 | ### Features 431 | 432 | * add rtp picker ([3580913](https://github.com/phanen/fzf-lua-overlay/commit/3580913fd9db8a9d54961862ed6c879670df9532)) 433 | * picker for scriptnames ([9d7f842](https://github.com/phanen/fzf-lua-overlay/commit/9d7f842e4c28c2b8c6464cd57f06e6cd93ddbafc)) 434 | 435 | 436 | ### Bug Fixes 437 | 438 | * missing actions ([6b7f108](https://github.com/phanen/fzf-lua-overlay/commit/6b7f108abad3dcc91ce101053d12c6d575fdace7)) 439 | 440 | ## [1.1.0](https://github.com/phanen/fzf-lua-overlay/compare/v1.0.0...v1.1.0) (2024-03-21) 441 | 442 | 443 | ### Features 444 | 445 | * init config ([6c43699](https://github.com/phanen/fzf-lua-overlay/commit/6c43699e1bdd5416c26d3bb2afc0186bde8b2946)) 446 | * preview dirent ([8a7d2e3](https://github.com/phanen/fzf-lua-overlay/commit/8a7d2e3d84d9e341beb06a703944b35fa37df8b8)) 447 | 448 | 449 | ### Bug Fixes 450 | 451 | * cd to plugins dir ([8a7d2e3](https://github.com/phanen/fzf-lua-overlay/commit/8a7d2e3d84d9e341beb06a703944b35fa37df8b8)) 452 | 453 | ## 1.0.0 (2024-03-18) 454 | 455 | 456 | ### Features 457 | 458 | * init config ([6c43699](https://github.com/phanen/fzf-lua-overlay/commit/6c43699e1bdd5416c26d3bb2afc0186bde8b2946)) 459 | --------------------------------------------------------------------------------