Managing persistent terminals and command execution with Harpoon
Harpoon provides powerful terminal management capabilities, allowing you to maintain persistent terminal sessions and execute commands across multiple terminals. You can choose between native Neovim terminals or tmux integration.
local term = require("harpoon.term")-- Navigate to terminal 1term.gotoTerminal(1)-- Navigate to terminal 2term.gotoTerminal(2)
If the terminal doesn’t exist, Harpoon creates it automatically:
-- From term.lua:34-53local function find_terminal(args) if type(args) == "number" then args = { idx = args } end local term_handle = terminals[args.idx] if not term_handle or not vim.api.nvim_buf_is_valid(term_handle.buf_id) then local buf_id, term_id = create_terminal(args.create_with) if buf_id == nil then error("Failed to find and create terminal.") return end term_handle = { buf_id = buf_id, term_id = term_id, } terminals[args.idx] = term_handle end return term_handleend
Terminal buffers are created with bufhidden=hide to ensure they persist even when not visible.
Execute commands in specific terminals without switching to them:
local term = require("harpoon.term")-- Send a command to terminal 1term.sendCommand(1, "npm run dev")-- Send to terminal 2term.sendCommand(2, "cargo watch -x test")-- Format strings are supportedterm.sendCommand(1, "echo 'Hello %s'", "World")
Use enter_on_sendcmd configuration to automatically append a newline (Enter key) to executed commands.
local term = require("harpoon.term")-- Add commands to your configterm.add_cmd("npm run dev")term.add_cmd("cargo test")term.add_cmd("python manage.py runserver")
Commands are stored in your Harpoon configuration:
-- Execute command 1 in terminal 1term.sendCommand(1, 1)-- Execute command 2 in terminal 2term.sendCommand(2, 2)
From the implementation:
-- From term.lua:73-89function M.sendCommand(idx, cmd, ...) local term_handle = find_terminal(idx) if type(cmd) == "number" then cmd = harpoon.get_term_config().cmds[cmd] end if global_config.enter_on_sendcmd then cmd = cmd .. "\n" end if cmd then vim.api.nvim_chan_send(term_handle.term_id, string.format(cmd, ...)) endend
local tmux = require("harpoon.tmux")-- Navigate to tmux window 1tmux.gotoTerminal(1)-- Send command to tmux window 1tmux.sendCommand(1, "npm run build")-- Target a specific tmux panetmux.sendCommand("%1", "echo 'Hello from pane 1'")
How tmux integration works
Harpoon creates new tmux windows and tracks them by pane ID:
-- From tmux.lua:23-46local function create_terminal() local window_id -- Create a new tmux window and store the window id local out, ret, _ = utils.get_os_command_output({ "tmux", "new-window", "-P", "-F", "#{pane_id}", }, vim.loop.cwd()) if ret == 0 then window_id = out[1]:sub(2) end if window_id == nil then error("window_id is nil") return nil end return window_idend
Auto-closing tmux windows
Configure Harpoon to automatically close tmux windows when exiting Neovim:
local term = require("harpoon.term")-- Add a new commandterm.add_cmd("docker compose up")-- Remove command at index 2term.rm_cmd(2)-- Replace all commandsterm.set_cmd_list({ "npm run dev", "npm test", "npm run build",})-- Get number of saved commandslocal count = term.get_length()-- Clear all terminalsterm.clear_all()
-- Jump to terminals with leader + numberfor i = 1, 5 do vim.keymap.set("n", "<leader>" .. i, function() require("harpoon.term").gotoTerminal(i) end)end