aboutsummaryrefslogtreecommitdiffstats
path: root/lua/pi/treesitter.lua
diff options
context:
space:
mode:
authorElvis Claros Castro <elvis@claros.ar>2026-09-27 12:46:49 -0300
committerElvis Claros Castro <elvis@claros.ar>2026-09-27 12:46:49 -0300
commitdf7c711e2a49d51249350f52c63274b72aacb739 (patch)
tree426da3b65371f857f3ee3d9893d7ff3279aac02e /lua/pi/treesitter.lua
parenta0bd84df02ce235123dfc939327e8bd33dffdd7e (diff)
downloadnvim-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.lua248
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