¿Alguna vez pensó usted: «creo que no tengo suficiente con ser usuario de Neovim, necesito ser todavía más pretencioso«? Eso es lo que probablemente se preguntó NotAShelf cuando le echo una mirada a nixvim y no se sintió satisfecho con lo que esta tecnología ofrecía.
Para los perdidos
Si no entendió ni la mitad de los términos que acaba de leer, aquí un poco de contexto yendo por cada concepto en orden de menos a más underground:
Vim es un editor de texto que se usa en la terminal caracterizado por no ser como los editores convencionales, pues su navegación se basa enteramente en el teclado. Además de lo anterior, destaca en su versatilidad a la hora de extender sus funcionalidades a través de un sistema de plugins. Un detalle relevante es que Vim fue lanzado 1991 y sigue siendo mantenido hasta la fecha.
Neovim es un fork de Vim que nació en 2014 para refactorizarlo desde cero, principalmente con el objetivo de modernizar sus capacidades de personalización. Sin adentrarse demasiado en detalles, una de las contribuciones más relevantes de Neovim es que permitió que los plugins pudieran ser escritos en cualquier lenguaje de programación, cuando anteriormente sólo era posible hacerlo en vimscript. Dato de vital importancia para la continuidad de este artículo: Este programa se configura a través de un lenguaje llamado Lua, creado por nuestros amigos en Brasil.
Nix es un lenguaje creado por Eelco Dolstra y compañía en 2003 para su tésis de licenciatura. Este lenguaje existe con el único propósito de asegurar el determinismo absoluto a la hora de empaquetar y distribuir software. Si los términos «reproducible», «autocontenido» y «determinista» no le provocan un mini orgasmo en la corteza prefontral, probablemente esto no sea para usted, pero aún así es un tema interesante del que le recomendaría investigar más.
Ahora sí, llegamos a nvf, que se trata ni más ni menos que de un framework para configurar su instalación de Neovim usando Nix.
WARNING
Aquí acaba el contexto rápido, a partir de aquí asumo que el lector está familiarizado con:
- Configurar su
init.luaen Neovim- Instalar plugins en Neovim
- Que tiene el administrador de paquetes
nixinstalado- Conocimiento básico de Nix:
- Qué es un attribute set, una lista, una función y una opción.
Si no cumple con dichos puntos, vaya y aprenda, es un mundo sumamente interesante.
¿Cuál es el punto?
La primera pregunta que se hará es ¿por qué? ¿Por qué tomarse la molestia de tomar una configuración que funciona y traducirla desde cero a otro lenguaje. Siendo honestos, esa interrogante no aplica únicamente a nvf, sino que a todo el ecosistema Nix en general y la respuesta se reduce a: estabilidad.
Cuando se administra correctamente, un sistema basado en Nix es de lo más estable que se puede conseguir. Por supuesto, puede decir esto acerca de cualquier cosa, pero la ventaja que ofrece Nix es que fue construido desde las bases con ese objetivo, la estabilidad no es una tarea relegada al usuario sino un requerimiento que forma parte central de la filosofía del lenguaje y, en consecuencia, realmente uno se tiene que descarrilar bastante para lograr romper un sistema Nix.
Pronto haré un artículo que cubra la maravilla del ecosistema Nix, pero por hoy me enfocaré en este nicho específico de la escena.
Probando las aguas
El paso 0 es probarlo, nvf no pierde el tiempo en presumir sus capacidades y te ofrece un (1) solo comando para probarlo, hay dos sabores disponibles:
nix run github:notashelf/nvf#nix test.nixPara una configuración con lo suficiente para editar archivos nix, pero que
no se chifla demasiado. O, en su defecto:
nix run github:notashelf/nvf#maximal test.nixPara abrir un buffer con todos los plugins que se pueda usted imaginar (a su
debido costo de almacenamiento, claro está). Evidentemente no es necesario, ni
recomendaría quedarse con ninguna de estas dos configuraciones, pero sirven
como demostración de las capacidades de nvf; no se limita a distribuir to
init.lua, sino que permite adjuntar todo lo necesario para su correcto
funcioinamiento: LSPs, formatters, herramientas de lenguajes y el límite es
nixpkgs, también conocido como el repositorio más grande y actualizado de
todo linux.
Instalación
Si ha tenido experiencias pasadas con Nix, es casi seguro que se topó con (o más bien echó en falta) su documentación, que tiene fama no ser precisamente buena, por ponerlo de forma amable. Pues hay excelentes noticias, nvf es la excepción a la regla aquí pues cuenta con un manual extenso y detallado para todas y cada una de las opciones que ofrece.
Si usted entra a la documentación, hallará, como es común en Nix, un chingo de
formas distintas de hacer lo mismo, en este caso, instalar el flake de nvf. No
me enrollaré en eso porque me da una hueva tremenda desconozco la forma en la
que tiene organizada sus configuraciones. Dependiendo del caso, refiérase a la
sección pertinente del manual.
Personalmente recomiendo instalarlo como un módulo de home-manager, lo cual, evidentemente implicaría instalar home-manager por sí mismo, buena suerte con ello. Recomiendo empezar por acá.
Configuración básica
Una vez instalado, puede comenzar a ensuciarse las manos con el módulo. Active
el programa nvf como cualquier otro programa:
{ ... }:
{
programs.nvf = {
enable = true;
};
}Configure las opciones básicas de vim de esta manera:
{ ... }:
{
programs.nvf = {
enable = true;
settings.vim = {
options = {
# 2 espacios de indentación
tabstop = 2;
softtabstop = 2;
};
};
};
}vim.options.foo = bar se traduce a vim.o.foo = bar en Lua. Continuamos con
las opciones globales:
{ ... }:
{
programs.nvf = {
enable = true;
settings.vim = {
options = {
# 2 espacios de indentación
tabstop = 2;
softtabstop = 2;
};
globals = {
mapleader = ";";
maplocalleader = ",";
};
};
};
}De forma análoga, vim.options.foo = bar se traduce a vim.o.foo = bar en Lua.
Plugins
Pero a lo que used vino fue a instalar plugins. Esta es una de las áreas en donde nvf resulta ser muchísimo más versátil que Nixvim (algo así como el predecesor de nvf). La forma canónica es a través de las opciones proveídas por defecto, por ejemplo:
{ ... }:
{
programs.nvf.settings.vim.autocomplete = {
blink-cmp = {
enable = true;
sourcePlugins = {
emoji.enable = true;
spell.enable = true;
};
};
};
}nvf toma los plugins directamente de nixpkgs, puedes consultar cuáles hay
disponibles
aquí.
Spoiler: son más de 2000, pero incluso con ese catálogo tan inmenso nunca
faltará ese plugin que no está empaquetado, siga leyendo para saber qué hacer
en esos casos.
{ ... }:
{
programs.nvf.settings.vim.autocomplete = {
blink-cmp = {
enable = true;
sourcePlugins = {
emoji.enable = true;
spell.enable = true;
};
setupOpts = {
sources = {
# Esto sí habilita las fuentes
default = [
"snippets"
"lsp"
"path"
"buffer"
"emoji"
];
};
};
};
};
}Para todos los plugins, el attribute set setupOpts se pasa a la función
setup() de su respectivo plugin. En el ejemplo anterior, esta sería la forma
de setupOpts en Lua:
require("blink.cmp").setup({
sources = {
default = {
"snippets",
"lsp",
"path",
"buffer",
"emoji",
},
},
})
Hay plugins cuya configuración entera está contenida bajo setupOpts, pues su
forma en Lua así lo permite, pero existen plugins con los que, por diseño, esto
no es posible. Un ejemplo claro es nvim-lint
{ ... }:
{
programs.nvf.settings.vim.diagnostics.nvim-lint = {
enable = true;
# Lintear después de guardar
lint_after_save = true;
# Función mínima de linteo
lint_function = mkLuaInline ''
function(buf)
require("lint").try_lint()
end
'';
# Mapeo de tipos de archivo a linter específico
linters_by_ft = {
python = [ "ruff" ];
nix = [ "statix" ];
tex = [ "chktex" ];
};
};
}
Nótese cómo aquí la opción setupOpts ni si quiera aparece, esto es
consecuencia directa de cómo está diseñado el plugin, pues no expone una
función setup() (véase su
documentación).
Siempre y cuando su plugin esté definido como una opción en nvf (y le agrade
cómo está adinistrado) no debería haber ningún problema si este no cuenta con
función setup(). Las complicaciones surgen cuando quiere hacer las cosas
como se deben e implementar a nuestro buen amigo el cargado huevón.
Cargado huevón (lazy loading)
Probablemente ya sepa usted lo que significa este término, pero para las dos personas de las diez que se quedaron leyendo este artículo que no lo saben: el lazy loading es el principio de no cargar cierto componente hasta que sea absolutamente necesario, de ahí el nombre.
En el contexto de Neovim, esto permite distribuir la demanda computacional de cargar plugins para así evitar tiempos de espera prolongados, lo cual resulta especialmente útil cuando ya agregó hasta el plugin del patito que te sigue en la pantalla y en consecuencia tiene que esperar cinco segundos antes de que el buffer sea visible.
Para esto, nvf expone la opción vim.lazy.plugins en donde puede declarar
cualquier plugin y cargarlo de forma huevona gracias a la librería
lz.n. Por ejemplo:
{ ... }:
programs.nvf.settings.vim.lazy.plugins = {
mini-indentscope = {
package = "mini-indentscope"; # Este atributo debe de ser de uno de los tipos de esta lista: https://nvf.notashelf.dev/options.html#option-vim-lazy-plugins-%3Cname%3E-package
setupModule = "mini.indentscope";
setupOpts = {
ignore_filetypes = [ "help" "neo-tree" ];
};
};
}El atributo setupModule simplemente es la cadena utilizada en la función
require().setup() y setupOpts lo que ya se discutió anteriormente. Para el
ejemplo anterior esta sería su forma en Lua:
require("mini.icons").setup({
ignore_filetypes = {
"help",
"neo-tree",
},
})Lo que seguramente se está preguntando es «¿Hey, pero en qué momento se define la regla de lazy loading?« Aquí:
{ ... }:
{
programs.nvf.settings.vim.lazy.plugins = {
mini-indentscope = {
package = "mini-indentscope";
setupModule = "mini.indentscope";
setupOpts = {
ignore_filetypes = [ "help" "neo-tree" ];
};
event = "BufEnter";
};
};
}Para otras reglas de cargado huevón refiérase a la página pertinente del manual. Aquí le listo algunos ejemplos, si prefiere que se cargue con un comando:
{ ... }:
{
programs.nvf.settings.vim.lazy.plugins = {
mini-indentscope = {
package = "mini-indentscope";
setupModule = "mini.indentscope";
setupOpts = {
ignore_filetypes = [ "help" "neo-tree" ];
};
event = "BufEnter";
cmd = "ComandoPerron";
};
};
}O con un comando y un keymap:
{ ... }:
{
programs.nvf.settings.vim.lazy.plugins = {
mini-indentscope = {
package = "mini-indentscope";
setupModule = "mini.indentscope";
setupOpts = {
ignore_filetypes = [ "help" "neo-tree" ];
};
cmd = "ComandoPerron";
keys = [
{
mode = "n";
key = "<leader>c";
action = ":ComandoPerron<cr>";
desc = "Comando perrón";
}
];
};
};
}De esta forma, usted ya sabe cómo configurar el 80% (estadística completamente
inventada) de los plugins de Neovim. Pero, ¿recuerda lo que habíamos dicho con
respecto a la opción setupOpts? Apuntemos a un ejemplo específico: ¿Qué pasa
si quiere cargar nvim-lint de forma huevona?
Cuando Nix y Lua dejan de llevarse bien
Como es evidente, llega un momento en el que resulta imposible traducir Nix a Lua, es aquí en donde entran en papel ciertos parches ofrecidos por nvf. Abordando la pregunta pendiente, empecemos haciendo lo que sí podemos hacer con Nix puro:
{ ... }:
{
programs.nvf.settings.vim.lazy.plugins = {
nvim-lint = {
package = "nvim-lint";
event = "VimEnter";
};
};
}Hasta ahora, lo único que hicimos fue instalar el plugin y decirle a Neovim que
no lo cargue hasta que suceda el evento VimEnter. Si se referie a la
documentación de nvim-lint hallará que se configura usando tablas Lua de la
siguiente manera:
require("lint").linters_by_ft = {
nix = { "statix" },
python = { "ruff" },
tex = { "chktex" },
}Lo cual es algo que, como ya se mencionó anteriormente, es imposible de hacer
en Nix, es aquí donde entra nuestros buenos amigos before, beforeSetup,
after que, como ya se podrá imaginar, se tratan de attributos que permiten
correr código en Lua antes de que el plugin sea cargado, entre la función
setup() y la carga del plugin y después de la función setup() (si no se
provee un setupModule entonces se corre después de cargar el plugin).
Volviendo al problema de nvim-lint, para configurarlo es necesario omitir el
atributo setupModule y agregar el código de Lua pertinente bajo after:
{ ... }:
{
programs.nvf.settings.vim.lazy.plugins = {
nvim-lint = {
package = "nvim-lint";
event = "VimEnter";
after = ''
require("lint").linters_by_ft = {
nix = { "statix" },
python = { "ruff" },
tex = { "chktex" },
}
'';
};
};
}Para cada pequeña parte en la que se genera código en Lua (es decir, prácticamente todas las opciones expuestas) existe otra opción colindante que permite adjuntar Lua directamente para aquellas ocasiones en las que una traducción directa de Nix no es posible.
Agregar plugins no disponibles en nixpkgs
Agregue el repositorio de su plugin como input en su flake:
{
inputs = {
# ...
tu-plugin = {
url = "github:autor/tu-plugin";
flake = false;
};
# ...
};
}Luego, debe importarlo en donde lo vaya a utilizar:
{ pkgs, inputs, ... }:
let
tu-plugin = pkgs.vimUtils.buildVimPlugin {
name = "tu-plugin";
src = inputs.tu-plugin;
};
in
{
programs.nvf.settings.vim.extraPlugins = {
tu-plugin = {
package = tu-plugin;
setup = "require("tu-plugin").setup()";
};
};
}O, si lo quiere cargar huevonamente:
{ pkgs, inputs, ... }:
let
tu-plugin = pkgs.vimUtils.buildVimPlugin {
name = "tu-plugin";
src = inputs.tu-plugin;
};
in
{
programs.nvf.settings.vim.lazy.plugins = {
tu-plugin = {
package = tu-plugin;
setupModule = "tu-plugin";
setupOpts = {
opcion = "valor";
};
};
};
}Cerrando
Esto fue nvf, ahora tiene el conocimiento necesario para migrar su configuración de Neovim a nvf y gozar de sus beneficios, así como sufrir sus limitantes. Dejo aquí otros materiales de consulta al respecto que considero valen muchísimo la pena: