32

Go进阶:如何开发多彩动感的终端UI应用

 4 years ago
source link: https://www.tuicool.com/articles/ZVNVBzF
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

女主宣言

之前小编有看过编程语句相关统计,echo、print之类的语句,使用最为频繁。但是直接输出的语句,总是那么的普普通通。所以今天小编来给大家分享一篇关于炫酷输出的文章。希望能对大家有所帮助。

PS:丰富的一线技术、多元化的表现形式,尽在“ 3 60云计算 ”,点关注哦!

1

终端(terminal)的发展历史

终端(Terminal 是计算机系统的输入输出设备,由于历史的原因终端这个概念非常混乱,终端的发展经历了字符终端、图形终端和网络终端三个阶段。

电传打字机的设备TTY(TeleTYpe)

在早期由于计算机非常昂贵,因此数十个用户共用一台主机,为了满足多用户同时使用,最初使用一种叫电传打字机的设备,简称TTY(TeleTYpe ,通过专用线缆与中央计算机相连,电传打字机通过键盘将电码信号发送给主机,同时接收主机程序的输出并打印在纸带上,缺点是非常浪费纸,TTY设备是现代控制台(Console 的鼻祖。

RRj2u2i.jpg!web

VT100

在20世纪70年代后期,VT100由DEC生产。本机具有单色显示屏。我们仍然无法改变颜色,但它能够表达丰富的视觉效果,如闪烁、删除文本,并使文本变为粗体或斜体。为特定操作定义了许多控制序列。

mMnyaaf.jpg!web

VT100是一个古老的终端定义,后面出现的终端几乎都兼容这种终端。VT100无法表达颜色,因为它嵌入了单色显示器。VT100控制码是用来在终端扩展显示的代码。比如果终端上任意坐标用不同的颜色显示字符。VT100控制码有时又称为ANSI Escape Sequence。如果感兴趣继续了解VT的发展历史请访问vt100.net

VT100控制码ANSI Escape Sequence 顾名思义,所有控制序列开始从\x1b 对应上ASCII码表。今天大多数个人计算机的Telnet用户端提供最普遍的终端(一般VT100)的模拟。VT100无法表达颜色,因为它嵌入了单色显示器。但是不知道为什么VT100控制码ANSI Escape Sequence有改变颜色的控制序列的细节,但VT241终端是高端模型嵌入彩色图形显示器。

让我们了解VT100控制码。所有的控制符是 \033 或 \e 打头(即 ESC 的 ASCII 码)用输出字符语句来输出。可以在命令行用 echo 命令,或者在 C 程序中用 printf 来输出 VT100 的控制字符。

VT100 控制码

\033[0m		// 关闭所有属性
\033[1m		// 设置为高亮
\033[4m		// 下划线
\033[5m		// 闪烁
\033[7m		// 反显
\033[8m		// 消隐
\033[nA		// 光标上移 n 行
\033[nB		// 光标下移 n 行
\033[nC		// 光标右移 n 行
\033[nD		// 光标左移 n 行
\033[y;xH	// 设置光标位置
\033[2J		// 清屏
\033[K		// 清除从光标到行尾的内容
\033[s		// 保存光标位置
\033[u		// 恢复光标位置
\033[?25l	// 隐藏光标
\033[?25h	// 显示光标

\033[30m– \033[37m为设置前景色

30: 黑色
31: 红色
32: 绿色
33: 黄色
34: 蓝色
35: 紫色
36: 青色
37: 白色

\033[40m– \033[47m 为设置背景色

40: 黑色
41: 红色
42: 绿色
43: 黄色
44: 蓝色
45: 紫色
46: 青色
47: 白色

ANSI / VT100控制码文档

PTY(pseudoTTY)伪终端/网络终端

在一些操作系统中,包括Unix的,一个伪终端,pseudotty,或PTY是一对伪设备,其中,所述一个的从属,模仿硬件文本终端装置,其中,所述其它的主,提供了这样的装置终端仿真器进程控制从站。终端仿真器进程还必须处理终端控制命令,例如,用于调整屏幕的大小。广泛使用的终端仿真程序包括xterm,GNOME终端,Konsole和终端。远程登录处理程序(如ssh和telnet服务器)扮演相同的角色,但与远程用户而不是本地用户进行通信。还要考虑诸如期望之类的程序。

zIj2yey.jpg!web

2

Go语言终端colorful-text

打印色彩文字示例

package main
import "fmt"
func main() {
   fmt.Print("\x1b[4;30;46m")//设置颜色样式
   fmt.Print("Hello World")//打印文本内容
   fmt.Println("\x1b[0m")//样式结束符,清楚之前的显示属性
}

运行效果

v2MRjuz.png!web

源代码解析,请关注第4行,这是VT100控制码改变颜色。\x1b[4;30;46m 由3部分组成。

  • \x1b[ :控制序列导入器

  • 4;30;46:由分号分隔的参数。4表示下划线,30表示设置前景色黑色,46表示设置背景颜色青色

  • m :最后一个字符(总是一个字符)

打印Hello World后,print\x1b[0m包含0用来表示清除显示属性。

开源库 fatih/color 的原理就是使用golang print VT100控制码(ANSI Escape Sequence 标记文本内容,色彩丰富的终端文本。

3

Go语言终端进度条progress

显示进度条的代码的原理:

  1. 终端需要擦除终端

  2. 打印进度条

  3. 移动光标位置

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>表示ASCII“转义”字符,0x1B
		fmt.Print("\x1b7")   // 保存光标位置 保存光标和Attrs <ESC> 7
		fmt.Print("\x1b[2k") // 清空当前行的内容 擦除线<ESC> [2K
		renderbar(i, total)
		time.Sleep(50 * time.Millisecond)
		fmt.Print("\x1b8") // 恢复光标位置 恢复光标和Attrs <ESC> 8
	}
	fmt.Println()
}

这部分代码缺陷就是barwidth这个值是固定的,但实际中这个变量 应该 跟随终端的宽度来确定。

4

关于终端仿真器的窗口大小

我们可以更改窗口大小,因为我们使用pty(终端模拟器),而不是终端机器。 在本节中,让我们了解如何获得终端仿真器的大小。 要获得窗口大小,你需要 syscall.SYS_IOCTL 使用 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
}

但从易用性和简单出发,最好直接调用 unix.IoctlGetWinsize,注意 GetWinsizeAPI 在 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()
}

不仅要了解如何获取窗口大小,还需要知道如何接收事件,通知事件窗口大小更改。

这里以macOS/unix系统为例,你可以从UNIX OS信号接收通知。你只需处理SIGWINCH os信号,如下所示:

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")       // 保存光标位置
	fmt.Print("\x1b[2k")     // 清除当前行内容
	defer fmt.Print("\x1b8") // 恢复光标位置

	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()
}

通过调用ioctl与TIOCGWINSZ当你收到SIGWINCH signal,你可以得到窗口的大小.您可以从此信息控制终端UI。但是很难正确擦除屏幕。实际上,如果在此代码中使终端窗口变小,则输出将崩溃。最简单的方法是每次都擦除整个屏幕。

nAfAvqz.gif

总结

思维扩展:根据 ANSI/VT100终端控制码文档结合 python/bash/go/java/c/php 等语言的 print 函数你可以开发出自己的富文本终端UI ap。

参考文档

  • VT100发展史

  • ANSI/VT100终端控制码

  • ANSI/VT100控制码go语言实现:GitHub开源库fatih/color

  • 终端GUI高级示例:https://github.com/jroimartin/gocui

上就是 篇文章的全部内容了 希望本文的内容对大家的学习或者工作能带来一定的帮助 如果有疑问大家可以留言交流

360云计算

由360云平台团队打造的技术分享公众号,内容涉及 数据库、大数据、微服务、容器、AIOps、IoT 等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享

UZ7zuen.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK