diff options
| author | Elvis Claros Castro <elvis@claros.ar> | 2026-09-27 12:46:49 -0300 |
|---|---|---|
| committer | Elvis Claros Castro <elvis@claros.ar> | 2026-09-27 12:46:49 -0300 |
| commit | df7c711e2a49d51249350f52c63274b72aacb739 (patch) | |
| tree | 426da3b65371f857f3ee3d9893d7ff3279aac02e /lua/pi/treesitter.lua | |
| parent | a0bd84df02ce235123dfc939327e8bd33dffdd7e (diff) | |
| download | nvim-conf-df7c711e2a49d51249350f52c63274b72aacb739.tar.gz nvim-conf-df7c711e2a49d51249350f52c63274b72aacb739.zip | |
Migración de plugins a funcionalidad nativa: statusline y treesitter propios, se quitan mason, lualine, mini, notify y otros
Diffstat (limited to 'lua/pi/treesitter.lua')
| -rw-r--r-- | lua/pi/treesitter.lua | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/lua/pi/treesitter.lua b/lua/pi/treesitter.lua new file mode 100644 index 0000000..d137ac5 --- /dev/null +++ b/lua/pi/treesitter.lua @@ -0,0 +1,248 @@ +-- Integración de treesitter para Neovim 0.13 + nvim-treesitter rama `main`. +-- +-- La rama `main` es una reescritura sin sistema de módulos: sólo instala parsers +-- y provee queries. Highlight, indent, folds e injections los da Neovim, así que +-- el enganche lo hacemos nosotros en un autocomando FileType. + +local M = {} + +local install_dir = vim.fs.joinpath(vim.fn.stdpath "data", "site") + +--- Parsers que queremos siempre disponibles. +M.ensure_installed = { + "bash", + "blade", + "css", + "dockerfile", + "cpp", + "elixir", + "erlang", + "go", + "gomod", + "html", + "javascript", + "json", + "latex", + "lua", + "luadoc", + "markdown", + "markdown_inline", + "nix", + "php", + "php_only", + "phpdoc", + "python", + "query", + "regex", + "rust", + "sql", + "svelte", + "twig", + "typescript", + "typst", + "vim", + "yaml", +} + +--- Filetypes de buffers de plugins que reusan el parser de otro lenguaje. +M.language_aliases = { + codecompanion = "markdown", + snacks_notif = "markdown", + oil = "markdown", +} + +--- Filetypes donde no queremos highlight ni indent de treesitter. +M.disabled_filetypes = { + oil = true, + snacks_notif = true, +} + +---@param lang string +---@return boolean +local function parser_available(lang) + return #vim.api.nvim_get_runtime_file("parser/" .. lang .. ".so", false) > 0 +end + +--- Reemplazo de `auto_install`: instala el parser que falte, una sola vez por +--- lenguaje y sin bloquear, y engancha el buffer cuando termina. +local installing = {} ---@type table<string, true> + +---@param lang string +---@param on_ready fun() +local function install_missing(lang, on_ready) + if installing[lang] then + return + end + local ok, parsers = pcall(require, "nvim-treesitter.parsers") + if not ok or not parsers[lang] then + return + end + installing[lang] = true + require("nvim-treesitter").install({ lang }):await(function() + installing[lang] = nil + vim.schedule(on_ready) + end) +end + +---@param buf integer +local function attach(buf) + if not vim.api.nvim_buf_is_valid(buf) then + return + end + + local ft = vim.bo[buf].filetype + if ft == "" or M.disabled_filetypes[ft] then + return + end + + local lang = vim.treesitter.language.get_lang(ft) + if not lang then + return + end + + if not parser_available(lang) then + install_missing(lang, function() + attach(buf) + end) + return + end + + if not pcall(vim.treesitter.start, buf, lang) then + return + end + + -- Indent de nvim-treesitter (experimental, era el módulo `indent` en master). + -- Sólo si el lenguaje trae query `indents`; si no, dejamos el indentexpr del + -- ftplugin, que es mejor que un fallback vacío. + if vim.treesitter.query.get(lang, "indents") then + vim.bo[buf].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" + end +end + +function M.setup() + require("nvim-treesitter").setup { install_dir = install_dir } + + for ft, lang in pairs(M.language_aliases) do + vim.treesitter.language.register(lang, ft) + end + + vim.filetype.add { + pattern = { + [".*%.blade%.php"] = "blade", + }, + } + + require("nvim-treesitter").install(M.ensure_installed) + + vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("pi_treesitter", { clear = true }), + callback = function(args) + attach(args.buf) + end, + }) + + -- El autocomando llega tarde para los buffers que ya se abrieron al arrancar. + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.api.nvim_buf_is_loaded(buf) then + attach(buf) + end + end + + M.setup_incremental_selection() +end + +-------------------------------------------------------------------------------- +-- Selección incremental +-- +-- El módulo `incremental_selection` desapareció con la rama `main` y Neovim no +-- trae equivalente. Esto reimplementa los cuatro mapeos que había: gnn / grn / +-- grc / grm. +-------------------------------------------------------------------------------- + +local stacks = {} ---@type table<integer, TSNode[]> + +---@param node TSNode +local function select_node(node) + local srow, scol, erow, ecol = node:range() + if ecol == 0 and erow > srow then + erow = erow - 1 + ecol = #(vim.api.nvim_buf_get_lines(0, erow, erow + 1, false)[1] or "") + end + if vim.fn.mode() ~= "n" then + vim.cmd "normal! \27" + end + vim.fn.setpos("'<", { 0, srow + 1, scol + 1, 0 }) + vim.fn.setpos("'>", { 0, erow + 1, math.max(ecol, 1), 0 }) + vim.cmd "normal! gv" +end + +---@return TSNode[] +local function stack() + local buf = vim.api.nvim_get_current_buf() + stacks[buf] = stacks[buf] or {} + return stacks[buf] +end + +local function init_selection() + local node = vim.treesitter.get_node() + if not node then + return + end + local s = stack() + for i = #s, 1, -1 do + s[i] = nil + end + s[1] = node + select_node(node) +end + +--- Sube al ancestro más chico que sea estrictamente más grande que el actual. +---@param predicate? fun(node: TSNode, from: TSNode): boolean +local function grow(predicate) + local s = stack() + local node = s[#s] or vim.treesitter.get_node() + if not node then + return + end + local parent = node:parent() + while parent do + local same = vim.deep_equal({ parent:range() }, { node:range() }) + if not same and (not predicate or predicate(parent, node)) then + s[#s + 1] = parent + select_node(parent) + return + end + parent = parent:parent() + end + select_node(node) +end + +local function shrink() + local s = stack() + if #s > 1 then + s[#s] = nil + end + local node = s[#s] + if node then + select_node(node) + end +end + +function M.setup_incremental_selection() + local map = vim.keymap.set + map("n", "gnn", init_selection, { desc = "Treesitter: iniciar selección" }) + map("x", "grn", function() + grow() + end, { desc = "Treesitter: expandir al nodo padre" }) + map("x", "grc", function() + -- "scope": el primer ancestro que abarque más líneas que el nodo actual. + grow(function(parent, from) + local psrow, _, perow = parent:range() + local srow, _, erow = from:range() + return (perow - psrow) > (erow - srow) + end) + end, { desc = "Treesitter: expandir al scope" }) + map("x", "grm", shrink, { desc = "Treesitter: reducir selección" }) +end + +return M |