fix: namespace all local packages and modules
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
local version = vim.version()
|
||||
|
||||
if version.major == 0 then
|
||||
if version.minor < 11 then
|
||||
error("Neovim version 0.11 or above is required with this configuration.")
|
||||
end
|
||||
end
|
||||
|
||||
local utils = require("ow.utils")
|
||||
|
||||
-- Install lazy.nvim
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not vim.uv.fs_stat(lazypath) then
|
||||
utils.assert_installed("git")
|
||||
if not os.getenv("CC") then
|
||||
utils.assert_installed("cc")
|
||||
end
|
||||
utils.assert_installed("make")
|
||||
utils.assert_any_installed({ "curl", "wget", })
|
||||
utils.assert_installed("unzip")
|
||||
utils.assert_installed("tar")
|
||||
utils.assert_installed("gzip")
|
||||
|
||||
local resp = vim.system({
|
||||
"git",
|
||||
"clone",
|
||||
"--filter=blob:none",
|
||||
"https://github.com/folke/lazy.nvim.git",
|
||||
"--branch=stable",
|
||||
lazypath,
|
||||
}):wait()
|
||||
|
||||
assert(resp.code == 0, "Failed to download lazy")
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
@@ -0,0 +1,75 @@
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = "help",
|
||||
callback = function()
|
||||
vim.wo.number = true
|
||||
vim.wo.relativenumber = true
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
desc = "Use tabs for indents in Go files",
|
||||
pattern = "go",
|
||||
callback = function()
|
||||
vim.bo.expandtab = false
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
desc = "Fix parsing compile errors into quickfixlist",
|
||||
pattern = "zig",
|
||||
callback = function()
|
||||
vim.bo.errorformat = "%f:%l:%c: %t%.%#: %m,%-G%.%#"
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd({ "BufReadPost" }, {
|
||||
desc = "Return cursor to last position when re-opening a buffer",
|
||||
pattern = "*",
|
||||
command = 'silent! normal! g`"zv',
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
desc = "Use two space indent for C++ files",
|
||||
pattern = { "cpp" },
|
||||
callback = function()
|
||||
vim.bo.tabstop = 2
|
||||
vim.bo.softtabstop = 2
|
||||
vim.bo.shiftwidth = 2
|
||||
vim.bo.cinoptions = "g0"
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
pattern = { "netrw" },
|
||||
callback = function()
|
||||
vim.keymap.set("n", "<C-h>", "-", { buffer = true, remap = true })
|
||||
vim.keymap.set("n", "<C-l>", "<CR>", { buffer = true, remap = true })
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("VimEnter", {
|
||||
pattern = "*",
|
||||
command = ":clearjumps",
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
desc = "Make markdown files a bit prettier",
|
||||
pattern = { "markdown" },
|
||||
callback = function()
|
||||
vim.wo.conceallevel = 2
|
||||
vim.wo.concealcursor = "n"
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
desc = "Customize python indentation",
|
||||
pattern = { "python" },
|
||||
callback = function()
|
||||
vim.g.python_indent = {
|
||||
open_paren = 'shiftwidth()',
|
||||
continue = 'shiftwidth()',
|
||||
closed_paren_align_last_line = false,
|
||||
}
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
vim.g.mapleader = " "
|
||||
vim.g.vimsyn_embed = "lpP"
|
||||
vim.g.netrw_banner = 0
|
||||
vim.g.netrw_liststyle = 1
|
||||
vim.g.netrw_list_hide = '\\.venv/,\\.git/'
|
||||
vim.g.netrw_maxfilenamelen = 47
|
||||
vim.g.netrw_mousemaps = 0
|
||||
vim.g.netrw_sizestyle = 'H'
|
||||
vim.g.netrw_sort_by = 'name'
|
||||
vim.g.netrw_sort_options = 'i'
|
||||
vim.g.netrw_sort_sequence = '[\\/]\\s*,*'
|
||||
vim.g.netrw_special_syntax = 1
|
||||
vim.g.netrw_timefmt = '%d-%m-%Y %H:%M'
|
||||
vim.g.c_syntax_for_h = 1
|
||||
|
||||
local termfeatures = vim.g.termfeatures or {}
|
||||
termfeatures.osc52 = false
|
||||
vim.g.termfeatures = termfeatures
|
||||
@@ -0,0 +1,146 @@
|
||||
-- Tab mappings ---
|
||||
vim.keymap.set("n", "tn", vim.cmd.tabnew)
|
||||
vim.keymap.set("n", "tq", vim.cmd.tabclose)
|
||||
|
||||
-- Clipboard
|
||||
if vim.env.TMUX and vim.fn.executable('tmux') then
|
||||
vim.keymap.set(
|
||||
{ "n", "x" },
|
||||
"<leader>y",
|
||||
"\"+y:call system('tmux load-buffer -w -', @+)<CR>"
|
||||
)
|
||||
else
|
||||
vim.keymap.set({ "n", "x", }, "<leader>y", '"+y')
|
||||
end
|
||||
vim.keymap.set({ "n", "x", }, "<leader>p", '"+p')
|
||||
vim.keymap.set({ "n", "x", }, "<leader>P", '"+P')
|
||||
vim.keymap.set({ "n", "x", }, "<leader>+", ":call setreg('+', @\")<CR>")
|
||||
|
||||
-- Allow exiting insert mode in terminal by hitting <ESC>
|
||||
vim.keymap.set("t", "<Esc>", "<C-\\><C-n>")
|
||||
|
||||
-- Use :diffput/get instead of normal one to allow staging visual selection
|
||||
vim.keymap.set({"n", "x"}, "<leader>dp", ":diffput<CR>")
|
||||
vim.keymap.set({"n", "x"}, "<leader>do", ":diffget<CR>")
|
||||
|
||||
local close_pum = function()
|
||||
if vim.fn.pumvisible() ~= 0 then
|
||||
return "<cmd>pclose<cr>"
|
||||
end
|
||||
|
||||
for _, winid in pairs(vim.api.nvim_tabpage_list_wins(0)) do
|
||||
if vim.api.nvim_win_get_config(winid).relative ~= "" then
|
||||
return "<cmd>fclose<cr>"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vim.keymap.set({ "n", "i" }, "<C-c>", function()
|
||||
return close_pum()
|
||||
end, { expr = true })
|
||||
|
||||
vim.keymap.set("n", "<C-w>q", ":bp | bd#<CR>")
|
||||
|
||||
-- Allow (de)indenting without deselecting
|
||||
vim.keymap.set({"x"}, "<", "<gv")
|
||||
vim.keymap.set({"x"}, ">", ">gv")
|
||||
|
||||
-- Remove default mappings
|
||||
vim.keymap.set("", "<C-LeftMouse>", "")
|
||||
vim.keymap.set('n', 'K', function()
|
||||
if vim.bo.filetype == 'vim' or vim.bo.filetype == 'help' then
|
||||
vim.cmd('help ' .. vim.fn.expand('<cword>'))
|
||||
else
|
||||
vim.cmd('Man ' .. vim.fn.expand('<cword>'))
|
||||
end
|
||||
end)
|
||||
|
||||
-- Remove right-click menu items
|
||||
vim.cmd.aunmenu({ "PopUp.-1-", })
|
||||
vim.cmd.aunmenu({ "PopUp.How-to\\ disable\\ mouse", })
|
||||
|
||||
-- Insert-mode Emacs bindings
|
||||
vim.keymap.set('i', '<C-f>', '<Right>')
|
||||
vim.keymap.set('i', '<C-b>', '<Left>')
|
||||
vim.keymap.set('i', '<C-a>', '<C-o>^')
|
||||
vim.keymap.set('i', '<C-e>', '<C-o>$')
|
||||
-- vim.keymap.set('i', '<C-d>', '<C-o>x') -- Overrides de-indent
|
||||
vim.keymap.set('i', '<M-f>', '<C-o>w')
|
||||
vim.keymap.set('i', '<M-b>', '<C-o>b')
|
||||
vim.keymap.set('i', '<M-d>', '<C-o>dw')
|
||||
vim.keymap.set('i', '<M-BS>', '<C-o>db')
|
||||
|
||||
-- Command-mode Emacs bindings
|
||||
vim.keymap.set('c', '<C-f>', '<Right>')
|
||||
vim.keymap.set('c', '<C-b>', '<Left>')
|
||||
vim.keymap.set('c', '<C-a>', '<Home>')
|
||||
vim.keymap.set('c', '<C-e>', '<End>')
|
||||
vim.keymap.set('c', '<C-d>', '<Delete>')
|
||||
vim.keymap.set('c', '<C-n>', '<Down>')
|
||||
vim.keymap.set('c', '<C-p>', '<Up>')
|
||||
vim.keymap.set('c', '<M-f>', '<C-Right>')
|
||||
vim.keymap.set('c', '<M-b>', '<C-Left>')
|
||||
vim.keymap.set('c', '<M-d>', '<C-Right><C-w>')
|
||||
vim.keymap.set('c', '<M-BS>', '<C-w>')
|
||||
|
||||
vim.keymap.set('n', '<leader>ve', function()
|
||||
if vim.o.virtualedit == 'all' then
|
||||
vim.o.virtualedit = 'block'
|
||||
else
|
||||
vim.o.virtualedit = 'all'
|
||||
end
|
||||
end)
|
||||
|
||||
-- Removed bindings
|
||||
vim.keymap.set('n', 'gr', '<Nop>')
|
||||
|
||||
-- Default bindings that are good to know:
|
||||
-- insert mode:
|
||||
-- <C-T> - indent, see :h i_CTRL-T
|
||||
-- <C-D> - un-indent, see :h i_CTRL-D
|
||||
-- normal mode:
|
||||
-- H/M/L - Jump to highest/middle/lowest line in window.
|
||||
-- <count?><C-E> - scroll window down <count> lines, see :h CTRL-E
|
||||
-- <count?><C-Y> - scroll window up <count> lines, see :h CTRL-Y
|
||||
-- <C-A> - Increment
|
||||
-- <C-X> - Decrement
|
||||
-- <C-w>H - Move window to the left
|
||||
-- <C-w>J - Move window down
|
||||
-- <C-w>K - Move window up
|
||||
-- <C-w>L - Move window to the right
|
||||
-- gq{motion} - format word-wrap of the line that {motion} moves over
|
||||
-- {Visual}gq - format word-wrap of the visually selected area
|
||||
-- gqq - format word-wrap of the current line
|
||||
-- commands:
|
||||
-- :make - execute makeprg with given args, and put the output in
|
||||
-- quickfix list
|
||||
-- :grep - execute grepprg with given args, and put the results in
|
||||
-- quickfix list
|
||||
-- :cex {expr} - Create a quickfix list using the result of {expr} and
|
||||
-- jump to the first error. For example:
|
||||
-- :cex system('make')
|
||||
-- :cgete {expr} - same as :cex but don't jump to the first error
|
||||
-- :copen - open quickfix list
|
||||
-- :cdo {cmd} - execute {cmd} in each valid entry in the quickfix list.
|
||||
-- works like this:
|
||||
-- :cfirst
|
||||
-- :{cmd}
|
||||
-- :cnext
|
||||
-- :{cmd}
|
||||
-- etc.
|
||||
-- :cfdo {cmd} - same as :cdo but on each file in quickfix list
|
||||
-- :cn - go to the next error in quickfix list that includes a file name
|
||||
-- :cp - go to the previous error in quickfix list that includes a file name
|
||||
-- :cc [num] - go to the specified error in quickfix list
|
||||
-- @: - repeat last command
|
||||
-- :s/foo/bar/ - substitute the first match of foo with bar in the current line
|
||||
-- :s/foo/bar/g - same as above but for all matches in the current line
|
||||
-- :%s/foo/bar/g - same as above, but for all lines in buffer
|
||||
-- :%s/foo/bar/gc - same as above but asking for confirmation on each match
|
||||
-- :lua << EOF - run a lua snippet using lua-heredoc syntax
|
||||
-- local tbl = {1, 2, 3}
|
||||
-- for k, v in ipairs(tbl) do
|
||||
-- print(v)
|
||||
-- end
|
||||
-- EOF
|
||||
-- :diffsplit <other-file> - open diff split
|
||||
@@ -0,0 +1,142 @@
|
||||
vim.opt.cursorline = true
|
||||
vim.opt.number = true
|
||||
vim.opt.relativenumber = true
|
||||
vim.opt.mouse = "a"
|
||||
vim.opt.mousemodel = "popup"
|
||||
vim.opt.fillchars = {
|
||||
diff = " ",
|
||||
horiz = "━",
|
||||
horizup = "┻",
|
||||
horizdown = "┳",
|
||||
vert = "┃",
|
||||
vertleft = "┫",
|
||||
vertright = "┣",
|
||||
verthoriz = "╋",
|
||||
}
|
||||
vim.opt.splitbelow = true
|
||||
vim.opt.splitright = true
|
||||
vim.opt.tabstop = 4
|
||||
vim.opt.softtabstop = 4
|
||||
vim.opt.expandtab = true
|
||||
vim.opt.shiftwidth = 4
|
||||
vim.opt.smarttab = false
|
||||
-- Folds are configured in nvim-treesitter, so this is only for fallback
|
||||
vim.opt.foldenable = false
|
||||
vim.opt.foldlevel = 99
|
||||
vim.opt.foldlevelstart = 99
|
||||
vim.opt.foldmethod = "indent"
|
||||
vim.opt.foldignore = ""
|
||||
vim.opt.completeopt:append({
|
||||
"menu",
|
||||
"menuone",
|
||||
"preview",
|
||||
"noinsert",
|
||||
"noselect",
|
||||
})
|
||||
-- set nowrap
|
||||
vim.opt.matchpairs:append({ "<:>", })
|
||||
-- Only relevant with wrap enabled (default)
|
||||
vim.opt.linebreak = true
|
||||
vim.opt.breakindent = true
|
||||
vim.opt.showbreak = "↪"
|
||||
-- Minimum amount of lines to show offset +/- to cursorline
|
||||
vim.opt.scrolloff = 3
|
||||
vim.opt.visualbell = true
|
||||
vim.opt.errorbells = false
|
||||
-- Persistent undo even after you close a file and re-open it
|
||||
vim.opt.undofile = true
|
||||
-- Align indent to next multiple value of shiftwidth.
|
||||
-- E.g., only insert as many spaces necessary for reaching the next shiftwidth
|
||||
vim.opt.shiftround = true
|
||||
-- Allow virtualedit (editing on empty spaces) in visual block mode (Ctrl+V)
|
||||
vim.opt.virtualedit = "block"
|
||||
-- True color support. Avoid if terminal does not support it.
|
||||
vim.opt.termguicolors = true
|
||||
vim.opt.signcolumn = "yes:2"
|
||||
-- Diff options
|
||||
vim.opt.diffopt = {}
|
||||
-- Use vertical split by default
|
||||
vim.opt.diffopt:append("vertical")
|
||||
-- Insert filler lines
|
||||
vim.opt.diffopt:append("filler")
|
||||
-- Execute :diffoff when only one diff window remain
|
||||
vim.opt.diffopt:append("closeoff")
|
||||
-- Use internal diff library
|
||||
vim.opt.diffopt:append("internal")
|
||||
-- These make diffs easier to read, please see the following:
|
||||
-- https://vimways.org/2018/the-power-of-diff/
|
||||
vim.opt.diffopt:append({ "indent-heuristic", "algorithm:histogram", })
|
||||
vim.opt.diffopt:append("linematch:40")
|
||||
vim.opt.hlsearch = true
|
||||
vim.opt.laststatus = 2
|
||||
vim.opt.textwidth = 0
|
||||
vim.opt.colorcolumn = "81"
|
||||
vim.opt.shortmess:append("a")
|
||||
vim.opt.autoread = true
|
||||
-- vim.opt.cmdheight = 0 -- To hide cmdline when not used. Disabled due to
|
||||
-- causing "Press ENTER to continue" messages for small messages.
|
||||
vim.opt.jumpoptions = {'stack', 'view', 'clean'}
|
||||
vim.opt.updatetime = 100
|
||||
|
||||
function _G._status_line_git()
|
||||
local status = vim.b.gitsigns_status_dict
|
||||
|
||||
if not status then
|
||||
return ''
|
||||
end
|
||||
|
||||
local added = status.added or 0
|
||||
local changed = status.changed or 0
|
||||
local removed = status.removed or 0
|
||||
|
||||
local parts = {}
|
||||
|
||||
if added > 0 then
|
||||
table.insert(parts, string.format("%%#GitSignsAdd#+%d%%*", status.added))
|
||||
end
|
||||
|
||||
if changed > 0 then
|
||||
table.insert(parts, string.format('%%#GitSignsChange#~%d%%*', status.changed))
|
||||
end
|
||||
|
||||
if removed > 0 then
|
||||
table.insert(parts, string.format('%%#GitSignsDelete#-%d%%*', status.removed))
|
||||
end
|
||||
|
||||
return table.concat(parts, " ")
|
||||
end
|
||||
|
||||
function _G._status_line_diagnostics()
|
||||
local diag = vim.diagnostic.count(0)
|
||||
local err = diag[vim.diagnostic.severity.ERROR] or 0
|
||||
local warn = diag[vim.diagnostic.severity.WARN] or 0
|
||||
local hint = diag[vim.diagnostic.severity.HINT] or 0
|
||||
local info = diag[vim.diagnostic.severity.INFO] or 0
|
||||
local parts = {}
|
||||
|
||||
if err > 0 then
|
||||
table.insert(parts, string.format("%%#DiagnosticError#E%d%%*", err))
|
||||
end
|
||||
|
||||
if warn > 0 then
|
||||
table.insert(parts, string.format('%%#DiagnosticWarn#W%d%%*', warn))
|
||||
end
|
||||
|
||||
if hint > 0 then
|
||||
table.insert(parts, string.format('%%#DiagnosticHint#H%d%%*', hint))
|
||||
end
|
||||
|
||||
if info > 0 then
|
||||
table.insert(parts, string.format('%%#DiagnosticInfo#I%d%%*', info))
|
||||
end
|
||||
|
||||
return table.concat(parts, " ")
|
||||
end
|
||||
|
||||
vim.opt.statusline = " %{expand('%:.')}%4( %m%) %{%v:lua._status_line_git()%} %="
|
||||
.. " %{%v:lua._status_line_diagnostics()%} "
|
||||
.. " %{&filetype} %-6.6{&fileencoding}"
|
||||
.. " %-4.4{&fileformat} %4.4(%p%%%)%6.6l:%-3.3v"
|
||||
|
||||
vim.cmd("syntax on")
|
||||
vim.cmd("filetype plugin indent on")
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
local utils = require("ow.utils")
|
||||
|
||||
local CONFIG_DIR = vim.fn.stdpath("config") .. "/lua/ow/lsp/config"
|
||||
|
||||
---@class Server
|
||||
local Server = require("ow.lsp.server")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, Server>
|
||||
local servers = {}
|
||||
|
||||
function M.get_servers()
|
||||
return servers
|
||||
end
|
||||
|
||||
local function get_module_name(filepath)
|
||||
return filepath:match("([^/\\]+)%.lua$")
|
||||
end
|
||||
|
||||
local function get_server_config(name)
|
||||
local module = "ow.lsp.config." .. name
|
||||
package.loaded[module] = nil
|
||||
return utils.try_require("ow.lsp.config." .. name)
|
||||
end
|
||||
|
||||
local reload_server_config = utils.debounce_with_id(function(name, events)
|
||||
utils.info(("Reloading server with new config"):format(name), name)
|
||||
---@type Server|nil
|
||||
local server = servers[name]
|
||||
|
||||
if server and server.config.enable then
|
||||
server:deinit()
|
||||
servers[name] = nil
|
||||
end
|
||||
|
||||
if events.rename then
|
||||
local _, _, err_name =
|
||||
vim.uv.fs_stat(("%s/%s.lua"):format(CONFIG_DIR, name))
|
||||
if err_name == "ENOENT" then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local config = get_server_config(name)
|
||||
if not config then
|
||||
return
|
||||
end
|
||||
|
||||
server = Server.new(name, config)
|
||||
if not server or not server.config.enable then
|
||||
return
|
||||
end
|
||||
|
||||
local on_done = function(success)
|
||||
if success then
|
||||
utils.info(("%s reloaded"):format(name))
|
||||
end
|
||||
end
|
||||
|
||||
if #server:get_ft_buffers() ~= 0 then
|
||||
server:setup(on_done)
|
||||
else
|
||||
server:init(on_done)
|
||||
end
|
||||
|
||||
servers[name] = server
|
||||
end, 1000)
|
||||
|
||||
local function process_change(error, filename, events)
|
||||
if error then
|
||||
utils.err(
|
||||
("Error on change for %s:\n%s"):format(filename, error),
|
||||
"ow.lsp.on_config_change"
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
local name = get_module_name(filename)
|
||||
if not name or name == "init" then
|
||||
return
|
||||
end
|
||||
|
||||
reload_server_config(name, name, events)
|
||||
end
|
||||
|
||||
local function load_configs()
|
||||
local handle = vim.uv.fs_scandir(CONFIG_DIR)
|
||||
while handle do
|
||||
local filepath = vim.uv.fs_scandir_next(handle)
|
||||
|
||||
if not filepath then
|
||||
break
|
||||
end
|
||||
|
||||
local name = get_module_name(filepath)
|
||||
|
||||
if name == "init" then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local config = get_server_config(name)
|
||||
|
||||
if not config then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local server = Server.new(name, config)
|
||||
if server then
|
||||
servers[name] = server
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
|
||||
vim.uv.fs_event_start(
|
||||
vim.uv.new_fs_event(),
|
||||
CONFIG_DIR,
|
||||
{},
|
||||
vim.schedule_wrap(process_change)
|
||||
)
|
||||
end
|
||||
|
||||
--- Setup diagnostics UI
|
||||
local function setup_diagnostics()
|
||||
vim.diagnostic.config({
|
||||
underline = true,
|
||||
signs = true,
|
||||
virtual_text = false,
|
||||
float = {
|
||||
show_header = false,
|
||||
source = true,
|
||||
border = "single",
|
||||
focusable = false,
|
||||
format = function(diagnostic)
|
||||
return string.format("%s", diagnostic.message)
|
||||
end,
|
||||
},
|
||||
update_in_insert = false,
|
||||
severity_sort = true,
|
||||
jump = {
|
||||
float = true,
|
||||
wrap = false,
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
function M.setup()
|
||||
load_configs()
|
||||
setup_diagnostics()
|
||||
|
||||
for _, server in pairs(servers) do
|
||||
if server.config.enable then
|
||||
server:init()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,36 @@
|
||||
local utils = require("ow.utils")
|
||||
|
||||
return {
|
||||
enable = true,
|
||||
dependencies = {
|
||||
"npm",
|
||||
},
|
||||
mason = {
|
||||
"bash-language-server",
|
||||
dependencies = {
|
||||
{ "shellcheck" },
|
||||
{ "shfmt" },
|
||||
},
|
||||
},
|
||||
keymaps = {
|
||||
{
|
||||
mode = "n",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = { "shfmt", "-s", "-i", "4", "-" },
|
||||
output = "stdout",
|
||||
})
|
||||
end,
|
||||
},
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"sh",
|
||||
"bash",
|
||||
"zsh",
|
||||
},
|
||||
cmd = { "bash-language-server", "start" },
|
||||
single_file_support = true,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
---@type ServerConfig
|
||||
return {
|
||||
enable = true,
|
||||
mason = { "clangd" },
|
||||
keymaps = {
|
||||
{
|
||||
mode = "n",
|
||||
lhs = "gs",
|
||||
rhs = vim.cmd.ClangdSwitchSourceHeader,
|
||||
},
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"c",
|
||||
"cpp",
|
||||
},
|
||||
cmd = {
|
||||
"clangd",
|
||||
"--clang-tidy",
|
||||
"--enable-config",
|
||||
|
||||
-- Fix for errors in files outside of project
|
||||
-- https://clangd.llvm.org/faq#how-do-i-fix-errors-i-get-when-opening-headers-outside-of-my-project-directory
|
||||
"--compile-commands-dir=build",
|
||||
},
|
||||
single_file_support = true,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
return {
|
||||
enable = true,
|
||||
dependencies = {
|
||||
"python3",
|
||||
},
|
||||
mason = {
|
||||
name = "cmake-language-server",
|
||||
-- version = "",
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"cmake",
|
||||
},
|
||||
cmd = { "cmake-language-server", },
|
||||
single_file_support = true,
|
||||
init_options = {
|
||||
buildDirectory = "build",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
local utils = require("ow.utils")
|
||||
|
||||
---@type ServerConfig
|
||||
return {
|
||||
enable = true,
|
||||
mason = { "gopls", dependencies = { "golines" } },
|
||||
keymaps = {
|
||||
{
|
||||
mode = "n",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = { "golines", "-m", "80", "--shorten-comments" },
|
||||
output = "stdout",
|
||||
})
|
||||
vim.lsp.buf.format({ async = true })
|
||||
end,
|
||||
},
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"go",
|
||||
"gomod",
|
||||
"gowork",
|
||||
"gotmpl",
|
||||
},
|
||||
cmd = { "gopls" },
|
||||
single_file_support = true,
|
||||
settings = {
|
||||
gopls = {
|
||||
staticcheck = true,
|
||||
semanticTokens = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
--[[
|
||||
Copyright 2023 Oscar Wallberg
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
]]
|
||||
|
||||
return {
|
||||
enable = false,
|
||||
dependencies = {
|
||||
"java",
|
||||
},
|
||||
mason = {
|
||||
"groovy-language-server",
|
||||
},
|
||||
lspconfig = {
|
||||
cmd = {
|
||||
"groovy-language-server",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
return {
|
||||
enable = true,
|
||||
mason = {
|
||||
name = "haskell-language-server",
|
||||
-- version = "",
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = { "haskell", "lhaskell", "cabal", },
|
||||
cmd = { "haskell-language-server-wrapper", "--lsp", },
|
||||
single_file_support = true,
|
||||
settings = {
|
||||
haskell = {
|
||||
cabalFormattingProvider = "cabalfmt",
|
||||
formattingProvider = "ormolu",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
-- spec:
|
||||
-- https://github.com/bmewburn/intelephense-docs/blob/master/installation.md
|
||||
-- https://github.com/bmewburn/vscode-intelephense/blob/master/package.json
|
||||
|
||||
local utils = require("ow.utils")
|
||||
|
||||
return {
|
||||
enable = true,
|
||||
dependencies = {
|
||||
"npm",
|
||||
},
|
||||
mason = { "intelephense", dependencies = { {"phpcs", "pretty-php" } } },
|
||||
linters = {
|
||||
{
|
||||
cmd = {
|
||||
"phpcs",
|
||||
"--standard=PSR12",
|
||||
"--report=emacs",
|
||||
"-s",
|
||||
"-q",
|
||||
"-",
|
||||
},
|
||||
stdin = true,
|
||||
stdout = true,
|
||||
pattern = "^.+:(%d+):(%d+): (%w+) %- (.*) %((.*)%)$",
|
||||
groups = { "lnum", "col", "severity", "message", "source" },
|
||||
source = "phpcs",
|
||||
severity_map = {
|
||||
error = vim.diagnostic.severity.ERROR,
|
||||
warning = vim.diagnostic.severity.WARN,
|
||||
},
|
||||
},
|
||||
},
|
||||
keymaps = {
|
||||
{
|
||||
mode = "n",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = {
|
||||
"pretty-php",
|
||||
"--psr12",
|
||||
"--enable=align-comments",
|
||||
"-qq",
|
||||
"-",
|
||||
},
|
||||
output = "stdout",
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
mode = "x",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = {
|
||||
"pretty-php",
|
||||
"--psr12",
|
||||
"--enable=align-comments",
|
||||
"-qq",
|
||||
"-",
|
||||
},
|
||||
output = "stdout",
|
||||
})
|
||||
end,
|
||||
},
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"php",
|
||||
},
|
||||
cmd = { "intelephense", "--stdio" },
|
||||
single_file_support = true,
|
||||
settings = {
|
||||
intelephense = {
|
||||
environment = {
|
||||
phpVersion = "7.4",
|
||||
},
|
||||
format = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
local utils = require("ow.utils")
|
||||
|
||||
local ERROR = vim.diagnostic.severity.ERROR
|
||||
local WARN = vim.diagnostic.severity.WARN
|
||||
local INFO = vim.diagnostic.severity.INFO
|
||||
local HINT = vim.diagnostic.severity.HINT
|
||||
|
||||
local SEVERITY_MAP = {
|
||||
YTT = WARN,
|
||||
ANN = WARN,
|
||||
ASYNC = WARN,
|
||||
B = WARN,
|
||||
A = WARN,
|
||||
COM = WARN,
|
||||
C = WARN,
|
||||
DTZ = WARN,
|
||||
T = WARN,
|
||||
FIX = WARN,
|
||||
FA = WARN,
|
||||
ISC = WARN,
|
||||
PIE = WARN,
|
||||
PYI = WARN,
|
||||
PT = WARN,
|
||||
RET = WARN,
|
||||
SIM = WARN,
|
||||
TC = WARN,
|
||||
I = WARN,
|
||||
E = ERROR,
|
||||
W = WARN,
|
||||
DOC = WARN,
|
||||
D = INFO,
|
||||
F = WARN,
|
||||
PLC = WARN,
|
||||
PLE = ERROR,
|
||||
PLR = WARN,
|
||||
PLW = WARN,
|
||||
UP = WARN,
|
||||
RUF = WARN,
|
||||
}
|
||||
|
||||
---@type ServerConfig
|
||||
return {
|
||||
enable = true,
|
||||
mason = { "jedi-language-server", dependencies = { "ruff" } },
|
||||
linters = {
|
||||
{
|
||||
cmd = {
|
||||
"ruff",
|
||||
"check",
|
||||
"--output-format=json",
|
||||
"-q",
|
||||
"-",
|
||||
},
|
||||
stdin = true,
|
||||
stdout = true,
|
||||
json = {
|
||||
lnum = "location.row",
|
||||
end_lnum = "end_location.row",
|
||||
col = "location.column",
|
||||
end_col = "end_location.column",
|
||||
code = "code",
|
||||
message = "message",
|
||||
callback = function(diag)
|
||||
if diag.severity or not diag.code then
|
||||
return
|
||||
end
|
||||
diag.severity = SEVERITY_MAP[diag.code:match("^(%u+)")]
|
||||
end,
|
||||
},
|
||||
source = "ruff",
|
||||
tags = {
|
||||
deprecated = {
|
||||
"PYI057",
|
||||
"PT020",
|
||||
"UP005",
|
||||
"UP019",
|
||||
"UP021",
|
||||
"UP023",
|
||||
"UP026",
|
||||
"UP035",
|
||||
},
|
||||
unnecessary = {
|
||||
"ARG001",
|
||||
"ARG002",
|
||||
"ARG003",
|
||||
"ARG004",
|
||||
"ARG005",
|
||||
"F401",
|
||||
"F504",
|
||||
"F522",
|
||||
"F811",
|
||||
"F841",
|
||||
"F842",
|
||||
"RUF029",
|
||||
"RUF059",
|
||||
"RUF100",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
keymaps = {
|
||||
{
|
||||
mode = "n",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = {
|
||||
"ruff",
|
||||
"format",
|
||||
"--stdin-filename=%filename%",
|
||||
"--quiet",
|
||||
"-",
|
||||
},
|
||||
output = "stdout",
|
||||
})
|
||||
utils.format({
|
||||
cmd = {
|
||||
"ruff",
|
||||
"check",
|
||||
"--select=I",
|
||||
"--fix",
|
||||
"--quiet",
|
||||
"-",
|
||||
},
|
||||
output = "stdout",
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
mode = "x",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = {
|
||||
"ruff",
|
||||
"format",
|
||||
"--stdin-filename=%filename%",
|
||||
"--quiet",
|
||||
"--range=%row_start%:%col_start%-%row_end%:%col_end%",
|
||||
"-",
|
||||
},
|
||||
output = "stdout",
|
||||
})
|
||||
end,
|
||||
},
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"python",
|
||||
},
|
||||
cmd = { "jedi-language-server" },
|
||||
single_file_support = true,
|
||||
init_options = {
|
||||
completion = {
|
||||
disableSnippets = true,
|
||||
},
|
||||
diagnostics = {
|
||||
enable = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
-- spec: https://github.com/eclipse/lemminx/blob/main/docs/Configuration.md
|
||||
|
||||
return {
|
||||
enable = true,
|
||||
mason = {
|
||||
name = "lemminx",
|
||||
dependencies = { "xmlformatter" },
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"xml",
|
||||
"xsd",
|
||||
"xsl",
|
||||
"xslt",
|
||||
"svg",
|
||||
},
|
||||
cmd = { "lemminx", },
|
||||
single_file_support = true,
|
||||
init_options = {
|
||||
settings = {
|
||||
xml = {
|
||||
format = {
|
||||
enabled = true, -- is able to format document
|
||||
splitAttributes = true, -- each attribute is formatted onto new line
|
||||
joinCDATALines = false, -- normalize content inside CDATA
|
||||
joinCommentLines = false, -- normalize content inside comments
|
||||
formatComments = true, -- keep comment in relative position
|
||||
joinContentLines = false, -- normalize content inside elements
|
||||
spaceBeforeEmptyCloseLine = true, -- insert whitespace before self closing tag end bracket
|
||||
},
|
||||
validation = {
|
||||
noGrammar = "ignore",
|
||||
enabled = true,
|
||||
schema = true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
-- spec: https://luals.github.io/wiki/settings/
|
||||
local utils = require("ow.utils")
|
||||
|
||||
---@type ServerConfig
|
||||
return {
|
||||
enable = true,
|
||||
dependencies = { "cargo" },
|
||||
mason = {
|
||||
"lua-language-server",
|
||||
post_install = { { cmd = { "cargo", "install", "stylua", "--features", "lua54" } } },
|
||||
},
|
||||
keymaps = {
|
||||
{
|
||||
mode = "n",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = { "stylua", "-" },
|
||||
output = "stdout",
|
||||
})
|
||||
end,
|
||||
},
|
||||
{
|
||||
mode = "x",
|
||||
lhs = "<leader>lf",
|
||||
rhs = function()
|
||||
utils.format({
|
||||
cmd = {
|
||||
"stylua",
|
||||
"-",
|
||||
"--range-start",
|
||||
"%byte_start%",
|
||||
"--range-end",
|
||||
"%byte_end%",
|
||||
},
|
||||
output = "stdout",
|
||||
})
|
||||
end,
|
||||
},
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = { "lua" },
|
||||
cmd = { "lua-language-server" },
|
||||
single_file_support = true,
|
||||
settings = {
|
||||
Lua = {
|
||||
completion = { showWord = "Disable" },
|
||||
diagnostics = {
|
||||
-- disable = { "missing-fields" },
|
||||
},
|
||||
runtime = {
|
||||
version = "LuaJIT",
|
||||
path = {
|
||||
-- "?.lua",
|
||||
"?.lua",
|
||||
"?/init.lua",
|
||||
-- "?.lua",
|
||||
-- "lua/?.lua",
|
||||
-- "lua/?/init.lua",
|
||||
-- "?/lua/?.lua",
|
||||
-- "?/lua/?/init.lua",
|
||||
},
|
||||
-- pathStrict = true,
|
||||
},
|
||||
workspace = {
|
||||
library = {
|
||||
vim.env.VIMRUNTIME,
|
||||
"~/repos/awesome-code-doc",
|
||||
"/usr/share/awesome/lib",
|
||||
vim.fn.stdpath("data") .. "/lazy",
|
||||
},
|
||||
checkThirdParty = false,
|
||||
},
|
||||
hint = {
|
||||
enable = false,
|
||||
arrayIndex = "Disable",
|
||||
await = true,
|
||||
paramName = "All",
|
||||
paramType = true,
|
||||
semicolon = "Disable",
|
||||
setType = true,
|
||||
},
|
||||
telemetry = { enable = false },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
---@type ServerConfig
|
||||
return {
|
||||
enable = true,
|
||||
mason = { "mesonlsp" },
|
||||
lspconfig = {
|
||||
---@type fun(filename: string, bufnr: number): string?
|
||||
root_dir = function(filename, bufnr)
|
||||
local parent = require("lspconfig").util.find_git_ancestor(filename)
|
||||
if not parent then
|
||||
local win = vim.fn.bufwinid(bufnr)
|
||||
parent = vim.fn.getcwd(win)
|
||||
end
|
||||
return parent
|
||||
end,
|
||||
settings = {
|
||||
others = {
|
||||
disableInlayHints = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
---@type ServerConfig
|
||||
return {
|
||||
enable = true,
|
||||
mason = { "pyright" },
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"python",
|
||||
},
|
||||
cmd = { "pyright-langserver", "--stdio" },
|
||||
single_file_support = true,
|
||||
-- https://microsoft.github.io/pyright/#/settings
|
||||
settings = {
|
||||
python = {
|
||||
analysis = {
|
||||
autoSearchPaths = true,
|
||||
diagnosticMode = "openFilesOnly",
|
||||
useLibraryCodeForTypes = true,
|
||||
typeCheckingMode = "strict",
|
||||
},
|
||||
},
|
||||
pyright = {
|
||||
disableLanguageServices = true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
-- spec: https://rust-analyzer.github.io/manual.html#configuration
|
||||
|
||||
return {
|
||||
enable = true,
|
||||
mason = {
|
||||
name = "rust-analyzer",
|
||||
-- version = "",
|
||||
},
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"rust",
|
||||
},
|
||||
cmd = { "rust-analyzer", },
|
||||
single_file_support = true,
|
||||
settings = {
|
||||
["rust-analyzer"] = {
|
||||
inlayHints = {
|
||||
chainingHints = {
|
||||
enable = false,
|
||||
},
|
||||
parameterHints = {
|
||||
enable = false,
|
||||
},
|
||||
typeHints = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
--[[ assist = {
|
||||
emitMustUse = false,
|
||||
expressionFillDefault = false,
|
||||
},
|
||||
cachePriming = {
|
||||
enable = false,
|
||||
},
|
||||
cargo = {
|
||||
autoreload = false,
|
||||
buildScripts = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
checkOnSave = false,
|
||||
completion = {
|
||||
autoimport = {
|
||||
enable = false,
|
||||
},
|
||||
autoself = {
|
||||
enable = false,
|
||||
},
|
||||
callable = {
|
||||
snippets = false,
|
||||
},
|
||||
fullFunctionSignatures = {
|
||||
enable = false,
|
||||
},
|
||||
postfix = {
|
||||
enable = false,
|
||||
},
|
||||
privateEditable = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
diagnostics = {
|
||||
enable = false,
|
||||
},
|
||||
highlightRelated = {
|
||||
breakPoints = {
|
||||
enable = false,
|
||||
},
|
||||
closureCaptures = {
|
||||
enable = false,
|
||||
},
|
||||
exitPoints = {
|
||||
enable = false,
|
||||
},
|
||||
references = {
|
||||
enable = false,
|
||||
},
|
||||
yieldPoints = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
hover = {
|
||||
actions = {
|
||||
enable = false,
|
||||
},
|
||||
documentation = {
|
||||
enable = true,
|
||||
},
|
||||
links = {
|
||||
enable = false,
|
||||
},
|
||||
memoryLayout = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
imports = {
|
||||
group = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
inlayHints = {
|
||||
bindingModeHints = {
|
||||
enable = false,
|
||||
},
|
||||
chainingHints = {
|
||||
enable = false,
|
||||
},
|
||||
closingBraceHints = {
|
||||
enable = false,
|
||||
},
|
||||
closureCaptureHints = {
|
||||
enable = false,
|
||||
},
|
||||
closureReturnTypeHints = {
|
||||
enable = false,
|
||||
},
|
||||
discriminantHints = {
|
||||
enable = false,
|
||||
},
|
||||
expressionAdjustmentHints = {
|
||||
enable = false,
|
||||
},
|
||||
lifetimeElisionHints = {
|
||||
enable = false,
|
||||
},
|
||||
parameterHints = {
|
||||
enable = false,
|
||||
},
|
||||
reborrowHints = {
|
||||
enable = false,
|
||||
},
|
||||
typeHints = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
joinLines = {
|
||||
joinAssignments = false,
|
||||
joinElseIf = false,
|
||||
removeTrailingComma = false,
|
||||
unwrapTrivialBlock = false,
|
||||
},
|
||||
lens = {
|
||||
enable = false,
|
||||
},
|
||||
notifications = {
|
||||
cargoTomlNotFound = false,
|
||||
},
|
||||
procMacro = {
|
||||
enable = false,
|
||||
},
|
||||
references = {
|
||||
excludeImports = false,
|
||||
},
|
||||
rustfmt = {
|
||||
rangeFormatting = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
semanticHighlighting = {
|
||||
doc = {
|
||||
comment = {
|
||||
inject = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
},
|
||||
nonStandardTokens = false,
|
||||
operator = {
|
||||
enable = false,
|
||||
},
|
||||
punctuation = {
|
||||
enable = false,
|
||||
},
|
||||
strings = {
|
||||
enable = false,
|
||||
},
|
||||
},
|
||||
signatureInfo = {
|
||||
documentation = {
|
||||
enable = true,
|
||||
},
|
||||
},
|
||||
typing = {
|
||||
autoClosingAngleBrackets = {
|
||||
enable = false,
|
||||
},
|
||||
}, ]]
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
-- spec: https://github.com/zigtools/zls#configuration-options
|
||||
|
||||
---@type ServerConfig
|
||||
return {
|
||||
enable = true,
|
||||
dependencies = {
|
||||
"zig",
|
||||
},
|
||||
-- mason = {
|
||||
-- name = "zls",
|
||||
-- -- version = "",
|
||||
-- },
|
||||
lspconfig = {
|
||||
filetypes = {
|
||||
"zig",
|
||||
"zir",
|
||||
},
|
||||
cmd = { "zls", },
|
||||
single_file_support = true,
|
||||
settings = {
|
||||
zls = {
|
||||
warn_style = true,
|
||||
highlight_global_var_declarations = true,
|
||||
inlay_hints_show_variable_type_hints = false,
|
||||
inlay_hints_show_struct_literal_field_type = false,
|
||||
inlay_hints_show_parameter_name = false,
|
||||
inlay_hints_show_builtin = false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
-- Mappings.
|
||||
-- See `:help vim.lsp.*` for documentation on any of the below functions
|
||||
|
||||
local utils = require("ow.utils")
|
||||
|
||||
---@class Keymap
|
||||
---@field mode string|string[]
|
||||
---@field lhs string
|
||||
---@field rhs string|function
|
||||
---@field opts? vim.keymap.set.Opts
|
||||
|
||||
local MODE_TYPES = { "n", "v", "s", "x", "o", "i", "l", "c" }
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<number, vim.api.keyset.keymap[]>
|
||||
M.old = {}
|
||||
|
||||
---@type table<number, Keymap[]>
|
||||
M.new = {}
|
||||
|
||||
--- Load LSP keybinds
|
||||
---@param server Server
|
||||
function M:init(server, bufnr)
|
||||
self.old[bufnr] = {}
|
||||
for _, mode in ipairs(MODE_TYPES) do
|
||||
vim.tbl_extend(
|
||||
"error",
|
||||
self.old[bufnr],
|
||||
vim.api.nvim_buf_get_keymap(bufnr, mode)
|
||||
)
|
||||
end
|
||||
|
||||
self.new[bufnr] = {
|
||||
{ mode = { "n" }, lhs = "<leader>df", rhs = vim.diagnostic.open_float },
|
||||
{
|
||||
mode = { "n" },
|
||||
lhs = "[d",
|
||||
rhs = function()
|
||||
vim.diagnostic.jump({ count = -1, float = true })
|
||||
end,
|
||||
},
|
||||
{
|
||||
mode = { "n" },
|
||||
lhs = "]d",
|
||||
rhs = function()
|
||||
vim.diagnostic.jump({ count = 1, float = true })
|
||||
end,
|
||||
},
|
||||
{ mode = { "n" }, lhs = "gD", rhs = vim.lsp.buf.declaration },
|
||||
{
|
||||
mode = { "n", "i" },
|
||||
lhs = "<C-k>",
|
||||
rhs = function()
|
||||
vim.lsp.buf.hover({ border = "single" })
|
||||
end,
|
||||
},
|
||||
{
|
||||
mode = { "n", "i" },
|
||||
lhs = "<C-j>",
|
||||
rhs = function()
|
||||
vim.lsp.buf.signature_help({ border = "single" })
|
||||
end,
|
||||
},
|
||||
{
|
||||
mode = { "n", "i" },
|
||||
lhs = "<C-h>",
|
||||
rhs = vim.lsp.buf.document_highlight,
|
||||
},
|
||||
{ mode = { "n", "x" }, lhs = "<leader>lf", rhs = vim.lsp.buf.format },
|
||||
{
|
||||
mode = { "n" },
|
||||
lhs = "<leader>ld",
|
||||
rhs = function()
|
||||
vim.diagnostic.enable(not vim.diagnostic.is_enabled())
|
||||
end,
|
||||
},
|
||||
{
|
||||
mode = { "n", "i" },
|
||||
lhs = "<C-l>",
|
||||
rhs = function()
|
||||
vim.lsp.buf.clear_references()
|
||||
vim.cmd.nohlsearch()
|
||||
vim.schedule(vim.cmd.diffupdate)
|
||||
return "<C-l>"
|
||||
end,
|
||||
opts = { expr = true },
|
||||
},
|
||||
}
|
||||
|
||||
local telescope = utils.try_require("telescope.builtin")
|
||||
|
||||
if telescope then
|
||||
vim.list_extend(self.new[bufnr], {
|
||||
{ mode = "n", lhs = "<leader>dl", rhs = telescope.diagnostics },
|
||||
{ mode = "n", lhs = "grt", rhs = telescope.lsp_type_definitions, },
|
||||
{ mode = "n", lhs = "gd", rhs = telescope.lsp_definitions },
|
||||
{ mode = "n", lhs = "gri", rhs = telescope.lsp_implementations },
|
||||
{ mode = "n", lhs = "grr", rhs = telescope.lsp_references },
|
||||
})
|
||||
else
|
||||
vim.list_extend(self.new[bufnr], {
|
||||
{ mode = "n", lhs = "<leader>dl", rhs = vim.diagnostic.setloclist },
|
||||
{ mode = "n", lhs = "grt", rhs = vim.lsp.buf.type_definition, },
|
||||
{ mode = "n", lhs = "gd", rhs = vim.lsp.buf.definition },
|
||||
})
|
||||
end
|
||||
|
||||
if server.config.keymaps then
|
||||
vim.list_extend(self.new[bufnr], server.config.keymaps)
|
||||
end
|
||||
|
||||
for _, keymap in ipairs(self.new[bufnr]) do
|
||||
keymap.opts = vim.tbl_extend(
|
||||
"force",
|
||||
keymap.opts or {},
|
||||
{ buffer = bufnr, remap = true }
|
||||
)
|
||||
vim.keymap.set(keymap.mode, keymap.lhs, keymap.rhs, keymap.opts)
|
||||
end
|
||||
end
|
||||
|
||||
function M:deinit(bufnr)
|
||||
if self.new[bufnr] then
|
||||
for _, keymap in ipairs(self.new[bufnr]) do
|
||||
-- pcall to avoid error if keymap was already removed,
|
||||
-- for example if server.config.keymaps overrides a default LSP keymap
|
||||
pcall(vim.keymap.del, keymap.mode, keymap.lhs, { buffer = bufnr })
|
||||
end
|
||||
self.new[bufnr] = nil
|
||||
end
|
||||
|
||||
if self.old[bufnr] then
|
||||
for _, keymap in ipairs(self.old[bufnr]) do
|
||||
vim.cmd.mapset(keymap)
|
||||
end
|
||||
self.old[bufnr] = nil
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,401 @@
|
||||
local utils = require("ow.utils")
|
||||
|
||||
---@class Linter
|
||||
---@field name string
|
||||
---@field namespace number
|
||||
---@field augroup number
|
||||
---@field buffers number[]
|
||||
---@field config LinterConfig
|
||||
M = {}
|
||||
M.__index = M
|
||||
|
||||
---@alias Group
|
||||
---| "lnum"
|
||||
---| "end_lnum"
|
||||
---| "col"
|
||||
---| "end_col"
|
||||
---| "severity"
|
||||
---| "message"
|
||||
---| "source"
|
||||
---| "code"
|
||||
|
||||
---@class JsonConfig
|
||||
---@field diagnostics_root? string
|
||||
---@field lnum? string
|
||||
---@field end_lnum? string
|
||||
---@field col? string
|
||||
---@field end_col? string
|
||||
---@field severity? string
|
||||
---@field message? string
|
||||
---@field source? string
|
||||
---@field code? string
|
||||
---@field callback? fun(diag: vim.Diagnostic)
|
||||
|
||||
---@class DiagnosticTagMap
|
||||
---@field unnecessary? string[]
|
||||
---@field deprecated? string[]
|
||||
|
||||
---@class LinterConfig
|
||||
---@field cmd string[] Command to run. The following keywords get replaces by the specified values:
|
||||
--- * %file% - path to the current file
|
||||
--- * %filename% - name of the current file
|
||||
---@field stdin? boolean
|
||||
---@field stdout? boolean
|
||||
---@field stderr? boolean
|
||||
---@field pattern? string
|
||||
---@field groups? Group[]
|
||||
---@field severity_map? table<string, vim.diagnostic.Severity>
|
||||
---@field source? string
|
||||
---@field debounce? number
|
||||
---@field json? JsonConfig
|
||||
---@field tags? DiagnosticTagMap
|
||||
---@field zero_idx_lnum? boolean
|
||||
---@field zero_idx_col? boolean
|
||||
M.config = {}
|
||||
|
||||
-- Extract a value from a JSON object using a path
|
||||
---@param obj table The JSON object
|
||||
---@param path string Path to the value (dot notation string)
|
||||
---@return any The value at the specified path, or nil if not found
|
||||
function M.get_json_value(obj, path)
|
||||
if not obj then
|
||||
return nil
|
||||
end
|
||||
|
||||
local current = obj
|
||||
local parts = {}
|
||||
|
||||
for part in path:gmatch("[^%.]+") do
|
||||
table.insert(parts, part)
|
||||
end
|
||||
|
||||
for _, key in ipairs(parts) do
|
||||
if tonumber(key) ~= nil then
|
||||
key = tonumber(key)
|
||||
end
|
||||
|
||||
if type(current) ~= "table" then
|
||||
return nil
|
||||
end
|
||||
|
||||
current = current[key]
|
||||
if current == nil then
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
return current
|
||||
end
|
||||
|
||||
--- Clamp column to line length
|
||||
---@param diag vim.Diagnostic
|
||||
---@param bufnr number
|
||||
function M:clamp_col(diag, bufnr)
|
||||
local lines =
|
||||
vim.api.nvim_buf_get_lines(bufnr, diag.lnum, diag.lnum + 1, false)
|
||||
if #lines == 0 then
|
||||
return
|
||||
end
|
||||
|
||||
local line_len = #lines[1] - 1
|
||||
if diag.col > line_len then
|
||||
diag.col = line_len
|
||||
end
|
||||
end
|
||||
|
||||
--- Add diagnostic tags
|
||||
---@param diag vim.Diagnostic
|
||||
function M:add_tags(diag)
|
||||
if not self.config.tags then
|
||||
return
|
||||
end
|
||||
|
||||
local have_unnecessary = vim.islist(self.config.tags.unnecessary)
|
||||
local have_deprecated = vim.islist(self.config.tags.deprecated)
|
||||
|
||||
if not have_unnecessary and not have_deprecated then
|
||||
return
|
||||
end
|
||||
|
||||
diag._tags = {}
|
||||
|
||||
if
|
||||
have_unnecessary
|
||||
and vim.list_contains(self.config.tags.unnecessary, diag.code)
|
||||
then
|
||||
diag._tags.unnecessary = true
|
||||
diag.severity = vim.diagnostic.severity.HINT
|
||||
end
|
||||
|
||||
if
|
||||
have_deprecated
|
||||
and vim.list_contains(self.config.tags.deprecated, diag.code)
|
||||
then
|
||||
diag._tags.deprecated = true
|
||||
diag.severity = vim.diagnostic.severity.WARN
|
||||
end
|
||||
end
|
||||
|
||||
--- Resolve 0/1-based indexing for lnum/col
|
||||
---@param diag vim.Diagnostic
|
||||
function M:fix_indexing(diag)
|
||||
if not self.config.zero_idx_lnum then
|
||||
if diag.lnum then
|
||||
diag.lnum = diag.lnum - 1
|
||||
end
|
||||
|
||||
if diag.end_lnum then
|
||||
diag.end_lnum = diag.end_lnum - 1
|
||||
end
|
||||
end
|
||||
|
||||
if not self.config.zero_idx_col then
|
||||
if diag.col then
|
||||
diag.col = diag.col - 1
|
||||
end
|
||||
|
||||
if diag.end_col then
|
||||
diag.end_col = diag.end_col - 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:process_json_output(json, bufnr)
|
||||
---@type vim.Diagnostic[]
|
||||
local diagnostics = {}
|
||||
|
||||
local items = json
|
||||
if self.config.json.diagnostics_root then
|
||||
items = M.get_json_value(json, self.config.json.diagnostics_root)
|
||||
end
|
||||
|
||||
if type(items) ~= "table" then
|
||||
utils.err("diagnostics root is not an array or object")
|
||||
return
|
||||
end
|
||||
|
||||
if not vim.islist(items) then
|
||||
items = { items }
|
||||
end
|
||||
|
||||
for _, item in ipairs(items) do
|
||||
local diag = {}
|
||||
|
||||
for field, path in pairs(self.config.json) do
|
||||
if field ~= "diagnostics_root" and field ~= "callback" then
|
||||
diag[field] = M.get_json_value(item, path)
|
||||
end
|
||||
end
|
||||
|
||||
diag.source = diag.source or self.config.source
|
||||
|
||||
if
|
||||
diag.severity
|
||||
and self.config.severity_map
|
||||
and self.config.severity_map[diag.severity]
|
||||
then
|
||||
diag.severity = self.config.severity_map[diag.severity]
|
||||
end
|
||||
|
||||
self:fix_indexing(diag)
|
||||
self:clamp_col(diag, bufnr)
|
||||
self:add_tags(diag)
|
||||
|
||||
if type(self.config.json.callback) == "function" then
|
||||
self.config.json.callback(diag)
|
||||
end
|
||||
|
||||
table.insert(diagnostics, diag)
|
||||
end
|
||||
|
||||
return diagnostics
|
||||
end
|
||||
|
||||
--- Validate input
|
||||
---@param name string
|
||||
---@param config LinterConfig
|
||||
---@return boolean
|
||||
function M.validate(name, config)
|
||||
local ok, resp = pcall(vim.validate, {
|
||||
name = { name, "string" },
|
||||
config = { config, "table" },
|
||||
})
|
||||
|
||||
if ok then
|
||||
ok, resp = pcall(vim.validate, {
|
||||
cmd = {
|
||||
config.cmd,
|
||||
function(t)
|
||||
return utils.is_list(t, "string")
|
||||
end,
|
||||
"list of strings",
|
||||
},
|
||||
stdin = { config.stdin, "boolean", true },
|
||||
stdout = { config.stdout, "boolean", true },
|
||||
stderr = { config.stderr, "boolean", true },
|
||||
pattern = { config.pattern, "string", true },
|
||||
groups = {
|
||||
config.groups,
|
||||
function(t)
|
||||
return utils.is_list(t, "string")
|
||||
end,
|
||||
true,
|
||||
"list of strings",
|
||||
},
|
||||
severity_map = {
|
||||
config.severity_map,
|
||||
function(t)
|
||||
return utils.is_map(t, "string", "number")
|
||||
end,
|
||||
true,
|
||||
"map of string and number",
|
||||
},
|
||||
debounce = { config.debounce, "number", true },
|
||||
source = { config.source, "string", true },
|
||||
json = { config.json, "table", true },
|
||||
tags = { config.tags, "table", true },
|
||||
})
|
||||
end
|
||||
|
||||
if not ok then
|
||||
utils.err(("Invalid config for linter:\n%s"):format(resp))
|
||||
return false
|
||||
end
|
||||
|
||||
if not config.json and (not config.pattern or not config.groups) then
|
||||
utils.err("Either json or pattern and groups must be provided")
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function M:run(bufnr)
|
||||
local input
|
||||
|
||||
if self.config.stdin then
|
||||
input = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
end
|
||||
|
||||
local cmd = vim.fn.copy(self.config.cmd)
|
||||
local file = vim.fn.expand("%:.")
|
||||
local filename = vim.fn.expand("%:t")
|
||||
for i, arg in ipairs(cmd) do
|
||||
arg = arg:gsub("%%file%%", file)
|
||||
arg = arg:gsub("%%filename%%", filename)
|
||||
cmd[i] = arg
|
||||
end
|
||||
|
||||
local ok, resp = pcall(
|
||||
vim.system,
|
||||
cmd,
|
||||
{ stdin = input },
|
||||
---@param out vim.SystemCompleted
|
||||
vim.schedule_wrap(function(out)
|
||||
local output
|
||||
|
||||
if self.config.stdout then
|
||||
output = out.stdout or ""
|
||||
end
|
||||
|
||||
if self.config.stderr then
|
||||
output = out.stderr or ""
|
||||
elseif out.stderr and out.stderr ~= "" then
|
||||
utils.err(out.stderr)
|
||||
end
|
||||
|
||||
if self.config.json then
|
||||
local ok, json = pcall(
|
||||
vim.json.decode,
|
||||
output,
|
||||
{ luanil = { object = true, array = true } }
|
||||
)
|
||||
if not ok then
|
||||
utils.err("Failed to parse JSON: " .. json)
|
||||
return
|
||||
end
|
||||
|
||||
local diagnostics = self:process_json_output(json, bufnr)
|
||||
if diagnostics then
|
||||
vim.diagnostic.set(self.namespace, bufnr, diagnostics)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local output_lines = vim.fn.split(output, "\n", false)
|
||||
local diagnostics = {}
|
||||
for _, line in ipairs(output_lines) do
|
||||
local ok, resp = pcall(
|
||||
vim.diagnostic.match,
|
||||
line,
|
||||
self.config.pattern,
|
||||
self.config.groups,
|
||||
self.config.severity_map
|
||||
)
|
||||
|
||||
if not ok then
|
||||
utils.err(tostring(resp))
|
||||
return
|
||||
elseif resp then
|
||||
resp.source = resp.source or self.config.source
|
||||
self:clamp_col(resp, bufnr)
|
||||
self:add_tags(resp)
|
||||
self:fix_indexing(resp)
|
||||
table.insert(diagnostics, resp)
|
||||
end
|
||||
end
|
||||
|
||||
vim.diagnostic.set(self.namespace, bufnr, diagnostics)
|
||||
end)
|
||||
)
|
||||
|
||||
if not ok then
|
||||
utils.err(("Failed to run %s:\n%s"):format(self.config.cmd[1], resp))
|
||||
end
|
||||
end
|
||||
|
||||
function M:init(bufnr)
|
||||
table.insert(self.buffers, bufnr)
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
|
||||
buffer = bufnr,
|
||||
callback = utils.debounce(function()
|
||||
self:run(bufnr)
|
||||
end, self.config.debounce),
|
||||
group = self.augroup,
|
||||
})
|
||||
self:run(bufnr)
|
||||
end
|
||||
|
||||
function M:deinit()
|
||||
for _, bufnr in ipairs(self.buffers) do
|
||||
vim.api.nvim_buf_clear_namespace(bufnr, self.namespace, 0, -1)
|
||||
end
|
||||
self.buffers = {}
|
||||
|
||||
vim.api.nvim_clear_autocmds({ group = self.augroup })
|
||||
end
|
||||
|
||||
--- Create a new instance
|
||||
---@param name string
|
||||
---@param config LinterConfig
|
||||
---@return Linter|nil
|
||||
function M.new(name, config)
|
||||
if not M.validate(name, config) then
|
||||
return
|
||||
end
|
||||
|
||||
config.debounce = config.debounce or 100
|
||||
|
||||
local linter = {
|
||||
name = name,
|
||||
namespace = vim.api.nvim_create_namespace(name),
|
||||
augroup = vim.api.nvim_create_augroup(name, {}),
|
||||
buffers = {},
|
||||
config = config,
|
||||
}
|
||||
|
||||
return setmetatable(linter, M)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,341 @@
|
||||
local utils = require("ow.utils")
|
||||
|
||||
---@class PostInstallStep
|
||||
---@field cmd string[]
|
||||
|
||||
---@class MasonPackage
|
||||
---@field dependencies? MasonPackage[]
|
||||
---@field config MasonPackageConfig
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
||||
---@class MasonPackageConfig
|
||||
---@field [1]? string
|
||||
---@field name? string
|
||||
---@field version? string
|
||||
---@field dependencies? string[]|MasonPackageConfig[]
|
||||
---@field post_install? PostInstallStep[]
|
||||
M.config = {}
|
||||
|
||||
--- Validate MasonPackageConfig
|
||||
---@param config string|MasonPackageConfig
|
||||
---@return boolean
|
||||
function M.validate(config)
|
||||
local ok, resp = pcall(vim.validate, { config = { config, { "table" } } })
|
||||
if ok then
|
||||
ok, resp = pcall(vim.validate, {
|
||||
name = { config.name or config[1], { "string" }, true },
|
||||
version = { config.version, { "string" }, true },
|
||||
dependencies = {
|
||||
config.dependencies,
|
||||
function(f)
|
||||
if not f then
|
||||
return true
|
||||
end
|
||||
|
||||
for _, dep in ipairs(f) do
|
||||
local t = type(dep)
|
||||
if
|
||||
(t ~= "string" and t ~= "table")
|
||||
or (type(dep) == "table" and not M.validate(dep))
|
||||
then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
"list of string|MasonPackageConfig",
|
||||
},
|
||||
post_install = {
|
||||
config.post_install,
|
||||
function(field)
|
||||
if not field then
|
||||
return true
|
||||
end
|
||||
|
||||
if not utils.is_list(field) then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, step in ipairs(field) do
|
||||
local o, r = pcall(vim.validate, { step = { step, { "table" } } })
|
||||
if o then
|
||||
o, r = pcall(vim.validate, {
|
||||
cmd = {
|
||||
step.cmd,
|
||||
function(f)
|
||||
return utils.is_list(f, "string")
|
||||
end,
|
||||
"list of strings or nil",
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
if not o then
|
||||
utils.err(("Invalid config for post_install step: %s"):format(r), "LSP")
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
"list of steps",
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
if not ok then
|
||||
utils.err(
|
||||
("Invalid config for %s:\n%s"):format(config.name or config[1] or config, resp),
|
||||
"LSP"
|
||||
)
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Run post installation steps
|
||||
---@param pkg Package
|
||||
function M:post_install(pkg)
|
||||
---@param step PostInstallStep
|
||||
---@param msg string
|
||||
local function log_err(step, msg)
|
||||
local cmd = table.concat(step.cmd, " ")
|
||||
|
||||
utils.err(
|
||||
("Post installation step for %s:\n`%s`\nfailed with:\n%s"):format(
|
||||
self.config.name,
|
||||
cmd,
|
||||
msg
|
||||
),
|
||||
"LSP"
|
||||
)
|
||||
end
|
||||
|
||||
if self.config.post_install then
|
||||
for _, step in ipairs(self.config.post_install) do
|
||||
utils.info("running post install step", "LSP")
|
||||
local cwd = pkg:get_install_path()
|
||||
local args = step.cmd
|
||||
local prog = table.remove(args, 1)
|
||||
|
||||
if prog:find("[/\\]") then
|
||||
prog = vim.fn.resolve(("%s/%s"):format(cwd, prog))
|
||||
end
|
||||
|
||||
if not utils.is_executable(prog) then
|
||||
log_err(step, "command not executable")
|
||||
return
|
||||
end
|
||||
|
||||
local job = require("plenary.job"):new({
|
||||
command = prog,
|
||||
args = args,
|
||||
cwd = pkg:get_install_path(),
|
||||
enabled_recording = true,
|
||||
on_exit = vim.schedule_wrap(function(job, code, signal)
|
||||
if code ~= 0 or signal ~= 0 then
|
||||
log_err(step, table.concat(job:stderr_result(), "\n"))
|
||||
return
|
||||
end
|
||||
utils.info("post install step done", "LSP")
|
||||
end),
|
||||
})
|
||||
job:start()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Handle installation
|
||||
---@param pkg Package
|
||||
---@param old_version? string
|
||||
---@param new_version? string
|
||||
---@param on_done? fun(success: boolean)
|
||||
function M:handle_install(pkg, old_version, new_version, on_done)
|
||||
local version_str = ""
|
||||
if old_version and new_version then
|
||||
version_str = (" %s -> %s"):format(old_version, new_version)
|
||||
elseif new_version then
|
||||
version_str = " " .. new_version
|
||||
end
|
||||
|
||||
utils.info(("Installing %s%s"):format(self.config.name, version_str), "LSP")
|
||||
local handle = pkg:install({ version = new_version })
|
||||
local err
|
||||
handle:on(
|
||||
"stderr",
|
||||
vim.schedule_wrap(function(msg)
|
||||
err = (err or "") .. msg
|
||||
end)
|
||||
)
|
||||
|
||||
handle:once(
|
||||
"closed",
|
||||
vim.schedule_wrap(function()
|
||||
local is_installed = pkg:is_installed()
|
||||
|
||||
if is_installed then
|
||||
utils.info(("Successfully installed %s"):format(self.config.name), "LSP")
|
||||
self:post_install(pkg)
|
||||
else
|
||||
if err then
|
||||
err = ":\n" .. err
|
||||
else
|
||||
err = ""
|
||||
end
|
||||
|
||||
utils.err(("Failed to install %s%s"):format(self.config.name, err), "LSP")
|
||||
end
|
||||
|
||||
if on_done then
|
||||
on_done(is_installed)
|
||||
end
|
||||
end)
|
||||
)
|
||||
end
|
||||
|
||||
--- Perform installation
|
||||
---@param on_done fun(success: boolean)?
|
||||
function M:install(on_done)
|
||||
local registry = require("mason-registry")
|
||||
local success, result = pcall(registry.get_package, self.config.name)
|
||||
|
||||
if not success then
|
||||
utils.err(("Failed to get package %s:\n%s"):format(self.config.name, result), "LSP")
|
||||
|
||||
if on_done then
|
||||
on_done(false)
|
||||
end
|
||||
|
||||
return
|
||||
end
|
||||
|
||||
local pkg = result
|
||||
|
||||
if not pkg:is_installed() then
|
||||
self:handle_install(pkg, nil, self.config.version, on_done)
|
||||
elseif self.config.version then
|
||||
pkg:get_installed_version(function(ok, version_or_err)
|
||||
if not ok then
|
||||
utils.err(
|
||||
("Failed to check currently installed version for %s:\n%s"):format(
|
||||
self.config.name,
|
||||
version_or_err
|
||||
),
|
||||
"LSP"
|
||||
)
|
||||
elseif self.config.version ~= version_or_err then
|
||||
self:handle_install(pkg, version_or_err, self.config.version, on_done)
|
||||
return
|
||||
end
|
||||
|
||||
if on_done then
|
||||
on_done(ok)
|
||||
end
|
||||
end)
|
||||
else
|
||||
pkg:check_new_version(function(update_available, res)
|
||||
local ok = type(res) == "table" or res == "Package is not outdated."
|
||||
|
||||
if not ok then
|
||||
utils.err(
|
||||
("Failed to check for update for %s:\n%s"):format(self.config.name, res),
|
||||
"LSP"
|
||||
)
|
||||
elseif update_available then
|
||||
self:handle_install(pkg, res.current_version, res.latest_version, on_done)
|
||||
return
|
||||
end
|
||||
|
||||
if on_done then
|
||||
on_done(ok)
|
||||
end
|
||||
end)
|
||||
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
--- Install package dependencies
|
||||
---@param on_done fun(success: boolean)?
|
||||
function M:install_dependencies(on_done)
|
||||
local total = #self.dependencies
|
||||
local completed = 0
|
||||
local all_successful = true
|
||||
|
||||
--- Handle install result
|
||||
---@param success boolean
|
||||
local function handle_result(success)
|
||||
completed = completed + 1
|
||||
|
||||
if not success then
|
||||
all_successful = false
|
||||
end
|
||||
|
||||
if completed == total and on_done then
|
||||
on_done(all_successful)
|
||||
end
|
||||
end
|
||||
|
||||
for _, dep in ipairs(self.dependencies) do
|
||||
dep:install_with_dependencies(handle_result)
|
||||
end
|
||||
end
|
||||
|
||||
--- Install package and any defined dependencies
|
||||
---@param on_done fun(success: boolean)?
|
||||
function M:install_with_dependencies(on_done)
|
||||
--- Handle install result
|
||||
---@param success boolean
|
||||
local function handle_result(success)
|
||||
if success then
|
||||
self:install(on_done)
|
||||
elseif on_done then
|
||||
on_done(success)
|
||||
end
|
||||
end
|
||||
|
||||
require("mason-registry").refresh(function()
|
||||
if self.dependencies and #self.dependencies ~= 0 then
|
||||
self:install_dependencies(handle_result)
|
||||
else
|
||||
self:install(on_done)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
--- Create a new instance
|
||||
---@param config MasonPackageConfig|string
|
||||
---@return MasonPackage|nil
|
||||
function M.new(config)
|
||||
if type(config) == "string" then
|
||||
config = { config }
|
||||
end
|
||||
|
||||
if not M.validate(config) then
|
||||
return
|
||||
end
|
||||
|
||||
config.name = config.name or config[1]
|
||||
|
||||
local pkg = { config = config }
|
||||
|
||||
if pkg.config.dependencies then
|
||||
pkg.dependencies = {}
|
||||
|
||||
for _, dep_cfg in ipairs(config.dependencies) do
|
||||
local dep = M.new(dep_cfg)
|
||||
if dep then
|
||||
table.insert(pkg.dependencies, dep)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable(pkg, M)
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,344 @@
|
||||
local keymap = require("ow.lsp.keymap")
|
||||
local utils = require("ow.utils")
|
||||
|
||||
---@class Linter
|
||||
local Linter = require("ow.lsp.linter")
|
||||
|
||||
---@class MasonPackage
|
||||
local MasonPackage = require("ow.lsp.package")
|
||||
|
||||
---@class Server
|
||||
---@field name? string
|
||||
---@field mason? MasonPackage
|
||||
---@field client? vim.lsp.Client
|
||||
---@field attached_buffers? number[]
|
||||
---@field manager lspconfig.Manager
|
||||
---@field linters? Linter[]
|
||||
---@field config ServerConfig
|
||||
local M = {}
|
||||
|
||||
M.__index = M
|
||||
|
||||
---@class ServerConfig
|
||||
---@field enable? boolean
|
||||
---@field dependencies? string[]
|
||||
---@field mason? string|MasonPackageConfig
|
||||
---@field keymaps? Keymap[]
|
||||
---@field linters? LinterConfig[]
|
||||
---@field lspconfig? lspconfig.Config
|
||||
M.config = {}
|
||||
|
||||
--- Validate ServerConfig
|
||||
---@param config ServerConfig
|
||||
---@return boolean
|
||||
function M.validate(name, config)
|
||||
local ok, resp = pcall(vim.validate, { config = { config, { "table" } } })
|
||||
|
||||
if ok then
|
||||
ok, resp = pcall(vim.validate, {
|
||||
enable = { config.enable, { "boolean" }, true },
|
||||
dependencies = {
|
||||
config.dependencies,
|
||||
function(f)
|
||||
return utils.is_list_or_nil(f, "string")
|
||||
end,
|
||||
"list of strings or nil",
|
||||
},
|
||||
mason = { config.mason, { "string", "table" }, true },
|
||||
keymaps = {
|
||||
config.keymaps,
|
||||
function(f)
|
||||
if not f then
|
||||
return true
|
||||
end
|
||||
|
||||
if not utils.is_list(f, "table") then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, key in ipairs(f) do
|
||||
local o, r = pcall(vim.validate, {
|
||||
mode = { key.mode, { "string", "table" } },
|
||||
lhs = { key.lhs, "string" },
|
||||
rhs = { key.rhs, { "string", "function" } },
|
||||
opts = { key.opts, "table", true },
|
||||
})
|
||||
|
||||
if not o then
|
||||
utils.err(("Invalid keymap:\n%s"):format(r))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
"list of keymaps",
|
||||
},
|
||||
lspconfig = { config.lspconfig, { "table" }, true },
|
||||
})
|
||||
end
|
||||
|
||||
if not ok then
|
||||
utils.err(("Invalid config for %s:\n%s"):format(name, resp))
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Called when language server attaches
|
||||
---@param client vim.lsp.Client
|
||||
---@param bufnr integer
|
||||
function M:on_attach(client, bufnr)
|
||||
if self.client and self.client.id ~= client.id then
|
||||
self.client:stop(true)
|
||||
end
|
||||
self.client = client
|
||||
self.attached_buffers = self.attached_buffers or {}
|
||||
table.insert(self.attached_buffers, bufnr)
|
||||
|
||||
keymap:init(self, bufnr)
|
||||
if self.linters then
|
||||
for _, linter in ipairs(self.linters) do
|
||||
local bin = linter.config.cmd[1]
|
||||
if utils.is_executable(bin) then
|
||||
linter:init(bufnr)
|
||||
else
|
||||
utils.warn(
|
||||
("Not adding %s because it is not installed"):format(bin)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- For document highlight
|
||||
vim.cmd.highlight({ "link LspReferenceRead Visual", bang = true })
|
||||
vim.cmd.highlight({ "link LspReferenceText Visual", bang = true })
|
||||
vim.cmd.highlight({ "link LspReferenceWrite Visual", bang = true })
|
||||
|
||||
---@alias lsp.Client vim.lsp.Client
|
||||
-- require("lsp_compl").attach(client, bufnr, {
|
||||
-- server_side_fuzzy_completion = true,
|
||||
-- })
|
||||
end
|
||||
|
||||
--- Configure the LSP client
|
||||
function M:configure_client()
|
||||
local lspconfig = require("lspconfig")
|
||||
|
||||
local capabilities = vim.lsp.protocol.make_client_capabilities()
|
||||
local cmp_nvim_lsp = utils.try_require("cmp_nvim_lsp")
|
||||
if cmp_nvim_lsp then
|
||||
capabilities = vim.tbl_deep_extend(
|
||||
"force",
|
||||
capabilities,
|
||||
cmp_nvim_lsp.default_capabilities()
|
||||
)
|
||||
end
|
||||
|
||||
-- local epo = utils.try_require("epo")
|
||||
-- if epo then
|
||||
-- capabilities = vim.tbl_deep_extend(
|
||||
-- "force",
|
||||
-- capabilities,
|
||||
-- epo.register_cap()
|
||||
-- )
|
||||
-- end
|
||||
|
||||
-- local lsp_compl = utils.try_require("lsp_compl")
|
||||
-- if lsp_compl then
|
||||
-- capabilities = vim.tbl_deep_extend("force", capabilities, lsp_compl.capabilities())
|
||||
-- end
|
||||
--
|
||||
|
||||
self.config.lspconfig.capabilities = capabilities
|
||||
self.config.lspconfig.on_attach = function(client, bufnr)
|
||||
local ok, ret = pcall(self.on_attach, self, client, bufnr)
|
||||
if not ok then
|
||||
utils.err(
|
||||
("Failed to load on_attach for %s:\n%s"):format(self.name, ret),
|
||||
"lsp.server:configure_client"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local ok, ret = pcall(lspconfig[self.name].setup, self.config.lspconfig)
|
||||
if not ok then
|
||||
utils.err(
|
||||
("Failed to setup LSP server %s with lspconfig: %s"):format(
|
||||
self.name,
|
||||
ret
|
||||
)
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
self.manager = lspconfig[self.name].manager
|
||||
for _, bufnr in ipairs(self:get_ft_buffers()) do
|
||||
self.manager:try_add_wrapper(bufnr)
|
||||
end
|
||||
|
||||
if self.config.linters then
|
||||
self.linters = {}
|
||||
for i, config in ipairs(self.config.linters) do
|
||||
local linter =
|
||||
Linter.new(("%s_linter%d"):format(self.name, i), config)
|
||||
|
||||
if linter then
|
||||
table.insert(self.linters, linter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:get_ft_buffers()
|
||||
local filetypes = self.config.lspconfig.filetypes or {}
|
||||
if not vim.list_contains(filetypes, self.config.lspconfig.filetype) then
|
||||
table.insert(filetypes, self.config.lspconfig.filetype)
|
||||
end
|
||||
if #filetypes == 0 then
|
||||
return {}
|
||||
end
|
||||
return vim.tbl_filter(function(bufnr)
|
||||
return vim.list_contains(filetypes, vim.bo[bufnr].filetype)
|
||||
end, vim.api.nvim_list_bufs())
|
||||
end
|
||||
|
||||
--- Check for and return missing dependencies
|
||||
---@return table<string>
|
||||
function M:get_missing_unmanaged_deps()
|
||||
local missing_deps = {}
|
||||
if self.config.dependencies ~= nil then
|
||||
for _, dep in ipairs(self.config.dependencies) do
|
||||
if not utils.is_executable(dep) then
|
||||
table.insert(missing_deps, dep)
|
||||
end
|
||||
end
|
||||
end
|
||||
return missing_deps
|
||||
end
|
||||
|
||||
--- Install LSP server
|
||||
---@param on_done fun(success: boolean)?
|
||||
function M:install(on_done)
|
||||
--- Handle install result
|
||||
---@param success boolean
|
||||
local function handle_result(success)
|
||||
if not success then
|
||||
self.config.enable = false
|
||||
end
|
||||
if on_done then
|
||||
on_done(success)
|
||||
end
|
||||
end
|
||||
|
||||
self.mason:install_with_dependencies(handle_result)
|
||||
end
|
||||
|
||||
--- Setup LSP server
|
||||
function M:setup(on_done)
|
||||
local missing_deps = self:get_missing_unmanaged_deps()
|
||||
|
||||
if #missing_deps > 0 then
|
||||
utils.warn(
|
||||
(
|
||||
"Disabling %s because the following package(s)"
|
||||
.. "are not installed: %s"
|
||||
):format(self.name, table.concat(missing_deps, ", "))
|
||||
)
|
||||
self.config.enable = false
|
||||
return
|
||||
end
|
||||
|
||||
if self.mason then
|
||||
self:install(function(success)
|
||||
if success then
|
||||
self:configure_client()
|
||||
end
|
||||
|
||||
if on_done then
|
||||
on_done(success)
|
||||
end
|
||||
end)
|
||||
elseif vim.fn.executable(self.config.lspconfig.cmd[1]) == 1 then
|
||||
self:configure_client()
|
||||
else
|
||||
utils.warn(self.name .. " not installed, disabling")
|
||||
self.config.enable = false
|
||||
end
|
||||
end
|
||||
|
||||
--- Load autocmd for setting up LSP server upon entering a buffer of related
|
||||
--- filetype
|
||||
function M:init(on_done)
|
||||
local group = vim.api.nvim_create_augroup("lsp_bootstrap_" .. self.name, {})
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
once = true,
|
||||
pattern = self.config.lspconfig.filetypes or {},
|
||||
callback = function()
|
||||
self:setup(on_done)
|
||||
end,
|
||||
group = group,
|
||||
})
|
||||
end
|
||||
|
||||
function M:deinit()
|
||||
if self.attached_buffers then
|
||||
for _, bufnr in ipairs(self.attached_buffers) do
|
||||
keymap:deinit(bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
if self.client then
|
||||
self.client:stop(true)
|
||||
self.client = nil
|
||||
end
|
||||
|
||||
vim.api.nvim_clear_autocmds({ group = "lsp_bootstrap_" .. self.name })
|
||||
|
||||
if self.linters then
|
||||
for _, linter in ipairs(self.linters) do
|
||||
linter:deinit()
|
||||
end
|
||||
|
||||
self.linters = nil
|
||||
end
|
||||
|
||||
require("lspconfig")[self.name] = nil
|
||||
end
|
||||
|
||||
--- Create a new instance
|
||||
---@param name string
|
||||
---@param config? ServerConfig
|
||||
---@return Server|nil
|
||||
function M.new(name, config)
|
||||
config = config or {}
|
||||
|
||||
if not M.validate(name, config) then
|
||||
return
|
||||
end
|
||||
|
||||
local ok, resp = pcall(require, "lspconfig.configs." .. name)
|
||||
|
||||
if not ok then
|
||||
utils.err(
|
||||
("Server with name %s does not exist in lspconfig"):format(name)
|
||||
)
|
||||
return
|
||||
end
|
||||
|
||||
config.lspconfig =
|
||||
vim.tbl_deep_extend("keep", config.lspconfig or {}, resp.default_config)
|
||||
local server = { name = name, config = config }
|
||||
|
||||
if server.config.mason then
|
||||
local pkg = MasonPackage.new(server.config.mason)
|
||||
if pkg then
|
||||
server.mason = pkg
|
||||
end
|
||||
end
|
||||
|
||||
return setmetatable(server, M)
|
||||
end
|
||||
|
||||
return M
|
||||
+4144
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,176 @@
|
||||
-- https://github.com/hrsh7th/nvim-cmp
|
||||
|
||||
local word_pattern = "[%w_.]"
|
||||
|
||||
local function has_words_before()
|
||||
unpack = unpack or table.unpack
|
||||
local line, col = unpack(vim.api.nvim_win_get_cursor(0))
|
||||
return col ~= 0
|
||||
and vim.api
|
||||
.nvim_buf_get_lines(0, line - 1, line, true)[1]
|
||||
:sub(col, col)
|
||||
:match(word_pattern)
|
||||
~= nil
|
||||
end
|
||||
|
||||
local function has_words_after()
|
||||
unpack = unpack or table.unpack
|
||||
local line, col = unpack(vim.api.nvim_win_get_cursor(0))
|
||||
return col ~= 0
|
||||
and vim.api
|
||||
.nvim_buf_get_lines(0, line - 1, line, true)[1]
|
||||
:sub(col + 1, col + 1)
|
||||
:match(word_pattern)
|
||||
~= nil
|
||||
end
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"hrsh7th/nvim-cmp",
|
||||
dependencies = {
|
||||
"saadparwaiz1/cmp_luasnip",
|
||||
"hrsh7th/cmp-path",
|
||||
"hrsh7th/cmp-cmdline",
|
||||
"hrsh7th/cmp-nvim-lsp",
|
||||
{
|
||||
"L3MON4D3/LuaSnip",
|
||||
config = function()
|
||||
require("luasnip.loaders.from_vscode").lazy_load()
|
||||
end,
|
||||
build = (
|
||||
require("ow.utils").os_name ~= "Windows_NT"
|
||||
and "make install_jsregexp"
|
||||
or nil
|
||||
),
|
||||
version = "2.*",
|
||||
dependencies = { "rafamadriz/friendly-snippets" },
|
||||
},
|
||||
},
|
||||
config = function()
|
||||
local cmp = require("cmp")
|
||||
local luasnip = require("luasnip")
|
||||
local utils = require("ow.utils")
|
||||
local lspkind = utils.try_require("lspkind")
|
||||
|
||||
---@type cmp.ConfigSchema
|
||||
local opts = {
|
||||
-- enabled = function()
|
||||
-- return has_words_before()
|
||||
-- end,
|
||||
preselect = "None",
|
||||
completion = {
|
||||
autocomplete = { "InsertEnter", "TextChanged" },
|
||||
keyword_length = 1,
|
||||
},
|
||||
snippet = {
|
||||
expand = function(args)
|
||||
luasnip.lsp_expand(args.body)
|
||||
end,
|
||||
},
|
||||
formatting = {
|
||||
format = function(entry, vim_item)
|
||||
if lspkind then
|
||||
vim_item = lspkind.cmp_format({
|
||||
mode = "symbol",
|
||||
maxwidth = 50,
|
||||
ellipsis_char = "...",
|
||||
before = function(_, item)
|
||||
item.dup = 0 -- remove duplicates, see nvim-cmp #511
|
||||
return item
|
||||
end,
|
||||
})(entry, vim_item)
|
||||
end
|
||||
|
||||
return vim_item
|
||||
end,
|
||||
},
|
||||
|
||||
mapping = {
|
||||
["<tab>"] = cmp.mapping(function(fallback)
|
||||
if cmp.visible() then
|
||||
cmp.select_next_item({
|
||||
behavior = cmp.SelectBehavior.Select,
|
||||
})
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end),
|
||||
["<S-tab>"] = cmp.mapping(function(fallback)
|
||||
if cmp.visible() then
|
||||
cmp.select_prev_item({
|
||||
behavior = cmp.SelectBehavior.Select,
|
||||
})
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end),
|
||||
["<C-n>"] = cmp.mapping.select_next_item({
|
||||
behavior = cmp.SelectBehavior.Select,
|
||||
}),
|
||||
["<C-p>"] = cmp.mapping.select_prev_item({
|
||||
behavior = cmp.SelectBehavior.Select,
|
||||
}),
|
||||
["<CR>"] = cmp.mapping(function(fallback)
|
||||
if cmp.visible() and cmp.get_active_entry() then
|
||||
cmp.confirm({
|
||||
select = false,
|
||||
behavior = cmp.ConfirmBehavior.Replace,
|
||||
})
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end),
|
||||
["<C-y>"] = cmp.mapping.confirm({
|
||||
select = true,
|
||||
behavior = cmp.ConfirmBehavior.Replace,
|
||||
}),
|
||||
["<C-x><C-o>"] = cmp.mapping.complete(),
|
||||
["<C-l>"] = function(fallback)
|
||||
if luasnip.locally_jumpable(1) then
|
||||
luasnip.jump(1)
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end,
|
||||
["<C-h>"] = function(fallback)
|
||||
if luasnip.locally_jumpable(-1) then
|
||||
luasnip.jump(-1)
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end,
|
||||
},
|
||||
sources = {
|
||||
{ name = "nvim_lsp" },
|
||||
{ name = "luasnip" },
|
||||
{ name = "orgmode" },
|
||||
{ name = "path" },
|
||||
},
|
||||
}
|
||||
|
||||
if utils.has_module("moonfly") then
|
||||
local winhighlight = {
|
||||
winhighlight = "Normal:NormalFloat,FloatBorder:FloatBorder,CursorLine:PmenuSel",
|
||||
}
|
||||
opts.window = {
|
||||
completion = cmp.config.window.bordered(winhighlight),
|
||||
documentation = cmp.config.window.bordered(winhighlight),
|
||||
}
|
||||
end
|
||||
|
||||
cmp.setup(opts)
|
||||
cmp.setup.cmdline("/", {
|
||||
mapping = cmp.mapping.preset.cmdline(),
|
||||
sources = { { name = "buffer" } },
|
||||
})
|
||||
cmp.setup.cmdline(":", {
|
||||
mapping = cmp.mapping.preset.cmdline(),
|
||||
sources = cmp.config.sources({ { name = "path" } }, {
|
||||
{
|
||||
name = "cmdline",
|
||||
option = { ignore_cmds = { "Man", "!" } },
|
||||
},
|
||||
}),
|
||||
})
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
-- https://github.com/numToStr/Comment.nvim
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"numToStr/Comment.nvim",
|
||||
event = "VeryLazy",
|
||||
opts = {
|
||||
--ignore empty lines
|
||||
ignore = "^$",
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
-- https://github.com/mfussenegger/nvim-dap
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"mfussenegger/nvim-dap",
|
||||
keys = {
|
||||
{
|
||||
"<Leader>db",
|
||||
function()
|
||||
require("dap").toggle_breakpoint()
|
||||
end,
|
||||
},
|
||||
{
|
||||
"<F1>",
|
||||
function()
|
||||
require("dap.ui.widgets").hover()
|
||||
end,
|
||||
},
|
||||
{
|
||||
"<F2>",
|
||||
function()
|
||||
require("dap").step_into()
|
||||
end,
|
||||
},
|
||||
{
|
||||
"<F3>",
|
||||
function()
|
||||
require("dap").step_over()
|
||||
end,
|
||||
},
|
||||
{
|
||||
"<F4>",
|
||||
function()
|
||||
require("dap").step_out()
|
||||
end,
|
||||
},
|
||||
{
|
||||
"<F5>",
|
||||
function()
|
||||
require("dap").continue()
|
||||
end,
|
||||
},
|
||||
{
|
||||
"<F6>",
|
||||
function()
|
||||
local widgets = require("dap.ui.widgets")
|
||||
widgets.centered_float(widgets.scopes)
|
||||
end,
|
||||
},
|
||||
{
|
||||
"<F9>",
|
||||
function()
|
||||
require("dap").terminate()
|
||||
end,
|
||||
},
|
||||
},
|
||||
config = function()
|
||||
local dap = require("dap")
|
||||
|
||||
-- https://sourceware.org/gdb/current/onlinedocs/gdb#Debugger-Adapter-Protocol
|
||||
dap.adapters.gdb = {
|
||||
type = "executable",
|
||||
command = "gdb",
|
||||
args = {
|
||||
"--interpreter=dap",
|
||||
"--eval-command",
|
||||
"set print pretty on",
|
||||
"--eval-command",
|
||||
"set startup-with-shell off",
|
||||
},
|
||||
}
|
||||
|
||||
dap.configurations.cpp = {
|
||||
{
|
||||
name = "Launch",
|
||||
type = "gdb",
|
||||
request = "launch",
|
||||
program = function()
|
||||
return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file")
|
||||
end,
|
||||
cwd = "${workspaceFolder}",
|
||||
stopAtBeginningOfMainSubprogram = false,
|
||||
},
|
||||
}
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
-- https://github.com/j-hui/fidget.nvim
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"j-hui/fidget.nvim",
|
||||
tag = "legacy",
|
||||
event = "LspAttach",
|
||||
config = true,
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
-- https://github.com/rbong/vim-flog
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"rbong/vim-flog",
|
||||
---@type LazyKeysSpec[]
|
||||
keys = {
|
||||
{ "<leader>gl", vim.cmd.Flog, mode = "n" },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
-- https://github.com/tpope/vim-fugitive
|
||||
|
||||
local function git_status_tab()
|
||||
vim.cmd.tabnew()
|
||||
vim.cmd("leftabove vertical G")
|
||||
vim.cmd("vertical resize 40")
|
||||
vim.cmd.set("wfw")
|
||||
end
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"tpope/vim-fugitive",
|
||||
event = "VeryLazy",
|
||||
---@type LazyKeysSpec[]
|
||||
keys = {
|
||||
{ "<leader>gd", vim.cmd.Gvdiffsplit, mode = "n" },
|
||||
{ "<leader>gc", function() vim.cmd.G("commit") end, mode = "n" },
|
||||
{ "<leader>ga", function() vim.cmd.G("commit --amend") end, mode = "n" },
|
||||
{ "<leader>gp", function() vim.cmd.G("push") end, mode = "n" },
|
||||
{ "<leader>gg", git_status_tab, mode = "n" },
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
-- https://github.com/lewis6991/gitsigns.nvim
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"lewis6991/gitsigns.nvim",
|
||||
event = "VeryLazy",
|
||||
opts = {
|
||||
on_attach = function(bufnr)
|
||||
local gs = package.loaded.gitsigns
|
||||
vim.keymap.set("n", "<leader>gv", gs.select_hunk, { buffer = bufnr })
|
||||
vim.keymap.set("n", "<leader>gs", gs.stage_hunk, { buffer = bufnr })
|
||||
vim.keymap.set(
|
||||
"x",
|
||||
"<leader>gs",
|
||||
function()
|
||||
gs.stage_hunk({ vim.fn.line("."), vim.fn.line("v") })
|
||||
end,
|
||||
{ buffer = bufnr }
|
||||
)
|
||||
vim.keymap.set({ "n", "x" }, "<leader>gu", gs.undo_stage_hunk, { buffer = bufnr })
|
||||
vim.keymap.set("n", "<leader>gr", gs.reset_hunk, { buffer = bufnr })
|
||||
vim.keymap.set("x", "<leader>gr", ":Gitsigns reset_hunk<CR>", { buffer = bufnr })
|
||||
vim.keymap.set("n", "<leader>g?", gs.preview_hunk, { buffer = bufnr })
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>gb",
|
||||
function()
|
||||
gs.blame_line { full = true, ignore_whitespace = true }
|
||||
end,
|
||||
{ buffer = bufnr }
|
||||
)
|
||||
vim.keymap.set(
|
||||
{ "n", "x" },
|
||||
"]g", function()
|
||||
gs.next_hunk({
|
||||
wrap = true,
|
||||
navigation_message = true,
|
||||
foldopen = true,
|
||||
preview = true,
|
||||
})
|
||||
end
|
||||
)
|
||||
vim.keymap.set(
|
||||
{ "n", "x" },
|
||||
"[g", function()
|
||||
gs.prev_hunk({
|
||||
wrap = true,
|
||||
navigation_message = true,
|
||||
foldopen = true,
|
||||
preview = true,
|
||||
})
|
||||
end
|
||||
)
|
||||
end,
|
||||
attach_to_untracked = true,
|
||||
sign_priority = 100,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
-- https://github.com/onsails/lspkind.nvim
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"onsails/lspkind.nvim",
|
||||
config = function()
|
||||
local ok, _ = pcall(require, "nvim-cmp")
|
||||
if not ok then
|
||||
require("lspkind").init()
|
||||
end
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
-- https://github.com/williamboman/mason.nvim
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"williamboman/mason.nvim",
|
||||
event = "VeryLazy",
|
||||
config = true,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
-- https://github.com/bluz71/vim-moonfly-colors
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"bluz71/vim-moonfly-colors",
|
||||
priority = 1000,
|
||||
lazy = false,
|
||||
name = "moonfly",
|
||||
init = function()
|
||||
vim.g.moonflyNormalFloat = true
|
||||
vim.g.moonflyCursorColor = true
|
||||
vim.g.moonflyWinSeparator = 2
|
||||
vim.g.moonflyVirtualTextColor = true
|
||||
end,
|
||||
config = function()
|
||||
vim.cmd.colorscheme("moonfly")
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
local function toggle_neo_tree()
|
||||
require("neo-tree.command").execute({
|
||||
action = "show",
|
||||
position = "left",
|
||||
toggle = true,
|
||||
reveal = true,
|
||||
})
|
||||
end
|
||||
|
||||
local function focus_neo_tree()
|
||||
require("neo-tree.command").execute({
|
||||
action = "focus",
|
||||
source = "last",
|
||||
})
|
||||
end
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"nvim-neo-tree/neo-tree.nvim",
|
||||
branch = "v3.x",
|
||||
dependencies = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
"MunifTanjim/nui.nvim",
|
||||
},
|
||||
lazy = false,
|
||||
keys = {
|
||||
{
|
||||
"<leader>tt",
|
||||
toggle_neo_tree,
|
||||
},
|
||||
{
|
||||
"<leader>a",
|
||||
focus_neo_tree,
|
||||
},
|
||||
},
|
||||
---@type neotree.Config?
|
||||
opts = {
|
||||
sources = {
|
||||
"filesystem",
|
||||
"git_status",
|
||||
},
|
||||
close_if_last_window = true,
|
||||
default_component_configs = {
|
||||
diagnostics = {
|
||||
symbols = {
|
||||
hint = "H",
|
||||
info = "I",
|
||||
warn = "W",
|
||||
error = "E",
|
||||
},
|
||||
},
|
||||
indent = {
|
||||
with_markers = false,
|
||||
},
|
||||
---@diagnostic disable-next-line: missing-fields
|
||||
icon = {
|
||||
provider = function(icon, _, _)
|
||||
icon.text = ""
|
||||
end,
|
||||
},
|
||||
modified = {
|
||||
symbol = "[+] ",
|
||||
highlight = "NeoTreeModified",
|
||||
},
|
||||
name = {
|
||||
use_git_status_colors = false,
|
||||
trailing_slash = true,
|
||||
},
|
||||
git_status = {
|
||||
symbols = {
|
||||
added = "A",
|
||||
deleted = "D",
|
||||
modified = "M",
|
||||
renamed = "R",
|
||||
untracked = "?",
|
||||
ignored = "",
|
||||
unstaged = "",
|
||||
staged = "+",
|
||||
conflict = "!",
|
||||
},
|
||||
},
|
||||
},
|
||||
window = {
|
||||
mappings = {
|
||||
["<Tab>"] = "open",
|
||||
},
|
||||
},
|
||||
filesystem = {
|
||||
check_gitignore_in_search = false,
|
||||
filtered_items = {
|
||||
show_hidden_count = false,
|
||||
hide_dotfiles = false,
|
||||
hide_gitignored = false,
|
||||
hide_by_name = { ".git" },
|
||||
},
|
||||
follow_current_file = {
|
||||
enabled = true,
|
||||
},
|
||||
hijack_netrw_behavior = "disabled",
|
||||
use_libuv_file_watcher = true,
|
||||
commands = {
|
||||
-- over write default 'delete' command to 'trash'.
|
||||
delete = function(state)
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
local path = state.tree:get_node().path
|
||||
local msg = "Send to trash?"
|
||||
inputs.confirm(msg, function(confirmed)
|
||||
if not confirmed then
|
||||
return
|
||||
end
|
||||
|
||||
vim.fn.system({
|
||||
"gio",
|
||||
"trash",
|
||||
vim.fn.fnameescape(path),
|
||||
})
|
||||
require("neo-tree.sources.manager").refresh(state.name)
|
||||
end)
|
||||
end,
|
||||
|
||||
-- over write default 'delete_visual' command to 'trash' x n.
|
||||
delete_visual = function(state, selected_nodes)
|
||||
local inputs = require("neo-tree.ui.inputs")
|
||||
|
||||
-- get table items count
|
||||
function GetTableLen(tbl)
|
||||
local len = 0
|
||||
for n in pairs(tbl) do
|
||||
len = len + 1
|
||||
end
|
||||
return len
|
||||
end
|
||||
|
||||
local count = GetTableLen(selected_nodes)
|
||||
local msg = "Send " .. count .. " files to trash?"
|
||||
inputs.confirm(msg, function(confirmed)
|
||||
if not confirmed then
|
||||
return
|
||||
end
|
||||
for _, node in ipairs(selected_nodes) do
|
||||
vim.fn.system({
|
||||
"gio",
|
||||
"trash",
|
||||
vim.fn.fnameescape(node.path),
|
||||
})
|
||||
end
|
||||
require("neo-tree.sources.manager").refresh(state.name)
|
||||
end)
|
||||
end,
|
||||
},
|
||||
},
|
||||
event_handlers = {
|
||||
{
|
||||
event = "neo_tree_window_after_open",
|
||||
handler = function(event)
|
||||
vim.api.nvim_set_option_value(
|
||||
"signcolumn",
|
||||
"no",
|
||||
{ scope = "local", win = event.winid }
|
||||
)
|
||||
end,
|
||||
},
|
||||
},
|
||||
},
|
||||
init = toggle_neo_tree,
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
-- https://github.com/rcarriga/nvim-notify
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"rcarriga/nvim-notify",
|
||||
priority = 900,
|
||||
lazy = false,
|
||||
opts = {
|
||||
render = "default",
|
||||
stages = "static",
|
||||
},
|
||||
config = function(_, opts)
|
||||
---@type notify
|
||||
vim.notify = require("notify")
|
||||
vim.notify.setup(opts)
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
-- https://github.com/NvChad/nvim-colorizer.lua
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"NvChad/nvim-colorizer.lua",
|
||||
opts = {
|
||||
user_default_options = {
|
||||
names = false,
|
||||
RGB = false,
|
||||
RRGGBB = true,
|
||||
RRGGBBAA = true,
|
||||
AARRGGBB = true,
|
||||
rgb_fn = true,
|
||||
hsl_fn = true,
|
||||
mode = "virtualtext",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"nvim-treesitter/nvim-treesitter-context",
|
||||
opts = {
|
||||
mode = 'topline',
|
||||
max_lines = 3,
|
||||
multiline_threshold = 1,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
-- https://github.com/NvChad/nvim-colorizer.lua
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"stevearc/oil.nvim",
|
||||
keys = {
|
||||
{
|
||||
"<leader>fe",
|
||||
function()
|
||||
vim.cmd.Oil("--float")
|
||||
end,
|
||||
mode = "n",
|
||||
},
|
||||
},
|
||||
opts = {
|
||||
default_file_explorer = true,
|
||||
columns = {
|
||||
-- "icon",
|
||||
"permissions",
|
||||
"size",
|
||||
"mtime",
|
||||
},
|
||||
constrain_cursor = "name",
|
||||
delete_to_trash = true,
|
||||
float = {
|
||||
max_width = 80,
|
||||
max_height = 40,
|
||||
},
|
||||
skip_confirm_for_simple_edits = true,
|
||||
watch_for_changes = false,
|
||||
keymaps = {
|
||||
["<Esc>"] = "actions.close",
|
||||
["q"] = "actions.close",
|
||||
["<C-s>"] = false,
|
||||
["<C-h>"] = "actions.parent",
|
||||
["<C-l>"] = "actions.select",
|
||||
["<C-r>"] = "actions.refresh",
|
||||
},
|
||||
view_options = {
|
||||
show_hidden = true,
|
||||
natural_order = false,
|
||||
},
|
||||
win_options = {
|
||||
colorcolumn = "",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
-- https://github.com/nvim-orgmode/orgmode
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"nvim-orgmode/orgmode",
|
||||
dependencies = {
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
},
|
||||
opts = {
|
||||
org_agenda_files = { "~/Documents/org/**/*" },
|
||||
org_default_notes_file = "~/Documents/org/notes.org",
|
||||
org_todo_keywords = {
|
||||
"TODO(t)",
|
||||
"ACTIVE(a)",
|
||||
"WAITING(w)",
|
||||
"|",
|
||||
"DONE(d)",
|
||||
"DISCARDED(c)",
|
||||
},
|
||||
org_todo_keyword_faces = {
|
||||
ACTIVE = ":foreground dodgerblue :weight bold",
|
||||
WAITING = ":foreground lightgoldenrod :weight bold",
|
||||
DISCARDED = ":foreground grey :weight bold",
|
||||
},
|
||||
win_split_mode = "float",
|
||||
win_border = "single",
|
||||
org_archive_location = "~/Documents/org/archive.org::",
|
||||
org_log_done = "note",
|
||||
org_log_into_drawer = "LOGBOOK",
|
||||
org_highlight_latex_and_related = "entities",
|
||||
org_agenda_span = "week",
|
||||
org_agenda_skip_scheduled_if_done = true,
|
||||
org_agenda_skip_deadline_if_done = true,
|
||||
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
-- https://github.com/nvim-telescope/telescope.nvim
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"nvim-telescope/telescope.nvim",
|
||||
event = "VeryLazy",
|
||||
dependencies = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
{
|
||||
"nvim-telescope/telescope-fzf-native.nvim",
|
||||
build = "make",
|
||||
},
|
||||
},
|
||||
config = function()
|
||||
local telescope = require("telescope")
|
||||
local builtin = require("telescope.builtin")
|
||||
local actions = require("telescope.actions")
|
||||
|
||||
telescope.setup({
|
||||
defaults = {
|
||||
mappings = {
|
||||
n = {
|
||||
q = actions.close,
|
||||
["<C-c>"] = actions.close,
|
||||
["<C-l>"] = actions.select_default,
|
||||
},
|
||||
},
|
||||
},
|
||||
pickers = {
|
||||
oldfiles = {
|
||||
initial_mode = "normal",
|
||||
},
|
||||
buffers = {
|
||||
initial_mode = "normal",
|
||||
mappings = {
|
||||
n = {
|
||||
["<C-d>"] = actions.delete_buffer + actions.move_to_top,
|
||||
},
|
||||
i = {
|
||||
["<C-d>"] = actions.delete_buffer + actions.move_to_top,
|
||||
},
|
||||
},
|
||||
},
|
||||
diagnostics = {
|
||||
initial_mode = "normal",
|
||||
},
|
||||
lsp_definitions = {
|
||||
initial_mode = "normal",
|
||||
},
|
||||
lsp_type_definitions = {
|
||||
initial_mode = "normal",
|
||||
},
|
||||
lsp_implementations = {
|
||||
initial_mode = "normal",
|
||||
},
|
||||
lsp_references = {
|
||||
initial_mode = "normal",
|
||||
},
|
||||
git_status = {
|
||||
initial_mode = "normal",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>ff",
|
||||
function()
|
||||
builtin.find_files({
|
||||
hidden = true,
|
||||
no_ignore = true,
|
||||
no_ignore_parent = true,
|
||||
previewer = false,
|
||||
})
|
||||
end
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>fr",
|
||||
function()
|
||||
builtin.oldfiles({
|
||||
only_cwd = true,
|
||||
hidden = true,
|
||||
previewer = false,
|
||||
})
|
||||
end
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>fg",
|
||||
function()
|
||||
builtin.live_grep({
|
||||
additional_args = function(_)
|
||||
return {
|
||||
"--hidden",
|
||||
"--iglob=!.venv",
|
||||
"--iglob=!vendor",
|
||||
"--iglob=!.git",
|
||||
}
|
||||
end,
|
||||
previewer = true,
|
||||
})
|
||||
end
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>fb",
|
||||
function()
|
||||
builtin.buffers({ previewer = false, sort_mru = true })
|
||||
end
|
||||
)
|
||||
vim.keymap.set(
|
||||
"n",
|
||||
"<leader>fd",
|
||||
function()
|
||||
builtin.diagnostics({
|
||||
bufnr = 0,
|
||||
})
|
||||
end
|
||||
)
|
||||
|
||||
telescope.load_extension("fzf")
|
||||
telescope.load_extension("notify")
|
||||
vim.keymap.set("n", "<leader>fn", telescope.extensions.notify.notify)
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
-- https://github.com/nvim-treesitter/nvim-treesitter
|
||||
|
||||
---@type LazyPluginSpec
|
||||
return {
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
build = ":TSUpdate",
|
||||
event = "VeryLazy",
|
||||
opts = {
|
||||
ensure_installed = {
|
||||
"c", -- recommended default
|
||||
"lua", -- recommended default
|
||||
"vim", -- recommended default
|
||||
"vimdoc", -- recommended default
|
||||
"query", -- recommended default
|
||||
"luadoc",
|
||||
"phpdoc",
|
||||
"comment",
|
||||
},
|
||||
auto_install = true,
|
||||
highlight = {
|
||||
enable = true,
|
||||
disable = {},
|
||||
|
||||
-- Setting this to true will run `:h syntax` and tree-sitter at the same time.
|
||||
-- Set this to `true` if you depend on 'syntax' being enabled (like for indentation).
|
||||
-- Using this option may slow down your editor, and you may see some duplicate highlights.
|
||||
-- Instead of true it can also be a list of languages
|
||||
additional_vim_regex_highlighting = { "org", "php", "python" },
|
||||
},
|
||||
},
|
||||
config = function(_, opts)
|
||||
require("nvim-treesitter.configs").setup(opts)
|
||||
|
||||
vim.opt.foldmethod = "expr"
|
||||
vim.opt.foldexpr = "nvim_treesitter#foldexpr()"
|
||||
|
||||
-- Disable LSP semantic highlighting for lua comments because it will
|
||||
-- otherwise override highlights from `comment`.
|
||||
vim.api.nvim_set_hl(0, "@lsp.type.comment.lua", {})
|
||||
|
||||
-- To set the priority of semantic highlighting lower than treesitter (100),
|
||||
-- uncomment the line below:
|
||||
-- vim.hl.priorities.semantic_tokens = 99
|
||||
end,
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
local M = {}
|
||||
|
||||
M.os_name = vim.uv.os_uname().sysname
|
||||
|
||||
--- Get the module path of a file
|
||||
---@param file string
|
||||
---@return string|nil
|
||||
local function get_module_path(file)
|
||||
for _, rtp in ipairs(vim.api.nvim_list_runtime_paths()) do
|
||||
if file:sub(1, #rtp) == rtp then
|
||||
file = file:sub(#rtp + 2)
|
||||
|
||||
if file:sub(1, 4) == "lua/" then
|
||||
file = file:sub(5)
|
||||
end
|
||||
|
||||
return file:match("(.*)%.lua$"):gsub("[/\\]", ".")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Send a notification
|
||||
---@param msg string Message to send
|
||||
---@param title? string Title of notification
|
||||
---@param level integer Log level
|
||||
local function notify(msg, title, level)
|
||||
if not title then
|
||||
local info = debug.getinfo(3)
|
||||
local file = info.source
|
||||
and (info.source:sub(1, 1) == "@" and info.source:sub(2) or info.source)
|
||||
or nil
|
||||
local module = file and (get_module_path(file) or file) or nil
|
||||
title = module
|
||||
and module .. (info.name and info.name ~= "" and ":" .. info.name or "")
|
||||
or nil
|
||||
end
|
||||
if title and not pcall(require, "notify") then
|
||||
msg = "[" .. title .. "] " .. msg
|
||||
end
|
||||
vim.notify(msg, level, { title = title })
|
||||
end
|
||||
|
||||
--- Check that an executable is available
|
||||
--- @param exe string Array to look for
|
||||
--- @return boolean
|
||||
function M.is_executable(exe)
|
||||
return vim.fn.executable(exe) == 1
|
||||
end
|
||||
|
||||
--- Check that at least one executable is available
|
||||
--- @param exes table Array of exes
|
||||
--- @return boolean
|
||||
function M.any_installed(exes)
|
||||
for _, e in ipairs(exes) do
|
||||
if M.is_executable(e) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
--- Asserts that an executable is available
|
||||
--- Raises error if missing.
|
||||
--- @param exe string Array to look for
|
||||
function M.assert_installed(exe)
|
||||
assert(M.is_executable(exe), "Missing executable '" .. exe .. "'.")
|
||||
end
|
||||
|
||||
--- Asserts that at least one executable is available
|
||||
--- Raises error if missing.
|
||||
--- @param exes table Array of exes
|
||||
function M.assert_any_installed(exes)
|
||||
assert(
|
||||
M.any_installed(exes),
|
||||
"At least one of the following is required:\n"
|
||||
.. table.concat(exes, ", ")
|
||||
)
|
||||
end
|
||||
|
||||
--- Asserts that a python module is installed
|
||||
---@param mod string The python module to check
|
||||
function M.python3_module_is_installed(mod)
|
||||
if not M.is_executable("python3") then
|
||||
return false
|
||||
end
|
||||
|
||||
local resp = vim.system({ "python3", "-c", "import " .. mod }):wait()
|
||||
return resp.code == 0
|
||||
end
|
||||
|
||||
--- Asserts that a python module is installed
|
||||
---@param mod string The python module to check
|
||||
function M.assert_python3_module_installed(mod)
|
||||
if not M.python3_module_is_installed(mod) then
|
||||
error("Python3 module " .. mod .. " not installed")
|
||||
end
|
||||
end
|
||||
|
||||
--- Send a debug notification
|
||||
---@param msg string Message to send
|
||||
---@param title? string Title of notification
|
||||
function M.debug(msg, title)
|
||||
notify(msg, title, vim.log.levels.DEBUG)
|
||||
end
|
||||
|
||||
--- Send an info notification
|
||||
---@param msg string Message to send
|
||||
---@param title? string Title of notification
|
||||
function M.info(msg, title)
|
||||
notify(msg, title, vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
--- Send a warning notification
|
||||
---@param msg string Message to send
|
||||
---@param title? string Title of notification
|
||||
function M.warn(msg, title)
|
||||
notify(msg, title, vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
--- Send an error notification
|
||||
---@param msg string Message to send
|
||||
---@param title? string Title of notification
|
||||
function M.err(msg, title)
|
||||
notify(msg, title, vim.log.levels.ERROR)
|
||||
end
|
||||
|
||||
--- Attempts to load a module and logs errors on failure.
|
||||
---@param module string The module to attempt to load.
|
||||
---@return any module The loaded module if successful, otherwise nil.
|
||||
function M.try_require(module)
|
||||
local has_module, resp = pcall(require, module)
|
||||
|
||||
if has_module then
|
||||
return resp
|
||||
end
|
||||
|
||||
M.err(("Failed to load module %s:\n%s"):format(module, resp))
|
||||
end
|
||||
|
||||
--- Checks if it is possible to require a module
|
||||
---@param module string
|
||||
---@return boolean
|
||||
function M.has_module(module)
|
||||
local has_module, _ = pcall(require, module)
|
||||
return has_module
|
||||
end
|
||||
|
||||
---@alias OutputStream
|
||||
---| '"stdout"'
|
||||
---| '"stderr"'
|
||||
|
||||
---@class FormatOptions
|
||||
---@field cmd string[] Command to run. The following keywords get replaces by the specified values:
|
||||
--- * %file% - path to the current file
|
||||
--- * %filename% - name of the current file
|
||||
--- * %row_start% - first row of selection
|
||||
--- * %row_end% - last row of selection
|
||||
--- * %col_start% - first column position of selection
|
||||
--- * %col_end% - last column position of selection
|
||||
--- * %byte_start% - byte count of first cell in selection
|
||||
--- * %byte_end% - byte count of last cell in selection
|
||||
---@field output OutputStream What stream to use as the result. May be one of `stdout` or `stderr`.
|
||||
---@field auto_indent? boolean Perform auto indent on formatted range. False by default.
|
||||
---@field only_selection? boolean Only send the selected lines to `stdin`. False by default.
|
||||
---@field ignore_ret? boolean Ignore non-zero return codes
|
||||
|
||||
--- Format buffer
|
||||
---@param opts FormatOptions
|
||||
function M.format(opts)
|
||||
opts = {
|
||||
cmd = opts.cmd,
|
||||
output = opts.output,
|
||||
auto_indent = opts.auto_indent or false,
|
||||
only_selection = opts.only_selection or false,
|
||||
}
|
||||
|
||||
if opts.output ~= "stdout" and opts.output ~= "stderr" then
|
||||
M.err("`output` must be set to either `stdout` or `stderr`.")
|
||||
return
|
||||
end
|
||||
|
||||
local file = vim.fn.expand("%")
|
||||
local filename = vim.fn.expand("%:t")
|
||||
local mode = vim.fn.mode()
|
||||
local is_visual = mode == "v" or mode == "V" or mode == ""
|
||||
|
||||
local row_start, row_end, col_start, col_end, byte_start, byte_end
|
||||
if is_visual then
|
||||
row_start, col_start = unpack(vim.fn.getpos("v"), 2, 3)
|
||||
row_end, col_end = unpack(vim.fn.getpos("."), 2, 3)
|
||||
|
||||
if row_start > row_end then
|
||||
row_start, row_end, col_start, col_end =
|
||||
row_end, row_start, col_end, col_start
|
||||
end
|
||||
|
||||
if mode == "V" then
|
||||
col_start = 1
|
||||
col_end = #vim.fn.getline(row_end)
|
||||
end
|
||||
|
||||
byte_start = vim.fn.line2byte(row_start) + col_start - 1
|
||||
byte_end = vim.fn.line2byte(row_end) + col_end - 1
|
||||
end
|
||||
|
||||
local input
|
||||
if opts.only_selection then
|
||||
input = vim.api.nvim_buf_get_lines(0, row_start - 1, row_end, false)
|
||||
else
|
||||
input = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
end
|
||||
|
||||
for i, arg in ipairs(opts.cmd) do
|
||||
arg = arg:gsub("%%file%%", file)
|
||||
arg = arg:gsub("%%filename%%", filename)
|
||||
if is_visual then
|
||||
arg = arg:gsub("%%row_start%%", row_start)
|
||||
arg = arg:gsub("%%row_end%%", row_end)
|
||||
arg = arg:gsub("%%col_start%%", col_start)
|
||||
arg = arg:gsub("%%col_end%%", col_end)
|
||||
arg = arg:gsub("%%byte_start%%", byte_start)
|
||||
arg = arg:gsub("%%byte_end%%", byte_end)
|
||||
end
|
||||
opts.cmd[i] = arg
|
||||
end
|
||||
|
||||
local stdout, stderr, err
|
||||
local resp = vim.system(opts.cmd, {
|
||||
stdin = input,
|
||||
stdout = opts.output == "stdout" and function(e, data)
|
||||
if data then
|
||||
stdout = stdout and stdout .. data or data
|
||||
end
|
||||
|
||||
if e then
|
||||
err = err and err .. e or e
|
||||
end
|
||||
end,
|
||||
stderr = function(e, data)
|
||||
if data then
|
||||
stderr = stderr and stderr .. data or data
|
||||
end
|
||||
|
||||
if e then
|
||||
err = err and err .. e or e
|
||||
end
|
||||
end,
|
||||
}):wait()
|
||||
|
||||
if err then
|
||||
M.err("Error during formatting:\n%s" .. err)
|
||||
return
|
||||
end
|
||||
|
||||
if
|
||||
not opts.ignore_ret and (resp.code ~= 0 or resp.signal ~= 0)
|
||||
or (opts.output ~= "stderr" and stderr)
|
||||
then
|
||||
local msg = ""
|
||||
if stderr then
|
||||
msg = ":\n" .. stderr
|
||||
end
|
||||
|
||||
M.err(("Failed to format%s"):format(msg))
|
||||
return
|
||||
end
|
||||
|
||||
local output = opts.output == "stdout" and stdout or stderr or ""
|
||||
output = output:gsub("\n$", "")
|
||||
local output_lines = vim.fn.split(output, "\n", true)
|
||||
|
||||
if opts.only_selection then
|
||||
vim.api.nvim_buf_set_lines(
|
||||
0,
|
||||
row_start - 1,
|
||||
row_end,
|
||||
false,
|
||||
output_lines
|
||||
)
|
||||
else
|
||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, output_lines)
|
||||
end
|
||||
|
||||
if opts.auto_indent then
|
||||
if is_visual then
|
||||
vim.api.nvim_command(
|
||||
("%d,%dnormal! =="):format(row_start, row_start + #output_lines)
|
||||
)
|
||||
else
|
||||
vim.api.nvim_command("normal! gg=G")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if `val` is a list of type `t` (if given)
|
||||
---@param val any
|
||||
---@param kt type
|
||||
---@param vt type
|
||||
---@return boolean
|
||||
function M.is_map(val, kt, vt)
|
||||
if type(val) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= kt then
|
||||
return false
|
||||
end
|
||||
|
||||
if type(v) ~= vt then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Check if `val` is a list of type `t` (if given)
|
||||
---@param val any
|
||||
---@param t? type
|
||||
---@return boolean
|
||||
function M.is_list(val, t)
|
||||
if not vim.islist(val) then
|
||||
return false
|
||||
end
|
||||
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
return false
|
||||
end
|
||||
|
||||
if t and type(v) ~= t then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Check if `val` is a list of type `t` (if given), or nil
|
||||
---@param val? any
|
||||
---@param t? type
|
||||
---@return boolean
|
||||
function M.is_list_or_nil(val, t)
|
||||
if val == nil then
|
||||
return true
|
||||
else
|
||||
return M.is_list(val, t)
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates a debounced function that delays execution of `fn` until after `delay` milliseconds have
|
||||
--- elapsed since the last time it was invoked.
|
||||
---@param fn fun(...) Function to be debounced
|
||||
---@param delay number Debounce delay in milliseconds
|
||||
---@return fun(...) function Debounced function
|
||||
function M.debounce(fn, delay)
|
||||
---@type uv_timer_t?
|
||||
local timer = nil
|
||||
|
||||
return function(...)
|
||||
local args = vim.F.pack_len(...)
|
||||
if timer then
|
||||
timer:stop()
|
||||
timer = nil
|
||||
end
|
||||
|
||||
timer = vim.defer_fn(function()
|
||||
timer = nil
|
||||
fn(vim.F.unpack_len(args))
|
||||
end, delay)
|
||||
end
|
||||
end
|
||||
|
||||
--- Creates a debounced function that delays execution of `fn` until after `delay` milliseconds have
|
||||
--- elapsed since the last time it was invoked with the same unique identifier.
|
||||
---@param fn fun(...) Function to be debounced
|
||||
---@param delay number Debounce delay in milliseconds
|
||||
---@return fun(id: any, ...) function Debounced function, where `id` is a unique identifier
|
||||
function M.debounce_with_id(fn, delay)
|
||||
local map = {}
|
||||
|
||||
return function(id, ...)
|
||||
local args = vim.F.pack_len(...)
|
||||
if map[id] then
|
||||
map[id]:stop()
|
||||
map[id] = nil
|
||||
end
|
||||
|
||||
map[id] = vim.defer_fn(function()
|
||||
map[id] = nil
|
||||
fn(vim.F.unpack_len(args))
|
||||
end, delay)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user