[Edit: the below is something I wrote when I was trying to figure out what I should implement in my fish shell. Then I spent about eight hours implementing it, and when I was done, I immediately found that there were things about it that I didn't like the way they "felt", so I changed them and edited the article to match. But experience shows that if there were already things that I didn't like before I tried them, there will probably be more things that I discover I don't like as I use them. So maybe it's best to consider this article less like a design guide and more like an exploration of the design space.]
Since I’ve started playing with the fish shell, I’ve started to mess around with my prompt so I can maintain the only feature I actually cared about from my bash configs, which is that it showed git status in the prompt. Because I’m vulnerable to nerd sniping, this made me start to think about what information I definitively wanted in my prompt and where (since fish offers a "right prompt" as well as the typical left one).
I found a bunch of examples online where people had customized their prompts to whatever they found appealing for whatever reason, but relatively little about what brought them to that design in the first place. What things did you put in what order in what locations and why? What things did you decide that you didn’t need and how does that affect your workflow?
This is a brief post about what my decisions were and why.
A prompt should balance three concerns:
- Display important information.
- Take up as little space as possible.
- Be aesthetically appealing.
These concerns are in tension, because as you display more information, you take up more space, and as you condense things into a smaller space, you sacrifice aesthetics. I cared less about aesthetics than about space, so I decided I would try to keep my prompt to one line if possible, even if that meant an uglier prompt.
Which information is important? On most machines I use, the "default" prompt includes
- my username
- current hostname
- current path
- some kind of symbol, traditionally "$"
Sometimes tools like virtualenv will add a prefix to indicate what environment you’re in. I wanted to subsume that too.
I knew I wanted to add
- git status when it makes sense — what branch, whether the working directory is clean, and if we’re in the middle of a some operation like merge or rebase. I do most of my git stuff from magit, but sometimes I do one-off commands from the shell.
- a "warning" that would indicate if the last command took too long (inspired by fish’s built-in $CMD_DURATION variable)
- a "warning" that would indicate if the last command failed
I decided that in order to maximize space, I was going to remove my username from the prompt — I usually only have one account on any given machine, and I use sudo when I need to execute commands as other users. I also didn’t need a symbol if there was some other clear way to distinguish where my prompt began (for example, if it were a different color, or if some other part of the prompt were distinctive).
Which things would go on the left, and which on the right? As far as I can tell, there are two factors that go into this decision:
- Left prompts are more visible, since you look at what you type and your typing begins just after the prompt. Because of this, it makes sense to put things in the right prompt that you need but only occasionally.
- Right prompts go away when your cursor gets to them, which frees up more space at the cost of dropping information. Because of this, it makes sense to put things in the left prompt that you may want to refer back to in a few lines.
I noticed that commands that change the git branch are usually pretty short, so I could put the git branch on the right and if I didn’t see it, I could assume it hadn’t changed.
On the other hand, if I were in the middle of a merge or rebase, I wanted that to be obvious, since (unlike most git operations) cleaning up after a mistake during an interrupted merge or rebase can be a pain. I thought the "command failed" warning could be condensed to a single character, and I thought I’d want to know every time a command failed, so that would go on the left too. And because I wanted last command status on the left, I should put $CMD_DURATION on the left too for consistency.
That left the hostname and path. My normal workflow involves opening a bunch of tabs, one for each "location", and changing rarely. As a result, during the normal course of my day, I usually know "where I am", and in modern shells, I can just glance at the title bar if I forget where a tab is. But both hostname and path are super useful to refer back to during a transcript or especially when something goes wrong. For this reason I thought I would experiment with removing hostname and path from my prompt entirely but emphasizing changes to those values so that they would show up more obviously in a transcript. fish doesn’t provide a hook for this, but it’s easy enough to simulate by tracking state in a global variable and checking if it changed. I wasn’t quite ready to get rid of it entirely, but for now, the cwd is in the right prompt. We’ll see how that goes.
It took me a while to discover that fish ships with extensive support for putting git in your prompt (with the builtin __fish_git_prompt). It’s not quite as flexible as you might want, but it does have a bunch of sample code that might be useful if you want some of its power.