From XON/XOFF to Forward Incremental Search
XON/XOFF
In the olden days of computing, software flow control with control codes XON and XOFF was a necessary feature that dumb terminals needed to support. When a terminal received more data than it could display, there needed to be a way for the terminal to tell the remote host to pause sending more data. The control code 19 was chosen for this. The control code 17 was chosen to tell the remote host to resume transmission of data.
The control code 19 is called Device Control 3 (DC3) in the ASCII chart. It is also known as "transmit off" (XOFF). The control code 17 is called Device Control 1 (DC1) as well as "transmit on" (XON). Now how does a user of the terminal really send these control codes? Well, how do they send any control code? Using the ctrl key of course.
Let us take a step back and see how a user can send familiar control
codes on modern terminal emulators, like say, the terminal software
we find on a Unix or Linux desktop environment. While any sane
computer user would just type the tab key to insert a tab
character, one could also type ctrl+i to
insert a tab character. The character I
has code 73
(binary 1001001) and holding the ctrl key while typing it
results in a control code made by taking 73 (binary 1001001),
keeping its five least significant bits, and discarding the rest to
get the control code 9 (binary 1001) which is the code of the tab
character. In other words, we get the control code by performing a
bitwise AND operation on the code of the character being modified
with binary code of 31 (binary 0011111).
In case you are wondering, if we get the same result if we choose
the binary code of the lowercase i
to perform the
aforementioned operation, the answer is, yes. While the code of
uppercase I
is 73 (binary 1001001), that of
lowercase i
is 105 (binary 1101001). The right-most
five bits are same for both. Thus when we preserve the five least
significant bits and discard the rest, we get the control code 9
(binary 1001) in both cases. This is a neat result due to the fact
that the five least significant bits of the code of a lowercase
character is exactly the same as that of the corresponding uppercase
character. They differ only in their sixth least significant bit.
That bit is on for the lowercase character but off for the
corresponding uppercase character. This is just an interesting
observation as far as this post is concerned. The very early
keyboards did not have lowercase letters. They only had uppercase
letters and when the ctrl key is pressed together with a
letter on such keyboards, the 7th bit of the character code was
flipped to get the control code.
The bitwise operation mentioned earlier explains why typing ctrl+h sends a backspace, typing ctrl+j sends a newline, and typing ctrl+g plays a bell. In modern terminal emulators these days, the bell often manifests in the form of an audible beep or a visual flash. Here is a table that summarises these control codes and a few more:
Key | Modified Character | Control Character | ||||
---|---|---|---|---|---|---|
Binary | Decimal | Character | Binary | Decimal | Character | |
ctrl+@ | 1000000 | 64 | @ | 00000 | 0 | Null |
ctrl+g | 1000111 | 71 | G | 00111 | 7 | Bell |
ctrl+h | 1001000 | 72 | H | 01000 | 8 | Backspace |
ctrl+i | 1001001 | 73 | I | 01001 | 9 | Horizontal Tab |
ctrl+j | 1001010 | 74 | I | 01010 | 10 | Line Feed |
ctrl+m | 1001101 | 77 | M | 01101 | 13 | Carriage Return |
ctrl+[ | 1011011 | 91 | [ | 11011 | 27 | Escape |
The last row in the table above explains why we can also type ctrl+[ in Vim to escape from insert mode to normal mode. This is, in fact, one of the convenient ways for touch-typists to return to normal mode in Vim instead of clumsily stretching the left hand fingers out to reach the esc key which is usually poorly located at the corner of most keyboards.
There is a bit of oversimplication in the description above. Throughout the history of computing, different systems have used slightly different methods to compute the resulting control code when the ctrl modifier key is held. Toggling the 7th least significant bit was an early method. Turning off both the 6th and 7th least significant bits is another method. Subtracting 64 from the character code is yet another method. These are implementation details and these various implementation methods lead to the same results for the examples in the table above. Then there are some special rules too. For example, many terminals implement a special rule to make ctrl+space behave the same as ctrl+@ thus producing the null character. Further ctrl+? produces the delete character in some terminals. These special rules are summarised in the table below.
Key | Modified Character | Resulting Character | ||||
---|---|---|---|---|---|---|
Binary | Decimal | Character | Binary | Decimal | Character | |
ctrl+space | 0100000 | 32 | Space | 0 | 0 | Null |
ctrl+? | 0111111 | 63 | ? | 1111111 | 127 | Delete |
For the purpose of this blog post, we don't need to worry about these special rules. Let us get back to the control codes 19 and 17 that represent XOFF and XON. Here is how the table looks for them:
Key | Modified Character | Resulting Character | ||||
---|---|---|---|---|---|---|
Binary | Decimal | Character | Binary | Decimal | Character | |
ctrl+q | 1010001 | 81 | Q | 10001 | 17 | DC1 (XON) |
ctrl+s | 1010011 | 83 | S | 10011 | 19 | DC3 (XOFF) |
We see that a user can type ctrl+s to send the
control code XOFF and then ctrl+q to send the
control code XON. Although we do not use dumb terminals anymore,
these conventions have survived in various forms in our modern
terminal emulators. To see a glimpse of it, launch the terminal
that comes with a modern operating system, then run ping
localhost
and while this command is printing its output,
type ctrl+s. The terminal should pause
printing the output of the command. Then
type ctrl+q and the terminal should resume
printing the output.
Incremental Search in Bash/Zsh
Let us now take a look at the shells that run within the terminals. Both Bash and Zsh are very popular these days. Both these shells have excellent support for performing incremental searches through the input history. To see a quick demonstration of this, open a new terminal that runs either Bash or Zsh and run these commands:
echo foo
echo bar
cal
uname
echo baz
Now type ctrl+r followed by echo
.
This should invoke the reverse search feature of the shell and
automatically complete the partial command echo
to echo baz
. Type ctrl+r again
to move back in the input history and autocomplete the command
to echo bar
. We can type enter anytime we
use the reverse search feature to execute the automatically
completed command. Typing ctrl+r yet another
time should bring the echo foo
command at the shell
prompt.
What if we went too far back in the input history and now want to go forward? There is a good news and there is some bad news. The good news is that both Bash and Zsh support forward search using the ctrl+s key sequence. The bad news is that this key sequence may not reach the shell. Most terminal emulators consume this key sequence and interpret it as the control code XOFF.
The Conflict
In the previous two sections, we have seen that the ctrl+s key sequence is used to send the control code XOFF. But the same key sequence is also used for forward incremental search in Bash and Zsh. Since the terminal consumes this key sequence and interprets it as the control code XOFF, the shell never sees the key sequence. As a result, the forward incremental search functionality does not work when we type this key sequence in the shell.
Bash offers the incremental search facility via a wonderful piece of library known as the the GNU Readline Library. ZSH offers this facility with its own Zsh Line Editor (ZLE). So other tools that rely on these libraries to offer line editing and history capability are also affected by this conflict. For example, many builds of Python offer line editing and history capability using GNU Readline, so while ctrl+r works fine to perform reverse search in the Python interpreter, it is very likely that ctrl+s does not work to perform forward search.
Reclaim Forward Incremental Search
We can forfeit the usage of control codes XON/XOFF to reclaim forward incremental search. Here is the command to disable XON/XOFF output control in the terminal:
stty -ixon
After running the command, ctrl+s is no longer consumed by the terminal to pause the output. To confirm that forward incremental search works now, first run the above command, and then run the following commands:
echo foo
echo bar
cal
uname
echo baz
Now type ctrl+r followed by echo
and the input line should be automatically completed to echo
baz
. Type ctrl+r again and echo
bar
should appear at the shell prompt.
Type ctrl+r one more time to bring the
command echo foo
at the shell prompt.
Now type ctrl+s once and the search facility
should switch itself to forward incremental search. The search
prompt should change to show this. For example, in Bash the search
prompt changes from reverse-i-search:
to i-search:
to indicate this. In Zsh, the search
prompt changes from bck-i-search:
to fwd-i-search:
.
Now type ctrl+s again and the input line
should be automatically completed to echo bar
.
Type ctrl+s again to bring back echo
baz
at the shell prompt. This is the forward incremental
search in action.
Conclusion
I believe the forward incremental search facility offered in shells and other tools with line editing and history capabilities is a very useful feature that can make navigating the input history very convenient. However due to the default setting of most terminals, this rather splendid feature remains unusable.
I believe heavy terminal users should add the command stty
-ixon
to their ~/.bash_profile
or ~/.zshrc
, so that the ctrl+s
key sequence can be used for forward incremental search. Forfeit
XON/XOFF to reclaim forward incremental search!