semi ok
This commit is contained in:
@@ -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)
|
||||
@@ -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 })
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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" })
|
||||
Reference in New Issue
Block a user