A minimal, standalone Neovim 0.11+ terminal plugin that provides floating and split terminal windows with toggle support. Serves as a drop-in replacement for Snacks.terminal with the same API shape, so existing integrations work with minimal changes.
- πͺ Floating and split terminals - Choose between floating windows or split layouts (bottom, top, left, right)
- π Toggle support - Quickly show/hide terminals with keymaps like
<C-/> - π’ Multiple terminals - Manage multiple terminals with stable IDs based on command, cwd, env, and count
- β¨οΈ Double-Escape to normal mode - Single
<Esc>passes through, double exits to normal mode - π― Snacks.terminal API compatible - Drop-in replacement with
toggle(),open(),get(),list() - π Window stacking - Multiple split terminals at the same position stack together
- π·οΈ VSCode-style winbar - Terminal tabs with icon, command name, and clickable close button (β)
- πͺ Auto-close on exit - Terminal windows close automatically when processes exit
- β‘ Zero dependencies - Lightweight and built for Neovim 0.11+
- Neovim >= 0.11.0
{
"jellydn/tiny-term.nvim",
opts = {
-- your configuration here
},
}use {
"jellydn/tiny-term.nvim",
config = function()
require("tiny-term").setup()
end,
}The plugin provides both Lua API and Vim commands for terminal management.
For zero-change compatibility with existing Snacks.terminal integrations, you can override Snacks.terminal with tiny-term:
{
"jellydn/tiny-term.nvim",
opts = {
override_snacks = true, -- Automatically override Snacks.terminal
},
}-- After both plugins are loaded
require("tiny-term").override_snacks()Snacks.terminal = require("tiny-term")All existing Snacks.terminal() calls will now use tiny-term without any code changes. This includes integrations from other plugins that use Snacks.terminal.
-- Toggle shell terminal (default)
require("tiny-term").toggle()
-- Toggle terminal running a command
require("tiny-term").toggle("lazygit")
-- Open a new terminal (always shows)
require("tiny-term").open("npm run dev")
-- Get existing terminal or create new one
local term, created = require("tiny-term").get("htop")
-- List all active terminals
local terminals = require("tiny-term").list()-- Toggle shell terminal
vim.keymap.set("n", "<C-/>", function()
require("tiny-term").toggle()
end, { desc = "Toggle terminal" })
-- Toggle terminal with count (e.g., 2<C-/> for terminal #2)
vim.keymap.set("n", "<C-_>", function()
local count = vim.v.count1
require("tiny-term").toggle(nil, { count = count })
end, { desc = "Toggle terminal with count" })| Command | Description |
|---|---|
:TinyTerm |
Toggle shell terminal |
:TinyTerm {cmd} |
Toggle terminal running {cmd} |
:TinyTermOpen {cmd} |
Open new terminal (always creates/shows) |
:TinyTermClose |
Close current terminal |
:TinyTermList |
List all active terminals |
require("tiny-term").setup({
-- Shell to use for terminals (default: vim.o.shell)
shell = vim.o.shell,
-- Window configuration
win = {
-- Position: "float", "bottom", "top", "left", "right"
-- Default: auto (cmd provided -> "float", no cmd -> "bottom")
-- Float window size (as fraction of editor)
width = 0.8,
height = 0.8,
-- Border style (nil uses 'winborder' option from Neovim 0.11)
border = nil,
-- Split size in rows/columns
split_size = 15,
-- Enable split stacking (tmux-like behavior)
stack = true,
-- Default keymaps for terminal windows (nil uses built-in defaults)
-- Override to customize or set to false to disable specific mappings
keys = nil,
},
-- Start in insert mode when terminal opens
start_insert = true,
-- Enter insert mode when toggling terminal visible
auto_insert = true,
-- Close window when terminal process exits
auto_close = true,
})- With command (
toggle("lazygit")) β Opens in float by default - Without command (
toggle(),toggle(nil)) β Opens in bottom split by default - Override with
opts.win.position:toggle("lazygit", { win = { position = "float" } })
Terminals are identified by a deterministic ID based on:
- Command (or shell if nil)
- Current working directory (cwd)
- Environment variables (env)
- Vim count (
vim.v.count1)
This allows toggling multiple terminals:
-- Terminal #1 (default)
require("tiny-term").toggle()
-- Terminal #2 (different count)
require("tiny-term").toggle(nil, { count = 2 })
-- Terminal for specific directory
require("tiny-term").toggle(nil, { cwd = "/path/to/project" })
-- Terminal with specific environment
require("tiny-term").toggle("npm run dev", { env = { NODE_ENV = "development" } })Configure the plugin with user options.
require("tiny-term").setup({
shell = "/bin/bash",
win = { position = "bottom" },
})Toggle terminal visibility. Shows if hidden, hides if visible.
-- Returns: Terminal object or nil
local term = require("tiny-term").toggle("lazygit")Open a new terminal (always creates/shows window).
-- Returns: Terminal object
local term = require("tiny-term").open("htop")Get existing terminal or create new one.
-- Returns: Terminal object, created boolean
local term, created = require("tiny-term").get("node", { create = true })List all active terminal objects.
-- Returns: Array of terminal objects
local terminals = require("tiny-term").list()Generate terminal ID for given command and options.
-- Returns: Terminal ID string
local id = require("tiny-term").tid("lazygit")Parse a shell command string into a table of arguments.
-- Returns: Parsed argument list
local args = require("tiny-term").parse('sh -c "echo hello"')
-- Result: {"sh", "-c", "echo hello"}Colorize the current buffer with ANSI color codes. Useful for viewing colorized command output.
# Usage example:
ls -la --color=always | nvim - -c "lua require('tiny-term').colorize()"Override Snacks.terminal with tiny-term for zero-change compatibility.
-- Returns: tiny-term module for chaining
require("tiny-term").override_snacks()Terminal objects returned by the API have the following methods:
| Method | Description |
|---|---|
term:show() |
Show terminal window (creates if needed) |
term:hide() |
Hide terminal window (keeps buffer/process) |
term:toggle() |
Toggle visibility based on current state |
term:close() |
Kill process and delete buffer |
term:is_floating() |
Returns true if terminal window is floating |
term:is_visible() |
Returns true if terminal has valid visible window |
term:buf_valid() |
Returns true if terminal buffer is still valid |
term:focus() |
Focus terminal window and enter insert mode |
- Buffer management: Uses
bufhidden = "hide"so terminal buffers persist when windows close - Window tracking: Windows are tracked separately from buffers; a terminal can exist without a window
- Split stacking: Multiple terminals at the same position share one split with VSCode-style winbar tabs featuring clickable close buttons
- Keymaps: Configurable terminal keymaps including double-esc detection using
vim.uv.new_timer() - Neovim 0.11 features: Leverages
'winborder', improvednvim_open_win(), terminal reflow, andhl-StatusLineTerm - Cross-platform: Uses configured shell (
vim.o.shell) for running commands
Huynh Duc Dung
- Website: https://productsway.com/
- Twitter: @jellydn
- GitHub: @jellydn
If this plugin has been helpful, please give it a βοΈ.
MIT
