fix: namespace all local packages and modules

This commit is contained in:
2025-04-16 23:16:58 +02:00
parent 0c327701a1
commit 16ccb1d107
45 changed files with 43 additions and 32 deletions
+35
View File
@@ -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)
+75
View File
@@ -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,
})
+18
View File
@@ -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
+146
View File
@@ -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
+142
View File
@@ -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
View File
@@ -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
+36
View File
@@ -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,
},
}
+28
View File
@@ -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,
},
}
+20
View File
@@ -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",
},
},
}
+36
View File
@@ -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,
},
},
},
}
+30
View File
@@ -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",
},
},
}
+18
View File
@@ -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",
},
},
},
}
+85
View File
@@ -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,
},
},
},
},
}
+162
View File
@@ -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,
},
},
},
}
+40
View File
@@ -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,
}
},
},
},
},
}
+87
View File
@@ -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 },
},
},
},
}
+21
View File
@@ -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,
},
},
},
}
+26
View File
@@ -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,
},
},
},
}
+191
View File
@@ -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,
},
}, ]]
},
},
},
}
+31
View File
@@ -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,
},
},
},
}
+141
View File
@@ -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
+401
View File
@@ -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
+341
View File
@@ -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
+344
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+176
View File
@@ -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,
}
+11
View File
@@ -0,0 +1,11 @@
-- https://github.com/numToStr/Comment.nvim
---@type LazyPluginSpec
return {
"numToStr/Comment.nvim",
event = "VeryLazy",
opts = {
--ignore empty lines
ignore = "^$",
},
}
+86
View File
@@ -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,
}
+9
View File
@@ -0,0 +1,9 @@
-- https://github.com/j-hui/fidget.nvim
---@type LazyPluginSpec
return {
"j-hui/fidget.nvim",
tag = "legacy",
event = "LspAttach",
config = true,
}
+10
View File
@@ -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" },
},
}
+22
View File
@@ -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" },
},
}
+58
View File
@@ -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,
},
}
+12
View File
@@ -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,
}
+8
View File
@@ -0,0 +1,8 @@
-- https://github.com/williamboman/mason.nvim
---@type LazyPluginSpec
return {
"williamboman/mason.nvim",
event = "VeryLazy",
config = true,
}
+18
View File
@@ -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,
}
+166
View File
@@ -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,
}
+17
View File
@@ -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,
}
+18
View File
@@ -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,
},
}
+47
View File
@@ -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 = "",
},
},
}
+36
View File
@@ -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,
},
}
+126
View File
@@ -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,
}
+45
View File
@@ -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,
}
+398
View File
@@ -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