a Neovim plugin that converts text to speech using each operating system's native TTS engine: say on macOS, PowerShell on Windows, and spd-say / espeak-ng / espeak / festival on Linux. No external Lua dependencies or paid APIs required.
Lua
1
32 commits
updated Sep 28, 2026
tennant.nvim is a Neovim plugin that converts text to speech using each operating system's native TTS engine: say on macOS, PowerShell on Windows, and spd-say / espeak-ng / espeak / festival on Linux. No external Lua dependencies or paid APIs required.
| Platform | Requirement |
|---|---|
| macOS | say (built-in) |
| Linux | spd-say, espeak-ng, espeak or festival (any one) |
| Windows | PowerShell + .NET Framework (built-in) |
| Neovim | >= 0.12 |
| Treesitter | Optional — improves block reading by type |
Add this to your init.lua:
vim.pack.add({ 'https://github.com/themakunga/tennant.nvim' })
require('tennant').setup()
{
'themakunga/tennant.nvim',
event = 'VeryLazy',
opts = {
-- prefix = '<leader>tv', -- default prefix
},
}
use {
'themakunga/tennant.nvim',
config = function()
require('tennant').setup()
end,
}
Plug 'themakunga/tennant.nvim'
Then in your config:
require('tennant').setup()
:h packages)mkdir -p ~/.local/share/nvim/site/pack/plugins/start
git clone https://github.com/themakunga/tennant.nvim \
~/.local/share/nvim/site/pack/plugins/start/tennant.nvim
Add to your init.lua:
require('tennant').setup()
# in your rocks.toml
[plugins]
"tennant.nvim" = "scm"
Or in your config:
require('rocks').install('tennant.nvim')
require('tennant').setup({
prefix = '<leader>tv',
language = 'en', -- 'es' (default) or 'en'
tts = {
-- backend = 'espeak-ng', -- optional; choose an installed engine
-- voice = 'en', -- voice name for the chosen engine
-- rate = 190, -- units and range depend on the engine
},
})
Set language = 'en' for English announcements, spoken labels, and command/keymap descriptions. Spanish ('es') remains the default. File contents and external notifications are read without translation. tts.voice selects an installed voice; language does not automatically change it. See the options table at the end.
With the default prefix <leader>tv:
| Key | Action |
|---|---|
<leader>tvw | Read word under cursor |
<leader>tvl | Read current line |
<leader>tvl (Visual mode) | Read selected text (v, V, or Ctrl-v) |
<leader>tvb | Read the full nearest block (uses Treesitter if available) |
<leader>tvp | Move up and read the enclosing block |
<leader>tvn | Read notifications |
<leader>tvs | Stop speaking |
| Command | Description |
|---|---|
:TennantWord | Read word under cursor |
:[range]TennantLine | Read current line or the given range (for example, :2,5TennantLine) |
:TennantBlock | Read the full nearest block |
:TennantParent | Move up and read the enclosing block |
:TennantNotify | Read notifications |
:TennantStop | Stop current speech |
<leader>tvb starts at the nearest block to the cursor. Each <leader>tvp moves up one level through the whole file, then announces “No more nesting levels” when configured in English. Moving the cursor or editing text restarts traversal from the current position. Without a Treesitter parser, reading falls back to the current line.
<leader>tvn or :TennantNotify opens a temporary window, announces the number of notifications captured during the session, and reads the first in arrival order. Press n for the next notification or x to cancel, stop speech, and close the window. Esc, :TennantStop, and <leader>tvs also cancel. The last notification announces the end; x closes the window. Your usual mappings remain intact outside it. New arrivals are included when starting another traversal; history is retained until Neovim exits.
When Treesitter is available, :TennantBlock identifies and announces the block type:
function, method, arrow function, variable, constant, class, if block, for loop, while loop, try block, return, import, export.
tennant.nvim es un plugin de Neovim que convierte texto en voz usando el motor TTS nativo de cada sistema operativo: say en macOS, PowerShell en Windows y spd-say / espeak-ng / espeak / festival en Linux. No requiere dependencias externas de Lua ni APIs de pago.
| Plataforma | Requisito |
|---|---|
| macOS | say (incluido en el sistema) |
| Linux | spd-say, espeak-ng, espeak o festival (cualquiera) |
| Windows | PowerShell + .NET Framework (incluido en Windows) |
| Neovim | >= 0.12 |
| Treesitter | Opcional — mejora la lectura de bloques por tipo |
Agrega esto a tu init.lua:
vim.pack.add({ 'https://github.com/themakunga/tennant.nvim' })
require('tennant').setup()
{
'themakunga/tennant.nvim',
event = 'VeryLazy',
opts = {
-- prefix = '<leader>tv', -- prefijo por defecto
},
}
use {
'themakunga/tennant.nvim',
config = function()
require('tennant').setup()
end,
}
Plug 'themakunga/tennant.nvim'
Luego en tu config:
require('tennant').setup()
:h packages)mkdir -p ~/.local/share/nvim/site/pack/plugins/start
git clone https://github.com/themakunga/tennant.nvim \
~/.local/share/nvim/site/pack/plugins/start/tennant.nvim
Agrega en tu init.lua:
require('tennant').setup()
# en tu rocks.toml
[plugins]
"tennant.nvim" = "scm"
O en tu config:
require('rocks').install('tennant.nvim')
require('tennant').setup({
prefix = '<leader>tv',
language = 'es', -- 'es' (predeterminado) o 'en'
tts = {
-- backend = 'espeak-ng', -- opcional; fuerza un motor instalado
-- voice = 'es', -- nombre de voz del motor seleccionado
-- rate = 190, -- unidad y rango dependen del motor
},
})
language cambia los avisos, las etiquetas leídas y las descripciones de comandos y atajos. El contenido del archivo y las notificaciones externas se leen sin traducir. tts.voice selecciona una voz instalada; language no cambia la voz automáticamente. Consulta la tabla de opciones al final.
Con el prefijo por defecto <leader>tv:
| Atajo | Acción |
|---|---|
<leader>tvw | Leer la palabra bajo el cursor |
<leader>tvl | Leer la línea actual |
<leader>tvl (modo visual) | Leer el texto seleccionado (v, V o Ctrl-v) |
<leader>tvb | Leer el bloque completo más cercano (usa Treesitter si está disponible) |
<leader>tvp | Subir y leer el bloque contenedor |
<leader>tvn | Leer las notificaciones |
<leader>tvs | Detener la lectura |
| Comando | Descripción |
|---|---|
:TennantWord | Lee la palabra bajo el cursor |
:[rango]TennantLine | Lee la línea actual o el rango indicado (por ejemplo, :2,5TennantLine) |
:TennantBlock | Lee el bloque completo más cercano |
:TennantParent | Sube y lee el bloque contenedor |
:TennantNotify | Lee las notificaciones |
:TennantStop | Detiene la lectura en curso |
<leader>tvb inicia la lectura desde el bloque más cercano al cursor. Cada <leader>tvp sube un nivel hasta leer el archivo; después anuncia «No existen más niveles de anidación». Mover el cursor o editar el texto reinicia el recorrido desde la posición actual. Sin un parser de Treesitter, se lee la línea actual.
<leader>tvn o :TennantNotify abre una ventana temporal, anuncia el total de notificaciones capturadas durante la sesión y lee la primera, en orden de llegada. Presiona n para leer la siguiente y x para cancelar, detener la voz y cerrar la ventana. Esc, :TennantStop y <leader>tvs también cancelan. Al terminar, se anuncia que no hay más notificaciones; x cierra la ventana. Tus atajos habituales se conservan fuera de ella. Las notificaciones nuevas se incluyen al iniciar otro recorrido; el historial se conserva hasta cerrar Neovim.
Cuando Treesitter está disponible, :TennantBlock identifica y anuncia el tipo de bloque:
función, método, función flecha, variable, constante, clase, bloque if, bucle for, bucle while, bloque try, retorno, importación, exportación.
See CONTRIBUTING.md for hooks, tests, branch policy and daily
pre-releases. Consulta esa guía para contribuir mediante PR hacia main.
Security reports / Reportes de seguridad: SECURITY.md.
MIT
require('tennant').setup({ ... }) accepts / acepta:
| Option / Opción | Default / Predeterminado | Description / Descripción |
|---|---|---|
prefix | '<leader>tv' | Keymap prefix / Prefijo de atajos. |
language | 'es' | Announcements and labels: 'es' or 'en' / Avisos y etiquetas: 'es' o 'en'. |
tts.backend | Auto-detected / Autodetectado | 'say' (macOS), 'powershell' (Windows), 'spd-say', 'espeak-ng', 'espeak' or / o 'festival' (Linux). The chosen engine must be available / El motor indicado debe estar disponible. |
tts.voice | System default / Voz del sistema | Name of an installed voice / Nombre de una voz instalada. Unavailable with / No disponible con festival. |
tts.rate | Engine default / Predeterminado del motor | Speed; units and ranges below / Velocidad; unidades y rangos en la tabla siguiente. |
tts.pitch | Engine default / Predeterminado del motor | Pitch / Tono; supported by / compatible con spd-say, espeak-ng, espeak. |
tts.volume | Engine default / Predeterminado del motor | Volume or amplitude / Volumen o amplitud; supported by / compatible con Windows, spd-say, espeak-ng, espeak. |
| Engine / Motor | tts.voice | tts.rate | tts.pitch | tts.volume |
|---|---|---|---|---|
macOS say | Voice from say -v '?' / Voz de say -v '?' | Words per minute, integer ≥ 1 / Palabras por minuto, entero ≥ 1 | — | — |
| Windows PowerShell | Installed voice name / Nombre de voz instalada | Integer / Entero −10…10 | — | Integer / Entero 0…100 |
Linux spd-say | Synthesis voice (spd-say -L) / Voz de síntesis (spd-say -L) | Integer / Entero −100…100 | Integer / Entero −100…100 | Integer / Entero −100…100 |
Linux espeak-ng, espeak | Voice from --voices / Voz de --voices | Words per minute, integer ≥ 1 / Palabras por minuto, entero ≥ 1 | Integer / Entero 0…99 | Amplitude, integer / Amplitud, entero 0…200 |
Linux festival | — | — | — | — |
English: On Linux, the first available engine is selected in this order: spd-say, espeak-ng, espeak, festival. Use tts.backend to choose another. All tts options are optional; an out-of-range value, unavailable engine, or option unsupported by the chosen engine raises a configuration error. Voice names and speed units depend on the engine. Example: tts = { backend = 'espeak-ng', voice = 'en', rate = 190, pitch = 55 }.
Español: En Linux, el primer motor disponible se elige en este orden: spd-say, espeak-ng, espeak, festival. Usa tts.backend para elegir otro. Todas las opciones de tts son opcionales; un valor fuera de rango, un motor inexistente o una opción que no admita el motor produce un error de configuración. El nombre de voz y las unidades de velocidad dependen del motor. Ejemplo: tts = { backend = 'espeak-ng', voice = 'es', rate = 190, pitch = 55 }.
Lua
73.3%
Python
26.7%
a Neovim plugin that converts text to speech using each operating system's native TTS engine: say on macOS, PowerShell on Windows, and spd-say / espeak-ng / espeak / festival on Linux. No external Lua dependencies or paid APIs required.
Lua
1
32 commits
updated Sep 28, 2026
tennant.nvim is a Neovim plugin that converts text to speech using each operating system's native TTS engine: say on macOS, PowerShell on Windows, and spd-say / espeak-ng / espeak / festival on Linux. No external Lua dependencies or paid APIs required.
| Platform | Requirement |
|---|---|
| macOS | say (built-in) |
| Linux | spd-say, espeak-ng, espeak or festival (any one) |
| Windows | PowerShell + .NET Framework (built-in) |
| Neovim | >= 0.12 |
| Treesitter | Optional — improves block reading by type |
Add this to your init.lua:
vim.pack.add({ 'https://github.com/themakunga/tennant.nvim' })
require('tennant').setup()
{
'themakunga/tennant.nvim',
event = 'VeryLazy',
opts = {
-- prefix = '<leader>tv', -- default prefix
},
}
use {
'themakunga/tennant.nvim',
config = function()
require('tennant').setup()
end,
}
Plug 'themakunga/tennant.nvim'
Then in your config:
require('tennant').setup()
:h packages)mkdir -p ~/.local/share/nvim/site/pack/plugins/start
git clone https://github.com/themakunga/tennant.nvim \
~/.local/share/nvim/site/pack/plugins/start/tennant.nvim
Add to your init.lua:
require('tennant').setup()
# in your rocks.toml
[plugins]
"tennant.nvim" = "scm"
Or in your config:
require('rocks').install('tennant.nvim')
require('tennant').setup({
prefix = '<leader>tv',
language = 'en', -- 'es' (default) or 'en'
tts = {
-- backend = 'espeak-ng', -- optional; choose an installed engine
-- voice = 'en', -- voice name for the chosen engine
-- rate = 190, -- units and range depend on the engine
},
})
Set language = 'en' for English announcements, spoken labels, and command/keymap descriptions. Spanish ('es') remains the default. File contents and external notifications are read without translation. tts.voice selects an installed voice; language does not automatically change it. See the options table at the end.
With the default prefix <leader>tv:
| Key | Action |
|---|---|
<leader>tvw | Read word under cursor |
<leader>tvl | Read current line |
<leader>tvl (Visual mode) | Read selected text (v, V, or Ctrl-v) |
<leader>tvb | Read the full nearest block (uses Treesitter if available) |
<leader>tvp | Move up and read the enclosing block |
<leader>tvn | Read notifications |
<leader>tvs | Stop speaking |
| Command | Description |
|---|---|
:TennantWord | Read word under cursor |
:[range]TennantLine | Read current line or the given range (for example, :2,5TennantLine) |
:TennantBlock | Read the full nearest block |
:TennantParent | Move up and read the enclosing block |
:TennantNotify | Read notifications |
:TennantStop | Stop current speech |
<leader>tvb starts at the nearest block to the cursor. Each <leader>tvp moves up one level through the whole file, then announces “No more nesting levels” when configured in English. Moving the cursor or editing text restarts traversal from the current position. Without a Treesitter parser, reading falls back to the current line.
<leader>tvn or :TennantNotify opens a temporary window, announces the number of notifications captured during the session, and reads the first in arrival order. Press n for the next notification or x to cancel, stop speech, and close the window. Esc, :TennantStop, and <leader>tvs also cancel. The last notification announces the end; x closes the window. Your usual mappings remain intact outside it. New arrivals are included when starting another traversal; history is retained until Neovim exits.
When Treesitter is available, :TennantBlock identifies and announces the block type:
function, method, arrow function, variable, constant, class, if block, for loop, while loop, try block, return, import, export.
tennant.nvim es un plugin de Neovim que convierte texto en voz usando el motor TTS nativo de cada sistema operativo: say en macOS, PowerShell en Windows y spd-say / espeak-ng / espeak / festival en Linux. No requiere dependencias externas de Lua ni APIs de pago.
| Plataforma | Requisito |
|---|---|
| macOS | say (incluido en el sistema) |
| Linux | spd-say, espeak-ng, espeak o festival (cualquiera) |
| Windows | PowerShell + .NET Framework (incluido en Windows) |
| Neovim | >= 0.12 |
| Treesitter | Opcional — mejora la lectura de bloques por tipo |
Agrega esto a tu init.lua:
vim.pack.add({ 'https://github.com/themakunga/tennant.nvim' })
require('tennant').setup()
{
'themakunga/tennant.nvim',
event = 'VeryLazy',
opts = {
-- prefix = '<leader>tv', -- prefijo por defecto
},
}
use {
'themakunga/tennant.nvim',
config = function()
require('tennant').setup()
end,
}
Plug 'themakunga/tennant.nvim'
Luego en tu config:
require('tennant').setup()
:h packages)mkdir -p ~/.local/share/nvim/site/pack/plugins/start
git clone https://github.com/themakunga/tennant.nvim \
~/.local/share/nvim/site/pack/plugins/start/tennant.nvim
Agrega en tu init.lua:
require('tennant').setup()
# en tu rocks.toml
[plugins]
"tennant.nvim" = "scm"
O en tu config:
require('rocks').install('tennant.nvim')
require('tennant').setup({
prefix = '<leader>tv',
language = 'es', -- 'es' (predeterminado) o 'en'
tts = {
-- backend = 'espeak-ng', -- opcional; fuerza un motor instalado
-- voice = 'es', -- nombre de voz del motor seleccionado
-- rate = 190, -- unidad y rango dependen del motor
},
})
language cambia los avisos, las etiquetas leídas y las descripciones de comandos y atajos. El contenido del archivo y las notificaciones externas se leen sin traducir. tts.voice selecciona una voz instalada; language no cambia la voz automáticamente. Consulta la tabla de opciones al final.
Con el prefijo por defecto <leader>tv:
| Atajo | Acción |
|---|---|
<leader>tvw | Leer la palabra bajo el cursor |
<leader>tvl | Leer la línea actual |
<leader>tvl (modo visual) | Leer el texto seleccionado (v, V o Ctrl-v) |
<leader>tvb | Leer el bloque completo más cercano (usa Treesitter si está disponible) |
<leader>tvp | Subir y leer el bloque contenedor |
<leader>tvn | Leer las notificaciones |
<leader>tvs | Detener la lectura |
| Comando | Descripción |
|---|---|
:TennantWord | Lee la palabra bajo el cursor |
:[rango]TennantLine | Lee la línea actual o el rango indicado (por ejemplo, :2,5TennantLine) |
:TennantBlock | Lee el bloque completo más cercano |
:TennantParent | Sube y lee el bloque contenedor |
:TennantNotify | Lee las notificaciones |
:TennantStop | Detiene la lectura en curso |
<leader>tvb inicia la lectura desde el bloque más cercano al cursor. Cada <leader>tvp sube un nivel hasta leer el archivo; después anuncia «No existen más niveles de anidación». Mover el cursor o editar el texto reinicia el recorrido desde la posición actual. Sin un parser de Treesitter, se lee la línea actual.
<leader>tvn o :TennantNotify abre una ventana temporal, anuncia el total de notificaciones capturadas durante la sesión y lee la primera, en orden de llegada. Presiona n para leer la siguiente y x para cancelar, detener la voz y cerrar la ventana. Esc, :TennantStop y <leader>tvs también cancelan. Al terminar, se anuncia que no hay más notificaciones; x cierra la ventana. Tus atajos habituales se conservan fuera de ella. Las notificaciones nuevas se incluyen al iniciar otro recorrido; el historial se conserva hasta cerrar Neovim.
Cuando Treesitter está disponible, :TennantBlock identifica y anuncia el tipo de bloque:
función, método, función flecha, variable, constante, clase, bloque if, bucle for, bucle while, bloque try, retorno, importación, exportación.
See CONTRIBUTING.md for hooks, tests, branch policy and daily
pre-releases. Consulta esa guía para contribuir mediante PR hacia main.
Security reports / Reportes de seguridad: SECURITY.md.
MIT
require('tennant').setup({ ... }) accepts / acepta:
| Option / Opción | Default / Predeterminado | Description / Descripción |
|---|---|---|
prefix | '<leader>tv' | Keymap prefix / Prefijo de atajos. |
language | 'es' | Announcements and labels: 'es' or 'en' / Avisos y etiquetas: 'es' o 'en'. |
tts.backend | Auto-detected / Autodetectado | 'say' (macOS), 'powershell' (Windows), 'spd-say', 'espeak-ng', 'espeak' or / o 'festival' (Linux). The chosen engine must be available / El motor indicado debe estar disponible. |
tts.voice | System default / Voz del sistema | Name of an installed voice / Nombre de una voz instalada. Unavailable with / No disponible con festival. |
tts.rate | Engine default / Predeterminado del motor | Speed; units and ranges below / Velocidad; unidades y rangos en la tabla siguiente. |
tts.pitch | Engine default / Predeterminado del motor | Pitch / Tono; supported by / compatible con spd-say, espeak-ng, espeak. |
tts.volume | Engine default / Predeterminado del motor | Volume or amplitude / Volumen o amplitud; supported by / compatible con Windows, spd-say, espeak-ng, espeak. |
| Engine / Motor | tts.voice | tts.rate | tts.pitch | tts.volume |
|---|---|---|---|---|
macOS say | Voice from say -v '?' / Voz de say -v '?' | Words per minute, integer ≥ 1 / Palabras por minuto, entero ≥ 1 | — | — |
| Windows PowerShell | Installed voice name / Nombre de voz instalada | Integer / Entero −10…10 | — | Integer / Entero 0…100 |
Linux spd-say | Synthesis voice (spd-say -L) / Voz de síntesis (spd-say -L) | Integer / Entero −100…100 | Integer / Entero −100…100 | Integer / Entero −100…100 |
Linux espeak-ng, espeak | Voice from --voices / Voz de --voices | Words per minute, integer ≥ 1 / Palabras por minuto, entero ≥ 1 | Integer / Entero 0…99 | Amplitude, integer / Amplitud, entero 0…200 |
Linux festival | — | — | — | — |
English: On Linux, the first available engine is selected in this order: spd-say, espeak-ng, espeak, festival. Use tts.backend to choose another. All tts options are optional; an out-of-range value, unavailable engine, or option unsupported by the chosen engine raises a configuration error. Voice names and speed units depend on the engine. Example: tts = { backend = 'espeak-ng', voice = 'en', rate = 190, pitch = 55 }.
Español: En Linux, el primer motor disponible se elige en este orden: spd-say, espeak-ng, espeak, festival. Usa tts.backend para elegir otro. Todas las opciones de tts son opcionales; un valor fuera de rango, un motor inexistente o una opción que no admita el motor produce un error de configuración. El nombre de voz y las unidades de velocidad dependen del motor. Ejemplo: tts = { backend = 'espeak-ng', voice = 'es', rate = 190, pitch = 55 }.
Lua
73.3%
Python
26.7%