简单介绍
https://github.com/gdamore/tcell
一个终端编辑库,受termbox启发,目前仍在维护,有不少终端工具在使用,如fzf, termshark, tview。由于打算使用这个库,翻了下代码,整理了一点内容,也方便后面查看
文件目录
1 | . |
入口views
views 是 tcell 自己的一层封装,从这里入手开始看。会看到 views/app.go 中 run 方法
1 | func (app *Application) run() { |
可以看到有几件事情
- app 初始化
- screen 初始化
- widget 设置 view
- 循环
- draw 和 show
- poollEvent
- 处理事件
app.initialize初始化
views/app.go 中 app.initialize 会调 tscreen.go 下面的方法
1 | func NewTerminfoScreenFromTty(tty Tty) (Screen, error) { |
可以看到有几件事情
- 从 env 中获取 TERM 环境变量,然后找 terminfo
- 创建 tScreen 实例,并初始化基本字段
screen.Init方法
再来看 tscreen.go Init 方法
1 | func (t *tScreen) Init() error { |
这里干了几件事情
- 初始化 tty
- 事件、按键chan的初始化,按键定时器
- quit chan初始化
- 终端操作调用
初始化tty
1 | // NewDevTtyFromDev opens a tty device given a path. This can be useful to bind to other nodes. |
tty 的初始化有个窗口大小改变的信号处理,后面会用到。另外一个是拿到 terminal 的 fd,已经初始状态,以便 app 退出的时候恢复
终端操作调用
1 | func (t *tScreen) engage() error { |
干了几件事情
- 注册窗口大小改变回调函数
- 函数注册到了 tty.cb 中
- tty.Start()
- stop chan 初始化
- 输入处理循环
- main循环
tty.Start()方法
tty.Start() 在 tty_unis.go 中
1 | func (tty *devTty) Start() error { |
干了几件事情:
- 初始化 tty.stopQ 管道
- 起了个go程,处理窗口大小改变信息
- select 两个管道 stopQ 和 tty.sig
- 执行了回调函数
- 注册窗口大小改变回调函数
inputLoop输入处理
再来看看 inputLoop 方法
1 | func (t *tScreen) inputLoop(stopQ chan struct{}) { |
干了几件事情:
- 从tty的fd读数据
- 将数据送到keychan
mainLoop主循环
再来看看 mainLoop 方法
1 | func (t *tScreen) mainLoop(stopQ chan struct{}) { |
干了几件事情:
- 接收 stopQ 和 quit 消息,终止循环
- 处理窗口大小改变信号过来的消息
- 定时处理
- keychan接收的输入可能不完整,超过 50ms 直接处理
- 超时的情况直接将符号推到evch中(让app来处理)
- kechan事件处理
- 将keychan的消息写到buf
- 设置过期时间
- 从buf读数据,处理成事件推到t.evch中
- 主动停timer,如果buf还有数据,说明buf数据有未完全数据,需要起50ms定时去看超过时间还没有keychan消息的情况
scanInput方法
处理buf的符号成对应的事件,发送到 t.evch 中
1 | func (t *tScreen) scanInput(buf *bytes.Buffer, expire bool) { |
screen.PollEvent方法
再来看看事件这一部分,在 views/app.go 主循环中,不停地 PollEvent 事件出来处理。PostEvent 方法在 tscreen.go 中
1 | func (t *tScreen) PollEvent() Event { |
可以看到就是一直从 t.evch chan 中拿事件
PostEvent方法
PostEvent 分两种,一种 PostEventWait 是如果 t.evch 满了,会阻塞直到能 Post;另一种 PostEvent 是直接发数据到 t.evch,如果满了就返回 Full 错误
1 | func (t *tScreen) PostEventWait(ev Event) { |
触发时机
- scanInput 的时候。即从终端读完 keychan 消息处理成事件后
- resize 的时候。而resize分别会在 Init, Show, Sync 和 mainLoop中resizeQ 被调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15func (t *tScreen) resize() {
if w, h, e := t.tty.WindowSize(); e == nil {
if w != t.w || h != t.h {
t.cx = -1
t.cy = -1
t.cells.Resize(w, h)
t.cells.Invalidate()
t.h = h
t.w = w
ev := NewEventResize(w, h)
_ = t.PostEvent(ev)
}
}
} - inputLoop 中 t.tty.Read(chunk) 从终端消息读取失败会推Event
1
2
3
4
5
6
7n, e := t.tty.Read(chunk)
switch e {
case nil:
default:
_ = t.PostEvent(NewEventError(e))
return
} - application 自己主动调用