:LSP

Tabla de contenidos

Introducción

Hemos llegado a una parte muy importante, pero muy complicada, porque ha llegado el momento de convertir nuestro NeoVim en un IDE con todas las de la Ley. Para eso vamos necesitar de los siguientes plugins:

Instalar y configurar Lspconfig

Primero vamos a instalar y a configurar el archivo lsp-cfg.lua dentro de lua/plugins/. Lo creamos y le añadimos esto:

return {
    {"neovim/nvim-lspconfig", dependencies = {"hrsh7th/cmp-nvim-lsp"},
    config = function()
        local lspconfig = require("lspconfig")
        local capabilities = require("cmp_nvim_lsp").default_capabilities()

        -- Listado de servidores de lenguaje
        local servers = {"html", "cssls","tsserver","pyright","lua_ls","jsonls"}

        for _, lsp in ipairs(servers) do
            lspconfig[lsp].setup {
                capabilities = capabilities,
            }
        end
    end,
    }
}

En la configuración le hemos pedido que cargue los servidores de lenguaje de HTML, CSS, JavaScript, TypeScript, Lua, Python y JSON.

Instalar y configurar LuaSnip

Creamos el archivo luasnip-cfg.lua y le metemos esto:

return {
    {"L3MON4DE/LuaSnip",
        dependencies = {"rafamadriz/friendly-snippets"},
        config = function()
            require("luasnip.loaders.from_vscode").lazy_load()
        end,
    }
}

Instalar y configurar el autocompletado

Este es el plugin con la configuración más tocha, pero con los comentarios se entenderá lo que hace cada cosa. Creamos el archivo cmp-cfg.lua y metemos todo esto:

return {
        {"hrsh7th/nvim-cmp",
        dependencies = {"onsails/lspkind.nvim", "saadparwaiz1/cmp_luasnip"},
        config = function()
            local cmp = require("cmp")
            local lspkind = require("lspkind")

            cmp.setup({
                -- Ventana para mostrar el autocompletado y su documentación
                window = {
                    completion = cmp.config.window,
                    documentation = cmp.config.window,
                },

                -- Origenes
                sources = cmp.config.sources({
                    {name = "nvim_lsp"},
                    {name = "luasnip", option = {show_autosnippets = true}}
                }),

                -- Atajos de teclado
                mapping = {
                    -- Enter acepta la sugerencia
                    ["<CR>"] = function(fallback)
                        if cmp.visible() then
                            cmp.confirm()
                        else
                            fallback()
                        end
                    end,

                    -- La tecla arriba selecciona la anterior sugerencia
                    ["<Up>"] = function(fallback)
                        if cmp.visible() then
                            cmp.select_prev_item()
                        else
                            fallback()
                        end
                    end,

                    -- La tecla abajo selecciona la siguiente sugerencia
                    ["<Down>"] = function(fallback)
                        if cmp.visible() then
                            cmp.select_next_item()
                        else
                            fallback()
                        end
                    end,
                },

                -- Formato de la ventana
                formatting = {
                    format = lspkind.cmp_format({
                        width_text = false,
                        mode = "symbol", -- Muestra solo los símbolos
                        maxwidth = 50
                    })
                }

                -- Snippets
                snippet = {
                    expand = function(args)
                        require("luasnip").lsp_expand(args.body)
                    end,
                }
            })
        end,
        },
}

Instalar y activar Mason

Creamos el archivo mason-cfg.lua en /plugins/ y escribimos esto:

return {
    {"williamboman/mason.nvim",
    dependencies = {"williamboman/mason-lspconfig.nvim"},
        config = function()
            require("mason").setup()
        end,
    }
}

Un apunte sobre Mason

Antes he comentado que este gestor de paquete nos permite gestionar los servidores de lenguaje, adaptadores de depuración, linters y formateadores. Vale, y tú te estarás preguntando ¿Qué es eso de un linter? ¿O un formateador?. Vamos a comentar cada cosa:

Después de los tecnicismos, vamos a cargar el Mason con el comando :Mason.

Primer arranque de Mason

La primera vez que cargamos el Mason no hay instalado nada. Pero eso lo vamos a cambiar.

La configuración por defecto de Mason no carga los diferentes iconos. En :help mason-settings tienes la configuración que carga los iconos.

Pero antes de instalar cosas, conviene tener instalado tanto nodejs y npm previamente en el equipo, que carga e instala librerías de JavaScript y TypeScript. También pip es necesario para instalar librerías y utilidades para Python. Y luarocks para librerías y utilizades de Lua. Todos esos paquetes son denominados gestores de paquetes de cada lenguaje.

Y ahora sí: dejamos la turra y volvemos con Mason.

Pulsamos 2 para irnos a los LSP y vamos a instalar los siguientes LSPs pulsando i sobre ellos:

LSPs instalados

Cerramos Neovim y lo volvemos a abrirlo. Vamos a crear un archivo en formato JavaScript .js.

Autocompletado
funcionando

¡POR FIN! ¡El autocompletado ya está funcionando! ¿Pero y qué hay de los snippets? 🤔

Snippets funcionando

¡Hombre, también funcionan los snippets!

Podemos consultar la información del LSP cargado en el en el archivo/búfer usando el comando :LspInfo.

LspInfo

Este comando nos viene de perlas para diagnosticar los posibles fallos del LSP, además de mostrarnos cuál es el LSP que se está usando y qué tipo de archivo está abierto.

Instalación, activación y configuración None-LS (Null-LS)

Antes este plugin se denominaba “Null-LS”, pero su autor decidió dejar de lado su desarrollo y ahora la comunidad se encarga de su mantenimiento.

Ahora mismo, a pesar de tener instalados los linters y formateadores, todavía no están activados. Para eso vamos a activarlos creando el archivo nls-cfg.lua dentro de /plugins/. Acto seguido, escribimos esto dentro:

return {
    "nvimtools/none-ls.nvim",
    config = function()
        -- Asignamos la variable nls para que cargue la función
        local nls = require("null-ls")

        nls.setup({
            sources = {
                -- Aquí se irán añadiendo los formateadores, linters y acciones de código
                nls.builtins.formatting.stylua, -- Formateador para Lua
                nls.builtins.formatting.prettierd, -- Formateador para JS/TS
                nls.builtins.formatting.black, -- Formateador para Python

                nls.builtins.diagnostics.eslint_d, -- Linter para JavaScript/TypeScript
                nls.builtins.diagnostics.pylint, -- Linter para Python

                nls.builtins.completion.spell, -- Autocompletado de ortografía
                nls.builtins.code_actions.refactoring, -- Refactorización
            }
        })
    end,
}

Guardamos el archivo. Cerramos Neovim y lo volvemos a abrir. Ahora ya debería estar activado el plugin. Para comprobarlo escribimos el comando :NullLsInfo.

NullLS funcionando

¡Um, vaya! NullLS está activo pero no hay ningún linter ni formateador en este búfer, ya que estamos en un archivo vacío. Pero vamos a crear un archivo en formato JavaScript. Veamos qué pasa.

NullLS - Orígenes para
JavaScript

Aquí ya va habiendo más cosas, porque nos dice cuales son los plugins que hay que instalar para tener formateado, linter y acciones de código. Como estamos usando un archivo escrito en JavaScript, vamos a instalarle eslint_d y prettier con Mason.

Eslint y Prettier instalados

Cerramos Neovim y volvemos a abrir el archivo de JavaScript con Neovim.

NullLS - Origenes de JS cargados

¡Esto ya tiene mejor pinta! Nos está mostrando los origenes que están asociados al tipo de archivo.

NullLS - Eslint funcionando

Además, en la barra de estado nos muestra la cantidad de avisos y errores que hay en el código, lo cuál está genial.

Pero… ¿y qué pasa con el formateado? Pues resulta que no lo hace automáticamente, sino que tenemos que hacerlo manualmente con el comando :lua vim.lsp.buf.format(). Vamos a hacer una indentación mala:

Mala indentación

Ahora escribimos el comando anterior y …

Prettier haciendo su
trabajo

¡Listo! Como ves, el amigo Prettier ha hecho un buen trabajo corrigiendo la indentación, haciendola mucho más legible. ¡Un aplauso!

Vamos a repetir lo mismo con un archivo en formato Lua

Mala indentación en Lua

Repetimos el comando anterior.

Archivo Lua bien
formateado

Sin comentarios. Un trabajo estupendo.

Pero resulta que por defecto, Stylua le aplica una indentación brutal de 8 espacios. Para configurar la indentación vamos a crear el archivo .stylua.toml dentro de /nvim/lua/ y dentro del mismo escribimos esto:

indent_type = "Spaces"
indent_width = 4

Con esto, cada vez que usemos el formateador Stylua, éste le aplicará 4 espacios en la indentación.

Para facilitarnos más la vida nos vamos al archivo keys.lua del directorio lua y añadimos un atajo para el formateado. Yo he elegido Líder + f.

vim.keymap.set("n", "<leader>f", ":lua vim.lsp.buf.format()<cr>", {silent = true, desc="Formatear documento"})

LSPs, linters y formateadores para cada lenguaje de programación

Ahora te estarás preguntando “Si yo programo en JavaScript/TypeScript/React/Angular/Vue, ¿qué necesito instalar para tener una experiencia en este lenguaje?“. Pues de esto va esta sección. Vamos a ver qué necesitamos instalar para cada lenguaje de programación. HERE WE GO!!.

JavaScript/TypeScript

Python

Java

C / C++

PHP

Kotlin

Rust

Ruby

Go

NOTA: Si estás usando un lenguaje de programación que no está aquí, busca por Internet el LSP, linter, formateador y el adaptador de la depuración.

Instalación, activación y configuración de TreeSitter

Ahora ha llegado el momento de activar y configurar el plugin de TreeSitter. Vamos a crear el archivo ts-cfg.lua dentro de /plugins/ y le vamos a meter esto:

return {
    "nvim-treesitter/nvim-treesitter",
    config = function()
        require("nvim-treesitter.configs").setup({

            -- Listado de parsers a instalar
            ensure_installed = {"html","css","javascript","typescript","lua", "python", "markdown"},

            -- Permite la instalación automática de parsers
            auto_install = true,

            -- Resaltado de código
            highlight = {
                enable = true,
            },

            -- Indentación
            indent = {
                enable = true,
            },

            -- Autoencerramiento (cierra las etiquetas HTML y los componentes)
            autotag = {
                enable = true,
            }
        })
    end,
}

Cerramos Neovim y lo volvemos a abrir. Ahora se van a instalar los “parsers” que hemos mencionado en el archivo de configuración. Podemos mirar que está dicho plugin en funcionamiento con el comando :checkhealth.

TreeSitter - Parsers instalados

Vamos a ver un ejemplo de un archivo básico de HTML para ver cómo es antes y después.

Resaltado de TS desactivado
Resaltado de Treesitter desactivado
Resaltado de TS activado
Resaltado de Treesitter activado

La diferencia es evidente: se han resaltado las etiquetas y los enlaces.

Puedes profundizar más sobre este plugin en la documentación oficial.

Autoemparejamiento y autoencerramiento

Nuestra suerte de IDE no está completo sin algún plugin que nos permita cerrar automáticamente etiquetas (que pueden ser HTML o componentes), corchetes, paréntesis, llaves, comillas y demás movidas. Vamos a activar mini.pairs y nvim-ts-autotag.

Creamos el archivo pairs-cfg.lua dentro de /plugins/ y escribimos esto:

return {
    {"echasnovski/mini.pairs",
        config = function()
            require("mini.pairs").setup()
        end,
    },

    {"windwp/nvim-ts-autotag",
        config = function()
            require("nvim-ts-autotag").setup()
        end,
    }
}

Salimos de Neovim y vamos a crear un archivo HTML dentro de Neovim. Vamos a probar que los corchetes, paréntesis y etiquetas se cierran correctamente.

Autotag funcionando

Pues sí que funciona, sí.

Final

Ya tenemos el autocompletado funcionando, los snippets funcionando y los diagnósticos de código rulando. Podríamos decir que ya tenemos una suerte de IDE en nuestro Neovim. Desde luego que en este artículo no se va a tocar lo del tema de la depuración (o “debug”) porque todavía no he llegado a ese punto.

Espero que le puedas meter caña a Neovim con tu lenguaje de programación favorito.