1. The Development History of Terminal
Terminal is the input and output equipment of computer system. For historical reasons, the concept of terminal is very confusing.
The development of terminal has gone through three stages: character terminal, graphics terminal and network terminal.
1.1 Teletypewriter TTY(TeleTYpe)
In the early days, because computers were very expensive, dozens of users shared a single host.
In order to satisfy the simultaneous use of multiple users, a device called teletypewriter, TTY(TeleTYpe), was initially used.
Connecting with the central computer through dedicated cables,
Teletypewriter transmits code signal to host computer through keyboard, and receives output of host program and prints it on paper tape. The disadvantage of teletypewriter is waste of paper. TTY device is the ancestor of Console.
1.2 VT100
In the late 1970s, VT100 was produced by DEC. The machine has a monochrome display screen.
We still can't change color, but it can express rich visual effects, such as flickering, deleting text, and making text bold or italic.
Many control sequences are defined for specific operations.
VT100 is an old definition of terminal, which is compatible with almost all the terminals appearing later. VT100 cannot express color because it is embedded in monochrome displays.
VT100 control code is used to expand the display code in the terminal.
For example, if any coordinates on the terminal display characters in different colors. VT100 control code is sometimes called ANSI Escape Sequence.
If you are interested in continuing to understand the history of VT, please visit vt100.net
VT100 Control Code ANSI Escape Sequence
As the name implies, all control sequences begin to escape from x1b to ASCII code table. Today most personal computer Telnet clients provide the most common terminal (VT100) simulation.
VT100 cannot express color because it has a monochrome display embedded in it. But I don't know why the VT100 control code ANSI Escape Sequence has the details of the control sequence that changes color.
But VT 241 terminal is a high-end model embedded color graphics display.
Let's look at the VT100 control code. All the controllers are_or e headers (that is, ASCII codes of ESC) which are output by character statements.
You can use echo command on the command line or printf in C program to output control characters of VT100.
1.2.1 VT100 Control Code
\033[0m // Close all properties \033[1m // Set to highlight \033[4m // Underline \033[5m // Twinkle \033[7m // Anti display \033[8m // Blanking \033[nA // Cursor up n lines \033[nB // Cursor down n lines \033[nC // Move the cursor right n rows \033[nD // Cursor left shift n lines \033[y;xH // Setting cursor position \033[2J // Clean screen \033[K // Clear content from cursor to end of line \033[s // Save the cursor position \033[u // restore cursor position \033[?25l // hide cursor \033[?25h // Display cursor
1.2.2
30:Black 31:Red 32:Green 33:Yellow 34:Blue 35:Purple 36:Blue 37:White
1.2.3 3[40m-3[47m] Sets Background Color
40:Black 41:Red 42:Green 43:Yellow 44:Blue 45:Purple 46:Cyan 47:White
ANSI/VT100 Control Code Document
1.3 PTY(pseudoTTY) pseudo-terminal/network terminal
In some operating systems, including Unix, a pseudo terminal, pseudotty, or PTY is a pair of pseudo devices.
Among them, the subordinate of the device imitates the hardware text terminal device, in which the other masters provide the process control subordinate of the device terminal simulator.
The terminal emulator process must also process terminal control commands, for example, to adjust the screen size.
The widely used terminal simulation programs include xterm, GNOME terminal, Konsole and terminal.
Remote login handlers (such as ssh and telnet servers) play the same role, but communicate with remote users rather than local users.
Consider procedures such as expectations.
2. Go Language Terminal colorful-text
Examples of printing color text
package main import "fmt" func main() { fmt.Print("\x1b[4;30;46m")//Set the color style fmt.Print("Hello World")//Print text content fmt.Println("\x1b[0m")//Style terminator, clear before the display properties }
Operation effect
Source code parsing, please pay attention to line 4, this is the VT100 control code to change the color. x1b[4;30;46m consists of three parts.
- \ x1b [: Control Sequence Importer
- 4; 30; 46: The semicolon-separated parameter. 4 represents the underline, 30 sets the foreground color black, 46 sets the background color blue.
- m: The last character (always a character).
After printing Hello World, printx1b[0m contains 0 to indicate clearance of display properties.
Open Source Library fatih/color The principle is to use golang print
VT100 Control Code (ANSI Escape Sequence) Marks Text Content, Colorful Terminal Text
3. progress bar of Go language terminal
The principle of displaying progress bar code:
- Terminals need to be erased.
- Print progress bar.
- And move the cursor position
package main import ( "fmt" "strings" "time" ) func renderbar(count, total int) { barwidth := 30 done := int(float64(barwidth) * float64(count) / float64(total)) fmt.Printf("Progress: \x1b[33m%3d%%\x1b[0m ", count*100/total) fmt.Printf("[%s%s]", strings.Repeat("=", done), strings.Repeat("-", barwidth-done)) } func main() { total := 50 for i := 1; i <= total; i++ { //<ESC> means ASCII "escape" character, 0x1B fmt.Print("\x1b7") // Save the cursor position, save the cursor and Attrs < ESC > 7 fmt.Print("\x1b[2k") // Clear the content erase line of the current line < ESC > [2K] renderbar(i, total) time.Sleep(50 * time.Millisecond) fmt.Print("\x1b8") // Recovery cursor position recovery cursor and Atrs < ESC > 8 } fmt.Println() }
This part of the code flaw is that the value of barwidth is fixed, but in practice this variable is determined by the width of the following terminal.
4. Window Size of Terminal Simulator
We can change the window size because we use pty.
In this section, let's see how to get the size of the terminal emulator.
To get the window size, you need syscall.SYS_IOCTL to be called below TIOCGWINSZ.
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 for ease of use and simplicity, it's better to call unix.IoctlGetWinsize directly. Note that the GetWinsize API is not good for windows.
package main import ( "fmt" "strings" "syscall" "time" "golang.org/x/sys/unix" ) var wscol = 30 func init() { ws, err := unix.IoctlGetWinsize(syscall.Stdout, unix.TIOCGWINSZ) if err != nil { panic(err) } wscol = int(ws.Col) } func renderbar(count, total int) { barwidth := wscol - len("Progress: 100% []") done := int(float64(barwidth) * float64(count) / float64(total)) fmt.Printf("Progress: \x1b[33m%3d%%\x1b[0m ", count*100/total) fmt.Printf("[%s%s]", strings.Repeat("=", done), strings.Repeat("-", barwidth-done)) } func main() { total := 50 for i := 1; i <= total; i++ { fmt.Print("\x1b7") // save the cursor position fmt.Print("\x1b[2k") // erase the current line renderbar(i, total) time.Sleep(50 * time.Millisecond) fmt.Print("\x1b8") // restore the cursor position } fmt.Println() }
Not only do you need to know how to get the window size, but you also need to know how to receive events and notify event window size changes.
Take the macOS/unix system as an example.
You can receive notifications from UNIX OS. You only need to process SIGWINCH os signals, as shown below.
package main import ( "fmt" "os" "os/signal" "strings" "syscall" "time" "golang.org/x/sys/unix" ) var ( total = 50 count = 0 wscol = 20 ) func init() { err := updateWSCol() if err != nil { panic(err) } } func updateWSCol() error { ws, err := unix.IoctlGetWinsize(syscall.Stdout, unix.TIOCGWINSZ) if err != nil { return err } wscol = int(ws.Col) return nil } func renderbar() { fmt.Print("\x1b7") // Save the cursor position fmt.Print("\x1b[2k") // Clear the current line content defer fmt.Print("\x1b8") // restore cursor position barwidth := wscol - len("Progress: 100% []") done := int(float64(barwidth) * float64(count) / float64(total)) fmt.Printf("Progress: \x1b[33m%3d%%\x1b[0m ", count*100/total) fmt.Printf("[%s%s]", strings.Repeat("=", done), strings.Repeat("-", barwidth-done)) } func main() { // set signal handler sigwinch := make(chan os.Signal, 1) defer close(sigwinch) signal.Notify(sigwinch, syscall.SIGWINCH) go func() { for { if _, ok := <-sigwinch; !ok { return } _ = updateWSCol() renderbar() } }() for count = 1; count <= 50; count++ { renderbar() time.Sleep(time.Second) } fmt.Println() }
By calling ioctl and TIOCGWINSZ, when you receive SIGWINCH signal, you can get the size of the window. From this information, you can control the terminal UI.
But it's hard to erase the screen correctly.
In fact, if you make the terminal window smaller in this code, the output will crash. The simplest way is to erase the entire screen every time.
Concluding remarks
Thought Extension: You can develop your own rich text terminal UI app according to the ANSI/VT100 terminal control code document combined with the print function of python/bash/go/java/c/php and other languages.
Reference Documents