diff --git a/buffer.go b/buffer.go deleted file mode 100644 index 177e035..0000000 --- a/buffer.go +++ /dev/null @@ -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 -} diff --git a/cell.go b/cell.go deleted file mode 100644 index e8e26ba..0000000 --- a/cell.go +++ /dev/null @@ -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 -} diff --git a/cmd/voidterm/main.go b/cmd/voidterm/main.go new file mode 100644 index 0000000..e8e7006 --- /dev/null +++ b/cmd/voidterm/main.go @@ -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) + } +} diff --git a/go.mod b/go.mod index ef588de..9375b07 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index fa4b6e6..8533e0e 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/line.go b/line.go deleted file mode 100644 index 488f110..0000000 --- a/line.go +++ /dev/null @@ -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 diff --git a/line_test.go b/line_test.go deleted file mode 100644 index 9b56511..0000000 --- a/line_test.go +++ /dev/null @@ -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) - }) -} diff --git a/position.go b/position.go deleted file mode 100644 index 76f8766..0000000 --- a/position.go +++ /dev/null @@ -1,5 +0,0 @@ -package voidterm - -type Position struct { - X, Y int -} diff --git a/termutil/ansi.go b/termutil/ansi.go new file mode 100644 index 0000000..421e4dd --- /dev/null +++ b/termutil/ansi.go @@ -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 +} diff --git a/termutil/buffer.go b/termutil/buffer.go new file mode 100644 index 0000000..7c7ed6d --- /dev/null +++ b/termutil/buffer.go @@ -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 + } +} diff --git a/termutil/buffer_test.go b/termutil/buffer_test.go new file mode 100644 index 0000000..fa7a2ad --- /dev/null +++ b/termutil/buffer_test.go @@ -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) +} diff --git a/termutil/cell.go b/termutil/cell.go new file mode 100644 index 0000000..edfb7c9 --- /dev/null +++ b/termutil/cell.go @@ -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 +} diff --git a/cell-attributes.go b/termutil/cell_attributes.go similarity index 75% rename from cell-attributes.go rename to termutil/cell_attributes.go index 59d7b15..c623624 100644 --- a/cell-attributes.go +++ b/termutil/cell_attributes.go @@ -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 diff --git a/termutil/charsets.go b/termutil/charsets.go new file mode 100644 index 0000000..7e2d17a --- /dev/null +++ b/termutil/charsets.go @@ -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 +} diff --git a/termutil/consts_unix.go b/termutil/consts_unix.go new file mode 100644 index 0000000..58537ee --- /dev/null +++ b/termutil/consts_unix.go @@ -0,0 +1,5 @@ +//+build !windows + +package termutil + +var oscTerminators = []rune{0x07, 0x5c} diff --git a/termutil/consts_windows.go b/termutil/consts_windows.go new file mode 100644 index 0000000..f965dd6 --- /dev/null +++ b/termutil/consts_windows.go @@ -0,0 +1,5 @@ +//+build windows + +package termutil + +var oscTerminators = []rune{0x07, 0x00} diff --git a/termutil/csi.go b/termutil/csi.go new file mode 100644 index 0000000..aa5569c --- /dev/null +++ b/termutil/csi.go @@ -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 +} diff --git a/termutil/line.go b/termutil/line.go new file mode 100644 index 0000000..0b76251 --- /dev/null +++ b/termutil/line.go @@ -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) +} diff --git a/measured-rune.go b/termutil/measured-rune.go similarity index 75% rename from measured-rune.go rename to termutil/measured-rune.go index 924b620..3d86f7a 100644 --- a/measured-rune.go +++ b/termutil/measured-rune.go @@ -1,4 +1,4 @@ -package voidterm +package termutil type MeasuredRune struct { Rune rune diff --git a/modes.go b/termutil/modes.go similarity index 59% rename from modes.go rename to termutil/modes.go index 6be1e81..5ded93b 100644 --- a/modes.go +++ b/termutil/modes.go @@ -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 diff --git a/termutil/options.go b/termutil/options.go new file mode 100644 index 0000000..501e1a3 --- /dev/null +++ b/termutil/options.go @@ -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 + } +} diff --git a/termutil/osc.go b/termutil/osc.go new file mode 100644 index 0000000..af199d1 --- /dev/null +++ b/termutil/osc.go @@ -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 +} diff --git a/termutil/resize.go b/termutil/resize.go new file mode 100644 index 0000000..5be4bb2 --- /dev/null +++ b/termutil/resize.go @@ -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)) +} diff --git a/termutil/selection.go b/termutil/selection.go new file mode 100644 index 0000000..c4acbae --- /dev/null +++ b/termutil/selection.go @@ -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 +} diff --git a/termutil/sixel.go b/termutil/sixel.go new file mode 100644 index 0000000..0e03f3b --- /dev/null +++ b/termutil/sixel.go @@ -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) + } +} diff --git a/termutil/terminal.go b/termutil/terminal.go new file mode 100644 index 0000000..9d9ca1c --- /dev/null +++ b/termutil/terminal.go @@ -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() +} diff --git a/termutil/theme.go b/termutil/theme.go new file mode 100644 index 0000000..b2f515a --- /dev/null +++ b/termutil/theme.go @@ -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") + } +} diff --git a/termutil/theme_factory.go b/termutil/theme_factory.go new file mode 100644 index 0000000..5f3bb57 --- /dev/null +++ b/termutil/theme_factory.go @@ -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 +} diff --git a/voidterm.go b/voidterm.go index 3ae8f4c..8a17b25 100644 --- a/voidterm.go +++ b/voidterm.go @@ -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() }