You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

485 lines
13 KiB
Lua

local utils = require "fzf-lua.utils"
local path = require "fzf-lua.path"
3 years ago
local M = {}
-- default action map key
local _default_action = "default"
3 years ago
-- return fzf '--expect=' string from actions keyval tbl
M.expect = function(actions)
if not actions then return nil end
3 years ago
local keys = {}
for k, v in pairs(actions) do
if k ~= _default_action and v ~= false then
3 years ago
table.insert(keys, k)
end
end
if #keys > 0 then
return string.format("--expect=%s", table.concat(keys, ','))
3 years ago
end
return nil
end
M.normalize_selected = function(actions, selected)
-- 1. If there are no additional actions but the default
-- the selected table will contain the selected item(s)
-- 2. If multiple actions where defined the first item
-- will contain the action keybind string
--
-- The below makes separates the keybind from the item(s)
-- and makes sure 'selected' contains only items or {}
-- so it can always be enumerated safely
if not actions or not selected then return end
local action = _default_action
if utils.tbl_length(actions)>1 then
-- keybind should be in item #1
-- default keybind is an empty string
-- so we leave that as "default"
if #selected[1] > 0 then
action = selected[1]
end
-- entries are items #2+
local entries = {}
for i = 2, #selected do
table.insert(entries, selected[i])
end
return action, entries
else
return action, selected
end
end
M.act = function(actions, selected, opts)
if not actions or not selected then return end
local keybind, entries = M.normalize_selected(actions, selected)
local action = actions[keybind]
if type(action) == 'table' then
for _, f in ipairs(action) do
f(entries, opts)
end
elseif type(action) == 'function' then
action(entries, opts)
elseif type(action) == 'string' then
vim.cmd(action)
else
utils.warn(("unsupported action: '%s', type:%s")
:format(action, type(action)))
3 years ago
end
end
M.resume = function(_, _)
-- must call via vim.cmd or we create
-- circular 'require'
-- TODO: is this really a big deal?
vim.cmd("lua require'fzf-lua'.resume()")
end
3 years ago
M.vimcmd = function(vimcmd, selected)
for i = 1, #selected do
3 years ago
vim.cmd(vimcmd .. " " .. vim.fn.fnameescape(selected[i]))
end
end
M.vimcmd_file = function(vimcmd, selected, opts)
local curbuf = vim.api.nvim_buf_get_name(0)
for i = 1, #selected do
local entry = path.entry_to_file(selected[i])
entry.ctag = path.entry_to_ctag(selected[i])
-- Java LSP entries, 'jdt://...'
if entry.uri then
vim.cmd("normal! m`")
vim.lsp.util.jump_to_location(entry)
vim.cmd("norm! zvzz")
else
-- only change buffer if we need to (issue #122)
local fullpath = entry.path
if not path.starts_with_separator(fullpath) then
fullpath = path.join({opts.cwd or vim.loop.cwd(), fullpath})
end
if vimcmd == 'e' and curbuf ~= fullpath
and not vim.o.hidden and
utils.buffer_is_dirty(nil, true) then
-- warn the user when trying to switch from a dirty buffer
-- when `:set nohidden`
return
end
-- add current location to jumplist
vim.cmd("normal! m`")
if vimcmd ~= "e" or curbuf ~= fullpath then
vim.cmd(vimcmd .. " " .. vim.fn.fnameescape(entry.path))
end
if entry.ctag or entry.line>1 or entry.col>1 then
if entry.line>1 or entry.col>1 then
vim.api.nvim_win_set_cursor(0, {tonumber(entry.line), tonumber(entry.col)-1})
else
vim.api.nvim_win_set_cursor(0, {1, 0})
vim.fn.search(entry.ctag, "W")
end
vim.cmd("norm! zvzz")
end
end
3 years ago
end
end
-- file actions
M.file_edit = function(selected, opts)
3 years ago
local vimcmd = "e"
M.vimcmd_file(vimcmd, selected, opts)
3 years ago
end
M.file_split = function(selected, opts)
3 years ago
local vimcmd = "new"
M.vimcmd_file(vimcmd, selected, opts)
3 years ago
end
M.file_vsplit = function(selected, opts)
3 years ago
local vimcmd = "vnew"
M.vimcmd_file(vimcmd, selected, opts)
3 years ago
end
M.file_tabedit = function(selected, opts)
local vimcmd = "tabnew"
M.vimcmd_file(vimcmd, selected, opts)
3 years ago
end
M.file_open_in_background = function(selected, opts)
local vimcmd = "badd"
M.vimcmd_file(vimcmd, selected, opts)
end
M.file_sel_to_qf = function(selected, _)
3 years ago
local qf_list = {}
for i = 1, #selected do
local file = path.entry_to_file(selected[i])
local text = selected[i]:match(":%d+:(.*)$")
table.insert(qf_list, {
filename = file.path,
lnum = file.line,
col = file.col,
text = text,
})
3 years ago
end
vim.fn.setqflist(qf_list)
vim.cmd 'copen'
end
M.file_edit_or_qf = function(selected, opts)
if #selected>1 then
return M.file_sel_to_qf(selected, opts)
else
return M.file_edit(selected, opts)
end
end
3 years ago
-- buffer actions
M.vimcmd_buf = function(vimcmd, selected, _)
for i = 1, #selected do
local bufnr = string.match(selected[i], "%[(%d+)")
if bufnr then
if vimcmd == 'b'
and not vim.o.hidden and
utils.buffer_is_dirty(nil, true) then
-- warn the user when trying to switch from a dirty buffer
-- when `:set nohidden`
return
end
local cmd = vimcmd .. " " .. bufnr
local ok, res = pcall(vim.cmd, cmd)
if not ok then
utils.warn(("':%s' failed: %s"):format(cmd, res))
end
end
end
end
M.buf_edit = function(selected, opts)
3 years ago
local vimcmd = "b"
M.vimcmd_buf(vimcmd, selected, opts)
3 years ago
end
M.buf_split = function(selected, opts)
3 years ago
local vimcmd = "split | b"
M.vimcmd_buf(vimcmd, selected, opts)
3 years ago
end
M.buf_vsplit = function(selected, opts)
3 years ago
local vimcmd = "vertical split | b"
M.vimcmd_buf(vimcmd, selected, opts)
3 years ago
end
M.buf_tabedit = function(selected, opts)
3 years ago
local vimcmd = "tab split | b"
M.vimcmd_buf(vimcmd, selected, opts)
3 years ago
end
M.buf_del = function(selected, opts)
3 years ago
local vimcmd = "bd"
local bufnrs = vim.tbl_filter(function(line)
local b = tonumber(line:match("%[(%d+)"))
return not utils.buffer_is_dirty(b, true)
end, selected)
M.vimcmd_buf(vimcmd, bufnrs, opts)
end
M.buf_switch = function(selected, _)
local tabnr = selected[1]:match("(%d+)%)")
if tabnr then
vim.cmd("tabn " .. tabnr)
else
tabnr = vim.api.nvim_win_get_tabpage(0)
end
local bufnr = tonumber(string.match(selected[1], "%[(%d+)"))
if bufnr then
local winid = utils.winid_from_tab_buf(tabnr, bufnr)
if winid then vim.api.nvim_set_current_win(winid) end
end
3 years ago
end
M.colorscheme = function(selected)
local colorscheme = selected[1]
vim.cmd("colorscheme " .. colorscheme)
end
M.run_builtin = function(selected)
local method = selected[1]
vim.cmd(string.format("lua require'fzf-lua'.%s()", method))
-- not sure what is causing this, tested with
-- 'NVIM v0.6.0-dev+575-g2ef9d2a66'
-- vim.cmd("startinsert") doesn't start INSERT mode
-- 'mode' returns { blocking = false, mode = "t" }
-- manually input 'i' seems to workaround this issue
-- **only if fzf term window was succefully opened (#235)
-- this is only required after the 'nt' (normal-terminal)
-- mode was introduced along with the 'ModeChanged' event
-- https://github.com/neovim/neovim/pull/15878
-- https://github.com/neovim/neovim/pull/15840
local has_mode_nt = not vim.tbl_isempty(
vim.fn.getcompletion('ModeChanged', 'event'))
or vim.fn.has('nvim-0.6') == 1
if has_mode_nt then
local mode = vim.api.nvim_get_mode()
local wininfo = vim.fn.getwininfo(vim.api.nvim_get_current_win())[1]
if vim.bo.ft == 'fzf'
and wininfo.terminal == 1
and mode and mode.mode == 't' then
vim.cmd[[noautocmd lua vim.api.nvim_feedkeys('i', 'n', true)]]
end
end
end
M.ex_run = function(selected)
local cmd = selected[1]
vim.cmd("stopinsert")
vim.fn.feedkeys(string.format(":%s", cmd), "n")
return cmd
end
M.ex_run_cr = function(selected)
local cmd = M.ex_run(selected)
utils.feed_keys_termcodes("<CR>")
vim.fn.histadd("cmd", cmd)
end
M.search = function(selected)
local query = selected[1]
vim.cmd("stopinsert")
vim.fn.feedkeys(string.format("/%s", query), "n")
return query
end
M.search_cr = function(selected)
local query = M.search(selected)
utils.feed_keys_termcodes("<CR>")
vim.fn.histadd("search", query)
end
3 years ago
M.goto_mark = function(selected)
local mark = selected[1]
mark = mark:match("[^ ]+")
vim.cmd("stopinsert")
vim.cmd("normal! '" .. mark)
-- vim.fn.feedkeys(string.format("'%s", mark))
end
M.goto_jump = function(selected, opts)
if opts.jump_using_norm then
local jump, _, _, _ = selected[1]:match("(%d+)%s+(%d+)%s+(%d+)%s+(.*)")
if tonumber(jump) then
vim.cmd(("normal! %d"):format(jump))
end
else
local _, lnum, col, filepath = selected[1]:match("(%d+)%s+(%d+)%s+(%d+)%s+(.*)")
local ok, res = pcall(vim.fn.expand, filepath)
if not ok then filepath = ''
else filepath = res end
if not filepath or not vim.loop.fs_stat(filepath) then
-- no accessible file
-- jump is in current
filepath = vim.api.nvim_buf_get_name(0)
end
local entry = ("%s:%d:%d:"):format(filepath, tonumber(lnum), tonumber(col)+1)
M.file_edit({ entry }, opts)
end
end
M.spell_apply = function(selected)
local word = selected[1]
vim.cmd("normal! ciw" .. word)
vim.cmd("stopinsert")
3 years ago
end
M.set_filetype = function(selected)
vim.api.nvim_buf_set_option(0, 'filetype', selected[1])
end
M.packadd = function(selected)
for i = 1, #selected do
vim.cmd("packadd " .. selected[i])
end
end
3 years ago
M.help = function(selected)
local vimcmd = "help"
M.vimcmd(vimcmd, selected)
end
M.help_vert = function(selected)
local vimcmd = "vert help"
M.vimcmd(vimcmd, selected)
end
M.help_tab = function(selected)
local vimcmd = "tab help"
M.vimcmd(vimcmd, selected)
end
M.man = function(selected)
local vimcmd = "Man"
M.vimcmd(vimcmd, selected)
end
M.man_vert = function(selected)
local vimcmd = "vert Man"
M.vimcmd(vimcmd, selected)
end
M.man_tab = function(selected)
local vimcmd = "tab Man"
M.vimcmd(vimcmd, selected)
end
M.git_switch = function(selected, opts)
local cmd = path.git_cwd({"git", "checkout"}, opts.cwd)
local git_ver = utils.git_version()
-- git switch was added with git version 2.23
if git_ver and git_ver >= 2.23 then
cmd = path.git_cwd({"git", "switch"}, opts.cwd)
end
-- remove anything past space
local branch = selected[1]:match("[^ ]+")
-- do nothing for active branch
if branch:find("%*") ~= nil then return end
if branch:find("^remotes/") then
table.insert(cmd, "--detach")
end
table.insert(cmd, branch)
local output = utils.io_systemlist(cmd)
if utils.shell_error() then
utils.err(unpack(output))
else
utils.info(unpack(output))
vim.cmd("edit!")
end
end
M.git_checkout = function(selected, opts)
local cmd_checkout = path.git_cwd({"git", "checkout"}, opts.cwd)
local cmd_cur_commit = path.git_cwd({"git", "rev-parse", "--short HEAD"}, opts.cwd)
local commit_hash = selected[1]:match("[^ ]+")
if vim.fn.input("Checkout commit " .. commit_hash .. "? [y/n] ") == "y" then
local current_commit = utils.io_systemlist(cmd_cur_commit)
if(commit_hash == current_commit) then return end
table.insert(cmd_checkout, commit_hash)
local output = utils.io_systemlist(cmd_checkout)
if utils.shell_error() then
utils.err(unpack(output))
else
utils.info(unpack(output))
vim.cmd("edit!")
end
end
end
local git_exec = function(selected, opts, cmd)
for _, e in ipairs(selected) do
local file = path.relative(path.entry_to_file(e, opts.cwd).path, opts.cwd)
local _cmd = vim.deepcopy(cmd)
table.insert(_cmd, file)
local output = utils.io_systemlist(_cmd)
if utils.shell_error() then
utils.err(unpack(output))
-- elseif not vim.tbl_isempty(output) then
-- utils.info(unpack(output))
end
end
end
M.git_stage = function(selected, opts)
local cmd = path.git_cwd({"git", "add", "--"}, opts.cwd)
git_exec(selected, opts, cmd)
end
M.git_unstage = function(selected, opts)
local cmd = path.git_cwd({"git", "reset", "--"}, opts.cwd)
git_exec(selected, opts, cmd)
end
M.git_buf_edit = function(selected, opts)
local cmd = path.git_cwd({"git", "show"}, opts.cwd)
local git_root = path.git_root(opts.cwd, true)
local win = vim.api.nvim_get_current_win()
local buffer_filetype = vim.bo.filetype
local file = path.relative(vim.fn.expand("%:p"), git_root)
local commit_hash = selected[1]:match("[^ ]+")
table.insert(cmd, commit_hash .. ":" .. file)
local git_file_contents = utils.io_systemlist(cmd)
local buf = vim.api.nvim_create_buf(true, true)
local file_name = string.gsub(file,"$","[" .. commit_hash .. "]")
vim.api.nvim_buf_set_lines(buf,0,0,true,git_file_contents)
vim.api.nvim_buf_set_name(buf,file_name)
vim.api.nvim_buf_set_option(buf, 'buftype', 'nofile')
vim.api.nvim_buf_set_option(buf, 'bufhidden', 'wipe')
vim.api.nvim_buf_set_option(buf, 'filetype', buffer_filetype)
vim.api.nvim_buf_set_option(buf, 'modifiable', false)
vim.api.nvim_win_set_buf(win, buf)
end
M.git_buf_tabedit = function(selected, opts)
vim.cmd('tab split')
M.git_buf_edit(selected, opts)
end
M.git_buf_split = function(selected, opts)
vim.cmd('split')
M.git_buf_edit(selected, opts)
end
M.git_buf_vsplit = function(selected, opts)
vim.cmd('vsplit')
M.git_buf_edit(selected, opts)
end
M.arg_add = function(selected, opts)
local vimcmd = "argadd"
M.vimcmd_file(vimcmd, selected, opts)
end
M.arg_del = function(selected, opts)
local vimcmd = "argdel"
M.vimcmd_file(vimcmd, selected, opts)
end
3 years ago
return M