Writing my own shell: how to implement command history?

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

However, what confused me was how to deal with pressing the up arrow and down arrow to scroll through my command history.

  • I tried ncurses , but that replaces the whole screen, while the system shell seems to just keep writing to the terminal.

  • I tried using tcgetattr to turn off canonical mode, but while this allows me to press the arrow keys as I type them, it also disables all left / right arrow key processing for text navigation, and backspace key and Ctrl-C ... Although I I could probably send the signal myself in response to Ctr-C, I have no idea how to make the terminal move the cursor back (except for the output "return" and re-record the beginning of the line). It also seems to give me different escape sequences for the keys, depending on whether I am running in Xcode a "dumb" terminal or in my Mac Terminal.app.

  • I looked at the sources for fish Shell and bash , but it looks like so much is happening that I cannot find the relevant parts.

How does a standard shell handle keystrokes? How do they handle cursor movement and perform backspace? How do they rewrite parts of a line without having to intercept the screen? Is there a standard somewhere that defines what the shell needs to do?

PS - I know how to record previous commands. This is actually getting keystrokes while typing them, and not after someone has pressed the return button that I can’t work.

+6
source share
1 answer

You must disable ICANON and ECHO and yourself interpret the escape sequences from the arrow keys.

You must save your own “actual” buffer of what is on the screen and where the cursor is. You also need the “desired” buffer of what you want on the screen and where you want to use the cursor. These buffers do not cover the entire screen, just the lines containing your prompt and user input (which you echoed manually because you disabled ECHO ). Since you printed everything on these lines, you know their contents.

As soon as you wait for the next input byte, you refresh the screen to match the required buffer. When you were on a 300 (or even 9600) baud connection, you took great care to make this update as efficient as possible by looking for the optimal sequence of printed bytes and terminal control sequences to convert the actual buffer to the desired buffer. These days, it’s far less important to be optimal.

These buffers will span lines if input files are wrapped, so you need to know and track the width of the terminal (using TIOCGWINSZ and SIGWINCH ). You can stick to one line with horizontal scrolling instead of line wrapping, but you still need to know the width of the terminal.

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

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

For bash, all this is done in the GNU readline library. Screen updates (called "re-rendering") are performed in display.c . Decoding the output is done in input.c .

However, if you need some sample code, you should probably take a look at linenoise , which is less than 2,000 lines. It is assumed that the VT100 terminal (and therefore xterm) is compatible.

See also "Is there a simple alternative to Readline?"

+1
source

All Articles