c++unixtermiosterminfo

Writing my own shell: How implement command history?


As an exercise in understanding my computer better, and as a tool, I'm writing my own shell in C++. Stephen Brennan's article on writing a simple shell was very helpful.

However, what has me flummoxed is how to handle pressing up-arrow and down-arrow to scroll through my command history.

How do the standard shells handle receiving keypresses? How do they handle moving the cursor and doing backspace? How do they re-write parts of a line without having to take over the screen? Is there a standard somewhere that defines what a shell needs to do?

PS - I know how to record the previous commands. It's the actually getting the keypresses while they are being typed, as opposed to after someone presses return, that I can't get to work.


Solution

  • You have to turn off ICANON and ECHO and interpret the escape sequences from the arrow keys yourself.

    You have to keep your own “actual” buffer of what's on the screen and where the cursor is. You also need a “desired” buffer of what you want on the screen and where you want the cursor. These buffers don't cover the whole screen, just the lines containing your prompt and the user's input (which you echoed manually because you turned off ECHO). Since you printed everything on these lines, you know their contents.

    Just before you wait for the next input byte, you update the screen to match the desired buffer. Back when you were on a 300 (or even 9600) baud connection you cared a lot about making this update as efficient as possible by looking for an optimal sequence of printable bytes and terminal-control sequences to transform the actual buffer into the desired buffer. These days it's a lot less important to be optimal.

    These buffers will span lines if the input wraps, so you need to know and track the terminal width (using TIOCGWINSZ and SIGWINCH). You could stick to a single line with horizontal scrolling instead of line wrapping, but you'd still need to know the terminal width.

    Theoretically you look up your terminal type (from $TERM) in the termcap or terminfo database. That tells you what escape sequences to expect when the user presses special keys (arrows, home, end, etc.), and what escape sequences to send to move the cursor, clear parts of the screen, insert or delete characters or lines, etc.

    These days it's pretty safe to assume everything's fairly xterm-compatible, especially for a hobby project.

    For bash, this is all done in the GNU readline library. Updating the screen (called “redisplay”) is done in display.c. Input escape decoding is done in input.c.

    However, if you want example code, you should probably take a look at linenoise, which is under 2000 lines. It assumes the terminal is VT100 (and therefore xterm) compatible.

    See also “Is there a simple alternative to Readline?”