The internal of go-prompt: How to control the rich terminal UI (Part I).

c-bata
6 min readAug 25, 2019

Hello, I’m a creator of go-prompt. Fortunately, go-prompt reaches 2300 stars on Github. And many awesome OSS adopts this library today. Thank you for all the users and contributors. By using go-prompt, you can write powerful interactive prompts, like kube-prompt.

https://github.com/c-bata/kube-prompt

As the number of Github stars of the terminal UI projects implies, The rich terminal UI applications are very attractive for most software developers. On the other hand, I guess most software developers don’t know how does it work because you don’t need to learn about terminal control for your job.

In this article, I describe how to develop a rich Terminal UI application in Go (the reason why I use Go is its portability). Don’t worry even if you are not Gopher. You can easily apply it to your preferred language. This article is a part I of the series which consists of 3 articles.

  • Part I A brief history of terminals. What is VT-100 escape sequences? How do we get the window size of terminal emulators?
  • Part II What is raw-mode. How to emulate the buffer information of the Terminal. (still not published)
  • Part III Re-thinking the design of components of go-prompt for v1.0.0. (still not published)

A brief history of the terminal.

Before digging into the source code of terminal control, let’s learn about the history of the terminal. I introduce two terminal machines here. Both of these were widely used in the past which I wasn’t still born.

Image refers from wikipedia.org

In the 1960s-1970s, ASR-33 produced by Teletype Corp was widely used (because of its low price and ASCII-compatibility). Then it became that these kinds of machines are called teletype(tty) terminal.

As you can see, this device embeds typewriter. Although this device could not express rich visual effects, it requires operations for breaking lines. But this is a dumb terminal which is just an output device that accepts text from minicomputers. So minicomputers need to send following operations to control this device.

  • Carriage Return(CR): Reset the printhead to the beginning of a line.
  • Line Feed (LF): Turned the wheel to move the paper to change the line.

You know, these are the root of CRLF breaking line characters.

Image refers from wikipedia.org

In the late 1970s, VT100 is produced by DEC. This machine has a monochrome display. We couldn’t still change the color, but it enables to express the rich visual effects like blinking, erasing texts and make the texts to bold or italic.

A lot of control sequences are defined for specific operations. These are called VT100 escape sequences. As the name implies, all control sequences start from \x1b which corresponds Escape on the ASCII code table. Most of the terminal emulators on UNIX OS adopt it to control its visual effects today¹.

VT100 escape sequences

Change the terminal color

In this section, let’s learn about VT100 escape sequences. First I describe a simple example that changes the color of texts and background. The source code is below.

The source code is quite simple. Please focus on line 6, it’s a VT100 escape sequence to change the color. \x1b[4;30;46m consists of 3 parts.

  • \x1b[ : control sequence introducer
  • 4;30;46 : parameters which separated by a semi-colon. 4 means underline, 30 means set foreground color Black and 46 means set background color Cyan.
  • m : a final character (which is always one character).

After printing Hello World, the code prints\x1b[0m which contains 0 which is a parameter to clear display attributes. By the way, VT100 couldn’t express the color because it embeds a monochrome display. I don’t know the details why there are control sequences for changing colors, but VT241 terminal which is the high-end model embeds color graphics display. So I guess the sequences to change the colors are added from this model. See the VT families for more details.

Next, let’s write code that displays progress bar. It needs to erase the terminal and move cursor positions. So it’s a little bit complicated than the previous example.

If you want to know more escape sequences, I recommended you to check this page or this page.

How to get a window size of the Terminal emulator.

We can change the window sizes because we use terminal emulators, not terminal machines. In this section, let’s learn about how to get the size of terminal emulators. To get the window size, you need to call ioctl(2) with TIOCGWINSZ like following².

type winsize struct {
Row uint16
Col uint16
X uint16
Y uint16
}

func getWinSize(fd int) (row, col uint16, err error) {
var ws *winsize
retCode, _, errno := syscall.Syscall(
syscall.SYS_IOCTL, uintptr(fd),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)))
if int(retCode) == -1 {
panic(errno)
}
return ws.Row, ws.Col, nil
}

But the viewpoint of portability, it’s better to call unix.IoctlGetWinsize .

Spread the progress bar with the window column size.

Not only learn about how to get the window size but also you need to know how to receive the events which notify the window size change.

This program has a problem that it doesn’t follow the changes of window size.

You can receive notification from UNIX OS signal. You just handle SIGWINCH os signals like following.

Follow the changes of window size

By calling ioctl(2) with TIOCGWINSZ when you receive SIGWINCH , you can get the window size. You can control the terminal UI from this informations.

However, it is difficult to erase the screen properly. In fact, the output will collapse if you make the terminal window smaller in this code. The most simple method is erasing the entire screen every time. Please try it if you want to learn more.

What you’ll learn next?

If you understand this article, you can develop rich terminal UI applications. In the next article, I describe how to handle user inputs. If you learn the next article, you can get all the basics to develop go-prompt.

Footnotes

[1]: Some of the terminals for Windows don’t adopt VT100 escape sequences. I don’t know the details but it seems needs to learn about Win32 Console API.

[2]: @Linda_pp tells me that it’s also able to use \x1b[999C\x1b[999B\x1b[6n which moves cursor position on the bottom of right and report the cursor position (tweet link). @ttdoda developer of TeraTerm tweets that it might be over 1000 columns today. So resize command which is installed with xterm now uses 9999 to move the cursor (tweet link). And @Dubhead tells me that we can also use \x1b[18t or \x1b[19t (tweet link). Thank you!

--

--

c-bata

Creator of go-prompt and kube-prompt. Optuna core-dev. GitHub: c-bata