1
0
Fork 0
dotfiles/.config/nvim/lua/funcs.lua

566 lines
14 KiB
Lua
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local M = {}
local icons = require("config.icons")
function M.set_title()
local title = " %t"
local f = io.popen([[ zsh -lc 'print -P $PS1' | sed 's/\[[0-9;]*m//g;s/» / %t/g' ]])
if f ~= nil then
title = f:read("*a") or ""
f:close()
end
vim.opt.titlestring = title
end
function M.buf_close(bufnr, force)
local kill_command = "bd"
local bo = vim.bo
local api = vim.api
local fnamemodify = vim.fn.fnamemodify
if bufnr == 0 or bufnr == nil then
bufnr = api.nvim_get_current_buf()
end
local bufname = api.nvim_buf_get_name(bufnr)
if not force then
local warning
if bo[bufnr].modified then
warning = string.format([[No write since last change for (%s)]], fnamemodify(bufname, ":t"))
elseif api.nvim_buf_get_option(bufnr, "buftype") == "terminal" then
warning = string.format([[Terminal %s will be killed]], bufname)
end
if warning then
vim.ui.input({
prompt = string.format([[%s. Close it anyway? [y]es or [n]o (default: no): ]], warning),
}, function(choice)
if choice:match("ye?s?") then
force = true
end
end)
if not force then
return
end
end
end
-- Get list of window IDs with the buffer to close
local windows = vim.tbl_filter(function(win)
return api.nvim_win_get_buf(win) == bufnr
end, api.nvim_list_wins())
if #windows == 0 then
return
end
if force then
kill_command = kill_command .. "!"
end
-- Get list of active buffers
local buffers = vim.tbl_filter(function(buf)
return api.nvim_buf_is_valid(buf) and bo[buf].buflisted
end, api.nvim_list_bufs())
-- If there is only one buffer (which has to be the current one), vim will
-- create a new buffer on :bd.
-- For more than one buffer, pick the previous buffer (wrapping around if necessary)
if #buffers > 1 then
for i, v in ipairs(buffers) do
if v == bufnr then
local prev_buf_idx = i == 1 and (#buffers - 1) or (i - 1)
local prev_buffer = buffers[prev_buf_idx]
for _, win in ipairs(windows) do
api.nvim_win_set_buf(win, prev_buffer)
end
end
end
else
vim.cmd("q!")
end
-- Check if buffer still exists, to ensure the target buffer wasn't killed
-- due to options like bufhidden=wipe.
if api.nvim_buf_is_valid(bufnr) and bo[bufnr].buflisted then
vim.cmd(string.format("%s %d", kill_command, bufnr))
end
end
function M.is_available(plugin)
local lazy_config_avail, lazy_config = pcall(require, "lazy.core.config")
return lazy_config_avail and lazy_config.spec.plugins[plugin] ~= nil
end
function M.empty_map_table()
local maps = {}
for _, mode in ipairs({ "", "n", "v", "x", "s", "o", "!", "i", "l", "c", "t" }) do
maps[mode] = {}
end
-- TODO: Check this on 0.10.0 release
if vim.fn.has("nvim-0.10.0") == 1 then
for _, abbr_mode in ipairs({ "ia", "ca", "!a" }) do
maps[abbr_mode] = {}
end
end
return maps
end
function M.toggle_term_cmd(opts)
if not vim.g.user_terminals then
vim.g.user_terminals = {}
end
local terms = vim.g.user_terminals
if type(opts) == "string" then
opts = { cmd = opts, hidden = true }
end
local num = vim.v.count > 0 and vim.v.count or 1
if not terms[opts.cmd] then
terms[opts.cmd] = {}
end
if not terms[opts.cmd][num] then
if not opts.count then
opts.count = vim.tbl_count(terms) * 100 + num
end
if not opts.on_exit then
opts.on_exit = function()
terms[opts.cmd][num] = nil
end
end
terms[opts.cmd][num] = require("toggleterm.terminal").Terminal:new(opts)
end
terms[opts.cmd][num]:toggle()
end
function M.cmd(cmd, show_error)
if type(cmd) == "string" then
cmd = { cmd }
end
local result = vim.fn.system(cmd)
local success = vim.api.nvim_get_vvar("shell_error") == 0
if not success and (show_error == nil or show_error) then
vim.api.nvim_err_writeln(
("Error running command %s\nError message:\n%s"):format(table.concat(cmd, " "), result)
)
end
return success and result:gsub("[\27\155][][()#;?%d]*[A-PRZcf-ntqry=><~]", "") or nil
end
function M.file_worktree(file, worktrees)
worktrees = worktrees or vim.g.git_worktrees
file = file or vim.fn.expand("%")
for _, worktree in ipairs(worktrees) do
if
M.cmd({
"git",
"--work-tree",
worktree.toplevel,
"--git-dir",
worktree.gitdir,
"ls-files",
"--error-unmatch",
file,
}, false)
then
return worktree
end
end
end
function M.which_key_register()
if M.which_key_queue then
local wk_avail, wk = pcall(require, "which-key")
if wk_avail then
for mode, registration in pairs(M.which_key_queue) do
wk.register(registration, { mode = mode })
end
M.which_key_queue = nil
end
end
end
function M.set_maps(map_table, base)
base = base or {}
for mode, maps in pairs(map_table) do
for keymap, options in pairs(maps) do
if options then
local cmd = options
local keymap_opts = base
if type(options) == "table" then
cmd = options[1]
keymap_opts = vim.tbl_deep_extend("force", keymap_opts, options)
keymap_opts[1] = nil
end
if not cmd or keymap_opts.name then -- which-key mapping
if not keymap_opts.name then
keymap_opts.name = keymap_opts.desc
end
if not M.which_key_queue then
M.which_key_queue = {}
end
if not M.which_key_queue[mode] then
M.which_key_queue[mode] = {}
end
M.which_key_queue[mode][keymap] = keymap_opts
else -- not which-key mapping
vim.keymap.set(mode, keymap, cmd, keymap_opts)
end
end
end
end
if package.loaded["which-key"] then
M.which_key_register()
end
end
local function del_buffer_autocmd(augroup, bufnr)
local cmds_found, cmds = pcall(vim.api.nvim_get_autocmds, { group = augroup, buffer = bufnr })
if cmds_found then
vim.tbl_map(function(cmd)
vim.api.nvim_del_autocmd(cmd.id)
end, cmds)
end
end
local function add_buffer_autocmd(augroup, bufnr, autocmds)
if not vim.tbl_islist(autocmds) then
autocmds = { autocmds }
end
local cmds_found, cmds = pcall(vim.api.nvim_get_autocmds, { group = augroup, buffer = bufnr })
if not cmds_found or vim.tbl_isempty(cmds) then
vim.api.nvim_create_augroup(augroup, { clear = false })
for _, autocmd in ipairs(autocmds) do
local events = autocmd.events
autocmd.events = nil
autocmd.group = augroup
autocmd.buffer = bufnr
vim.api.nvim_create_autocmd(events, autocmd)
end
end
end
local function has_capability(capability, filter)
for _, client in ipairs(vim.lsp.get_active_clients(filter)) do
if client.supports_method(capability) then
return true
end
end
return false
end
-- NOTE: LSP Keymaps here
function M.lsp_on_attach(client, bufnr)
local lsp_mappings = M.empty_map_table()
lsp_mappings.n["<Leader>ld"] = {
function()
vim.diagnostic.open_float()
end,
desc = "Hover diagnostics",
}
lsp_mappings.n["[d"] = {
function()
vim.diagnostic.goto_prev()
end,
desc = "Previous diagnostic",
}
lsp_mappings.n["]d"] = {
function()
vim.diagnostic.goto_next()
end,
desc = "Next diagnostic",
}
lsp_mappings.n["gl"] = {
function()
vim.diagnostic.open_float()
end,
desc = "Hover diagnostics",
}
if M.is_available("telescope.nvim") then
lsp_mappings.n["<Leader>lD"] = {
function()
require("telescope.builtin").diagnostics()
end,
desc = "Search diagnostics",
}
end
if M.is_available("mason-lspconfig.nvim") then
lsp_mappings.n["<Leader>li"] = { "<cmd>LspInfo<cr>", desc = "LSP information" }
end
if M.is_available("none-ls.nvim") then
lsp_mappings.n["<Leader>lI"] = { "<cmd>NullLsInfo<cr>", desc = "Null-ls information" }
end
if client.supports_method("textDocument/codeAction") then
lsp_mappings.n["<Leader>la"] = {
function()
vim.lsp.buf.code_action()
end,
desc = "Code action",
}
lsp_mappings.v["<Leader>la"] = lsp_mappings.n["<Leader>la"]
end
if client.supports_method("textDocument/codeLens") then
vim.lsp.codelens.refresh()
lsp_mappings.n["<Leader>ll"] = {
function()
vim.lsp.codelens.refresh()
end,
desc = "Refresh CodeLens",
}
lsp_mappings.n["<Leader>lL"] = {
function()
vim.lsp.codelens.run()
end,
desc = "Run CodeLens",
}
lsp_mappings.n["<Leader>u"] = { desc = icons.Gear .. " Utility" }
lsp_mappings.n["<Leader>uL"] = {
function()
vim.lsp.codelens.clear()
end,
desc = "Toggle CodeLens",
}
end
if client.supports_method("textDocument/definition") then
lsp_mappings.n["gd"] = {
function()
vim.lsp.buf.definition()
end,
desc = "Go to definition",
}
end
if client.supports_method("textDocument/typeDefinition") then
lsp_mappings.n["gy"] = {
function()
vim.lsp.buf.type_definition()
end,
desc = "Go to type definition",
}
end
if client.supports_method("textDocument/declaration") then
lsp_mappings.n["gD"] = {
function()
vim.lsp.buf.declaration()
end,
desc = "Go to declaration",
}
end
if client.supports_method("textDocument/implementation") then
lsp_mappings.n["gI"] = {
function()
vim.lsp.buf.implementation()
end,
desc = "List implementations",
}
end
if client.supports_method("textDocument/references") then
lsp_mappings.n["gr"] = {
function()
vim.lsp.buf.references()
end,
desc = "List references",
}
end
if client.supports_method("workspace/symbol") then
lsp_mappings.n["<Leader>lG"] = {
function()
vim.lsp.buf.workspace_symbol()
end,
desc = "List symbols",
}
end
if client.supports_method("textDocument/rename") then
lsp_mappings.n["<Leader>lr"] = {
function()
vim.lsp.buf.rename()
end,
desc = "Rename symbol",
}
end
-- TODO: Check this on 0.10.0 release
if client.supports_method("textDocument/semanticTokens/full") and vim.lsp.semantic_tokens then
vim.b[bufnr].semantic_tokens_enabled = true
lsp_mappings.n["<Leader>u"] = { desc = icons.Gear .. " Utility" }
lsp_mappings.n["<Leader>uY"] = {
function()
vim.b[bufnr].semantic_tokens_enabled = not vim.b[bufnr].semantic_tokens_enabled
for _, active_client in ipairs(vim.lsp.get_active_clients({ bufnr = bufnr })) do
if active_client.server_capabilities.semanticTokensProvider then
vim.lsp.semantic_tokens[vim.b[bufnr].semantic_tokens_enabled and "start" or "stop"](
bufnr,
active_client.id
)
end
end
end,
desc = "Toggle LSP semantic highlight (buffer)",
}
end
if client.supports_method("textDocument/formatting") then
lsp_mappings.n["<Leader>lf"] = {
function()
vim.lsp.buf.format({
filter = function(c)
local filetype = vim.bo.filetype
local n = require("null-ls")
local s = require("null-ls.sources")
local method = n.methods.FORMATTING
local available_formatters = s.get_available(filetype, method)
if #available_formatters > 0 then
return c.name == "null-ls"
end
return true
end,
})
end,
desc = "Format buffer",
}
lsp_mappings.v["<Leader>lf"] = lsp_mappings.n["<Leader>lf"]
end
if client.supports_method("textDocument/documentHighlight") then
add_buffer_autocmd("lsp_document_highlight", bufnr, {
{
events = { "CursorHold", "CursorHoldI" },
desc = "highlight references when cursor holds",
callback = function()
if not has_capability("textDocument/documentHighlight", { bufnr = bufnr }) then
del_buffer_autocmd("lsp_document_highlight", bufnr)
return
end
vim.lsp.buf.document_highlight()
end,
},
{
events = { "CursorMoved", "CursorMovedI", "BufLeave" },
desc = "clear references when cursor moves",
callback = function()
vim.lsp.buf.clear_references()
end,
},
})
end
if client.supports_method("textDocument/hover") then
-- TODO: Check this on 0.10.0 release
if vim.fn.has("nvim-0.10") == 0 then
lsp_mappings.n["K"] = {
function()
vim.lsp.buf.hover()
end,
desc = "Hover symbol",
}
end
end
if client.supports_method("textDocument/inlayHint") then
if vim.b.inlay_hints_enabled == nil then
vim.b.inlay_hints_enabled = true
end
-- TODO: Check this on 0.10.0 release
if vim.lsp.inlay_hint then
if vim.b.inlay_hints_enabled then
vim.lsp.inlay_hint.enable(bufnr, true)
end
lsp_mappings.n["<Leader>u"] = { desc = icons.Gear .. " Utility" }
lsp_mappings.n["<Leader>uH"] = {
function()
vim.b[bufnr].inlay_hints_enabled = not vim.b[bufnr].inlay_hints_enabled
if vim.lsp.inlay_hint then
vim.lsp.inlay_hint.enable(bufnr, vim.b[bufnr].inlay_hints_enabled)
end
end,
desc = "Toggle inlay hints",
}
end
end
if client.supports_method("textDocument/signatureHelp") then
lsp_mappings.n["<Leader>lh"] = {
function()
vim.lsp.buf.signature_help()
end,
desc = "Signature help",
}
end
if M.is_available("telescope.nvim") then
if lsp_mappings.n.gd then
lsp_mappings.n.gd[1] = function()
require("telescope.builtin").lsp_definitions()
end
end
if lsp_mappings.n.gI then
lsp_mappings.n.gI[1] = function()
require("telescope.builtin").lsp_implementations()
end
end
if lsp_mappings.n.gr then
lsp_mappings.n.gr[1] = function()
require("telescope.builtin").lsp_references()
end
end
if lsp_mappings.n["<Leader>lR"] then
lsp_mappings.n["<Leader>lR"][1] = function()
require("telescope.builtin").lsp_references()
end
end
if lsp_mappings.n.gy then
lsp_mappings.n.gy[1] = function()
require("telescope.builtin").lsp_type_definitions()
end
end
if lsp_mappings.n["<Leader>lG"] then
lsp_mappings.n["<Leader>lG"][1] = function()
vim.ui.input({ prompt = "Symbol Query: (leave empty for word under cursor)" }, function(query)
if query then
-- word under cursor if given query is empty
if query == "" then
query = vim.fn.expand("<cword>")
end
require("telescope.builtin").lsp_workspace_symbols({
query = query,
prompt_title = ("Find word (%s)"):format(query),
})
end
end)
end
end
end
if not vim.tbl_isempty(lsp_mappings.v) then
if lsp_mappings.v["<Leader>l"] then
lsp_mappings.v["<Leader>l"] = { desc = icons.Code .. " LSP" }
end
if lsp_mappings.v["<Leader>u"] then
lsp_mappings.v["<Leader>u"] = { desc = icons.Code .. " LSP" }
end
end
M.set_maps(lsp_mappings, { buffer = bufnr })
end
function M.has_value(table, value)
for _, v in ipairs(table) do
if v == value then
return true
end
end
return false
end
return M