Call me old fashioned, but in an era of IDEs written in JavaScript reaching peak popularity among developers, I still use a terminal to get everything done. In contrast to IDEs, which attempt to own the main productivity experience and defer customization to plugins, being productive in the terminal hinges on hand-selecting your tools and knowing how to use them.
Here are some ways I stay productive in the terminal.
Many people use tmux to enhance the productivity of their terminal sessions. Tmux’s most basic commands involve splitting panes and creating new windows. These functions allow multiple shells to run simultaneously, which can make SSH or Linux console sessions on a headless server or embedded system much more productive. However, tmux’s subtler features enable even further gains in productivity. Among them:
Often, while running a text editor in a single-pane window, I need to
open a new shell in the same working directory. In vi-like editors (such
as vim and vis), run :!tmux sp -h
in normal mode to spawn a new pane
to the right of the current pane. If you know how to do this in Emacs
or another popular terminal editor, let me know, and I’ll include it here.
To make this convenient, add bind-key j joinp -h
to your tmux
configuration.
Switch to the pane you want to move and mark it with <prefix> m
. Switch
to the destination window and use <prefix> j
to relocate the marked
pane to the right of the current pane.
The reverse operation is also useful, and requires no special
configuration. Just use <prefix> !
to transform the current pane into
a new window.
Like vi, tmux employs a modal user interface, where the function of certain keys can change. I put off understanding it for years, but recently, in a fit of frustration, I sorted it out.
Aside from the “default” mode (which is akin to vi’s “normal” mode),
tmux has a “copy” mode. Don’t be mislead by the name: it is also used for
scrolling back through history and searching for text. Complicating this
picture, copy mode offers two “key tables” to choose from: copy-mode
,
the default, provides Emacs-style bindings; and copy-mode-vi
, for
vi-style bindings. I don’t understand the Emacs bias. Moreover, the
Emacs mode has no keybinding for searching through text, the function
I typically want when I enter “copy” mode!
A clearer name for this mode would be “review” or “buffer”, because it is the only way that one can look at previous output. In any case, why doesn’t Emacs mode have a keybinding for search? I suspect there are many users who don’t know that tmux can search through panes.
When I finally switched to copy-mode-vi
by adding set -g mode-keys vi
to my configuration, life got a lot better. If you use this configuration,
enter copy mode with <prefix> [
. Just like in vi, search for text with
/
.
Once you’re in copy mode, you can move the cursor per the usual
keybindings. Start a selection with the spacebar. Copy it with
return. Paste with <prefix> ]
.
I’ve been troubleshooting a quality of service (QoS) problem with my GL-AR300M router, which runs OpenWRT. I attempted to diagnose the issue through an interactive terminal session, but hadn’t thought about how I’d share the output with others who could help me. I could have used script(1) (“make transcript of terminal session”), but it works less well than one might hope, as it records control characters and escape sequences. We just want to share what we see.
Tmux to the rescue. It takes two steps:
<prefix> :capture-pane -S -
.<prefix> :save-buffer filename.txt
,
where filename.txt
is the name of the file you want to save. When
specifying a relative path, it is relative to $HOME
.Here’s a more streamlined version, to be placed in your configuration,
bound to <prefix> S
:
# quickly save pane to file
bind-key S command-prompt -p "Save pane to file:" "capture-pane -S - ; save-buffer %%"
When visual-bell
is enabled, tmux will highlight the window in the
status line when the \a
character is printed to the terminal. This
can be used to flag a window when tests fail.
I often use entr
to run tests whenever source files change. (I treat
entr
itself in a later section.) Sometimes test printouts are long,
so they compete with source code for screen space. The tests can be moved
to another window, but then one has to switch to that window to see the
test results. With visual-bell
, a test script like ./test || printf '\a'
will notify you of test failures.
I’ve experimented with a number of different monospace fonts over the years. While I prefer the look of Fira Mono, the virtue of Iosevka Term is its horizontal compactness. When I have to cram my 27-inch monitor (or 13-inch laptop display) full of tmux or text editor panes, I keep coming back to Iosevka.
Aesthetically, Iosevka is more consistent than Inconsolata while keeping some of the roundness. At larger sizes, it rewards one with subtle cusps and curves in k, v, x, and y. Its tall characters preserve legibility without using up precious horizontal space. I find it lacking in distracting glyphs.
A programmer’s text editor guides them through thick and thin. It has to be ready for anything. In 2018, I switched permanently to vis, having been a vim user since 2014.
Vis incorporates the best elements of vi and sam. Upon its first release, it claimed to be “80% of vim in 1% of the code”. For that reason alone, it caught my attention. It’s been easy to patch it to add new features, like a command to switch the layout between horizontal and vertical. I’ve had a good experience contributing these patches upstream.
Vis has kept my attention by its cleanness and consistency, manifested in the choice of Lua as both a configuration language and plugin API, and in the pithiness of its feature set.
I use the ctags and vis-go plugins, which ease navigating large or unfamiliar codebases. I also wrote a small command to format paragraphs:
vis:map(vis.modes.NORMAL, 'fp', function()
-- format paragraph
vis:feedkeys('{v}=<Escape>')
end)
When developing software, entr is useful for speeding up the edit-compile-run loop. It has only one job: run an arbitrary command when files change. When used in combination with tmux and a fast build system, it approximates the IDE experience.
While one can write many useful one-liners with entr, it becomes the most useful when executing a script. This allows you to edit the triggered action without having to kill the entr process. For example:
find . -name '*.go' | entr -c ./test
Where the script test
might contain
#!/bin/sh
go vet
go test -race -coverprofile=c.out || printf '\a'
go tool cover -html=c.out
The first interactive shells emerged in the 1960s, and have pretty much always been used for both automating repetitive tasks and interacting with a computer in real time. The challenge of staying productive in the terminal is the challenge of working in a historical user interface idiom.
The terminal has evolved much since the mid 20th century, of course, but has picked up a lot of baggage. We have resources that do not constrain us to the designs that have persisted, and constraints that challenge these designs. But it remains difficult to break with the old paradigm because of our tendency to commit the sunk cost fallacy; to value what exists over what is possible.
Thus, I do not want to be seen as a terminal chauvinist simply because I have found a set of tools that make life a bit easier. I delight in the irony of something like PowerShell, the object-oriented shell for Windows, written by a Unix person, who wanted to bring the power of Unix-style automation to the GUI-centric operating system. The advantage of PowerShell lies in its lack of serialization between command stages: data passes between commands in fully structured form. This might be the sort of thing Rich Hickey would advocate in order to keep data models, not code, at the center of programming.
Discuss this page by emailing my public inbox. Please note the etiquette guidelines.
© 2024 Karl Schultheisz