如何将AI能力与大数据技术结合,助力数据分析治理等工作的效率大幅提升,优化大数据引擎的性能及成本? 了解详情
写点什么

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

  • 2019-11-14
  • 本文字数:3808 字

    阅读完需:约 12 分钟

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

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


1 终端(terminal)的发展历史

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


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


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



VT100


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



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 服务器)扮演相同的角色,但与远程用户而不是本地用户进行通信。还要考虑诸如期望之类的程序。



2 Go 语言终端 colorful-text

打印色彩文字示例


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


运行效果



源代码解析,请关注第 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。但是很难正确擦除屏幕。实际上,如果在此代码中使终端窗口变小,则输出将崩溃。最简单的方法是每次都擦除整个屏幕。



总结

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


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


本文转载自公众号 360 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/DQzlfQXt4dV9PYwoVdomaw


2019-11-14 16:551192

评论

发布
暂无评论
发现更多内容

加密猫MIMI系统APP开发|加密猫MIMI软件开发

系统开发

架构师训练营 - 大作业1

阿甘

Flash Player终将成为历史,HTML5正站在舞台的中央

葡萄城技术团队

云上可靠性测试:让我们一起给开发找点事儿

华为云开发者联盟

安全 云服务 可靠性

速来围观!阿里P8大牛写出的JDK源码剖析及大型网站技术架构与业务架构融合之道

Java架构之路

Java 程序员 架构 面试 编程语言

测开之函数进阶· 第4篇《匿名函数》

清菡软件测试

测试开发

为什么香港云服务器更适合放新网站

德胜网络-阳

用一把吃鸡的时间,免费上云搭建网站应用

华为云开发者联盟

服务 建站

Demo分享丨看ModelArts与HiLens是如何让车自己跑起来的

华为云开发者联盟

人工智能 智能车 hilens

Java岗四面字节跳动成功之前,我都刷了那些面试题以及做了那些准备!

Java架构之路

Java 程序员 架构 面试 编程语言

SpringBoot,来实现MySQL读写分离技术

Java架构师迁哥

7. JDK拍了拍你:字符串拼接一定记得用MessageFormat#format

YourBatman

Spring Framework 类型转换 MessageFormat DateFormat

微服务架构思考 - 理清楚,管起来

jorden wang

APICloud AVM 多端开发 |外卖 app 开发案例源码教程(上)

APICloud

Vue 大前端 Web Worker 移动终端

GitHub标星力推!我掏空了各大搜索引擎,给你整理了188道Java面试题,满满干货记得收藏

Java架构之路

Java 程序员 架构 面试 编程语言

支持 gRPC 长链接,深度解读 Nacos 2.0 架构设计及新模型

阿里巴巴云原生

云计算 阿里云 开源 微服务 云原生

JAVA并发编程原理与实战

Geek_53983e

原理 java 并发 实战

二十多岁的年纪是怎么成功四面字节跳动,最终拿到offer的?

Java架构之路

Java 程序员 架构 面试 编程语言

一个企业用电有多浪费?90后开发者大显身手,让每度电从此更“聪明”!

华为云开发者联盟

AI 物联网 智慧园区

面试必问的 Redis:主从复制

Java架构师迁哥

Spring Cloud 2020.0.0 正式发布,对开发者来说意味着什么?

阿里巴巴云原生

阿里云 容器 开发者 云原生 架构师

CAP 原理 <笔记>

raox

极客大学架构师训练营

在wildfly 21中搭建cluster集群

程序那些事

程序那些事 wildfly wildfly21 集群部署 集群架构

2021 云原生走向何处?

云原生实验室

PostgreSQL 13 RPM中有哪些新功能?

PostgreSQLChina

数据库 postgresql 开源

软件测试必须掌握的http网络协议知识

测试人生路

软件测试

姐夫半夜不睡觉,竟躲在厕所看这“57道Redis面试题”?

Java架构之路

Java 程序员 架构 面试 编程语言

K8S 资源可视化利器:Kubectl-Graph

郭旭东

Kubernetes Kubernetes Plugin

架构大作业一

Geek_michael

极客大学架构师训练营

LeetCode题解:剑指 Offer 40. 最小的k个数,快速排序,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

Go进阶:如何开发多彩动感的终端UI应用_文化 & 方法_周庆_InfoQ精选文章