Skip to content

jellydn/tiny-term.nvim

Repository files navigation

tiny-term.nvim

tiny-term.nvim logo

License Stars Issues

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.

Demo

✨ Features

  • πŸͺŸ 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+

⚑️ Requirements

  • Neovim >= 0.11.0

πŸ“¦ Installation

{
  "jellydn/tiny-term.nvim",
  opts = {
    -- your configuration here
  },
}
use {
  "jellydn/tiny-term.nvim",
  config = function()
    require("tiny-term").setup()
  end,
}

πŸš€ Usage

The plugin provides both Lua API and Vim commands for terminal management.

Snacks.terminal Override

For zero-change compatibility with existing Snacks.terminal integrations, you can override Snacks.terminal with tiny-term:

Option 1: Auto-override in setup

{
  "jellydn/tiny-term.nvim",
  opts = {
    override_snacks = true,  -- Automatically override Snacks.terminal
  },
}

Option 2: Manual override

-- After both plugins are loaded
require("tiny-term").override_snacks()

Option 3: Direct assignment

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.

Basic Usage

-- 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()

Recommended Keymaps

-- 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" })

⌨️ Commands

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

βš™οΈ Configuration

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,
})

Window Position Behavior

  • 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" } })

Terminal IDs

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" } })

πŸ”§ API Reference

Module Functions

setup(opts?)

Configure the plugin with user options.

require("tiny-term").setup({
  shell = "/bin/bash",
  win = { position = "bottom" },
})

toggle(cmd?, opts?)

Toggle terminal visibility. Shows if hidden, hides if visible.

-- Returns: Terminal object or nil
local term = require("tiny-term").toggle("lazygit")

open(cmd?, opts?)

Open a new terminal (always creates/shows window).

-- Returns: Terminal object
local term = require("tiny-term").open("htop")

get(cmd?, opts?)

Get existing terminal or create new one.

-- Returns: Terminal object, created boolean
local term, created = require("tiny-term").get("node", { create = true })

list()

List all active terminal objects.

-- Returns: Array of terminal objects
local terminals = require("tiny-term").list()

tid(cmd?, opts?)

Generate terminal ID for given command and options.

-- Returns: Terminal ID string
local id = require("tiny-term").tid("lazygit")

parse(cmd)

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()

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()

Override Snacks.terminal with tiny-term for zero-change compatibility.

-- Returns: tiny-term module for chaining
require("tiny-term").override_snacks()

Terminal Object Methods

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

πŸ”§ How It Works

  • 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', improved nvim_open_win(), terminal reflow, and hl-StatusLineTerm
  • Cross-platform: Uses configured shell (vim.o.shell) for running commands

πŸ‘€ Author

Huynh Duc Dung

Show Your Support

If this plugin has been helpful, please give it a ⭐️.

Ko-fi PayPal Buy Me A Coffee

Star History

Star History Chart

πŸ“ License

MIT

About

A minimal, standalone Neovim 0.11+ terminal

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors