Before editing termutil

This commit is contained in:
Melon 2024-01-14 20:15:26 +00:00
parent 7080321cb9
commit 4d10bde04b
Signed by: melon
GPG Key ID: 6C9D970C50D26A25
29 changed files with 4685 additions and 167 deletions

View File

@ -1,59 +0,0 @@
package voidterm
import (
"io"
"sync/atomic"
)
type Buffer struct {
lines []Line
cursor Position
inputStream io.Reader
width, height atomic.Uint64
done chan struct{}
updateBuffer chan struct{}
}
func NewBuffer(width, height uint64, input io.Reader) *Buffer {
v := &Buffer{
lines: make([]Line, 0),
cursor: Position{0, 0},
inputStream: input,
done: make(chan struct{}),
updateBuffer: make(chan struct{}),
}
v.width.Store(width)
v.height.Store(height)
return v
}
func (v *Buffer) renderBuffer() [][]Cell {
width := v.width.Load()
height := v.height.Load()
buf := make([][]Cell, height)
y := v.height.Load() - 1
lineIdx := len(v.lines) - 1
for _, i := range v.lines {
w := i.Wrap(width)
for _, j := range w {
buf[y] = j
y++
}
}
return nil
}
var specialChars = map[rune]func(t *Buffer){
0x07: handleOutputBell,
0x08: handleOutputBackspace,
'\n': handleOutputLineFeed,
'\v': handleOutputLineFeed,
'\f': handleOutputLineFeed,
'\r': handleOutputCarriageReturn,
'\t': handleOutputTab,
0x0e: handleShiftOut, // handle switch to G1 character set
0x0f: handleShiftIn, // handle switch to G0 character set
}

14
cell.go
View File

@ -1,14 +0,0 @@
package voidterm
type Cell struct {
r MeasuredRune
attr CellAttributes
}
func (c *Cell) Attr() CellAttributes {
return c.attr
}
func (c *Cell) Rune() MeasuredRune {
return c.r
}

19
cmd/voidterm/main.go Normal file
View File

@ -0,0 +1,19 @@
package main
import (
"github.com/1f349/voidterm"
"github.com/1f349/voidterm/termutil"
"log"
)
func main() {
updateChan := make(chan struct{})
void := voidterm.New("unix:///var/run/docker.sock", "aa5e8ebe40c4")
void.Run(updateChan, 14, 11)
term := termutil.New(termutil.WithShell("/usr/bin/bash"))
err := term.Run(updateChan, 14, 11)
if err != nil {
log.Fatal(err)
}
}

8
go.mod
View File

@ -2,10 +2,16 @@ module github.com/1f349/voidterm
go 1.21.6
require github.com/stretchr/testify v1.8.4
require (
github.com/creack/pty v1.1.21
github.com/liamg/darktile v0.0.11
github.com/stretchr/testify v1.8.4
golang.org/x/term v0.16.0
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.16.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

551
go.sum
View File

@ -1,10 +1,559 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.wow.st/gmp/clip v0.0.0-20191001134149-1458ba6a7cf5/go.mod h1:NLdpaBoMQNFqncwP8OVRNWUDw1Kt9XWm3snfT7cXu24=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/BurntSushi/xgb v0.0.0-20200324125942-20f126ea2843/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.12/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/d-tsuji/clipboard v0.0.3/go.mod h1:hF88aLYx9LHNUFRrT6KPRkXEUm34nqP97IFgORGBRFs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210715014612-ab6297867137/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hajimehoshi/bitmapfont/v2 v2.1.3/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=
github.com/hajimehoshi/ebiten/v2 v2.2.0-alpha.11.0.20210724070913-1706d9436a78/go.mod h1:4AG16fE4/E9OfftCnkhL1KXUEAkA/my+AQ0eY/vi8jw=
github.com/hajimehoshi/file2byteslice v0.0.0-20200812174855-0e5e8a80490e/go.mod h1:CqqAHp7Dk/AqQiwuhV1yT2334qbA/tFWQW0MD2dGqUE=
github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM=
github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI=
github.com/hajimehoshi/oto v0.7.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/liamg/darktile v0.0.11 h1:RmJSTWgdqA3hM7blp3NY92xJVPlNzNsIK2O8CimPuQw=
github.com/liamg/darktile v0.0.11/go.mod h1:vFhboN4Fqebzm9KL6U/XHKbbq+M4pSdO/8jBUMa3lu0=
github.com/liamg/fontinfo v0.1.3/go.mod h1:6REdGXLC8yXmxpX31DDwjjzT06g1c7UcvY75AGf9sH4=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20210729172720-737cce5152fc/go.mod h1:DVyR6MI7P4kEQgvZJSj1fQGrWIi2RzIrfYWycwheUAc=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190703141733-d6a02ce849c9/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mobile v0.0.0-20201217150744-e6ae53a27f4f/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mobile v0.0.0-20210716004757-34ab1303b554/go.mod h1:jFTmtFYCV0MFtXBU+J5V/+5AUeVS0ON/0WkE/KSrl6E=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
mvdan.cc/xurls v1.1.0/go.mod h1:TNWuhvo+IqbUCmtUIb/3LJSQdrzel8loVpgFm0HikbI=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

32
line.go
View File

@ -1,32 +0,0 @@
package voidterm
type Line struct {
wrapped bool
cells []Cell
}
func LineFromRunes(runes []rune, style CellAttributes) Line {
l := make(Line, len(runes))
for i, r := range runes {
l[i] = Cell{
r: r,
s: style,
}
}
return l
}
func (l Line) Wrap(width uint64) WrappedLine {
if uint64(len(l)) <= width {
return WrappedLine{l}
}
a := l
w := make(WrappedLine, 0, 1+uint64(len(l)-1)/width)
for uint64(len(a)) > width {
w = append(w, a[:width])
a = a[width:]
}
return w
}
type WrappedLine []Line

View File

@ -1,23 +0,0 @@
package voidterm
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestLine_Wrap(t *testing.T) {
t.Run("too wide", func(t *testing.T) {
l := LineFromRunes([]rune("Hello world!"), CellAttributes{}).Wrap(16)
assert.Equal(t, WrappedLine{
LineFromRunes([]rune("Hello world!"), CellAttributes{}),
}, l)
})
t.Run("too thin", func(t *testing.T) {
l := LineFromRunes([]rune("Hello world!"), CellAttributes{}).Wrap(4)
assert.Equal(t, WrappedLine{
LineFromRunes([]rune("Hell"), CellAttributes{}),
LineFromRunes([]rune("o wo"), CellAttributes{}),
LineFromRunes([]rune("rld!"), CellAttributes{}),
}, l)
})
}

View File

@ -1,5 +0,0 @@
package voidterm
type Position struct {
X, Y int
}

109
termutil/ansi.go Normal file
View File

@ -0,0 +1,109 @@
package termutil
func (t *Terminal) handleANSI(readChan chan MeasuredRune) (renderRequired bool) {
// if the byte is an escape character, read the next byte to determine which one
r := <-readChan
t.log("ANSI SEQ %c 0x%X", r.Rune, r.Rune)
t.mu.Lock()
defer t.mu.Unlock()
switch r.Rune {
case '[':
return t.handleCSI(readChan)
case ']':
return t.handleOSC(readChan)
case '(':
return t.handleSCS0(readChan) // select character set into G0
case ')':
return t.handleSCS1(readChan) // select character set into G1
case '*':
return swallowHandler(1)(readChan) // character set bullshit
case '+':
return swallowHandler(1)(readChan) // character set bullshit
case '>':
return swallowHandler(0)(readChan) // numeric char selection
case '=':
return swallowHandler(0)(readChan) // alt char selection
case '7':
t.GetActiveBuffer().saveCursor()
case '8':
t.GetActiveBuffer().restoreCursor()
case 'D':
t.GetActiveBuffer().index()
case 'E':
t.GetActiveBuffer().newLineEx(true)
case 'H':
t.GetActiveBuffer().tabSetAtCursor()
case 'M':
t.GetActiveBuffer().reverseIndex()
case 'P': // sixel
t.handleSixel(readChan)
case 'c':
t.GetActiveBuffer().clear()
case '#':
return t.handleScreenState(readChan)
case '^':
return t.handlePrivacyMessage(readChan)
default:
t.log("UNKNOWN ESCAPE SEQUENCE: 0x%X", r.Rune)
return false
}
return true
}
func swallowHandler(size int) func(pty chan MeasuredRune) bool {
return func(pty chan MeasuredRune) bool {
for i := 0; i < size; i++ {
<-pty
}
return false
}
}
func (t *Terminal) handleScreenState(readChan chan MeasuredRune) bool {
b := <-readChan
switch b.Rune {
case '8': // DECALN -- Screen Alignment Pattern
// hide cursor?
buffer := t.GetActiveBuffer()
buffer.resetVerticalMargins(uint(buffer.viewHeight))
buffer.SetScrollOffset(0)
// Fill the whole screen with E's
count := buffer.ViewHeight() * buffer.ViewWidth()
for count > 0 {
buffer.write(MeasuredRune{Rune: 'E', Width: 1})
count--
if count > 0 && !buffer.modes.AutoWrap && count%buffer.ViewWidth() == 0 {
buffer.index()
buffer.carriageReturn()
}
}
// restore cursor
buffer.setPosition(0, 0)
default:
return false
}
return true
}
func (t *Terminal) handlePrivacyMessage(readChan chan MeasuredRune) bool {
isEscaped := false
for {
b := <-readChan
if b.Rune == 0x18 /*CAN*/ || b.Rune == 0x1a /*SUB*/ || (b.Rune == 0x5c /*backslash*/ && isEscaped) {
break
}
if isEscaped {
isEscaped = false
} else if b.Rune == 0x1b {
isEscaped = true
continue
}
}
return false
}

891
termutil/buffer.go Normal file
View File

@ -0,0 +1,891 @@
package termutil
import (
"image"
"image/color"
"sync"
)
const TabSize = 8
type CursorShape uint8
const (
CursorShapeBlinkingBlock CursorShape = iota
CursorShapeDefault
CursorShapeSteadyBlock
CursorShapeBlinkingUnderline
CursorShapeSteadyUnderline
CursorShapeBlinkingBar
CursorShapeSteadyBar
)
type Buffer struct {
lines []Line
savedCursorPos Position
savedCursorAttr *CellAttributes
cursorShape CursorShape
savedCharsets []*map[rune]rune
savedCurrentCharset int
topMargin uint // see DECSTBM docs - this is for scrollable regions
bottomMargin uint // see DECSTBM docs - this is for scrollable regions
viewWidth uint16
viewHeight uint16
cursorPosition Position // raw
cursorAttr CellAttributes
scrollLinesFromBottom uint
maxLines uint64
tabStops []uint16
charsets []*map[rune]rune // array of 2 charsets, nil means ASCII (no conversion)
currentCharset int // active charset index in charsets array, valid values are 0 or 1
modes Modes
selectionStart *Position
selectionEnd *Position
highlightStart *Position
highlightEnd *Position
highlightAnnotation *Annotation
sixels []Sixel
selectionMu sync.Mutex
}
type Annotation struct {
Image image.Image
Text string
Width float64 // Width in cells
Height float64 // Height in cells
}
type Selection struct {
Start Position
End Position
}
type Position struct {
Line uint64
Col uint16
}
// NewBuffer creates a new terminal buffer
func NewBuffer(width, height uint16, maxLines uint64, fg color.Color, bg color.Color) *Buffer {
b := &Buffer{
lines: []Line{},
viewHeight: height,
viewWidth: width,
maxLines: maxLines,
topMargin: 0,
bottomMargin: uint(height - 1),
cursorAttr: CellAttributes{
fgColour: fg,
bgColour: bg,
},
charsets: []*map[rune]rune{nil, nil},
modes: Modes{
LineFeedMode: true,
AutoWrap: true,
ShowCursor: true,
SixelScrolling: true,
},
cursorShape: CursorShapeDefault,
}
return b
}
func (buffer *Buffer) SetCursorShape(shape CursorShape) {
buffer.cursorShape = shape
}
func (buffer *Buffer) GetCursorShape() CursorShape {
return buffer.cursorShape
}
func (buffer *Buffer) IsCursorVisible() bool {
return buffer.modes.ShowCursor
}
func (buffer *Buffer) IsApplicationCursorKeysModeEnabled() bool {
return buffer.modes.ApplicationCursorKeys
}
func (buffer *Buffer) HasScrollableRegion() bool {
return buffer.topMargin > 0 || buffer.bottomMargin < uint(buffer.ViewHeight())-1
}
func (buffer *Buffer) InScrollableRegion() bool {
cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)
return buffer.HasScrollableRegion() && uint(cursorVY) >= buffer.topMargin && uint(cursorVY) <= buffer.bottomMargin
}
// NOTE: bottom is exclusive
func (buffer *Buffer) getAreaScrollRange() (top uint64, bottom uint64) {
top = buffer.convertViewLineToRawLine(uint16(buffer.topMargin))
bottom = buffer.convertViewLineToRawLine(uint16(buffer.bottomMargin)) + 1
if bottom > uint64(len(buffer.lines)) {
bottom = uint64(len(buffer.lines))
}
return top, bottom
}
func (buffer *Buffer) areaScrollDown(lines uint16) {
// NOTE: bottom is exclusive
top, bottom := buffer.getAreaScrollRange()
for i := bottom; i > top; {
i--
if i >= top+uint64(lines) {
buffer.lines[i] = buffer.lines[i-uint64(lines)]
} else {
buffer.lines[i] = newLine()
}
}
}
func (buffer *Buffer) areaScrollUp(lines uint16) {
// NOTE: bottom is exclusive
top, bottom := buffer.getAreaScrollRange()
for i := top; i < bottom; i++ {
from := i + uint64(lines)
if from < bottom {
buffer.lines[i] = buffer.lines[from]
} else {
buffer.lines[i] = newLine()
}
}
}
func (buffer *Buffer) saveCursor() {
copiedAttr := buffer.cursorAttr
buffer.savedCursorAttr = &copiedAttr
buffer.savedCursorPos = buffer.cursorPosition
buffer.savedCharsets = make([]*map[rune]rune, len(buffer.charsets))
copy(buffer.savedCharsets, buffer.charsets)
buffer.savedCurrentCharset = buffer.currentCharset
}
func (buffer *Buffer) restoreCursor() {
// TODO: Do we need to restore attributes on cursor restore? conflicting sources but vim + htop work better without doing so
//if buffer.savedCursorAttr != nil {
// copiedAttr := *buffer.savedCursorAttr
// copiedAttr.bgColour = buffer.defaultCell(false).attr.bgColour
// copiedAttr.fgColour = buffer.defaultCell(false).attr.fgColour
// buffer.cursorAttr = copiedAttr
//}
buffer.cursorPosition = buffer.savedCursorPos
if buffer.savedCharsets != nil {
buffer.charsets = make([]*map[rune]rune, len(buffer.savedCharsets))
copy(buffer.charsets, buffer.savedCharsets)
buffer.currentCharset = buffer.savedCurrentCharset
}
}
func (buffer *Buffer) getCursorAttr() *CellAttributes {
return &buffer.cursorAttr
}
func (buffer *Buffer) GetCell(viewCol uint16, viewRow uint16) *Cell {
rawLine := buffer.convertViewLineToRawLine(viewRow)
return buffer.getRawCell(viewCol, rawLine)
}
func (buffer *Buffer) getRawCell(viewCol uint16, rawLine uint64) *Cell {
if rawLine >= uint64(len(buffer.lines)) {
return nil
}
line := &buffer.lines[rawLine]
if int(viewCol) >= len(line.cells) {
return nil
}
return &line.cells[viewCol]
}
// Column returns cursor column
func (buffer *Buffer) CursorColumn() uint16 {
// @todo originMode and left margin
return buffer.cursorPosition.Col
}
// CursorLineAbsolute returns absolute cursor line coordinate (ignoring Origin Mode) - view format
func (buffer *Buffer) CursorLineAbsolute() uint16 {
cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)
return cursorVY
}
// CursorLine returns cursor line (in Origin Mode it is relative to the top margin)
func (buffer *Buffer) CursorLine() uint16 {
if buffer.modes.OriginMode {
return buffer.CursorLineAbsolute() - uint16(buffer.topMargin)
}
return buffer.CursorLineAbsolute()
}
func (buffer *Buffer) TopMargin() uint {
return buffer.topMargin
}
func (buffer *Buffer) BottomMargin() uint {
return buffer.bottomMargin
}
// cursor Y (raw)
func (buffer *Buffer) RawLine() uint64 {
return buffer.cursorPosition.Line
}
func (buffer *Buffer) convertViewLineToRawLine(viewLine uint16) uint64 {
rawHeight := buffer.Height()
if int(buffer.viewHeight) > rawHeight {
return uint64(viewLine)
}
return uint64(int(viewLine) + (rawHeight - int(buffer.viewHeight+uint16(buffer.scrollLinesFromBottom))))
}
func (buffer *Buffer) convertRawLineToViewLine(rawLine uint64) uint16 {
rawHeight := buffer.Height()
if int(buffer.viewHeight) > rawHeight {
return uint16(rawLine)
}
return uint16(int(rawLine) - (rawHeight - int(buffer.viewHeight+uint16(buffer.scrollLinesFromBottom))))
}
func (buffer *Buffer) GetVPosition() int {
result := int(uint(buffer.Height()) - uint(buffer.ViewHeight()) - buffer.scrollLinesFromBottom)
if result < 0 {
result = 0
}
return result
}
// Width returns the width of the buffer in columns
func (buffer *Buffer) Width() uint16 {
return buffer.viewWidth
}
func (buffer *Buffer) ViewWidth() uint16 {
return buffer.viewWidth
}
func (buffer *Buffer) Height() int {
return len(buffer.lines)
}
func (buffer *Buffer) ViewHeight() uint16 {
return buffer.viewHeight
}
func (buffer *Buffer) deleteLine() {
index := int(buffer.RawLine())
buffer.lines = buffer.lines[:index+copy(buffer.lines[index:], buffer.lines[index+1:])]
}
func (buffer *Buffer) insertLine() {
if !buffer.InScrollableRegion() {
pos := buffer.RawLine()
maxLines := buffer.GetMaxLines()
newLineCount := uint64(len(buffer.lines) + 1)
if newLineCount > maxLines {
newLineCount = maxLines
}
out := make([]Line, newLineCount)
copy(
out[:pos-(uint64(len(buffer.lines))+1-newLineCount)],
buffer.lines[uint64(len(buffer.lines))+1-newLineCount:pos])
out[pos] = newLine()
copy(out[pos+1:], buffer.lines[pos:])
buffer.lines = out
} else {
topIndex := buffer.convertViewLineToRawLine(uint16(buffer.topMargin))
bottomIndex := buffer.convertViewLineToRawLine(uint16(buffer.bottomMargin))
before := buffer.lines[:topIndex]
after := buffer.lines[bottomIndex+1:]
out := make([]Line, len(buffer.lines))
copy(out[0:], before)
pos := buffer.RawLine()
for i := topIndex; i < bottomIndex; i++ {
if i < pos {
out[i] = buffer.lines[i]
} else {
out[i+1] = buffer.lines[i]
}
}
copy(out[bottomIndex+1:], after)
out[pos] = newLine()
buffer.lines = out
}
}
func (buffer *Buffer) insertBlankCharacters(count int) {
index := int(buffer.RawLine())
for i := 0; i < count; i++ {
cells := buffer.lines[index].cells
buffer.lines[index].cells = append(cells[:buffer.cursorPosition.Col], append([]Cell{buffer.defaultCell(true)}, cells[buffer.cursorPosition.Col:]...)...)
}
}
func (buffer *Buffer) insertLines(count int) {
if buffer.HasScrollableRegion() && !buffer.InScrollableRegion() {
// should have no effect outside of scrollable region
return
}
buffer.cursorPosition.Col = 0
for i := 0; i < count; i++ {
buffer.insertLine()
}
}
func (buffer *Buffer) deleteLines(count int) {
if buffer.HasScrollableRegion() && !buffer.InScrollableRegion() {
// should have no effect outside of scrollable region
return
}
buffer.cursorPosition.Col = 0
for i := 0; i < count; i++ {
buffer.deleteLine()
}
}
func (buffer *Buffer) index() {
// This sequence causes the active position to move downward one line without changing the column position.
// If the active position is at the bottom margin, a scroll up is performed."
cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)
if buffer.InScrollableRegion() {
if uint(cursorVY) < buffer.bottomMargin {
buffer.cursorPosition.Line++
} else {
buffer.areaScrollUp(1)
}
return
}
if cursorVY >= buffer.ViewHeight()-1 {
buffer.lines = append(buffer.lines, newLine())
maxLines := buffer.GetMaxLines()
if uint64(len(buffer.lines)) > maxLines {
copy(buffer.lines, buffer.lines[uint64(len(buffer.lines))-maxLines:])
buffer.lines = buffer.lines[:maxLines]
}
}
buffer.cursorPosition.Line++
}
func (buffer *Buffer) reverseIndex() {
cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)
if uint(cursorVY) == buffer.topMargin {
buffer.areaScrollDown(1)
} else if cursorVY > 0 {
buffer.cursorPosition.Line--
}
}
// write will write a rune to the terminal at the position of the cursor, and increment the cursor position
func (buffer *Buffer) write(runes ...MeasuredRune) {
// scroll to bottom on input
buffer.scrollLinesFromBottom = 0
for _, r := range runes {
line := buffer.getCurrentLine()
if buffer.modes.ReplaceMode {
if buffer.CursorColumn() >= buffer.Width() {
if buffer.modes.AutoWrap {
buffer.cursorPosition.Line++
buffer.cursorPosition.Col = 0
line = buffer.getCurrentLine()
} else {
// no more room on line and wrapping is disabled
return
}
}
for int(buffer.CursorColumn()) >= len(line.cells) {
line.append(buffer.defaultCell(int(buffer.CursorColumn()) == len(line.cells)))
}
line.cells[buffer.cursorPosition.Col].attr = buffer.cursorAttr
line.cells[buffer.cursorPosition.Col].setRune(r)
buffer.incrementCursorPosition()
continue
}
if buffer.CursorColumn() >= buffer.Width() { // if we're after the line, move to next
if buffer.modes.AutoWrap {
buffer.newLineEx(true)
newLine := buffer.getCurrentLine()
if len(newLine.cells) == 0 {
newLine.append(buffer.defaultCell(true))
}
cell := &newLine.cells[0]
cell.setRune(r)
cell.attr = buffer.cursorAttr
} else {
// no more room on line and wrapping is disabled
return
}
} else {
for int(buffer.CursorColumn()) >= len(line.cells) {
line.append(buffer.defaultCell(int(buffer.CursorColumn()) == len(line.cells)))
}
cell := &line.cells[buffer.CursorColumn()]
cell.setRune(r)
cell.attr = buffer.cursorAttr
}
buffer.incrementCursorPosition()
}
}
func (buffer *Buffer) incrementCursorPosition() {
// we can increment one column past the end of the line.
// this is effectively the beginning of the next line, except when we \r etc.
if buffer.CursorColumn() < buffer.Width() {
buffer.cursorPosition.Col++
}
}
func (buffer *Buffer) inDoWrap() bool {
// xterm uses 'do_wrap' flag for this special terminal state
// we use the cursor position right after the boundary
// let's see how it works out
return buffer.cursorPosition.Col == buffer.viewWidth // @todo rightMargin
}
func (buffer *Buffer) backspace() {
if buffer.cursorPosition.Col == 0 {
line := buffer.getCurrentLine()
if line.wrapped {
buffer.movePosition(int16(buffer.Width()-1), -1)
}
} else if buffer.inDoWrap() {
// the "do_wrap" implementation
buffer.movePosition(-2, 0)
} else {
buffer.movePosition(-1, 0)
}
}
func (buffer *Buffer) carriageReturn() {
cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)
for {
line := buffer.getCurrentLine()
if line == nil {
break
}
if line.wrapped && cursorVY > 0 {
buffer.cursorPosition.Line--
} else {
break
}
}
buffer.cursorPosition.Col = 0
}
func (buffer *Buffer) tab() {
tabStop := buffer.getNextTabStopAfter(buffer.cursorPosition.Col)
for buffer.cursorPosition.Col < tabStop && buffer.cursorPosition.Col < buffer.viewWidth-1 { // @todo rightMargin
buffer.write(MeasuredRune{Rune: ' ', Width: 1})
}
}
// return next tab stop x pos
func (buffer *Buffer) getNextTabStopAfter(col uint16) uint16 {
defaultStop := col + (TabSize - (col % TabSize))
if defaultStop == col {
defaultStop += TabSize
}
var low uint16
for _, stop := range buffer.tabStops {
if stop > col {
if stop < low || low == 0 {
low = stop
}
}
}
if low == 0 {
return defaultStop
}
return low
}
func (buffer *Buffer) newLine() {
buffer.newLineEx(false)
}
func (buffer *Buffer) verticalTab() {
buffer.index()
for {
line := buffer.getCurrentLine()
if !line.wrapped {
break
}
buffer.index()
}
}
func (buffer *Buffer) newLineEx(forceCursorToMargin bool) {
if buffer.IsNewLineMode() || forceCursorToMargin {
buffer.cursorPosition.Col = 0
}
buffer.index()
for {
line := buffer.getCurrentLine()
if !line.wrapped {
break
}
buffer.index()
}
}
func (buffer *Buffer) movePosition(x int16, y int16) {
var toX uint16
var toY uint16
if int16(buffer.CursorColumn())+x < 0 {
toX = 0
} else {
toX = uint16(int16(buffer.CursorColumn()) + x)
}
// should either use CursorLine() and setPosition() or use absolutes, mind Origin Mode (DECOM)
if int16(buffer.CursorLine())+y < 0 {
toY = 0
} else {
toY = uint16(int16(buffer.CursorLine()) + y)
}
buffer.setPosition(toX, toY)
}
func (buffer *Buffer) setPosition(col uint16, line uint16) {
useCol := col
useLine := line
maxLine := buffer.ViewHeight() - 1
if buffer.modes.OriginMode {
useLine += uint16(buffer.topMargin)
maxLine = uint16(buffer.bottomMargin)
// @todo left and right margins
}
if useLine > maxLine {
useLine = maxLine
}
if useCol >= buffer.ViewWidth() {
useCol = buffer.ViewWidth() - 1
}
buffer.cursorPosition.Col = useCol
buffer.cursorPosition.Line = buffer.convertViewLineToRawLine(useLine)
}
func (buffer *Buffer) GetVisibleLines() []Line {
lines := []Line{}
for i := buffer.Height() - int(buffer.ViewHeight()); i < buffer.Height(); i++ {
y := i - int(buffer.scrollLinesFromBottom)
if y >= 0 && y < len(buffer.lines) {
lines = append(lines, buffer.lines[y])
}
}
return lines
}
// tested to here
func (buffer *Buffer) clear() {
for i := 0; i < int(buffer.ViewHeight()); i++ {
buffer.lines = append(buffer.lines, newLine())
}
buffer.setPosition(0, 0)
}
// creates if necessary
func (buffer *Buffer) getCurrentLine() *Line {
cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)
return buffer.getViewLine(cursorVY)
}
func (buffer *Buffer) getViewLine(index uint16) *Line {
if index >= buffer.ViewHeight() {
return &buffer.lines[len(buffer.lines)-1]
}
if len(buffer.lines) < int(buffer.ViewHeight()) {
for int(index) >= len(buffer.lines) {
buffer.lines = append(buffer.lines, newLine())
}
return &buffer.lines[int(index)]
}
if raw := int(buffer.convertViewLineToRawLine(index)); raw < len(buffer.lines) {
return &buffer.lines[raw]
}
return nil
}
func (buffer *Buffer) eraseLine() {
buffer.clearSixelsAtRawLine(buffer.cursorPosition.Line)
line := buffer.getCurrentLine()
for i := 0; i < int(buffer.viewWidth); i++ {
if i >= len(line.cells) {
line.cells = append(line.cells, buffer.defaultCell(false))
} else {
line.cells[i] = buffer.defaultCell(false)
}
}
}
func (buffer *Buffer) eraseLineToCursor() {
buffer.clearSixelsAtRawLine(buffer.cursorPosition.Line)
line := buffer.getCurrentLine()
for i := 0; i <= int(buffer.cursorPosition.Col); i++ {
if i < len(line.cells) {
line.cells[i].erase(buffer.cursorAttr.bgColour)
}
}
}
func (buffer *Buffer) eraseLineFromCursor() {
buffer.clearSixelsAtRawLine(buffer.cursorPosition.Line)
line := buffer.getCurrentLine()
for i := buffer.cursorPosition.Col; i < buffer.viewWidth; i++ {
if int(i) >= len(line.cells) {
line.cells = append(line.cells, buffer.defaultCell(false))
} else {
line.cells[i] = buffer.defaultCell(false)
}
}
}
func (buffer *Buffer) eraseDisplay() {
for i := uint16(0); i < (buffer.ViewHeight()); i++ {
rawLine := buffer.convertViewLineToRawLine(i)
buffer.clearSixelsAtRawLine(rawLine)
if int(rawLine) < len(buffer.lines) {
buffer.lines[int(rawLine)].cells = []Cell{}
}
}
}
func (buffer *Buffer) deleteChars(n int) {
line := buffer.getCurrentLine()
if int(buffer.cursorPosition.Col) >= len(line.cells) {
return
}
before := line.cells[:buffer.cursorPosition.Col]
if int(buffer.cursorPosition.Col)+n >= len(line.cells) {
n = len(line.cells) - int(buffer.cursorPosition.Col)
}
after := line.cells[int(buffer.cursorPosition.Col)+n:]
line.cells = append(before, after...)
}
func (buffer *Buffer) eraseCharacters(n int) {
line := buffer.getCurrentLine()
max := int(buffer.cursorPosition.Col) + n
if max > len(line.cells) {
max = len(line.cells)
}
for i := int(buffer.cursorPosition.Col); i < max; i++ {
line.cells[i].erase(buffer.cursorAttr.bgColour)
}
}
func (buffer *Buffer) eraseDisplayFromCursor() {
line := buffer.getCurrentLine()
max := int(buffer.cursorPosition.Col)
if max > len(line.cells) {
max = len(line.cells)
}
line.cells = line.cells[:max]
for rawLine := buffer.cursorPosition.Line + 1; int(rawLine) < len(buffer.lines); rawLine++ {
buffer.clearSixelsAtRawLine(rawLine)
buffer.lines[int(rawLine)].cells = []Cell{}
}
}
func (buffer *Buffer) eraseDisplayToCursor() {
line := buffer.getCurrentLine()
for i := 0; i <= int(buffer.cursorPosition.Col); i++ {
if i >= len(line.cells) {
break
}
line.cells[i].erase(buffer.cursorAttr.bgColour)
}
cursorVY := buffer.convertRawLineToViewLine(buffer.cursorPosition.Line)
for i := uint16(0); i < cursorVY; i++ {
rawLine := buffer.convertViewLineToRawLine(i)
buffer.clearSixelsAtRawLine(rawLine)
if int(rawLine) < len(buffer.lines) {
buffer.lines[int(rawLine)].cells = []Cell{}
}
}
}
func (buffer *Buffer) GetMaxLines() uint64 {
result := buffer.maxLines
if result < uint64(buffer.viewHeight) {
result = uint64(buffer.viewHeight)
}
return result
}
func (buffer *Buffer) setVerticalMargins(top uint, bottom uint) {
buffer.topMargin = top
buffer.bottomMargin = bottom
}
// resetVerticalMargins resets margins to extreme positions
func (buffer *Buffer) resetVerticalMargins(height uint) {
buffer.setVerticalMargins(0, height-1)
}
func (buffer *Buffer) defaultCell(applyEffects bool) Cell {
attr := buffer.cursorAttr
if !applyEffects {
attr.blink = false
attr.bold = false
attr.dim = false
attr.inverse = false
attr.underline = false
attr.dim = false
}
return Cell{attr: attr}
}
func (buffer *Buffer) IsNewLineMode() bool {
return !buffer.modes.LineFeedMode
}
func (buffer *Buffer) tabReset() {
buffer.tabStops = nil
}
func (buffer *Buffer) tabSet(index uint16) {
buffer.tabStops = append(buffer.tabStops, index)
}
func (buffer *Buffer) tabClear(index uint16) {
var filtered []uint16
for _, stop := range buffer.tabStops {
if stop != buffer.cursorPosition.Col {
filtered = append(filtered, stop)
}
}
buffer.tabStops = filtered
}
func (buffer *Buffer) IsTabSetAtCursor() bool {
if buffer.cursorPosition.Col%TabSize > 0 {
return false
}
for _, stop := range buffer.tabStops {
if stop == buffer.cursorPosition.Col {
return true
}
}
return false
}
func (buffer *Buffer) tabClearAtCursor() {
buffer.tabClear(buffer.cursorPosition.Col)
}
func (buffer *Buffer) tabSetAtCursor() {
buffer.tabSet(buffer.cursorPosition.Col)
}
func (buffer *Buffer) GetScrollOffset() uint {
return buffer.scrollLinesFromBottom
}
func (buffer *Buffer) SetScrollOffset(offset uint) {
buffer.scrollLinesFromBottom = offset
}
func (buffer *Buffer) ScrollToEnd() {
buffer.scrollLinesFromBottom = 0
}
func (buffer *Buffer) ScrollUp(lines uint) {
if int(buffer.scrollLinesFromBottom)+int(lines) < len(buffer.lines)-int(buffer.viewHeight) {
buffer.scrollLinesFromBottom += lines
} else {
lines := len(buffer.lines) - int(buffer.viewHeight)
if lines < 0 {
lines = 0
}
buffer.scrollLinesFromBottom = uint(lines)
}
}
func (buffer *Buffer) ScrollDown(lines uint) {
if int(buffer.scrollLinesFromBottom)-int(lines) >= 0 {
buffer.scrollLinesFromBottom -= lines
} else {
buffer.scrollLinesFromBottom = 0
}
}

688
termutil/buffer_test.go Normal file
View File

@ -0,0 +1,688 @@
package termutil
import (
"image/color"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/assert"
)
func writeRaw(buf *Buffer, runes ...rune) {
for _, r := range runes {
buf.write(MeasuredRune{Rune: r, Width: 1})
}
}
func TestBufferCreation(t *testing.T) {
b := makeBufferForTesting(10, 20)
assert.Equal(t, uint16(10), b.Width())
assert.Equal(t, uint16(20), b.ViewHeight())
assert.Equal(t, uint16(0), b.CursorColumn())
assert.Equal(t, uint16(0), b.CursorLine())
assert.NotNil(t, b.lines)
}
func TestNewLine(t *testing.T) {
b := makeBufferForTesting(30, 3)
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("goodbye")...)
b.carriageReturn()
b.newLine()
expected := `
hello
goodbye
`
lines := b.GetVisibleLines()
strs := []string{}
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(strings.Join(strs, "\n")))
}
func TestTabbing(t *testing.T) {
b := makeBufferForTesting(30, 3)
writeRaw(b, []rune("hello")...)
b.tab()
writeRaw(b, []rune("x")...)
b.tab()
writeRaw(b, []rune("goodbye")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hell")...)
b.tab()
writeRaw(b, []rune("xxx")...)
b.tab()
writeRaw(b, []rune("good")...)
b.carriageReturn()
b.newLine()
expected := `
hello x goodbye
hell xxx good
`
lines := b.GetVisibleLines()
strs := []string{}
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(strings.Join(strs, "\n")))
}
func TestOffsets(t *testing.T) {
b := makeBufferForTesting(10, 3)
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello")...)
assert.Equal(t, uint16(10), b.ViewWidth())
assert.Equal(t, uint16(10), b.Width())
assert.Equal(t, uint16(3), b.ViewHeight())
assert.Equal(t, 5, b.Height())
}
func TestBufferWriteIncrementsCursorCorrectly(t *testing.T) {
b := makeBufferForTesting(5, 4)
/*01234
|-----
0|xxxxx
1|
2|
3|
|-----
*/
writeRaw(b, 'x')
require.Equal(t, uint16(1), b.CursorColumn())
require.Equal(t, uint16(0), b.CursorLine())
writeRaw(b, 'x')
require.Equal(t, uint16(2), b.CursorColumn())
require.Equal(t, uint16(0), b.CursorLine())
writeRaw(b, 'x')
require.Equal(t, uint16(3), b.CursorColumn())
require.Equal(t, uint16(0), b.CursorLine())
writeRaw(b, 'x')
require.Equal(t, uint16(4), b.CursorColumn())
require.Equal(t, uint16(0), b.CursorLine())
writeRaw(b, 'x')
require.Equal(t, uint16(5), b.CursorColumn())
require.Equal(t, uint16(0), b.CursorLine())
writeRaw(b, 'x')
require.Equal(t, uint16(1), b.CursorColumn())
require.Equal(t, uint16(1), b.CursorLine())
writeRaw(b, 'x')
require.Equal(t, uint16(2), b.CursorColumn())
require.Equal(t, uint16(1), b.CursorLine())
lines := b.GetVisibleLines()
require.Equal(t, 2, len(lines))
assert.Equal(t, "xxxxx", lines[0].String())
assert.Equal(t, "xx", lines[1].String())
}
func TestWritingNewLineAsFirstRuneOnWrappedLine(t *testing.T) {
b := makeBufferForTesting(3, 20)
b.modes.LineFeedMode = false
writeRaw(b, 'a', 'b', 'c')
assert.Equal(t, uint16(3), b.cursorPosition.Col)
assert.Equal(t, uint64(0), b.cursorPosition.Line)
b.newLine()
assert.Equal(t, uint16(0), b.cursorPosition.Col)
assert.Equal(t, uint64(1), b.cursorPosition.Line)
writeRaw(b, 'd', 'e', 'f')
assert.Equal(t, uint16(3), b.cursorPosition.Col)
assert.Equal(t, uint64(1), b.cursorPosition.Line)
b.newLine()
assert.Equal(t, uint16(0), b.cursorPosition.Col)
assert.Equal(t, uint64(2), b.cursorPosition.Line)
require.Equal(t, 3, len(b.lines))
assert.Equal(t, "abc", b.lines[0].String())
assert.Equal(t, "def", b.lines[1].String())
}
func TestWritingNewLineAsSecondRuneOnWrappedLine(t *testing.T) {
b := makeBufferForTesting(3, 20)
b.modes.LineFeedMode = false
/*
|abc
|d
|ef
|
|
|z
*/
writeRaw(b, 'a', 'b', 'c', 'd')
b.newLine()
writeRaw(b, 'e', 'f')
b.newLine()
b.newLine()
b.newLine()
writeRaw(b, 'z')
assert.Equal(t, "abc", b.lines[0].String())
assert.Equal(t, "d", b.lines[1].String())
assert.Equal(t, "ef", b.lines[2].String())
assert.Equal(t, "", b.lines[3].String())
assert.Equal(t, "", b.lines[4].String())
assert.Equal(t, "z", b.lines[5].String())
}
func TestSetPosition(t *testing.T) {
b := makeBufferForTesting(120, 80)
assert.Equal(t, 0, int(b.CursorColumn()))
assert.Equal(t, 0, int(b.CursorLine()))
b.setPosition(60, 10)
assert.Equal(t, 60, int(b.CursorColumn()))
assert.Equal(t, 10, int(b.CursorLine()))
b.setPosition(0, 0)
assert.Equal(t, 0, int(b.CursorColumn()))
assert.Equal(t, 0, int(b.CursorLine()))
b.setPosition(120, 90)
assert.Equal(t, 119, int(b.CursorColumn()))
assert.Equal(t, 79, int(b.CursorLine()))
}
func TestMovePosition(t *testing.T) {
b := makeBufferForTesting(120, 80)
assert.Equal(t, 0, int(b.CursorColumn()))
assert.Equal(t, 0, int(b.CursorLine()))
b.movePosition(-1, -1)
assert.Equal(t, 0, int(b.CursorColumn()))
assert.Equal(t, 0, int(b.CursorLine()))
b.movePosition(30, 20)
assert.Equal(t, 30, int(b.CursorColumn()))
assert.Equal(t, 20, int(b.CursorLine()))
b.movePosition(30, 20)
assert.Equal(t, 60, int(b.CursorColumn()))
assert.Equal(t, 40, int(b.CursorLine()))
b.movePosition(-1, -1)
assert.Equal(t, 59, int(b.CursorColumn()))
assert.Equal(t, 39, int(b.CursorLine()))
b.movePosition(100, 100)
assert.Equal(t, 119, int(b.CursorColumn()))
assert.Equal(t, 79, int(b.CursorLine()))
}
func TestVisibleLines(t *testing.T) {
b := makeBufferForTesting(80, 10)
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 2")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 3")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 4")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 5")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 6")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 7")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 8")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 9")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 10")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 11")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 12")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 13")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 14")...)
lines := b.GetVisibleLines()
require.Equal(t, 10, len(lines))
assert.Equal(t, "hello 5", lines[0].String())
assert.Equal(t, "hello 14", lines[9].String())
}
func TestClearWithoutFullView(t *testing.T) {
b := makeBufferForTesting(80, 10)
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.clear()
lines := b.GetVisibleLines()
for _, line := range lines {
assert.Equal(t, "", line.String())
}
}
func TestClearWithFullView(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("hello 1")...)
b.clear()
lines := b.GetVisibleLines()
for _, line := range lines {
assert.Equal(t, "", line.String())
}
}
func TestCarriageReturn(t *testing.T) {
b := makeBufferForTesting(80, 20)
writeRaw(b, []rune("hello!")...)
b.carriageReturn()
writeRaw(b, []rune("secret")...)
lines := b.GetVisibleLines()
assert.Equal(t, "secret", lines[0].String())
}
func TestCarriageReturnOnFullLine(t *testing.T) {
b := makeBufferForTesting(20, 20)
writeRaw(b, []rune("abcdeabcdeabcdeabcde")...)
b.carriageReturn()
writeRaw(b, []rune("xxxxxxxxxxxxxxxxxxxx")...)
lines := b.GetVisibleLines()
assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[0].String())
}
func TestCarriageReturnOnFullLastLine(t *testing.T) {
b := makeBufferForTesting(20, 2)
b.newLine()
writeRaw(b, []rune("abcdeabcdeabcdeabcde")...)
b.carriageReturn()
writeRaw(b, []rune("xxxxxxxxxxxxxxxxxxxx")...)
lines := b.GetVisibleLines()
assert.Equal(t, "", lines[0].String())
assert.Equal(t, "xxxxxxxxxxxxxxxxxxxx", lines[1].String())
}
func TestCarriageReturnOnWrappedLine(t *testing.T) {
b := makeBufferForTesting(80, 6)
writeRaw(b, []rune("hello!")...)
b.carriageReturn()
writeRaw(b, []rune("secret")...)
lines := b.GetVisibleLines()
assert.Equal(t, "secret", lines[0].String())
}
func TestCarriageReturnOnLineThatDoesntExist(t *testing.T) {
b := makeBufferForTesting(6, 10)
b.cursorPosition.Line = 3
b.carriageReturn()
assert.Equal(t, uint16(0), b.cursorPosition.Col)
assert.Equal(t, uint64(3), b.cursorPosition.Line)
}
func TestGetCell(t *testing.T) {
b := makeBufferForTesting(80, 20)
writeRaw(b, []rune("Hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("there")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("something...")...)
cell := b.GetCell(8, 2)
require.NotNil(t, cell)
assert.Equal(t, 'g', cell.Rune().Rune)
}
func TestGetCellWithHistory(t *testing.T) {
b := makeBufferForTesting(80, 2)
writeRaw(b, []rune("Hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("there")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("something...")...)
cell := b.GetCell(8, 1)
require.NotNil(t, cell)
assert.Equal(t, 'g', cell.Rune().Rune)
}
func TestGetCellWithBadCursor(t *testing.T) {
b := makeBufferForTesting(80, 2)
writeRaw(b, []rune("Hello\r\nthere\r\nsomething...")...)
require.Nil(t, b.GetCell(8, 3))
require.Nil(t, b.GetCell(90, 0))
}
func TestCursorPositionQuerying(t *testing.T) {
b := makeBufferForTesting(80, 20)
b.cursorPosition.Col = 17
b.cursorPosition.Line = 9
assert.Equal(t, b.cursorPosition.Col, b.CursorColumn())
assert.Equal(t, b.convertRawLineToViewLine(b.cursorPosition.Line), b.CursorLine())
}
// CSI 2 K
func TestEraseLine(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello, this is a test")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("this line should be deleted")...)
b.eraseLine()
assert.Equal(t, "hello, this is a test", b.lines[0].String())
assert.Equal(t, "", b.lines[1].String())
}
// CSI 1 K
func TestEraseLineToCursor(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello, this is a test")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("deleted")...)
b.movePosition(-3, 0)
b.eraseLineToCursor()
assert.Equal(t, "hello, this is a test", b.lines[0].String())
assert.Equal(t, "\x00\x00\x00\x00\x00ed", b.lines[1].String())
}
// CSI 0 K
func TestEraseLineAfterCursor(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello, this is a test")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("deleted")...)
b.movePosition(-3, 0)
b.eraseLineFromCursor()
assert.Equal(t, "hello, this is a test", b.lines[0].String())
assert.Equal(t, "dele", b.lines[1].String())
}
func TestEraseDisplay(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("asdasd")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("thing")...)
b.movePosition(2, 1)
b.eraseDisplay()
lines := b.GetVisibleLines()
for _, line := range lines {
assert.Equal(t, "", line.String())
}
}
func TestEraseDisplayToCursor(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("asdasd")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("thing")...)
b.movePosition(-2, 0)
b.eraseDisplayToCursor()
lines := b.GetVisibleLines()
assert.Equal(t, "", lines[0].String())
assert.Equal(t, "", lines[1].String())
assert.Equal(t, "\x00\x00\x00\x00g", lines[2].String())
}
func TestEraseDisplayFromCursor(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("asdasd")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("things")...)
b.movePosition(-3, -1)
b.eraseDisplayFromCursor()
lines := b.GetVisibleLines()
assert.Equal(t, "hello", lines[0].String())
assert.Equal(t, "asd", lines[1].String())
assert.Equal(t, "", lines[2].String())
}
func TestBackspace(t *testing.T) {
b := makeBufferForTesting(80, 5)
writeRaw(b, []rune("hello")...)
b.backspace()
b.backspace()
writeRaw(b, []rune("p")...)
lines := b.GetVisibleLines()
assert.Equal(t, "helpo", lines[0].String())
}
func TestHorizontalResizeView(t *testing.T) {
b := makeBufferForTesting(80, 10)
// 60 characters
writeRaw(b, []rune(`hellohellohellohellohellohellohellohellohellohellohellohello`)...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune(`goodbyegoodbye`)...)
require.Equal(t, uint16(14), b.cursorPosition.Col)
require.Equal(t, uint64(1), b.cursorPosition.Line)
b.resizeView(40, 10)
expected := `hellohellohellohellohellohellohellohello
hellohellohellohello
goodbyegoodbye`
require.Equal(t, uint16(14), b.cursorPosition.Col)
require.Equal(t, uint64(2), b.cursorPosition.Line)
lines := b.GetVisibleLines()
strs := []string{}
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, expected, strings.Join(strs, "\n"))
b.resizeView(20, 10)
expected = `hellohellohellohello
hellohellohellohello
hellohellohellohello
goodbyegoodbye`
lines = b.GetVisibleLines()
strs = []string{}
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, expected, strings.Join(strs, "\n"))
b.resizeView(10, 10)
expected = `hellohello
hellohello
hellohello
hellohello
hellohello
hellohello
goodbyegoo
dbye`
lines = b.GetVisibleLines()
strs = []string{}
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, expected, strings.Join(strs, "\n"))
b.resizeView(80, 20)
expected = `hellohellohellohellohellohellohellohellohellohellohellohello
goodbyegoodbye`
lines = b.GetVisibleLines()
strs = []string{}
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, expected, strings.Join(strs, "\n"))
require.Equal(t, uint16(4), b.cursorPosition.Col)
require.Equal(t, uint64(1), b.cursorPosition.Line)
}
func TestBufferMaxLines(t *testing.T) {
b := NewBuffer(80, 2, 2, color.White, color.Black)
b.modes.LineFeedMode = false
writeRaw(b, []rune("hello")...)
b.newLine()
writeRaw(b, []rune("funny")...)
b.newLine()
writeRaw(b, []rune("world")...)
assert.Equal(t, 2, len(b.lines))
assert.Equal(t, "funny", b.lines[0].String())
assert.Equal(t, "world", b.lines[1].String())
}
func TestShrinkingThenGrowing(t *testing.T) {
b := makeBufferForTesting(30, 100)
writeRaw(b, []rune("hellohellohellohellohello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("01234567890123456789")...)
b.carriageReturn()
b.newLine()
b.resizeView(25, 100)
b.resizeView(24, 100)
b.resizeView(30, 100)
expected := `hellohellohellohellohello
01234567890123456789
`
lines := b.GetVisibleLines()
var strs []string
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, expected, strings.Join(strs, "\n"))
}
func TestShrinkingThenRestoring(t *testing.T) {
b := makeBufferForTesting(30, 100)
writeRaw(b, []rune("hellohellohellohellohello")...)
b.carriageReturn()
b.newLine()
writeRaw(b, []rune("01234567890123456789")...)
b.carriageReturn()
b.newLine()
b.cursorPosition.Line = 2
for i := uint16(29); i > 5; i-- {
b.resizeView(i, 100)
}
for i := uint16(15); i < 30; i++ {
b.resizeView(i, 100)
}
expected := `hellohellohellohellohello
01234567890123456789
`
lines := b.GetVisibleLines()
var strs []string
for _, l := range lines {
strs = append(strs, l.String())
}
require.Equal(t, expected, strings.Join(strs, "\n"))
}
func makeBufferForTesting(cols, rows uint16) *Buffer {
return NewBuffer(cols, rows, 100, color.White, color.Black)
}

59
termutil/cell.go Normal file
View File

@ -0,0 +1,59 @@
package termutil
import "image/color"
type Cell struct {
r MeasuredRune
attr CellAttributes
}
func (cell *Cell) Attr() CellAttributes {
return cell.attr
}
func (cell *Cell) Rune() MeasuredRune {
return cell.r
}
func (cell *Cell) Fg() color.Color {
if cell.Attr().inverse {
return cell.attr.bgColour
}
return cell.attr.fgColour
}
func (cell *Cell) Bold() bool {
return cell.attr.bold
}
func (cell *Cell) Dim() bool {
return cell.attr.dim
}
func (cell *Cell) Italic() bool {
return cell.attr.italic
}
func (cell *Cell) Underline() bool {
return cell.attr.underline
}
func (cell *Cell) Strikethrough() bool {
return cell.attr.strikethrough
}
func (cell *Cell) Bg() color.Color {
if cell.Attr().inverse {
return cell.attr.fgColour
}
return cell.attr.bgColour
}
func (cell *Cell) erase(bgColour color.Color) {
cell.setRune(MeasuredRune{Rune: 0})
cell.attr.bgColour = bgColour
}
func (cell *Cell) setRune(r MeasuredRune) {
cell.r = r
}

View File

@ -1,12 +1,12 @@
package voidterm
package termutil
import (
"image/color"
)
type CellAttributes struct {
fg color.Color
bg color.Color
fgColour color.Color
bgColour color.Color
bold bool
italic bool
dim bool

64
termutil/charsets.go Normal file
View File

@ -0,0 +1,64 @@
package termutil
var charSets = map[rune]*map[rune]rune{
'0': &decSpecGraphics,
'B': nil, // ASCII
// @todo 1,2,A
}
var decSpecGraphics = map[rune]rune{
0x5f: 0x00A0, // NO-BREAK SPACE
0x60: 0x25C6, // BLACK DIAMOND
0x61: 0x2592, // MEDIUM SHADE
0x62: 0x2409, // SYMBOL FOR HORIZONTAL TABULATION
0x63: 0x240C, // SYMBOL FOR FORM FEED
0x64: 0x240D, // SYMBOL FOR CARRIAGE RETURN
0x65: 0x240A, // SYMBOL FOR LINE FEED
0x66: 0x00B0, // DEGREE SIGN
0x67: 0x00B1, // PLUS-MINUS SIGN
0x68: 0x2424, // SYMBOL FOR NEWLINE
0x69: 0x240B, // SYMBOL FOR VERTICAL TABULATION
0x6a: 0x2518, // BOX DRAWINGS LIGHT UP AND LEFT
0x6b: 0x2510, // BOX DRAWINGS LIGHT DOWN AND LEFT
0x6c: 0x250C, // BOX DRAWINGS LIGHT DOWN AND RIGHT
0x6d: 0x2514, // BOX DRAWINGS LIGHT UP AND RIGHT
0x6e: 0x253C, // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
0x6f: 0x23BA, // HORIZONTAL SCAN LINE-1
0x70: 0x23BB, // HORIZONTAL SCAN LINE-3
0x71: 0x2500, // BOX DRAWINGS LIGHT HORIZONTAL
0x72: 0x23BC, // HORIZONTAL SCAN LINE-7
0x73: 0x23BD, // HORIZONTAL SCAN LINE-9
0x74: 0x251C, // BOX DRAWINGS LIGHT VERTICAL AND RIGHT
0x75: 0x2524, // BOX DRAWINGS LIGHT VERTICAL AND LEFT
0x76: 0x2534, // BOX DRAWINGS LIGHT UP AND HORIZONTAL
0x77: 0x252C, // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
0x78: 0x2502, // BOX DRAWINGS LIGHT VERTICAL
0x79: 0x2264, // LESS-THAN OR EQUAL TO
0x7a: 0x2265, // GREATER-THAN OR EQUAL TO
0x7b: 0x03C0, // GREEK SMALL LETTER PI
0x7c: 0x2260, // NOT EQUAL TO
0x7d: 0x00A3, // POUND SIGN
0x7e: 0x00B7, // MIDDLE DOT
}
func (t *Terminal) handleSCS0(pty chan MeasuredRune) bool {
return t.scsHandler(pty, 0)
}
func (t *Terminal) handleSCS1(pty chan MeasuredRune) bool {
return t.scsHandler(pty, 1)
}
func (t *Terminal) scsHandler(pty chan MeasuredRune, which int) bool {
b := <-pty
cs, ok := charSets[b.Rune]
if ok {
//terminal.logger.Debugf("Selected charset %v into G%v", string(b), which)
t.activeBuffer.charsets[which] = cs
return false
}
t.activeBuffer.charsets[which] = nil
return false
}

5
termutil/consts_unix.go Normal file
View File

@ -0,0 +1,5 @@
//+build !windows
package termutil
var oscTerminators = []rune{0x07, 0x5c}

View File

@ -0,0 +1,5 @@
//+build windows
package termutil
var oscTerminators = []rune{0x07, 0x00}

997
termutil/csi.go Normal file
View File

@ -0,0 +1,997 @@
package termutil
import (
"fmt"
"strconv"
"strings"
)
func parseCSI(readChan chan MeasuredRune) (final rune, params []string, intermediate []rune, raw []rune) {
var b MeasuredRune
param := ""
intermediate = []rune{}
CSI:
for {
b = <-readChan
raw = append(raw, b.Rune)
switch true {
case b.Rune >= 0x30 && b.Rune <= 0x3F:
param = param + string(b.Rune)
case b.Rune > 0 && b.Rune <= 0x2F:
intermediate = append(intermediate, b.Rune)
case b.Rune >= 0x40 && b.Rune <= 0x7e:
final = b.Rune
break CSI
}
}
unprocessed := strings.Split(param, ";")
for _, par := range unprocessed {
if par != "" {
par = strings.TrimLeft(par, "0")
if par == "" {
par = "0"
}
params = append(params, par)
}
}
return final, params, intermediate, raw
}
func (t *Terminal) handleCSI(readChan chan MeasuredRune) (renderRequired bool) {
final, params, intermediate, raw := parseCSI(readChan)
t.log("CSI P(%q) I(%q) %c", strings.Join(params, ";"), string(intermediate), final)
switch final {
case 'c':
return t.csiSendDeviceAttributesHandler(params)
case 'd':
return t.csiLinePositionAbsoluteHandler(params)
case 'f':
return t.csiCursorPositionHandler(params)
case 'g':
return t.csiTabClearHandler(params)
case 'h':
return t.csiSetModeHandler(params)
case 'l':
return t.csiResetModeHandler(params)
case 'm':
return t.sgrSequenceHandler(params)
case 'n':
return t.csiDeviceStatusReportHandler(params)
case 'r':
return t.csiSetMarginsHandler(params)
case 't':
return t.csiWindowManipulation(params)
case 'q':
if string(intermediate) == " " {
return t.csiCursorSelection(params)
}
case 'A':
return t.csiCursorUpHandler(params)
case 'B':
return t.csiCursorDownHandler(params)
case 'C':
return t.csiCursorForwardHandler(params)
case 'D':
return t.csiCursorBackwardHandler(params)
case 'E':
return t.csiCursorNextLineHandler(params)
case 'F':
return t.csiCursorPrecedingLineHandler(params)
case 'G':
return t.csiCursorCharacterAbsoluteHandler(params)
case 'H':
return t.csiCursorPositionHandler(params)
case 'J':
return t.csiEraseInDisplayHandler(params)
case 'K':
return t.csiEraseInLineHandler(params)
case 'L':
return t.csiInsertLinesHandler(params)
case 'M':
return t.csiDeleteLinesHandler(params)
case 'P':
return t.csiDeleteHandler(params)
case 'S':
return t.csiScrollUpHandler(params)
case 'T':
return t.csiScrollDownHandler(params)
case 'X':
return t.csiEraseCharactersHandler(params)
case '@':
return t.csiInsertBlankCharactersHandler(params)
case 'p': // reset handler
if string(intermediate) == "!" {
return t.csiSoftResetHandler(params)
}
return false
}
for _, b := range intermediate {
t.processRunes(MeasuredRune{
Rune: b,
Width: 1,
})
}
// TODO review this:
// if this is an unknown CSI sequence, write it to stdout as we can't handle it?
//_ = t.writeToRealStdOut(append([]rune{0x1b, '['}, raw...)...)
_ = raw
t.log("UNKNOWN CSI P(%s) I(%s) %c", strings.Join(params, ";"), string(intermediate), final)
return false
}
type WindowState uint8
const (
StateUnknown WindowState = iota
StateMinimised
StateNormal
StateMaximised
)
type WindowManipulator interface {
State() WindowState
Minimise()
Maximise()
Restore()
SetTitle(title string)
Position() (int, int)
SizeInPixels() (int, int)
CellSizeInPixels() (int, int)
SizeInChars() (int, int)
ResizeInPixels(int, int)
ResizeInChars(int, int)
ScreenSizeInPixels() (int, int)
ScreenSizeInChars() (int, int)
Move(x, y int)
IsFullscreen() bool
SetFullscreen(enabled bool)
GetTitle() string
SaveTitleToStack()
RestoreTitleFromStack()
ReportError(err error)
}
func (t *Terminal) csiWindowManipulation(params []string) (renderRequired bool) {
if t.windowManipulator == nil {
return false
}
for i := 0; i < len(params); i++ {
switch params[i] {
case "1":
t.windowManipulator.Restore()
case "2":
t.windowManipulator.Minimise()
case "3": //move window
if i+2 >= len(params) {
return false
}
x, _ := strconv.Atoi(params[i+1])
y, _ := strconv.Atoi(params[i+2])
i += 2
t.windowManipulator.Move(x, y)
case "4": //resize h,w
w, h := t.windowManipulator.SizeInPixels()
if i+1 < len(params) {
h, _ = strconv.Atoi(params[i+1])
i++
}
if i+2 < len(params) {
w, _ = strconv.Atoi(params[i+2])
i++
}
sw, sh := t.windowManipulator.ScreenSizeInPixels()
if w == 0 {
w = sw
}
if h == 0 {
h = sh
}
t.windowManipulator.ResizeInPixels(w, h)
case "8":
// resize in rows, cols
w, h := t.windowManipulator.SizeInChars()
if i+1 < len(params) {
h, _ = strconv.Atoi(params[i+1])
i++
}
if i+2 < len(params) {
w, _ = strconv.Atoi(params[i+2])
i++
}
sw, sh := t.windowManipulator.ScreenSizeInChars()
if w == 0 {
w = sw
}
if h == 0 {
h = sh
}
t.windowManipulator.ResizeInChars(w, h)
case "9":
if i+1 >= len(params) {
return false
}
switch params[i+1] {
case "0":
t.windowManipulator.Restore()
case "1":
t.windowManipulator.Maximise()
case "2":
w, _ := t.windowManipulator.SizeInPixels()
_, sh := t.windowManipulator.ScreenSizeInPixels()
t.windowManipulator.ResizeInPixels(w, sh)
case "3":
_, h := t.windowManipulator.SizeInPixels()
sw, _ := t.windowManipulator.ScreenSizeInPixels()
t.windowManipulator.ResizeInPixels(sw, h)
}
i++
case "10":
if i+1 >= len(params) {
return false
}
switch params[i+1] {
case "0":
t.windowManipulator.SetFullscreen(false)
case "1":
t.windowManipulator.SetFullscreen(true)
case "2":
// toggle
t.windowManipulator.SetFullscreen(!t.windowManipulator.IsFullscreen())
}
i++
case "11":
if t.windowManipulator.State() != StateMinimised {
t.WriteToPty([]byte("\x1b[1t"))
} else {
t.WriteToPty([]byte("\x1b[2t"))
}
case "13":
if i < len(params)-1 {
i++
}
x, y := t.windowManipulator.Position()
t.WriteToPty([]byte(fmt.Sprintf("\x1b[3;%d;%dt", x, y)))
case "14":
if i < len(params)-1 {
i++
}
w, h := t.windowManipulator.SizeInPixels()
t.WriteToPty([]byte(fmt.Sprintf("\x1b[4;%d;%dt", h, w)))
case "15":
w, h := t.windowManipulator.ScreenSizeInPixels()
t.WriteToPty([]byte(fmt.Sprintf("\x1b[5;%d;%dt", h, w)))
case "16":
w, h := t.windowManipulator.CellSizeInPixels()
t.WriteToPty([]byte(fmt.Sprintf("\x1b[6;%d;%dt", h, w)))
case "18":
w, h := t.windowManipulator.SizeInChars()
t.WriteToPty([]byte(fmt.Sprintf("\x1b[8;%d;%dt", h, w)))
case "19":
w, h := t.windowManipulator.ScreenSizeInChars()
t.WriteToPty([]byte(fmt.Sprintf("\x1b[9;%d;%dt", h, w)))
case "20":
t.WriteToPty([]byte(fmt.Sprintf("\x1b]L%s\x1b\\", t.windowManipulator.GetTitle())))
case "21":
t.WriteToPty([]byte(fmt.Sprintf("\x1b]l%s\x1b\\", t.windowManipulator.GetTitle())))
case "22":
if i < len(params)-1 {
i++
}
t.windowManipulator.SaveTitleToStack()
case "23":
if i < len(params)-1 {
i++
}
t.windowManipulator.RestoreTitleFromStack()
}
}
return true
}
// CSI c
// Send Device Attributes (Primary/Secondary/Tertiary DA)
func (t *Terminal) csiSendDeviceAttributesHandler(params []string) (renderRequired bool) {
// we are VT100
// for DA1 we'll respond ?1;2
// for DA2 we'll respond >0;0;0
response := "?1;2"
if len(params) > 0 && len(params[0]) > 0 && params[0][0] == '>' {
response = ">0;0;0"
}
// write response to source pty
t.WriteToPty([]byte("\x1b[" + response + "c"))
return false
}
// CSI n
// Device Status Report (DSR)
func (t *Terminal) csiDeviceStatusReportHandler(params []string) (renderRequired bool) {
if len(params) == 0 {
return false
}
switch params[0] {
case "5":
t.WriteToPty([]byte("\x1b[0n")) // everything is cool
case "6": // report cursor position
t.WriteToPty([]byte(fmt.Sprintf(
"\x1b[%d;%dR",
t.GetActiveBuffer().CursorLine()+1,
t.GetActiveBuffer().CursorColumn()+1,
)))
}
return false
}
// CSI A
// Cursor Up Ps Times (default = 1) (CUU)
func (t *Terminal) csiCursorUpHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 0 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().movePosition(0, -int16(distance))
return true
}
// CSI B
// Cursor Down Ps Times (default = 1) (CUD)
func (t *Terminal) csiCursorDownHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 0 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().movePosition(0, int16(distance))
return true
}
// CSI C
// Cursor Forward Ps Times (default = 1) (CUF)
func (t *Terminal) csiCursorForwardHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 0 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().movePosition(int16(distance), 0)
return true
}
// CSI D
// Cursor Backward Ps Times (default = 1) (CUB)
func (t *Terminal) csiCursorBackwardHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 0 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().movePosition(-int16(distance), 0)
return true
}
// CSI E
// Cursor Next Line Ps Times (default = 1) (CNL)
func (t *Terminal) csiCursorNextLineHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 0 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().movePosition(0, int16(distance))
t.GetActiveBuffer().setPosition(0, t.GetActiveBuffer().CursorLine())
return true
}
// CSI F
// Cursor Preceding Line Ps Times (default = 1) (CPL)
func (t *Terminal) csiCursorPrecedingLineHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 0 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().movePosition(0, -int16(distance))
t.GetActiveBuffer().setPosition(0, t.GetActiveBuffer().CursorLine())
return true
}
// CSI G
// Cursor Horizontal Absolute [column] (default = [row,1]) (CHA)
func (t *Terminal) csiCursorCharacterAbsoluteHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 0 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || params[0] == "" {
distance = 1
}
}
t.GetActiveBuffer().setPosition(uint16(distance-1), t.GetActiveBuffer().CursorLine())
return true
}
func parseCursorPosition(params []string) (x, y int) {
x, y = 1, 1
if len(params) >= 1 {
var err error
if params[0] != "" {
y, err = strconv.Atoi(string(params[0]))
if err != nil || y < 1 {
y = 1
}
}
}
if len(params) >= 2 {
if params[1] != "" {
var err error
x, err = strconv.Atoi(string(params[1]))
if err != nil || x < 1 {
x = 1
}
}
}
return x, y
}
// CSI f
// Horizontal and Vertical Position [row;column] (default = [1,1]) (HVP)
// AND
// CSI H
// Cursor Position [row;column] (default = [1,1]) (CUP)
func (t *Terminal) csiCursorPositionHandler(params []string) (renderRequired bool) {
x, y := parseCursorPosition(params)
t.GetActiveBuffer().setPosition(uint16(x-1), uint16(y-1))
return true
}
// CSI S
// Scroll up Ps lines (default = 1) (SU), VT420, ECMA-48
func (t *Terminal) csiScrollUpHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 1 {
return false
}
if len(params) == 1 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().areaScrollUp(uint16(distance))
return true
}
// CSI @
// Insert Ps (Blank) Character(s) (default = 1) (ICH)
func (t *Terminal) csiInsertBlankCharactersHandler(params []string) (renderRequired bool) {
count := 1
if len(params) > 1 {
return false
}
if len(params) == 1 {
var err error
count, err = strconv.Atoi(params[0])
if err != nil || count < 1 {
count = 1
}
}
t.GetActiveBuffer().insertBlankCharacters(count)
return true
}
// CSI L
// Insert Ps Line(s) (default = 1) (IL)
func (t *Terminal) csiInsertLinesHandler(params []string) (renderRequired bool) {
count := 1
if len(params) > 1 {
return false
}
if len(params) == 1 {
var err error
count, err = strconv.Atoi(params[0])
if err != nil || count < 1 {
count = 1
}
}
t.GetActiveBuffer().insertLines(count)
return true
}
// CSI M
// Delete Ps Line(s) (default = 1) (DL)
func (t *Terminal) csiDeleteLinesHandler(params []string) (renderRequired bool) {
count := 1
if len(params) > 1 {
return false
}
if len(params) == 1 {
var err error
count, err = strconv.Atoi(params[0])
if err != nil || count < 1 {
count = 1
}
}
t.GetActiveBuffer().deleteLines(count)
return true
}
// CSI T
// Scroll down Ps lines (default = 1) (SD), VT420
func (t *Terminal) csiScrollDownHandler(params []string) (renderRequired bool) {
distance := 1
if len(params) > 1 {
return false
}
if len(params) == 1 {
var err error
distance, err = strconv.Atoi(params[0])
if err != nil || distance < 1 {
distance = 1
}
}
t.GetActiveBuffer().areaScrollDown(uint16(distance))
return true
}
// CSI r
// Set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM), VT100
func (t *Terminal) csiSetMarginsHandler(params []string) (renderRequired bool) {
top := 1
bottom := int(t.GetActiveBuffer().ViewHeight())
if len(params) > 2 {
return false
}
if len(params) > 0 {
var err error
top, err = strconv.Atoi(params[0])
if err != nil || top < 1 {
top = 1
}
if len(params) > 1 {
var err error
bottom, err = strconv.Atoi(params[1])
if err != nil || bottom > int(t.GetActiveBuffer().ViewHeight()) || bottom < 1 {
bottom = int(t.GetActiveBuffer().ViewHeight())
}
}
}
top--
bottom--
t.activeBuffer.setVerticalMargins(uint(top), uint(bottom))
t.GetActiveBuffer().setPosition(0, 0)
return true
}
// CSI X
// Erase Ps Character(s) (default = 1) (ECH)
func (t *Terminal) csiEraseCharactersHandler(params []string) (renderRequired bool) {
count := 1
if len(params) > 0 {
var err error
count, err = strconv.Atoi(params[0])
if err != nil || count < 1 {
count = 1
}
}
t.GetActiveBuffer().eraseCharacters(count)
return true
}
// CSI l
// Reset Mode (RM)
func (t *Terminal) csiResetModeHandler(params []string) (renderRequired bool) {
return t.csiSetModes(params, false)
}
// CSI h
// Set Mode (SM)
func (t *Terminal) csiSetModeHandler(params []string) (renderRequired bool) {
return t.csiSetModes(params, true)
}
func (t *Terminal) csiSetModes(modes []string, enabled bool) bool {
if len(modes) == 0 {
return false
}
if len(modes) == 1 {
return t.csiSetMode(modes[0], enabled)
}
// should we propagate DEC prefix?
const decPrefix = '?'
isDec := len(modes[0]) > 0 && modes[0][0] == decPrefix
var render bool
// iterate through params, propagating DEC prefix to subsequent elements
for i, v := range modes {
updatedMode := v
if i > 0 && isDec {
updatedMode = string(decPrefix) + v
}
render = t.csiSetMode(updatedMode, enabled) || render
}
return render
}
func parseModes(mode string) []string {
var output []string
if mode == "" {
return nil
}
var prefix string
if mode[0] == '?' {
prefix = "?"
mode = mode[1:]
}
for len(mode) > 4 {
output = append(output, prefix+mode[:4])
mode = mode[4:]
}
output = append(output, prefix+mode)
return output
}
func (t *Terminal) csiSetMode(modes string, enabled bool) bool {
for _, modeStr := range parseModes(modes) {
switch modeStr {
case "4":
t.activeBuffer.modes.ReplaceMode = !enabled
case "20":
t.activeBuffer.modes.LineFeedMode = false
case "?1":
t.activeBuffer.modes.ApplicationCursorKeys = enabled
case "?3":
if t.windowManipulator != nil {
if enabled {
// DECCOLM - COLumn mode, 132 characters per line
t.windowManipulator.ResizeInChars(132, int(t.activeBuffer.viewHeight))
} else {
// DECCOLM - 80 characters per line (erases screen)
t.windowManipulator.ResizeInChars(80, int(t.activeBuffer.viewHeight))
}
t.activeBuffer.clear()
}
case "?5": // DECSCNM
t.activeBuffer.modes.ScreenMode = enabled
case "?6":
// DECOM
t.activeBuffer.modes.OriginMode = enabled
case "?7":
// auto-wrap mode
//DECAWM
t.activeBuffer.modes.AutoWrap = enabled
case "?9":
if enabled {
t.mouseMode = (MouseModeX10)
} else {
t.mouseMode = (MouseModeNone)
}
case "?12", "?13":
t.activeBuffer.modes.BlinkingCursor = enabled
case "?25":
t.activeBuffer.modes.ShowCursor = enabled
case "?47", "?1047":
if enabled {
t.useAltBuffer()
} else {
t.useMainBuffer()
}
case "?1000": // ?10061000 seen from htop
// enable mouse tracking
// 1000 refers to ext mode for extended mouse click area - otherwise only x <= 255-31
if enabled {
t.mouseMode = (MouseModeVT200)
} else {
t.mouseMode = (MouseModeNone)
}
case "?1002":
if enabled {
t.mouseMode = (MouseModeButtonEvent)
} else {
t.mouseMode = (MouseModeNone)
}
case "?1003":
if enabled {
t.mouseMode = MouseModeAnyEvent
} else {
t.mouseMode = MouseModeNone
}
case "?1005":
if enabled {
t.mouseExtMode = MouseExtUTF
} else {
t.mouseExtMode = MouseExtNone
}
case "?1006":
if enabled {
t.mouseExtMode = MouseExtSGR
} else {
t.mouseExtMode = (MouseExtNone)
}
case "?1015":
if enabled {
t.mouseExtMode = (MouseExtURXVT)
} else {
t.mouseExtMode = (MouseExtNone)
}
case "?1048":
if enabled {
t.GetActiveBuffer().saveCursor()
} else {
t.GetActiveBuffer().restoreCursor()
}
case "?1049":
if enabled {
t.useAltBuffer()
} else {
t.useMainBuffer()
}
case "?2004":
t.activeBuffer.modes.BracketedPasteMode = enabled
case "?80":
t.activeBuffer.modes.SixelScrolling = enabled
default:
t.log("Unsupported CSI mode %s = %t", modeStr, enabled)
}
}
return false
}
// CSI d
// Line Position Absolute [row] (default = [1,column]) (VPA)
func (t *Terminal) csiLinePositionAbsoluteHandler(params []string) (renderRequired bool) {
row := 1
if len(params) > 0 {
var err error
row, err = strconv.Atoi(params[0])
if err != nil || row < 1 {
row = 1
}
}
t.GetActiveBuffer().setPosition(t.GetActiveBuffer().CursorColumn(), uint16(row-1))
return true
}
// CSI P
// Delete Ps Character(s) (default = 1) (DCH)
func (t *Terminal) csiDeleteHandler(params []string) (renderRequired bool) {
n := 1
if len(params) >= 1 {
var err error
n, err = strconv.Atoi(params[0])
if err != nil || n < 1 {
n = 1
}
}
t.GetActiveBuffer().deleteChars(n)
return true
}
// CSI g
// tab clear (TBC)
func (t *Terminal) csiTabClearHandler(params []string) (renderRequired bool) {
n := "0"
if len(params) > 0 {
n = params[0]
}
switch n {
case "0", "":
t.activeBuffer.tabClearAtCursor()
case "3":
t.activeBuffer.tabReset()
default:
return false
}
return true
}
// CSI J
// Erase in Display (ED), VT100
func (t *Terminal) csiEraseInDisplayHandler(params []string) (renderRequired bool) {
n := "0"
if len(params) > 0 {
n = params[0]
}
switch n {
case "0", "":
t.GetActiveBuffer().eraseDisplayFromCursor()
case "1":
t.GetActiveBuffer().eraseDisplayToCursor()
case "2", "3":
t.GetActiveBuffer().eraseDisplay()
default:
return false
}
return true
}
// CSI K
// Erase in Line (EL), VT100
func (t *Terminal) csiEraseInLineHandler(params []string) (renderRequired bool) {
n := "0"
if len(params) > 0 {
n = params[0]
}
switch n {
case "0", "": //erase adter cursor
t.GetActiveBuffer().eraseLineFromCursor()
case "1": // erase to cursor inclusive
t.GetActiveBuffer().eraseLineToCursor()
case "2": // erase entire
t.GetActiveBuffer().eraseLine()
default:
return false
}
return true
}
// CSI m
// Character Attributes (SGR)
func (t *Terminal) sgrSequenceHandler(params []string) bool {
if len(params) == 0 {
params = []string{"0"}
}
for i := range params {
p := strings.Replace(strings.Replace(params[i], "[", "", -1), "]", "", -1)
switch p {
case "00", "0", "":
attr := t.GetActiveBuffer().getCursorAttr()
*attr = CellAttributes{}
case "1", "01":
t.GetActiveBuffer().getCursorAttr().bold = true
t.GetActiveBuffer().getCursorAttr().dim = false
case "2", "02":
t.GetActiveBuffer().getCursorAttr().bold = false
t.GetActiveBuffer().getCursorAttr().dim = true
case "3", "03":
t.GetActiveBuffer().getCursorAttr().italic = true
case "4", "04":
t.GetActiveBuffer().getCursorAttr().underline = true
case "5", "05":
t.GetActiveBuffer().getCursorAttr().blink = true
case "7", "07":
t.GetActiveBuffer().getCursorAttr().inverse = true
case "8", "08":
t.GetActiveBuffer().getCursorAttr().hidden = true
case "9", "09":
t.GetActiveBuffer().getCursorAttr().strikethrough = true
case "21":
t.GetActiveBuffer().getCursorAttr().bold = false
case "22":
t.GetActiveBuffer().getCursorAttr().dim = false
t.GetActiveBuffer().getCursorAttr().bold = false
case "23":
t.GetActiveBuffer().getCursorAttr().italic = false
case "24":
t.GetActiveBuffer().getCursorAttr().underline = false
case "25":
t.GetActiveBuffer().getCursorAttr().blink = false
case "27":
t.GetActiveBuffer().getCursorAttr().inverse = false
case "28":
t.GetActiveBuffer().getCursorAttr().hidden = false
case "29":
t.GetActiveBuffer().getCursorAttr().strikethrough = false
case "38": // set foreground
t.GetActiveBuffer().getCursorAttr().fgColour, _ = t.theme.ColourFromAnsi(params[i+1:], false)
return false
case "48": // set background
t.GetActiveBuffer().getCursorAttr().bgColour, _ = t.theme.ColourFromAnsi(params[i+1:], true)
return false
case "39":
t.GetActiveBuffer().getCursorAttr().fgColour = t.theme.DefaultForeground()
case "49":
t.GetActiveBuffer().getCursorAttr().bgColour = t.theme.DefaultBackground()
default:
bi, err := strconv.Atoi(p)
if err != nil {
return false
}
i := byte(bi)
switch true {
case i >= 30 && i <= 37, i >= 90 && i <= 97:
t.GetActiveBuffer().getCursorAttr().fgColour = t.theme.ColourFrom4Bit(i)
case i >= 40 && i <= 47, i >= 100 && i <= 107:
t.GetActiveBuffer().getCursorAttr().bgColour = t.theme.ColourFrom4Bit(i)
}
}
}
x := t.GetActiveBuffer().CursorColumn()
y := t.GetActiveBuffer().CursorLine()
if cell := t.GetActiveBuffer().GetCell(x, y); cell != nil {
cell.attr = t.GetActiveBuffer().cursorAttr
}
return false
}
func (t *Terminal) csiSoftResetHandler(params []string) bool {
t.reset()
return true
}
func (t *Terminal) csiCursorSelection(params []string) (renderRequired bool) {
if len(params) == 0 {
return false
}
i, err := strconv.Atoi(params[0])
if err != nil {
return false
}
t.GetActiveBuffer().SetCursorShape(CursorShape(i))
return true
}

66
termutil/line.go Normal file
View File

@ -0,0 +1,66 @@
package termutil
import "strings"
type Line struct {
wrapped bool // whether line was wrapped onto from the previous one
cells []Cell
}
func newLine() Line {
return Line{
wrapped: false,
cells: []Cell{},
}
}
func (line *Line) Len() uint16 {
return uint16(len(line.cells))
}
func (line *Line) String() string {
runes := []rune{}
for _, cell := range line.cells {
runes = append(runes, cell.r.Rune)
}
return strings.TrimRight(string(runes), "\x00")
}
func (line *Line) append(cells ...Cell) {
line.cells = append(line.cells, cells...)
}
func (line *Line) shrink(width uint16) {
if line.Len() <= width {
return
}
remove := line.Len() - width
var cells []Cell
for _, cell := range line.cells {
if cell.r.Rune == 0 && remove > 0 {
remove--
} else {
cells = append(cells, cell)
}
}
line.cells = cells
}
func (line *Line) wrap(width uint16) []Line {
var output []Line
var current Line
current.wrapped = line.wrapped
for _, cell := range line.cells {
if len(current.cells) == int(width) {
output = append(output, current)
current = newLine()
current.wrapped = true
}
current.cells = append(current.cells, cell)
}
return append(output, current)
}

View File

@ -1,4 +1,4 @@
package voidterm
package termutil
type MeasuredRune struct {
Rune rune

View File

@ -1,15 +1,15 @@
package voidterm
package termutil
type Modes struct {
ShowCursor bool
ApplicationCursorKeys bool
BlinkingCursor bool
ReplaceMode bool
OriginMode bool
ReplaceMode bool // overwrite character at cursor or insert new
OriginMode bool // see DECOM docs - whether cursor is positioned within the margins or not
LineFeedMode bool
ScreenMode bool
ScreenMode bool // DECSCNM (black on white background)
AutoWrap bool
SixelScrolling bool
SixelScrolling bool // DECSDM
BracketedPasteMode bool
}
@ -23,9 +23,6 @@ const (
MouseModeVT200Highlight
MouseModeButtonEvent
MouseModeAnyEvent
)
const (
MouseExtNone MouseExtMode = iota
MouseExtUTF
MouseExtSGR

41
termutil/options.go Normal file
View File

@ -0,0 +1,41 @@
package termutil
import (
"os"
)
type Option func(t *Terminal)
func WithLogFile(path string) Option {
return func(t *Terminal) {
if path == "-" {
t.logFile = os.Stdout
return
}
t.logFile, _ = os.Create(path)
}
}
func WithTheme(theme *Theme) Option {
return func(t *Terminal) {
t.theme = theme
}
}
func WithShell(shell string) Option {
return func(t *Terminal) {
t.shell = shell
}
}
func WithInitialCommand(cmd string) Option {
return func(t *Terminal) {
t.initialCommand = cmd + "\n"
}
}
func WithWindowManipulator(m WindowManipulator) Option {
return func(t *Terminal) {
t.windowManipulator = m
}
}

69
termutil/osc.go Normal file
View File

@ -0,0 +1,69 @@
package termutil
import (
"fmt"
)
func (t *Terminal) handleOSC(readChan chan MeasuredRune) (renderRequired bool) {
params := []string{}
param := ""
READ:
for {
select {
case b := <-readChan:
if t.isOSCTerminator(b.Rune) {
params = append(params, param)
break READ
}
if b.Rune == ';' {
params = append(params, param)
param = ""
continue
}
param = fmt.Sprintf("%s%c", param, b.Rune)
default:
return false
}
}
if len(params) == 0 {
return false
}
pT := params[len(params)-1]
pS := params[:len(params)-1]
if len(pS) == 0 {
pS = []string{pT}
pT = ""
}
switch pS[0] {
case "0", "2", "l":
t.setTitle(pT)
case "10": // get/set foreground colour
if len(pS) > 1 {
if pS[1] == "?" {
t.WriteToPty([]byte("\x1b]10;15"))
}
}
case "11": // get/set background colour
if len(pS) > 1 {
if pS[1] == "?" {
t.WriteToPty([]byte("\x1b]10;0"))
}
}
}
return false
}
func (t *Terminal) isOSCTerminator(r rune) bool {
for _, terminator := range oscTerminators {
if terminator == r {
return true
}
}
return false
}

93
termutil/resize.go Normal file
View File

@ -0,0 +1,93 @@
package termutil
func (buffer *Buffer) shrink(width uint16) {
var replace []Line
prevCursor := int(buffer.cursorPosition.Line)
for i, line := range buffer.lines {
line.shrink(width)
// this line fits within the new width restriction, keep it as is and continue
if line.Len() <= width {
replace = append(replace, line)
continue
}
wrappedLines := line.wrap(width)
if prevCursor >= i {
buffer.cursorPosition.Line += uint64(len(wrappedLines) - 1)
}
replace = append(replace, wrappedLines...)
}
buffer.cursorPosition.Col = buffer.cursorPosition.Col % width
buffer.lines = replace
}
func (buffer *Buffer) grow(width uint16) {
var replace []Line
var current Line
prevCursor := int(buffer.cursorPosition.Line)
for i, line := range buffer.lines {
if !line.wrapped {
if i > 0 {
replace = append(replace, current)
}
current = newLine()
}
if i == prevCursor {
buffer.cursorPosition.Line -= uint64(i - len(replace))
}
for _, cell := range line.cells {
if len(current.cells) == int(width) {
replace = append(replace, current)
current = newLine()
current.wrapped = true
}
current.cells = append(current.cells, cell)
}
}
replace = append(replace, current)
buffer.lines = replace
}
// deprecated
func (buffer *Buffer) resizeView(width uint16, height uint16) {
if buffer.viewHeight == 0 {
buffer.viewWidth = width
buffer.viewHeight = height
return
}
// scroll to bottom
buffer.scrollLinesFromBottom = 0
if width < buffer.viewWidth { // wrap lines if we're shrinking
buffer.shrink(width)
buffer.grow(width)
} else if width > buffer.viewWidth { // unwrap lines if we're growing
buffer.grow(width)
}
buffer.viewWidth = width
buffer.viewHeight = height
buffer.resetVerticalMargins(uint(buffer.viewHeight))
}

324
termutil/selection.go Normal file
View File

@ -0,0 +1,324 @@
package termutil
func (buffer *Buffer) ClearSelection() {
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
buffer.selectionStart = nil
buffer.selectionEnd = nil
}
func (buffer *Buffer) GetBoundedTextAtPosition(pos Position) (start Position, end Position, text string, textIndex int, found bool) {
return buffer.FindWordAt(pos, func(r rune) bool {
return r > 0 && r < 256
})
}
// if the selection is invalid - e.g. lines are selected that no longer exist in the buffer
func (buffer *Buffer) fixSelection() bool {
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
if buffer.selectionStart == nil || buffer.selectionEnd == nil {
return false
}
if buffer.selectionStart.Line >= uint64(len(buffer.lines)) {
buffer.selectionStart.Line = uint64(len(buffer.lines)) - 1
}
if buffer.selectionEnd.Line >= uint64(len(buffer.lines)) {
buffer.selectionEnd.Line = uint64(len(buffer.lines)) - 1
}
if buffer.selectionStart.Col >= uint16(len(buffer.lines[buffer.selectionStart.Line].cells)) {
buffer.selectionStart.Col = 0
if buffer.selectionStart.Line < uint64(len(buffer.lines))-1 {
buffer.selectionStart.Line++
}
}
if buffer.selectionEnd.Col >= uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) {
buffer.selectionEnd.Col = uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) - 1
}
return true
}
func (buffer *Buffer) ExtendSelectionToEntireLines() {
if !buffer.fixSelection() {
return
}
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
buffer.selectionStart.Col = 0
buffer.selectionEnd.Col = uint16(len(buffer.lines[buffer.selectionEnd.Line].cells)) - 1
}
type RuneMatcher func(r rune) bool
func (buffer *Buffer) SelectWordAt(pos Position, runeMatcher RuneMatcher) {
start, end, _, _, found := buffer.FindWordAt(pos, runeMatcher)
if !found {
return
}
buffer.setRawSelectionStart(start)
buffer.setRawSelectionEnd(end)
}
// takes raw coords
func (buffer *Buffer) Highlight(start Position, end Position, annotation *Annotation) {
buffer.highlightStart = &start
buffer.highlightEnd = &end
buffer.highlightAnnotation = annotation
}
func (buffer *Buffer) ClearHighlight() {
buffer.highlightStart = nil
buffer.highlightEnd = nil
}
// returns raw lines
func (buffer *Buffer) FindWordAt(pos Position, runeMatcher RuneMatcher) (start Position, end Position, text string, textIndex int, found bool) {
line := buffer.convertViewLineToRawLine(uint16(pos.Line))
col := pos.Col
if line >= uint64(len(buffer.lines)) {
return
}
if col >= uint16(len(buffer.lines[line].cells)) {
return
}
if !runeMatcher(buffer.lines[line].cells[col].r.Rune) {
return
}
found = true
start = Position{
Line: line,
Col: col,
}
end = Position{
Line: line,
Col: col,
}
var startCol uint16
BACK:
for y := int(line); y >= 0; y-- {
if y == int(line) {
startCol = col
} else {
if len(buffer.lines[y].cells) < int(buffer.viewWidth) {
break
}
startCol = uint16(len(buffer.lines[y].cells) - 1)
}
for x := int(startCol); x >= 0; x-- {
if runeMatcher(buffer.lines[y].cells[x].r.Rune) {
start = Position{
Line: uint64(y),
Col: uint16(x),
}
text = string(buffer.lines[y].cells[x].r.Rune) + text
} else {
break BACK
}
}
}
textIndex = len([]rune(text)) - 1
FORWARD:
for y := uint64(line); y < uint64(len(buffer.lines)); y++ {
if y == line {
startCol = col + 1
} else {
startCol = 0
}
for x := int(startCol); x < len(buffer.lines[y].cells); x++ {
if runeMatcher(buffer.lines[y].cells[x].r.Rune) {
end = Position{
Line: y,
Col: uint16(x),
}
text = text + string(buffer.lines[y].cells[x].r.Rune)
} else {
break FORWARD
}
}
if len(buffer.lines[y].cells) < int(buffer.viewWidth) {
break
}
}
return
}
func (buffer *Buffer) SetSelectionStart(pos Position) {
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
buffer.selectionStart = &Position{
Col: pos.Col,
Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
}
}
func (buffer *Buffer) setRawSelectionStart(pos Position) {
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
buffer.selectionStart = &pos
}
func (buffer *Buffer) SetSelectionEnd(pos Position) {
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
buffer.selectionEnd = &Position{
Col: pos.Col,
Line: buffer.convertViewLineToRawLine(uint16(pos.Line)),
}
}
func (buffer *Buffer) setRawSelectionEnd(pos Position) {
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
buffer.selectionEnd = &pos
}
func (buffer *Buffer) GetSelection() (string, *Selection) {
if !buffer.fixSelection() {
return "", nil
}
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
start := *buffer.selectionStart
end := *buffer.selectionEnd
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
swap := end
end = start
start = swap
}
var text string
for y := start.Line; y <= end.Line; y++ {
if y >= uint64(len(buffer.lines)) {
break
}
line := buffer.lines[y]
startX := 0
endX := len(line.cells) - 1
if y == start.Line {
startX = int(start.Col)
}
if y == end.Line {
endX = int(end.Col)
}
if y > start.Line {
text += "\n"
}
for x := startX; x <= endX; x++ {
if x >= len(line.cells) {
break
}
mr := line.cells[x].Rune()
if mr.Width == 0 {
continue
}
x += mr.Width - 1
text += string(mr.Rune)
}
}
viewSelection := Selection{
Start: start,
End: end,
}
viewSelection.Start.Line = uint64(buffer.convertRawLineToViewLine(viewSelection.Start.Line))
viewSelection.End.Line = uint64(buffer.convertRawLineToViewLine(viewSelection.End.Line))
return text, &viewSelection
}
func (buffer *Buffer) InSelection(pos Position) bool {
if !buffer.fixSelection() {
return false
}
buffer.selectionMu.Lock()
defer buffer.selectionMu.Unlock()
start := *buffer.selectionStart
end := *buffer.selectionEnd
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
swap := end
end = start
start = swap
}
rY := buffer.convertViewLineToRawLine(uint16(pos.Line))
if rY < start.Line {
return false
}
if rY > end.Line {
return false
}
if rY == start.Line {
if pos.Col < start.Col {
return false
}
}
if rY == end.Line {
if pos.Col > end.Col {
return false
}
}
return true
}
func (buffer *Buffer) GetHighlightAnnotation() *Annotation {
return buffer.highlightAnnotation
}
func (buffer *Buffer) GetViewHighlight() (start Position, end Position, exists bool) {
if buffer.highlightStart == nil || buffer.highlightEnd == nil {
return
}
if buffer.highlightStart.Line >= uint64(len(buffer.lines)) {
return
}
if buffer.highlightEnd.Line >= uint64(len(buffer.lines)) {
return
}
if buffer.highlightStart.Col >= uint16(len(buffer.lines[buffer.highlightStart.Line].cells)) {
return
}
if buffer.highlightEnd.Col >= uint16(len(buffer.lines[buffer.highlightEnd.Line].cells)) {
return
}
start = *buffer.highlightStart
end = *buffer.highlightEnd
if end.Line < start.Line || (end.Line == start.Line && end.Col < start.Col) {
swap := end
end = start
start = swap
}
start.Line = uint64(buffer.convertRawLineToViewLine(start.Line))
end.Line = uint64(buffer.convertRawLineToViewLine(end.Line))
return start, end, true
}

106
termutil/sixel.go Normal file
View File

@ -0,0 +1,106 @@
package termutil
import (
"image"
"math"
"strings"
"github.com/liamg/darktile/internal/app/darktile/sixel"
)
type Sixel struct {
X uint16
Y uint64 // raw line
Width uint64
Height uint64
Image image.Image
}
type VisibleSixel struct {
ViewLineOffset int
Sixel Sixel
}
func (b *Buffer) addSixel(img image.Image, widthCells int, heightCells int) {
b.sixels = append(b.sixels, Sixel{
X: b.CursorColumn(),
Y: b.cursorPosition.Line,
Width: uint64(widthCells),
Height: uint64(heightCells),
Image: img,
})
if b.modes.SixelScrolling {
b.cursorPosition.Line += uint64(heightCells)
}
}
func (b *Buffer) clearSixelsAtRawLine(rawLine uint64) {
var filtered []Sixel
for _, sixelImage := range b.sixels {
if sixelImage.Y+sixelImage.Height-1 >= rawLine && sixelImage.Y <= rawLine {
continue
}
filtered = append(filtered, sixelImage)
}
b.sixels = filtered
}
func (b *Buffer) GetVisibleSixels() []VisibleSixel {
firstLine := b.convertViewLineToRawLine(0)
lastLine := b.convertViewLineToRawLine(b.viewHeight - 1)
var visible []VisibleSixel
for _, sixelImage := range b.sixels {
if sixelImage.Y+sixelImage.Height-1 < firstLine {
continue
}
if sixelImage.Y > lastLine {
continue
}
visible = append(visible, VisibleSixel{
ViewLineOffset: int(sixelImage.Y) - int(firstLine),
Sixel: sixelImage,
})
}
return visible
}
func (t *Terminal) handleSixel(readChan chan MeasuredRune) (renderRequired bool) {
var data []rune
var inEscape bool
for {
r := <-readChan
switch r.Rune {
case 0x1b:
inEscape = true
continue
case 0x5c:
if inEscape {
img, err := sixel.Decode(strings.NewReader(string(data)), t.theme.DefaultBackground())
if err != nil {
return false
}
w, h := t.windowManipulator.CellSizeInPixels()
cw := int(math.Ceil(float64(img.Bounds().Dx()) / float64(w)))
ch := int(math.Ceil(float64(img.Bounds().Dy()) / float64(h)))
t.activeBuffer.addSixel(img, cw, ch)
return true
}
}
inEscape = false
data = append(data, r.Rune)
}
}

323
termutil/terminal.go Normal file
View File

@ -0,0 +1,323 @@
package termutil
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"sync"
"github.com/creack/pty"
"golang.org/x/term"
)
const (
MainBuffer uint8 = 0
AltBuffer uint8 = 1
InternalBuffer uint8 = 2
)
// Terminal communicates with the underlying terminal
type Terminal struct {
mu sync.Mutex
windowManipulator WindowManipulator
pty *os.File
updateChan chan struct{}
processChan chan MeasuredRune
closeChan chan struct{}
buffers []*Buffer
activeBuffer *Buffer
mouseMode MouseMode
mouseExtMode MouseExtMode
logFile *os.File
theme *Theme
running bool
shell string
initialCommand string
}
// NewTerminal creates a new terminal instance
func New(options ...Option) *Terminal {
term := &Terminal{
processChan: make(chan MeasuredRune, 0xffff),
closeChan: make(chan struct{}),
theme: &Theme{},
}
for _, opt := range options {
opt(term)
}
fg := term.theme.DefaultForeground()
bg := term.theme.DefaultBackground()
term.buffers = []*Buffer{
NewBuffer(1, 1, 0xffff, fg, bg),
NewBuffer(1, 1, 0xffff, fg, bg),
NewBuffer(1, 1, 0xffff, fg, bg),
}
term.activeBuffer = term.buffers[0]
return term
}
func (t *Terminal) SetWindowManipulator(m WindowManipulator) {
t.windowManipulator = m
}
func (t *Terminal) log(line string, params ...interface{}) {
if t.logFile != nil {
_, _ = fmt.Fprintf(t.logFile, line+"\n", params...)
}
}
func (t *Terminal) reset() {
fg := t.theme.DefaultForeground()
bg := t.theme.DefaultBackground()
t.buffers = []*Buffer{
NewBuffer(1, 1, 0xffff, fg, bg),
NewBuffer(1, 1, 0xffff, fg, bg),
NewBuffer(1, 1, 0xffff, fg, bg),
}
t.useMainBuffer()
}
// Pty exposes the underlying terminal pty, if it exists
func (t *Terminal) Pty() *os.File {
return t.pty
}
func (t *Terminal) WriteToPty(data []byte) error {
_, err := t.pty.Write(data)
return err
}
func (t *Terminal) GetTitle() string {
return t.windowManipulator.GetTitle()
}
func (t *Terminal) Theme() *Theme {
return t.theme
}
// write takes data from StdOut of the child shell and processes it
func (t *Terminal) Write(data []byte) (n int, err error) {
reader := bufio.NewReader(bytes.NewBuffer(data))
for {
r, size, err := reader.ReadRune()
if err == io.EOF {
break
}
t.processChan <- MeasuredRune{Rune: r, Width: size}
}
return len(data), nil
}
func (t *Terminal) SetSize(rows, cols uint16) error {
if t.pty == nil {
return fmt.Errorf("terminal is not running")
}
t.log("RESIZE %d, %d\n", cols, rows)
t.activeBuffer.resizeView(cols, rows)
if err := pty.Setsize(t.pty, &pty.Winsize{
Rows: rows,
Cols: cols,
}); err != nil {
return err
}
return nil
}
// Run starts the terminal/shell proxying process
func (t *Terminal) Run(updateChan chan struct{}, rows uint16, cols uint16) error {
os.Setenv("TERM", "xterm-256color")
t.updateChan = updateChan
if t.shell == "" {
t.shell = os.Getenv("SHELL")
if t.shell == "" {
t.shell = "/bin/sh"
}
}
// Create arbitrary command.
c := exec.Command(t.shell)
// Start the command with a pty.
var err error
t.pty, err = pty.Start(c)
if err != nil {
return err
}
// Make sure to close the pty at the end.
defer func() { _ = t.pty.Close() }() // Best effort.
if err := t.SetSize(rows, cols); err != nil {
return err
}
// Set stdin in raw mode.
if fd := int(os.Stdin.Fd()); term.IsTerminal(fd) {
oldState, err := term.MakeRaw(fd)
if err != nil {
t.windowManipulator.ReportError(err)
}
defer func() { _ = term.Restore(fd, oldState) }() // Best effort.
}
go t.process()
t.running = true
t.windowManipulator.SetTitle("darktile")
if t.initialCommand != "" {
if err := t.WriteToPty([]byte(t.initialCommand)); err != nil {
return err
}
}
_, _ = io.Copy(t, t.pty)
close(t.closeChan)
return nil
}
func (t *Terminal) IsRunning() bool {
return t.running
}
func (t *Terminal) requestRender() {
select {
case t.updateChan <- struct{}{}:
default:
}
}
func (t *Terminal) processSequence(mr MeasuredRune) (render bool) {
if mr.Rune == 0x1b {
return t.handleANSI(t.processChan)
}
return t.processRunes(mr)
}
func (t *Terminal) process() {
for {
select {
case <-t.closeChan:
return
case mr := <-t.processChan:
if t.processSequence(mr) {
t.requestRender()
}
}
}
}
func (t *Terminal) processRunes(runes ...MeasuredRune) (renderRequired bool) {
t.mu.Lock()
defer t.mu.Unlock()
for _, r := range runes {
t.log("%c 0x%X", r.Rune, r.Rune)
switch r.Rune {
case 0x05: //enq
continue
case 0x07: //bell
//DING DING DING
continue
case 0x8: //backspace
t.activeBuffer.backspace()
renderRequired = true
case 0x9: //tab
t.activeBuffer.tab()
renderRequired = true
case 0xa, 0xc: //newLine/form feed
t.activeBuffer.newLine()
renderRequired = true
case 0xb: //vertical tab
t.activeBuffer.verticalTab()
renderRequired = true
case 0xd: //carriageReturn
t.activeBuffer.carriageReturn()
renderRequired = true
case 0xe: //shiftOut
t.activeBuffer.currentCharset = 1
case 0xf: //shiftIn
t.activeBuffer.currentCharset = 0
default:
if r.Rune < 0x20 {
// handle any other control chars here?
continue
}
t.activeBuffer.write(t.translateRune(r))
renderRequired = true
}
}
return renderRequired
}
func (t *Terminal) translateRune(b MeasuredRune) MeasuredRune {
table := t.activeBuffer.charsets[t.activeBuffer.currentCharset]
if table == nil {
return b
}
chr, ok := (*table)[b.Rune]
if ok {
return MeasuredRune{Rune: chr, Width: 1}
}
return b
}
func (t *Terminal) setTitle(title string) {
t.windowManipulator.SetTitle(title)
}
func (t *Terminal) switchBuffer(index uint8) {
var carrySize bool
var w, h uint16
if t.activeBuffer != nil {
w, h = t.activeBuffer.viewWidth, t.activeBuffer.viewHeight
carrySize = true
}
t.activeBuffer = t.buffers[index]
if carrySize {
t.activeBuffer.resizeView(w, h)
}
}
func (t *Terminal) GetMouseMode() MouseMode {
return t.mouseMode
}
func (t *Terminal) GetMouseExtMode() MouseExtMode {
return t.mouseExtMode
}
func (t *Terminal) GetActiveBuffer() *Buffer {
return t.activeBuffer
}
func (t *Terminal) useMainBuffer() {
t.switchBuffer(MainBuffer)
}
func (t *Terminal) useAltBuffer() {
t.switchBuffer(AltBuffer)
}
func (t *Terminal) Lock() {
t.mu.Lock()
}
func (t *Terminal) Unlock() {
t.mu.Unlock()
}

214
termutil/theme.go Normal file
View File

@ -0,0 +1,214 @@
package termutil
import (
"fmt"
"image/color"
"strconv"
)
type Colour uint8
// See https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
const (
ColourBlack Colour = iota
ColourRed
ColourGreen
ColourYellow
ColourBlue
ColourMagenta
ColourCyan
ColourWhite
ColourBrightBlack
ColourBrightRed
ColourBrightGreen
ColourBrightYellow
ColourBrightBlue
ColourBrightMagenta
ColourBrightCyan
ColourBrightWhite
ColourBackground
ColourForeground
ColourSelectionBackground
ColourSelectionForeground
ColourCursorForeground
ColourCursorBackground
)
type Theme struct {
colourMap map[Colour]color.Color
}
var (
map4Bit = map[uint8]Colour{
30: ColourBlack,
31: ColourRed,
32: ColourGreen,
33: ColourYellow,
34: ColourBlue,
35: ColourMagenta,
36: ColourCyan,
37: ColourWhite,
90: ColourBrightBlack,
91: ColourBrightRed,
92: ColourBrightGreen,
93: ColourBrightYellow,
94: ColourBrightBlue,
95: ColourBrightMagenta,
96: ColourBrightCyan,
97: ColourBrightWhite,
40: ColourBlack,
41: ColourRed,
42: ColourGreen,
43: ColourYellow,
44: ColourBlue,
45: ColourMagenta,
46: ColourCyan,
47: ColourWhite,
100: ColourBrightBlack,
101: ColourBrightRed,
102: ColourBrightGreen,
103: ColourBrightYellow,
104: ColourBrightBlue,
105: ColourBrightMagenta,
106: ColourBrightCyan,
107: ColourBrightWhite,
}
)
func (t *Theme) ColourFrom4Bit(code uint8) color.Color {
colour, ok := map4Bit[code]
if !ok {
return color.Black
}
return t.colourMap[colour]
}
func (t *Theme) DefaultBackground() color.Color {
c, ok := t.colourMap[ColourBackground]
if !ok {
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
func (t *Theme) DefaultForeground() color.Color {
c, ok := t.colourMap[ColourForeground]
if !ok {
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
func (t *Theme) SelectionBackground() color.Color {
c, ok := t.colourMap[ColourSelectionBackground]
if !ok {
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
func (t *Theme) SelectionForeground() color.Color {
c, ok := t.colourMap[ColourSelectionForeground]
if !ok {
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
func (t *Theme) CursorBackground() color.Color {
c, ok := t.colourMap[ColourCursorBackground]
if !ok {
return color.RGBA{255, 255, 255, 0xff}
}
return c
}
func (t *Theme) CursorForeground() color.Color {
c, ok := t.colourMap[ColourCursorForeground]
if !ok {
return color.RGBA{0, 0, 0, 0xff}
}
return c
}
func (t *Theme) ColourFrom8Bit(n string) (color.Color, error) {
index, err := strconv.Atoi(n)
if err != nil {
return nil, err
}
if index < 16 {
return t.colourMap[Colour(index)], nil
}
if index >= 232 {
c := ((index - 232) * 0xff) / 0x18
return color.RGBA{
R: byte(c),
G: byte(c),
B: byte(c),
A: 0xff,
}, nil
}
var colour color.RGBA
colour.A = 0xff
indexR := ((index - 16) / 36)
if indexR > 0 {
colour.R = uint8(55 + indexR*40)
}
indexG := (((index - 16) % 36) / 6)
if indexG > 0 {
colour.G = uint8(55 + indexG*40)
}
indexB := ((index - 16) % 6)
if indexB > 0 {
colour.B = uint8(55 + indexB*40)
}
return colour, nil
}
func (t *Theme) ColourFrom24Bit(r, g, b string) (color.Color, error) {
ri, err := strconv.Atoi(r)
if err != nil {
return nil, err
}
gi, err := strconv.Atoi(g)
if err != nil {
return nil, err
}
bi, err := strconv.Atoi(b)
if err != nil {
return nil, err
}
return color.RGBA{
R: byte(ri),
G: byte(gi),
B: byte(bi),
A: 0xff,
}, nil
}
func (t *Theme) ColourFromAnsi(ansi []string, bg bool) (color.Color, error) {
if len(ansi) == 0 {
return nil, fmt.Errorf("invalid ansi colour code")
}
switch ansi[0] {
case "2":
if len(ansi) != 4 {
return nil, fmt.Errorf("invalid 24-bit ansi colour code")
}
return t.ColourFrom24Bit(ansi[1], ansi[2], ansi[3])
case "5":
if len(ansi) != 2 {
return nil, fmt.Errorf("invalid 8-bit ansi colour code")
}
return t.ColourFrom8Bit(ansi[1])
default:
return nil, fmt.Errorf("invalid ansi colour code")
}
}

35
termutil/theme_factory.go Normal file
View File

@ -0,0 +1,35 @@
package termutil
import "image/color"
type ThemeFactory struct {
theme *Theme
colourMap map[Colour]color.Color
}
func NewThemeFactory() *ThemeFactory {
return &ThemeFactory{
theme: &Theme{
colourMap: map[Colour]color.Color{},
},
colourMap: make(map[Colour]color.Color),
}
}
func (t *ThemeFactory) Build() *Theme {
for id, col := range t.colourMap {
r, g, b, _ := col.RGBA()
t.theme.colourMap[id] = color.RGBA{
R: uint8(r / 0xff),
G: uint8(g / 0xff),
B: uint8(b / 0xff),
A: 0xff,
}
}
return t.theme
}
func (t *ThemeFactory) WithColour(key Colour, colour color.Color) *ThemeFactory {
t.colourMap[key] = colour
return t
}

View File

@ -1,27 +1,18 @@
package voidterm
import (
"os"
"sync"
)
const (
MainBuffer uint8 = 0
AltBuffer uint8 = 1
InternalBuffer uint8 = 2
"github.com/1f349/voidterm/termutil"
)
type VoidTerm struct {
mu sync.Mutex
pty *os.File
updateChan chan struct{}
processChan chan MeasuredRune
closeChan chan struct{}
buffers []*Buffer
activeBuffer *Buffer
mouseMode MouseMode
mouseExtMode MouseExtMode
running bool
shell string
initialCommand string
term *termutil.Terminal
}
func New(dockerContainer, shell string) *VoidTerm {
term := termutil.New(termutil.WithShell(shell))
return &VoidTerm{term: term}
}
func (v *VoidTerm) Run() {
v.term.Run()
}