The Vim built-in terminal is a fully functional terminal emulator that lives inside your Vim buffer, allowing you to run shell commands and see their output without ever switching windows.
Here’s Vim’s terminal in action, running a git status command and then editing a file directly from the output:
:terminal git status
This opens a new buffer that looks and acts like your regular terminal. You can type commands, press Enter, and see the output. Once the command finishes, you can navigate the output buffer with Vim’s normal motion keys.
Now, let’s say git status shows a file that needs editing. You can simply move your cursor over the filename and type vi (that’s v, i, space) to open that file in a new split window, all within Vim:
:split
:terminal git status
" Move cursor to filename, then type 'vi '
This demonstrates the seamless integration. You’re not just seeing output; you’re interacting with the shell’s results and using Vim’s editing capabilities on them.
The Problem It Solves: Context Switching Overhead
The primary problem the built-in terminal solves is the cognitive overhead of context switching. Imagine you’re coding, you need to compile, run a test, or check your Git status. Traditionally, this means:
- Saving your current file (if you haven’t already).
- Switching to a separate terminal window (e.g.,
Ctrl+Alt+T,Cmd+T). - Typing the command.
- Observing the output.
- Switching back to Vim (
Alt+Tab,Cmd+Tab). - Potentially reloading your file if a build process modified it.
Each switch incurs a small mental cost. Over a day of heavy development, these costs add up, breaking your flow and making you less productive. Vim’s terminal eliminates most of these switches. You can compile, run tests, manage Git, or even SSH into another machine, all without leaving your editor.
How It Works Internally: PTYs and Buffers
Under the hood, Vim’s terminal uses a Pseudo-Terminal (PTY). A PTY is a pair of devices that simulate a real terminal. When you open :terminal, Vim essentially:
- Creates a PTY pair: a master side and a slave side.
- Spawns a shell process (e.g.,
bash,zsh) and connects its standard input, output, and error to the slave side of the PTY. - Connects Vim’s internal buffer to the master side of the PTY.
This means anything written to the slave side by the shell appears in Vim’s buffer, and anything you type in the Vim buffer is sent to the master side, which the shell on the slave side reads as its input. Vim then renders this as terminal output.
The key is that Vim treats the terminal buffer like any other buffer. You can navigate it, search it (/), copy from it (y), paste into it (p), and even execute commands from it (like the vi example).
Controlling the Terminal: The terminal Command and Options
The primary command is :terminal. You can run it directly or with a command:
:terminal: Opens an empty terminal.:terminal ls: Runslsand shows output.:terminal split: Opens a terminal in a horizontal split.:terminal vsplit: Opens a terminal in a vertical split.
You can also control terminal behavior with options:
set termwinsize=80x24: Sets the initial size of the terminal window.set ttyfast: Optimizes terminal responsiveness, especially for fast-scrolling output.set lazyredraw: Can be useful for very slow terminals, but generally not recommended for interactive use.
When you are inside a terminal buffer, Vim’s normal mode commands are generally disabled. You’re in "terminal mode." To interact with the terminal as a Vim buffer (e.g., to copy text), you need to exit terminal mode. This is done by pressing Ctrl-\ Ctrl-n. Pressing i or a will re-enter terminal mode, allowing you to type commands.
The Ctrl-\ Ctrl-n sequence is your escape hatch. It tells Vim, "I’m done typing commands into the terminal for now; treat this buffer like a regular Vim buffer." From here, you can use all your familiar Vim navigation and editing commands.
The autocmd mechanism is incredibly powerful for integrating terminal workflows. For instance, you can automatically restart a build command if you save a file:
augroup AutoBuild
autocmd!
autocmd BufWritePost *.c,*.cpp,*.go call TermSendKeys("make\r")
augroup END
function! TermSendKeys(keys)
" Find the terminal buffer and send keys to it
let tbuf = bufnr('%')
if getbufvar(tbuf, '&buftype') == 'terminal'
call term_sendkeys(tbuf, a:keys, 1000) " 1000ms timeout
else
echo "Not in a terminal buffer."
endif
endfunction
This autocmd listens for BufWritePost events on C/C++/Go files. When a file is saved, it calls TermSendKeys to send the make\r (make and carriage return) command to the currently active terminal buffer. This means you save your code, and the build process kicks off automatically in the background, all within Vim.
The most surprising thing is how seamlessly Vim handles terminal resizing. When you resize your Vim window (e.g., by dragging the window edge or using :resize), Vim doesn’t just display the existing output differently; it actually communicates the new dimensions to the running process within the PTY. The shell and any running programs (like top or htop) will receive a SIGWINCH (window size change) signal and adjust their output accordingly, just as if you had resized a standalone terminal emulator. This means that interactive programs like man pages or htop will correctly reflow their content, making them fully usable within Vim.
The next step is exploring how to manage multiple terminal sessions and integrate them with your project workflows using plugins.