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

Masashi SHIBATA

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 the 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 of software developers. On the other hand, I guess most of 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. The both of these were widely used in the past which I wasn’t still born.

Image refers from wikipedia.org

In 1960s-1970s, ASR-33 produced by Teletype Corp was widely used (because of it’s 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 a rich visual effects, it requires operations for breaking lines. But this is a dumb terminal which is simply an output device that accepts text from minicomputers. So minicomputers needs to send following operations to control this device.

  • Carriage Return(CR): Reset the printhead to the beginning of 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 of 1970s, VT100 is produced by DEC. This machine has the 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 starts from \x1b which corresponds Escape on 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 which changes the color of texts and background. The source code is below.

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

  • \x1b[ : control sequence introducer
  • 4;30;46 : parameters which separated by 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 embed 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 embed 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 which displays progress bar. It needs to erase the terminal and move cursor positions. So it’s a little bit complicated than previous example.

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

How to get the window size of 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 notifies 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 receives 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 the 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 basics to develop go-prompt.

Footnotes

[1]: Some of 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!

Masashi SHIBATA

Written by

Creator of go-prompt and kube-prompt. github: c-bata

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade