How to Implement a Readline?

Maybe you have heard about vim, wondering how vim can run in termial which is not same with other stdin/stdout programs. It can capture any keys you press immediately, and control the terminal to display it in fullscreen.

All right, this article would not teach you how to writing a vim, that’s very hard. Let’s try something easier, aka writing a readline. Everybody knew GNU-readline, which is a very popular line-editing library. We can write a simple line-editing program in golang as follows:

package main

import (
	"bufio"
	"os"
)

func main() {
	buf := bufio.NewReader(os.Stdin)
	for {
		line, err := buf.ReadString('\n')
		if err != nil {
			break
		}
		print(line)
	}
}

Now we can try to enter some letters, press <Enter>, we can see that once you press <Enter>, the screen will print the letters you just type in a new line.

Figure 1

As we know, the terminal will buffer all the data(usually it will be limited in 1024 bytes) in memory, once we press <Enter>, it will flush those data to stdin of the program. So the first line is the terminal buffer, and the second line is what the program read. When we are in the terminal buffer, we can delete or insert characters.


OK, now we are trying something interesting, type bye<Left>~<Enter>, let’s see what happens.

Figure 2

In termial buffer, we can see that ^[[D represent the <Left> (instead of move the cursor back). Now we have two questions.

  1. Why the output is by~ ?
  2. Why termial represent <Left> as ^[[D ?

The first question is obviously, we know that this program print exactly what we type, so after we type bye<Left>, the cursor is under the character e, so after we type ~, the e character will be overwritten.

To talk about ^[[D, we must talk about ANSI escape code which can enable us to control output options. Usually it starts with <Esc> (maybe that why it called escape code). The sequence that start with <Esc>[ is called CSI. It can be found that <Left> is belong to CSI codes.

Just for mention, terminal represent <Esc> as ^[ (I don’t know why, maybe it has a story -_-). When the program read from stdin, it still is 0x1b.

Code Name
CSI n A CUU – Cursor Up
CSI n B CUD – Cursor Down
CSI n C CUF – Cursor Forward
CSI n D CUB – Cursor Back

n is optional that control n(default 1) cell to move


To be continue

Comments