To understand why Control+i inserts a tab in your terminal you need to understand ASCII, and to understand ASCII you need know a bit about its history and the world it was developed in. Please bear with me (or just go the table).
Teleprinters evolved from the telegraph. Connect a printer and keyboard to a telegraph and you’ve got a teleprinter. Early versions were called “printing telegraphs”.
Teleprinters communicated using ITA2. For the most part this was just a standard to encode the alphabet, but there are a few control codes: WRU (“Who R U”) would cause the receiving teletype to send back its identification, BEL would ring a bell, and the familiar CR (Carriage Return) and LF (Line Feed).
This is all early 20th century stuff. There are no computers; it’s all mechanical working with punched tape. ITA2 (and codes like it) were mechanical efficient; common letters such as “e” and “t” required only a single hole to be punched.
These 5-bit codes could only encode 32 characters, which is not enough. The solution was to add the FIGS and LTRS codes, which would switch between “figures” and “letters” mode. “FIGS R W” would produce “42”. This worked, but typo’ing a FIGS or LTRS, or losing one in line noise, would result in gibberish. Not ideal.
Teleprinters were also used to connect to computers, rather than other teleprinters. ITA2 was designed for mechanical machines and awkward, so ASCII designed specifically for computer use and published in 1962. Teleprinters used with computers were called terminals (as in “end of a connection”, like “train terminal”).
It’s perhaps hard to imagine, but people really programmed computers using Teletypes. Here’s a video of a teleprinter in action, and here’s a cheesy (but interesting and cute) video which explains how they were used to program a PDP 11/10.
The Teletype model 33 was massively popular, and the brand name Teletype became synonymous with terminal. The abbreviated form of Teletype is TTY. Yes, like /dev/tty or /bin/stty on your modern Linux or macOS system.
A terminal would connect to a computer using a serial port (RS-232) which simply transferred characters. The best way to see a terminal is as a monitor with an integrated keyboard, rather than a computer on its own. A modern monitors connected with HDMI is told “draw this pixel in this colour”. In the 1960s the computer merely said “draw this character”.
Teleprinters and terminals connected with only a serial port sending characters, so they needed some way to communicate events such as “stop sending me data” or “end of transmission”. This is what control characters are for. The exact meaning of control characters has varied greatly over the years (which is why extensive termcap databases are required). ASCII is more than just a character set; it’s a way to communicate between a terminal and a computer.
An additional method to communicate which came along with visual terminals like the ADM-3A and VT100 is sending escape sequences. This is a list of characters starting with the ESC control character (0x1b) which have a special meaning. For example F1 is “<Esc>OP”, the left arrow is “<Esc>[OD”, “<Esc>[2C” is move the cursor 2 positions forward, “<Esc>[4m” underlines all subsequent text, etc.
All of this matters because modern terminals operate on the same fundamentals as those of the 60s. If you’re opening three windows with xterm or iTerm2 then you’re emulating three terminals connecting to a “mainframe”.
If you look at the ASCII table below then there are some interesting properties: in the 1st column you can see how the left two bits are always set to zero, and that the rest count to 31 (32 characters in total; it starts at 0). The 2nd column then repeats this pattern but with the 5th bit set to 1 (remember, read binary numbers from right-to-left, so that’s 5th from the right). The 3rd column repeats this pattern again with the 6th bit set, and the final column has both bits set.
The interesting part here is that the letters A-Z, as well as some punctuation, map directly to the control characters in the first column. All that’s needed is removing one bit, and that’s exactly what the Control key did: clear the 6th bit. Lowercase and uppercase letters align in the 3rd and 4th columns, and this is what the Shift key did: clear the 5th bit.
Pressing Control+a (lowercase) would mean sending !, which is not very useful. So most terminals interpret this as Control+A (uppercase), which sends SOH.
DEL is last is because all bits are set to 1. This is how you “deleted” a character in punch tapes: punch all the holes!
This is kind of neat and well designed, but it does have some effects, even for modern terminals:
The world has not completely stood still, and there are some improvements from the 1960s, but terminals are still fundamentally ASCII-based text interfaces, and programs running inside a terminal – like a shell or Vim – still have very limited facilities for modern key events. Non-terminal programs don’t have these problems as they’re not restricted to a 1960s text interface.