This commit is contained in:
2026-06-03 05:23:07 +02:00
commit a67da8c917
102 changed files with 9238 additions and 0 deletions
+224
View File
@@ -0,0 +1,224 @@
local M = {}
-- C/kernel-doc style: /** ... @param: ... Return: ... */
local function generate_c_doc(bufnr, row, line)
-- strip static/inline/extern prefixes
local stripped = line:gsub("^%s*static%s+", ""):gsub("^%s*inline%s+", ""):gsub("^%s*extern%s+", "")
-- match: type *func_name(params) or type* func_name(params)
local ret, name, params = stripped:match("^%s*([%w_]+%s*%**)%s*([%w_]+)%s*%((.*)%)%s*{?%s*$")
if not name then
return nil, "No C function signature found on current line"
end
local doc = { "/**", " * " .. name .. "() - " }
-- parse parameters
if params and params:match("%S") and not params:match("^%s*void%s*$") then
for param in params:gmatch("([^,]+)") do
local pname = param:match("([%w_]+)%s*$")
or param:match("%*%s*([%w_]+)")
or param:match("([%w_]+)%s*%[")
if pname then
table.insert(doc, " * @" .. pname .. ": ")
end
end
end
table.insert(doc, " *")
-- add Return: if not void
ret = ret and ret:gsub("%s+", " "):gsub("^%s*", ""):gsub("%s*$", "") or ""
if ret ~= "void" and ret ~= "" then
table.insert(doc, " * Return: ")
end
table.insert(doc, " */")
return doc, nil
end
-- Go style: // FunctionName does something.
local function generate_go_doc(bufnr, row, line)
-- match: func (receiver) name(params) return or func name(params) return
local name, params, ret
-- method with receiver: func (r *Receiver) Name(params) return
name, params, ret = line:match("^%s*func%s+%([^)]+%)%s+([%w_]+)%s*%((.-)%)%s*(.-)%s*{?%s*$")
-- regular function: func Name(params) return
if not name then
name, params, ret = line:match("^%s*func%s+([%w_]+)%s*%((.-)%)%s*(.-)%s*{?%s*$")
end
if not name then
return nil, "No Go function signature found on current line"
end
local doc = { "// " .. name .. " " }
-- add parameter hints if present
if params and params:match("%S") then
local param_names = {}
for param in params:gmatch("([^,]+)") do
-- Go params: name type or name, name2 type
local pname = param:match("^%s*([%w_]+)")
if pname then
table.insert(param_names, pname)
end
end
if #param_names > 0 then
table.insert(doc, "//")
table.insert(doc, "// Parameters:")
for _, pname in ipairs(param_names) do
table.insert(doc, "// - " .. pname .. ": ")
end
end
end
-- add return hint if present
ret = ret and ret:gsub("^%s*", ""):gsub("%s*$", "") or ""
if ret ~= "" and ret ~= "error" then
table.insert(doc, "//")
table.insert(doc, "// Returns: ")
end
return doc, nil
end
-- Rust style: /// Description
local function generate_rust_doc(bufnr, row, line)
-- match: fn name(params) -> return or pub fn name...
local name, params, ret = line:match("^%s*pub%s+fn%s+([%w_]+)%s*%((.-)%)%s*%->%s*(.-)%s*{?%s*$")
if not name then
name, params, ret = line:match("^%s*fn%s+([%w_]+)%s*%((.-)%)%s*%->%s*(.-)%s*{?%s*$")
end
if not name then
name, params = line:match("^%s*pub%s+fn%s+([%w_]+)%s*%((.-)%)%s*{?%s*$")
end
if not name then
name, params = line:match("^%s*fn%s+([%w_]+)%s*%((.-)%)%s*{?%s*$")
end
if not name then
return nil, "No Rust function signature found on current line"
end
local doc = { "/// " }
-- add parameter hints if present
if params and params:match("%S") then
local param_names = {}
for param in params:gmatch("([^,]+)") do
local pname = param:match("^%s*([%w_]+)%s*:")
if pname and pname ~= "self" and pname ~= "&self" and pname ~= "&mut" then
table.insert(param_names, pname)
end
end
if #param_names > 0 then
table.insert(doc, "///")
table.insert(doc, "/// # Arguments")
table.insert(doc, "///")
for _, pname in ipairs(param_names) do
table.insert(doc, "/// * `" .. pname .. "` - ")
end
end
end
-- add return hint if present
ret = ret and ret:gsub("^%s*", ""):gsub("%s*$", "") or ""
if ret ~= "" then
table.insert(doc, "///")
table.insert(doc, "/// # Returns")
table.insert(doc, "///")
table.insert(doc, "/// ")
end
return doc, nil
end
-- Python style: """docstring"""
local function generate_python_doc(bufnr, row, line)
local name, params = line:match("^%s*def%s+([%w_]+)%s*%((.-)%)%s*:?%s*$")
if not name then
name, params = line:match("^%s*async%s+def%s+([%w_]+)%s*%((.-)%)%s*:?%s*$")
end
if not name then
return nil, "No Python function signature found on current line"
end
local indent = line:match("^(%s*)") or ""
local doc = { indent .. ' """' }
-- parse parameters
if params and params:match("%S") then
local param_names = {}
for param in params:gmatch("([^,]+)") do
local pname = param:match("^%s*([%w_]+)")
if pname and pname ~= "self" and pname ~= "cls" then
table.insert(param_names, pname)
end
end
if #param_names > 0 then
table.insert(doc, indent .. "")
table.insert(doc, indent .. " Args:")
for _, pname in ipairs(param_names) do
table.insert(doc, indent .. " " .. pname .. ": ")
end
end
end
table.insert(doc, indent .. "")
table.insert(doc, indent .. " Returns:")
table.insert(doc, indent .. " ")
table.insert(doc, indent .. ' """')
return doc, nil
end
-- filetype to generator mapping
local generators = {
c = generate_c_doc,
cpp = generate_c_doc,
h = generate_c_doc,
go = generate_go_doc,
rust = generate_rust_doc,
python = generate_python_doc,
}
function M.generate_doc()
local bufnr = vim.api.nvim_get_current_buf()
local row = vim.api.nvim_win_get_cursor(0)[1]
local line = vim.api.nvim_buf_get_lines(bufnr, row - 1, row, false)[1]
local ft = vim.bo[bufnr].filetype
local generator = generators[ft]
if not generator then
vim.notify("No doc generator for filetype: " .. ft, vim.log.levels.WARN)
return
end
local doc, err = generator(bufnr, row, line)
if err then
vim.notify(err, vim.log.levels.ERROR)
return
end
vim.api.nvim_buf_set_lines(bufnr, row - 1, row - 1, false, doc)
-- position cursor at first empty description spot
local cursor_row = row
local cursor_col = #doc[1]
for i, docline in ipairs(doc) do
if docline:match("%s$") or docline:match(":%s*$") or docline:match("%-%s*$") then
cursor_row = row + i - 1
cursor_col = #docline
break
end
end
vim.api.nvim_win_set_cursor(0, { cursor_row, cursor_col })
vim.cmd("startinsert!")
end
vim.keymap.set("n", "<leader>dg", M.generate_doc)
+55
View File
@@ -0,0 +1,55 @@
-- Remap leaving 'terminal mode' to double tap esc
vim.keymap.set("t", "<esc><esc>", "<c-\\><c-n>")
local state = {
floating = {
buf = -1,
win = -1,
}
}
local function open_floating_terminal(opts)
opts = opts or {}
local width = opts.width or math.floor(vim.o.columns * 0.8)
local height = opts.height or math.floor(vim.o.lines * 0.8)
local row = math.floor((vim.o.lines - height) / 2)
local col = math.floor((vim.o.columns - width) / 2)
local buf = nil
if vim.api.nvim_buf_is_valid(opts.buf) then
buf = opts.buf
else
buf = vim.api.nvim_create_buf(false, true)
end
if not buf then
error("Failed to create buffer")
end
local win = vim.api.nvim_open_win(buf, true, {
relative = 'editor',
width = width,
height = height,
row = row,
col = col,
style = 'minimal',
border = 'rounded',
})
return { buf = buf, win = win }
end
local toggle_terminal = function()
if not vim.api.nvim_win_is_valid(state.floating.win) then
state.floating = open_floating_terminal({ buf = state.floating.buf });
if vim.bo[state.floating.buf].buftype ~= "terminal" then
vim.cmd.terminal()
vim.cmd("startinsert!")
end
else
vim.api.nvim_win_hide(state.floating.win)
end
end
vim.api.nvim_create_user_command("Flterm", toggle_terminal, {})
vim.api.nvim_set_keymap('n', '<leader>ft', [[:Flterm<CR>]], { noremap = true, silent = true })
+283
View File
@@ -0,0 +1,283 @@
vim.lsp.config('*', {
root_markers = { '.git' },
})
vim.diagnostic.config({
virtual_text = true,
severity_sort = true,
float = {
style = 'minimal',
border = 'rounded',
source = 'if_many',
header = '',
prefix = '',
},
signs = {
text = {
[vim.diagnostic.severity.ERROR] = '',
[vim.diagnostic.severity.WARN] = '',
[vim.diagnostic.severity.HINT] = '',
[vim.diagnostic.severity.INFO] = '»',
},
},
})
local orig = vim.lsp.util.open_floating_preview
---@diagnostic disable-next-line: duplicate-set-field
function vim.lsp.util.open_floating_preview(contents, syntax, opts, ...)
opts = opts or {}
opts.border = opts.border or 'rounded'
opts.max_width = opts.max_width or 80
opts.max_height = opts.max_height or 24
opts.wrap = opts.wrap ~= false
return orig(contents, syntax, opts, ...)
end
vim.api.nvim_create_autocmd('LspAttach', {
group = vim.api.nvim_create_augroup('my.lsp', {}),
callback = function(args)
local client = assert(vim.lsp.get_client_by_id(args.data.client_id))
local buf = args.buf
local map = function(mode, lhs, rhs) vim.keymap.set(mode, lhs, rhs, { buffer = buf }) end
map('n', 'K', vim.lsp.buf.hover)
map('n', 'gd', vim.lsp.buf.definition)
map('n', 'gD', vim.lsp.buf.declaration)
map('n', 'gi', vim.lsp.buf.implementation)
map('n', 'go', vim.lsp.buf.type_definition)
map('n', 'gr', vim.lsp.buf.references)
map('n', 'gs', vim.lsp.buf.signature_help)
map('n', 'gl', vim.diagnostic.open_float)
map('n', '<F2>', vim.lsp.buf.rename)
map({ 'n', 'x' }, '<F3>', function() vim.lsp.buf.format({ async = true }) end)
map('n', '<F4>', vim.lsp.buf.code_action)
if client:supports_method('textDocument/documentHighlight') then
local highlight_augroup = vim.api.nvim_create_augroup('my.lsp.highlight', { clear = false })
vim.api.nvim_create_autocmd({ 'CursorHold', 'CursorHoldI' }, {
buffer = buf,
group = highlight_augroup,
callback = vim.lsp.buf.document_highlight,
})
vim.api.nvim_create_autocmd({ 'CursorMoved', 'CursorMovedI' }, {
buffer = buf,
group = highlight_augroup,
callback = vim.lsp.buf.clear_references,
})
end
local excluded_filetypes = { php = true, c = true, cpp = true }
if not client:supports_method('textDocument/willSaveWaitUntil')
and client:supports_method('textDocument/formatting')
and not excluded_filetypes[vim.bo[buf].filetype]
then
vim.api.nvim_create_autocmd('BufWritePre', {
group = vim.api.nvim_create_augroup('my.lsp.format', { clear = false }),
buffer = buf,
callback = function()
vim.lsp.buf.format({ bufnr = buf, id = client.id, timeout_ms = 1000 })
end,
})
end
end,
})
local caps = require("cmp_nvim_lsp").default_capabilities()
vim.lsp.config['luals'] = {
cmd = { 'lua-language-server' },
filetypes = { 'lua' },
root_markers = { { '.luarc.json', '.luarc.jsonc' }, '.git' },
capabilities = caps,
settings = {
Lua = {
runtime = { version = 'LuaJIT' },
diagnostics = { globals = { 'vim' } },
workspace = {
checkThirdParty = false,
library = vim.list_extend(
vim.api.nvim_get_runtime_file('', true),
{ '/home/tony/repos/oxwm/templates' }
),
},
telemetry = { enable = false },
},
},
}
vim.lsp.config['cssls'] = {
cmd = { 'vscode-css-language-server', '--stdio' },
filetypes = { 'css', 'scss', 'less' },
root_markers = { 'package.json', '.git' },
capabilities = caps,
settings = {
css = { validate = true },
scss = { validate = true },
less = { validate = true },
},
}
vim.lsp.config['phpls'] = {
cmd = { 'intelephense', '--stdio' },
filetypes = { 'php' },
root_markers = { 'composer.json', '.git' },
capabilities = caps,
settings = {
intelephense = {
files = {
maxSize = 5000000, -- default 5MB
},
},
},
}
vim.lsp.config['ts_ls'] = {
cmd = { 'typescript-language-server', '--stdio' },
filetypes = {
'javascript', 'javascriptreact', 'javascript.jsx',
'typescript', 'typescriptreact', 'typescript.tsx',
},
root_markers = { 'package.json', 'tsconfig.json', 'jsconfig.json', '.git' },
capabilities = caps,
settings = {
completions = {
completeFunctionCalls = true,
},
},
}
vim.lsp.config['zls'] = {
cmd = { 'zls' },
filetypes = { 'zig', 'zir' },
root_markers = { 'zls.json', 'build.zig', '.git' },
capabilities = caps,
settings = {
zls = {
enable_build_on_save = true,
build_on_save_step = "install",
warn_style = false,
enable_snippets = true,
}
}
}
vim.lsp.config['nil_ls'] = {
cmd = { 'nil' },
filetypes = { 'nix' },
root_markers = { 'flake.nix', 'default.nix', '.git' },
capabilities = caps,
settings = {
['nil'] = {
formatting = {
command = { "alejandra" }
}
}
}
}
vim.lsp.config['rust_analyzer'] = {
cmd = { 'rust-analyzer' },
filetypes = { 'rust' },
root_markers = { 'Cargo.toml', 'rust-project.json', '.git' },
capabilities = caps,
settings = {
['rust-analyzer'] = {
cargo = { allFeatures = true },
formatting = {
command = { "rustfmt" }
},
},
},
}
-- C / C++ via clangd
vim.lsp.config['clangd'] = {
cmd = {
'clangd',
-- '--background-index',
-- '--clang-tidy',
-- '--header-insertion=never',
-- '--completion-style=detailed',
-- '--query-driver=/nix/store/*-gcc-*/bin/gcc*,/nix/store/*-clang-*/bin/clang*,/run/current-system/sw/bin/cc*',
},
filetypes = { 'c', 'cpp', 'objc', 'objcpp' },
root_markers = { 'compile_commands.json', '.clangd', 'configure.ac', 'Makefile', '.git' },
capabilities = caps,
-- init_options = {
-- fallbackFlags = { '-std=c23' }, -- Default to C23
-- },
}
vim.lsp.config['c3lsp'] = {
cmd = { 'c3-lsp' },
filetypes = { 'c3' },
root_markers = { 'project.json', '.git' },
capabilities = caps,
}
vim.lsp.config['serve_d'] = {
cmd = { 'serve-d' },
filetypes = { 'd' },
root_markers = { 'dub.sdl', 'dub.json', '.git' },
capabilities = caps,
}
vim.lsp.config['jsonls'] = {
cmd = { 'vscode-json-languageserver', '--stdio' },
filetypes = { 'json', 'jsonc' },
root_markers = { 'package.json', '.git', 'config.jsonc' },
capabilities = caps,
}
vim.lsp.config['hls'] = {
cmd = { 'haskell-language-server-wrapper', '--lsp' },
filetypes = { 'haskell', 'lhaskell' },
root_markers = { 'stack.yaml', 'cabal.project', 'package.yaml', '*.cabal', 'hie.yaml', '.git' },
capabilities = caps,
settings = {
haskell = {
formattingProvider = 'fourmolu',
plugin = {
semanticTokens = { globalOn = false }
},
},
},
}
vim.lsp.config['gopls'] = {
cmd = { 'gopls' },
filetypes = { 'go', 'gomod', 'gowork', 'gotmpl' },
root_markers = { 'go.mod', 'go.work', '.git' },
capabilities = caps,
settings = {
gopls = {
analyses = {
unusedparams = false,
ST1003 = false,
ST1000 = false,
},
staticcheck = true,
},
},
}
vim.lsp.config['templ'] = {
cmd = { 'templ', 'lsp' },
filetypes = { 'templ' },
root_markers = { 'go.mod', '.git' },
capabilities = caps,
}
vim.filetype.add({
extension = {
h = 'c',
c3 = 'c3',
d = 'd',
templ = 'templ',
},
})
---@diagnostic disable-next-line: invisible
for name, _ in pairs(vim.lsp.config._configs) do
if name ~= '*' then
vim.lsp.enable(name)
end
end
+40
View File
@@ -0,0 +1,40 @@
local function reformat_parenthesized_content()
local bufnr = vim.api.nvim_get_current_buf()
local row = vim.api.nvim_win_get_cursor(0)[1]
local line = vim.api.nvim_buf_get_lines(bufnr, row - 1, row, false)[1]
local inside = line:match("%((.-)%)")
if not inside then
vim.notify(
"No content found inside parentheses",
vim.log.levels.ERROR
)
return
end
local prefix = line:match("^(.-)%(") or ""
local suffix = line:match("%)(.*)$") or ""
local parts = vim.split(inside, ",%s*")
if #parts == 0 then
vim.notify("No comma-separated content found", vim.log.levels.ERROR)
return
end
local new_lines = {}
table.insert(new_lines, prefix .. "(")
for i, part in ipairs(parts) do
if i < #parts then
table.insert(new_lines, " " .. part .. ",")
else
table.insert(new_lines, " " .. part)
end
end
table.insert(new_lines, " )" .. suffix)
vim.api.nvim_buf_set_lines(bufnr, row - 1, row, false, new_lines)
end
vim.keymap.set("n", "<leader>qq", function()
reformat_parenthesized_content()
end)
+110
View File
@@ -0,0 +1,110 @@
-- Minimal sticky context header. Replaces nvim-treesitter-context.
-- Shows the first line of the enclosing function/class pinned to the
-- top of the window when that line has scrolled offscreen above.
--
-- One level of context only (innermost enclosing). Extend CONTEXT_TYPES
-- to support more languages or richer node kinds.
--
-- Toggle:
-- <leader>th hide
-- <leader>tu unhide
local M = { enabled = true }
local ctx_buf, ctx_win
local CONTEXT_TYPES = {
-- C / PHP
function_definition = true,
method_declaration = true,
class_declaration = true,
-- Lua
function_declaration = true,
-- Rust
function_item = true,
impl_item = true,
trait_item = true,
-- Go (function_declaration shared)
-- JS
method_definition = true,
arrow_function = true,
-- Zig
fn_proto = true,
-- Nix
function_expression = true,
}
local function close_ctx()
if ctx_win and vim.api.nvim_win_is_valid(ctx_win) then
vim.api.nvim_win_close(ctx_win, true)
end
ctx_win = nil
end
local function update_ctx()
if not M.enabled then close_ctx() return end
local bufnr = vim.api.nvim_get_current_buf()
local node = vim.treesitter.get_node()
if not node then close_ctx() return end
while node and not CONTEXT_TYPES[node:type()] do
node = node:parent()
end
if not node then close_ctx() return end
local sr = node:start()
local top_visible = vim.fn.line("w0") - 1
if sr >= top_visible then close_ctx() return end
local lines = vim.api.nvim_buf_get_lines(bufnr, sr, sr + 1, false)
if #lines == 0 then close_ctx() return end
if not ctx_buf or not vim.api.nvim_buf_is_valid(ctx_buf) then
ctx_buf = vim.api.nvim_create_buf(false, true)
vim.bo[ctx_buf].buftype = "nofile"
end
vim.api.nvim_buf_set_lines(ctx_buf, 0, -1, false, lines)
vim.bo[ctx_buf].filetype = vim.bo[bufnr].filetype
local config = {
relative = "win",
win = vim.api.nvim_get_current_win(),
row = 0,
col = 0,
width = vim.api.nvim_win_get_width(0),
height = 1,
focusable = false,
style = "minimal",
zindex = 20,
}
if ctx_win and vim.api.nvim_win_is_valid(ctx_win) then
vim.api.nvim_win_set_config(ctx_win, config)
else
ctx_win = vim.api.nvim_open_win(ctx_buf, false, config)
vim.wo[ctx_win].winhighlight = "Normal:TonyContext,NormalFloat:TonyContext"
end
end
vim.api.nvim_set_hl(0, "TonyContext", { link = "NormalFloat", default = true })
local group = vim.api.nvim_create_augroup("TonyContext", { clear = true })
vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI", "WinScrolled", "BufEnter" }, {
group = group,
callback = update_ctx,
})
vim.api.nvim_create_autocmd({ "BufLeave", "WinLeave" }, {
group = group,
callback = close_ctx,
})
vim.keymap.set("n", "<leader>th", function()
M.enabled = false
close_ctx()
end, { desc = "Hide context header" })
vim.keymap.set("n", "<leader>tu", function()
M.enabled = true
update_ctx()
end, { desc = "Unhide context header" })
return M
+85
View File
@@ -0,0 +1,85 @@
-- Local treesitter setup. Replaces nvim-treesitter + nvim-treesitter-textobjects.
-- Parsers: ~/.config/nvim/parser/<lang>.so
-- Queries: ~/.config/nvim/queries/<lang>/
-- Start treesitter highlighting on FileType when a parser is available.
vim.api.nvim_create_autocmd("FileType", {
callback = function(args)
pcall(vim.treesitter.start, args.buf)
end,
})
-- Function text objects: af / if
-- Walks the textobjects query for the buffer's language and selects the
-- smallest @function.outer / @function.inner range containing the cursor.
local function select_function(capture)
local bufnr = vim.api.nvim_get_current_buf()
local ok, parser = pcall(vim.treesitter.get_parser, bufnr)
if not ok or not parser then return end
local lang = parser:lang()
local query = vim.treesitter.query.get(lang, "textobjects")
if not query then return end
local tree = parser:parse()[1]
if not tree then return end
local root = tree:root()
local cur = vim.api.nvim_win_get_cursor(0)
local crow, ccol = cur[1] - 1, cur[2]
-- A single textobject (e.g. @function.inner) is often split across
-- multiple captures within the same match (e.g. one per statement in
-- the body). Use iter_matches and union the ranges of all captures
-- with our target name within each match.
local best, best_size
for _, match in query:iter_matches(root, bufnr, 0, -1, { all = true }) do
local min_sr, min_sc, max_er, max_ec
for id, nodes in pairs(match) do
if query.captures[id] == capture then
if type(nodes) ~= "table" then nodes = { nodes } end
for _, node in ipairs(nodes) do
local sr, sc, er, ec = node:range()
if not min_sr or sr < min_sr or (sr == min_sr and sc < min_sc) then
min_sr, min_sc = sr, sc
end
if not max_er or er > max_er or (er == max_er and ec > max_ec) then
max_er, max_ec = er, ec
end
end
end
end
if min_sr then
local contains = (min_sr < crow or (min_sr == crow and min_sc <= ccol))
and (max_er > crow or (max_er == crow and max_ec >= ccol))
if contains then
local size = (max_er - min_sr) * 1e6 + (max_ec - min_sc)
if not best_size or size < best_size then
best_size = size
best = { min_sr, min_sc, max_er, max_ec }
end
end
end
end
if not best then return end
local sr, sc, er, ec = best[1], best[2], best[3], best[4]
-- If we were invoked from visual mode (`vif`, `vaf`, etc.), exit it
-- first — otherwise `normal! v` below would toggle visual off instead
-- of re-entering it, and we'd end up only moving the cursor.
local mode = vim.fn.mode()
if mode == "v" or mode == "V" or mode == "\22" then
vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes("<Esc>", true, false, true), "nx", false)
end
vim.api.nvim_win_set_cursor(0, { sr + 1, sc })
vim.cmd("normal! v")
vim.api.nvim_win_set_cursor(0, { er + 1, math.max(0, ec - 1) })
end
vim.keymap.set({ "x", "o" }, "af", function() select_function("function.outer") end,
{ desc = "around function" })
vim.keymap.set({ "x", "o" }, "if", function() select_function("function.inner") end,
{ desc = "inside function" })