Introducción
Ultimamente me he estado metiendo mas de lleno al mundo de React, y creo que probablemente estés familiarizado con su modelo de componentes. ¿Qué pasaría si pudieras aplicar esos mismos conceptos dentro de Neovim para crear interfaces de usuario interactivas? Eso es exactamente lo que ofrece morph.nvim

morph.nvim es un renderer inspirado en React que permite construir interfaces de usuario interactivas en buffers de Neovim usando componentes, estado y un sistema de reconciliación eficiente. En lugar de gestionar manualmente el contenido del buffer, extmarks y keymaps, puedes declarar tu UI y dejar que morph.nvim se encargue del resto.
¿Qué es morph.nvim?
morph.nvim transforma Neovim en un potente framework TUI (Terminal User Interface), permitiéndote crear buffers dinámicos e interactivos usando patrones familiares de React como componentes, estado y manejadores de eventos. Es perfecto para construir UIs personalizadas, formularios, dashboards, exploradores de archivos o cualquier interfaz basada en texto dentro del entorno de edición de Neovim.
Características principales
- Arquitectura basada en componentes: Componentes reutilizables con props, estado y métodos de ciclo de vida
- Reconciliación eficiente: Solo actualiza lo que cambió, usando un algoritmo similar al Virtual DOM de React
- Estilizado de texto enriquecido: Aplica grupos de highlight y extmarks con atributos simples
- Manejo de eventos interactivos: Responde a teclas con manejadores específicos por modo
- Detección de cambios de texto: Detecta automáticamente cuando los usuarios editan texto dentro de tags
Ejemplo del mundo real
Uno de los ejemplos más interesantes en la documentación de morph.nvim es un formulario de búsqueda que demuestra varias características clave:
--- @param ctx morph.Ctx<{}, { query: string, results: table[] }>
local function SearchForm(ctx)
if ctx.phase == 'mount' then
ctx.state = { query = '', results = {} }
end
return {
h.Title({}, 'Search: '),
h('text', {
on_change = function(e)
ctx:update({
query = e.text,
results = performSearch(e.text)
})
end
}, ctx.state.query),
'\n\n',
-- Results
vim.tbl_map(function(result)
return {
h.Directory({
nmap = {
['<CR>'] = function()
vim.cmd('edit ' .. result.path)
return ''
end
}
}, result.name),
'\n'
}
end, ctx.state.results)
}
end
Este componente muestra:
- Estado local:
queryyresultsse mantienen en el estado del componente - Eventos en tiempo real:
on_changese dispara mientras el usuario escribe - Manejo de teclas:
<CR>abre el archivo correspondiente - Renderizado condicional: Muestra resultados solo cuando existen
Sintaxis Hyperscript
morph.nvim usa una sintaxis similar a Hyperscript para crear elementos, parecida al JSX de React pero en Lua:
-- Elemento básico de texto
h('text', { hl = 'Comment' }, 'Hello world')
-- Abreviatura para grupos de highlight
h.Comment({}, 'Hello world') -- equivalente al anterior
-- Elementos anidados
h('text', {}, {
'Outer text ',
h.Keyword({}, 'highlighted'),
' more text'
})
-- Con manejadores de eventos
h.Directory({
nmap = {
['<CR>'] = function()
vim.cmd('edit ' .. filename)
return '' -- consume la tecla
end
}
}, filename)
Contexto de componentes
En lugar de hooks estilo React como useState y useEffect, morph.nvim usa un objeto de contexto (ctx) que persiste a través de los renders. Este enfoque es más simple y predecible:
--- @param ctx morph.Ctx<{ initial: number }, { count: number, history: number[] }>
local function StatefulCounter(ctx)
-- Inicializa estado solo en el primer render
if ctx.phase == 'mount' then
ctx.state = {
count = ctx.props.initial or 0,
history = {}
}
end
local state = ctx.state
return {
'Count: ', tostring(state.count), '\n',
'History: ', table.concat(state.history, ', '), '\n',
h.Keyword({
nmap = {
['<CR>'] = function()
-- Actualiza estado y dispara re-render
ctx:update({
count = state.count + 1,
history = vim.list_extend({}, state.history, { state.count })
})
return ''
end
}
}, '[Press Enter to increment]')
}
end
Propiedades clave del contexto:
ctx.props: Propiedades del componente (solo lectura, actualizadas por el padre)ctx.state: Estado del componente (tus datos persistentes entre renders)ctx.children: Elementos hijos pasados a este componentectx:update(new_state): Actualiza estado y dispara re-renderctx.phase: Fase actual del ciclo de vida ('mount', 'update', 'unmount')
Instalación
Para autores de plugins
Neovim no tiene una buena solución para la gestión automática de dependencias de plugins. Se recomienda que los autores de bibliotecas incluyan morph.nvim dentro de su plugin. morph.nvim está implementado en un solo archivo, por lo que esto debería ser relativamente sencillo.
# En tu repositorio de plugin
git submodule add -- https://github.com/jrop/morph.nvim lua/my_plugin/morph
cd lua/my_plugin/morph/
git checkout artifact-v0.1.0 # usa la versión que quieras
Para usuarios que quieren usar morph.nvim en su configuración
lazy.nvim:
{ 'jrop/morph.nvim' }
packer.nvim:
use({ 'jrop/morph.nvim' })
¿Por qué morph.nvim?
Neovim ya es un editor de texto excepcional, pero morph.nvim desbloquea su potencial como un host de aplicaciones TUI con todas las funciones. En lugar de estar limitado a UIs tradicionales de plugins, puedes construir aplicaciones ricas e interactivas que se sientan nativas de la terminal mientras aprovechas las poderosas capacidades de manipulación de texto de Neovim.
Construir UIs interactivas en Neovim tradicionalmente requiere gestionar manualmente el contenido del buffer, extmarks, keymaps y autocmds. morph.nvim abstrae esta complejidad detrás de una API declarativa y basada en componentes que se siente familiar para los desarrolladores web mientras está optimizada para las capacidades únicas de Neovim.
MFP
Creo que no lo he dado a conocer lo suficiente, pero cuando trabajaba en una oficina generalmente había demasiado ruido, de hecho, a veces era difícil concentrarse. Para solucionar esto, siempre visitaba la pagina de Music For Programming y ponía música de fondo para ayudarme a concentrarme. La música de esta página es especialmente diseñada para mejorar la concentración y la productividad, lo que la convierte en una excelente opción para programadores.
Entonces lo que hice fue crear un cliente de terminal con Rust, para luego hacer su implementacion en Neovim usando morph.nvim, y así poder escuchar música de fondo mientras programaba sin tener que salir de mi editor. El resultado fue un plugin llamado mfp.nvim
Es un gran ejemplo de cómo morph.nvim puede ser utilizado para crear aplicaciones TUI completas dentro de Neovim, integrando funcionalidades como reproducción de música, controles interactivos y visualizaciones, todo sin salir del editor.
Proyectos similares
Conclusión
morph.nvim representa un paso significativo hacia la creación de aplicaciones TUI sofisticadas dentro de Neovim. Al traer los patrones de React al mundo de los editores de texto, abre nuevas posibilidades para plugins y herramientas que pueden ofrecer experiencias de usuario más ricas e interactivas.
Si ya estás familiarizado con React y usas Neovim, morph.nvim te permitirá aplicar tus conocimientos existentes para crear interfaces que antes requerían una gestión manual compleja. Es una herramienta poderosa que merece la pena explorar si estás interesado en llevar tus plugins de Neovim al siguiente nivel.

Referencias:
