diff --git a/cmd/src/batch_apply.go b/cmd/src/batch_apply.go index 196660d628..2b1270526b 100644 --- a/cmd/src/batch_apply.go +++ b/cmd/src/batch_apply.go @@ -6,7 +6,7 @@ import ( "flag" "fmt" - "github.com/sourcegraph/src-cli/internal/output" + "github.com/sourcegraph/sourcegraph/lib/output" ) func init() { diff --git a/cmd/src/batch_common.go b/cmd/src/batch_common.go index 6ad35a2918..653238fc5f 100644 --- a/cmd/src/batch_common.go +++ b/cmd/src/batch_common.go @@ -16,13 +16,13 @@ import ( "github.com/hashicorp/go-multierror" "github.com/neelance/parallel" "github.com/pkg/errors" + "github.com/sourcegraph/sourcegraph/lib/output" "github.com/sourcegraph/src-cli/internal/api" "github.com/sourcegraph/src-cli/internal/batches" "github.com/sourcegraph/src-cli/internal/batches/executor" "github.com/sourcegraph/src-cli/internal/batches/graphql" "github.com/sourcegraph/src-cli/internal/batches/service" "github.com/sourcegraph/src-cli/internal/batches/workspace" - "github.com/sourcegraph/src-cli/internal/output" ) var ( diff --git a/cmd/src/batch_preview.go b/cmd/src/batch_preview.go index dfdf0921f3..4b810c7fc0 100644 --- a/cmd/src/batch_preview.go +++ b/cmd/src/batch_preview.go @@ -6,7 +6,7 @@ import ( "flag" "fmt" - "github.com/sourcegraph/src-cli/internal/output" + "github.com/sourcegraph/sourcegraph/lib/output" ) func init() { diff --git a/cmd/src/batch_progress_printer.go b/cmd/src/batch_progress_printer.go index c4fc22cde6..0d46d28744 100644 --- a/cmd/src/batch_progress_printer.go +++ b/cmd/src/batch_progress_printer.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/sourcegraph/go-diff/diff" + "github.com/sourcegraph/sourcegraph/lib/output" "github.com/sourcegraph/src-cli/internal/batches/executor" - "github.com/sourcegraph/src-cli/internal/output" "golang.org/x/sync/semaphore" ) diff --git a/cmd/src/batch_progress_printer_test.go b/cmd/src/batch_progress_printer_test.go index 09ee75709d..fcd4d55317 100644 --- a/cmd/src/batch_progress_printer_test.go +++ b/cmd/src/batch_progress_printer_test.go @@ -7,9 +7,9 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/sourcegraph/sourcegraph/lib/output" "github.com/sourcegraph/src-cli/internal/batches" "github.com/sourcegraph/src-cli/internal/batches/executor" - "github.com/sourcegraph/src-cli/internal/output" ) const progressPrinterDiff = `diff --git README.md README.md diff --git a/cmd/src/batch_repositories.go b/cmd/src/batch_repositories.go index 7884b33739..5dcda4cffc 100644 --- a/cmd/src/batch_repositories.go +++ b/cmd/src/batch_repositories.go @@ -6,10 +6,10 @@ import ( "fmt" "github.com/pkg/errors" + "github.com/sourcegraph/sourcegraph/lib/output" "github.com/sourcegraph/src-cli/internal/api" "github.com/sourcegraph/src-cli/internal/batches/graphql" "github.com/sourcegraph/src-cli/internal/batches/service" - "github.com/sourcegraph/src-cli/internal/output" ) func init() { diff --git a/cmd/src/batch_validate.go b/cmd/src/batch_validate.go index d586e44e99..70c9620637 100644 --- a/cmd/src/batch_validate.go +++ b/cmd/src/batch_validate.go @@ -5,8 +5,8 @@ import ( "flag" "fmt" + "github.com/sourcegraph/sourcegraph/lib/output" "github.com/sourcegraph/src-cli/internal/batches/service" - "github.com/sourcegraph/src-cli/internal/output" ) func init() { diff --git a/cmd/src/doc.go b/cmd/src/doc.go index 7da262c736..fc42819963 100644 --- a/cmd/src/doc.go +++ b/cmd/src/doc.go @@ -11,7 +11,7 @@ import ( "text/template" "github.com/pkg/errors" - "github.com/sourcegraph/src-cli/internal/output" + "github.com/sourcegraph/sourcegraph/lib/output" ) func init() { diff --git a/go.mod b/go.mod index 35cb8c4795..fbfd1e0dcd 100644 --- a/go.mod +++ b/go.mod @@ -8,26 +8,26 @@ require ( github.com/efritz/pentimento v0.0.0-20190429011147-ade47d831101 github.com/gobwas/glob v0.2.3 github.com/google/go-cmp v0.5.5 - github.com/hashicorp/go-multierror v1.1.0 + github.com/hashicorp/go-multierror v1.1.1 github.com/jig/teereadcloser v0.0.0-20181016160506-953720c48e05 github.com/json-iterator/go v1.1.10 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/mattn/go-isatty v0.0.12 - github.com/mattn/go-runewidth v0.0.9 github.com/neelance/parallel v0.0.0-20160708114440-4de9ce63d14c - github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 + github.com/nsf/termbox-go v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.4 // indirect github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 github.com/pkg/errors v0.9.1 + github.com/rivo/uniseg v0.2.0 // indirect github.com/sourcegraph/batch-change-utils v0.0.0-20210309183117-206c057cc03e github.com/sourcegraph/go-diff v0.6.1 github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf - github.com/sourcegraph/sourcegraph/lib v0.0.0-20210312004335-4dae89dd19bc + github.com/sourcegraph/sourcegraph/lib v0.0.0-20210425142047-b8f4790093e5 github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect golang.org/x/net v0.0.0-20200625001655-4c5254603344 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 + golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 // indirect gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7 ) diff --git a/go.sum b/go.sum index 92ed040589..10925ed7fb 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,9 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/jig/teereadcloser v0.0.0-20181016160506-953720c48e05 h1:dSwwtWuwMyarzsbVWOq4QJ8xVy9wgcNomvWyGtrKe+E= github.com/jig/teereadcloser v0.0.0-20181016160506-953720c48e05/go.mod h1:sRUFlj+HCejvoCRpuhU0EYnNw5FG+YJpz8UFfCf0F2U= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= @@ -27,16 +28,17 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/neelance/parallel v0.0.0-20160708114440-4de9ce63d14c h1:NZOii9TDGRAfCS5VM16XnF4K7afoLQmIiZX8EkKnxtE= github.com/neelance/parallel v0.0.0-20160708114440-4de9ce63d14c/go.mod h1:eTBvSIlRgLo+CNFFQRQTwUGTZOEdvXIKeZS/xG+D2yU= -github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 h1:lh3PyZvY+B9nFliSGTn5uFuqQQJGuNrD0MLCokv09ag= github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/nsf/termbox-go v1.0.0 h1:i1ohxKvN1/5WJ5VHOdmy/Ps0iiblAzgtqhhozNT1nhA= +github.com/nsf/termbox-go v1.0.0/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98= @@ -45,6 +47,9 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sourcegraph/batch-change-utils v0.0.0-20210309183117-206c057cc03e h1:8dpmTar1H4dS4f3WWTtG6FSK/GbjQl7alHRJlzNY0lo= @@ -53,8 +58,8 @@ github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0H github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf h1:oAdWFqhStsWiiMP/vkkHiMXqFXzl1XfUNOdxKJbd6bI= github.com/sourcegraph/jsonx v0.0.0-20200629203448-1a936bd500cf/go.mod h1:ppFaPm6kpcHnZGqQTFhUIAQRIEhdQDWP1PCv4/ON354= -github.com/sourcegraph/sourcegraph/lib v0.0.0-20210312004335-4dae89dd19bc h1:LiXz6LQ7uNqrjIkGG+ZIi2P4nX5GpfRW9sA+K/VvMTA= -github.com/sourcegraph/sourcegraph/lib v0.0.0-20210312004335-4dae89dd19bc/go.mod h1:rLgD/nZsI+CrAcblMx+LSYFg2uFdrXY3oqOG1Ji2H4A= +github.com/sourcegraph/sourcegraph/lib v0.0.0-20210425142047-b8f4790093e5 h1:jE/jTSVbwxtCDBmoCMhzjEBEiHBrjv0+AHbdhtb+nPs= +github.com/sourcegraph/sourcegraph/lib v0.0.0-20210425142047-b8f4790093e5/go.mod h1:NopPnbkgFn6YDGOhy0+VQ/WH41U80IVfJSHxtebQyuo= github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152 h1:z/MpntplPaW6QW95pzcAR/72Z5TWDyDnSo0EOcyij9o= github.com/sourcegraph/yaml v1.0.1-0.20200714132230-56936252f152/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo= @@ -90,8 +95,9 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190428183149-804c0c7841b5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 h1:kHSDPqCtsHZOg0nVylfTo20DDhE9gG4Y0jn7hKQ0QAM= +golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -105,8 +111,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:mub0MmFLOn8XLikZOAhgLD1kXJq8jgftSrrv7m00xFo= diff --git a/internal/batches/debug.go b/internal/batches/debug.go index e9bf658281..a8eb696ecd 100644 --- a/internal/batches/debug.go +++ b/internal/batches/debug.go @@ -1,6 +1,6 @@ package batches -import "github.com/sourcegraph/src-cli/internal/output" +import "github.com/sourcegraph/sourcegraph/lib/output" // DebugOut can be used to print debug messages in development to the TUI. // For that it needs to be set to an actual *output.Output. diff --git a/internal/output/_examples/main.go b/internal/output/_examples/main.go deleted file mode 100644 index 0502588262..0000000000 --- a/internal/output/_examples/main.go +++ /dev/null @@ -1,199 +0,0 @@ -package main - -import ( - "flag" - "strings" - "sync" - "time" - - "github.com/sourcegraph/src-cli/internal/output" -) - -var ( - duration time.Duration - verbose bool - withBars bool -) - -func init() { - flag.DurationVar(&duration, "progress", 5*time.Second, "time to take in the progress bar and pending samples") - flag.BoolVar(&verbose, "verbose", false, "enable verbose mode") - flag.BoolVar(&withBars, "with-bars", false, "show status bars on top of progress bar") -} - -func main() { - flag.Parse() - - out := output.NewOutput(flag.CommandLine.Output(), output.OutputOpts{ - Verbose: verbose, - }) - - if withBars { - demoProgressWithBars(out, duration) - } else { - demo(out, duration) - } -} - -func demo(out *output.Output, duration time.Duration) { - var wg sync.WaitGroup - progress := out.Progress([]output.ProgressBar{ - {Label: "A", Max: 1.0}, - {Label: "BB", Max: 1.0, Value: 0.5}, - {Label: strings.Repeat("X", 200), Max: 1.0}, - }, nil) - - wg.Add(1) - go func() { - ticker := time.NewTicker(duration / 20) - defer ticker.Stop() - defer wg.Done() - - i := 0 - for range ticker.C { - i += 1 - if i > 20 { - return - } - - progress.Verbosef("%slog line %d", output.StyleWarning, i) - } - }() - - wg.Add(1) - go func() { - ticker := time.NewTicker(10 * time.Millisecond) - defer ticker.Stop() - defer wg.Done() - - start := time.Now() - until := start.Add(duration) - for range ticker.C { - now := time.Now() - if now.After(until) { - return - } - - progress.SetValue(0, float64(now.Sub(start))/float64(duration)) - progress.SetValue(1, 0.5+float64(now.Sub(start))/float64(duration)/2) - progress.SetValue(2, 2*float64(now.Sub(start))/float64(duration)) - } - }() - - wg.Wait() - progress.Complete() - - func() { - ticker := time.NewTicker(10 * time.Millisecond) - defer ticker.Stop() - - pending := out.Pending(output.Linef("", output.StylePending, "Starting pending ticker")) - defer pending.Complete(output.Line(output.EmojiSuccess, output.StyleSuccess, "Ticker done!")) - - until := time.Now().Add(duration) - for range ticker.C { - now := time.Now() - if now.After(until) { - return - } - - pending.Updatef("Waiting for another %s", time.Until(until)) - } - }() - - out.Write("") - block := out.Block(output.Line(output.EmojiSuccess, output.StyleSuccess, "Done!")) - block.Write("Here is some additional information.\nIt even line wraps.") - block.Close() -} - -func demoProgressWithBars(out *output.Output, duration time.Duration) { - var wg sync.WaitGroup - progress := out.ProgressWithStatusBars([]output.ProgressBar{ - {Label: "Running steps", Max: 1.0}, - }, []*output.StatusBar{ - output.NewStatusBarWithLabel("github.com/sourcegraph/src-cli"), - output.NewStatusBarWithLabel("github.com/sourcegraph/sourcegraph"), - }, nil) - - wg.Add(1) - go func() { - ticker := time.NewTicker(duration / 10) - defer ticker.Stop() - defer wg.Done() - - i := 0 - for range ticker.C { - i += 1 - if i > 10 { - return - } - - progress.Verbosef("%slog line %d", output.StyleWarning, i) - } - }() - - wg.Add(1) - go func() { - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - defer wg.Done() - - start := time.Now() - until := start.Add(duration) - for range ticker.C { - now := time.Now() - if now.After(until) { - return - } - - elapsed := time.Since(start) - - if elapsed < 5*time.Second { - if elapsed < 1*time.Second { - progress.StatusBarUpdatef(0, "Downloading archive...") - progress.StatusBarUpdatef(1, "Downloading archive...") - - } else if elapsed > 1*time.Second && elapsed < 2*time.Second { - progress.StatusBarUpdatef(0, `comby -in-place 'fmt.Sprintf("%%d", :[v])' 'strconv.Itoa(:[v])' main.go`) - progress.StatusBarUpdatef(1, `comby -in-place 'fmt.Sprintf("%%d", :[v])' 'strconv.Itoa(:[v])' pkg/main.go pkg/utils.go`) - - } else if elapsed > 2*time.Second && elapsed < 4*time.Second { - progress.StatusBarUpdatef(0, `goimports -w main.go`) - if elapsed > (2*time.Second + 500*time.Millisecond) { - progress.StatusBarUpdatef(1, `goimports -w pkg/main.go pkg/utils.go`) - } - - } else if elapsed > 4*time.Second && elapsed < 5*time.Second { - progress.StatusBarCompletef(1, `Done!`) - if elapsed > (4*time.Second + 500*time.Millisecond) { - progress.StatusBarCompletef(0, `Done!`) - } - } - } - - if elapsed > 5*time.Second && elapsed < 6*time.Second { - progress.StatusBarResetf(0, "github.com/sourcegraph/code-intel", `Downloading archive...`) - if elapsed > (5*time.Second + 200*time.Millisecond) { - progress.StatusBarResetf(1, "github.com/sourcegraph/srcx86", `Downloading archive...`) - } - } else if elapsed > 6*time.Second && elapsed < 7*time.Second { - progress.StatusBarUpdatef(1, `comby -in-place 'fmt.Sprintf("%%d", :[v])' 'strconv.Itoa(:[v])' main.go (%s)`) - if elapsed > (6*time.Second + 100*time.Millisecond) { - progress.StatusBarUpdatef(0, `comby -in-place 'fmt.Sprintf("%%d", :[v])' 'strconv.Itoa(:[v])' main.go`) - } - } else if elapsed > 7*time.Second && elapsed < 8*time.Second { - progress.StatusBarCompletef(0, "Done!") - if elapsed > (7*time.Second + 320*time.Millisecond) { - progress.StatusBarCompletef(1, "Done!") - } - } - - progress.SetValue(0, float64(now.Sub(start))/float64(duration)) - } - }() - - wg.Wait() - - progress.Complete() -} diff --git a/internal/output/block.go b/internal/output/block.go deleted file mode 100644 index 75d68a9e4d..0000000000 --- a/internal/output/block.go +++ /dev/null @@ -1,59 +0,0 @@ -package output - -import ( - "bytes" - "sync" -) - -// Block represents a block of output with one status line, and then zero or -// more lines of output nested under the status line. -type Block struct { - *Output - - indent []byte - unwrapped *Output - writer *indentedWriter -} - -func newBlock(indent int, o *Output) *Block { - w := &indentedWriter{} - - // Block uses Output's implementation, but with a wrapped writer that - // indents all output lines. (Note, however, that o's lock mutex is still - // used.) - return &Block{ - Output: &Output{ - w: w, - caps: o.caps, - opts: o.opts, - }, - indent: bytes.Repeat([]byte(" "), indent), - unwrapped: o, - writer: w, - } -} - -func (b *Block) Close() { - b.unwrapped.Lock() - defer b.unwrapped.Unlock() - - // This is a little tricky: output from Writer methods includes a trailing - // newline, so we need to trim that so we don't output extra blank lines. - for _, line := range bytes.Split(bytes.TrimRight(b.writer.buffer.Bytes(), "\n"), []byte("\n")) { - _, _ = b.unwrapped.w.Write(b.indent) - _, _ = b.unwrapped.w.Write(line) - _, _ = b.unwrapped.w.Write([]byte("\n")) - } -} - -type indentedWriter struct { - buffer bytes.Buffer - lock sync.Mutex -} - -func (w *indentedWriter) Write(p []byte) (int, error) { - w.lock.Lock() - defer w.lock.Unlock() - - return w.buffer.Write(p) -} diff --git a/internal/output/capabilities.go b/internal/output/capabilities.go deleted file mode 100644 index aa5ea964e6..0000000000 --- a/internal/output/capabilities.go +++ /dev/null @@ -1,69 +0,0 @@ -package output - -import ( - "os" - "strconv" - - "github.com/mattn/go-isatty" - "github.com/nsf/termbox-go" -) - -type capabilities struct { - Color bool - Isatty bool - Height int - Width int -} - -func detectCapabilities() capabilities { - // There's a pretty obvious flaw here in that we only check the terminal - // size once. We may want to consider adding a background goroutine that - // updates the capabilities struct every second or two. - // - // Pulling in termbox is probably overkill, but finding a pure Go library - // that could just provide terminfo was surprisingly hard. At least termbox - // is widely used. - w, h := 80, 25 - if err := termbox.Init(); err == nil { - w, h = termbox.Size() - termbox.Close() - } - - atty := isatty.IsTerminal(os.Stdout.Fd()) - - return capabilities{ - Color: detectColor(atty), - Isatty: atty, - Height: h, - Width: w, - } -} - -func detectColor(atty bool) bool { - if os.Getenv("NO_COLOR") != "" { - return false - } - - if color := os.Getenv("COLOR"); color != "" { - enabled, _ := strconv.ParseBool(color) - return enabled - } - - if !atty { - return false - } - - return true -} - -func (c *capabilities) formatArgs(args []interface{}) []interface{} { - out := make([]interface{}, len(args)) - for i, arg := range args { - if _, ok := arg.(Style); ok && !c.Color { - out[i] = "" - } else { - out[i] = arg - } - } - return out -} diff --git a/internal/output/emoji.go b/internal/output/emoji.go deleted file mode 100644 index 56a38d4257..0000000000 --- a/internal/output/emoji.go +++ /dev/null @@ -1,9 +0,0 @@ -package output - -// Standard emoji for use in output. -const ( - EmojiFailure = "❌" - EmojiWarning = "❗️" - EmojiSuccess = "✅" - EmojiLightbulb = "💡" -) diff --git a/internal/output/line.go b/internal/output/line.go deleted file mode 100644 index 26a216e704..0000000000 --- a/internal/output/line.go +++ /dev/null @@ -1,44 +0,0 @@ -package output - -import ( - "fmt" - "io" -) - -// FancyLine is a formatted output line with an optional emoji and style. -type FancyLine struct { - emoji string - style Style - format string - args []interface{} -} - -// Line creates a new FancyLine without a format string. -func Line(emoji string, style Style, s string) FancyLine { - return FancyLine{ - emoji: emoji, - style: style, - format: "%s", - args: []interface{}{s}, - } -} - -// Line creates a new FancyLine with a format string. As with Writer, the -// arguments may include Style instances with the %s specifier. -func Linef(emoji string, style Style, format string, a ...interface{}) FancyLine { - return FancyLine{ - emoji: emoji, - style: style, - format: format, - args: a, - } -} - -func (ol FancyLine) write(w io.Writer, caps capabilities) { - if ol.emoji != "" { - fmt.Fprint(w, ol.emoji+" ") - } - - fmt.Fprintf(w, "%s"+ol.format+"%s", caps.formatArgs(append(append([]interface{}{ol.style}, ol.args...), StyleReset))...) - _, _ = w.Write([]byte("\n")) -} diff --git a/internal/output/noop_writer.go b/internal/output/noop_writer.go deleted file mode 100644 index de76a65938..0000000000 --- a/internal/output/noop_writer.go +++ /dev/null @@ -1,10 +0,0 @@ -package output - -type NoopWriter struct{} - -func (NoopWriter) Write(s string) {} -func (NoopWriter) Writef(format string, args ...interface{}) {} -func (NoopWriter) WriteLine(line FancyLine) {} -func (NoopWriter) Verbose(s string) {} -func (NoopWriter) Verbosef(format string, args ...interface{}) {} -func (NoopWriter) VerboseLine(line FancyLine) {} diff --git a/internal/output/output.go b/internal/output/output.go deleted file mode 100644 index 25e08bcce8..0000000000 --- a/internal/output/output.go +++ /dev/null @@ -1,207 +0,0 @@ -// Package output provides types related to formatted terminal output. -package output - -import ( - "fmt" - "io" - "sync" - - "github.com/mattn/go-runewidth" -) - -// Writer defines a common set of methods that can be used to output status -// information. -// -// Note that the *f methods can accept Style instances in their arguments with -// the %s format specifier: if given, the detected colour support will be -// respected when outputting. -type Writer interface { - // These methods only write the given message if verbose mode is enabled. - Verbose(s string) - Verbosef(format string, args ...interface{}) - VerboseLine(line FancyLine) - - // These methods write their messages unconditionally. - Write(s string) - Writef(format string, args ...interface{}) - WriteLine(line FancyLine) -} - -type Context interface { - Writer - - Close() -} - -// Output encapsulates a standard set of functionality for commands that need -// to output human-readable data. -// -// Output is not appropriate for machine-readable data, such as JSON. -type Output struct { - w io.Writer - caps capabilities - opts OutputOpts - - // Unsurprisingly, it would be bad if multiple goroutines wrote at the same - // time, so we have a basic mutex to guard against that. - lock sync.Mutex -} - -var _ sync.Locker = &Output{} - -type OutputOpts struct { - // ForceColor ignores all terminal detection and enabled coloured output. - ForceColor bool - // ForceTTY ignores all terminal detection and enables TTY output. - ForceTTY bool - - // ForceHeight ignores all terminal detection and sets the height to this value. - ForceHeight int - // ForceWidth ignores all terminal detection and sets the width to this value. - ForceWidth int - - Verbose bool -} - -// newOutputPlatformQuirks provides a way for conditionally compiled code to -// hook into NewOutput to perform any required setup. -var newOutputPlatformQuirks func(o *Output) error - -func NewOutput(w io.Writer, opts OutputOpts) *Output { - caps := detectCapabilities() - if opts.ForceColor { - caps.Color = true - } - if opts.ForceTTY { - caps.Isatty = true - } - if opts.ForceHeight != 0 { - caps.Height = opts.ForceHeight - } - if opts.ForceWidth != 0 { - caps.Width = opts.ForceWidth - } - - o := &Output{caps: caps, opts: opts, w: w} - if newOutputPlatformQuirks != nil { - if err := newOutputPlatformQuirks(o); err != nil { - o.Verbosef("Error handling platform quirks: %v", err) - } - } - - return o -} - -func (o *Output) Lock() { - o.lock.Lock() - - // Hide the cursor while we update: this reduces the jitteriness of the - // whole thing, and some terminals are smart enough to make the update we're - // about to render atomic if the cursor is hidden for a short length of - // time. - o.w.Write([]byte("\033[?25l")) -} - -func (o *Output) Unlock() { - // Show the cursor once more. - o.w.Write([]byte("\033[?25h")) - - o.lock.Unlock() -} - -func (o *Output) Verbose(s string) { - if o.opts.Verbose { - o.Write(s) - } -} - -func (o *Output) Verbosef(format string, args ...interface{}) { - if o.opts.Verbose { - o.Writef(format, args...) - } -} - -func (o *Output) VerboseLine(line FancyLine) { - if o.opts.Verbose { - o.WriteLine(line) - } -} - -func (o *Output) Write(s string) { - o.Lock() - defer o.Unlock() - fmt.Fprintln(o.w, s) -} - -func (o *Output) Writef(format string, args ...interface{}) { - o.Lock() - defer o.Unlock() - fmt.Fprintf(o.w, format, o.caps.formatArgs(args)...) - fmt.Fprint(o.w, "\n") -} - -func (o *Output) WriteLine(line FancyLine) { - o.Lock() - defer o.Unlock() - line.write(o.w, o.caps) -} - -// Block starts a new block context. This should not be invoked if there is an -// active Pending or Progress context. -func (o *Output) Block(summary FancyLine) *Block { - o.WriteLine(summary) - return newBlock(runewidth.StringWidth(summary.emoji)+1, o) -} - -// Pending sets up a new pending context. This should not be invoked if there -// is an active Block or Progress context. The emoji in the message will be -// ignored, as Pending will render its own spinner. -// -// A Pending instance must be disposed of via the Complete or Destroy methods. -func (o *Output) Pending(message FancyLine) Pending { - return newPending(message, o) -} - -// Progress sets up a new progress bar context. This should not be invoked if -// there is an active Block or Pending context. -// -// A Progress instance must be disposed of via the Complete or Destroy methods. -func (o *Output) Progress(bars []ProgressBar, opts *ProgressOpts) Progress { - return newProgress(bars, o, opts) -} - -// ProgressWithStatusBars sets up a new progress bar context with StatusBar -// contexts. This should not be invoked if there is an active Block or Pending -// context. -// -// A Progress instance must be disposed of via the Complete or Destroy methods. -func (o *Output) ProgressWithStatusBars(bars []ProgressBar, statusBars []*StatusBar, opts *ProgressOpts) ProgressWithStatusBars { - return newProgressWithStatusBars(bars, statusBars, o, opts) -} - -// The utility functions below do not make checks for whether the terminal is a -// TTY, and should only be invoked from behind appropriate guards. - -func (o *Output) clearCurrentLine() { - fmt.Fprint(o.w, "\033[2K") -} - -func (o *Output) moveDown(lines int) { - fmt.Fprintf(o.w, "\033[%dB", lines) - - // Move the cursor to the leftmost column. - fmt.Fprintf(o.w, "\033[%dD", o.caps.Width+1) -} - -func (o *Output) moveUp(lines int) { - fmt.Fprintf(o.w, "\033[%dA", lines) - - // Move the cursor to the leftmost column. - fmt.Fprintf(o.w, "\033[%dD", o.caps.Width+1) -} - -// writeStyle is a helper to write a style while respecting the terminal -// capabilities. -func (o *Output) writeStyle(style Style) { - fmt.Fprintf(o.w, "%s", o.caps.formatArgs([]interface{}{style})...) -} diff --git a/internal/output/output_windows.go b/internal/output/output_windows.go deleted file mode 100644 index d55647fbc0..0000000000 --- a/internal/output/output_windows.go +++ /dev/null @@ -1,36 +0,0 @@ -package output - -import ( - "github.com/hashicorp/go-multierror" - "golang.org/x/sys/windows" -) - -func init() { - newOutputPlatformQuirks = func(o *Output) error { - var errs *multierror.Error - - if err := setConsoleMode(windows.Stdout, windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil { - errs = multierror.Append(errs, err) - } - if err := setConsoleMode(windows.Stderr, windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING); err != nil { - errs = multierror.Append(errs, err) - } - - return errs.ErrorOrNil() - } -} - -func setConsoleMode(handle windows.Handle, flags uint32) error { - // This is shamelessly lifted from gitlab-runner, specifically - // https://gitlab.com/gitlab-org/gitlab-runner/blob/f8d87f1e3e3af1cc8aadcea3e40bbb069eee72ef/helpers/cli/init_cli_windows.go - - // First we have to get the current console mode so we can add the desired - // flags. - var mode uint32 - if err := windows.GetConsoleMode(handle, &mode); err != nil { - return err - } - - // Now we can set the console mode. - return windows.SetConsoleMode(handle, mode|flags) -} diff --git a/internal/output/pending.go b/internal/output/pending.go deleted file mode 100644 index c1fd136571..0000000000 --- a/internal/output/pending.go +++ /dev/null @@ -1,26 +0,0 @@ -package output - -type Pending interface { - // Anything sent to the Writer methods will be displayed as a log message - // above the pending line. - Context - - // Update and Updatef change the message shown after the spinner. - Update(s string) - Updatef(format string, args ...interface{}) - - // Complete stops the spinner and replaces the pending line with the given - // message. - Complete(message FancyLine) - - // Destroy stops the spinner and removes the pending line. - Destroy() -} - -func newPending(message FancyLine, o *Output) Pending { - if !o.caps.Isatty { - return newPendingSimple(message, o) - } - - return newPendingTTY(message, o) -} diff --git a/internal/output/pending_simple.go b/internal/output/pending_simple.go deleted file mode 100644 index aa783e7189..0000000000 --- a/internal/output/pending_simple.go +++ /dev/null @@ -1,26 +0,0 @@ -package output - -type pendingSimple struct { - *Output -} - -func (p *pendingSimple) Update(s string) { - p.Write(s + "...") -} - -func (p *pendingSimple) Updatef(format string, args ...interface{}) { - p.Writef(format+"...", args...) -} - -func (p *pendingSimple) Complete(message FancyLine) { - p.WriteLine(message) -} - -func (p *pendingSimple) Close() {} -func (p *pendingSimple) Destroy() {} - -func newPendingSimple(message FancyLine, o *Output) *pendingSimple { - message.format += "..." - o.WriteLine(message) - return &pendingSimple{o} -} diff --git a/internal/output/pending_tty.go b/internal/output/pending_tty.go deleted file mode 100644 index 5442f6d044..0000000000 --- a/internal/output/pending_tty.go +++ /dev/null @@ -1,160 +0,0 @@ -package output - -import ( - "bytes" - "fmt" - "time" - - "github.com/mattn/go-runewidth" -) - -type pendingTTY struct { - o *Output - line FancyLine - spinner *spinner -} - -func (p *pendingTTY) Verbose(s string) { - if p.o.opts.Verbose { - p.Write(s) - } -} - -func (p *pendingTTY) Verbosef(format string, args ...interface{}) { - if p.o.opts.Verbose { - p.Writef(format, args...) - } -} - -func (p *pendingTTY) VerboseLine(line FancyLine) { - if p.o.opts.Verbose { - p.WriteLine(line) - } -} - -func (p *pendingTTY) Write(s string) { - p.o.Lock() - defer p.o.Unlock() - - p.o.moveUp(1) - p.o.clearCurrentLine() - fmt.Fprintln(p.o.w, s) - p.write(p.line) -} - -func (p *pendingTTY) Writef(format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - p.o.moveUp(1) - p.o.clearCurrentLine() - fmt.Fprintf(p.o.w, format, p.o.caps.formatArgs(args)...) - fmt.Fprint(p.o.w, "\n") - p.write(p.line) -} - -func (p *pendingTTY) WriteLine(line FancyLine) { - p.o.Lock() - defer p.o.Unlock() - - p.o.moveUp(1) - p.o.clearCurrentLine() - line.write(p.o.w, p.o.caps) - p.write(p.line) -} - -func (p *pendingTTY) Update(s string) { - p.o.Lock() - defer p.o.Unlock() - - p.line.format = "%s" - p.line.args = []interface{}{s} - - p.o.moveUp(1) - p.o.clearCurrentLine() - p.write(p.line) -} - -func (p *pendingTTY) Updatef(format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - p.line.format = format - p.line.args = args - - p.o.moveUp(1) - p.o.clearCurrentLine() - p.write(p.line) -} - -func (p *pendingTTY) Complete(message FancyLine) { - p.spinner.stop() - - p.o.Lock() - defer p.o.Unlock() - - p.o.moveUp(1) - p.o.clearCurrentLine() - p.write(message) -} - -func (p *pendingTTY) Close() { p.Destroy() } - -func (p *pendingTTY) Destroy() { - p.spinner.stop() - - p.o.Lock() - defer p.o.Unlock() - - p.o.moveUp(1) - p.o.clearCurrentLine() -} - -func newPendingTTY(message FancyLine, o *Output) *pendingTTY { - p := &pendingTTY{ - o: o, - line: message, - spinner: newSpinner(100 * time.Millisecond), - } - p.updateEmoji(spinnerStrings[0]) - fmt.Fprintln(p.o.w, "") - - go func() { - for s := range p.spinner.C { - func() { - p.o.Lock() - defer p.o.Unlock() - - p.updateEmoji(s) - - p.o.moveUp(1) - p.o.clearCurrentLine() - p.write(p.line) - }() - } - }() - - return p -} - -func (p *pendingTTY) updateEmoji(emoji string) { - // We add an extra space because the Braille characters are single width, - // but emoji are generally double width and that's what will most likely be - // used in the completion message, if any. - p.line.emoji = fmt.Sprintf("%s%s ", p.o.caps.formatArgs([]interface{}{ - p.line.style, - emoji, - })...) -} - -func (p *pendingTTY) write(message FancyLine) { - var buf bytes.Buffer - - // This appends a newline to buf, so we have to be careful to ensure that - // we also add a newline if the line is truncated. - message.write(&buf, p.o.caps) - - // FIXME: This doesn't account for escape codes right now, so we may - // truncate shorter than we mean to. - fmt.Fprint(p.o.w, runewidth.Truncate(buf.String(), p.o.caps.Width, "...\n")) -} diff --git a/internal/output/progress.go b/internal/output/progress.go deleted file mode 100644 index 2b7d9688a4..0000000000 --- a/internal/output/progress.go +++ /dev/null @@ -1,60 +0,0 @@ -package output - -type Progress interface { - Context - - // Complete stops the set of progress bars and marks them all as completed. - Complete() - - // Destroy stops the set of progress bars and clears them from the - // terminal. - Destroy() - - // SetLabel updates the label for the given bar. - SetLabel(i int, label string) - - // SetLabelAndRecalc updates the label for the given bar and recalculates - // the maximum width of the labels. - SetLabelAndRecalc(i int, label string) - - // SetValue updates the value for the given bar. - SetValue(i int, v float64) -} - -type ProgressBar struct { - Label string - Max float64 - Value float64 - - labelWidth int -} - -type ProgressOpts struct { - PendingStyle Style - SuccessEmoji string - SuccessStyle Style - - // NoSpinner turns of the automatic updating of the progress bar and - // spinner in a background goroutine. - // Used for testing only! - NoSpinner bool -} - -func (opt *ProgressOpts) WithNoSpinner(noSpinner bool) *ProgressOpts { - c := *opt - c.NoSpinner = noSpinner - return &c -} - -func newProgress(bars []ProgressBar, o *Output, opts *ProgressOpts) Progress { - barPtrs := make([]*ProgressBar, len(bars)) - for i := range bars { - barPtrs[i] = &bars[i] - } - - if !o.caps.Isatty { - return newProgressSimple(barPtrs, o, opts) - } - - return newProgressTTY(barPtrs, o, opts) -} diff --git a/internal/output/progress_simple.go b/internal/output/progress_simple.go deleted file mode 100644 index cf15aeeab2..0000000000 --- a/internal/output/progress_simple.go +++ /dev/null @@ -1,91 +0,0 @@ -package output - -import ( - "math" - "time" -) - -type progressSimple struct { - *Output - - bars []*ProgressBar - done chan chan struct{} -} - -func (p *progressSimple) Complete() { - p.stop() - writeBars(p.Output, p.bars) -} - -func (p *progressSimple) Close() { p.Destroy() } -func (p *progressSimple) Destroy() { p.stop() } - -func (p *progressSimple) SetLabel(i int, label string) { - p.bars[i].Label = label -} - -func (p *progressSimple) SetLabelAndRecalc(i int, label string) { - p.bars[i].Label = label -} - -func (p *progressSimple) SetValue(i int, v float64) { - p.bars[i].Value = v -} - -func (p *progressSimple) stop() { - c := make(chan struct{}) - p.done <- c - <-c -} - -func newProgressSimple(bars []*ProgressBar, o *Output, opts *ProgressOpts) *progressSimple { - p := &progressSimple{ - Output: o, - bars: bars, - done: make(chan chan struct{}), - } - - if opts != nil && opts.NoSpinner { - if p.Output.opts.Verbose { - writeBars(p.Output, p.bars) - } - return p - } - - go func() { - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if p.Output.opts.Verbose { - writeBars(p.Output, p.bars) - } - - case c := <-p.done: - c <- struct{}{} - return - } - } - }() - - return p -} - -func writeBar(w Writer, bar *ProgressBar) { - w.Writef("%s: %d%%", bar.Label, int64(math.Round((100.0*bar.Value)/bar.Max))) -} - -func writeBars(o *Output, bars []*ProgressBar) { - if len(bars) > 1 { - block := o.Block(Line("", StyleReset, "Progress:")) - defer block.Close() - - for _, bar := range bars { - writeBar(block, bar) - } - } else if len(bars) == 1 { - writeBar(o, bars[0]) - } -} diff --git a/internal/output/progress_tty.go b/internal/output/progress_tty.go deleted file mode 100644 index fc74875a1d..0000000000 --- a/internal/output/progress_tty.go +++ /dev/null @@ -1,267 +0,0 @@ -package output - -import ( - "fmt" - "math" - "strings" - "time" - - "github.com/mattn/go-runewidth" -) - -var DefaultProgressTTYOpts = &ProgressOpts{ - SuccessEmoji: "\u2705", - SuccessStyle: StyleSuccess, - PendingStyle: StylePending, -} - -type progressTTY struct { - bars []*ProgressBar - - o *Output - opts ProgressOpts - - emojiWidth int - labelWidth int - pendingEmoji string - spinner *spinner -} - -func (p *progressTTY) Complete() { - p.spinner.stop() - - p.o.Lock() - defer p.o.Unlock() - - for _, bar := range p.bars { - bar.Value = bar.Max - } - p.drawInSitu() -} - -func (p *progressTTY) Close() { p.Destroy() } - -func (p *progressTTY) Destroy() { - p.spinner.stop() - - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - for i := 0; i < len(p.bars); i += 1 { - p.o.clearCurrentLine() - p.o.moveDown(1) - } - - p.moveToOrigin() -} - -func (p *progressTTY) SetLabel(i int, label string) { - p.o.Lock() - defer p.o.Unlock() - - p.bars[i].Label = label - p.bars[i].labelWidth = runewidth.StringWidth(label) - p.drawInSitu() -} - -func (p *progressTTY) SetLabelAndRecalc(i int, label string) { - p.o.Lock() - defer p.o.Unlock() - - p.bars[i].Label = label - p.bars[i].labelWidth = runewidth.StringWidth(label) - - p.determineLabelWidth() - p.drawInSitu() -} - -func (p *progressTTY) SetValue(i int, v float64) { - p.o.Lock() - defer p.o.Unlock() - - p.bars[i].Value = v - p.drawInSitu() -} - -func (p *progressTTY) Verbose(s string) { - if p.o.opts.Verbose { - p.Write(s) - } -} - -func (p *progressTTY) Verbosef(format string, args ...interface{}) { - if p.o.opts.Verbose { - p.Writef(format, args...) - } -} - -func (p *progressTTY) VerboseLine(line FancyLine) { - if p.o.opts.Verbose { - p.WriteLine(line) - } -} - -func (p *progressTTY) Write(s string) { - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.o.clearCurrentLine() - fmt.Fprintln(p.o.w, s) - p.draw() -} - -func (p *progressTTY) Writef(format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.o.clearCurrentLine() - fmt.Fprintf(p.o.w, format, p.o.caps.formatArgs(args)...) - fmt.Fprint(p.o.w, "\n") - p.draw() -} - -func (p *progressTTY) WriteLine(line FancyLine) { - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.o.clearCurrentLine() - line.write(p.o.w, p.o.caps) - p.draw() -} - -func newProgressTTY(bars []*ProgressBar, o *Output, opts *ProgressOpts) *progressTTY { - p := &progressTTY{ - bars: bars, - o: o, - emojiWidth: 3, - pendingEmoji: spinnerStrings[0], - spinner: newSpinner(100 * time.Millisecond), - } - - if opts != nil { - p.opts = *opts - } else { - p.opts = *DefaultProgressTTYOpts - } - - p.determineEmojiWidth() - p.determineLabelWidth() - - p.o.Lock() - defer p.o.Unlock() - - p.draw() - - if opts != nil && opts.NoSpinner { - return p - } - - go func() { - for s := range p.spinner.C { - func() { - p.pendingEmoji = s - - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.draw() - }() - } - }() - - return p -} - -func (p *progressTTY) determineEmojiWidth() { - if w := runewidth.StringWidth(p.opts.SuccessEmoji); w > p.emojiWidth { - p.emojiWidth = w + 1 - } -} - -func (p *progressTTY) determineLabelWidth() { - p.labelWidth = 0 - for _, bar := range p.bars { - bar.labelWidth = runewidth.StringWidth(bar.Label) - if bar.labelWidth > p.labelWidth { - p.labelWidth = bar.labelWidth - } - } - - if maxWidth := p.o.caps.Width/2 - p.emojiWidth; (p.labelWidth + 2) > maxWidth { - p.labelWidth = maxWidth - 2 - } -} - -func (p *progressTTY) draw() { - for _, bar := range p.bars { - p.writeBar(bar) - } -} - -func (p *progressTTY) drawInSitu() { - p.moveToOrigin() - p.draw() -} - -func (p *progressTTY) moveToOrigin() { - p.o.moveUp(len(p.bars)) -} - -func (p *progressTTY) writeBar(bar *ProgressBar) { - p.o.clearCurrentLine() - - value := bar.Value - if bar.Value >= bar.Max { - p.o.writeStyle(p.opts.SuccessStyle) - fmt.Fprint(p.o.w, runewidth.FillRight(p.opts.SuccessEmoji, p.emojiWidth)) - value = bar.Max - } else { - p.o.writeStyle(p.opts.PendingStyle) - fmt.Fprint(p.o.w, runewidth.FillRight(p.pendingEmoji, p.emojiWidth)) - } - - fmt.Fprint(p.o.w, runewidth.FillRight(runewidth.Truncate(bar.Label, p.labelWidth, "..."), p.labelWidth)) - - // The bar width is the width of the terminal, minus the label width, minus - // two spaces. - barWidth := p.o.caps.Width - p.labelWidth - p.emojiWidth - 2 - - // Unicode box drawing gives us eight possible bar widths, so we need to - // calculate both the bar width and then the final character, if any. - var segments int - if bar.Max > 0 { - segments = int(math.Round((float64(8*barWidth) * value) / bar.Max)) - } - - fillWidth := segments / 8 - remainder := segments % 8 - if remainder == 0 { - if fillWidth > barWidth { - fillWidth = barWidth - } - } else { - if fillWidth+1 > barWidth { - fillWidth = barWidth - 1 - } - } - - fmt.Fprintf(p.o.w, " ") - fmt.Fprint(p.o.w, strings.Repeat("█", fillWidth)) - fmt.Fprintln(p.o.w, []string{ - "", - "▏", - "▎", - "▍", - "▌", - "▋", - "▊", - "▉", - }[remainder]) - - p.o.writeStyle(StyleReset) -} diff --git a/internal/output/progress_with_status_bars.go b/internal/output/progress_with_status_bars.go deleted file mode 100644 index 969b695347..0000000000 --- a/internal/output/progress_with_status_bars.go +++ /dev/null @@ -1,23 +0,0 @@ -package output - -type ProgressWithStatusBars interface { - Progress - - StatusBarUpdatef(i int, format string, args ...interface{}) - StatusBarCompletef(i int, format string, args ...interface{}) - StatusBarFailf(i int, format string, args ...interface{}) - StatusBarResetf(i int, label, format string, args ...interface{}) -} - -func newProgressWithStatusBars(bars []ProgressBar, statusBars []*StatusBar, o *Output, opts *ProgressOpts) ProgressWithStatusBars { - barPtrs := make([]*ProgressBar, len(bars)) - for i := range bars { - barPtrs[i] = &bars[i] - } - - if !o.caps.Isatty { - return newProgressWithStatusBarsSimple(barPtrs, statusBars, o, opts) - } - - return newProgressWithStatusBarsTTY(barPtrs, statusBars, o, opts) -} diff --git a/internal/output/progress_with_status_bars_simple.go b/internal/output/progress_with_status_bars_simple.go deleted file mode 100644 index 8b41a6b67b..0000000000 --- a/internal/output/progress_with_status_bars_simple.go +++ /dev/null @@ -1,106 +0,0 @@ -package output - -import ( - "time" -) - -type progressWithStatusBarsSimple struct { - *progressSimple - - statusBars []*StatusBar -} - -func (p *progressWithStatusBarsSimple) Complete() { - p.stop() - writeBars(p.Output, p.bars) - writeStatusBars(p.Output, p.statusBars) -} - -func (p *progressWithStatusBarsSimple) StatusBarUpdatef(i int, format string, args ...interface{}) { - if p.statusBars[i] != nil { - p.statusBars[i].Updatef(format, args...) - } -} - -func (p *progressWithStatusBarsSimple) StatusBarCompletef(i int, format string, args ...interface{}) { - if p.statusBars[i] != nil { - wasComplete := p.statusBars[i].completed - p.statusBars[i].Completef(format, args...) - if !wasComplete { - writeStatusBar(p.Output, p.statusBars[i]) - } - } -} - -func (p *progressWithStatusBarsSimple) StatusBarFailf(i int, format string, args ...interface{}) { - if p.statusBars[i] != nil { - wasCompleted := p.statusBars[i].completed - p.statusBars[i].Failf(format, args...) - if !wasCompleted { - writeStatusBar(p.Output, p.statusBars[i]) - } - } -} - -func (p *progressWithStatusBarsSimple) StatusBarResetf(i int, label, format string, args ...interface{}) { - if p.statusBars[i] != nil { - p.statusBars[i].Resetf(label, format, args...) - } -} - -func newProgressWithStatusBarsSimple(bars []*ProgressBar, statusBars []*StatusBar, o *Output, opts *ProgressOpts) *progressWithStatusBarsSimple { - p := &progressWithStatusBarsSimple{ - progressSimple: &progressSimple{ - Output: o, - bars: bars, - done: make(chan chan struct{}), - }, - statusBars: statusBars, - } - - if opts != nil && opts.NoSpinner { - if p.Output.opts.Verbose { - writeBars(p.Output, p.bars) - writeStatusBars(p.Output, p.statusBars) - } - return p - } - - go func() { - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if p.Output.opts.Verbose { - writeBars(p.Output, p.bars) - writeStatusBars(p.Output, p.statusBars) - } - - case c := <-p.done: - c <- struct{}{} - return - } - } - }() - - return p -} - -func writeStatusBar(w Writer, bar *StatusBar) { - w.Writef("%s: "+bar.format, append([]interface{}{bar.label}, bar.args...)...) -} - -func writeStatusBars(o *Output, bars []*StatusBar) { - if len(bars) > 1 { - block := o.Block(Line("", StyleReset, "Status:")) - defer block.Close() - - for _, bar := range bars { - writeStatusBar(block, bar) - } - } else if len(bars) == 1 { - writeStatusBar(o, bars[0]) - } -} diff --git a/internal/output/progress_with_status_bars_tty.go b/internal/output/progress_with_status_bars_tty.go deleted file mode 100644 index d6178edd03..0000000000 --- a/internal/output/progress_with_status_bars_tty.go +++ /dev/null @@ -1,304 +0,0 @@ -package output - -import ( - "fmt" - "time" - - "github.com/mattn/go-runewidth" -) - -func newProgressWithStatusBarsTTY(bars []*ProgressBar, statusBars []*StatusBar, o *Output, opts *ProgressOpts) *progressWithStatusBarsTTY { - p := &progressWithStatusBarsTTY{ - progressTTY: &progressTTY{ - bars: bars, - o: o, - emojiWidth: 3, - pendingEmoji: spinnerStrings[0], - spinner: newSpinner(100 * time.Millisecond), - }, - statusBars: statusBars, - } - - if opts != nil { - p.opts = *opts - } else { - p.opts = *DefaultProgressTTYOpts - } - - p.determineEmojiWidth() - p.determineLabelWidth() - p.determineStatusBarLabelWidth() - - p.o.Lock() - defer p.o.Unlock() - - p.draw() - - if opts != nil && opts.NoSpinner { - return p - } - - go func() { - for s := range p.spinner.C { - func() { - p.pendingEmoji = s - - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.draw() - }() - } - }() - - return p -} - -type progressWithStatusBarsTTY struct { - *progressTTY - - statusBars []*StatusBar - statusBarLabelWidth int - numPrintedStatusBars int -} - -func (p *progressWithStatusBarsTTY) Close() { p.Destroy() } - -func (p *progressWithStatusBarsTTY) Destroy() { - p.spinner.stop() - - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - - for i := 0; i < p.lines(); i += 1 { - p.o.clearCurrentLine() - p.o.moveDown(1) - } - - p.moveToOrigin() -} - -func (p *progressWithStatusBarsTTY) Complete() { - p.spinner.stop() - - p.o.Lock() - defer p.o.Unlock() - - // +1 because of the line between progress and status bars - for i := 0; i < p.numPrintedStatusBars+1; i += 1 { - p.o.moveUp(1) - p.o.clearCurrentLine() - } - p.statusBars = p.statusBars[0:0] - - for _, bar := range p.bars { - bar.Value = bar.Max - } - - p.o.moveUp(len(p.bars)) - p.draw() -} - -func (p *progressWithStatusBarsTTY) lines() int { - return len(p.bars) + p.numPrintedStatusBars + 1 -} - -func (p *progressWithStatusBarsTTY) SetLabel(i int, label string) { - p.o.Lock() - defer p.o.Unlock() - - p.bars[i].Label = label - p.bars[i].labelWidth = runewidth.StringWidth(label) - p.drawInSitu() -} - -func (p *progressWithStatusBarsTTY) SetValue(i int, v float64) { - p.o.Lock() - defer p.o.Unlock() - - p.bars[i].Value = v - p.drawInSitu() -} - -func (p *progressWithStatusBarsTTY) StatusBarResetf(i int, label, format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - if p.statusBars[i] != nil { - p.statusBars[i].Resetf(label, format, args...) - } - - p.determineStatusBarLabelWidth() - p.drawInSitu() -} - -func (p *progressWithStatusBarsTTY) StatusBarUpdatef(i int, format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - if p.statusBars[i] != nil { - p.statusBars[i].Updatef(format, args...) - } - - p.drawInSitu() -} - -func (p *progressWithStatusBarsTTY) StatusBarCompletef(i int, format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - if p.statusBars[i] != nil { - p.statusBars[i].Completef(format, args...) - } - - p.drawInSitu() -} - -func (p *progressWithStatusBarsTTY) StatusBarFailf(i int, format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - if p.statusBars[i] != nil { - p.statusBars[i].Failf(format, args...) - } - - p.drawInSitu() -} - -func (p *progressWithStatusBarsTTY) draw() { - for _, bar := range p.bars { - p.writeBar(bar) - } - - if len(p.statusBars) > 0 { - p.o.clearCurrentLine() - fmt.Fprint(p.o.w, StylePending, "│", runewidth.FillLeft("\n", p.o.caps.Width-1)) - - } - - p.numPrintedStatusBars = 0 - for i, statusBar := range p.statusBars { - if statusBar == nil { - continue - } - if !statusBar.initialized { - continue - } - - last := i == len(p.statusBars)-1 - p.writeStatusBar(last, statusBar) - p.numPrintedStatusBars += 1 - } -} - -func (p *progressWithStatusBarsTTY) moveToOrigin() { - p.o.moveUp(p.lines()) -} - -func (p *progressWithStatusBarsTTY) drawInSitu() { - p.moveToOrigin() - p.draw() -} - -func (p *progressWithStatusBarsTTY) determineStatusBarLabelWidth() { - p.statusBarLabelWidth = 0 - for _, bar := range p.statusBars { - labelWidth := runewidth.StringWidth(bar.label) - if labelWidth > p.statusBarLabelWidth { - p.statusBarLabelWidth = labelWidth - } - } - - statusBarPrefixWidth := 4 // statusBars have box char and space - if maxWidth := p.o.caps.Width/2 - statusBarPrefixWidth; (p.statusBarLabelWidth + 2) > maxWidth { - p.statusBarLabelWidth = maxWidth - 2 - } -} - -func (p *progressWithStatusBarsTTY) writeStatusBar(last bool, statusBar *StatusBar) { - style := StylePending - if statusBar.completed { - if statusBar.failed { - style = StyleWarning - } else { - style = StyleSuccess - } - } - - box := "├── " - if last { - box = "└── " - } - const boxWidth = 4 - - labelFillWidth := p.statusBarLabelWidth + 2 - label := runewidth.FillRight(runewidth.Truncate(statusBar.label, p.statusBarLabelWidth, "..."), labelFillWidth) - - duration := statusBar.runtime().String() - durationLength := runewidth.StringWidth(duration) - - textMaxLength := p.o.caps.Width - boxWidth - labelFillWidth - (durationLength + 2) - text := runewidth.Truncate(fmt.Sprintf(statusBar.format, p.o.caps.formatArgs(statusBar.args)...), textMaxLength, "...") - - // The text might contain invisible control characters, so we need to - // exclude them when counting length - textLength := visibleStringWidth(text) - - durationMaxWidth := textMaxLength - textLength + (durationLength + 2) - durationText := runewidth.FillLeft(duration, durationMaxWidth) - - p.o.clearCurrentLine() - fmt.Fprint(p.o.w, style, box, label, StyleReset, text, StyleBold, durationText, StyleReset, "\n") -} - -func (p *progressWithStatusBarsTTY) Verbose(s string) { - if p.o.opts.Verbose { - p.Write(s) - } -} - -func (p *progressWithStatusBarsTTY) Verbosef(format string, args ...interface{}) { - if p.o.opts.Verbose { - p.Writef(format, args...) - } -} - -func (p *progressWithStatusBarsTTY) VerboseLine(line FancyLine) { - if p.o.opts.Verbose { - p.WriteLine(line) - } -} - -func (p *progressWithStatusBarsTTY) Write(s string) { - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.o.clearCurrentLine() - fmt.Fprintln(p.o.w, s) - p.draw() -} - -func (p *progressWithStatusBarsTTY) Writef(format string, args ...interface{}) { - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.o.clearCurrentLine() - fmt.Fprintf(p.o.w, format, p.o.caps.formatArgs(args)...) - fmt.Fprint(p.o.w, "\n") - p.draw() -} - -func (p *progressWithStatusBarsTTY) WriteLine(line FancyLine) { - p.o.Lock() - defer p.o.Unlock() - - p.moveToOrigin() - p.o.clearCurrentLine() - line.write(p.o.w, p.o.caps) - p.draw() -} diff --git a/internal/output/spinner.go b/internal/output/spinner.go deleted file mode 100644 index b0b5b86de2..0000000000 --- a/internal/output/spinner.go +++ /dev/null @@ -1,47 +0,0 @@ -package output - -import "time" - -type spinner struct { - C chan string - - done chan chan struct{} -} - -var spinnerStrings = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} - -func newSpinner(interval time.Duration) *spinner { - c := make(chan string) - done := make(chan chan struct{}) - s := &spinner{ - C: c, - done: done, - } - - go func() { - ticker := time.NewTicker(interval) - defer ticker.Stop() - defer close(s.C) - - i := 0 - for { - select { - case <-ticker.C: - i = (i + 1) % len(spinnerStrings) - s.C <- spinnerStrings[i] - - case c := <-done: - c <- struct{}{} - return - } - } - }() - - return s -} - -func (s *spinner) stop() { - c := make(chan struct{}) - s.done <- c - <-c -} diff --git a/internal/output/status_bar.go b/internal/output/status_bar.go deleted file mode 100644 index ae9005b19a..0000000000 --- a/internal/output/status_bar.go +++ /dev/null @@ -1,67 +0,0 @@ -package output - -import "time" - -// StatusBar is a sub-element of a progress bar that displays the current status -// of a process. -type StatusBar struct { - completed bool - failed bool - - label string - format string - args []interface{} - - initialized bool - startedAt time.Time - finishedAt time.Time -} - -// Completef sets the StatusBar to completed and updates its text. -func (sb *StatusBar) Completef(format string, args ...interface{}) { - sb.completed = true - sb.format = format - sb.args = args - sb.finishedAt = time.Now() -} - -// Failf sets the StatusBar to completed and failed and updates its text. -func (sb *StatusBar) Failf(format string, args ...interface{}) { - sb.Completef(format, args...) - sb.failed = true -} - -// Resetf sets the status of the StatusBar to incomplete and updates its label and text. -func (sb *StatusBar) Resetf(label, format string, args ...interface{}) { - sb.initialized = true - sb.completed = false - sb.failed = false - sb.label = label - sb.format = format - sb.args = args - sb.startedAt = time.Now() -} - -// Updatef updates the StatusBar's text. -func (sb *StatusBar) Updatef(format string, args ...interface{}) { - sb.initialized = true - sb.format = format - sb.args = args -} - -func (sb *StatusBar) runtime() time.Duration { - if sb.startedAt.IsZero() { - return 0 - } - if sb.finishedAt.IsZero() { - return time.Since(sb.startedAt).Truncate(time.Second) - } - - return sb.finishedAt.Sub(sb.startedAt).Truncate(time.Second) -} - -func NewStatusBarWithLabel(label string) *StatusBar { - return &StatusBar{label: label, startedAt: time.Now()} -} - -func NewStatusBar() *StatusBar { return &StatusBar{} } diff --git a/internal/output/style.go b/internal/output/style.go deleted file mode 100644 index f43bd75109..0000000000 --- a/internal/output/style.go +++ /dev/null @@ -1,60 +0,0 @@ -package output - -import ( - "fmt" - "strings" -) - -type Style interface { - fmt.Stringer -} - -func CombineStyles(styles ...Style) Style { - sb := strings.Builder{} - for _, s := range styles { - fmt.Fprint(&sb, s) - } - return &style{sb.String()} -} - -func Fg256Color(code int) Style { return &style{fmt.Sprintf("\033[38;5;%dm", code)} } -func Bg256Color(code int) Style { return &style{fmt.Sprintf("\033[48;5;%dm", code)} } - -type style struct{ code string } - -func (s *style) String() string { return s.code } - -var ( - StyleReset = &style{"\033[0m"} - StyleLogo = Fg256Color(57) - StylePending = Fg256Color(4) - StyleWarning = Fg256Color(124) - StyleSuccess = Fg256Color(2) - StyleSuggestion = Fg256Color(244) - - StyleBold = &style{"\033[1m"} - StyleItalic = &style{"\033[3m"} - StyleUnderline = &style{"\033[4m"} - - // Search-specific colors. - StyleSearchQuery = Fg256Color(68) - StyleSearchBorder = Fg256Color(239) - StyleSearchLink = Fg256Color(237) - StyleSearchRepository = Fg256Color(23) - StyleSearchFilename = Fg256Color(69) - StyleSearchMatch = CombineStyles(Fg256Color(0), Bg256Color(11)) - StyleSearchLineNumbers = Fg256Color(69) - StyleSearchCommitAuthor = Fg256Color(2) - StyleSearchCommitSubject = Fg256Color(68) - StyleSearchCommitDate = Fg256Color(23) - - // Search alert specific colors. - StyleSearchAlertTitle = Fg256Color(124) - StyleSearchAlertDescription = Fg256Color(124) - StyleSearchAlertProposedTitle = &style{""} - StyleSearchAlertProposedQuery = Fg256Color(69) - StyleSearchAlertProposedDescription = &style{""} - - StyleLinesDeleted = Fg256Color(196) - StyleLinesAdded = Fg256Color(2) -) diff --git a/internal/output/visible_string_width.go b/internal/output/visible_string_width.go deleted file mode 100644 index 32ca28abe0..0000000000 --- a/internal/output/visible_string_width.go +++ /dev/null @@ -1,17 +0,0 @@ -package output - -import ( - "regexp" - - "github.com/mattn/go-runewidth" -) - -// This regex is taken from here: -// https://github.com/acarl005/stripansi/blob/5a71ef0e047df0427e87a79f27009029921f1f9b/stripansi.go -const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))" - -var ansiRegex = regexp.MustCompile(ansi) - -func visibleStringWidth(str string) int { - return runewidth.StringWidth(ansiRegex.ReplaceAllString(str, "")) -}