██ ██ ██ ██ ██
░██ ░██ ░░ ░██ ░░
██████ ██████████ ██ ██ ██████ ██ ░██ ██████ ███████ ██ ██ ██ ██████████
░░░██░ ░░██░░██░░██░██ ░██░░░██░ ░██ ░██ ██░░░░ ░░██░░░██░██ ░██░██░░██░░██░░██
░██ ░██ ░██ ░██░██ ░██ ░██ ░██ ░██░░█████ ░██ ░██░░██ ░██ ░██ ░██ ░██ ░██
░██ ░██ ░██ ░██░██ ░██ ░██ ░██ ░██ ░░░░░██ ██ ░██ ░██ ░░████ ░██ ░██ ░██ ░██
░░██ ███ ░██ ░██░░██████ ░░██ ░██ ███ ██████ ░██ ███ ░██ ░░██ ░██ ███ ░██ ░██
░░ ░░░ ░░ ░░ ░░░░░░ ░░ ░░ ░░░ ░░░░░░ ░░ ░░░ ░░ ░░ ░░ ░░░ ░░ ░░
tmutils.nvim
is a Neovim plugin designed to streamline common development tasks that involve both tmux
and Neovim. Key features include:
- Sending a range of lines from Neovim to a
tmux
pane. - Collecting output from a
tmux
pane into Neovim. - Creating a configurable
tmux
pane to serve as a terminal. - Setting up and managing REPLs within
tmux
panes directly from Neovim. - Creating a scratch window that uses the syntax of the configured REPL and helps editing the commands to send.
For a more detailed guide and documentation, review the help page: :help tmutils.txt
-
lazy
: to install this plugin using lazy:{ "juselara1/tmutils.nvim", dependencies = { --NOTE: you can optionally add one of these dependencies if you --want to use a custom selector different from the default vim.ui --selector. --"MunifTanjim/nui.nvim", --"nvim-telescope/telescope.nvim" --"vijaymarupudi/nvim-fzf" }, config = function() require("tmutils").setup({}) end, }
A comprehensive usage guide can be read in the help page: :help tmutils-usage
. However, here are some
examples of it:
-
Handling external panes:
external_panes.mp4
- Set up a target pane:
:help :TmutilsConfig
. - Send a range of lines to the pane:
:help :TmutilsSend
. - Capture the pane's content into a buffer:
:help :TmutilsCapture
.
- Set up a target pane:
-
Creating a terminal pane:
terminal_pane.mp4
- Create a new terminal pane:
:help :TmutilsWindow
. - Send a range of lines to the terminal:
:help :TmutilsSend
. - Delete the terminal:
:help :TmutilsWindow
.
- Create a new terminal pane:
-
Creating a repl pane:
repl_pane.mp4
- Create a new REPL pane (ensure the
window.repls.{repl}
option is set in yoursetup
)::help :TmutilsWindow
. - Send a range of lines to the REPL:
:help :TmutilsSend
. - Delete the REPL:
:help :TmutilsWindow
.
- Create a new REPL pane (ensure the
-
Using a scratch window:
scratch.mp4
- Create a terminal or a REPL pane (ensure the
window.repls.{repl}
option is set in yoursetup
)::help :TmutilsWindow
. - Send a range of lines to the REPL:
:help :TmutilsSend
. - Toggle the scratch window:
:help :TmutilsScratchToggle
- Delete the terminal or REPL:
:help :TmutilsWindow
- Create a terminal or a REPL pane (ensure the
-
Literate programming:
literate.mp4
Let's see a more advanced example for literate programming using markdown as the base file format and an IPython repl:
-
We'll use the
mini.ai
plugin to define a custom text objectx
(vix
will select all the code inside of a cell that's usually delimited using a triple backquote). Let's see the configuration usinglazy.nvim
:{ "echasnovski/mini.nvim", config = function() require("mini.ai").setup({ custom_textobjects = { ---Code cell x = { "```%S+%s()[^`]+()```" }, }, mappings = { -- Main textobject prefixes around = "a", inside = "i", }, search_method = "cover_or_next", silent = true, }) end, }
-
Let's configure the repl in
tmutils
:local selectors = require("tmutils.selectors") require("tmutils").setup({ window = { repls = { --We configure here an IPython repl ipython = { syntax = "python", commands = function() return { ("cd %s"):format(vim.fn.getcwd()), "ipython", "clear", } end, }, }, }, })
-
Now, we'll define some useful keybindings:
--<leader>r creates a repl vim.keymap.set("n", "<leader>r", ":TmutilsWindow repl<CR>", { noremap = true, silent = true, desc = "Shows a menu to select and launch a repl", }) --<leader>x sends a code cell to the repl vim.keymap.set("n", "<leader>x", function() vim.cmd("norm vix") local pos_l = vim.fn.getpos(".") local pos_r = vim.fn.getpos("v") vim.cmd(("%d,%dTmutilsSend"):format(pos_l[2], pos_r[2])) vim.api.nvim_input("<Esc>") end, { noremap = true, silent = false, desc = "Sends a code cell to a tmux pane", })
-
Open or create a markdown file, you can define Python cells using the following format:
```python import os import sys ```
-
We can populate the quickfix list using
:vimgrep
to find the start of all the code cells. This allows an easy navigation between cells using:cnext
and:cprev
::vim /\v```python/ %
-
Open a new repl using
<leader>r
, navigate between cells using:cnext
and:cprev
, and execute them using<leader>x
.
-
The configuration table has the following schema (review LuaLS
type specification for the type annotations):
{
--Configuration for UI-based selection.
selector = {
--The backend used to select options.
selector = [[
fun(
opts: string[],
message: string,
callback: fun(selected_opt: string): nil
): nil
]],
},
--Configuration for the scratch window.
scratch = {
--Scratch window width.
width = "integer",
--Scratch window height.
height = "integer",
--Scratch window center col.
col = "integer",
--Scratch window center row.
row = "integer",
--Scratch window border.
border = "none" | "single" | "double" | "rounded" | "solid" | "shadow",
--Scratch window title position.
title_pos = "center" | "left" | "right",
},
--Configuration for window management commands.
window = {
--Configuration for the terminal pane.
terminal = {
--Direction in which to split the terminal pane.
direction = '"vertical" | "horizontal"',
--Relative size (in percentage) for the terminal pane.
size = "number",
--Function that returns a list of commands to be executed
--when launching a new terminal pane.
commands = "fun(): string[]",
},
repls = {
--Assign a key to the repl
["string"] = {
--Direction in which to split the repl pane.
direction = '"vertical" | "horizontal"',
--Syntax for this REPL.
syntax = "string",
--Relative size (in percentage) for the repl pane.
size = "number",
--Function that returns a list of commands to be executed
--when launching a new repl pane.
commands = "fun(): string[]",
},
},
},
}
Let's see a full example configuration using lazy.nvim
with the following features:
- Uses
nvim-telescope/telescope.nvim
as the selector. - Setups three REPLs:
python
,ipython
, andcompose
. - Creates keybindings for different user commands.
{
"juselara1/tmutils.nvim",
dependencies = {
"nvim-telescope/telescope.nvim",
},
config = function()
local selectors = require("tmutils.selectors")
require("tmutils").setup({
selector = {
selector = selectors.telescope_selector,
},
window = {
repls = {
python = {
syntax = "python",
commands = function()
return {
("cd %s"):format(vim.fn.getcwd()),
"clear",
"python",
}
end,
},
ipython = {
syntax = "python",
commands = function()
return {
("cd %s"):format(vim.fn.getcwd()),
"ipython",
"clear",
}
end,
},
compose = {
syntax = "sh",
commands = function()
return {
("cd %s"):format(vim.fn.getcwd()),
"docker compose up -d",
"docker exec -it `docker compose config --services` bash",
"clear",
}
end,
},
},
},
})
vim.keymap.set("n", "<leader>tc", ":TmutilsConfig<CR>", {
noremap = true,
silent = true,
desc = "Setups the Tmutils pane.",
})
vim.keymap.set("n", "<leader>ta", ":TmutilsCapture newbuffer<CR>", {
noremap = true,
silent = true,
desc = "Captures the content of a Tmutils pane.",
})
vim.keymap.set("n", "<leader>tt", ":TmutilsWindow terminal<CR>", {
noremap = true,
silent = true,
desc = "Launches a Tmutils terminal.",
})
vim.keymap.set("n", "<leader>tr", ":TmutilsWindow repl<CR>", {
noremap = true,
silent = true,
desc = "Shows a menu to select and launch a Tmutils repl.",
})
vim.keymap.set("n", "<leader>td", ":TmutilsWindow delete<CR>", {
noremap = true,
silent = true,
desc = "Deletes the configured Tmutils pane.",
})
vim.keymap.set("n", "<leader>ts", ":TmutilsScratchToggle<CR>", {
noremap = true,
silent = true,
desc = "Opens Tmutils Scratch",
})
vim.keymap.set("n", "<leader>tx", function()
vim.cmd("norm vix")
local pos_l = vim.fn.getpos(".")
local pos_r = vim.fn.getpos("v")
vim.cmd(("%d,%dTmutilsSend"):format(pos_l[2], pos_r[2]))
vim.api.nvim_input("<Esc>")
end, {
noremap = true,
silent = true,
desc = "Sends a code cell to a Tmutils pane.",
})
vim.keymap.set("n", "<leader>tl", ":.TmutilsSend<CR>", {
noremap = true,
silent = true,
desc = "Sends a visual selection to a Tmutils pane.",
})
vim.keymap.set("v", "<leader>tv", ":TmutilsSend<CR>", {
noremap = true,
silent = true,
desc = "Sends a visual selection to a Tmutils pane.",
})
end,
}