In this article, I'll look at a shortcoming of command-line interfaces—discoverability—and a few ways to overcome this problem.
I love command lines. My first command line was DOS 6.2, back in 1997. I learned the syntax for various commands and showed off how to list hidden files in a directory (attrib). I would carefully craft my commands one character at a time. When I made a mistake, I would proceed to retype the command from the beginning. One fine day someone showed me how to traverse the history using the up and down arrow keys and I was blown away.
Later when I was introduced to Linux, I was pleasantly surprised that up and down arrows retained their ability to traverse the history. I was still typing each character meticulously, but by now I knew how to touch type and I was doing exceedingly well with my 55 words per minute. Then someone showed me tab-completion and changed my life once again.
In GUI applications menus, tool tips and icons are used to advertise a feature for the user. Command lines lack that ability, but there are ways to overcome this problem. Before diving into solutions, I'll look at a couple of problematic CLI apps:
First we have our beloved MySQL REPL. I often find myself typing SELECT * FROM and then press Tab out of habit. MySQL asks whether I'd like to see all 871 possibilities. I most definitely don't have 871 tables in my database. If I said yes, it shows a bunch of SQL keywords, tables, functions, and so on.
Let's look at another example, the standard Python REPL. I start typing a command and press the Tab key out of habit. Lo and behold a Tab character is inserted, which is a problem considering that a Tab character has no business in a Python source code.
Now let's look at well-designed CLI programs and how they overcome some discoverability problems.
Bpython is a fancy replacement for the Python REPL. When I launch bpython and start typing, suggestions appear right away. I haven't triggered them via a special key combo, not even the famed Tab key.
When I press the Tab key out of habit, it completes the first suggestion from the list. This is a great example of bringing discoverability to CLI design.
The next aspect of bpython is the way it surfaces documentation for modules and functions. When I type in the name of a function, it presents the function signature and the doc string attached with the function. What an incredibly thoughtful design.
Context-aware completion: mycli
Mycli is a modern alternative to the default MySQL client. This tool does to MySQL what bpython does to the standard Python REPL. Mycli will auto-complete keywords, table names, columns, and functions as you type them.
The completion suggestions are context-sensitive. For example, after the SELECT * FROM, only tables from the current database are listed in the completion, rather than every possible keyword under the sun.
Fuzzy search and online Help: pgcli
If you're looking for a PostgreSQL version of mycli, check out pgcli. As with mycli, context-aware auto-completion is presented. The items in the menu are narrowed down using fuzzy search. Fuzzy search allows users to type sub-strings from different parts of the whole string to try and find the right match.
Both pgcli and mycli implement this feature in their CLI. Documentation for slash commands are presented as part of the completion menu.
In traditional Unix shells (Bash, zsh, etc.), there is a way to search your history. This search mode is triggered by Ctrl-R. This is an incredibly useful tool for recalling a command you ran last week that starts with, for example, ssh or docker. Once you know this feature, you'll find yourself using it often.
If this feature is so useful, why not do this search all the time? That's exactly what the fish shell does. As soon as you start typing a command, fish will start suggesting commands from history that are similar to the one you're typing. You can then press the right arrow key to accept that suggestion.
I've reviewed innovative ways to solve the discoverability problems, but there are command-line basics everyone should implement as part of the basic REPL functionality:
- Make sure the REPL has a history that can be recalled via the arrow keys. Make sure the history persists between sessions.
- Provide a way to edit the command in an editor. No matter how awesome your completions are, sometimes users just need an editor to craft that perfect command to drop all the tables in production.
- Use a pager to pipe the output. Don't make the user scroll through their terminal. Oh, and use sane defaults for your pager. (Add the option to handle color codes.)
- Provide a way to search the history either via the Ctrl-R interface or the fish-style auto-search.
In part 2, I'll look at specific libraries in Python that allow you to implement these techniques. In the meantime, check out some of these well-designed command-line applications:
- bpython or ptpython: Fancy REPL for Python with auto-completion support.
- http-prompt: An interactive HTTP client.
- mycli: A command-line interface for MySQL, MariaDB, and Percona with auto-completion and syntax highlighting.
- pgcli: An alternative to psql with auto-completion and syntax-highlighting.
- wharfee: A shell for managing Docker containers.