Compare commits

..

1 Commits

Author SHA1 Message Date
4ef849767f WIP
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-02 14:13:40 +01:00
856 changed files with 28476 additions and 43791 deletions

View File

@ -260,7 +260,6 @@ checkout as-is. Recipe commit hashes are also supported as values for
app.Name,
app.Server,
internal.DontWaitConverge,
internal.NoInput,
f,
); err != nil {
log.Fatal(err)

View File

@ -113,13 +113,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
totalAppsCount++
if status {
if err := app.Recipe.EnsureUpToDate(); err != nil {
log.Warnf(
"Failed to ensure repo is up to date for recipe: %s err: %s",
app.Recipe.Name,
err,
)
}
status := i18n.G("unknown")
version := i18n.G("unknown")
chaos := i18n.G("unknown")

View File

@ -128,7 +128,6 @@ Pass "--all-services/-a" to restart all services.`),
AppName: app.Name,
ServerName: app.Server,
Filters: f,
NoInput: internal.NoInput,
NoLog: true,
Quiet: true,
}

View File

@ -246,7 +246,6 @@ beforehand. See "abra app backup" for more.`),
stackName,
app.Server,
internal.DontWaitConverge,
internal.NoInput,
f,
); err != nil {
log.Fatal(err)

View File

@ -282,7 +282,6 @@ beforehand. See "abra app backup" for more.`),
stackName,
app.Server,
internal.DontWaitConverge,
internal.NoInput,
f,
); err != nil {
log.Fatal(err)

View File

@ -64,7 +64,7 @@ func DeployOverview(
server = "local"
}
domain := fmt.Sprintf("https://%s", app.Domain)
domain := app.Domain
if domain == "" {
domain = config.MISSING_DEFAULT
}

View File

@ -1,7 +1,6 @@
package recipe
import (
"errors"
"fmt"
"strconv"
"strings"
@ -20,9 +19,6 @@ import (
"github.com/spf13/cobra"
)
// Errors
var emptyVersionsInCatalogue = errors.New(i18n.G("catalogue versions list is unexpectedly empty"))
// translators: `abra recipe reset` aliases. use a comma separated list of
// aliases with no spaces in between
var recipeSyncAliases = i18n.G("s")
@ -125,18 +121,20 @@ likely to change.
log.Fatal(err)
}
versions, err := recipePkg.GetRecipeCatalogueVersions(recipe.Name, catl)
if err != nil {
log.Fatal(err)
}
changesTable, err := formatter.CreateTable()
if err != nil {
log.Fatal(err)
}
latestRelease := tags[len(tags)-1]
latestRecipeVersion, err := getLatestVersion(recipe, catl)
if err != nil && err != emptyVersionsInCatalogue {
log.Fatal(err)
}
changesTable.Headers(i18n.G("SERVICE"), latestRelease, i18n.G("PROPOSED CHANGES"))
latestRecipeVersion := versions[len(versions)-1]
allRecipeVersions := catl[recipe.Name].Versions
for _, recipeVersion := range allRecipeVersions {
if serviceVersions, ok := recipeVersion[latestRecipeVersion]; ok {
@ -300,14 +298,3 @@ func init() {
i18n.G("increase the patch part of the version"),
)
}
func getLatestVersion(recipe recipePkg.Recipe, catl recipePkg.RecipeCatalogue) (string, error) {
versions, err := recipePkg.GetRecipeCatalogueVersions(recipe.Name, catl)
if err != nil {
return "", err
}
if len(versions) > 0 {
return versions[len(versions)-1], nil
}
return "", emptyVersionsInCatalogue
}

View File

@ -1,33 +0,0 @@
package recipe
import (
"testing"
recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/stretchr/testify/assert"
)
func TestGetLatestVersionReturnsErrorWhenVersionsIsEmpty(t *testing.T) {
recipe := recipePkg.Recipe{}
catalogue := recipePkg.RecipeCatalogue{}
_, err := getLatestVersion(recipe, catalogue)
assert.Equal(t, err, emptyVersionsInCatalogue)
}
func TestGetLatestVersionReturnsLastVersion(t *testing.T) {
recipe := recipePkg.Recipe{
Name: "test",
}
versions := []map[string]map[string]recipePkg.ServiceMeta{
make(map[string]map[string]recipePkg.ServiceMeta),
make(map[string]map[string]recipePkg.ServiceMeta),
}
versions[0]["0.0.3"] = make(map[string]recipePkg.ServiceMeta)
versions[1]["0.0.2"] = make(map[string]recipePkg.ServiceMeta)
catalogue := make(recipePkg.RecipeCatalogue)
catalogue["test"] = recipePkg.RecipeMeta{
Versions: versions,
}
version, _ := getLatestVersion(recipe, catalogue)
assert.Equal(t, version, "0.0.3")
}

View File

@ -57,7 +57,9 @@ is up to the end-user to decide.
The command is interactive and will show a select input which allows you to
make a seclection. Use the "?" key to see more help on navigating this
interface.`),
interface.
You may invoke this command in "wizard" mode and be prompted for input.`),
Args: cobra.RangeArgs(0, 1),
ValidArgsFunction: func(
cmd *cobra.Command,

105
go.mod
View File

@ -1,27 +1,29 @@
module coopcloud.tech/abra
go 1.24.2
go 1.24.0
toolchain go1.24.1
require (
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/charmbracelet/bubbles v0.21.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/log v0.4.2
github.com/distribution/reference v0.6.0
github.com/docker/cli v29.1.5+incompatible
github.com/docker/docker v28.5.2+incompatible
github.com/docker/cli v28.4.0+incompatible
github.com/docker/docker v28.4.0+incompatible
github.com/docker/go-units v0.5.0
github.com/go-git/go-git/v5 v5.16.4
github.com/go-git/go-git/v5 v5.16.2
github.com/google/go-cmp v0.7.0
github.com/leonelquinteros/gotext v1.7.2
github.com/moby/moby/client v0.2.1
github.com/moby/sys/signal v0.7.1
github.com/moby/term v0.5.2
github.com/pkg/errors v0.9.1
github.com/schollz/progressbar/v3 v3.19.0
golang.org/x/term v0.39.0
github.com/schollz/progressbar/v3 v3.18.0
golang.org/x/term v0.35.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.2
)
@ -29,7 +31,7 @@ require (
require (
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
@ -37,21 +39,18 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/x/ansi v0.11.4 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.7.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/cloudflare/circl v1.6.2 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/x/ansi v0.10.2 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/go-connections v0.6.0 // indirect
@ -62,20 +61,20 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.7.0 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.4.0 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
@ -83,14 +82,14 @@ require (
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/api v1.53.0-rc.1 // indirect
github.com/moby/go-archive v0.1.0 // indirect
github.com/moby/sys/atomicwriter v0.1.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
github.com/morikuni/aec v1.1.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
@ -101,40 +100,39 @@ require (
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.19.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.4 // indirect
github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.13.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
@ -143,7 +141,7 @@ require (
github.com/containers/image v3.0.2+incompatible
github.com/containers/storage v1.38.2 // indirect
github.com/decentral1se/passgen v1.0.1
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gorilla/mux v1.8.1 // indirect
@ -153,8 +151,9 @@ require (
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/spf13/cobra v1.10.2
github.com/spf13/cobra v1.10.1
github.com/stretchr/testify v1.11.1
github.com/theupdateframework/notary v0.7.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
golang.org/x/sys v0.40.0
golang.org/x/sys v0.36.0
)

275
go.sum
View File

@ -49,8 +49,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
@ -85,6 +85,7 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
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=
@ -103,12 +104,14 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/beorn7/perks v0.0.0-20150223135152-b965b613227f/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
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 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
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/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
@ -117,6 +120,7 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dR
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
@ -125,29 +129,28 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
@ -163,16 +166,11 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE=
github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/cloudflare/circl v1.6.2 h1:hL7VBpHHKzrV5WTfHCaBsgx/HGbBYlgrwvNXEVDYYsQ=
github.com/cloudflare/circl v1.6.2/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@ -298,12 +296,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
@ -313,6 +311,7 @@ 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/decentral1se/passgen v1.0.1 h1:j2AxK/kHKxDHWZZfkJj8Wgae9+O+DYEqR5sjKthIYKA=
github.com/decentral1se/passgen v1.0.1/go.mod h1:530V+lNoPhKtkrX2fIVsIfLhkl47CuiOM7HRgi7C+SU=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0=
github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -321,25 +320,21 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.1.3+incompatible h1:+kz9uDWgs+mAaIZojWfFt4d53/jv0ZUOOoSh5ZnH36c=
github.com/docker/cli v29.1.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.1.4+incompatible h1:AI8fwZhqsAsrqZnVv9h6lbexeW/LzNTasf6A4vcNN8M=
github.com/docker/cli v29.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao=
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
github.com/docker/docker-credential-helpers v0.9.4 h1:76ItO69/AP/V4yT9V4uuuItG0B1N8hvt0T0c0NN/DzI=
github.com/docker/docker-credential-helpers v0.9.4/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
@ -358,6 +353,7 @@ github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZ
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
@ -373,6 +369,7 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
@ -394,12 +391,12 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
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-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -408,8 +405,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
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.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@ -428,11 +425,10 @@ github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
@ -441,12 +437,14 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU=
github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
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=
@ -481,6 +479,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
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=
@ -518,6 +517,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.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/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
@ -531,10 +531,10 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de
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/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4 h1:kEISI/Gx67NzH3nJxAmY/dGac80kKZgZt134u7Y/k1s=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.4/go.mod h1:6Nz966r3vQYCqIzWsuEl9d7cf7mRhtDmm++sOxlnfxI=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
@ -564,6 +564,9 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8=
@ -575,6 +578,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
@ -587,10 +591,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@ -612,9 +614,11 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -637,28 +641,29 @@ github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
github.com/mattn/go-sqlite3 v1.6.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/moby/api v1.53.0-rc.1 h1:M5SUwRbTrNy+plCTiV6gn4ZiN/Csynk0imIsUmOgHGI=
github.com/moby/moby/api v1.53.0-rc.1/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
@ -685,9 +690,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
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/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ=
github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
@ -755,6 +759,7 @@ github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqi
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@ -769,6 +774,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
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=
@ -789,10 +795,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc=
github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
@ -804,8 +808,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
@ -814,15 +818,13 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
@ -839,24 +841,26 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
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/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -864,6 +868,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -885,6 +890,8 @@ github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtse
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/tchap/go-patricia v2.3.0+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I=
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
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/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
@ -936,29 +943,29 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGNANqpVFCndZvcuyKbl0g+UAVcbBcqGkG28H0Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
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/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -967,27 +974,26 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/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-20190325154230-a5d413f7728c/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-20190611184440-5c40567a22f8/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-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
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=
@ -998,10 +1004,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -1065,10 +1069,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
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=
@ -1164,16 +1166,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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=
@ -1183,18 +1182,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
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/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1290,15 +1287,12 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3 h1:X9z6obt+cWRX8XjDVOn+SZWhWe5kZHm46TThU9j+jss=
google.golang.org/genproto/googleapis/api v0.0.0-20260114163908-3f89685c29c3/go.mod h1:dd646eSK+Dk9kxVBl1nChEOhJPtMXriCcVb4x3o6J+E=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -1316,8 +1310,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1331,10 +1325,11 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -1349,6 +1344,7 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKW
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@ -1368,7 +1364,6 @@ 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=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
@ -1410,8 +1405,6 @@ k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -633,11 +633,6 @@ func (a App) WipeRecipeVersion() error {
// WriteRecipeVersion writes the recipe version to the app .env file.
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
if version == config.UNKNOWN_DEFAULT {
log.Debug(i18n.G("version is unknown, skipping env write"))
return nil
}
file, err := os.Open(a.Path)
if err != nil {
return err

View File

@ -224,16 +224,3 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
assert.Equal(t, "foo", app.Recipe.EnvVersion)
}
func TestWriteRecipeVersionUnknown(t *testing.T) {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
if err != nil {
t.Fatal(err)
}
if err := app.WriteRecipeVersion(config.UNKNOWN_DEFAULT, false); err != nil {
t.Fatal(err)
}
assert.NotEqual(t, config.UNKNOWN_DEFAULT, app.Recipe.EnvVersion)
}

View File

@ -37,27 +37,18 @@ func WithTimeout(timeout int) Opt {
// New initiates a new Docker client. New client connections are validated so
// that we ensure connections via SSH to the daemon can succeed. It takes into
// account that you may only want the local client and not communicate via SSH.
// For this use-case, please pass "default" as the serverName.
// For this use-case, please pass "default" as the contextName.
func New(serverName string, opts ...Opt) (*client.Client, error) {
var clientOpts []client.Opt
ctx, err := GetContext(serverName)
if err != nil {
serverDir := path.Join(config.SERVERS_DIR, serverName)
if _, err := os.Stat(serverDir); err != nil {
return nil, errors.New(i18n.G("server missing, run \"abra server add %s\"?", serverName))
}
// NOTE(p4u1): when the docker context does not exist but the server folder
// is there, let's create a new docker context.
if err = CreateContext(serverName); err != nil {
return nil, errors.New(i18n.G("server missing context, context creation failed: %s", err))
}
ctx, err = GetContext(serverName)
if err != nil {
if _, err := os.Stat(serverDir); err == nil {
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
}
return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName))
}
ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)

View File

@ -2,18 +2,13 @@ package context
import (
"errors"
"os"
"coopcloud.tech/abra/pkg/i18n"
"github.com/docker/cli/cli/command"
dConfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/context"
"github.com/docker/cli/cli/context/docker"
"github.com/docker/cli/cli/context/store"
contextStore "github.com/docker/cli/cli/context/store"
cliflags "github.com/docker/cli/cli/flags"
dopts "github.com/docker/cli/opts"
"github.com/moby/moby/client"
)
func NewDefaultDockerContextStore() *command.ContextStoreWithDefault {
@ -26,107 +21,13 @@ func NewDefaultDockerContextStore() *command.ContextStoreWithDefault {
dockerContextStore := &command.ContextStoreWithDefault{
Store: store,
Resolver: func() (*command.DefaultContext, error) {
return resolveDefaultContext(opts, storeConfig)
return command.ResolveDefaultContext(opts, storeConfig)
},
}
return dockerContextStore
}
// resolveDefaultContext creates a Metadata for the current CLI invocation parameters
func resolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*command.DefaultContext, error) {
contextTLSData := store.ContextTLSData{
Endpoints: make(map[string]store.EndpointTLSData),
}
contextMetadata := store.Metadata{
Endpoints: make(map[string]any),
Metadata: command.DockerContext{
Description: "",
},
Name: "default",
}
dockerEP, err := resolveDefaultDockerEndpoint(opts)
if err != nil {
return nil, err
}
contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP.EndpointMeta
if dockerEP.TLSData != nil {
contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerEP.TLSData.ToStoreTLSData()
}
if err := config.ForeachEndpointType(func(n string, get store.TypeGetter) error {
if n == docker.DockerEndpoint { // handled above
return nil
}
ep := get()
if i, ok := ep.(command.EndpointDefaultResolver); ok {
meta, tls, err := i.ResolveDefault()
if err != nil {
return err
}
if meta == nil {
return nil
}
contextMetadata.Endpoints[n] = meta
if tls != nil {
contextTLSData.Endpoints[n] = *tls
}
}
// Nothing to be done
return nil
}); err != nil {
return nil, err
}
return &command.DefaultContext{Meta: contextMetadata, TLS: contextTLSData}, nil
}
// Resolve the Docker endpoint for the default context (based on config, env vars and CLI flags)
func resolveDefaultDockerEndpoint(opts *cliflags.ClientOptions) (docker.Endpoint, error) {
// defaultToTLS determines whether we should use a TLS host as default
// if nothing was configured by the user.
defaultToTLS := opts.TLSOptions != nil
host, err := getServerHost(opts.Hosts, defaultToTLS)
if err != nil {
return docker.Endpoint{}, err
}
var (
skipTLSVerify bool
tlsData *context.TLSData
)
if opts.TLSOptions != nil {
skipTLSVerify = opts.TLSOptions.InsecureSkipVerify
tlsData, err = context.TLSDataFromFiles(opts.TLSOptions.CAFile, opts.TLSOptions.CertFile, opts.TLSOptions.KeyFile)
if err != nil {
return docker.Endpoint{}, err
}
}
return docker.Endpoint{
EndpointMeta: docker.EndpointMeta{
Host: host,
SkipTLSVerify: skipTLSVerify,
},
TLSData: tlsData,
}, nil
}
func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
switch len(hosts) {
case 0:
return dopts.ParseHost(defaultToTLS, os.Getenv(client.EnvOverrideHost))
case 1:
return dopts.ParseHost(defaultToTLS, hosts[0])
default:
return "", errors.New("specify only one -H")
}
}
func GetContextEndpoint(ctx contextStore.Metadata) (string, error) {
endpointmeta, ok := ctx.Endpoints["docker"].(context.EndpointMetaBase)
if !ok {

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ import (
"coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/logs"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/docker/cli/cli/command/service/progress"
containerTypes "github.com/docker/docker/api/types/container"
@ -41,6 +42,12 @@ type ServiceMeta struct {
ID string
}
const (
statusMode = iota
logsMode = iota
errorsMode = iota
)
type Model struct {
appName string
cl *dockerClient.Client
@ -49,6 +56,10 @@ type Model struct {
timeout time.Duration
width int
filters filters.Args
mode int
logsViewport viewport.Model
logsViewportReady bool
Streams *[]stream
Logs *[]string
@ -236,7 +247,10 @@ func deployTimeout(m Model) tea.Msg {
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
var (
cmd tea.Cmd
cmds []tea.Cmd
)
switch msg := msg.(type) {
case tea.KeyMsg:
@ -244,11 +258,25 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "ctrl+c", "q":
m.Quit = true
return m, tea.Quit
case "s":
m.mode = statusMode
case "l":
m.mode = logsMode
case "e":
m.mode = errorsMode
}
case tea.WindowSizeMsg:
m.width = msg.Width
if !m.logsViewportReady {
m.logsViewport = viewport.New(msg.Width, 20)
m.logsViewportReady = true
} else {
m.logsViewport.Width = msg.Width
m.logsViewport.Height = 20
}
case progressCompleteMsg:
if msg.failed {
m.Failed = true
@ -256,9 +284,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.count += 1
if m.complete() {
return m, tea.Quit
}
// if m.complete() {
// return m, tea.Quit
// }
case timeoutMsg:
m.TimedOut = true
@ -318,12 +346,46 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
)
}
m.logsViewport, cmd = m.logsViewport.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m Model) View() string {
body := strings.Builder{}
body.WriteString("menu: [s]tatus [l]ogs [e]rrors\n")
var res string
switch {
case m.mode == statusMode:
res = statusView(m)
case m.mode == logsMode:
res = logsView(m)
}
return body.String() + res
}
func logsView(m Model) string {
body := strings.Builder{}
m.logsViewport.SetContent(strings.Join(*m.Logs, "\n"))
m.logsViewport.GotoBottom()
body.WriteString(m.logsViewport.View())
return body.String()
}
func errorsView(m Model) string {
body := strings.Builder{}
body.WriteString("ERRORS COMING SOON")
return body.String()
}
func statusView(m Model) string {
body := strings.Builder{}
for _, stream := range *m.Streams {
split := strings.Split(stream.Name, "_")
short := split[len(split)-1]

View File

@ -11,7 +11,7 @@ import (
"github.com/docker/cli/cli/connhelper/ssh"
"github.com/docker/cli/cli/context/docker"
dCliContextStore "github.com/docker/cli/cli/context/store"
dClient "github.com/moby/moby/client"
dClient "github.com/docker/docker/client"
"github.com/pkg/errors"
)
@ -73,7 +73,7 @@ func getDockerEndpoint(host string) (docker.Endpoint, error) {
if err != nil {
return docker.Endpoint{}, err
}
if _, err := dClient.New(opts...); err != nil {
if _, err := dClient.NewClientWithOpts(opts...); err != nil {
return docker.Endpoint{}, err
}
return ep, nil

View File

@ -19,7 +19,7 @@ import (
"coopcloud.tech/abra/pkg/ui"
"coopcloud.tech/abra/pkg/upstream/convert"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/formatter"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
@ -201,7 +201,6 @@ func RunDeploy(
appName string,
serverName string,
dontWait bool,
noInput bool,
filters filters.Args,
) error {
log.Info(i18n.G("initialising deployment"))
@ -227,7 +226,6 @@ func RunDeploy(
appName,
serverName,
dontWait,
noInput,
filters,
)
}
@ -250,7 +248,6 @@ func deployCompose(
appName string,
serverName string,
dontWait bool,
noInput bool,
filters filters.Args,
) error {
namespace := convert.NewNamespace(opts.Namespace)
@ -314,7 +311,6 @@ func deployCompose(
Services: serviceIDs,
AppName: appName,
ServerName: serverName,
NoInput: noInput,
Filters: filters,
}
@ -565,7 +561,6 @@ func timestamp() string {
type WaitOpts struct {
AppName string
Filters filters.Args
NoInput bool
NoLog bool
Quiet bool
ServerName string
@ -575,13 +570,7 @@ type WaitOpts struct {
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
timeout := time.Duration(WaitTimeout) * time.Second
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
var tui *tea.Program
if opts.NoInput {
tui = tea.NewProgram(model, tea.WithoutRenderer(), tea.WithInput(nil))
} else {
tui = tea.NewProgram(model)
}
tui := tea.NewProgram(model)
if !opts.Quiet {
log.Info(i18n.G("polling deployment status"))

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash
ABRA_VERSION="0.12.0-beta"
ABRA_VERSION="0.11.0-beta"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION"
RC_VERSION="0.12.0-beta"
RC_VERSION="0.11.0-beta"
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION"
for arg in "$@"; do
@ -14,15 +14,15 @@ done
function show_banner {
echo ""
echo " ____ ____ _ _ "
echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |"
echo " | | / _ \ ___ / _ \| '_ \ | | | |/ _ \| | | |/ _' |"
echo " | |__| (_) |___| (_) | |_) | | |___| | (_) | |_| | (_| |"
echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|"
echo " |_|"
echo " ____ ____ _ _ "
echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |"
echo " | | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |"
echo " | |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |"
echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|"
echo " |_|"
echo ""
echo ""
echo " === Public interest infrastructure === "
echo " === Public interest infrastructure === "
echo ""
echo ""
}
@ -89,7 +89,7 @@ function install_abra_release {
if [ $? -ne 0 ]; then
echo "$(tput setaf 3)WARNING: $HOME/.local/bin/ is not in \$PATH! If you want to run abra by just typing "abra" you should add it to your \$PATH! To do that run this once and restart your terminal:$(tput sgr0)"
p=$HOME/.local/bin
com='echo PATH="$PATH:'"$p"'"'
com="echo PATH=\$PATH:$p"
if [[ $SHELL =~ "bash" ]]; then
echo "$com >> $HOME/.bashrc"
elif [[ $SHELL =~ "fizsh" ]]; then

View File

@ -543,7 +543,7 @@ teardown(){
# bats test_tags=slow
@test "ignore timeout when not present in env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
assert_success
refute_output --partial "timeout: set to"
}
@ -554,7 +554,6 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
# NOTE(d1}: --debug required
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
assert_success
assert_output --partial "timeout: set to 120"
@ -580,25 +579,16 @@ teardown(){
}
# bats test_tags=slow
@test "re-deploy updates existing env vars" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input
@test "manually created server without context bails gracefully" {
run mkdir -p "$ABRA_DIR/servers/default2"
assert_success
assert_exists "$ABRA_DIR/servers/default2"
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
assert_success
assert_output --partial "WITH_COMMENT=foo"
assert_exists "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
run sed -i 's/WITH_COMMENT=foo/WITH_COMMENT=bar/g' \
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --force
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
refute_output --partial "WITH_COMMENT=foo"
assert_output --partial "WITH_COMMENT=bar"
run $ABRA app deploy "$TEST_APP_DOMAIN_2" --no-input --no-converge-checks
assert_failure
assert_output --partial "server missing context"
}

View File

@ -68,13 +68,6 @@ teardown(){
assert_success
}
@test "domain shown with https" {
run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks
assert_success
assert_output --partial "https://$TEST_DOMAIN"
}
# bats test_tags=slow
@test "show changed config version on re-deploy" {
run $ABRA app deploy "$TEST_APP_DOMAIN" \

View File

@ -67,16 +67,6 @@ teardown(){
assert_output --partial "$TEST_SERVER"
assert_output --partial "$TEST_APP_DOMAIN"
assert_output --partial "deployed"
assert_output --partial "latest"
_remove_tags
run $ABRA app ls --status
assert_success
assert_output --partial "$TEST_SERVER"
assert_output --partial "$TEST_APP_DOMAIN"
assert_output --partial "deployed"
assert_output --partial "latest"
}
@test "filter by server" {
@ -170,6 +160,23 @@ teardown(){
assert_not_exists "$ABRA_DIR/servers/foo.com"
}
@test "list with status skips unknown servers" {
if [[ ! -d "$ABRA_DIR/servers/foo" ]]; then
run mkdir -p "$ABRA_DIR/servers/foo"
assert_success
assert_exists "$ABRA_DIR/servers/foo"
run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \
"$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
assert_success
assert_exists "$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
fi
run $ABRA app ls --status
assert_success
assert_output --partial "server missing context"
}
# bats test_tags=slow
@test "list does not fail if missing .env" {
_deploy_app

View File

@ -17,12 +17,7 @@ _remove_tags(){
run bash -c 'git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag -l | wc -l'
assert_success
# If this was done without the --regexp I get this error:
# -- output differs --
# expected : 0
# actual : 0
# --
assert_output --regexp '[[:space:]]0'
assert_output '0'
}
_reset_tags() {

View File

@ -19,10 +19,6 @@ setup(){
teardown(){
_reset_recipe
_reset_tags
if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then
run rm -rf "$ABRA_DIR/recipes/foobar"
assert_success
fi
}
@test "validate recipe argument" {
@ -130,71 +126,3 @@ teardown(){
assert_line --index 0 --partial 'synced label'
refute_line --index 1 --partial 'synced label'
}
@test "sync with no tags or previous release" {
_remove_tags
run $ABRA recipe upgrade "$TEST_RECIPE" --no-input --patch
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --partial 'image: nginx:1.21.6'
# NOTE(d1): ensure the latest tag is the one we expect
_remove_tags
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag \
-a "0.3.0+1.21.0" -m "fake: 0.3.0+1.21.0"
assert_success
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.3\.1\+1\.2.*'
}
@test "sync recipe without input fails with prompt" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run $ABRA recipe sync foobar --no-input --patch
assert_failure
assert_output --partial "input required for initial version"
}
@test "sync new recipe: development release" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.1\.0\+1\.2.*'
}
@test "sync new recipe: public release" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run bash -c "echo 1.0.0 | $ABRA recipe sync foobar --patch"
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=1\.0\.0\+1\.2.*'
}
@test "sync newly created recipe with no version label" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run sed -i 's/- "coop-cloud.${STACK_NAME}.version="/#- "coop-cloud.${STACK_NAME}.version="/g' \
"$ABRA_DIR/recipes/foobar/compose.yml"
assert_success
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
assert_failure
assert_output --partial "automagic insertion not supported yet"
}

View File

@ -1,7 +1,7 @@
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml` packages.
Compatible with TOML version [v1.1.0](https://toml.io/en/v1.1.0).
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
Documentation: https://pkg.go.dev/github.com/BurntSushi/toml

View File

@ -206,13 +206,6 @@ func markDecodedRecursive(md *MetaData, tmap map[string]any) {
markDecodedRecursive(md, tmap)
md.context = md.context[0 : len(md.context)-1]
}
if tarr, ok := tmap[key].([]map[string]any); ok {
for _, elm := range tarr {
md.context = append(md.context, key)
markDecodedRecursive(md, elm)
md.context = md.context[0 : len(md.context)-1]
}
}
}
}
@ -430,7 +423,7 @@ func (md *MetaData) unifyString(data any, rv reflect.Value) error {
if i, ok := data.(int64); ok {
rv.SetString(strconv.FormatInt(i, 10))
} else if f, ok := data.(float64); ok {
rv.SetString(strconv.FormatFloat(f, 'g', -1, 64))
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
} else {
return md.badtype("string", data)
}

View File

@ -228,9 +228,9 @@ func (enc *Encoder) eElement(rv reflect.Value) {
}
switch v.Location() {
default:
enc.write(v.Format(format))
enc.wf(v.Format(format))
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
enc.write(v.In(time.UTC).Format(format))
enc.wf(v.In(time.UTC).Format(format))
}
return
case Marshaler:
@ -279,40 +279,40 @@ func (enc *Encoder) eElement(rv reflect.Value) {
case reflect.String:
enc.writeQuoted(rv.String())
case reflect.Bool:
enc.write(strconv.FormatBool(rv.Bool()))
enc.wf(strconv.FormatBool(rv.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
enc.write(strconv.FormatInt(rv.Int(), 10))
enc.wf(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
enc.write(strconv.FormatUint(rv.Uint(), 10))
enc.wf(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32:
f := rv.Float()
if math.IsNaN(f) {
if math.Signbit(f) {
enc.write("-")
enc.wf("-")
}
enc.write("nan")
enc.wf("nan")
} else if math.IsInf(f, 0) {
if math.Signbit(f) {
enc.write("-")
enc.wf("-")
}
enc.write("inf")
enc.wf("inf")
} else {
enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 32)))
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
}
case reflect.Float64:
f := rv.Float()
if math.IsNaN(f) {
if math.Signbit(f) {
enc.write("-")
enc.wf("-")
}
enc.write("nan")
enc.wf("nan")
} else if math.IsInf(f, 0) {
if math.Signbit(f) {
enc.write("-")
enc.wf("-")
}
enc.write("inf")
enc.wf("inf")
} else {
enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 64)))
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
}
case reflect.Array, reflect.Slice:
enc.eArrayOrSliceElement(rv)
@ -330,32 +330,27 @@ func (enc *Encoder) eElement(rv reflect.Value) {
// By the TOML spec, all floats must have a decimal with at least one number on
// either side.
func floatAddDecimal(fstr string) string {
for _, c := range fstr {
if c == 'e' { // Exponent syntax
return fstr
}
if c == '.' {
return fstr
}
if !strings.Contains(fstr, ".") {
return fstr + ".0"
}
return fstr + ".0"
return fstr
}
func (enc *Encoder) writeQuoted(s string) {
enc.write(`"` + dblQuotedReplacer.Replace(s) + `"`)
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
}
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
length := rv.Len()
enc.write("[")
enc.wf("[")
for i := 0; i < length; i++ {
elem := eindirect(rv.Index(i))
enc.eElement(elem)
if i != length-1 {
enc.write(", ")
enc.wf(", ")
}
}
enc.write("]")
enc.wf("]")
}
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
@ -368,7 +363,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
continue
}
enc.newline()
enc.writef("%s[[%s]]", enc.indentStr(key), key)
enc.wf("%s[[%s]]", enc.indentStr(key), key)
enc.newline()
enc.eMapOrStruct(key, trv, false)
}
@ -381,7 +376,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
enc.newline()
}
if len(key) > 0 {
enc.writef("%s[%s]", enc.indentStr(key), key)
enc.wf("%s[%s]", enc.indentStr(key), key)
enc.newline()
}
enc.eMapOrStruct(key, rv, false)
@ -427,7 +422,7 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
if inline {
enc.writeKeyValue(Key{mapKey.String()}, val, true)
if trailC || i != len(mapKeys)-1 {
enc.write(", ")
enc.wf(", ")
}
} else {
enc.encode(key.add(mapKey.String()), val)
@ -436,12 +431,12 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
}
if inline {
enc.write("{")
enc.wf("{")
}
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
writeMapKeys(mapKeysSub, false)
if inline {
enc.write("}")
enc.wf("}")
}
}
@ -539,7 +534,7 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
if inline {
enc.writeKeyValue(Key{keyName}, fieldVal, true)
if fieldIndex[0] != totalFields-1 {
enc.write(", ")
enc.wf(", ")
}
} else {
enc.encode(key.add(keyName), fieldVal)
@ -548,14 +543,14 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
}
if inline {
enc.write("{")
enc.wf("{")
}
l := len(fieldsDirect) + len(fieldsSub)
writeFields(fieldsDirect, l)
writeFields(fieldsSub, l)
if inline {
enc.write("}")
enc.wf("}")
}
}
@ -705,7 +700,7 @@ func isEmpty(rv reflect.Value) bool {
func (enc *Encoder) newline() {
if enc.hasWritten {
enc.write("\n")
enc.wf("\n")
}
}
@ -727,22 +722,14 @@ func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
enc.eElement(val)
return
}
enc.writef("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.eElement(val)
if !inline {
enc.newline()
}
}
func (enc *Encoder) write(s string) {
_, err := enc.w.WriteString(s)
if err != nil {
encPanic(err)
}
enc.hasWritten = true
}
func (enc *Encoder) writef(format string, v ...any) {
func (enc *Encoder) wf(format string, v ...any) {
_, err := fmt.Fprintf(enc.w, format, v...)
if err != nil {
encPanic(err)

View File

@ -13,6 +13,7 @@ type itemType int
const (
itemError itemType = iota
itemNIL // used in the parser to indicate no type
itemEOF
itemText
itemString
@ -46,13 +47,14 @@ func (p Position) String() string {
}
type lexer struct {
input string
start int
pos int
line int
state stateFn
items chan item
esc bool
input string
start int
pos int
line int
state stateFn
items chan item
tomlNext bool
esc bool
// Allow for backing up up to 4 runes. This is necessary because TOML
// contains 3-rune tokens (""" and ''').
@ -88,13 +90,14 @@ func (lx *lexer) nextItem() item {
}
}
func lex(input string) *lexer {
func lex(input string, tomlNext bool) *lexer {
lx := &lexer{
input: input,
state: lexTop,
items: make(chan item, 10),
stack: make([]stateFn, 0, 10),
line: 1,
input: input,
state: lexTop,
items: make(chan item, 10),
stack: make([]stateFn, 0, 10),
line: 1,
tomlNext: tomlNext,
}
return lx
}
@ -105,7 +108,7 @@ func (lx *lexer) push(state stateFn) {
func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 {
panic("BUG in lexer: no states to pop")
return lx.errorf("BUG in lexer: no states to pop")
}
last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1]
@ -302,8 +305,6 @@ func lexTop(lx *lexer) stateFn {
return lexTableStart
case eof:
if lx.pos > lx.start {
// TODO: never reached? I think this can only occur on a bug in the
// lexer(?)
return lx.errorf("unexpected EOF")
}
lx.emit(itemEOF)
@ -391,6 +392,8 @@ func lexTableNameStart(lx *lexer) stateFn {
func lexTableNameEnd(lx *lexer) stateFn {
lx.skip(isWhitespace)
switch r := lx.next(); {
case isWhitespace(r):
return lexTableNameEnd
case r == '.':
lx.ignore()
return lexTableNameStart
@ -409,7 +412,7 @@ func lexTableNameEnd(lx *lexer) stateFn {
// Lexes only one part, e.g. only 'a' inside 'a.b'.
func lexBareName(lx *lexer) stateFn {
r := lx.next()
if isBareKeyChar(r) {
if isBareKeyChar(r, lx.tomlNext) {
return lexBareName
}
lx.backup()
@ -417,23 +420,23 @@ func lexBareName(lx *lexer) stateFn {
return lx.pop()
}
// lexQuotedName lexes one part of a quoted key or table name. It assumes that
// it starts lexing at the quote itself (" or ').
// lexBareName lexes one part of a key or table.
//
// It assumes that at least one valid character for the table has already been
// read.
//
// Lexes only one part, e.g. only '"a"' inside '"a".b'.
func lexQuotedName(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexValue)
case r == '"':
lx.ignore() // ignore the '"'
return lexString
case r == '\'':
lx.ignore() // ignore the "'"
return lexRawString
// TODO: I don't think any of the below conditions can ever be reached?
case isWhitespace(r):
return lexSkip(lx, lexValue)
case r == eof:
return lx.errorf("unexpected EOF; expected value")
default:
@ -461,19 +464,17 @@ func lexKeyStart(lx *lexer) stateFn {
func lexKeyNameStart(lx *lexer) stateFn {
lx.skip(isWhitespace)
switch r := lx.peek(); {
default:
lx.push(lexKeyEnd)
return lexBareName
case r == '"' || r == '\'':
lx.ignore()
lx.push(lexKeyEnd)
return lexQuotedName
// TODO: I think these can never be reached?
case r == '=' || r == eof:
return lx.errorf("unexpected '='")
case r == '.':
return lx.errorf("unexpected '.'")
case r == '"' || r == '\'':
lx.ignore()
lx.push(lexKeyEnd)
return lexQuotedName
default:
lx.push(lexKeyEnd)
return lexBareName
}
}
@ -484,7 +485,7 @@ func lexKeyEnd(lx *lexer) stateFn {
switch r := lx.next(); {
case isWhitespace(r):
return lexSkip(lx, lexKeyEnd)
case r == eof: // TODO: never reached
case r == eof:
return lx.errorf("unexpected EOF; expected key separator '='")
case r == '.':
lx.ignore()
@ -627,7 +628,10 @@ func lexInlineTableValue(lx *lexer) stateFn {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValue)
case isNL(r):
return lexSkip(lx, lexInlineTableValue)
if lx.tomlNext {
return lexSkip(lx, lexInlineTableValue)
}
return lx.errorPrevLine(errLexInlineTableNL{})
case r == '#':
lx.push(lexInlineTableValue)
return lexCommentStart
@ -649,7 +653,10 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValueEnd)
case isNL(r):
return lexSkip(lx, lexInlineTableValueEnd)
if lx.tomlNext {
return lexSkip(lx, lexInlineTableValueEnd)
}
return lx.errorPrevLine(errLexInlineTableNL{})
case r == '#':
lx.push(lexInlineTableValueEnd)
return lexCommentStart
@ -657,7 +664,10 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
lx.ignore()
lx.skip(isWhitespace)
if lx.peek() == '}' {
return lexInlineTableValueEnd
if lx.tomlNext {
return lexInlineTableValueEnd
}
return lx.errorf("trailing comma not allowed in inline tables")
}
return lexInlineTableValue
case r == '}':
@ -845,6 +855,9 @@ func lexStringEscape(lx *lexer) stateFn {
r := lx.next()
switch r {
case 'e':
if !lx.tomlNext {
return lx.error(errLexEscape{r})
}
fallthrough
case 'b':
fallthrough
@ -865,6 +878,9 @@ func lexStringEscape(lx *lexer) stateFn {
case '\\':
return lx.pop()
case 'x':
if !lx.tomlNext {
return lx.error(errLexEscape{r})
}
return lexHexEscape
case 'u':
return lexShortUnicodeEscape
@ -912,9 +928,19 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
// lexBaseNumberOrDate can differentiate base prefixed integers from other
// types.
func lexNumberOrDateStart(lx *lexer) stateFn {
if lx.next() == '0' {
r := lx.next()
switch r {
case '0':
return lexBaseNumberOrDate
}
if !isDigit(r) {
// The only way to reach this state is if the value starts
// with a digit, so specifically treat anything else as an
// error.
return lx.errorf("expected a digit but got %q", r)
}
return lexNumberOrDate
}
@ -1170,13 +1196,13 @@ func lexSkip(lx *lexer, nextState stateFn) stateFn {
}
func (s stateFn) String() string {
if s == nil {
return "<nil>"
}
name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
if i := strings.LastIndexByte(name, '.'); i > -1 {
name = name[i+1:]
}
if s == nil {
name = "<nil>"
}
return name + "()"
}
@ -1184,6 +1210,8 @@ func (itype itemType) String() string {
switch itype {
case itemError:
return "Error"
case itemNIL:
return "NIL"
case itemEOF:
return "EOF"
case itemText:
@ -1198,22 +1226,18 @@ func (itype itemType) String() string {
return "Float"
case itemDatetime:
return "DateTime"
case itemArray:
return "Array"
case itemArrayEnd:
return "ArrayEnd"
case itemTableStart:
return "TableStart"
case itemTableEnd:
return "TableEnd"
case itemArrayTableStart:
return "ArrayTableStart"
case itemArrayTableEnd:
return "ArrayTableEnd"
case itemKeyStart:
return "KeyStart"
case itemKeyEnd:
return "KeyEnd"
case itemArray:
return "Array"
case itemArrayEnd:
return "ArrayEnd"
case itemCommentStart:
return "CommentStart"
case itemInlineTableStart:
@ -1242,7 +1266,7 @@ func isDigit(r rune) bool { return r >= '0' && r <= '9' }
func isBinary(r rune) bool { return r == '0' || r == '1' }
func isOctal(r rune) bool { return r >= '0' && r <= '7' }
func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r|0x20 >= 'a' && r|0x20 <= 'f') }
func isBareKeyChar(r rune) bool {
func isBareKeyChar(r rune, tomlNext bool) bool {
return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') || r == '_' || r == '-'
}

View File

@ -3,6 +3,7 @@ package toml
import (
"fmt"
"math"
"os"
"strconv"
"strings"
"time"
@ -16,6 +17,7 @@ type parser struct {
context Key // Full key for the current hash in scope.
currentKey string // Base key name for everything except hashes.
pos Position // Current position in the TOML file.
tomlNext bool
ordered []Key // List of keys in the order that they appear in the TOML data.
@ -30,6 +32,8 @@ type keyInfo struct {
}
func parse(data string) (p *parser, err error) {
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
defer func() {
if r := recover(); r != nil {
if pErr, ok := r.(ParseError); ok {
@ -69,9 +73,10 @@ func parse(data string) (p *parser, err error) {
p = &parser{
keyInfo: make(map[string]keyInfo),
mapping: make(map[string]any),
lx: lex(data),
lx: lex(data, tomlNext),
ordered: make([]Key, 0),
implicits: make(map[string]struct{}),
tomlNext: tomlNext,
}
for {
item := p.next()
@ -345,14 +350,17 @@ func (p *parser) valueFloat(it item) (any, tomlType) {
var dtTypes = []struct {
fmt string
zone *time.Location
next bool
}{
{time.RFC3339Nano, time.Local},
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime},
{"2006-01-02", internal.LocalDate},
{"15:04:05.999999999", internal.LocalTime},
{"2006-01-02T15:04Z07:00", time.Local},
{"2006-01-02T15:04", internal.LocalDatetime},
{"15:04", internal.LocalTime},
{time.RFC3339Nano, time.Local, false},
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false},
{"2006-01-02", internal.LocalDate, false},
{"15:04:05.999999999", internal.LocalTime, false},
// tomlNext
{"2006-01-02T15:04Z07:00", time.Local, true},
{"2006-01-02T15:04", internal.LocalDatetime, true},
{"15:04", internal.LocalTime, true},
}
func (p *parser) valueDatetime(it item) (any, tomlType) {
@ -363,6 +371,9 @@ func (p *parser) valueDatetime(it item) (any, tomlType) {
err error
)
for _, dt := range dtTypes {
if dt.next && !p.tomlNext {
continue
}
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
if err == nil {
if missingLeadingZero(it.val, dt.fmt) {
@ -633,11 +644,6 @@ func (p *parser) setValue(key string, value any) {
// Note that since it has already been defined (as a hash), we don't
// want to overwrite it. So our business is done.
if p.isArray(keyContext) {
if !p.isImplicit(keyContext) {
if _, ok := hash[key]; ok {
p.panicf("Key '%s' has already been defined.", keyContext)
}
}
p.removeImplicit(keyContext)
hash[key] = value
return
@ -796,8 +802,10 @@ func (p *parser) replaceEscapes(it item, str string) string {
b.WriteByte(0x0d)
skip = 1
case 'e':
b.WriteByte(0x1b)
skip = 1
if p.tomlNext {
b.WriteByte(0x1b)
skip = 1
}
case '"':
b.WriteByte(0x22)
skip = 1
@ -807,9 +815,11 @@ func (p *parser) replaceEscapes(it item, str string) string {
// The lexer guarantees the correct number of characters are present;
// don't need to check here.
case 'x':
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
b.WriteRune(escaped)
skip = 3
if p.tomlNext {
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
b.WriteRune(escaped)
skip = 3
}
case 'u':
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6])
b.WriteRune(escaped)

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 Matt Sherman
Copyright (c) 2020-2023 Charmbracelet, Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

140
vendor/github.com/charmbracelet/bubbles/key/key.go generated vendored Normal file
View File

@ -0,0 +1,140 @@
// Package key provides some types and functions for generating user-definable
// keymappings useful in Bubble Tea components. There are a few different ways
// you can define a keymapping with this package. Here's one example:
//
// type KeyMap struct {
// Up key.Binding
// Down key.Binding
// }
//
// var DefaultKeyMap = KeyMap{
// Up: key.NewBinding(
// key.WithKeys("k", "up"), // actual keybindings
// key.WithHelp("↑/k", "move up"), // corresponding help text
// ),
// Down: key.NewBinding(
// key.WithKeys("j", "down"),
// key.WithHelp("↓/j", "move down"),
// ),
// }
//
// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// switch msg := msg.(type) {
// case tea.KeyMsg:
// switch {
// case key.Matches(msg, DefaultKeyMap.Up):
// // The user pressed up
// case key.Matches(msg, DefaultKeyMap.Down):
// // The user pressed down
// }
// }
//
// // ...
// }
//
// The help information, which is not used in the example above, can be used
// to render help text for keystrokes in your views.
package key
import "fmt"
// Binding describes a set of keybindings and, optionally, their associated
// help text.
type Binding struct {
keys []string
help Help
disabled bool
}
// BindingOpt is an initialization option for a keybinding. It's used as an
// argument to NewBinding.
type BindingOpt func(*Binding)
// NewBinding returns a new keybinding from a set of BindingOpt options.
func NewBinding(opts ...BindingOpt) Binding {
b := &Binding{}
for _, opt := range opts {
opt(b)
}
return *b
}
// WithKeys initializes a keybinding with the given keystrokes.
func WithKeys(keys ...string) BindingOpt {
return func(b *Binding) {
b.keys = keys
}
}
// WithHelp initializes a keybinding with the given help text.
func WithHelp(key, desc string) BindingOpt {
return func(b *Binding) {
b.help = Help{Key: key, Desc: desc}
}
}
// WithDisabled initializes a disabled keybinding.
func WithDisabled() BindingOpt {
return func(b *Binding) {
b.disabled = true
}
}
// SetKeys sets the keys for the keybinding.
func (b *Binding) SetKeys(keys ...string) {
b.keys = keys
}
// Keys returns the keys for the keybinding.
func (b Binding) Keys() []string {
return b.keys
}
// SetHelp sets the help text for the keybinding.
func (b *Binding) SetHelp(key, desc string) {
b.help = Help{Key: key, Desc: desc}
}
// Help returns the Help information for the keybinding.
func (b Binding) Help() Help {
return b.help
}
// Enabled returns whether or not the keybinding is enabled. Disabled
// keybindings won't be activated and won't show up in help. Keybindings are
// enabled by default.
func (b Binding) Enabled() bool {
return !b.disabled && b.keys != nil
}
// SetEnabled enables or disables the keybinding.
func (b *Binding) SetEnabled(v bool) {
b.disabled = !v
}
// Unbind removes the keys and help from this binding, effectively nullifying
// it. This is a step beyond disabling it, since applications can enable
// or disable key bindings based on application state.
func (b *Binding) Unbind() {
b.keys = nil
b.help = Help{}
}
// Help is help information for a given keybinding.
type Help struct {
Key string
Desc string
}
// Matches checks if the given key matches the given bindings.
func Matches[Key fmt.Stringer](k Key, b ...Binding) bool {
keys := k.String()
for _, binding := range b {
for _, v := range binding.keys {
if keys == v && binding.Enabled() {
return true
}
}
}
return false
}

View File

@ -0,0 +1,60 @@
// Package viewport provides a component for rendering a viewport in a Bubble
// Tea.
package viewport
import "github.com/charmbracelet/bubbles/key"
const spacebar = " "
// KeyMap defines the keybindings for the viewport. Note that you don't
// necessary need to use keybindings at all; the viewport can be controlled
// programmatically with methods like Model.LineDown(1). See the GoDocs for
// details.
type KeyMap struct {
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
Down key.Binding
Up key.Binding
Left key.Binding
Right key.Binding
}
// DefaultKeyMap returns a set of pager-like default keybindings.
func DefaultKeyMap() KeyMap {
return KeyMap{
PageDown: key.NewBinding(
key.WithKeys("pgdown", spacebar, "f"),
key.WithHelp("f/pgdn", "page down"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup", "b"),
key.WithHelp("b/pgup", "page up"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("u", "ctrl+u"),
key.WithHelp("u", "½ page up"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("d", "ctrl+d"),
key.WithHelp("d", "½ page down"),
),
Up: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("↑/k", "up"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("↓/j", "down"),
),
Left: key.NewBinding(
key.WithKeys("left", "h"),
key.WithHelp("←/h", "move left"),
),
Right: key.NewBinding(
key.WithKeys("right", "l"),
key.WithHelp("→/l", "move right"),
),
}
}

View File

@ -0,0 +1,544 @@
package viewport
import (
"math"
"strings"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
)
// New returns a new model with the given width and height as well as default
// key mappings.
func New(width, height int) (m Model) {
m.Width = width
m.Height = height
m.setInitialValues()
return m
}
// Model is the Bubble Tea model for this viewport element.
type Model struct {
Width int
Height int
KeyMap KeyMap
// Whether or not to respond to the mouse. The mouse must be enabled in
// Bubble Tea for this to work. For details, see the Bubble Tea docs.
MouseWheelEnabled bool
// The number of lines the mouse wheel will scroll. By default, this is 3.
MouseWheelDelta int
// YOffset is the vertical scroll position.
YOffset int
// xOffset is the horizontal scroll position.
xOffset int
// horizontalStep is the number of columns we move left or right during a
// default horizontal scroll.
horizontalStep int
// YPosition is the position of the viewport in relation to the terminal
// window. It's used in high performance rendering only.
YPosition int
// Style applies a lipgloss style to the viewport. Realistically, it's most
// useful for setting borders, margins and padding.
Style lipgloss.Style
// HighPerformanceRendering bypasses the normal Bubble Tea renderer to
// provide higher performance rendering. Most of the time the normal Bubble
// Tea rendering methods will suffice, but if you're passing content with
// a lot of ANSI escape codes you may see improved rendering in certain
// terminals with this enabled.
//
// This should only be used in program occupying the entire terminal,
// which is usually via the alternate screen buffer.
//
// Deprecated: high performance rendering is now deprecated in Bubble Tea.
HighPerformanceRendering bool
initialized bool
lines []string
longestLineWidth int
}
func (m *Model) setInitialValues() {
m.KeyMap = DefaultKeyMap()
m.MouseWheelEnabled = true
m.MouseWheelDelta = 3
m.initialized = true
}
// Init exists to satisfy the tea.Model interface for composability purposes.
func (m Model) Init() tea.Cmd {
return nil
}
// AtTop returns whether or not the viewport is at the very top position.
func (m Model) AtTop() bool {
return m.YOffset <= 0
}
// AtBottom returns whether or not the viewport is at or past the very bottom
// position.
func (m Model) AtBottom() bool {
return m.YOffset >= m.maxYOffset()
}
// PastBottom returns whether or not the viewport is scrolled beyond the last
// line. This can happen when adjusting the viewport height.
func (m Model) PastBottom() bool {
return m.YOffset > m.maxYOffset()
}
// ScrollPercent returns the amount scrolled as a float between 0 and 1.
func (m Model) ScrollPercent() float64 {
if m.Height >= len(m.lines) {
return 1.0
}
y := float64(m.YOffset)
h := float64(m.Height)
t := float64(len(m.lines))
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
// HorizontalScrollPercent returns the amount horizontally scrolled as a float
// between 0 and 1.
func (m Model) HorizontalScrollPercent() float64 {
if m.xOffset >= m.longestLineWidth-m.Width {
return 1.0
}
y := float64(m.xOffset)
h := float64(m.Width)
t := float64(m.longestLineWidth)
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
// SetContent set the pager's text content.
func (m *Model) SetContent(s string) {
s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings
m.lines = strings.Split(s, "\n")
m.longestLineWidth = findLongestLineWidth(m.lines)
if m.YOffset > len(m.lines)-1 {
m.GotoBottom()
}
}
// maxYOffset returns the maximum possible value of the y-offset based on the
// viewport's content and set height.
func (m Model) maxYOffset() int {
return max(0, len(m.lines)-m.Height+m.Style.GetVerticalFrameSize())
}
// visibleLines returns the lines that should currently be visible in the
// viewport.
func (m Model) visibleLines() (lines []string) {
h := m.Height - m.Style.GetVerticalFrameSize()
w := m.Width - m.Style.GetHorizontalFrameSize()
if len(m.lines) > 0 {
top := max(0, m.YOffset)
bottom := clamp(m.YOffset+h, top, len(m.lines))
lines = m.lines[top:bottom]
}
if (m.xOffset == 0 && m.longestLineWidth <= w) || w == 0 {
return lines
}
cutLines := make([]string, len(lines))
for i := range lines {
cutLines[i] = ansi.Cut(lines[i], m.xOffset, m.xOffset+w)
}
return cutLines
}
// scrollArea returns the scrollable boundaries for high performance rendering.
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func (m Model) scrollArea() (top, bottom int) {
top = max(0, m.YPosition)
bottom = max(top, top+m.Height)
if top > 0 && bottom > top {
bottom--
}
return top, bottom
}
// SetYOffset sets the Y offset.
func (m *Model) SetYOffset(n int) {
m.YOffset = clamp(n, 0, m.maxYOffset())
}
// ViewDown moves the view down by the number of lines in the viewport.
// Basically, "page down".
//
// Deprecated: use [Model.PageDown] instead.
func (m *Model) ViewDown() []string {
return m.PageDown()
}
// PageDown moves the view down by the number of lines in the viewport.
func (m *Model) PageDown() []string {
if m.AtBottom() {
return nil
}
return m.ScrollDown(m.Height)
}
// ViewUp moves the view up by one height of the viewport.
// Basically, "page up".
//
// Deprecated: use [Model.PageUp] instead.
func (m *Model) ViewUp() []string {
return m.PageUp()
}
// PageUp moves the view up by one height of the viewport.
func (m *Model) PageUp() []string {
if m.AtTop() {
return nil
}
return m.ScrollUp(m.Height)
}
// HalfViewDown moves the view down by half the height of the viewport.
//
// Deprecated: use [Model.HalfPageDown] instead.
func (m *Model) HalfViewDown() (lines []string) {
return m.HalfPageDown()
}
// HalfPageDown moves the view down by half the height of the viewport.
func (m *Model) HalfPageDown() (lines []string) {
if m.AtBottom() {
return nil
}
return m.ScrollDown(m.Height / 2) //nolint:mnd
}
// HalfViewUp moves the view up by half the height of the viewport.
//
// Deprecated: use [Model.HalfPageUp] instead.
func (m *Model) HalfViewUp() (lines []string) {
return m.HalfPageUp()
}
// HalfPageUp moves the view up by half the height of the viewport.
func (m *Model) HalfPageUp() (lines []string) {
if m.AtTop() {
return nil
}
return m.ScrollUp(m.Height / 2) //nolint:mnd
}
// LineDown moves the view down by the given number of lines.
//
// Deprecated: use [Model.ScrollDown] instead.
func (m *Model) LineDown(n int) (lines []string) {
return m.ScrollDown(n)
}
// ScrollDown moves the view down by the given number of lines.
func (m *Model) ScrollDown(n int) (lines []string) {
if m.AtBottom() || n == 0 || len(m.lines) == 0 {
return nil
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we actually have left before we reach
// the bottom.
m.SetYOffset(m.YOffset + n)
// Gather lines to send off for performance scrolling.
//
// XXX: high performance rendering is deprecated in Bubble Tea.
bottom := clamp(m.YOffset+m.Height, 0, len(m.lines))
top := clamp(m.YOffset+m.Height-n, 0, bottom)
return m.lines[top:bottom]
}
// LineUp moves the view down by the given number of lines. Returns the new
// lines to show.
//
// Deprecated: use [Model.ScrollUp] instead.
func (m *Model) LineUp(n int) (lines []string) {
return m.ScrollUp(n)
}
// ScrollUp moves the view down by the given number of lines. Returns the new
// lines to show.
func (m *Model) ScrollUp(n int) (lines []string) {
if m.AtTop() || n == 0 || len(m.lines) == 0 {
return nil
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we are from the top.
m.SetYOffset(m.YOffset - n)
// Gather lines to send off for performance scrolling.
//
// XXX: high performance rendering is deprecated in Bubble Tea.
top := max(0, m.YOffset)
bottom := clamp(m.YOffset+n, 0, m.maxYOffset())
return m.lines[top:bottom]
}
// SetHorizontalStep sets the default amount of columns to scroll left or right
// with the default viewport key map.
//
// If set to 0 or less, horizontal scrolling is disabled.
//
// On v1, horizontal scrolling is disabled by default.
func (m *Model) SetHorizontalStep(n int) {
m.horizontalStep = max(n, 0)
}
// SetXOffset sets the X offset.
func (m *Model) SetXOffset(n int) {
m.xOffset = clamp(n, 0, m.longestLineWidth-m.Width)
}
// ScrollLeft moves the viewport to the left by the given number of columns.
func (m *Model) ScrollLeft(n int) {
m.SetXOffset(m.xOffset - n)
}
// ScrollRight moves viewport to the right by the given number of columns.
func (m *Model) ScrollRight(n int) {
m.SetXOffset(m.xOffset + n)
}
// TotalLineCount returns the total number of lines (both hidden and visible) within the viewport.
func (m Model) TotalLineCount() int {
return len(m.lines)
}
// VisibleLineCount returns the number of the visible lines within the viewport.
func (m Model) VisibleLineCount() int {
return len(m.visibleLines())
}
// GotoTop sets the viewport to the top position.
func (m *Model) GotoTop() (lines []string) {
if m.AtTop() {
return nil
}
m.SetYOffset(0)
return m.visibleLines()
}
// GotoBottom sets the viewport to the bottom position.
func (m *Model) GotoBottom() (lines []string) {
m.SetYOffset(m.maxYOffset())
return m.visibleLines()
}
// Sync tells the renderer where the viewport will be located and requests
// a render of the current state of the viewport. It should be called for the
// first render and after a window resize.
//
// For high performance rendering only.
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func Sync(m Model) tea.Cmd {
if len(m.lines) == 0 {
return nil
}
top, bottom := m.scrollArea()
return tea.SyncScrollArea(m.visibleLines(), top, bottom)
}
// ViewDown is a high performance command that moves the viewport up by a given
// number of lines. Use Model.ViewDown to get the lines that should be rendered.
// For example:
//
// lines := model.ViewDown(1)
// cmd := ViewDown(m, lines)
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func ViewDown(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
top, bottom := m.scrollArea()
// XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we
// won't need to return a command here.
return tea.ScrollDown(lines, top, bottom)
}
// ViewUp is a high performance command the moves the viewport down by a given
// number of lines height. Use Model.ViewUp to get the lines that should be
// rendered.
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func ViewUp(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
top, bottom := m.scrollArea()
// XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we
// won't need to return a command here.
return tea.ScrollUp(lines, top, bottom)
}
// Update handles standard message-based viewport updates.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var cmd tea.Cmd
m, cmd = m.updateAsModel(msg)
return m, cmd
}
// Author's note: this method has been broken out to make it easier to
// potentially transition Update to satisfy tea.Model.
func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) {
if !m.initialized {
m.setInitialValues()
}
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.KeyMap.PageDown):
lines := m.PageDown()
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
case key.Matches(msg, m.KeyMap.PageUp):
lines := m.PageUp()
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
case key.Matches(msg, m.KeyMap.HalfPageDown):
lines := m.HalfPageDown()
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
case key.Matches(msg, m.KeyMap.HalfPageUp):
lines := m.HalfPageUp()
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
case key.Matches(msg, m.KeyMap.Down):
lines := m.ScrollDown(1)
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
case key.Matches(msg, m.KeyMap.Up):
lines := m.ScrollUp(1)
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
case key.Matches(msg, m.KeyMap.Left):
m.ScrollLeft(m.horizontalStep)
case key.Matches(msg, m.KeyMap.Right):
m.ScrollRight(m.horizontalStep)
}
case tea.MouseMsg:
if !m.MouseWheelEnabled || msg.Action != tea.MouseActionPress {
break
}
switch msg.Button { //nolint:exhaustive
case tea.MouseButtonWheelUp:
if msg.Shift {
// Note that not every terminal emulator sends the shift event for mouse actions by default (looking at you Konsole)
m.ScrollLeft(m.horizontalStep)
} else {
lines := m.ScrollUp(m.MouseWheelDelta)
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
}
case tea.MouseButtonWheelDown:
if msg.Shift {
m.ScrollRight(m.horizontalStep)
} else {
lines := m.ScrollDown(m.MouseWheelDelta)
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
}
// Note that not every terminal emulator sends the horizontal wheel events by default (looking at you Konsole)
case tea.MouseButtonWheelLeft:
m.ScrollLeft(m.horizontalStep)
case tea.MouseButtonWheelRight:
m.ScrollRight(m.horizontalStep)
}
}
return m, cmd
}
// View renders the viewport into a string.
func (m Model) View() string {
if m.HighPerformanceRendering {
// Just send newlines since we're going to be rendering the actual
// content separately. We still need to send something that equals the
// height of this view so that the Bubble Tea standard renderer can
// position anything below this view properly.
return strings.Repeat("\n", max(0, m.Height-1))
}
w, h := m.Width, m.Height
if sw := m.Style.GetWidth(); sw != 0 {
w = min(w, sw)
}
if sh := m.Style.GetHeight(); sh != 0 {
h = min(h, sh)
}
contentWidth := w - m.Style.GetHorizontalFrameSize()
contentHeight := h - m.Style.GetVerticalFrameSize()
contents := lipgloss.NewStyle().
Width(contentWidth). // pad to width.
Height(contentHeight). // pad to height.
MaxHeight(contentHeight). // truncate height if taller.
MaxWidth(contentWidth). // truncate width if wider.
Render(strings.Join(m.visibleLines(), "\n"))
return m.Style.
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
Render(contents)
}
func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}
func findLongestLineWidth(lines []string) int {
w := 0
for _, l := range lines {
if ww := ansi.StringWidth(l); ww > w {
w = ww
}
}
return w
}

View File

@ -33,9 +33,6 @@ linters:
generated: lax
presets:
- common-false-positives
settings:
exhaustive:
default-signifies-exhaustive: true
issues:
max-issues-per-linter: 0
max-same-issues: 0

View File

@ -83,8 +83,8 @@ func colorProfile(isatty bool, env environ) (p Profile) {
}
if envNoColor(env) && isatty {
if p > ASCII {
p = ASCII
if p > Ascii {
p = Ascii
}
return //nolint:nakedret
}
@ -153,24 +153,29 @@ func envColorProfile(env environ) (p Profile) {
p = ANSI
}
switch {
case strings.Contains(term, "alacritty"),
strings.Contains(term, "contour"),
strings.Contains(term, "foot"),
strings.Contains(term, "ghostty"),
strings.Contains(term, "kitty"),
strings.Contains(term, "rio"),
strings.Contains(term, "st"),
strings.Contains(term, "wezterm"):
parts := strings.Split(term, "-")
switch parts[0] {
case "alacritty",
"contour",
"foot",
"ghostty",
"kitty",
"rio",
"st",
"wezterm":
return TrueColor
case strings.HasPrefix(term, "tmux"), strings.HasPrefix(term, "screen"):
case "xterm":
if len(parts) > 1 {
switch parts[1] {
case "ghostty", "kitty":
// These terminals can be defined as xterm-TERMNAME
return TrueColor
}
}
case "tmux", "screen":
if p < ANSI256 {
p = ANSI256
}
case strings.HasPrefix(term, "xterm"):
if p < ANSI {
p = ANSI
}
}
if isCloudShell, _ := strconv.ParseBool(env.get("GOOGLE_CLOUD_SHELL")); isCloudShell {

View File

@ -11,12 +11,10 @@ import (
type Profile byte
const (
// Unknown is a profile that represents the absence of a profile.
Unknown Profile = iota
// NoTTY is a profile with no terminal support.
NoTTY
// ASCII is a profile with no color support.
ASCII
NoTTY Profile = iota
// Ascii is a profile with no color support.
Ascii //nolint:revive
// ANSI is a profile with 16 colors (4-bit).
ANSI
// ANSI256 is a profile with 256 colors (8-bit).
@ -25,9 +23,6 @@ const (
TrueColor
)
// Ascii is an alias for the [ASCII] profile for backwards compatibility.
const Ascii = ASCII //nolint:revive
// String returns the string representation of a Profile.
func (p Profile) String() string {
switch p {
@ -37,13 +32,12 @@ func (p Profile) String() string {
return "ANSI256"
case ANSI:
return "ANSI"
case ASCII:
case Ascii:
return "Ascii"
case NoTTY:
return "NoTTY"
default:
return "Unknown"
}
return "Unknown"
}
var (
@ -56,7 +50,7 @@ var (
// Convert transforms a given Color to a Color supported within the Profile.
func (p Profile) Convert(c color.Color) (cc color.Color) {
if p <= ASCII {
if p <= Ascii {
return nil
}
if p == TrueColor {
@ -96,13 +90,11 @@ func (p Profile) Convert(c color.Color) (cc color.Color) {
return c
default:
switch p {
case ANSI256:
if p == ANSI256 {
return ansi.Convert256(c)
case ANSI:
} else if p == ANSI {
return ansi.Convert16(c)
default:
return c
}
return c
}
}

View File

@ -36,12 +36,12 @@ type Writer struct {
// Write writes the given text to the underlying writer.
func (w *Writer) Write(p []byte) (int, error) {
switch {
case w.Profile == TrueColor:
switch w.Profile {
case TrueColor:
return w.Forward.Write(p) //nolint:wrapcheck
case w.Profile <= NoTTY:
case NoTTY:
return io.WriteString(w.Forward, ansi.Strip(string(p))) //nolint:wrapcheck
case w.Profile == ASCII, w.Profile == ANSI, w.Profile == ANSI256:
case Ascii, ANSI, ANSI256:
return w.downsample(p)
default:
return 0, fmt.Errorf("invalid profile: %v", w.Profile)
@ -112,7 +112,7 @@ func handleSgr(w *Writer, p *ansi.Parser, buf *bytes.Buffer) {
if w.Profile < ANSI {
continue
}
style = style.ForegroundColor(nil)
style = style.DefaultForegroundColor()
case 40, 41, 42, 43, 44, 45, 46, 47: // 8-bit background color
if w.Profile < ANSI {
continue
@ -132,7 +132,7 @@ func handleSgr(w *Writer, p *ansi.Parser, buf *bytes.Buffer) {
if w.Profile < ANSI {
continue
}
style = style.BackgroundColor(nil)
style = style.DefaultBackgroundColor()
case 58: // 16 or 24-bit underline color
var c color.Color
if n := ansi.ReadStyleColor(params[i:], &c); n > 0 {
@ -146,7 +146,7 @@ func handleSgr(w *Writer, p *ansi.Parser, buf *bytes.Buffer) {
if w.Profile < ANSI {
continue
}
style = style.UnderlineColor(nil)
style = style.DefaultUnderlineColor()
case 90, 91, 92, 93, 94, 95, 96, 97: // 8-bit bright foreground color
if w.Profile < ANSI {
continue

View File

@ -261,7 +261,7 @@ func CHA(col int) string {
//
// See: https://vt100.net/docs/vt510-rm/CUP.html
func CursorPosition(col, row int) string {
if row <= 1 && col <= 1 {
if row <= 0 && col <= 0 {
return CursorHomePosition
}
@ -281,9 +281,7 @@ func CUP(col, row int) string {
}
// CursorHomePosition is a sequence for moving the cursor to the upper left
// corner of the scrolling region.
//
// This is equivalent to [CursorPosition](1, 1).
// corner of the scrolling region. This is equivalent to `CursorPosition(1, 1)`.
const CursorHomePosition = "\x1b[H"
// SetCursorPosition (CUP) returns a sequence for setting the cursor to the

View File

@ -108,7 +108,7 @@ func DECRST(modes ...Mode) string {
func setMode(reset bool, modes ...Mode) (s string) {
if len(modes) == 0 {
return s
return //nolint:nakedret
}
cmd := "h"
@ -142,7 +142,7 @@ func setMode(reset bool, modes ...Mode) (s string) {
if len(dec) > 0 {
s += seq + "?" + strings.Join(dec, ";") + cmd
}
return s
return //nolint:nakedret
}
// RequestMode (DECRQM) returns a sequence to request a mode from the terminal.
@ -228,12 +228,12 @@ func (m DECMode) Mode() int {
//
// See: https://vt100.net/docs/vt510-rm/KAM.html
const (
ModeKeyboardAction = ANSIMode(2)
KAM = ModeKeyboardAction
KeyboardActionMode = ANSIMode(2)
KAM = KeyboardActionMode
SetModeKeyboardAction = "\x1b[2h"
ResetModeKeyboardAction = "\x1b[2l"
RequestModeKeyboardAction = "\x1b[2$p"
SetKeyboardActionMode = "\x1b[2h"
ResetKeyboardActionMode = "\x1b[2l"
RequestKeyboardActionMode = "\x1b[2$p"
)
// Insert/Replace Mode (IRM) is a mode that determines whether characters are
@ -245,12 +245,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/IRM.html
const (
ModeInsertReplace = ANSIMode(4)
IRM = ModeInsertReplace
InsertReplaceMode = ANSIMode(4)
IRM = InsertReplaceMode
SetModeInsertReplace = "\x1b[4h"
ResetModeInsertReplace = "\x1b[4l"
RequestModeInsertReplace = "\x1b[4$p"
SetInsertReplaceMode = "\x1b[4h"
ResetInsertReplaceMode = "\x1b[4l"
RequestInsertReplaceMode = "\x1b[4$p"
)
// BiDirectional Support Mode (BDSM) is a mode that determines whether the
@ -260,12 +260,12 @@ const (
//
// See ECMA-48 7.2.1.
const (
ModeBiDirectionalSupport = ANSIMode(8)
BDSM = ModeBiDirectionalSupport
BiDirectionalSupportMode = ANSIMode(8)
BDSM = BiDirectionalSupportMode
SetModeBiDirectionalSupport = "\x1b[8h"
ResetModeBiDirectionalSupport = "\x1b[8l"
RequestModeBiDirectionalSupport = "\x1b[8$p"
SetBiDirectionalSupportMode = "\x1b[8h"
ResetBiDirectionalSupportMode = "\x1b[8l"
RequestBiDirectionalSupportMode = "\x1b[8$p"
)
// Send Receive Mode (SRM) or Local Echo Mode is a mode that determines whether
@ -274,17 +274,17 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/SRM.html
const (
ModeSendReceive = ANSIMode(12)
ModeLocalEcho = ModeSendReceive
SRM = ModeSendReceive
SendReceiveMode = ANSIMode(12)
LocalEchoMode = SendReceiveMode
SRM = SendReceiveMode
SetModeSendReceive = "\x1b[12h"
ResetModeSendReceive = "\x1b[12l"
RequestModeSendReceive = "\x1b[12$p"
SetSendReceiveMode = "\x1b[12h"
ResetSendReceiveMode = "\x1b[12l"
RequestSendReceiveMode = "\x1b[12$p"
SetModeLocalEcho = "\x1b[12h"
ResetModeLocalEcho = "\x1b[12l"
RequestModeLocalEcho = "\x1b[12$p"
SetLocalEchoMode = "\x1b[12h"
ResetLocalEchoMode = "\x1b[12l"
RequestLocalEchoMode = "\x1b[12$p"
)
// Line Feed/New Line Mode (LNM) is a mode that determines whether the terminal
@ -299,12 +299,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/LNM.html
const (
ModeLineFeedNewLine = ANSIMode(20)
LNM = ModeLineFeedNewLine
LineFeedNewLineMode = ANSIMode(20)
LNM = LineFeedNewLineMode
SetModeLineFeedNewLine = "\x1b[20h"
ResetModeLineFeedNewLine = "\x1b[20l"
RequestModeLineFeedNewLine = "\x1b[20$p"
SetLineFeedNewLineMode = "\x1b[20h"
ResetLineFeedNewLineMode = "\x1b[20l"
RequestLineFeedNewLineMode = "\x1b[20$p"
)
// Cursor Keys Mode (DECCKM) is a mode that determines whether the cursor keys
@ -312,12 +312,18 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECCKM.html
const (
ModeCursorKeys = DECMode(1)
DECCKM = ModeCursorKeys
CursorKeysMode = DECMode(1)
DECCKM = CursorKeysMode
SetModeCursorKeys = "\x1b[?1h"
ResetModeCursorKeys = "\x1b[?1l"
RequestModeCursorKeys = "\x1b[?1$p"
SetCursorKeysMode = "\x1b[?1h"
ResetCursorKeysMode = "\x1b[?1l"
RequestCursorKeysMode = "\x1b[?1$p"
)
// Deprecated: use [SetCursorKeysMode] and [ResetCursorKeysMode] instead.
const (
EnableCursorKeys = "\x1b[?1h" //nolint:revive // grouped constants
DisableCursorKeys = "\x1b[?1l"
)
// Origin Mode (DECOM) is a mode that determines whether the cursor moves to the
@ -325,12 +331,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECOM.html
const (
ModeOrigin = DECMode(6)
DECOM = ModeOrigin
OriginMode = DECMode(6)
DECOM = OriginMode
SetModeOrigin = "\x1b[?6h"
ResetModeOrigin = "\x1b[?6l"
RequestModeOrigin = "\x1b[?6$p"
SetOriginMode = "\x1b[?6h"
ResetOriginMode = "\x1b[?6l"
RequestOriginMode = "\x1b[?6$p"
)
// Auto Wrap Mode (DECAWM) is a mode that determines whether the cursor wraps
@ -338,12 +344,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECAWM.html
const (
ModeAutoWrap = DECMode(7)
DECAWM = ModeAutoWrap
AutoWrapMode = DECMode(7)
DECAWM = AutoWrapMode
SetModeAutoWrap = "\x1b[?7h"
ResetModeAutoWrap = "\x1b[?7l"
RequestModeAutoWrap = "\x1b[?7$p"
SetAutoWrapMode = "\x1b[?7h"
ResetAutoWrapMode = "\x1b[?7l"
RequestAutoWrapMode = "\x1b[?7$p"
)
// X10 Mouse Mode is a mode that determines whether the mouse reports on button
@ -358,29 +364,39 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseX10 = DECMode(9)
X10MouseMode = DECMode(9)
SetModeMouseX10 = "\x1b[?9h"
ResetModeMouseX10 = "\x1b[?9l"
RequestModeMouseX10 = "\x1b[?9$p"
SetX10MouseMode = "\x1b[?9h"
ResetX10MouseMode = "\x1b[?9l"
RequestX10MouseMode = "\x1b[?9$p"
)
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
//
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
const (
ModeTextCursorEnable = DECMode(25)
DECTCEM = ModeTextCursorEnable
TextCursorEnableMode = DECMode(25)
DECTCEM = TextCursorEnableMode
SetModeTextCursorEnable = "\x1b[?25h"
ResetModeTextCursorEnable = "\x1b[?25l"
RequestModeTextCursorEnable = "\x1b[?25$p"
SetTextCursorEnableMode = "\x1b[?25h"
ResetTextCursorEnableMode = "\x1b[?25l"
RequestTextCursorEnableMode = "\x1b[?25$p"
)
// These are aliases for [SetModeTextCursorEnable] and [ResetModeTextCursorEnable].
// These are aliases for [SetTextCursorEnableMode] and [ResetTextCursorEnableMode].
const (
ShowCursor = SetModeTextCursorEnable
HideCursor = ResetModeTextCursorEnable
ShowCursor = SetTextCursorEnableMode
HideCursor = ResetTextCursorEnableMode
)
// Text Cursor Enable Mode (DECTCEM) is a mode that shows/hides the cursor.
//
// See: https://vt100.net/docs/vt510-rm/DECTCEM.html
//
// Deprecated: use [SetTextCursorEnableMode] and [ResetTextCursorEnableMode] instead.
const (
CursorEnableMode = DECMode(25)
RequestCursorVisibility = "\x1b[?25$p"
)
// Numeric Keypad Mode (DECNKM) is a mode that determines whether the keypad
@ -390,12 +406,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECNKM.html
const (
ModeNumericKeypad = DECMode(66)
DECNKM = ModeNumericKeypad
NumericKeypadMode = DECMode(66)
DECNKM = NumericKeypadMode
SetModeNumericKeypad = "\x1b[?66h"
ResetModeNumericKeypad = "\x1b[?66l"
RequestModeNumericKeypad = "\x1b[?66$p"
SetNumericKeypadMode = "\x1b[?66h"
ResetNumericKeypadMode = "\x1b[?66l"
RequestNumericKeypadMode = "\x1b[?66$p"
)
// Backarrow Key Mode (DECBKM) is a mode that determines whether the backspace
@ -403,12 +419,12 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECBKM.html
const (
ModeBackarrowKey = DECMode(67)
DECBKM = ModeBackarrowKey
BackarrowKeyMode = DECMode(67)
DECBKM = BackarrowKeyMode
SetModeBackarrowKey = "\x1b[?67h"
ResetModeBackarrowKey = "\x1b[?67l"
RequestModeBackarrowKey = "\x1b[?67$p"
SetBackarrowKeyMode = "\x1b[?67h"
ResetBackarrowKeyMode = "\x1b[?67l"
RequestBackarrowKeyMode = "\x1b[?67$p"
)
// Left Right Margin Mode (DECLRMM) is a mode that determines whether the left
@ -416,33 +432,47 @@ const (
//
// See: https://vt100.net/docs/vt510-rm/DECLRMM.html
const (
ModeLeftRightMargin = DECMode(69)
DECLRMM = ModeLeftRightMargin
LeftRightMarginMode = DECMode(69)
DECLRMM = LeftRightMarginMode
SetModeLeftRightMargin = "\x1b[?69h"
ResetModeLeftRightMargin = "\x1b[?69l"
RequestModeLeftRightMargin = "\x1b[?69$p"
SetLeftRightMarginMode = "\x1b[?69h"
ResetLeftRightMarginMode = "\x1b[?69l"
RequestLeftRightMarginMode = "\x1b[?69$p"
)
// Normal Mouse Mode is a mode that determines whether the mouse reports on
// button presses and releases. It will also report modifier keys, wheel
// events, and extra buttons.
//
// It uses the same encoding as [ModeMouseX10] with a few differences:
// It uses the same encoding as [X10MouseMode] with a few differences:
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseNormal = DECMode(1000)
NormalMouseMode = DECMode(1000)
SetModeMouseNormal = "\x1b[?1000h"
ResetModeMouseNormal = "\x1b[?1000l"
RequestModeMouseNormal = "\x1b[?1000$p"
SetNormalMouseMode = "\x1b[?1000h"
ResetNormalMouseMode = "\x1b[?1000l"
RequestNormalMouseMode = "\x1b[?1000$p"
)
// VT Mouse Tracking is a mode that determines whether the mouse reports on
// button press and release.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [NormalMouseMode] instead.
const (
MouseMode = DECMode(1000)
EnableMouse = "\x1b[?1000h"
DisableMouse = "\x1b[?1000l"
RequestMouse = "\x1b[?1000$p"
)
// Highlight Mouse Tracking is a mode that determines whether the mouse reports
// on button presses, releases, and highlighted cells.
//
// It uses the same encoding as [ModeMouseNormal] with a few differences:
// It uses the same encoding as [NormalMouseMode] with a few differences:
//
// On highlight events, the terminal responds with the following encoding:
//
@ -451,11 +481,11 @@ const (
//
// Where the parameters are startx, starty, endx, endy, mousex, and mousey.
const (
ModeMouseHighlight = DECMode(1001)
HighlightMouseMode = DECMode(1001)
SetModeMouseHighlight = "\x1b[?1001h"
ResetModeMouseHighlight = "\x1b[?1001l"
RequestModeMouseHighlight = "\x1b[?1001$p"
SetHighlightMouseMode = "\x1b[?1001h"
ResetHighlightMouseMode = "\x1b[?1001l"
RequestHighlightMouseMode = "\x1b[?1001$p"
)
// VT Hilite Mouse Tracking is a mode that determines whether the mouse reports on
@ -463,29 +493,65 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [HighlightMouseMode] instead.
const (
MouseHiliteMode = DECMode(1001)
// Button Event Mouse Tracking is essentially the same as [ModeMouseNormal],
EnableMouseHilite = "\x1b[?1001h"
DisableMouseHilite = "\x1b[?1001l"
RequestMouseHilite = "\x1b[?1001$p"
)
// Button Event Mouse Tracking is essentially the same as [NormalMouseMode],
// but it also reports button-motion events when a button is pressed.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseButtonEvent = DECMode(1002)
ButtonEventMouseMode = DECMode(1002)
SetModeMouseButtonEvent = "\x1b[?1002h"
ResetModeMouseButtonEvent = "\x1b[?1002l"
RequestModeMouseButtonEvent = "\x1b[?1002$p"
SetButtonEventMouseMode = "\x1b[?1002h"
ResetButtonEventMouseMode = "\x1b[?1002l"
RequestButtonEventMouseMode = "\x1b[?1002$p"
)
// Any Event Mouse Tracking is the same as [ModeMouseButtonEvent], except that
// Cell Motion Mouse Tracking is a mode that determines whether the mouse
// reports on button press, release, and motion events.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [ButtonEventMouseMode] instead.
const (
MouseCellMotionMode = DECMode(1002)
EnableMouseCellMotion = "\x1b[?1002h"
DisableMouseCellMotion = "\x1b[?1002l"
RequestMouseCellMotion = "\x1b[?1002$p"
)
// Any Event Mouse Tracking is the same as [ButtonEventMouseMode], except that
// all motion events are reported even if no mouse buttons are pressed.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseAnyEvent = DECMode(1003)
AnyEventMouseMode = DECMode(1003)
SetModeMouseAnyEvent = "\x1b[?1003h"
ResetModeMouseAnyEvent = "\x1b[?1003l"
RequestModeMouseAnyEvent = "\x1b[?1003$p"
SetAnyEventMouseMode = "\x1b[?1003h"
ResetAnyEventMouseMode = "\x1b[?1003l"
RequestAnyEventMouseMode = "\x1b[?1003$p"
)
// All Mouse Tracking is a mode that determines whether the mouse reports on
// button press, release, motion, and highlight events.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
//
// Deprecated: use [AnyEventMouseMode] instead.
const (
MouseAllMotionMode = DECMode(1003)
EnableMouseAllMotion = "\x1b[?1003h"
DisableMouseAllMotion = "\x1b[?1003l"
RequestMouseAllMotion = "\x1b[?1003$p"
)
// Focus Event Mode is a mode that determines whether the terminal reports focus
@ -498,11 +564,22 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Focus-Tracking
const (
ModeFocusEvent = DECMode(1004)
FocusEventMode = DECMode(1004)
SetModeFocusEvent = "\x1b[?1004h"
ResetModeFocusEvent = "\x1b[?1004l"
RequestModeFocusEvent = "\x1b[?1004$p"
SetFocusEventMode = "\x1b[?1004h"
ResetFocusEventMode = "\x1b[?1004l"
RequestFocusEventMode = "\x1b[?1004$p"
)
// Deprecated: use [SetFocusEventMode], [ResetFocusEventMode], and
// [RequestFocusEventMode] instead.
// Focus reporting mode constants.
const (
ReportFocusMode = DECMode(1004) //nolint:revive // grouped constants
EnableReportFocus = "\x1b[?1004h"
DisableReportFocus = "\x1b[?1004l"
RequestReportFocus = "\x1b[?1004$p"
)
// SGR Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -512,15 +589,24 @@ const (
//
// CSI < Cb ; Cx ; Cy M
//
// Where Cb is the same as [ModeMouseNormal], and Cx and Cy are the x and y.
// Where Cb is the same as [NormalMouseMode], and Cx and Cy are the x and y.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseExtSgr = DECMode(1006)
SgrExtMouseMode = DECMode(1006)
SetModeMouseExtSgr = "\x1b[?1006h"
ResetModeMouseExtSgr = "\x1b[?1006l"
RequestModeMouseExtSgr = "\x1b[?1006$p"
SetSgrExtMouseMode = "\x1b[?1006h"
ResetSgrExtMouseMode = "\x1b[?1006l"
RequestSgrExtMouseMode = "\x1b[?1006$p"
)
// Deprecated: use [SgrExtMouseMode] [SetSgrExtMouseMode],
// [ResetSgrExtMouseMode], and [RequestSgrExtMouseMode] instead.
const (
MouseSgrExtMode = DECMode(1006) //nolint:revive // grouped constants
EnableMouseSgrExt = "\x1b[?1006h"
DisableMouseSgrExt = "\x1b[?1006l"
RequestMouseSgrExt = "\x1b[?1006$p"
)
// UTF-8 Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -528,11 +614,11 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseExtUtf8 = DECMode(1005)
Utf8ExtMouseMode = DECMode(1005)
SetModeMouseExtUtf8 = "\x1b[?1005h"
ResetModeMouseExtUtf8 = "\x1b[?1005l"
RequestModeMouseExtUtf8 = "\x1b[?1005$p"
SetUtf8ExtMouseMode = "\x1b[?1005h"
ResetUtf8ExtMouseMode = "\x1b[?1005l"
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
)
// URXVT Extended Mouse Mode is a mode that changes the mouse tracking encoding
@ -540,25 +626,25 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseExtUrxvt = DECMode(1015)
UrxvtExtMouseMode = DECMode(1015)
SetModeMouseExtUrxvt = "\x1b[?1015h"
ResetModeMouseExtUrxvt = "\x1b[?1015l"
RequestModeMouseExtUrxvt = "\x1b[?1015$p"
SetUrxvtExtMouseMode = "\x1b[?1015h"
ResetUrxvtExtMouseMode = "\x1b[?1015l"
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
)
// SGR Pixel Extended Mouse Mode is a mode that changes the mouse tracking
// encoding to use SGR parameters with pixel coordinates.
//
// This is similar to [ModeMouseExtSgr], but also reports pixel coordinates.
// This is similar to [SgrExtMouseMode], but also reports pixel coordinates.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
const (
ModeMouseExtSgrPixel = DECMode(1016)
SgrPixelExtMouseMode = DECMode(1016)
SetModeMouseExtSgrPixel = "\x1b[?1016h"
ResetModeMouseExtSgrPixel = "\x1b[?1016l"
RequestModeMouseExtSgrPixel = "\x1b[?1016$p"
SetSgrPixelExtMouseMode = "\x1b[?1016h"
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
)
// Alternate Screen Mode is a mode that determines whether the alternate screen
@ -567,11 +653,11 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const (
ModeAltScreen = DECMode(1047)
AltScreenMode = DECMode(1047)
SetModeAltScreen = "\x1b[?1047h"
ResetModeAltScreen = "\x1b[?1047l"
RequestModeAltScreen = "\x1b[?1047$p"
SetAltScreenMode = "\x1b[?1047h"
ResetAltScreenMode = "\x1b[?1047l"
RequestAltScreenMode = "\x1b[?1047$p"
)
// Save Cursor Mode is a mode that saves the cursor position.
@ -579,24 +665,42 @@ const (
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const (
ModeSaveCursor = DECMode(1048)
SaveCursorMode = DECMode(1048)
SetModeSaveCursor = "\x1b[?1048h"
ResetModeSaveCursor = "\x1b[?1048l"
RequestModeSaveCursor = "\x1b[?1048$p"
SetSaveCursorMode = "\x1b[?1048h"
ResetSaveCursorMode = "\x1b[?1048l"
RequestSaveCursorMode = "\x1b[?1048$p"
)
// Alternate Screen Save Cursor Mode is a mode that saves the cursor position as in
// [ModeSaveCursor], switches to the alternate screen buffer as in [ModeAltScreen],
// [SaveCursorMode], switches to the alternate screen buffer as in [AltScreenMode],
// and clears the screen on switch.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
const (
ModeAltScreenSaveCursor = DECMode(1049)
AltScreenSaveCursorMode = DECMode(1049)
SetModeAltScreenSaveCursor = "\x1b[?1049h"
ResetModeAltScreenSaveCursor = "\x1b[?1049l"
RequestModeAltScreenSaveCursor = "\x1b[?1049$p"
SetAltScreenSaveCursorMode = "\x1b[?1049h"
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
)
// Alternate Screen Buffer is a mode that determines whether the alternate screen
// buffer is active.
//
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
//
// Deprecated: use [AltScreenSaveCursorMode] instead.
const (
AltScreenBufferMode = DECMode(1049)
SetAltScreenBufferMode = "\x1b[?1049h"
ResetAltScreenBufferMode = "\x1b[?1049l"
RequestAltScreenBufferMode = "\x1b[?1049$p"
EnableAltScreenBuffer = "\x1b[?1049h"
DisableAltScreenBuffer = "\x1b[?1049l"
RequestAltScreenBuffer = "\x1b[?1049$p"
)
// Bracketed Paste Mode is a mode that determines whether pasted text is
@ -605,11 +709,19 @@ const (
// See: https://cirw.in/blog/bracketed-paste
// See: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Bracketed-Paste-Mode
const (
ModeBracketedPaste = DECMode(2004)
BracketedPasteMode = DECMode(2004)
SetModeBracketedPaste = "\x1b[?2004h"
ResetModeBracketedPaste = "\x1b[?2004l"
RequestModeBracketedPaste = "\x1b[?2004$p"
SetBracketedPasteMode = "\x1b[?2004h"
ResetBracketedPasteMode = "\x1b[?2004l"
RequestBracketedPasteMode = "\x1b[?2004$p"
)
// Deprecated: use [SetBracketedPasteMode], [ResetBracketedPasteMode], and
// [RequestBracketedPasteMode] instead.
const (
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive // grouped constants
DisableBracketedPaste = "\x1b[?2004l"
RequestBracketedPaste = "\x1b[?2004$p"
)
// Synchronized Output Mode is a mode that determines whether output is
@ -617,11 +729,23 @@ const (
//
// See: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
const (
ModeSynchronizedOutput = DECMode(2026)
SynchronizedOutputMode = DECMode(2026)
SetModeSynchronizedOutput = "\x1b[?2026h"
ResetModeSynchronizedOutput = "\x1b[?2026l"
RequestModeSynchronizedOutput = "\x1b[?2026$p"
SetSynchronizedOutputMode = "\x1b[?2026h"
ResetSynchronizedOutputMode = "\x1b[?2026l"
RequestSynchronizedOutputMode = "\x1b[?2026$p"
)
// Synchronized Output Mode. See [SynchronizedOutputMode].
//
// Deprecated: use [SynchronizedOutputMode], [SetSynchronizedOutputMode], and
// [ResetSynchronizedOutputMode], and [RequestSynchronizedOutputMode] instead.
const (
SyncdOutputMode = DECMode(2026)
EnableSyncdOutput = "\x1b[?2026h"
DisableSyncdOutput = "\x1b[?2026l"
RequestSyncdOutput = "\x1b[?2026$p"
)
// Unicode Core Mode is a mode that determines whether the terminal should use
@ -630,16 +754,41 @@ const (
//
// See: https://github.com/contour-terminal/terminal-unicode-core
const (
ModeUnicodeCore = DECMode(2027)
UnicodeCoreMode = DECMode(2027)
SetModeUnicodeCore = "\x1b[?2027h"
ResetModeUnicodeCore = "\x1b[?2027l"
RequestModeUnicodeCore = "\x1b[?2027$p"
SetUnicodeCoreMode = "\x1b[?2027h"
ResetUnicodeCoreMode = "\x1b[?2027l"
RequestUnicodeCoreMode = "\x1b[?2027$p"
)
// Grapheme Clustering Mode is a mode that determines whether the terminal
// should look for grapheme clusters instead of single runes in the rendered
// text. This makes the terminal properly render combining characters such as
// emojis.
//
// See: https://github.com/contour-terminal/terminal-unicode-core
//
// Deprecated: use [GraphemeClusteringMode], [SetUnicodeCoreMode],
// [ResetUnicodeCoreMode], and [RequestUnicodeCoreMode] instead.
const (
GraphemeClusteringMode = DECMode(2027)
// ModeLightDark is a mode that enables reporting the operating system's color
SetGraphemeClusteringMode = "\x1b[?2027h"
ResetGraphemeClusteringMode = "\x1b[?2027l"
RequestGraphemeClusteringMode = "\x1b[?2027$p"
)
// Grapheme Clustering Mode. See [GraphemeClusteringMode].
//
// Deprecated: use [SetUnicodeCoreMode], [ResetUnicodeCoreMode], and
// [RequestUnicodeCoreMode] instead.
const (
EnableGraphemeClustering = "\x1b[?2027h"
DisableGraphemeClustering = "\x1b[?2027l"
RequestGraphemeClustering = "\x1b[?2027$p"
)
// LightDarkMode is a mode that enables reporting the operating system's color
// scheme (light or dark) preference. It reports the color scheme as a [DSR]
// and [LightDarkReport] escape sequences encoded as follows:
//
@ -653,14 +802,14 @@ const (
//
// See: https://contour-terminal.org/vt-extensions/color-palette-update-notifications/
const (
ModeLightDark = DECMode(2031)
LightDarkMode = DECMode(2031)
SetModeLightDark = "\x1b[?2031h"
ResetModeLightDark = "\x1b[?2031l"
RequestModeLightDark = "\x1b[?2031$p"
SetLightDarkMode = "\x1b[?2031h"
ResetLightDarkMode = "\x1b[?2031l"
RequestLightDarkMode = "\x1b[?2031$p"
)
// ModeInBandResize is a mode that reports terminal resize events as escape
// InBandResizeMode is a mode that reports terminal resize events as escape
// sequences. This is useful for systems that do not support [SIGWINCH] like
// Windows.
//
@ -670,11 +819,11 @@ const (
//
// See: https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83
const (
ModeInBandResize = DECMode(2048)
InBandResizeMode = DECMode(2048)
SetModeInBandResize = "\x1b[?2048h"
ResetModeInBandResize = "\x1b[?2048l"
RequestModeInBandResize = "\x1b[?2048$p"
SetInBandResizeMode = "\x1b[?2048h"
ResetInBandResizeMode = "\x1b[?2048l"
RequestInBandResizeMode = "\x1b[?2048$p"
)
// Win32Input is a mode that determines whether input is processed by the
@ -682,9 +831,17 @@ const (
//
// See: https://github.com/microsoft/terminal/blob/main/doc/specs/%234999%20-%20Improved%20keyboard%20handling%20in%20Conpty.md
const (
ModeWin32Input = DECMode(9001)
Win32InputMode = DECMode(9001)
SetModeWin32Input = "\x1b[?9001h"
ResetModeWin32Input = "\x1b[?9001l"
RequestModeWin32Input = "\x1b[?9001$p"
SetWin32InputMode = "\x1b[?9001h"
ResetWin32InputMode = "\x1b[?9001l"
RequestWin32InputMode = "\x1b[?9001$p"
)
// Deprecated: use [SetWin32InputMode], [ResetWin32InputMode], and
// [RequestWin32InputMode] instead.
const (
EnableWin32Input = "\x1b[?9001h" //nolint:revive // grouped constants
DisableWin32Input = "\x1b[?9001l"
RequestWin32Input = "\x1b[?9001$p"
)

View File

@ -1,495 +0,0 @@
package ansi
// Keyboard Action Mode (KAM) controls locking of the keyboard.
//
// Deprecated: use [ModeKeyboardAction] instead.
const (
KeyboardActionMode = ANSIMode(2)
SetKeyboardActionMode = "\x1b[2h"
ResetKeyboardActionMode = "\x1b[2l"
RequestKeyboardActionMode = "\x1b[2$p"
)
// Insert/Replace Mode (IRM) determines whether characters are inserted or replaced.
//
// Deprecated: use [ModeInsertReplace] instead.
const (
InsertReplaceMode = ANSIMode(4)
SetInsertReplaceMode = "\x1b[4h"
ResetInsertReplaceMode = "\x1b[4l"
RequestInsertReplaceMode = "\x1b[4$p"
)
// BiDirectional Support Mode (BDSM) determines whether the terminal supports bidirectional text.
//
// Deprecated: use [ModeBiDirectionalSupport] instead.
const (
BiDirectionalSupportMode = ANSIMode(8)
SetBiDirectionalSupportMode = "\x1b[8h"
ResetBiDirectionalSupportMode = "\x1b[8l"
RequestBiDirectionalSupportMode = "\x1b[8$p"
)
// Send Receive Mode (SRM) or Local Echo Mode determines whether the terminal echoes characters.
//
// Deprecated: use [ModeSendReceive] instead.
const (
SendReceiveMode = ANSIMode(12)
LocalEchoMode = SendReceiveMode
SetSendReceiveMode = "\x1b[12h"
ResetSendReceiveMode = "\x1b[12l"
RequestSendReceiveMode = "\x1b[12$p"
SetLocalEchoMode = "\x1b[12h"
ResetLocalEchoMode = "\x1b[12l"
RequestLocalEchoMode = "\x1b[12$p"
)
// Line Feed/New Line Mode (LNM) determines whether the terminal interprets line feed as new line.
//
// Deprecated: use [ModeLineFeedNewLine] instead.
const (
LineFeedNewLineMode = ANSIMode(20)
SetLineFeedNewLineMode = "\x1b[20h"
ResetLineFeedNewLineMode = "\x1b[20l"
RequestLineFeedNewLineMode = "\x1b[20$p"
)
// Cursor Keys Mode (DECCKM) determines whether cursor keys send ANSI or application sequences.
//
// Deprecated: use [ModeCursorKeys] instead.
const (
CursorKeysMode = DECMode(1)
SetCursorKeysMode = "\x1b[?1h"
ResetCursorKeysMode = "\x1b[?1l"
RequestCursorKeysMode = "\x1b[?1$p"
)
// Cursor Keys mode.
//
// Deprecated: use [SetModeCursorKeys] and [ResetModeCursorKeys] instead.
const (
EnableCursorKeys = "\x1b[?1h"
DisableCursorKeys = "\x1b[?1l"
)
// Origin Mode (DECOM) determines whether the cursor moves to home or margin position.
//
// Deprecated: use [ModeOrigin] instead.
const (
OriginMode = DECMode(6)
SetOriginMode = "\x1b[?6h"
ResetOriginMode = "\x1b[?6l"
RequestOriginMode = "\x1b[?6$p"
)
// Auto Wrap Mode (DECAWM) determines whether the cursor wraps to the next line.
//
// Deprecated: use [ModeAutoWrap] instead.
const (
AutoWrapMode = DECMode(7)
SetAutoWrapMode = "\x1b[?7h"
ResetAutoWrapMode = "\x1b[?7l"
RequestAutoWrapMode = "\x1b[?7$p"
)
// X10 Mouse Mode determines whether the mouse reports on button presses.
//
// Deprecated: use [ModeMouseX10] instead.
const (
X10MouseMode = DECMode(9)
SetX10MouseMode = "\x1b[?9h"
ResetX10MouseMode = "\x1b[?9l"
RequestX10MouseMode = "\x1b[?9$p"
)
// Text Cursor Enable Mode (DECTCEM) shows/hides the cursor.
//
// Deprecated: use [ModeTextCursorEnable] instead.
const (
TextCursorEnableMode = DECMode(25)
SetTextCursorEnableMode = "\x1b[?25h"
ResetTextCursorEnableMode = "\x1b[?25l"
RequestTextCursorEnableMode = "\x1b[?25$p"
)
// Text Cursor Enable mode.
//
// Deprecated: use [SetModeTextCursorEnable] and [ResetModeTextCursorEnable] instead.
const (
CursorEnableMode = DECMode(25)
RequestCursorVisibility = "\x1b[?25$p"
)
// Numeric Keypad Mode (DECNKM) determines whether the keypad sends application or numeric sequences.
//
// Deprecated: use [ModeNumericKeypad] instead.
const (
NumericKeypadMode = DECMode(66)
SetNumericKeypadMode = "\x1b[?66h"
ResetNumericKeypadMode = "\x1b[?66l"
RequestNumericKeypadMode = "\x1b[?66$p"
)
// Backarrow Key Mode (DECBKM) determines whether the backspace key sends backspace or delete.
//
// Deprecated: use [ModeBackarrowKey] instead.
const (
BackarrowKeyMode = DECMode(67)
SetBackarrowKeyMode = "\x1b[?67h"
ResetBackarrowKeyMode = "\x1b[?67l"
RequestBackarrowKeyMode = "\x1b[?67$p"
)
// Left Right Margin Mode (DECLRMM) determines whether left and right margins can be set.
//
// Deprecated: use [ModeLeftRightMargin] instead.
const (
LeftRightMarginMode = DECMode(69)
SetLeftRightMarginMode = "\x1b[?69h"
ResetLeftRightMarginMode = "\x1b[?69l"
RequestLeftRightMarginMode = "\x1b[?69$p"
)
// Normal Mouse Mode determines whether the mouse reports on button presses and releases.
//
// Deprecated: use [ModeMouseNormal] instead.
const (
NormalMouseMode = DECMode(1000)
SetNormalMouseMode = "\x1b[?1000h"
ResetNormalMouseMode = "\x1b[?1000l"
RequestNormalMouseMode = "\x1b[?1000$p"
)
// VT Mouse Tracking mode.
//
// Deprecated: use [ModeMouseNormal] instead.
const (
MouseMode = DECMode(1000)
EnableMouse = "\x1b[?1000h"
DisableMouse = "\x1b[?1000l"
RequestMouse = "\x1b[?1000$p"
)
// Highlight Mouse Tracking determines whether the mouse reports on button presses and highlighted cells.
//
// Deprecated: use [ModeMouseHighlight] instead.
const (
HighlightMouseMode = DECMode(1001)
SetHighlightMouseMode = "\x1b[?1001h"
ResetHighlightMouseMode = "\x1b[?1001l"
RequestHighlightMouseMode = "\x1b[?1001$p"
)
// VT Hilite Mouse Tracking mode.
//
// Deprecated: use [ModeMouseHighlight] instead.
const (
MouseHiliteMode = DECMode(1001)
EnableMouseHilite = "\x1b[?1001h"
DisableMouseHilite = "\x1b[?1001l"
RequestMouseHilite = "\x1b[?1001$p"
)
// Button Event Mouse Tracking reports button-motion events when a button is pressed.
//
// Deprecated: use [ModeMouseButtonEvent] instead.
const (
ButtonEventMouseMode = DECMode(1002)
SetButtonEventMouseMode = "\x1b[?1002h"
ResetButtonEventMouseMode = "\x1b[?1002l"
RequestButtonEventMouseMode = "\x1b[?1002$p"
)
// Cell Motion Mouse Tracking mode.
//
// Deprecated: use [ModeMouseButtonEvent] instead.
const (
MouseCellMotionMode = DECMode(1002)
EnableMouseCellMotion = "\x1b[?1002h"
DisableMouseCellMotion = "\x1b[?1002l"
RequestMouseCellMotion = "\x1b[?1002$p"
)
// Any Event Mouse Tracking reports all motion events.
//
// Deprecated: use [ModeMouseAnyEvent] instead.
const (
AnyEventMouseMode = DECMode(1003)
SetAnyEventMouseMode = "\x1b[?1003h"
ResetAnyEventMouseMode = "\x1b[?1003l"
RequestAnyEventMouseMode = "\x1b[?1003$p"
)
// All Mouse Tracking mode.
//
// Deprecated: use [ModeMouseAnyEvent] instead.
const (
MouseAllMotionMode = DECMode(1003)
EnableMouseAllMotion = "\x1b[?1003h"
DisableMouseAllMotion = "\x1b[?1003l"
RequestMouseAllMotion = "\x1b[?1003$p"
)
// Focus Event Mode determines whether the terminal reports focus and blur events.
//
// Deprecated: use [ModeFocusEvent] instead.
const (
FocusEventMode = DECMode(1004)
SetFocusEventMode = "\x1b[?1004h"
ResetFocusEventMode = "\x1b[?1004l"
RequestFocusEventMode = "\x1b[?1004$p"
)
// Focus reporting mode.
//
// Deprecated: use [SetModeFocusEvent], [ResetModeFocusEvent], and
// [RequestModeFocusEvent] instead.
const (
ReportFocusMode = DECMode(1004)
EnableReportFocus = "\x1b[?1004h"
DisableReportFocus = "\x1b[?1004l"
RequestReportFocus = "\x1b[?1004$p"
)
// UTF-8 Extended Mouse Mode changes the mouse tracking encoding to use UTF-8 parameters.
//
// Deprecated: use [ModeMouseExtUtf8] instead.
const (
Utf8ExtMouseMode = DECMode(1005)
SetUtf8ExtMouseMode = "\x1b[?1005h"
ResetUtf8ExtMouseMode = "\x1b[?1005l"
RequestUtf8ExtMouseMode = "\x1b[?1005$p"
)
// SGR Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters.
//
// Deprecated: use [ModeMouseExtSgr] instead.
const (
SgrExtMouseMode = DECMode(1006)
SetSgrExtMouseMode = "\x1b[?1006h"
ResetSgrExtMouseMode = "\x1b[?1006l"
RequestSgrExtMouseMode = "\x1b[?1006$p"
)
// Mouse SGR Extended mode.
//
// Deprecated: use [ModeMouseExtSgr], [SetModeMouseExtSgr],
// [ResetModeMouseExtSgr], and [RequestModeMouseExtSgr] instead.
const (
MouseSgrExtMode = DECMode(1006)
EnableMouseSgrExt = "\x1b[?1006h"
DisableMouseSgrExt = "\x1b[?1006l"
RequestMouseSgrExt = "\x1b[?1006$p"
)
// URXVT Extended Mouse Mode changes the mouse tracking encoding to use an alternate encoding.
//
// Deprecated: use [ModeMouseUrxvtExt] instead.
const (
UrxvtExtMouseMode = DECMode(1015)
SetUrxvtExtMouseMode = "\x1b[?1015h"
ResetUrxvtExtMouseMode = "\x1b[?1015l"
RequestUrxvtExtMouseMode = "\x1b[?1015$p"
)
// SGR Pixel Extended Mouse Mode changes the mouse tracking encoding to use SGR parameters with pixel coordinates.
//
// Deprecated: use [ModeMouseExtSgrPixel] instead.
const (
SgrPixelExtMouseMode = DECMode(1016)
SetSgrPixelExtMouseMode = "\x1b[?1016h"
ResetSgrPixelExtMouseMode = "\x1b[?1016l"
RequestSgrPixelExtMouseMode = "\x1b[?1016$p"
)
// Alternate Screen Mode determines whether the alternate screen buffer is active.
//
// Deprecated: use [ModeAltScreen] instead.
const (
AltScreenMode = DECMode(1047)
SetAltScreenMode = "\x1b[?1047h"
ResetAltScreenMode = "\x1b[?1047l"
RequestAltScreenMode = "\x1b[?1047$p"
)
// Save Cursor Mode saves the cursor position.
//
// Deprecated: use [ModeSaveCursor] instead.
const (
SaveCursorMode = DECMode(1048)
SetSaveCursorMode = "\x1b[?1048h"
ResetSaveCursorMode = "\x1b[?1048l"
RequestSaveCursorMode = "\x1b[?1048$p"
)
// Alternate Screen Save Cursor Mode saves the cursor position and switches to alternate screen.
//
// Deprecated: use [ModeAltScreenSaveCursor] instead.
const (
AltScreenSaveCursorMode = DECMode(1049)
SetAltScreenSaveCursorMode = "\x1b[?1049h"
ResetAltScreenSaveCursorMode = "\x1b[?1049l"
RequestAltScreenSaveCursorMode = "\x1b[?1049$p"
)
// Alternate Screen Buffer mode.
//
// Deprecated: use [ModeAltScreenSaveCursor] instead.
const (
AltScreenBufferMode = DECMode(1049)
SetAltScreenBufferMode = "\x1b[?1049h"
ResetAltScreenBufferMode = "\x1b[?1049l"
RequestAltScreenBufferMode = "\x1b[?1049$p"
EnableAltScreenBuffer = "\x1b[?1049h"
DisableAltScreenBuffer = "\x1b[?1049l"
RequestAltScreenBuffer = "\x1b[?1049$p"
)
// Bracketed Paste Mode determines whether pasted text is bracketed with escape sequences.
//
// Deprecated: use [ModeBracketedPaste] instead.
const (
BracketedPasteMode = DECMode(2004)
SetBracketedPasteMode = "\x1b[?2004h"
ResetBracketedPasteMode = "\x1b[?2004l"
RequestBracketedPasteMode = "\x1b[?2004$p"
)
// Deprecated: use [SetModeBracketedPaste], [ResetModeBracketedPaste], and
// [RequestModeBracketedPaste] instead.
const (
EnableBracketedPaste = "\x1b[?2004h" //nolint:revive
DisableBracketedPaste = "\x1b[?2004l"
RequestBracketedPaste = "\x1b[?2004$p"
)
// Synchronized Output Mode determines whether output is synchronized with the terminal.
//
// Deprecated: use [ModeSynchronizedOutput] instead.
const (
SynchronizedOutputMode = DECMode(2026)
SetSynchronizedOutputMode = "\x1b[?2026h"
ResetSynchronizedOutputMode = "\x1b[?2026l"
RequestSynchronizedOutputMode = "\x1b[?2026$p"
)
// Synchronized output mode.
//
// Deprecated: use [ModeSynchronizedOutput], [SetModeSynchronizedOutput],
// [ResetModeSynchronizedOutput], and [RequestModeSynchronizedOutput] instead.
const (
SyncdOutputMode = DECMode(2026)
EnableSyncdOutput = "\x1b[?2026h"
DisableSyncdOutput = "\x1b[?2026l"
RequestSyncdOutput = "\x1b[?2026$p"
)
// Unicode Core Mode determines whether the terminal uses Unicode grapheme clustering.
//
// Deprecated: use [ModeUnicodeCore] instead.
const (
UnicodeCoreMode = DECMode(2027)
SetUnicodeCoreMode = "\x1b[?2027h"
ResetUnicodeCoreMode = "\x1b[?2027l"
RequestUnicodeCoreMode = "\x1b[?2027$p"
)
// Grapheme Clustering Mode determines whether the terminal looks for grapheme clusters.
//
// Deprecated: use [ModeUnicodeCore], [SetModeUnicodeCore],
// [ResetModeUnicodeCore], and [RequestModeUnicodeCore] instead.
const (
GraphemeClusteringMode = DECMode(2027)
SetGraphemeClusteringMode = "\x1b[?2027h"
ResetGraphemeClusteringMode = "\x1b[?2027l"
RequestGraphemeClusteringMode = "\x1b[?2027$p"
)
// Unicode Core mode.
//
// Deprecated: use [SetModeUnicodeCore], [ResetModeUnicodeCore], and
// [RequestModeUnicodeCore] instead.
const (
EnableGraphemeClustering = "\x1b[?2027h"
DisableGraphemeClustering = "\x1b[?2027l"
RequestGraphemeClustering = "\x1b[?2027$p"
)
// Light Dark Mode enables reporting the operating system's color scheme preference.
//
// Deprecated: use [ModeLightDark] instead.
const (
LightDarkMode = DECMode(2031)
SetLightDarkMode = "\x1b[?2031h"
ResetLightDarkMode = "\x1b[?2031l"
RequestLightDarkMode = "\x1b[?2031$p"
)
// In Band Resize Mode reports terminal resize events as escape sequences.
//
// Deprecated: use [ModeInBandResize] instead.
const (
InBandResizeMode = DECMode(2048)
SetInBandResizeMode = "\x1b[?2048h"
ResetInBandResizeMode = "\x1b[?2048l"
RequestInBandResizeMode = "\x1b[?2048$p"
)
// Win32Input determines whether input is processed by the Win32 console and Conpty.
//
// Deprecated: use [ModeWin32Input] instead.
const (
Win32InputMode = DECMode(9001)
SetWin32InputMode = "\x1b[?9001h"
ResetWin32InputMode = "\x1b[?9001l"
RequestWin32InputMode = "\x1b[?9001$p"
)
// Deprecated: use [SetModeWin32Input], [ResetModeWin32Input], and
// [RequestModeWin32Input] instead.
const (
EnableWin32Input = "\x1b[?9001h" //nolint:revive
DisableWin32Input = "\x1b[?9001l"
RequestWin32Input = "\x1b[?9001$p"
)

View File

@ -134,7 +134,7 @@ func EncodeMouseButton(b MouseButton, motion, shift, alt, ctrl bool) (m byte) {
m |= bitMotion
}
return m
return //nolint:nakedret
}
// x10Offset is the offset for X10 mouse events.

View File

@ -1,10 +1,5 @@
package ansi
import (
"fmt"
"strings"
)
// Notify sends a desktop notification using iTerm's OSC 9.
//
// OSC 9 ; Mc ST
@ -16,17 +11,3 @@ import (
func Notify(s string) string {
return "\x1b]9;" + s + "\x07"
}
// DesktopNotification sends a desktop notification based on the extensible OSC
// 99 escape code.
//
// OSC 99 ; <metadata> ; <payload> ST
// OSC 99 ; <metadata> ; <payload> BEL
//
// Where <metadata> is a colon-separated list of key-value pairs, and
// <payload> is the notification body.
//
// See: https://sw.kovidgoyal.net/kitty/desktop-notifications/
func DesktopNotification(payload string, metadata ...string) string {
return fmt.Sprintf("\x1b]99;%s;%s\x07", strings.Join(metadata, ":"), payload)
}

View File

@ -4,9 +4,8 @@ import (
"unicode/utf8"
"github.com/charmbracelet/x/ansi/parser"
"github.com/clipperhouse/displaywidth"
"github.com/clipperhouse/uax29/v2/graphemes"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// State represents the state of the ANSI escape sequence parser used by
@ -177,7 +176,10 @@ func decodeSequence[T string | []byte](m Method, b T, state State, p *Parser) (s
}
if utf8.RuneStart(c) {
seq, width = FirstGraphemeCluster(b, m)
seq, _, width, _ = FirstGraphemeCluster(b, -1)
if m == WcWidth {
width = runewidth.StringWidth(string(seq))
}
i += len(seq)
return b[:i], width, i, NormalState
}
@ -432,22 +434,17 @@ func HasEscPrefix[T string | []byte](b T) bool {
return len(b) > 0 && b[0] == ESC
}
// FirstGraphemeCluster returns the first grapheme cluster in the given string
// or byte slice, and its monospace display width.
func FirstGraphemeCluster[T string | []byte](b T, m Method) (T, int) {
// FirstGraphemeCluster returns the first grapheme cluster in the given string or byte slice.
// This is a syntactic sugar function that wraps
// uniseg.FirstGraphemeClusterInString and uniseg.FirstGraphemeCluster.
func FirstGraphemeCluster[T string | []byte](b T, state int) (T, T, int, int) {
switch b := any(b).(type) {
case string:
cluster := graphemes.FromString(b).First()
if m == WcWidth {
return T(cluster), runewidth.StringWidth(cluster)
}
return T(cluster), displaywidth.String(cluster)
cluster, rest, width, newState := uniseg.FirstGraphemeClusterInString(b, state)
return T(cluster), T(rest), width, newState
case []byte:
cluster := graphemes.FromBytes(b).First()
if m == WcWidth {
return T(cluster), runewidth.StringWidth(string(cluster))
}
return T(cluster), displaywidth.Bytes(cluster)
cluster, rest, width, newState := uniseg.FirstGraphemeCluster(b, state)
return T(cluster), T(rest), width, newState
}
panic("unreachable")
}
@ -493,7 +490,7 @@ func Command(prefix, inter, final byte) (c int) {
c = int(final)
c |= int(prefix) << parser.PrefixShift
c |= int(inter) << parser.IntermedShift
return c
return
}
// Param represents a sequence parameter. Sequence parameters with
@ -523,5 +520,5 @@ func Parameter(p int, hasMore bool) (s int) {
if hasMore {
s |= parser.HasMoreFlag
}
return s
return
}

View File

@ -21,59 +21,59 @@ func SGR(ps ...Attr) string {
}
var attrStrings = map[int]string{
AttrReset: attrReset,
AttrBold: attrBold,
AttrFaint: attrFaint,
AttrItalic: attrItalic,
AttrUnderline: attrUnderline,
AttrBlink: attrBlink,
AttrRapidBlink: attrRapidBlink,
AttrReverse: attrReverse,
AttrConceal: attrConceal,
AttrStrikethrough: attrStrikethrough,
AttrNormalIntensity: attrNormalIntensity,
AttrNoItalic: attrNoItalic,
AttrNoUnderline: attrNoUnderline,
AttrNoBlink: attrNoBlink,
AttrNoReverse: attrNoReverse,
AttrNoConceal: attrNoConceal,
AttrNoStrikethrough: attrNoStrikethrough,
AttrBlackForegroundColor: attrBlackForegroundColor,
AttrRedForegroundColor: attrRedForegroundColor,
AttrGreenForegroundColor: attrGreenForegroundColor,
AttrYellowForegroundColor: attrYellowForegroundColor,
AttrBlueForegroundColor: attrBlueForegroundColor,
AttrMagentaForegroundColor: attrMagentaForegroundColor,
AttrCyanForegroundColor: attrCyanForegroundColor,
AttrWhiteForegroundColor: attrWhiteForegroundColor,
AttrExtendedForegroundColor: attrExtendedForegroundColor,
AttrDefaultForegroundColor: attrDefaultForegroundColor,
AttrBlackBackgroundColor: attrBlackBackgroundColor,
AttrRedBackgroundColor: attrRedBackgroundColor,
AttrGreenBackgroundColor: attrGreenBackgroundColor,
AttrYellowBackgroundColor: attrYellowBackgroundColor,
AttrBlueBackgroundColor: attrBlueBackgroundColor,
AttrMagentaBackgroundColor: attrMagentaBackgroundColor,
AttrCyanBackgroundColor: attrCyanBackgroundColor,
AttrWhiteBackgroundColor: attrWhiteBackgroundColor,
AttrExtendedBackgroundColor: attrExtendedBackgroundColor,
AttrDefaultBackgroundColor: attrDefaultBackgroundColor,
AttrExtendedUnderlineColor: attrExtendedUnderlineColor,
AttrDefaultUnderlineColor: attrDefaultUnderlineColor,
AttrBrightBlackForegroundColor: attrBrightBlackForegroundColor,
AttrBrightRedForegroundColor: attrBrightRedForegroundColor,
AttrBrightGreenForegroundColor: attrBrightGreenForegroundColor,
AttrBrightYellowForegroundColor: attrBrightYellowForegroundColor,
AttrBrightBlueForegroundColor: attrBrightBlueForegroundColor,
AttrBrightMagentaForegroundColor: attrBrightMagentaForegroundColor,
AttrBrightCyanForegroundColor: attrBrightCyanForegroundColor,
AttrBrightWhiteForegroundColor: attrBrightWhiteForegroundColor,
AttrBrightBlackBackgroundColor: attrBrightBlackBackgroundColor,
AttrBrightRedBackgroundColor: attrBrightRedBackgroundColor,
AttrBrightGreenBackgroundColor: attrBrightGreenBackgroundColor,
AttrBrightYellowBackgroundColor: attrBrightYellowBackgroundColor,
AttrBrightBlueBackgroundColor: attrBrightBlueBackgroundColor,
AttrBrightMagentaBackgroundColor: attrBrightMagentaBackgroundColor,
AttrBrightCyanBackgroundColor: attrBrightCyanBackgroundColor,
AttrBrightWhiteBackgroundColor: attrBrightWhiteBackgroundColor,
ResetAttr: resetAttr,
BoldAttr: boldAttr,
FaintAttr: faintAttr,
ItalicAttr: italicAttr,
UnderlineAttr: underlineAttr,
SlowBlinkAttr: slowBlinkAttr,
RapidBlinkAttr: rapidBlinkAttr,
ReverseAttr: reverseAttr,
ConcealAttr: concealAttr,
StrikethroughAttr: strikethroughAttr,
NormalIntensityAttr: normalIntensityAttr,
NoItalicAttr: noItalicAttr,
NoUnderlineAttr: noUnderlineAttr,
NoBlinkAttr: noBlinkAttr,
NoReverseAttr: noReverseAttr,
NoConcealAttr: noConcealAttr,
NoStrikethroughAttr: noStrikethroughAttr,
BlackForegroundColorAttr: blackForegroundColorAttr,
RedForegroundColorAttr: redForegroundColorAttr,
GreenForegroundColorAttr: greenForegroundColorAttr,
YellowForegroundColorAttr: yellowForegroundColorAttr,
BlueForegroundColorAttr: blueForegroundColorAttr,
MagentaForegroundColorAttr: magentaForegroundColorAttr,
CyanForegroundColorAttr: cyanForegroundColorAttr,
WhiteForegroundColorAttr: whiteForegroundColorAttr,
ExtendedForegroundColorAttr: extendedForegroundColorAttr,
DefaultForegroundColorAttr: defaultForegroundColorAttr,
BlackBackgroundColorAttr: blackBackgroundColorAttr,
RedBackgroundColorAttr: redBackgroundColorAttr,
GreenBackgroundColorAttr: greenBackgroundColorAttr,
YellowBackgroundColorAttr: yellowBackgroundColorAttr,
BlueBackgroundColorAttr: blueBackgroundColorAttr,
MagentaBackgroundColorAttr: magentaBackgroundColorAttr,
CyanBackgroundColorAttr: cyanBackgroundColorAttr,
WhiteBackgroundColorAttr: whiteBackgroundColorAttr,
ExtendedBackgroundColorAttr: extendedBackgroundColorAttr,
DefaultBackgroundColorAttr: defaultBackgroundColorAttr,
ExtendedUnderlineColorAttr: extendedUnderlineColorAttr,
DefaultUnderlineColorAttr: defaultUnderlineColorAttr,
BrightBlackForegroundColorAttr: brightBlackForegroundColorAttr,
BrightRedForegroundColorAttr: brightRedForegroundColorAttr,
BrightGreenForegroundColorAttr: brightGreenForegroundColorAttr,
BrightYellowForegroundColorAttr: brightYellowForegroundColorAttr,
BrightBlueForegroundColorAttr: brightBlueForegroundColorAttr,
BrightMagentaForegroundColorAttr: brightMagentaForegroundColorAttr,
BrightCyanForegroundColorAttr: brightCyanForegroundColorAttr,
BrightWhiteForegroundColorAttr: brightWhiteForegroundColorAttr,
BrightBlackBackgroundColorAttr: brightBlackBackgroundColorAttr,
BrightRedBackgroundColorAttr: brightRedBackgroundColorAttr,
BrightGreenBackgroundColorAttr: brightGreenBackgroundColorAttr,
BrightYellowBackgroundColorAttr: brightYellowBackgroundColorAttr,
BrightBlueBackgroundColorAttr: brightBlueBackgroundColorAttr,
BrightMagentaBackgroundColorAttr: brightMagentaBackgroundColorAttr,
BrightCyanBackgroundColorAttr: brightCyanBackgroundColorAttr,
BrightWhiteBackgroundColorAttr: brightWhiteBackgroundColorAttr,
}

View File

@ -17,9 +17,7 @@ type Attr = int
// Style represents an ANSI SGR (Select Graphic Rendition) style.
type Style []string
// NewStyle returns a new style with the given attributes. Attributes are SGR
// (Select Graphic Rendition) codes that control text formatting like bold,
// italic, colors, etc.
// NewStyle returns a new style with the given attributes.
func NewStyle(attrs ...Attr) Style {
if len(attrs) == 0 {
return Style{}
@ -48,8 +46,7 @@ func (s Style) String() string {
return "\x1b[" + strings.Join(s, ";") + "m"
}
// Styled returns a styled string with the given style applied. The style is
// applied at the beginning and reset at the end of the string.
// Styled returns a styled string with the given style applied.
func (s Style) Styled(str string) string {
if len(s) == 0 {
return str
@ -57,446 +54,309 @@ func (s Style) Styled(str string) string {
return s.String() + str + ResetStyle
}
// Reset appends the reset style attribute to the style. This resets all
// formatting attributes to their defaults.
// Reset appends the reset style attribute to the style.
func (s Style) Reset() Style {
return append(s, attrReset)
return append(s, resetAttr)
}
// Bold appends the bold or normal intensity style attribute to the style.
// You can use [Style.Normal] to reset to normal intensity.
// Bold appends the bold style attribute to the style.
func (s Style) Bold() Style {
return append(s, attrBold)
return append(s, boldAttr)
}
// Faint appends the faint or normal intensity style attribute to the style.
// You can use [Style.Normal] to reset to normal intensity.
// Faint appends the faint style attribute to the style.
func (s Style) Faint() Style {
return append(s, attrFaint)
return append(s, faintAttr)
}
// Italic appends the italic or no italic style attribute to the style.
// When v is true, text is rendered in italic. When false, italic is disabled.
func (s Style) Italic(v bool) Style {
if v {
return append(s, attrItalic)
}
return append(s, attrNoItalic)
// Italic appends the italic style attribute to the style.
func (s Style) Italic() Style {
return append(s, italicAttr)
}
// Underline appends the underline or no underline style attribute to the style.
// When v is true, text is underlined. When false, underline is disabled.
func (s Style) Underline(v bool) Style {
if v {
return append(s, attrUnderline)
}
return append(s, attrNoUnderline)
// Underline appends the underline style attribute to the style.
func (s Style) Underline() Style {
return append(s, underlineAttr)
}
// UnderlineStyle appends the underline style attribute to the style.
// Supports various underline styles including single, double, curly, dotted,
// and dashed.
func (s Style) UnderlineStyle(u Underline) Style {
func (s Style) UnderlineStyle(u UnderlineStyle) Style {
switch u {
case UnderlineNone:
return s.Underline(false)
case UnderlineSingle:
return s.Underline(true)
case UnderlineDouble:
return append(s, underlineDouble)
case UnderlineCurly:
return append(s, underlineCurly)
case UnderlineDotted:
return append(s, underlineDotted)
case UnderlineDashed:
return append(s, underlineDashed)
case NoUnderlineStyle:
return s.NoUnderline()
case SingleUnderlineStyle:
return s.Underline()
case DoubleUnderlineStyle:
return append(s, doubleUnderlineStyle)
case CurlyUnderlineStyle:
return append(s, curlyUnderlineStyle)
case DottedUnderlineStyle:
return append(s, dottedUnderlineStyle)
case DashedUnderlineStyle:
return append(s, dashedUnderlineStyle)
}
return s
}
// Blink appends the slow blink or no blink style attribute to the style.
// When v is true, text blinks slowly (less than 150 per minute). When false,
// blinking is disabled.
func (s Style) Blink(v bool) Style {
if v {
return append(s, attrBlink)
}
return append(s, attrNoBlink)
// DoubleUnderline appends the double underline style attribute to the style.
// This is a convenience method for UnderlineStyle(DoubleUnderlineStyle).
func (s Style) DoubleUnderline() Style {
return s.UnderlineStyle(DoubleUnderlineStyle)
}
// RapidBlink appends the rapid blink or no blink style attribute to the style.
// When v is true, text blinks rapidly (150+ per minute). When false, blinking
// is disabled.
//
// Note that this is not widely supported in terminal emulators.
func (s Style) RapidBlink(v bool) Style {
if v {
return append(s, attrRapidBlink)
}
return append(s, attrNoBlink)
// CurlyUnderline appends the curly underline style attribute to the style.
// This is a convenience method for UnderlineStyle(CurlyUnderlineStyle).
func (s Style) CurlyUnderline() Style {
return s.UnderlineStyle(CurlyUnderlineStyle)
}
// Reverse appends the reverse or no reverse style attribute to the style.
// When v is true, foreground and background colors are swapped. When false,
// reverse video is disabled.
func (s Style) Reverse(v bool) Style {
if v {
return append(s, attrReverse)
}
return append(s, attrNoReverse)
// DottedUnderline appends the dotted underline style attribute to the style.
// This is a convenience method for UnderlineStyle(DottedUnderlineStyle).
func (s Style) DottedUnderline() Style {
return s.UnderlineStyle(DottedUnderlineStyle)
}
// Conceal appends the conceal or no conceal style attribute to the style.
// When v is true, text is hidden/concealed. When false, concealment is
// disabled.
func (s Style) Conceal(v bool) Style {
if v {
return append(s, attrConceal)
}
return append(s, attrNoConceal)
// DashedUnderline appends the dashed underline style attribute to the style.
// This is a convenience method for UnderlineStyle(DashedUnderlineStyle).
func (s Style) DashedUnderline() Style {
return s.UnderlineStyle(DashedUnderlineStyle)
}
// Strikethrough appends the strikethrough or no strikethrough style attribute
// to the style. When v is true, text is rendered with a horizontal line through
// it. When false, strikethrough is disabled.
func (s Style) Strikethrough(v bool) Style {
if v {
return append(s, attrStrikethrough)
}
return append(s, attrNoStrikethrough)
// SlowBlink appends the slow blink style attribute to the style.
func (s Style) SlowBlink() Style {
return append(s, slowBlinkAttr)
}
// Normal appends the normal intensity style attribute to the style. This
// resets [Style.Bold] and [Style.Faint] attributes.
func (s Style) Normal() Style {
return append(s, attrNormalIntensity)
// RapidBlink appends the rapid blink style attribute to the style.
func (s Style) RapidBlink() Style {
return append(s, rapidBlinkAttr)
}
// Reverse appends the reverse style attribute to the style.
func (s Style) Reverse() Style {
return append(s, reverseAttr)
}
// Conceal appends the conceal style attribute to the style.
func (s Style) Conceal() Style {
return append(s, concealAttr)
}
// Strikethrough appends the strikethrough style attribute to the style.
func (s Style) Strikethrough() Style {
return append(s, strikethroughAttr)
}
// NormalIntensity appends the normal intensity style attribute to the style.
func (s Style) NormalIntensity() Style {
return append(s, normalIntensityAttr)
}
// NoItalic appends the no italic style attribute to the style.
//
// Deprecated: use [Style.Italic](false) instead.
func (s Style) NoItalic() Style {
return append(s, attrNoItalic)
return append(s, noItalicAttr)
}
// NoUnderline appends the no underline style attribute to the style.
//
// Deprecated: use [Style.Underline](false) instead.
func (s Style) NoUnderline() Style {
return append(s, attrNoUnderline)
return append(s, noUnderlineAttr)
}
// NoBlink appends the no blink style attribute to the style.
//
// Deprecated: use [Style.Blink](false) or [Style.RapidBlink](false) instead.
func (s Style) NoBlink() Style {
return append(s, attrNoBlink)
return append(s, noBlinkAttr)
}
// NoReverse appends the no reverse style attribute to the style.
//
// Deprecated: use [Style.Reverse](false) instead.
func (s Style) NoReverse() Style {
return append(s, attrNoReverse)
return append(s, noReverseAttr)
}
// NoConceal appends the no conceal style attribute to the style.
//
// Deprecated: use [Style.Conceal](false) instead.
func (s Style) NoConceal() Style {
return append(s, attrNoConceal)
return append(s, noConcealAttr)
}
// NoStrikethrough appends the no strikethrough style attribute to the style.
//
// Deprecated: use [Style.Strikethrough](false) instead.
func (s Style) NoStrikethrough() Style {
return append(s, attrNoStrikethrough)
return append(s, noStrikethroughAttr)
}
// DefaultForegroundColor appends the default foreground color style attribute to the style.
//
// Deprecated: use [Style.ForegroundColor](nil) instead.
func (s Style) DefaultForegroundColor() Style {
return append(s, attrDefaultForegroundColor)
return append(s, defaultForegroundColorAttr)
}
// DefaultBackgroundColor appends the default background color style attribute to the style.
//
// Deprecated: use [Style.BackgroundColor](nil) instead.
func (s Style) DefaultBackgroundColor() Style {
return append(s, attrDefaultBackgroundColor)
return append(s, defaultBackgroundColorAttr)
}
// DefaultUnderlineColor appends the default underline color style attribute to the style.
//
// Deprecated: use [Style.UnderlineColor](nil) instead.
func (s Style) DefaultUnderlineColor() Style {
return append(s, attrDefaultUnderlineColor)
return append(s, defaultUnderlineColorAttr)
}
// ForegroundColor appends the foreground color style attribute to the style.
// If c is nil, the default foreground color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) ForegroundColor(c Color) Style {
if c == nil {
return append(s, attrDefaultForegroundColor)
}
return append(s, foregroundColorString(c))
}
// BackgroundColor appends the background color style attribute to the style.
// If c is nil, the default background color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) BackgroundColor(c Color) Style {
if c == nil {
return append(s, attrDefaultBackgroundColor)
}
return append(s, backgroundColorString(c))
}
// UnderlineColor appends the underline color style attribute to the style.
// If c is nil, the default underline color is used. Supports [BasicColor],
// [IndexedColor] (256-color), and [color.Color] (24-bit RGB).
func (s Style) UnderlineColor(c Color) Style {
if c == nil {
return append(s, attrDefaultUnderlineColor)
}
return append(s, underlineColorString(c))
}
// Underline represents an ANSI SGR (Select Graphic Rendition) underline style.
type Underline = byte
// UnderlineStyle represents an ANSI SGR (Select Graphic Rendition) underline
// style.
//
// Deprecated: use [Underline] instead.
type UnderlineStyle = byte
const (
underlineDouble = "4:2"
underlineCurly = "4:3"
underlineDotted = "4:4"
underlineDashed = "4:5"
doubleUnderlineStyle = "4:2"
curlyUnderlineStyle = "4:3"
dottedUnderlineStyle = "4:4"
dashedUnderlineStyle = "4:5"
)
// Underline styles constants.
const (
UnderlineNone Underline = iota
UnderlineSingle
UnderlineDouble
UnderlineCurly
UnderlineDotted
UnderlineDashed
)
// Underline styles constants.
//
// Deprecated: use [UnderlineNone], [UnderlineSingle], etc. instead.
const (
NoUnderlineStyle Underline = iota
// NoUnderlineStyle is the default underline style.
NoUnderlineStyle UnderlineStyle = iota
// SingleUnderlineStyle is a single underline style.
SingleUnderlineStyle
// DoubleUnderlineStyle is a double underline style.
DoubleUnderlineStyle
// CurlyUnderlineStyle is a curly underline style.
CurlyUnderlineStyle
// DottedUnderlineStyle is a dotted underline style.
DottedUnderlineStyle
// DashedUnderlineStyle is a dashed underline style.
DashedUnderlineStyle
)
// Underline styles constants.
//
// Deprecated: use [UnderlineNone], [UnderlineSingle], etc. instead.
const (
UnderlineStyleNone Underline = iota
UnderlineStyleSingle
UnderlineStyleDouble
UnderlineStyleCurly
UnderlineStyleDotted
UnderlineStyleDashed
)
// SGR (Select Graphic Rendition) style attributes.
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
const (
AttrReset Attr = 0
AttrBold Attr = 1
AttrFaint Attr = 2
AttrItalic Attr = 3
AttrUnderline Attr = 4
AttrBlink Attr = 5
AttrRapidBlink Attr = 6
AttrReverse Attr = 7
AttrConceal Attr = 8
AttrStrikethrough Attr = 9
AttrNormalIntensity Attr = 22
AttrNoItalic Attr = 23
AttrNoUnderline Attr = 24
AttrNoBlink Attr = 25
AttrNoReverse Attr = 27
AttrNoConceal Attr = 28
AttrNoStrikethrough Attr = 29
AttrBlackForegroundColor Attr = 30
AttrRedForegroundColor Attr = 31
AttrGreenForegroundColor Attr = 32
AttrYellowForegroundColor Attr = 33
AttrBlueForegroundColor Attr = 34
AttrMagentaForegroundColor Attr = 35
AttrCyanForegroundColor Attr = 36
AttrWhiteForegroundColor Attr = 37
AttrExtendedForegroundColor Attr = 38
AttrDefaultForegroundColor Attr = 39
AttrBlackBackgroundColor Attr = 40
AttrRedBackgroundColor Attr = 41
AttrGreenBackgroundColor Attr = 42
AttrYellowBackgroundColor Attr = 43
AttrBlueBackgroundColor Attr = 44
AttrMagentaBackgroundColor Attr = 45
AttrCyanBackgroundColor Attr = 46
AttrWhiteBackgroundColor Attr = 47
AttrExtendedBackgroundColor Attr = 48
AttrDefaultBackgroundColor Attr = 49
AttrExtendedUnderlineColor Attr = 58
AttrDefaultUnderlineColor Attr = 59
AttrBrightBlackForegroundColor Attr = 90
AttrBrightRedForegroundColor Attr = 91
AttrBrightGreenForegroundColor Attr = 92
AttrBrightYellowForegroundColor Attr = 93
AttrBrightBlueForegroundColor Attr = 94
AttrBrightMagentaForegroundColor Attr = 95
AttrBrightCyanForegroundColor Attr = 96
AttrBrightWhiteForegroundColor Attr = 97
AttrBrightBlackBackgroundColor Attr = 100
AttrBrightRedBackgroundColor Attr = 101
AttrBrightGreenBackgroundColor Attr = 102
AttrBrightYellowBackgroundColor Attr = 103
AttrBrightBlueBackgroundColor Attr = 104
AttrBrightMagentaBackgroundColor Attr = 105
AttrBrightCyanBackgroundColor Attr = 106
AttrBrightWhiteBackgroundColor Attr = 107
ResetAttr Attr = 0
BoldAttr Attr = 1
FaintAttr Attr = 2
ItalicAttr Attr = 3
UnderlineAttr Attr = 4
SlowBlinkAttr Attr = 5
RapidBlinkAttr Attr = 6
ReverseAttr Attr = 7
ConcealAttr Attr = 8
StrikethroughAttr Attr = 9
NormalIntensityAttr Attr = 22
NoItalicAttr Attr = 23
NoUnderlineAttr Attr = 24
NoBlinkAttr Attr = 25
NoReverseAttr Attr = 27
NoConcealAttr Attr = 28
NoStrikethroughAttr Attr = 29
BlackForegroundColorAttr Attr = 30
RedForegroundColorAttr Attr = 31
GreenForegroundColorAttr Attr = 32
YellowForegroundColorAttr Attr = 33
BlueForegroundColorAttr Attr = 34
MagentaForegroundColorAttr Attr = 35
CyanForegroundColorAttr Attr = 36
WhiteForegroundColorAttr Attr = 37
ExtendedForegroundColorAttr Attr = 38
DefaultForegroundColorAttr Attr = 39
BlackBackgroundColorAttr Attr = 40
RedBackgroundColorAttr Attr = 41
GreenBackgroundColorAttr Attr = 42
YellowBackgroundColorAttr Attr = 43
BlueBackgroundColorAttr Attr = 44
MagentaBackgroundColorAttr Attr = 45
CyanBackgroundColorAttr Attr = 46
WhiteBackgroundColorAttr Attr = 47
ExtendedBackgroundColorAttr Attr = 48
DefaultBackgroundColorAttr Attr = 49
ExtendedUnderlineColorAttr Attr = 58
DefaultUnderlineColorAttr Attr = 59
BrightBlackForegroundColorAttr Attr = 90
BrightRedForegroundColorAttr Attr = 91
BrightGreenForegroundColorAttr Attr = 92
BrightYellowForegroundColorAttr Attr = 93
BrightBlueForegroundColorAttr Attr = 94
BrightMagentaForegroundColorAttr Attr = 95
BrightCyanForegroundColorAttr Attr = 96
BrightWhiteForegroundColorAttr Attr = 97
BrightBlackBackgroundColorAttr Attr = 100
BrightRedBackgroundColorAttr Attr = 101
BrightGreenBackgroundColorAttr Attr = 102
BrightYellowBackgroundColorAttr Attr = 103
BrightBlueBackgroundColorAttr Attr = 104
BrightMagentaBackgroundColorAttr Attr = 105
BrightCyanBackgroundColorAttr Attr = 106
BrightWhiteBackgroundColorAttr Attr = 107
AttrRGBColorIntroducer Attr = 2
AttrExtendedColorIntroducer Attr = 5
)
// SGR (Select Graphic Rendition) style attributes.
//
// Deprecated: use Attr* constants instead.
const (
ResetAttr = AttrReset
BoldAttr = AttrBold
FaintAttr = AttrFaint
ItalicAttr = AttrItalic
UnderlineAttr = AttrUnderline
SlowBlinkAttr = AttrBlink
RapidBlinkAttr = AttrRapidBlink
ReverseAttr = AttrReverse
ConcealAttr = AttrConceal
StrikethroughAttr = AttrStrikethrough
NormalIntensityAttr = AttrNormalIntensity
NoItalicAttr = AttrNoItalic
NoUnderlineAttr = AttrNoUnderline
NoBlinkAttr = AttrNoBlink
NoReverseAttr = AttrNoReverse
NoConcealAttr = AttrNoConceal
NoStrikethroughAttr = AttrNoStrikethrough
BlackForegroundColorAttr = AttrBlackForegroundColor
RedForegroundColorAttr = AttrRedForegroundColor
GreenForegroundColorAttr = AttrGreenForegroundColor
YellowForegroundColorAttr = AttrYellowForegroundColor
BlueForegroundColorAttr = AttrBlueForegroundColor
MagentaForegroundColorAttr = AttrMagentaForegroundColor
CyanForegroundColorAttr = AttrCyanForegroundColor
WhiteForegroundColorAttr = AttrWhiteForegroundColor
ExtendedForegroundColorAttr = AttrExtendedForegroundColor
DefaultForegroundColorAttr = AttrDefaultForegroundColor
BlackBackgroundColorAttr = AttrBlackBackgroundColor
RedBackgroundColorAttr = AttrRedBackgroundColor
GreenBackgroundColorAttr = AttrGreenBackgroundColor
YellowBackgroundColorAttr = AttrYellowBackgroundColor
BlueBackgroundColorAttr = AttrBlueBackgroundColor
MagentaBackgroundColorAttr = AttrMagentaBackgroundColor
CyanBackgroundColorAttr = AttrCyanBackgroundColor
WhiteBackgroundColorAttr = AttrWhiteBackgroundColor
ExtendedBackgroundColorAttr = AttrExtendedBackgroundColor
DefaultBackgroundColorAttr = AttrDefaultBackgroundColor
ExtendedUnderlineColorAttr = AttrExtendedUnderlineColor
DefaultUnderlineColorAttr = AttrDefaultUnderlineColor
BrightBlackForegroundColorAttr = AttrBrightBlackForegroundColor
BrightRedForegroundColorAttr = AttrBrightRedForegroundColor
BrightGreenForegroundColorAttr = AttrBrightGreenForegroundColor
BrightYellowForegroundColorAttr = AttrBrightYellowForegroundColor
BrightBlueForegroundColorAttr = AttrBrightBlueForegroundColor
BrightMagentaForegroundColorAttr = AttrBrightMagentaForegroundColor
BrightCyanForegroundColorAttr = AttrBrightCyanForegroundColor
BrightWhiteForegroundColorAttr = AttrBrightWhiteForegroundColor
BrightBlackBackgroundColorAttr = AttrBrightBlackBackgroundColor
BrightRedBackgroundColorAttr = AttrBrightRedBackgroundColor
BrightGreenBackgroundColorAttr = AttrBrightGreenBackgroundColor
BrightYellowBackgroundColorAttr = AttrBrightYellowBackgroundColor
BrightBlueBackgroundColorAttr = AttrBrightBlueBackgroundColor
BrightMagentaBackgroundColorAttr = AttrBrightMagentaBackgroundColor
BrightCyanBackgroundColorAttr = AttrBrightCyanBackgroundColor
BrightWhiteBackgroundColorAttr = AttrBrightWhiteBackgroundColor
RGBColorIntroducerAttr = AttrRGBColorIntroducer
ExtendedColorIntroducerAttr = AttrExtendedColorIntroducer
RGBColorIntroducerAttr Attr = 2
ExtendedColorIntroducerAttr Attr = 5
)
const (
attrReset = "0"
attrBold = "1"
attrFaint = "2"
attrItalic = "3"
attrUnderline = "4"
attrBlink = "5"
attrRapidBlink = "6"
attrReverse = "7"
attrConceal = "8"
attrStrikethrough = "9"
attrNormalIntensity = "22"
attrNoItalic = "23"
attrNoUnderline = "24"
attrNoBlink = "25"
attrNoReverse = "27"
attrNoConceal = "28"
attrNoStrikethrough = "29"
attrBlackForegroundColor = "30"
attrRedForegroundColor = "31"
attrGreenForegroundColor = "32"
attrYellowForegroundColor = "33"
attrBlueForegroundColor = "34"
attrMagentaForegroundColor = "35"
attrCyanForegroundColor = "36"
attrWhiteForegroundColor = "37"
attrExtendedForegroundColor = "38"
attrDefaultForegroundColor = "39"
attrBlackBackgroundColor = "40"
attrRedBackgroundColor = "41"
attrGreenBackgroundColor = "42"
attrYellowBackgroundColor = "43"
attrBlueBackgroundColor = "44"
attrMagentaBackgroundColor = "45"
attrCyanBackgroundColor = "46"
attrWhiteBackgroundColor = "47"
attrExtendedBackgroundColor = "48"
attrDefaultBackgroundColor = "49"
attrExtendedUnderlineColor = "58"
attrDefaultUnderlineColor = "59"
attrBrightBlackForegroundColor = "90"
attrBrightRedForegroundColor = "91"
attrBrightGreenForegroundColor = "92"
attrBrightYellowForegroundColor = "93"
attrBrightBlueForegroundColor = "94"
attrBrightMagentaForegroundColor = "95"
attrBrightCyanForegroundColor = "96"
attrBrightWhiteForegroundColor = "97"
attrBrightBlackBackgroundColor = "100"
attrBrightRedBackgroundColor = "101"
attrBrightGreenBackgroundColor = "102"
attrBrightYellowBackgroundColor = "103"
attrBrightBlueBackgroundColor = "104"
attrBrightMagentaBackgroundColor = "105"
attrBrightCyanBackgroundColor = "106"
attrBrightWhiteBackgroundColor = "107"
resetAttr = "0"
boldAttr = "1"
faintAttr = "2"
italicAttr = "3"
underlineAttr = "4"
slowBlinkAttr = "5"
rapidBlinkAttr = "6"
reverseAttr = "7"
concealAttr = "8"
strikethroughAttr = "9"
normalIntensityAttr = "22"
noItalicAttr = "23"
noUnderlineAttr = "24"
noBlinkAttr = "25"
noReverseAttr = "27"
noConcealAttr = "28"
noStrikethroughAttr = "29"
blackForegroundColorAttr = "30"
redForegroundColorAttr = "31"
greenForegroundColorAttr = "32"
yellowForegroundColorAttr = "33"
blueForegroundColorAttr = "34"
magentaForegroundColorAttr = "35"
cyanForegroundColorAttr = "36"
whiteForegroundColorAttr = "37"
extendedForegroundColorAttr = "38"
defaultForegroundColorAttr = "39"
blackBackgroundColorAttr = "40"
redBackgroundColorAttr = "41"
greenBackgroundColorAttr = "42"
yellowBackgroundColorAttr = "43"
blueBackgroundColorAttr = "44"
magentaBackgroundColorAttr = "45"
cyanBackgroundColorAttr = "46"
whiteBackgroundColorAttr = "47"
extendedBackgroundColorAttr = "48"
defaultBackgroundColorAttr = "49"
extendedUnderlineColorAttr = "58"
defaultUnderlineColorAttr = "59"
brightBlackForegroundColorAttr = "90"
brightRedForegroundColorAttr = "91"
brightGreenForegroundColorAttr = "92"
brightYellowForegroundColorAttr = "93"
brightBlueForegroundColorAttr = "94"
brightMagentaForegroundColorAttr = "95"
brightCyanForegroundColorAttr = "96"
brightWhiteForegroundColorAttr = "97"
brightBlackBackgroundColorAttr = "100"
brightRedBackgroundColorAttr = "101"
brightGreenBackgroundColorAttr = "102"
brightYellowBackgroundColorAttr = "103"
brightBlueBackgroundColorAttr = "104"
brightMagentaBackgroundColorAttr = "105"
brightCyanBackgroundColorAttr = "106"
brightWhiteBackgroundColorAttr = "107"
)
// foregroundColorString returns the style SGR attribute for the given
@ -504,44 +364,42 @@ const (
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func foregroundColorString(c Color) string {
switch c := c.(type) {
case nil:
return attrDefaultForegroundColor
case BasicColor:
// 3-bit or 4-bit ANSI foreground
// "3<n>" or "9<n>" where n is the color number from 0 to 7
switch c {
case Black:
return attrBlackForegroundColor
return blackForegroundColorAttr
case Red:
return attrRedForegroundColor
return redForegroundColorAttr
case Green:
return attrGreenForegroundColor
return greenForegroundColorAttr
case Yellow:
return attrYellowForegroundColor
return yellowForegroundColorAttr
case Blue:
return attrBlueForegroundColor
return blueForegroundColorAttr
case Magenta:
return attrMagentaForegroundColor
return magentaForegroundColorAttr
case Cyan:
return attrCyanForegroundColor
return cyanForegroundColorAttr
case White:
return attrWhiteForegroundColor
return whiteForegroundColorAttr
case BrightBlack:
return attrBrightBlackForegroundColor
return brightBlackForegroundColorAttr
case BrightRed:
return attrBrightRedForegroundColor
return brightRedForegroundColorAttr
case BrightGreen:
return attrBrightGreenForegroundColor
return brightGreenForegroundColorAttr
case BrightYellow:
return attrBrightYellowForegroundColor
return brightYellowForegroundColorAttr
case BrightBlue:
return attrBrightBlueForegroundColor
return brightBlueForegroundColorAttr
case BrightMagenta:
return attrBrightMagentaForegroundColor
return brightMagentaForegroundColorAttr
case BrightCyan:
return attrBrightCyanForegroundColor
return brightCyanForegroundColorAttr
case BrightWhite:
return attrBrightWhiteForegroundColor
return brightWhiteForegroundColorAttr
}
case ExtendedColor:
// 256-color ANSI foreground
@ -556,7 +414,7 @@ func foregroundColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return attrDefaultForegroundColor
return defaultForegroundColorAttr
}
// backgroundColorString returns the style SGR attribute for the given
@ -564,44 +422,42 @@ func foregroundColorString(c Color) string {
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func backgroundColorString(c Color) string {
switch c := c.(type) {
case nil:
return attrDefaultBackgroundColor
case BasicColor:
// 3-bit or 4-bit ANSI foreground
// "4<n>" or "10<n>" where n is the color number from 0 to 7
switch c {
case Black:
return attrBlackBackgroundColor
return blackBackgroundColorAttr
case Red:
return attrRedBackgroundColor
return redBackgroundColorAttr
case Green:
return attrGreenBackgroundColor
return greenBackgroundColorAttr
case Yellow:
return attrYellowBackgroundColor
return yellowBackgroundColorAttr
case Blue:
return attrBlueBackgroundColor
return blueBackgroundColorAttr
case Magenta:
return attrMagentaBackgroundColor
return magentaBackgroundColorAttr
case Cyan:
return attrCyanBackgroundColor
return cyanBackgroundColorAttr
case White:
return attrWhiteBackgroundColor
return whiteBackgroundColorAttr
case BrightBlack:
return attrBrightBlackBackgroundColor
return brightBlackBackgroundColorAttr
case BrightRed:
return attrBrightRedBackgroundColor
return brightRedBackgroundColorAttr
case BrightGreen:
return attrBrightGreenBackgroundColor
return brightGreenBackgroundColorAttr
case BrightYellow:
return attrBrightYellowBackgroundColor
return brightYellowBackgroundColorAttr
case BrightBlue:
return attrBrightBlueBackgroundColor
return brightBlueBackgroundColorAttr
case BrightMagenta:
return attrBrightMagentaBackgroundColor
return brightMagentaBackgroundColorAttr
case BrightCyan:
return attrBrightCyanBackgroundColor
return brightCyanBackgroundColorAttr
case BrightWhite:
return attrBrightWhiteBackgroundColor
return brightWhiteBackgroundColorAttr
}
case ExtendedColor:
// 256-color ANSI foreground
@ -616,7 +472,7 @@ func backgroundColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return attrDefaultBackgroundColor
return defaultBackgroundColorAttr
}
// underlineColorString returns the style SGR attribute for the given underline
@ -624,8 +480,6 @@ func backgroundColorString(c Color) string {
// See: https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters
func underlineColorString(c Color) string {
switch c := c.(type) {
case nil:
return attrDefaultUnderlineColor
// NOTE: we can't use 3-bit and 4-bit ANSI color codes with underline
// color, use 256-color instead.
//
@ -644,7 +498,7 @@ func underlineColorString(c Color) string {
strconv.FormatUint(uint64(shift(g)), 10) + ";" +
strconv.FormatUint(uint64(shift(b)), 10)
}
return attrDefaultUnderlineColor
return defaultUnderlineColorAttr
}
// ReadStyleColor decodes a color from a slice of parameters. It returns the
@ -672,7 +526,7 @@ func underlineColorString(c Color) string {
// 2. Support ignoring and omitting the color space id (second parameter) with respect to RGB colors
// 3. Support ignoring and omitting the 6th parameter with respect to RGB and CMY colors
// 4. Support reading RGBA colors
func ReadStyleColor(params Params, co *color.Color) int {
func ReadStyleColor(params Params, co *color.Color) (n int) {
if len(params) < 2 { // Need at least SGR type and color type
return 0
}
@ -681,7 +535,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
s := params[0]
p := params[1]
colorType := p.Param(0)
n := 2
n = 2
paramsfn := func() (p1, p2, p3, p4 int) {
// Where should we start reading the color?
@ -740,7 +594,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
B: uint8(b), //nolint:gosec
A: 0xff,
}
return n
return //nolint:nakedret
case 3: // CMY direct color
if len(params) < 5 {
@ -758,7 +612,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
Y: uint8(y), //nolint:gosec
K: 0,
}
return n
return //nolint:nakedret
case 4: // CMYK direct color
if len(params) < 6 {
@ -776,7 +630,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
Y: uint8(y), //nolint:gosec
K: uint8(k), //nolint:gosec
}
return n
return //nolint:nakedret
case 5: // indexed color
if len(params) < 3 {
@ -811,7 +665,7 @@ func ReadStyleColor(params Params, co *color.Color) int {
B: uint8(b), //nolint:gosec
A: uint8(a), //nolint:gosec
}
return n
return //nolint:nakedret
default:
return 0

View File

@ -1,11 +1,11 @@
package ansi
import (
"strings"
"bytes"
"github.com/charmbracelet/x/ansi/parser"
"github.com/clipperhouse/displaywidth"
"github.com/clipperhouse/uax29/v2/graphemes"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// Cut the string, without adding any prefix or tail strings. This function is
@ -74,11 +74,12 @@ func truncate(m Method, s string, length int, tail string) string {
return ""
}
var cluster string
var buf strings.Builder
var cluster []byte
var buf bytes.Buffer
curWidth := 0
ignoring := false
pstate := parser.GroundState // initial state
b := []byte(s)
i := 0
// Here we iterate over the bytes of the string and collect printable
@ -87,12 +88,16 @@ func truncate(m Method, s string, length int, tail string) string {
//
// Once we reach the given length, we start ignoring characters and only
// collect ANSI escape codes until we reach the end of string.
for i < len(s) {
state, action := parser.Table.Transition(pstate, s[i])
for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State {
// This action happens when we transition to the Utf8State.
var width int
cluster, width = FirstGraphemeCluster(s[i:], m)
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
// increment the index by the length of the cluster
i += len(cluster)
curWidth += width
@ -113,7 +118,7 @@ func truncate(m Method, s string, length int, tail string) string {
continue
}
buf.WriteString(cluster)
buf.Write(cluster)
// Done collecting, now we're back in the ground state.
pstate = parser.GroundState
@ -147,7 +152,7 @@ func truncate(m Method, s string, length int, tail string) string {
}
fallthrough
default:
buf.WriteByte(s[i])
buf.WriteByte(b[i])
i++
}
@ -188,23 +193,27 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
return s
}
var cluster string
var buf strings.Builder
var cluster []byte
var buf bytes.Buffer
curWidth := 0
ignoring := true
pstate := parser.GroundState
b := []byte(s)
i := 0
for i < len(s) {
for i < len(b) {
if !ignoring {
buf.WriteString(s[i:])
buf.Write(b[i:])
break
}
state, action := parser.Table.Transition(pstate, s[i])
state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State {
var width int
cluster, width = FirstGraphemeCluster(s[i:], m)
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster)
curWidth += width
@ -215,7 +224,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
}
if curWidth > n {
buf.WriteString(cluster)
buf.Write(cluster)
}
if ignoring {
@ -250,7 +259,7 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
}
fallthrough
default:
buf.WriteByte(s[i])
buf.WriteByte(b[i])
i++
}
@ -269,22 +278,22 @@ func truncateLeft(m Method, s string, n int, prefix string) string {
// You can use this with [Truncate], [TruncateLeft], and [Cut].
func ByteToGraphemeRange(str string, byteStart, byteStop int) (charStart, charStop int) {
bytePos, charPos := 0, 0
gr := graphemes.FromString(str)
gr := uniseg.NewGraphemes(str)
for byteStart > bytePos {
if !gr.Next() {
break
}
bytePos += len(gr.Value())
charPos += max(1, displaywidth.String(gr.Value()))
bytePos += len(gr.Str())
charPos += max(1, gr.Width())
}
charStart = charPos
for byteStop > bytePos {
if !gr.Next() {
break
}
bytePos += len(gr.Value())
charPos += max(1, displaywidth.String(gr.Value()))
bytePos += len(gr.Str())
charPos += max(1, gr.Width())
}
charStop = charPos
return charStart, charStop
return
}

View File

@ -1,17 +0,0 @@
package ansi
import (
"fmt"
"strings"
)
// URxvtExt returns an escape sequence for calling a URxvt perl extension with
// the given name and parameters.
//
// OSC 777 ; extension_name ; param1 ; param2 ; ... ST
// OSC 777 ; extension_name ; param1 ; param2 ; ... BEL
//
// See: https://man.archlinux.org/man/extra/rxvt-unicode/urxvt.7.en#XTerm_Operating_System_Commands
func URxvtExt(extension string, params ...string) string {
return fmt.Sprintf("\x1b]777;%s;%s\x07", extension, strings.Join(params, ";"))
}

View File

@ -4,6 +4,8 @@ import (
"bytes"
"github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// Strip removes ANSI escape codes from a string.
@ -81,16 +83,20 @@ func stringWidth(m Method, s string) int {
}
var (
pstate = parser.GroundState // initial state
width int
pstate = parser.GroundState // initial state
cluster string
width int
)
for i := 0; i < len(s); i++ {
state, action := parser.Table.Transition(pstate, s[i])
if state == parser.Utf8State {
cluster, w := FirstGraphemeCluster(s[i:], m)
var w int
cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
if m == WcWidth {
w = runewidth.StringWidth(cluster)
}
width += w
i += len(cluster) - 1
pstate = parser.GroundState
continue

View File

@ -2,11 +2,12 @@ package ansi
import (
"bytes"
"strings"
"unicode"
"unicode/utf8"
"github.com/charmbracelet/x/ansi/parser"
"github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
)
// nbsp is a non-breaking space.
@ -54,9 +55,12 @@ func hardwrap(m Method, s string, limit int, preserveSpace bool) string {
i := 0
for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State {
if state == parser.Utf8State { //nolint:nestif
var width int
cluster, width = FirstGraphemeCluster(b[i:], m)
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster)
if curWidth+width > limit {
@ -188,7 +192,10 @@ func wordwrap(m Method, s string, limit int, breakpoints string) string {
state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State { //nolint:nestif
var width int
cluster, width = FirstGraphemeCluster(b[i:], m)
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster)
r, _ := utf8.DecodeRune(cluster)
@ -296,7 +303,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
}
var (
cluster string
cluster []byte
buf bytes.Buffer
word bytes.Buffer
space bytes.Buffer
@ -304,12 +311,10 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
curWidth int // written width of the line
wordLen int // word buffer len without ANSI escape codes
pstate = parser.GroundState // initial state
b = []byte(s)
)
addSpace := func() {
if spaceWidth == 0 && space.Len() == 0 {
return
}
curWidth += spaceWidth
buf.Write(space.Bytes())
space.Reset()
@ -336,27 +341,30 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
}
i := 0
for i < len(s) {
state, action := parser.Table.Transition(pstate, s[i])
for i < len(b) {
state, action := parser.Table.Transition(pstate, b[i])
if state == parser.Utf8State { //nolint:nestif
var width int
cluster, width = FirstGraphemeCluster(s[i:], m)
cluster, _, width, _ = uniseg.FirstGraphemeCluster(b[i:], -1)
if m == WcWidth {
width = runewidth.StringWidth(string(cluster))
}
i += len(cluster)
r, _ := utf8.DecodeRuneInString(cluster)
r, _ := utf8.DecodeRune(cluster)
switch {
case r != utf8.RuneError && unicode.IsSpace(r) && r != nbsp: // nbsp is a non-breaking space
addWord()
space.WriteRune(r)
spaceWidth += width
case strings.ContainsAny(cluster, breakpoints):
case bytes.ContainsAny(cluster, breakpoints):
addSpace()
if curWidth+wordLen+width > limit {
word.WriteString(cluster)
word.Write(cluster)
wordLen += width
} else {
addWord()
buf.WriteString(cluster)
buf.Write(cluster)
curWidth += width
}
default:
@ -365,17 +373,12 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
addWord()
}
word.WriteString(cluster)
word.Write(cluster)
wordLen += width
if curWidth+wordLen+spaceWidth > limit {
addNewline()
}
if wordLen == limit {
// Hardwrap the word if it's too long
addWord()
}
}
pstate = parser.GroundState
@ -384,7 +387,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
switch action {
case parser.PrintAction, parser.ExecuteAction:
switch r := rune(s[i]); {
switch r := rune(b[i]); {
case r == '\n':
if wordLen == 0 {
if curWidth+spaceWidth > limit {
@ -421,7 +424,6 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
if curWidth == limit {
addNewline()
}
word.WriteRune(r)
wordLen++
@ -436,7 +438,7 @@ func wrap(m Method, s string, limit int, breakpoints string) string {
}
default:
word.WriteByte(s[i])
word.WriteByte(b[i])
}
// We manage the UTF8 state separately manually above.

View File

@ -1,4 +1,3 @@
// Package cellbuf provides terminal cell buffer functionality.
package cellbuf
import (
@ -25,7 +24,7 @@ func NewCell(r rune, comb ...rune) (c *Cell) {
}
c.Comb = comb
c.Width = runewidth.StringWidth(string(append([]rune{r}, comb...)))
return c
return
}
// NewCellString returns a new cell with the given string content. This is a
@ -47,7 +46,7 @@ func NewCellString(s string) (c *Cell) {
c.Comb = append(c.Comb, r)
}
}
return c
return
}
// NewGraphemeCell returns a new cell. This is a convenience function that
@ -72,7 +71,7 @@ func newGraphemeCell(s string, w int) (c *Cell) {
c.Comb = append(c.Comb, r)
}
}
return c
return
}
// Line represents a line in the terminal.
@ -105,7 +104,7 @@ func (l Line) String() (s string) {
}
}
s = strings.TrimRight(s, " ")
return s
return
}
// At returns the cell at the given x position.
@ -151,7 +150,7 @@ func (l Line) set(x int, c *Cell, clone bool) bool {
for j := 1; j < maxCellWidth && x-j >= 0; j++ {
wide := l.At(x - j)
if wide != nil && wide.Width > 1 && j < wide.Width {
for k := range wide.Width {
for k := 0; k < wide.Width; k++ {
l[x-j+k] = wide.Clone().Blank()
}
break
@ -207,7 +206,7 @@ func (b *Buffer) String() (s string) {
s += "\r\n"
}
}
return s
return
}
// Line returns a pointer to the line at the given y position.
@ -297,7 +296,7 @@ func (b *Buffer) FillRect(c *Cell, rect Rectangle) {
}
for y := rect.Min.Y; y < rect.Max.Y; y++ {
for x := rect.Min.X; x < rect.Max.X; x += cellWidth {
b.setCell(x, y, c, false)
b.setCell(x, y, c, false) //nolint:errcheck
}
}
}

View File

@ -96,7 +96,7 @@ func (c *Cell) Clear() bool {
func (c *Cell) Clone() (n *Cell) {
n = new(Cell)
*n = *c
return n
return
}
// Blank makes the cell a blank cell by setting the rune to a space, comb to
@ -164,12 +164,12 @@ type UnderlineStyle = ansi.UnderlineStyle
// These are the available underline styles.
const (
NoUnderline = ansi.UnderlineStyleNone
SingleUnderline = ansi.UnderlineStyleSingle
DoubleUnderline = ansi.UnderlineStyleDouble
CurlyUnderline = ansi.UnderlineStyleCurly
DottedUnderline = ansi.UnderlineStyleDotted
DashedUnderline = ansi.UnderlineStyleDashed
NoUnderline = ansi.NoUnderlineStyle
SingleUnderline = ansi.SingleUnderlineStyle
DoubleUnderline = ansi.DoubleUnderlineStyle
CurlyUnderline = ansi.CurlyUnderlineStyle
DottedUnderline = ansi.DottedUnderlineStyle
DashedUnderline = ansi.DashedUnderlineStyle
)
// Style represents the Style of a cell.
@ -189,7 +189,7 @@ func (s Style) Sequence() string {
var b ansi.Style
if s.Attrs != 0 { //nolint:nestif
if s.Attrs != 0 {
if s.Attrs&BoldAttr != 0 {
b = b.Bold()
}
@ -197,31 +197,36 @@ func (s Style) Sequence() string {
b = b.Faint()
}
if s.Attrs&ItalicAttr != 0 {
b = b.Italic(true)
b = b.Italic()
}
if s.Attrs&SlowBlinkAttr != 0 {
b = b.Blink(true)
b = b.SlowBlink()
}
if s.Attrs&RapidBlinkAttr != 0 {
b = b.RapidBlink(true)
b = b.RapidBlink()
}
if s.Attrs&ReverseAttr != 0 {
b = b.Reverse(true)
b = b.Reverse()
}
if s.Attrs&ConcealAttr != 0 {
b = b.Conceal(true)
b = b.Conceal()
}
if s.Attrs&StrikethroughAttr != 0 {
b = b.Strikethrough(true)
b = b.Strikethrough()
}
}
if s.UlStyle != NoUnderline {
switch u := s.UlStyle; u {
case NoUnderline:
b = b.Underline(false)
default:
b = b.Underline(true)
b = b.UnderlineStyle(u)
switch s.UlStyle {
case SingleUnderline:
b = b.Underline()
case DoubleUnderline:
b = b.DoubleUnderline()
case CurlyUnderline:
b = b.CurlyUnderline()
case DottedUnderline:
b = b.DottedUnderline()
case DashedUnderline:
b = b.DashedUnderline()
}
}
if s.Fg != nil {
@ -263,48 +268,64 @@ func (s Style) DiffSequence(o Style) string {
isNormal bool
)
if s.Attrs != o.Attrs { //nolint:nestif
if s.Attrs != o.Attrs {
if s.Attrs&BoldAttr != o.Attrs&BoldAttr {
if s.Attrs&BoldAttr != 0 {
b = b.Bold()
} else if !isNormal {
isNormal = true
b = b.Normal()
b = b.NormalIntensity()
}
}
if s.Attrs&FaintAttr != o.Attrs&FaintAttr {
if s.Attrs&FaintAttr != 0 {
b = b.Faint()
} else if !isNormal {
b = b.Normal()
b = b.NormalIntensity()
}
}
if s.Attrs&ItalicAttr != o.Attrs&ItalicAttr {
b = b.Italic(s.Attrs&ItalicAttr != 0)
if s.Attrs&ItalicAttr != 0 {
b = b.Italic()
} else {
b = b.NoItalic()
}
}
if s.Attrs&SlowBlinkAttr != o.Attrs&SlowBlinkAttr {
if s.Attrs&SlowBlinkAttr != 0 {
b = b.Blink(true)
b = b.SlowBlink()
} else if !noBlink {
noBlink = true
b = b.Blink(false)
b = b.NoBlink()
}
}
if s.Attrs&RapidBlinkAttr != o.Attrs&RapidBlinkAttr {
if s.Attrs&RapidBlinkAttr != 0 {
b = b.RapidBlink(true)
b = b.RapidBlink()
} else if !noBlink {
b = b.Blink(false)
b = b.NoBlink()
}
}
if s.Attrs&ReverseAttr != o.Attrs&ReverseAttr {
b = b.Reverse(s.Attrs&ReverseAttr != 0)
if s.Attrs&ReverseAttr != 0 {
b = b.Reverse()
} else {
b = b.NoReverse()
}
}
if s.Attrs&ConcealAttr != o.Attrs&ConcealAttr {
b = b.Conceal(s.Attrs&ConcealAttr != 0)
if s.Attrs&ConcealAttr != 0 {
b = b.Conceal()
} else {
b = b.NoConceal()
}
}
if s.Attrs&StrikethroughAttr != o.Attrs&StrikethroughAttr {
b = b.Strikethrough(s.Attrs&StrikethroughAttr != 0)
if s.Attrs&StrikethroughAttr != 0 {
b = b.Strikethrough()
} else {
b = b.NoStrikethrough()
}
}
}

View File

@ -12,7 +12,7 @@ func Pos(x, y int) Position {
return image.Pt(x, y)
}
// Rectangle represents a rectangle.
// Rectange represents a rectangle.
type Rectangle = image.Rectangle
// Rect is a shorthand for Rectangle.

View File

@ -75,7 +75,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
)
blank := s.clearBlank()
if n > 0 { //nolint:nestif
if n > 0 {
// Scroll up (forward)
v = s.scrollUp(n, top, bot, 0, maxY, blank)
if !v {
@ -99,7 +99,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
s.move(0, bot-n+1)
s.clearToBottom(nil)
} else {
for i := range n {
for i := 0; i < n; i++ {
s.move(0, bot-i)
s.clearToEnd(nil, false)
}
@ -124,7 +124,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
// Clear newly shifted-in lines.
if v &&
(nonDestScrollRegion || (memoryBelow && top == 0)) {
for i := range -n {
for i := 0; i < -n; i++ {
s.move(0, top+i)
s.clearToEnd(nil, false)
}
@ -133,7 +133,7 @@ func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { //nolint:unparam
}
if !v {
return v
return
}
s.scrollBuffer(s.curbuf, n, top, bot, blank)
@ -193,7 +193,7 @@ func (s *Screen) touchLine(width, height, y, n int, changed bool) {
// scrollUp scrolls the screen up by n lines.
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
if n == 1 && top == minY && bot == maxY { //nolint:nestif
if n == 1 && top == minY && bot == maxY {
s.move(0, bot)
s.updatePen(blank)
s.buf.WriteByte('\n')
@ -202,14 +202,13 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
s.updatePen(blank)
s.buf.WriteString(ansi.DeleteLine(1))
} else if top == minY && bot == maxY {
supportsSU := s.caps.Contains(capSU)
if supportsSU {
if s.xtermLike {
s.move(0, bot)
} else {
s.move(0, top)
}
s.updatePen(blank)
if supportsSU {
if s.xtermLike {
s.buf.WriteString(ansi.ScrollUp(n))
} else {
s.buf.WriteString(strings.Repeat("\n", n))
@ -226,7 +225,7 @@ func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool {
// scrollDown scrolls the screen down by n lines.
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
if n == 1 && top == minY && bot == maxY { //nolint:nestif
if n == 1 && top == minY && bot == maxY {
s.move(0, top)
s.updatePen(blank)
s.buf.WriteString(ansi.ReverseIndex)
@ -237,7 +236,7 @@ func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool {
} else if top == minY && bot == maxY {
s.move(0, top)
s.updatePen(blank)
if s.caps.Contains(capSD) {
if s.xtermLike {
s.buf.WriteString(ansi.ScrollDown(n))
} else {
s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n))

View File

@ -15,7 +15,7 @@ func hash(l Line) (h uint64) {
}
h += (h << 5) + uint64(r)
}
return h
return
}
// hashmap represents a single [Line] hash.
@ -33,7 +33,7 @@ func (s *Screen) updateHashmap() {
height := s.newbuf.Height()
if len(s.oldhash) >= height && len(s.newhash) >= height {
// rehash changed lines
for i := range height {
for i := 0; i < height; i++ {
_, ok := s.touch[i]
if ok {
s.oldhash[i] = hash(s.curbuf.Line(i))
@ -48,14 +48,14 @@ func (s *Screen) updateHashmap() {
if len(s.newhash) != height {
s.newhash = make([]uint64, height)
}
for i := range height {
for i := 0; i < height; i++ {
s.oldhash[i] = hash(s.curbuf.Line(i))
s.newhash[i] = hash(s.newbuf.Line(i))
}
}
s.hashtab = make([]hashmap, height*2)
for i := range height {
for i := 0; i < height; i++ {
hashval := s.oldhash[i]
// Find matching hash or empty slot
@ -71,7 +71,7 @@ func (s *Screen) updateHashmap() {
s.hashtab[idx].oldcount++
s.hashtab[idx].oldindex = i
}
for i := range height {
for i := 0; i < height; i++ {
hashval := s.newhash[i]
// Find matching hash or empty slot
@ -130,7 +130,7 @@ func (s *Screen) updateHashmap() {
s.growHunks()
}
// scrollOldhash.
// scrollOldhash
func (s *Screen) scrollOldhash(n, top, bot int) {
if len(s.oldhash) == 0 {
return
@ -287,7 +287,7 @@ func (s *Screen) updateCost(from, to Line) (cost int) {
cost++
}
}
return cost
return
}
func (s *Screen) updateCostBlank(to Line) (cost int) {
@ -297,5 +297,5 @@ func (s *Screen) updateCostBlank(to Line) (cost int) {
cost++
}
}
return cost
return
}

View File

@ -4,7 +4,7 @@ import (
"github.com/charmbracelet/colorprofile"
)
// ConvertLink converts a hyperlink to respect the given color profile.
// Convert converts a hyperlink to respect the given color profile.
func ConvertLink(h Link, p colorprofile.Profile) Link {
if p == colorprofile.NoTTY {
return Link{}

View File

@ -1,92 +0,0 @@
package cellbuf
import (
"io"
"github.com/charmbracelet/x/ansi"
)
// PenWriter is a writer that writes to a buffer and keeps track of the current
// pen style and link state for the purpose of wrapping with newlines.
type PenWriter struct {
w io.Writer
p *ansi.Parser
style Style
link Link
}
// NewPenWriter returns a new PenWriter.
func NewPenWriter(w io.Writer) *PenWriter {
pw := &PenWriter{w: w}
pw.p = ansi.GetParser()
handleCsi := func(cmd ansi.Cmd, params ansi.Params) {
if cmd == 'm' {
ReadStyle(params, &pw.style)
}
}
handleOsc := func(cmd int, data []byte) {
if cmd == 8 {
ReadLink(data, &pw.link)
}
}
pw.p.SetHandler(ansi.Handler{
HandleCsi: handleCsi,
HandleOsc: handleOsc,
})
return pw
}
// Style returns the current pen style.
func (w *PenWriter) Style() Style {
return w.style
}
// Link returns the current pen link.
func (w *PenWriter) Link() Link {
return w.link
}
// Write writes to the buffer.
func (w *PenWriter) Write(p []byte) (int, error) {
for i := range p {
b := p[i]
w.p.Advance(b)
if b == '\n' {
if !w.style.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetStyle))
}
if !w.link.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
}
}
_, _ = w.w.Write([]byte{b})
if b == '\n' {
if !w.link.Empty() {
_, _ = w.w.Write([]byte(ansi.SetHyperlink(w.link.URL, w.link.Params)))
}
if !w.style.Empty() {
_, _ = w.w.Write([]byte(w.style.Sequence()))
}
}
}
return len(p), nil
}
// Close closes the writer, resets the style and link if necessary, and releases
// its parser. Calling it is performance critical, but forgetting it does not
// cause safety issues or leaks.
func (w *PenWriter) Close() error {
if !w.style.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetStyle))
}
if !w.link.Empty() {
_, _ = w.w.Write([]byte(ansi.ResetHyperlink()))
}
if w.p != nil {
ansi.PutParser(w.p)
w.p = nil
}
return nil
}

View File

@ -39,9 +39,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
var seq strings.Builder
width, height := s.newbuf.Width(), s.newbuf.Height()
if ty != fy { //nolint:nestif
if ty != fy {
var yseq string
if s.caps.Contains(capVPA) && !s.opts.RelativeCursor {
if s.xtermLike && !s.opts.RelativeCursor {
yseq = ansi.VerticalPositionAbsolute(ty + 1)
}
@ -54,13 +54,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
}
shouldScroll := !s.opts.AltScreen && fy+n >= s.scrollHeight
if lf := strings.Repeat("\n", n); shouldScroll || (fy+n < height && len(lf) < len(yseq)) {
//nolint:godox
// TODO: Ensure we're not unintentionally scrolling the screen down.
yseq = lf
s.scrollHeight = max(s.scrollHeight, fy+n)
if s.opts.MapNL {
fx = 0
}
}
} else if ty < fy {
n := fy - ty
@ -68,7 +64,6 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
yseq = cuu
}
if n == 1 && fy-1 > 0 {
//nolint:godox
// TODO: Ensure we're not unintentionally scrolling the screen up.
yseq = ansi.ReverseIndex
}
@ -77,9 +72,9 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
seq.WriteString(yseq)
}
if tx != fx { //nolint:nestif
if tx != fx {
var xseq string
if s.caps.Contains(capHPA) && !s.opts.RelativeCursor {
if s.xtermLike && !s.opts.RelativeCursor {
xseq = ansi.HorizontalPositionAbsolute(tx + 1)
}
@ -98,8 +93,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
if tabs > 0 {
cht := ansi.CursorHorizontalForwardTab(tabs)
tab := strings.Repeat("\t", tabs)
if false && s.caps.Contains(capCHT) && len(cht) < len(tab) {
//nolint:godox
if false && s.xtermLike && len(cht) < len(tab) {
// TODO: The linux console and some terminals such as
// Alacritty don't support [ansi.CHT]. Enable this when
// we have a way to detect this, or after 5 years when
@ -150,7 +144,7 @@ func relativeCursorMove(s *Screen, fx, fy, tx, ty int, overwrite, useTabs, useBa
}
} else if tx < fx {
n := fx - tx
if useTabs && s.caps.Contains(capCBT) {
if useTabs && s.xtermLike {
// VT100 does not support backward tabs [ansi.CBT].
col := fx
@ -196,7 +190,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
// Method #0: Use [ansi.CUP] if the distance is long.
seq = ansi.CursorPosition(x+1, y+1)
if fx == -1 || fy == -1 || notLocal(s.newbuf.Width(), fx, fy, x, y) {
return seq
return
}
}
@ -240,7 +234,7 @@ func moveCursor(s *Screen, x, y int, overwrite bool) (seq string) {
}
}
return seq
return
}
// moveCursor moves the cursor to the specified position.
@ -248,10 +242,10 @@ func (s *Screen) moveCursor(x, y int, overwrite bool) {
if !s.opts.AltScreen && s.cur.X == -1 && s.cur.Y == -1 {
// First cursor movement in inline mode, move the cursor to the first
// column before moving to the target position.
s.buf.WriteByte('\r')
s.buf.WriteByte('\r') //nolint:errcheck
s.cur.X, s.cur.Y = 0, 0
}
s.buf.WriteString(moveCursor(s, x, y, overwrite))
s.buf.WriteString(moveCursor(s, x, y, overwrite)) //nolint:errcheck
s.cur.X, s.cur.Y = x, y
}
@ -280,11 +274,10 @@ func (s *Screen) move(x, y int) {
// Reset wrap around (phantom cursor) state
if s.atPhantom {
s.cur.X = 0
s.buf.WriteByte('\r')
s.atPhantom = false // reset phantom cell state
s.buf.WriteByte('\r') //nolint:errcheck
s.atPhantom = false // reset phantom cell state
}
//nolint:godox
// TODO: Investigate if we need to handle this case and/or if we need the
// following code.
//
@ -298,7 +291,7 @@ func (s *Screen) move(x, y int) {
//
// if l > 0 {
// s.cur.X = 0
// s.buf.WriteString("\r" + strings.Repeat("\n", l))
// s.buf.WriteString("\r" + strings.Repeat("\n", l)) //nolint:errcheck
// }
// }
@ -346,10 +339,6 @@ type ScreenOptions struct {
HardTabs bool
// Backspace is whether to use backspace characters to move the cursor.
Backspace bool
// MapNL whether we have ONLCR mapping enabled. When we set the terminal to
// raw mode, the ONLCR mode gets disabled. ONLCR maps any newline/linefeed
// (`\n`) character to carriage return + line feed (`\r\n`).
MapNL bool
}
// lineData represents the metadata for a line.
@ -376,13 +365,13 @@ type Screen struct {
opts ScreenOptions
mu sync.Mutex
method ansi.Method
scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
altScreenMode bool // whether alternate screen mode is enabled
cursorHidden bool // whether text cursor mode is enabled
clear bool // whether to force clear the screen
caps capabilities // terminal control sequence capabilities
queuedText bool // whether we have queued non-zero width text queued up
atPhantom bool // whether the cursor is out of bounds and at a phantom cell
scrollHeight int // keeps track of how many lines we've scrolled down (inline mode)
altScreenMode bool // whether alternate screen mode is enabled
cursorHidden bool // whether text cursor mode is enabled
clear bool // whether to force clear the screen
xtermLike bool // whether to use xterm-like optimizations, otherwise, it uses vt100 only
queuedText bool // whether we have queued non-zero width text queued up
atPhantom bool // whether the cursor is out of bounds and at a phantom cell
}
// SetMethod sets the method used to calculate the width of cells.
@ -502,77 +491,36 @@ func (s *Screen) FillRect(cell *Cell, r Rectangle) bool {
return true
}
// capabilities represents a mask of supported ANSI escape sequences.
type capabilities uint
const (
// Vertical Position Absolute [ansi.VPA].
capVPA capabilities = 1 << iota
// Horizontal Position Absolute [ansi.HPA].
capHPA
// Cursor Horizontal Tab [ansi.CHT].
capCHT
// Cursor Backward Tab [ansi.CBT].
capCBT
// Repeat Previous Character [ansi.REP].
capREP
// Erase Character [ansi.ECH].
capECH
// Insert Character [ansi.ICH].
capICH
// Scroll Down [ansi.SD].
capSD
// Scroll Up [ansi.SU].
capSU
noCaps capabilities = 0
allCaps = capVPA | capHPA | capCHT | capCBT | capREP | capECH | capICH |
capSD | capSU
)
// Contains returns whether the capabilities contains the given capability.
func (v capabilities) Contains(c capabilities) bool {
return v&c == c
}
// xtermCaps returns whether the terminal is xterm-like. This means that the
// isXtermLike returns whether the terminal is xterm-like. This means that the
// terminal supports ECMA-48 and ANSI X3.64 escape sequences.
// xtermCaps returns a list of control sequence capabilities for the given
// terminal type. This only supports a subset of sequences that can
// be different among terminals.
// NOTE: A hybrid approach would be to support Terminfo databases for a full
// set of capabilities.
func xtermCaps(termtype string) (v capabilities) {
// TODO: Should this be a lookup table into each $TERM terminfo database? Like
// we could keep a map of ANSI escape sequence to terminfo capability name and
// check if the database supports the escape sequence. Instead of keeping a
// list of terminal names here.
func isXtermLike(termtype string) (v bool) {
parts := strings.Split(termtype, "-")
if len(parts) == 0 {
return v
return
}
switch parts[0] {
case
"alacritty",
"contour",
"foot",
"ghostty",
"kitty",
"linux",
"rio",
"screen",
"st",
"tmux",
"wezterm",
"xterm":
v = allCaps
case "alacritty":
v = allCaps
v &^= capCHT // NOTE: alacritty added support for [ansi.CHT] in 2024-12-28 #62d5b13.
case "screen":
// See https://www.gnu.org/software/screen/manual/screen.html#Control-Sequences-1
v = allCaps
v &^= capREP
case "linux":
// See https://man7.org/linux/man-pages/man4/console_codes.4.html
v = capVPA | capHPA | capECH | capICH
v = true
}
return v
return
}
// NewScreen creates a new Screen.
@ -600,14 +548,14 @@ func NewScreen(w io.Writer, width, height int, opts *ScreenOptions) (s *Screen)
}
s.buf = new(bytes.Buffer)
s.caps = xtermCaps(s.opts.Term)
s.xtermLike = isXtermLike(s.opts.Term)
s.curbuf = NewBuffer(width, height)
s.newbuf = NewBuffer(width, height)
s.cur = Cursor{Position: Pos(-1, -1)} // start at -1 to force a move
s.saved = s.cur
s.reset()
return s
return
}
// Width returns the width of the screen.
@ -647,7 +595,7 @@ func (s *Screen) putCell(cell *Cell) {
// wrapCursor wraps the cursor to the next line.
//
//nolint:unused
func (s *Screen) wrapCursor() {
const autoRightMargin = true
if autoRightMargin {
@ -680,9 +628,9 @@ func (s *Screen) putAttrCell(cell *Cell) {
}
s.updatePen(cell)
s.buf.WriteRune(cell.Rune)
s.buf.WriteRune(cell.Rune) //nolint:errcheck
for _, c := range cell.Comb {
s.buf.WriteRune(c)
s.buf.WriteRune(c) //nolint:errcheck
}
s.cur.X += cell.Width
@ -701,12 +649,12 @@ func (s *Screen) putCellLR(cell *Cell) {
// Optimize for the lower right corner cell.
curX := s.cur.X
if cell == nil || !cell.Empty() {
s.buf.WriteString(ansi.ResetModeAutoWrap)
s.buf.WriteString(ansi.ResetAutoWrapMode) //nolint:errcheck
s.putAttrCell(cell)
// Writing to lower-right corner cell should not wrap.
s.atPhantom = false
s.cur.X = curX
s.buf.WriteString(ansi.SetModeAutoWrap)
s.buf.WriteString(ansi.SetAutoWrapMode) //nolint:errcheck
}
}
@ -727,11 +675,11 @@ func (s *Screen) updatePen(cell *Cell) {
if cell.Style.Empty() && len(seq) > len(ansi.ResetStyle) {
seq = ansi.ResetStyle
}
s.buf.WriteString(seq)
s.buf.WriteString(seq) //nolint:errcheck
s.cur.Style = cell.Style
}
if !cell.Link.Equal(&s.cur.Link) {
s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params))
s.buf.WriteString(ansi.SetHyperlink(cell.Link.URL, cell.Link.Params)) //nolint:errcheck
s.cur.Link = cell.Link
}
}
@ -764,9 +712,9 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
ech := ansi.EraseCharacter(count)
cup := ansi.CursorPosition(s.cur.X+count, s.cur.Y)
rep := ansi.RepeatPreviousCharacter(count)
if s.caps.Contains(capECH) && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() { //nolint:nestif
if s.xtermLike && count > len(ech)+len(cup) && cell0 != nil && cell0.Clear() {
s.updatePen(cell0)
s.buf.WriteString(ech)
s.buf.WriteString(ech) //nolint:errcheck
// If this is the last cell, we don't need to move the cursor.
if count < n {
@ -774,7 +722,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
} else {
return true // cursor in the middle
}
} else if s.caps.Contains(capREP) && count > len(rep) &&
} else if s.xtermLike && count > len(rep) &&
(cell0 == nil || (len(cell0.Comb) == 0 && cell0.Rune < 256)) {
// We only support ASCII characters. Most terminals will handle
// non-ASCII characters correctly, but some might not, ahem xterm.
@ -792,13 +740,13 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
s.putCell(cell0)
repCount-- // cell0 is a single width cell ASCII character
s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount))
s.buf.WriteString(ansi.RepeatPreviousCharacter(repCount)) //nolint:errcheck
s.cur.X += repCount
if wrapPossible {
s.putCell(cell0)
}
} else {
for i := range count {
for i := 0; i < count; i++ {
s.putCell(line.At(i))
}
}
@ -807,7 +755,7 @@ func (s *Screen) emitRange(line Line, n int) (eoi bool) {
n -= count
}
return eoi
return
}
// putRange puts a range of cells from the old line to the new line.
@ -817,7 +765,7 @@ func (s *Screen) putRange(oldLine, newLine Line, y, start, end int) (eoi bool) {
inline := min(len(ansi.CursorPosition(start+1, y+1)),
min(len(ansi.HorizontalPositionAbsolute(start+1)),
len(ansi.CursorForward(start+1))))
if (end - start + 1) > inline { //nolint:nestif
if (end - start + 1) > inline {
var j, same int
for j, same = start, 0; j <= end; j++ {
oldCell, newCell := oldLine.At(j), newLine.At(j)
@ -869,9 +817,9 @@ func (s *Screen) clearToEnd(blank *Cell, force bool) { //nolint:unparam
s.updatePen(blank)
count := s.newbuf.Width() - s.cur.X
if s.el0Cost() <= count {
s.buf.WriteString(ansi.EraseLineRight)
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
} else {
for range count {
for i := 0; i < count; i++ {
s.putCell(blank)
}
}
@ -891,13 +839,12 @@ func (s *Screen) clearBlank() *Cell {
// insertCells inserts the count cells pointed by the given line at the current
// cursor position.
func (s *Screen) insertCells(line Line, count int) {
supportsICH := s.caps.Contains(capICH)
if supportsICH {
if s.xtermLike {
// Use [ansi.ICH] as an optimization.
s.buf.WriteString(ansi.InsertCharacter(count))
s.buf.WriteString(ansi.InsertCharacter(count)) //nolint:errcheck
} else {
// Otherwise, use [ansi.IRM] mode.
s.buf.WriteString(ansi.SetModeInsertReplace)
s.buf.WriteString(ansi.SetInsertReplaceMode) //nolint:errcheck
}
for i := 0; count > 0; i++ {
@ -905,8 +852,8 @@ func (s *Screen) insertCells(line Line, count int) {
count--
}
if !supportsICH {
s.buf.WriteString(ansi.ResetModeInsertReplace)
if !s.xtermLike {
s.buf.WriteString(ansi.ResetInsertReplaceMode) //nolint:errcheck
}
}
@ -915,7 +862,7 @@ func (s *Screen) insertCells(line Line, count int) {
// [ansi.EL] 0 i.e. [ansi.EraseLineRight] to clear
// trailing spaces.
func (s *Screen) el0Cost() int {
if s.caps != noCaps {
if s.xtermLike {
return 0
}
return len(ansi.EraseLineRight)
@ -931,7 +878,7 @@ func (s *Screen) transformLine(y int) {
// Find the first changed cell in the line
var lineChanged bool
for i := range s.newbuf.Width() {
for i := 0; i < s.newbuf.Width(); i++ {
if !cellEqual(newLine.At(i), oldLine.At(i)) {
lineChanged = true
break
@ -939,7 +886,7 @@ func (s *Screen) transformLine(y int) {
}
const ceolStandoutGlitch = false
if ceolStandoutGlitch && lineChanged { //nolint:nestif
if ceolStandoutGlitch && lineChanged {
s.move(0, y)
s.clearToEnd(nil, false)
s.putRange(oldLine, newLine, y, 0, s.newbuf.Width()-1)
@ -950,12 +897,12 @@ func (s *Screen) transformLine(y int) {
// [ansi.EraseLineLeft].
if blank == nil || blank.Clear() {
var oFirstCell, nFirstCell int
for oFirstCell = range s.curbuf.Width() {
for oFirstCell = 0; oFirstCell < s.curbuf.Width(); oFirstCell++ {
if !cellEqual(oldLine.At(oFirstCell), blank) {
break
}
}
for nFirstCell = range s.newbuf.Width() {
for nFirstCell = 0; nFirstCell < s.newbuf.Width(); nFirstCell++ {
if !cellEqual(newLine.At(nFirstCell), blank) {
break
}
@ -978,11 +925,11 @@ func (s *Screen) transformLine(y int) {
if nFirstCell >= s.newbuf.Width() {
s.move(0, y)
s.updatePen(blank)
s.buf.WriteString(ansi.EraseLineRight)
s.buf.WriteString(ansi.EraseLineRight) //nolint:errcheck
} else {
s.move(nFirstCell-1, y)
s.updatePen(blank)
s.buf.WriteString(ansi.EraseLineLeft)
s.buf.WriteString(ansi.EraseLineLeft) //nolint:errcheck
}
for firstCell < nFirstCell {
@ -1098,7 +1045,7 @@ func (s *Screen) transformLine(y int) {
s.move(n+1, y)
ichCost := 3 + nLastCell - oLastCell
if s.caps.Contains(capICH) && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
if s.xtermLike && (nLastCell < nLastNonBlank || ichCost > (m-n)) {
s.putRange(oldLine, newLine, y, n+1, m)
} else {
s.insertCells(newLine[n+1:], nLastCell-oLastCell)
@ -1132,7 +1079,7 @@ func (s *Screen) transformLine(y int) {
func (s *Screen) deleteCells(count int) {
// [ansi.DCH] will shift in cells from the right margin so we need to
// ensure that they are the right style.
s.buf.WriteString(ansi.DeleteCharacter(count))
s.buf.WriteString(ansi.DeleteCharacter(count)) //nolint:errcheck
}
// clearToBottom clears the screen from the current cursor position to the end
@ -1144,7 +1091,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
}
s.updatePen(blank)
s.buf.WriteString(ansi.EraseScreenBelow)
s.buf.WriteString(ansi.EraseScreenBelow) //nolint:errcheck
// Clear the rest of the current line
s.curbuf.ClearRect(Rect(col, row, s.curbuf.Width()-col, 1))
// Clear everything below the current line
@ -1157,7 +1104,7 @@ func (s *Screen) clearToBottom(blank *Cell) {
// It returns the top line.
func (s *Screen) clearBottom(total int) (top int) {
if total <= 0 {
return top
return
}
top = total
@ -1165,7 +1112,7 @@ func (s *Screen) clearBottom(total int) (top int) {
blank := s.clearBlank()
canClearWithBlank := blank == nil || blank.Clear()
if canClearWithBlank { //nolint:nestif
if canClearWithBlank {
var row int
for row = total - 1; row >= 0; row-- {
oldLine := s.curbuf.Line(row)
@ -1200,14 +1147,14 @@ func (s *Screen) clearBottom(total int) (top int) {
}
}
return top
return
}
// clearScreen clears the screen and put cursor at home.
func (s *Screen) clearScreen(blank *Cell) {
s.updatePen(blank)
s.buf.WriteString(ansi.CursorHomePosition)
s.buf.WriteString(ansi.EraseEntireScreen)
s.buf.WriteString(ansi.CursorHomePosition) //nolint:errcheck
s.buf.WriteString(ansi.EraseEntireScreen) //nolint:errcheck
s.cur.X, s.cur.Y = 0, 0
s.curbuf.Fill(blank)
}
@ -1232,7 +1179,7 @@ func (s *Screen) clearUpdate() {
s.clearBelow(blank, 0)
}
nonEmpty = s.clearBottom(nonEmpty)
for i := range nonEmpty {
for i := 0; i < nonEmpty; i++ {
s.transformLine(i)
}
}
@ -1247,13 +1194,13 @@ func (s *Screen) Flush() (err error) {
func (s *Screen) flush() (err error) {
// Write the buffer
if s.buf.Len() > 0 {
_, err = s.w.Write(s.buf.Bytes())
_, err = s.w.Write(s.buf.Bytes()) //nolint:errcheck
if err == nil {
s.buf.Reset()
}
}
return err //nolint:wrapcheck
return
}
// Render renders changes of the screen to the internal buffer. Call
@ -1274,7 +1221,6 @@ func (s *Screen) render() {
return
}
//nolint:godox
// TODO: Investigate whether this is necessary. Theoretically, terminals
// can add/remove tab stops and we should be able to handle that. We could
// use [ansi.DECTABSR] to read the tab stops, but that's not implemented in
@ -1289,9 +1235,9 @@ func (s *Screen) render() {
// Do we need alt-screen mode?
if s.opts.AltScreen != s.altScreenMode {
if s.opts.AltScreen {
s.buf.WriteString(ansi.SetModeAltScreenSaveCursor)
s.buf.WriteString(ansi.SetAltScreenSaveCursorMode)
} else {
s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
}
s.altScreenMode = s.opts.AltScreen
}
@ -1306,9 +1252,7 @@ func (s *Screen) render() {
// Do we have queued strings to write above the screen?
if len(s.queueAbove) > 0 {
//nolint:godox
// TODO: Use scrolling region if available.
//nolint:godox
// TODO: Use [Screen.Write] [io.Writer] interface.
// We need to scroll the screen up by the number of lines in the queue.
@ -1346,13 +1290,12 @@ func (s *Screen) render() {
s.clearBelow(nil, s.newbuf.Height()-1)
}
if s.clear { //nolint:nestif
if s.clear {
s.clearUpdate()
s.clear = false
} else if len(s.touch) > 0 {
if s.opts.AltScreen {
// Optimize scrolling for the alternate screen buffer.
//nolint:godox
// TODO: Should we optimize for inline mode as well? If so, we need
// to know the actual cursor position to use [ansi.DECSTBM].
s.scrollOptimize()
@ -1368,7 +1311,7 @@ func (s *Screen) render() {
}
nonEmpty = s.clearBottom(nonEmpty)
for i = range nonEmpty {
for i = 0; i < nonEmpty; i++ {
_, ok := s.touch[i]
if ok {
s.transformLine(i)
@ -1416,7 +1359,7 @@ func (s *Screen) Close() (err error) {
s.move(0, s.newbuf.Height()-1)
if s.altScreenMode {
s.buf.WriteString(ansi.ResetModeAltScreenSaveCursor)
s.buf.WriteString(ansi.ResetAltScreenSaveCursorMode)
s.altScreenMode = false
}
@ -1428,11 +1371,11 @@ func (s *Screen) Close() (err error) {
// Write the buffer
err = s.flush()
if err != nil {
return err
return
}
s.reset()
return err
return
}
// reset resets the screen to its initial state.
@ -1477,9 +1420,9 @@ func (s *Screen) Resize(width, height int) bool {
}
if height > oldh {
s.ClearRect(Rect(0, max(oldh, 0), width, height-oldh))
s.ClearRect(Rect(0, max(oldh-1, 0), width, height-oldh))
} else if height < oldh {
s.ClearRect(Rect(0, max(height, 0), width, oldh-height))
s.ClearRect(Rect(0, max(height-1, 0), width, oldh-height))
}
s.mu.Lock()

View File

@ -4,9 +4,9 @@ import (
"github.com/charmbracelet/colorprofile"
)
// ConvertStyle converts a style to respect the given color profile.
// Convert converts a style to respect the given color profile.
func ConvertStyle(s Style, p colorprofile.Profile) Style {
switch p { //nolint:exhaustive
switch p {
case colorprofile.TrueColor:
return s
case colorprofile.Ascii:

View File

@ -9,6 +9,20 @@ func Height(s string) int {
return strings.Count(s, "\n") + 1
}
func min(a, b int) int { //nolint:predeclared
if a > b {
return b
}
return a
}
func max(a, b int) int { //nolint:predeclared
if a > b {
return a
}
return b
}
func clamp(v, low, high int) int {
if high < low {
low, high = high, low

View File

@ -2,7 +2,6 @@ package cellbuf
import (
"bytes"
"slices"
"unicode"
"unicode/utf8"
@ -21,16 +20,6 @@ const nbsp = '\u00a0'
//
// Note: breakpoints must be a string of 1-cell wide rune characters.
func Wrap(s string, limit int, breakpoints string) string {
//nolint:godox
// TODO: Use [PenWriter] once we get
// https://github.com/charmbracelet/lipgloss/pull/489 out the door and
// released.
// The problem is that [ansi.Wrap] doesn't keep track of style and link
// state, so combining both breaks styled space cells. To fix this, we use
// non-breaking space cells for padding and styled blank cells. And since
// both wrapping methods respect non-breaking spaces, we can use them to
// preserve styled spaces in the output.
if len(s) == 0 {
return ""
}
@ -101,7 +90,7 @@ func Wrap(s string, limit int, breakpoints string) string {
seq, width, n, newState := ansi.DecodeSequence(s, state, p)
switch width {
case 0:
if ansi.Equal(seq, "\t") { //nolint:nestif
if ansi.Equal(seq, "\t") {
addWord()
space.WriteString(seq)
break
@ -187,5 +176,10 @@ func Wrap(s string, limit int, breakpoints string) string {
}
func runeContainsAny[T string | []rune](r rune, s T) bool {
return slices.Contains([]rune(s), r)
for _, c := range []rune(s) {
if c == r {
return true
}
}
return false
}

View File

@ -25,7 +25,7 @@ type CellBuffer interface {
func FillRect(s CellBuffer, c *Cell, rect Rectangle) {
for y := rect.Min.Y; y < rect.Max.Y; y++ {
for x := rect.Min.X; x < rect.Max.X; x++ {
s.SetCell(x, y, c)
s.SetCell(x, y, c) //nolint:errcheck
}
}
}
@ -68,7 +68,7 @@ func SetContent(s CellBuffer, str string) {
func Render(d CellBuffer) string {
var buf bytes.Buffer
height := d.Bounds().Dy()
for y := range height {
for y := 0; y < height; y++ {
_, line := RenderLine(d, y)
buf.WriteString(line)
if y < height-1 {
@ -98,32 +98,32 @@ func RenderLine(d CellBuffer, n int) (w int, line string) {
pendingLine = ""
}
for x := range d.Bounds().Dx() {
if cell := d.Cell(x, n); cell != nil && cell.Width > 0 { //nolint:nestif
for x := 0; x < d.Bounds().Dx(); x++ {
if cell := d.Cell(x, n); cell != nil && cell.Width > 0 {
// Convert the cell's style and link to the given color profile.
cellStyle := cell.Style
cellLink := cell.Link
if cellStyle.Empty() && !pen.Empty() {
writePending()
buf.WriteString(ansi.ResetStyle)
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
pen.Reset()
}
if !cellStyle.Equal(&pen) {
writePending()
seq := cellStyle.DiffSequence(pen)
buf.WriteString(seq)
buf.WriteString(seq) // nolint:errcheck
pen = cellStyle
}
// Write the URL escape sequence
if cellLink != link && link.URL != "" {
writePending()
buf.WriteString(ansi.ResetHyperlink())
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
link.Reset()
}
if cellLink != link {
writePending()
buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params))
buf.WriteString(ansi.SetHyperlink(cellLink.URL, cellLink.Params)) //nolint:errcheck
link = cellLink
}
@ -140,10 +140,10 @@ func RenderLine(d CellBuffer, n int) (w int, line string) {
}
}
if link.URL != "" {
buf.WriteString(ansi.ResetHyperlink())
buf.WriteString(ansi.ResetHyperlink()) //nolint:errcheck
}
if !pen.Empty() {
buf.WriteString(ansi.ResetStyle)
buf.WriteString(ansi.ResetStyle) //nolint:errcheck
}
return w, strings.TrimRight(buf.String(), " ") // Trim trailing spaces
}
@ -201,7 +201,7 @@ func (s *ScreenWriter) SetContentRect(str string, rect Rectangle) {
// string to the width of the screen if it exceeds the width of the screen.
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
// sequences.
func (s *ScreenWriter) Print(str string, v ...any) {
func (s *ScreenWriter) Print(str string, v ...interface{}) {
if len(v) > 0 {
str = fmt.Sprintf(str, v...)
}
@ -214,7 +214,7 @@ func (s *ScreenWriter) Print(str string, v ...any) {
// the width of the screen if it exceeds the width of the screen.
// This will recognize ANSI [ansi.SGR] style and [ansi.SetHyperlink] escape
// sequences.
func (s *ScreenWriter) PrintAt(x, y int, str string, v ...any) {
func (s *ScreenWriter) PrintAt(x, y int, str string, v ...interface{}) {
if len(v) > 0 {
str = fmt.Sprintf(str, v...)
}
@ -299,7 +299,7 @@ func printString[T []byte | string](
// Print the cell to the screen
cell.Style = style
cell.Link = link
s.SetCell(x, y, &cell)
s.SetCell(x, y, &cell) //nolint:errcheck
x += width
}
}
@ -309,7 +309,6 @@ func printString[T []byte | string](
cell.Reset()
default:
// Valid sequences always have a non-zero Cmd.
//nolint:godox
// TODO: Handle cursor movement and other sequences
switch {
case ansi.HasCsiPrefix(seq) && p.Command() == 'm':
@ -334,7 +333,7 @@ func printString[T []byte | string](
// Make sure to set the last cell if it's not empty.
if !cell.Empty() {
s.SetCell(x, y, &cell)
s.SetCell(x, y, &cell) //nolint:errcheck
cell.Reset()
}
}

View File

@ -1,5 +1,3 @@
// Package term provides a platform-independent interfaces for interacting with
// Terminal and TTY devices.
package term
// State contains platform-specific state of a terminal.

View File

@ -1,118 +0,0 @@
package term
import (
"fmt"
"os"
"path/filepath"
"strings"
)
type state struct {
termName string
raw bool
ctl *os.File
}
// termName returns the name of the terminal or os.ErrNotExist if there is no terminal.
func termName(fd uintptr) (string, error) {
ctl, err := os.ReadFile(filepath.Join("/fd", fmt.Sprintf("%dctl", fd)))
if err != nil {
return "", err
}
f := strings.Fields(string(ctl))
if len(f) == 0 {
return "", os.ErrNotExist
}
return f[len(f)-1], nil
}
func isTerminal(fd uintptr) bool {
ctl, err := os.ReadFile(filepath.Join("/fd", fmt.Sprintf("%dctl", fd)))
if err != nil {
return false
}
if strings.Contains(string(ctl), "/dev/cons") {
return true
}
return false
}
func makeRaw(fd uintptr) (*State, error) {
t, err := termName(fd)
if err != nil {
return nil, err
}
ctl, err := os.OpenFile(t, os.O_RDWR, 0)
if err != nil {
return nil, err
}
if _, err := ctl.Write([]byte("rawon")); err != nil {
return nil, err
}
return &State{state: state{termName: t, raw: true, ctl: ctl}}, nil
}
func getState(fd uintptr) (*State, error) {
t, err := termName(fd)
if err != nil {
return nil, err
}
ctl, err := os.OpenFile(t, os.O_RDWR, 0)
if err != nil {
return nil, err
}
return &State{state: state{termName: t, raw: false, ctl: ctl}}, nil
}
func restore(_ uintptr, state *State) error {
if _, err := state.ctl.Write([]byte("rawoff")); err != nil {
return err
}
return nil
}
// getSize returns the size. This will only work if you are running
// under a window manager in Plan 9. Else, the only option
// is to return a reasonable default.
func getSize(fd uintptr) (int, int, error) {
w, h := 80, 40
b, err := os.ReadFile("/dev/wctl")
if err != nil {
return w, h, err
}
f := strings.Fields(string(b))
if len(f) != 4 {
return w, h, fmt.Errorf("%q only has %d of 4 needed fields:%w", f, len(f), os.ErrInvalid)
}
// The contents of wctl, as defined in the driver, are
// 4 12-char fields: upper left x, y; and lower-right x, y
var ulx, uly, lrx, lry int
if n, err := fmt.Sscanf(string(b[:48]), "%d%d%d%d", &ulx, &uly, &lrx, &lry); n != 4 || err != nil {
return w, h, fmt.Errorf("scanning %q:%d of 4 items scanned:%w", string(b[:48]), n, err)
}
w, h = lrx-lrx, lry-uly
return w, h, nil
}
func setState(_ uintptr, state *State) error {
raw := "rawoff"
if state.raw {
raw = "rawon"
}
if _, err := state.ctl.Write([]byte(raw)); err != nil {
return err
}
return nil
}
func readPassword(fd uintptr) ([]byte, error) {
f := os.NewFile(fd, "cons")
var b [128]byte
n, err := f.Read(b[:])
if err != nil {
return nil, err
}
return b[:n], nil
}

View File

@ -19,7 +19,7 @@ func isTerminal(fd uintptr) bool {
func makeRaw(fd uintptr) (*State, error) {
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
if err != nil {
return nil, err //nolint:wrapcheck
return nil, err
}
oldState := State{state{Termios: *termios}}
@ -34,7 +34,7 @@ func makeRaw(fd uintptr) (*State, error) {
termios.Cc[unix.VMIN] = 1
termios.Cc[unix.VTIME] = 0
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios); err != nil {
return nil, err //nolint:wrapcheck
return nil, err
}
return &oldState, nil
@ -45,26 +45,26 @@ func setState(fd uintptr, state *State) error {
if state != nil {
termios = &state.Termios
}
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) //nolint:wrapcheck
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
}
func getState(fd uintptr) (*State, error) {
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
if err != nil {
return nil, err //nolint:wrapcheck
return nil, err
}
return &State{state{Termios: *termios}}, nil
}
func restore(fd uintptr, state *State) error {
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios) //nolint:wrapcheck
return unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &state.Termios)
}
func getSize(fd uintptr) (width, height int, err error) {
ws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
if err != nil {
return 0, 0, err //nolint:wrapcheck
return 0, 0, err
}
return int(ws.Col), int(ws.Row), nil
}
@ -73,13 +73,13 @@ func getSize(fd uintptr) (width, height int, err error) {
type passwordReader int
func (r passwordReader) Read(buf []byte) (int, error) {
return unix.Read(int(r), buf) //nolint:wrapcheck
return unix.Read(int(r), buf)
}
func readPassword(fd uintptr) ([]byte, error) {
termios, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
if err != nil {
return nil, err //nolint:wrapcheck
return nil, err
}
newState := *termios
@ -87,10 +87,10 @@ func readPassword(fd uintptr) ([]byte, error) {
newState.Lflag |= unix.ICANON | unix.ISIG
newState.Iflag |= unix.ICRNL
if err := unix.IoctlSetTermios(int(fd), ioctlWriteTermios, &newState); err != nil {
return nil, err //nolint:wrapcheck
return nil, err
}
defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios) //nolint:errcheck
defer unix.IoctlSetTermios(int(fd), ioctlWriteTermios, termios)
return readPasswordLine(passwordReader(fd))
}

View File

@ -24,7 +24,7 @@ func makeRaw(fd uintptr) (*State, error) {
if err := windows.GetConsoleMode(windows.Handle(fd), &st); err != nil {
return nil, err
}
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT)
raw := st &^ (windows.ENABLE_ECHO_INPUT | windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT)
raw |= windows.ENABLE_VIRTUAL_TERMINAL_INPUT
if err := windows.SetConsoleMode(windows.Handle(fd), raw); err != nil {
return nil, err

View File

@ -41,7 +41,7 @@ func readPasswordLine(reader io.Reader) ([]byte, error) {
if err == io.EOF && len(ret) > 0 {
return ret, nil
}
return ret, err //nolint:wrapcheck
return ret, err
}
}
}

View File

@ -1,2 +0,0 @@
.DS_Store
*.out

View File

@ -1,37 +0,0 @@
The goals and overview of this package can be found in the README.md file,
start by reading that.
The goal of this package is to determine the display (column) width of a
string, UTF-8 bytes, or runes, as would happen in a monospace font, especially
in a terminal.
When troubleshooting, write Go unit tests instead of executing debug scripts.
The tests can return whatever logs or output you need. If those tests are
only for temporary troubleshooting, clean up the tests after the debugging is
done.
(Separate executable debugging scripts are messy, tend to have conflicting
dependencies and are hard to cleanup.)
If you make changes to the trie generation in internal/gen, it can be invoked
by running `go generate` from the top package directory.
## Pull Requests and branches
For PRs (pull requests), you can use the gh CLI tool to retrieve details,
or post comments. Then, compare the current branch with main. Reviewing a PR
and reviewing a branch are about the same, but the PR may add context.
Look for bugs. Think like GitHub Copilot or Cursor BugBot.
Offer to post a brief summary of the review to the PR, via the gh CLI tool.
## Comparisons to go-runewidth
We originally attempted to make this package compatible with go-runewidth.
However, we found that there were too many differences in the handling of
certain characters and properties.
We believe, preliminarily, that our choices are more correct and complete,
by using more complete categories such as Unicode Cf (format) for zero-width
and Mn (Nonspacing_Mark) for combining marks.

View File

@ -1,70 +0,0 @@
# Changelog
## [0.6.1]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.6.0...v0.6.1)
### Changed
- Perf improvements: replaced the ASCII lookup table with a simple
function. A bit more cache-friendly. More inlining.
- Bug fix: single regional indicators are now treated as width 2, since that
is what actual terminals do.
## [0.6.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.5.0...v0.6.0)
### Added
- New `StringGraphemes` and `BytesGraphemes` methods, for iterating over the
widths of grapheme clusters.
### Changed
- Added ASCII fast paths
## [0.5.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.4.1...v0.5.0)
### Added
- Unicode 16 support
- Improved emoji presentation handling per Unicode TR51
### Changed
- Corrected VS15 (U+FE0E) handling: now preserves base character width (no-op) per Unicode TR51
- Performance optimizations: reduced property lookups
### Fixed
- VS15 variation selector now correctly preserves base character width instead of forcing width 1
## [0.4.1]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.4.0...v0.4.1)
### Changed
- Updated uax29 dependency
- Improved flag handling
## [0.4.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.3.1...v0.4.0)
### Added
- Support for variation selectors (VS15, VS16) and regional indicator pairs (flags)
## [0.3.1]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.3.0...v0.3.1)
### Added
- Fuzz testing support
### Changed
- Updated stringish dependency
## [0.3.0]
[Compare](https://github.com/clipperhouse/displaywidth/compare/v0.2.0...v0.3.0)
### Changed
- Dropped compatibility with go-runewidth
- Trie implementation cleanup

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2025 Matt Sherman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,171 +0,0 @@
# displaywidth
A high-performance Go package for measuring the monospace display width of strings, UTF-8 bytes, and runes.
[![Documentation](https://pkg.go.dev/badge/github.com/clipperhouse/displaywidth.svg)](https://pkg.go.dev/github.com/clipperhouse/displaywidth)
[![Test](https://github.com/clipperhouse/displaywidth/actions/workflows/gotest.yml/badge.svg)](https://github.com/clipperhouse/displaywidth/actions/workflows/gotest.yml)
[![Fuzz](https://github.com/clipperhouse/displaywidth/actions/workflows/gofuzz.yml/badge.svg)](https://github.com/clipperhouse/displaywidth/actions/workflows/gofuzz.yml)
## Install
```bash
go get github.com/clipperhouse/displaywidth
```
## Usage
```go
package main
import (
"fmt"
"github.com/clipperhouse/displaywidth"
)
func main() {
width := displaywidth.String("Hello, 世界!")
fmt.Println(width)
width = displaywidth.Bytes([]byte("🌍"))
fmt.Println(width)
width = displaywidth.Rune('🌍')
fmt.Println(width)
}
```
For most purposes, you should use the `String` or `Bytes` methods. They sum
the widths of grapheme clusters in the string or byte slice.
> Note: in your application, iterating over runes to measure width is likely incorrect;
the smallest unit of display is a grapheme, not a rune.
### Iterating over graphemes
If you need the individual graphemes:
```go
import (
"fmt"
"github.com/clipperhouse/displaywidth"
)
func main() {
g := displaywidth.StringGraphemes("Hello, 世界!")
for g.Next() {
width := g.Width()
value := g.Value()
// do something with the width or value
}
}
```
### Options
There is one option, `displaywidth.Options.EastAsianWidth`, which defines
how [East Asian Ambiguous characters](https://www.unicode.org/reports/tr11/#Ambiguous)
are treated.
When `false` (default), East Asian Ambiguous characters are treated as width 1.
When `true`, they are treated as width 2.
You may wish to configure this based on environment variables or locale.
`go-runewidth`, for example, does so
[during package initialization](https://github.com/mattn/go-runewidth/blob/master/runewidth.go#L26C1-L45C2).
`displaywidth` does not do this automatically, we prefer to leave it to you.
You might do something like:
```go
var width displaywidth.Options // zero value is default
func init() {
if os.Getenv("EAST_ASIAN_WIDTH") == "true" {
width = displaywidth.Options{EastAsianWidth: true}
}
// or check locale, or any other logic you want
}
// use it in your logic
func myApp() {
fmt.Println(width.String("Hello, 世界!"))
}
```
## Technical standards and compatibility
This package implements the Unicode East Asian Width standard
([UAX #11](https://www.unicode.org/reports/tr11/tr11-43.html)), and handles
[version selectors](https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)),
and [regional indicator pairs](https://en.wikipedia.org/wiki/Regional_indicator_symbol)
(flags). We implement [Unicode TR51](https://www.unicode.org/reports/tr51/tr51-27.html). We are keeping
an eye on [emerging standards](https://www.jeffquast.com/post/state-of-terminal-emulation-2025/).
`clipperhouse/displaywidth`, `mattn/go-runewidth`, and `rivo/uniseg` will
give the same outputs for most real-world text. Extensive details are in the
[compatibility analysis](comparison/COMPATIBILITY_ANALYSIS.md).
If you wish to investigate the core logic, see the `lookupProperties` and `width`
functions in [width.go](width.go#L139). The essential trie generation logic is in
`buildPropertyBitmap` in [unicode.go](internal/gen/unicode.go#L316).
## Prior Art
[mattn/go-runewidth](https://github.com/mattn/go-runewidth)
[rivo/uniseg](https://github.com/rivo/uniseg)
[x/text/width](https://pkg.go.dev/golang.org/x/text/width)
[x/text/internal/triegen](https://pkg.go.dev/golang.org/x/text/internal/triegen)
## Benchmarks
```bash
cd comparison
go test -bench=. -benchmem
```
```
goos: darwin
goarch: arm64
pkg: github.com/clipperhouse/displaywidth/comparison
cpu: Apple M2
BenchmarkString_Mixed/clipperhouse/displaywidth-8 10400 ns/op 162.21 MB/s 0 B/op 0 allocs/op
BenchmarkString_Mixed/mattn/go-runewidth-8 14296 ns/op 118.00 MB/s 0 B/op 0 allocs/op
BenchmarkString_Mixed/rivo/uniseg-8 19770 ns/op 85.33 MB/s 0 B/op 0 allocs/op
BenchmarkString_EastAsian/clipperhouse/displaywidth-8 10593 ns/op 159.26 MB/s 0 B/op 0 allocs/op
BenchmarkString_EastAsian/mattn/go-runewidth-8 23980 ns/op 70.35 MB/s 0 B/op 0 allocs/op
BenchmarkString_EastAsian/rivo/uniseg-8 19777 ns/op 85.30 MB/s 0 B/op 0 allocs/op
BenchmarkString_ASCII/clipperhouse/displaywidth-8 1032 ns/op 124.09 MB/s 0 B/op 0 allocs/op
BenchmarkString_ASCII/mattn/go-runewidth-8 1162 ns/op 110.16 MB/s 0 B/op 0 allocs/op
BenchmarkString_ASCII/rivo/uniseg-8 1586 ns/op 80.69 MB/s 0 B/op 0 allocs/op
BenchmarkString_Emoji/clipperhouse/displaywidth-8 3017 ns/op 240.01 MB/s 0 B/op 0 allocs/op
BenchmarkString_Emoji/mattn/go-runewidth-8 4745 ns/op 152.58 MB/s 0 B/op 0 allocs/op
BenchmarkString_Emoji/rivo/uniseg-8 6745 ns/op 107.34 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Mixed/clipperhouse/displaywidth-8 3381 ns/op 498.90 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Mixed/mattn/go-runewidth-8 5383 ns/op 313.41 MB/s 0 B/op 0 allocs/op
BenchmarkRune_EastAsian/clipperhouse/displaywidth-8 3395 ns/op 496.96 MB/s 0 B/op 0 allocs/op
BenchmarkRune_EastAsian/mattn/go-runewidth-8 15645 ns/op 107.83 MB/s 0 B/op 0 allocs/op
BenchmarkRune_ASCII/clipperhouse/displaywidth-8 257.8 ns/op 496.57 MB/s 0 B/op 0 allocs/op
BenchmarkRune_ASCII/mattn/go-runewidth-8 267.3 ns/op 478.89 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Emoji/clipperhouse/displaywidth-8 1338 ns/op 541.24 MB/s 0 B/op 0 allocs/op
BenchmarkRune_Emoji/mattn/go-runewidth-8 2287 ns/op 316.58 MB/s 0 B/op 0 allocs/op
BenchmarkTruncateWithTail/clipperhouse/displaywidth-8 3689 ns/op 47.98 MB/s 192 B/op 14 allocs/op
BenchmarkTruncateWithTail/mattn/go-runewidth-8 8069 ns/op 21.93 MB/s 192 B/op 14 allocs/op
BenchmarkTruncateWithoutTail/clipperhouse/displaywidth-8 3457 ns/op 66.24 MB/s 0 B/op 0 allocs/op
BenchmarkTruncateWithoutTail/mattn/go-runewidth-8 10441 ns/op 21.93 MB/s 0 B/op 0 allocs/op
```
Here are some notes on [how to make Unicode things fast](https://clipperhouse.com/go-unicode/).

View File

@ -1,3 +0,0 @@
package displaywidth
//go:generate go run -C internal/gen .

View File

@ -1,72 +0,0 @@
package displaywidth
import (
"github.com/clipperhouse/stringish"
"github.com/clipperhouse/uax29/v2/graphemes"
)
// Graphemes is an iterator over grapheme clusters.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
type Graphemes[T stringish.Interface] struct {
iter graphemes.Iterator[T]
options Options
}
// Next advances the iterator to the next grapheme cluster.
func (g *Graphemes[T]) Next() bool {
return g.iter.Next()
}
// Value returns the current grapheme cluster.
func (g *Graphemes[T]) Value() T {
return g.iter.Value()
}
// Width returns the display width of the current grapheme cluster.
func (g *Graphemes[T]) Width() int {
return graphemeWidth(g.Value(), g.options)
}
// StringGraphemes returns an iterator over grapheme clusters for the given
// string.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func StringGraphemes(s string) Graphemes[string] {
return DefaultOptions.StringGraphemes(s)
}
// StringGraphemes returns an iterator over grapheme clusters for the given
// string, with the given options.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func (options Options) StringGraphemes(s string) Graphemes[string] {
return Graphemes[string]{
iter: graphemes.FromString(s),
options: options,
}
}
// BytesGraphemes returns an iterator over grapheme clusters for the given
// []byte.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func BytesGraphemes(s []byte) Graphemes[[]byte] {
return DefaultOptions.BytesGraphemes(s)
}
// BytesGraphemes returns an iterator over grapheme clusters for the given
// []byte, with the given options.
//
// Iterate using the Next method, and get the width of the current grapheme
// using the Width method.
func (options Options) BytesGraphemes(s []byte) Graphemes[[]byte] {
return Graphemes[[]byte]{
iter: graphemes.FromBytes(s),
options: options,
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,236 +0,0 @@
package displaywidth
import (
"unicode/utf8"
"github.com/clipperhouse/stringish"
"github.com/clipperhouse/uax29/v2/graphemes"
)
// Options allows you to specify the treatment of ambiguous East Asian
// characters. When EastAsianWidth is false (default), ambiguous East Asian
// characters are treated as width 1. When EastAsianWidth is true, ambiguous
// East Asian characters are treated as width 2.
type Options struct {
EastAsianWidth bool
}
// DefaultOptions is the default options for the display width
// calculation, which is EastAsianWidth: false.
var DefaultOptions = Options{EastAsianWidth: false}
// String calculates the display width of a string,
// by iterating over grapheme clusters in the string
// and summing their widths.
func String(s string) int {
return DefaultOptions.String(s)
}
// String calculates the display width of a string, for the given options, by
// iterating over grapheme clusters in the string and summing their widths.
func (options Options) String(s string) int {
// Optimization: no need to parse grapheme
switch len(s) {
case 0:
return 0
case 1:
return asciiWidth(s[0])
}
width := 0
g := graphemes.FromString(s)
for g.Next() {
width += graphemeWidth(g.Value(), options)
}
return width
}
// Bytes calculates the display width of a []byte,
// by iterating over grapheme clusters in the byte slice
// and summing their widths.
func Bytes(s []byte) int {
return DefaultOptions.Bytes(s)
}
// Bytes calculates the display width of a []byte, for the given options, by
// iterating over grapheme clusters in the slice and summing their widths.
func (options Options) Bytes(s []byte) int {
// Optimization: no need to parse grapheme
switch len(s) {
case 0:
return 0
case 1:
return asciiWidth(s[0])
}
width := 0
g := graphemes.FromBytes(s)
for g.Next() {
width += graphemeWidth(g.Value(), options)
}
return width
}
// Rune calculates the display width of a rune. You
// should almost certainly use [String] or [Bytes] for
// most purposes.
//
// The smallest unit of display width is a grapheme
// cluster, not a rune. Iterating over runes to measure
// width is incorrect in many cases.
func Rune(r rune) int {
return DefaultOptions.Rune(r)
}
// Rune calculates the display width of a rune, for the given options.
//
// You should almost certainly use [String] or [Bytes] for most purposes.
//
// The smallest unit of display width is a grapheme cluster, not a rune.
// Iterating over runes to measure width is incorrect in many cases.
func (options Options) Rune(r rune) int {
if r < utf8.RuneSelf {
return asciiWidth(byte(r))
}
// Surrogates (U+D800-U+DFFF) are invalid UTF-8.
if r >= 0xD800 && r <= 0xDFFF {
return 0
}
var buf [4]byte
n := utf8.EncodeRune(buf[:], r)
// Skip the grapheme iterator
return graphemeWidth(buf[:n], options)
}
const _Default property = 0
// TruncateString truncates a string to the given maxWidth, and appends the
// given tail if the string is truncated.
//
// It ensures the total width, including the width of the tail, is less than or
// equal to maxWidth.
func (options Options) TruncateString(s string, maxWidth int, tail string) string {
maxWidthWithoutTail := maxWidth - options.String(tail)
var pos, total int
g := graphemes.FromString(s)
for g.Next() {
gw := graphemeWidth(g.Value(), options)
if total+gw <= maxWidthWithoutTail {
pos = g.End()
}
total += gw
if total > maxWidth {
return s[:pos] + tail
}
}
// No truncation
return s
}
// TruncateString truncates a string to the given maxWidth, and appends the
// given tail if the string is truncated.
//
// It ensures the total width, including the width of the tail, is less than or
// equal to maxWidth.
func TruncateString(s string, maxWidth int, tail string) string {
return DefaultOptions.TruncateString(s, maxWidth, tail)
}
// TruncateBytes truncates a []byte to the given maxWidth, and appends the
// given tail if the []byte is truncated.
//
// It ensures the total width, including the width of the tail, is less than or
// equal to maxWidth.
func (options Options) TruncateBytes(s []byte, maxWidth int, tail []byte) []byte {
maxWidthWithoutTail := maxWidth - options.Bytes(tail)
var pos, total int
g := graphemes.FromBytes(s)
for g.Next() {
gw := graphemeWidth(g.Value(), options)
if total+gw <= maxWidthWithoutTail {
pos = g.End()
}
total += gw
if total > maxWidth {
result := make([]byte, 0, pos+len(tail))
result = append(result, s[:pos]...)
result = append(result, tail...)
return result
}
}
// No truncation
return s
}
// TruncateBytes truncates a []byte to the given maxWidth, and appends the
// given tail if the []byte is truncated.
//
// It ensures the total width, including the width of the tail, is less than or
// equal to maxWidth.
func TruncateBytes(s []byte, maxWidth int, tail []byte) []byte {
return DefaultOptions.TruncateBytes(s, maxWidth, tail)
}
// graphemeWidth returns the display width of a grapheme cluster.
// The passed string must be a single grapheme cluster.
func graphemeWidth[T stringish.Interface](s T, options Options) int {
// Optimization: no need to look up properties
switch len(s) {
case 0:
return 0
case 1:
return asciiWidth(s[0])
}
p, sz := lookup(s)
prop := property(p)
// Variation Selector 16 (VS16) requests emoji presentation
if prop != _Wide && sz > 0 && len(s) >= sz+3 {
vs := s[sz : sz+3]
if isVS16(vs) {
prop = _Wide
}
// VS15 (0x8E) requests text presentation but does not affect width,
// in my reading of Unicode TR51. Falls through to return the base
// character's property.
}
if options.EastAsianWidth && prop == _East_Asian_Ambiguous {
prop = _Wide
}
if prop > upperBound {
prop = _Default
}
return propertyWidths[prop]
}
func asciiWidth(b byte) int {
if b <= 0x1F || b == 0x7F {
return 0
}
return 1
}
// isVS16 checks if the slice matches VS16 (U+FE0F) UTF-8 encoding
// (EF B8 8F). It assumes len(s) >= 3.
func isVS16[T stringish.Interface](s T) bool {
return s[0] == 0xEF && s[1] == 0xB8 && s[2] == 0x8F
}
// propertyWidths is a jump table of sorts, instead of a switch
var propertyWidths = [4]int{
_Default: 1,
_Zero_Width: 0,
_Wide: 2,
_East_Asian_Ambiguous: 1,
}
const upperBound = property(len(propertyWidths) - 1)

View File

@ -1,2 +0,0 @@
.DS_Store
*.test

View File

@ -1,64 +0,0 @@
# stringish
A small Go module that provides a generic type constraint for “string-like”
data, and a utf8 package that works with both strings and byte slices
without conversions.
```go
type Interface interface {
~[]byte | ~string
}
```
[![Go Reference](https://pkg.go.dev/badge/github.com/clipperhouse/stringish/utf8.svg)](https://pkg.go.dev/github.com/clipperhouse/stringish/utf8)
[![Test Status](https://github.com/clipperhouse/stringish/actions/workflows/gotest.yml/badge.svg)](https://github.com/clipperhouse/stringish/actions/workflows/gotest.yml)
## Install
```
go get github.com/clipperhouse/stringish
```
## Examples
```go
import (
"github.com/clipperhouse/stringish"
"github.com/clipperhouse/stringish/utf8"
)
s := "Hello, 世界"
r, size := utf8.DecodeRune(s) // not DecodeRuneInString 🎉
b := []byte("Hello, 世界")
r, size = utf8.DecodeRune(b) // same API!
func MyFoo[T stringish.Interface](s T) T {
// pass a string or a []byte
// iterate, slice, transform, whatever
}
```
## Motivation
Sometimes we want APIs to accept `string` or `[]byte` without having to convert
between those types. That conversion usually allocates!
By implementing with `stringish.Interface`, we can have a single API, and
single implementation for both types: one `Foo` instead of `Foo` and
`FooString`.
We have converted the
[`unicode/utf8` package](https://github.com/clipperhouse/stringish/blob/main/utf8/utf8.go)
as an example -- note the absence of`*InString` funcs. We might look at `x/text`
next.
## Used by
- clipperhouse/uax29: [stringish trie](https://github.com/clipperhouse/uax29/blob/master/graphemes/trie.go#L27), [stringish iterator](https://github.com/clipperhouse/uax29/blob/master/internal/iterators/iterator.go#L9), [stringish SplitFunc](https://github.com/clipperhouse/uax29/blob/master/graphemes/splitfunc.go#L21)
- [clipperhouse/displaywidth](https://github.com/clipperhouse/displaywidth)
## Prior discussion
- [Consideration of similar by the Go team](https://github.com/golang/go/issues/48643)

View File

@ -1,5 +0,0 @@
package stringish
type Interface interface {
~[]byte | ~string
}

View File

@ -1,9 +1,5 @@
An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0.
[![Documentation](https://pkg.go.dev/badge/github.com/clipperhouse/uax29/v2/graphemes.svg)](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
![Tests](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg)
![Fuzz](https://github.com/clipperhouse/uax29/actions/workflows/gofuzz.yml/badge.svg)
## Quick start
```
@ -22,14 +18,15 @@ for tokens.Next() { // Next() returns true until end of data
}
```
[![Documentation](https://pkg.go.dev/badge/github.com/clipperhouse/uax29/v2/graphemes.svg)](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes)
_A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._
## Conformance
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29).
We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status:
![Tests](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg)
![Fuzz](https://github.com/clipperhouse/uax29/actions/workflows/gofuzz.yml/badge.svg)
![Go](https://github.com/clipperhouse/uax29/actions/workflows/gotest.yml/badge.svg)
## APIs
@ -74,18 +71,9 @@ for tokens.Next() { // Next() returns true until end of data
}
```
### Benchmarks
### Performance
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second, and no allocations.
```
goos: darwin
goarch: arm64
pkg: github.com/clipperhouse/uax29/graphemes/comparative
cpu: Apple M2
BenchmarkGraphemes/clipperhouse/uax29-8 173805 ns/op 201.16 MB/s 0 B/op 0 allocs/op
BenchmarkGraphemes/rivo/uniseg-8 2045128 ns/op 17.10 MB/s 0 B/op 0 allocs/op
```
On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second. You should see ~constant memory, and no allocations.
### Invalid inputs

View File

@ -1,11 +1,8 @@
package graphemes
import (
"github.com/clipperhouse/stringish"
"github.com/clipperhouse/uax29/v2/internal/iterators"
)
import "github.com/clipperhouse/uax29/v2/internal/iterators"
type Iterator[T stringish.Interface] struct {
type Iterator[T iterators.Stringish] struct {
*iterators.Iterator[T]
}

View File

@ -3,7 +3,7 @@ package graphemes
import (
"bufio"
"github.com/clipperhouse/stringish"
"github.com/clipperhouse/uax29/v2/internal/iterators"
)
// is determines if lookup intersects propert(ies)
@ -18,7 +18,7 @@ const _Ignore = _Extend
// See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries.
var SplitFunc bufio.SplitFunc = splitFunc[[]byte]
func splitFunc[T stringish.Interface](data T, atEOF bool) (advance int, token T, err error) {
func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) {
var empty T
if len(data) == 0 {
return 0, empty, nil

View File

@ -1,10 +1,10 @@
package graphemes
import "github.com/clipperhouse/stringish"
// generated by github.com/clipperhouse/uax29/v2
// from https://www.unicode.org/Public/15.0.0/ucd/auxiliary/GraphemeBreakProperty.txt
import "github.com/clipperhouse/uax29/v2/internal/iterators"
type property uint16
const (
@ -27,7 +27,7 @@ const (
// lookup returns the trie value for the first UTF-8 encoding in s and
// the width in bytes of this encoding. The size will be 0 if s does not
// hold enough bytes to complete the encoding. len(s) must be greater than 0.
func lookup[T stringish.Interface](s T) (v property, sz int) {
func lookup[T iterators.Stringish](s T) (v property, sz int) {
c0 := s[0]
switch {
case c0 < 0x80: // is ASCII

View File

@ -1,12 +1,14 @@
package iterators
import "github.com/clipperhouse/stringish"
type Stringish interface {
[]byte | string
}
type SplitFunc[T stringish.Interface] func(T, bool) (int, T, error)
type SplitFunc[T Stringish] func(T, bool) (int, T, error)
// Iterator is a generic iterator for words that are either []byte or string.
// Iterate while Next() is true, and access the word via Value().
type Iterator[T stringish.Interface] struct {
type Iterator[T Stringish] struct {
split SplitFunc[T]
data T
start int
@ -14,7 +16,7 @@ type Iterator[T stringish.Interface] struct {
}
// New creates a new Iterator for the given data and SplitFunc.
func New[T stringish.Interface](split SplitFunc[T], data T) *Iterator[T] {
func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] {
return &Iterator[T]{
split: split,
data: data,
@ -81,20 +83,3 @@ func (iter *Iterator[T]) Reset() {
iter.start = 0
iter.pos = 0
}
func (iter *Iterator[T]) First() T {
if len(iter.data) == 0 {
return iter.data
}
advance, _, err := iter.split(iter.data, true)
if err != nil {
panic(err)
}
if advance <= 0 {
panic("SplitFunc returned a zero or negative advance")
}
if advance > len(iter.data) {
panic("SplitFunc advanced beyond the end of the data")
}
return iter.data[:advance]
}

View File

@ -14,14 +14,14 @@ import "unsafe"
type storageBuf [maxRate / 8]uint64
func (b *storageBuf) asBytes() *[maxRate]byte {
return (*[maxRate]byte)(unsafe.Pointer(b)) //nolint:gosec
return (*[maxRate]byte)(unsafe.Pointer(b))
}
// xorInuses unaligned reads and writes to update d.a to contain d.a
// XOR buf.
func xorIn(d *State, buf []byte) {
n := len(buf)
bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] //nolint:gosec
bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8]
if n >= 72 {
d.a[0] ^= bw[0]
d.a[1] ^= bw[1]
@ -56,6 +56,6 @@ func xorIn(d *State, buf []byte) {
}
func copyOut(d *State, buf []byte) {
ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) //nolint:gosec
ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0]))
copy(buf, ab[:])
}

View File

@ -38,12 +38,6 @@ type PrivateKey interface {
encoding.BinaryMarshaler
}
// A private key that retains the seed with which it was generated.
type Seeded interface {
// returns the seed if retained, otherwise nil
Seed() []byte
}
// A Scheme represents a specific instance of a signature scheme.
type Scheme interface {
// Name of the scheme.

View File

@ -9,10 +9,6 @@
version: "2"
run:
build-tags:
- libpathrs
linters:
enable:
- asasalint

View File

@ -6,82 +6,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased] ##
## [0.6.1] - 2025-11-19 ##
> At last up jumped the cunning spider, and fiercely held her fast.
### Fixed ###
- Our logic for deciding whether to use `openat2(2)` or fallback to an `O_PATH`
resolver would cache the result to avoid doing needless test runs of
`openat2(2)`. However, this causes issues when `pathrs-lite` is being used by
a program that applies new seccomp-bpf filters onto itself -- if the filter
denies `openat2(2)` then we would return that error rather than falling back
to the `O_PATH` resolver. To resolve this issue, we no longer cache the
result if `openat2(2)` was successful, only if there was an error.
- A file descriptor leak in our `openat2` wrapper (when doing the necessary
`dup` for `RESOLVE_IN_ROOT`) has been removed.
## [0.5.2] - 2025-11-19 ##
> "Will you walk into my parlour?" said a spider to a fly.
### Fixed ###
- Our logic for deciding whether to use `openat2(2)` or fallback to an `O_PATH`
resolver would cache the result to avoid doing needless test runs of
`openat2(2)`. However, this causes issues when `pathrs-lite` is being used by
a program that applies new seccomp-bpf filters onto itself -- if the filter
denies `openat2(2)` then we would return that error rather than falling back
to the `O_PATH` resolver. To resolve this issue, we no longer cache the
result if `openat2(2)` was successful, only if there was an error.
- A file descriptor leak in our `openat2` wrapper (when doing the necessary
`dup` for `RESOLVE_IN_ROOT`) has been removed.
## [0.6.0] - 2025-11-03 ##
> By the Power of Greyskull!
### Breaking ###
- The deprecated `MkdirAll`, `MkdirAllHandle`, `OpenInRoot`, `OpenatInRoot` and
`Reopen` wrappers have been removed. Please switch to using `pathrs-lite`
directly.
### Added ###
- `pathrs-lite` now has support for using libpathrs as a backend. This is
opt-in and can be enabled at build time with the `libpathrs` build tag. The
intention is to allow for downstream libraries and other projects to make use
of the pure-Go `github.com/cyphar/filepath-securejoin/pathrs-lite` package
and distributors can then opt-in to using `libpathrs` for the entire binary
if they wish.
## [0.5.1] - 2025-10-31 ##
> Spooky scary skeletons send shivers down your spine!
### Changed ###
- `openat2` can return `-EAGAIN` if it detects a possible attack in certain
scenarios (namely if there was a rename or mount while walking a path with a
`..` component). While this is necessary to avoid a denial-of-service in the
kernel, it does require retry loops in userspace.
In previous versions, `pathrs-lite` would retry `openat2` 32 times before
returning an error, but we've received user reports that this limit can be
hit on systems with very heavy load. In some synthetic benchmarks (testing
the worst-case of an attacker doing renames in a tight loop on every core of
a 16-core machine) we managed to get a ~3% failure rate in runc. We have
improved this situation in two ways:
* We have now increased this limit to 128, which should be good enough for
most use-cases without becoming a denial-of-service vector (the number of
syscalls called by the `O_PATH` resolver in a typical case is within the
same ballpark). The same benchmarks show a failure rate of ~0.12% which
(while not zero) is probably sufficient for most users.
* In addition, we now return a `unix.EAGAIN` error that is bubbled up and can
be detected by callers. This means that callers with stricter requirements
to avoid spurious errors can choose to do their own infinite `EAGAIN` retry
loop (though we would strongly recommend users use time-based deadlines in
such retry loops to avoid potentially unbounded denials-of-service).
## [0.5.0] - 2025-09-26 ##
> Let the past die. Kill it if you have to.
@ -430,11 +354,7 @@ This is our first release of `github.com/cyphar/filepath-securejoin`,
containing a full implementation with a coverage of 93.5% (the only missing
cases are the error cases, which are hard to mocktest at the moment).
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.1...HEAD
[0.6.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.6.0...v0.6.1
[0.6.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.6.0
[0.5.2]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.1...v0.5.2
[0.5.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...v0.5.1
[Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0
[0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1
[0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0

View File

@ -1 +1 @@
0.6.1
0.5.0

View File

@ -0,0 +1,48 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package securejoin
import (
"github.com/cyphar/filepath-securejoin/pathrs-lite"
)
var (
// MkdirAll is a wrapper around [pathrs.MkdirAll].
//
// Deprecated: You should use [pathrs.MkdirAll] directly instead. This
// wrapper will be removed in filepath-securejoin v0.6.
MkdirAll = pathrs.MkdirAll
// MkdirAllHandle is a wrapper around [pathrs.MkdirAllHandle].
//
// Deprecated: You should use [pathrs.MkdirAllHandle] directly instead.
// This wrapper will be removed in filepath-securejoin v0.6.
MkdirAllHandle = pathrs.MkdirAllHandle
// OpenInRoot is a wrapper around [pathrs.OpenInRoot].
//
// Deprecated: You should use [pathrs.OpenInRoot] directly instead. This
// wrapper will be removed in filepath-securejoin v0.6.
OpenInRoot = pathrs.OpenInRoot
// OpenatInRoot is a wrapper around [pathrs.OpenatInRoot].
//
// Deprecated: You should use [pathrs.OpenatInRoot] directly instead. This
// wrapper will be removed in filepath-securejoin v0.6.
OpenatInRoot = pathrs.OpenatInRoot
// Reopen is a wrapper around [pathrs.Reopen].
//
// Deprecated: You should use [pathrs.Reopen] directly instead. This
// wrapper will be removed in filepath-securejoin v0.6.
Reopen = pathrs.Reopen
)

View File

@ -0,0 +1,33 @@
## `pathrs-lite` ##
`github.com/cyphar/filepath-securejoin/pathrs-lite` provides a minimal **pure
Go** implementation of the core bits of [libpathrs][]. This is not intended to
be a complete replacement for libpathrs, instead it is mainly intended to be
useful as a transition tool for existing Go projects.
The long-term plan for `pathrs-lite` is to provide a build tag that will cause
all `pathrs-lite` operations to call into libpathrs directly, thus removing
code duplication for projects that wish to make use of libpathrs (and providing
the ability for software packagers to opt-in to libpathrs support without
needing to patch upstream).
[libpathrs]: https://github.com/cyphar/libpathrs
### License ###
Most of this subpackage is licensed under the Mozilla Public License (version
2.0). For more information, see the top-level [COPYING.md][] and
[LICENSE.MPL-2.0][] files, as well as the individual license headers for each
file.
```
Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
Copyright (C) 2024-2025 SUSE LLC
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at https://mozilla.org/MPL/2.0/.
```
[COPYING.md]: ../COPYING.md
[LICENSE.MPL-2.0]: ../LICENSE.MPL-2.0

View File

@ -0,0 +1,14 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Package pathrs (pathrs-lite) is a less complete pure Go implementation of
// some of the APIs provided by [libpathrs].
package pathrs

View File

@ -0,0 +1,30 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Package assert provides some basic assertion helpers for Go.
package assert
import (
"fmt"
)
// Assert panics if the predicate is false with the provided argument.
func Assert(predicate bool, msg any) {
if !predicate {
panic(msg)
}
}
// Assertf panics if the predicate is false and formats the message using the
// same formatting as [fmt.Printf].
//
// [fmt.Printf]: https://pkg.go.dev/fmt#Printf
func Assertf(predicate bool, fmtMsg string, args ...any) {
Assert(predicate, fmt.Sprintf(fmtMsg, args...))
}

View File

@ -0,0 +1,30 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Package internal contains unexported common code for filepath-securejoin.
package internal
import (
"errors"
)
var (
// ErrPossibleAttack indicates that some attack was detected.
ErrPossibleAttack = errors.New("possible attack detected")
// ErrPossibleBreakout indicates that during an operation we ended up in a
// state that could be a breakout but we detected it.
ErrPossibleBreakout = errors.New("possible breakout detected")
// ErrInvalidDirectory indicates an unlinked directory.
ErrInvalidDirectory = errors.New("wandered into deleted directory")
// ErrDeletedInode indicates an unlinked file (non-directory).
ErrDeletedInode = errors.New("cannot verify path of deleted inode")
)

View File

@ -0,0 +1,148 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package fd
import (
"fmt"
"os"
"path/filepath"
"runtime"
"golang.org/x/sys/unix"
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal/gocompat"
)
// prepareAtWith returns -EBADF (an invalid fd) if dir is nil, otherwise using
// the dir.Fd(). We use -EBADF because in filepath-securejoin we generally
// don't want to allow relative-to-cwd paths. The returned path is an
// *informational* string that describes a reasonable pathname for the given
// *at(2) arguments. You must not use the full path for any actual filesystem
// operations.
func prepareAt(dir Fd, path string) (dirFd int, unsafeUnmaskedPath string) {
dirFd, dirPath := -int(unix.EBADF), "."
if dir != nil {
dirFd, dirPath = int(dir.Fd()), dir.Name()
}
if !filepath.IsAbs(path) {
// only prepend the dirfd path for relative paths
path = dirPath + "/" + path
}
// NOTE: If path is "." or "", the returned path won't be filepath.Clean,
// but that's okay since this path is either used for errors (in which case
// a trailing "/" or "/." is important information) or will be
// filepath.Clean'd later (in the case of fd.Openat).
return dirFd, path
}
// Openat is an [Fd]-based wrapper around unix.Openat.
func Openat(dir Fd, path string, flags int, mode int) (*os.File, error) { //nolint:unparam // wrapper func
dirFd, fullPath := prepareAt(dir, path)
// Make sure we always set O_CLOEXEC.
flags |= unix.O_CLOEXEC
fd, err := unix.Openat(dirFd, path, flags, uint32(mode))
if err != nil {
return nil, &os.PathError{Op: "openat", Path: fullPath, Err: err}
}
runtime.KeepAlive(dir)
// openat is only used with lexically-safe paths so we can use
// filepath.Clean here, and also the path itself is not going to be used
// for actual path operations.
fullPath = filepath.Clean(fullPath)
return os.NewFile(uintptr(fd), fullPath), nil
}
// Fstatat is an [Fd]-based wrapper around unix.Fstatat.
func Fstatat(dir Fd, path string, flags int) (unix.Stat_t, error) {
dirFd, fullPath := prepareAt(dir, path)
var stat unix.Stat_t
if err := unix.Fstatat(dirFd, path, &stat, flags); err != nil {
return stat, &os.PathError{Op: "fstatat", Path: fullPath, Err: err}
}
runtime.KeepAlive(dir)
return stat, nil
}
// Faccessat is an [Fd]-based wrapper around unix.Faccessat.
func Faccessat(dir Fd, path string, mode uint32, flags int) error {
dirFd, fullPath := prepareAt(dir, path)
err := unix.Faccessat(dirFd, path, mode, flags)
if err != nil {
err = &os.PathError{Op: "faccessat", Path: fullPath, Err: err}
}
runtime.KeepAlive(dir)
return err
}
// Readlinkat is an [Fd]-based wrapper around unix.Readlinkat.
func Readlinkat(dir Fd, path string) (string, error) {
dirFd, fullPath := prepareAt(dir, path)
size := 4096
for {
linkBuf := make([]byte, size)
n, err := unix.Readlinkat(dirFd, path, linkBuf)
if err != nil {
return "", &os.PathError{Op: "readlinkat", Path: fullPath, Err: err}
}
runtime.KeepAlive(dir)
if n != size {
return string(linkBuf[:n]), nil
}
// Possible truncation, resize the buffer.
size *= 2
}
}
const (
// STATX_MNT_ID_UNIQUE is provided in golang.org/x/sys@v0.20.0, but in order to
// avoid bumping the requirement for a single constant we can just define it
// ourselves.
_STATX_MNT_ID_UNIQUE = 0x4000 //nolint:revive // unix.* name
// We don't care which mount ID we get. The kernel will give us the unique
// one if it is supported. If the kernel doesn't support
// STATX_MNT_ID_UNIQUE, the bit is ignored and the returned request mask
// will only contain STATX_MNT_ID (if supported).
wantStatxMntMask = _STATX_MNT_ID_UNIQUE | unix.STATX_MNT_ID
)
var hasStatxMountID = gocompat.SyncOnceValue(func() bool {
var stx unix.Statx_t
err := unix.Statx(-int(unix.EBADF), "/", 0, wantStatxMntMask, &stx)
return err == nil && stx.Mask&wantStatxMntMask != 0
})
// GetMountID gets the mount identifier associated with the fd and path
// combination. It is effectively a wrapper around fetching
// STATX_MNT_ID{,_UNIQUE} with unix.Statx, but with a fallback to 0 if the
// kernel doesn't support the feature.
func GetMountID(dir Fd, path string) (uint64, error) {
// If we don't have statx(STATX_MNT_ID*) support, we can't do anything.
if !hasStatxMountID() {
return 0, nil
}
dirFd, fullPath := prepareAt(dir, path)
var stx unix.Statx_t
err := unix.Statx(dirFd, path, unix.AT_EMPTY_PATH|unix.AT_SYMLINK_NOFOLLOW, wantStatxMntMask, &stx)
if stx.Mask&wantStatxMntMask == 0 {
// It's not a kernel limitation, for some reason we couldn't get a
// mount ID. Assume it's some kind of attack.
err = fmt.Errorf("could not get mount id: %w", err)
}
if err != nil {
return 0, &os.PathError{Op: "statx(STATX_MNT_ID_...)", Path: fullPath, Err: err}
}
runtime.KeepAlive(dir)
return stx.Mnt_id, nil
}

View File

@ -0,0 +1,55 @@
// SPDX-License-Identifier: MPL-2.0
// Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
// Package fd provides a drop-in interface-based replacement of [*os.File] that
// allows for things like noop-Close wrappers to be used.
//
// [*os.File]: https://pkg.go.dev/os#File
package fd
import (
"io"
"os"
)
// Fd is an interface that mirrors most of the API of [*os.File], allowing you
// to create wrappers that can be used in place of [*os.File].
//
// [*os.File]: https://pkg.go.dev/os#File
type Fd interface {
io.Closer
Name() string
Fd() uintptr
}
// Compile-time interface checks.
var (
_ Fd = (*os.File)(nil)
_ Fd = noClose{}
)
type noClose struct{ inner Fd }
func (f noClose) Name() string { return f.inner.Name() }
func (f noClose) Fd() uintptr { return f.inner.Fd() }
func (f noClose) Close() error { return nil }
// NopCloser returns an [*os.File]-like object where the [Close] method is now
// a no-op.
//
// Note that for [*os.File] and similar objects, the Go garbage collector will
// still call [Close] on the underlying file unless you use
// [runtime.SetFinalizer] to disable this behaviour. This is up to the caller
// to do (if necessary).
//
// [*os.File]: https://pkg.go.dev/os#File
// [Close]: https://pkg.go.dev/io#Closer
// [runtime.SetFinalizer]: https://pkg.go.dev/runtime#SetFinalizer
func NopCloser(f Fd) Fd { return noClose{inner: f} }

View File

@ -0,0 +1,78 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package fd
import (
"fmt"
"os"
"runtime"
"golang.org/x/sys/unix"
"github.com/cyphar/filepath-securejoin/pathrs-lite/internal"
)
// DupWithName creates a new file descriptor referencing the same underlying
// file, but with the provided name instead of fd.Name().
func DupWithName(fd Fd, name string) (*os.File, error) {
fd2, err := unix.FcntlInt(fd.Fd(), unix.F_DUPFD_CLOEXEC, 0)
if err != nil {
return nil, os.NewSyscallError("fcntl(F_DUPFD_CLOEXEC)", err)
}
runtime.KeepAlive(fd)
return os.NewFile(uintptr(fd2), name), nil
}
// Dup creates a new file description referencing the same underlying file.
func Dup(fd Fd) (*os.File, error) {
return DupWithName(fd, fd.Name())
}
// Fstat is an [Fd]-based wrapper around unix.Fstat.
func Fstat(fd Fd) (unix.Stat_t, error) {
var stat unix.Stat_t
if err := unix.Fstat(int(fd.Fd()), &stat); err != nil {
return stat, &os.PathError{Op: "fstat", Path: fd.Name(), Err: err}
}
runtime.KeepAlive(fd)
return stat, nil
}
// Fstatfs is an [Fd]-based wrapper around unix.Fstatfs.
func Fstatfs(fd Fd) (unix.Statfs_t, error) {
var statfs unix.Statfs_t
if err := unix.Fstatfs(int(fd.Fd()), &statfs); err != nil {
return statfs, &os.PathError{Op: "fstatfs", Path: fd.Name(), Err: err}
}
runtime.KeepAlive(fd)
return statfs, nil
}
// IsDeadInode detects whether the file has been unlinked from a filesystem and
// is thus a "dead inode" from the kernel's perspective.
func IsDeadInode(file Fd) error {
// If the nlink of a file drops to 0, there is an attacker deleting
// directories during our walk, which could result in weird /proc values.
// It's better to error out in this case.
stat, err := Fstat(file)
if err != nil {
return fmt.Errorf("check for dead inode: %w", err)
}
if stat.Nlink == 0 {
err := internal.ErrDeletedInode
if stat.Mode&unix.S_IFMT == unix.S_IFDIR {
err = internal.ErrInvalidDirectory
}
return fmt.Errorf("%w %q", err, file.Name())
}
return nil
}

View File

@ -0,0 +1,54 @@
// SPDX-License-Identifier: MPL-2.0
//go:build linux
// Copyright (C) 2024-2025 Aleksa Sarai <cyphar@cyphar.com>
// Copyright (C) 2024-2025 SUSE LLC
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
package fd
import (
"os"
"runtime"
"golang.org/x/sys/unix"
)
// Fsopen is an [Fd]-based wrapper around unix.Fsopen.
func Fsopen(fsName string, flags int) (*os.File, error) {
// Make sure we always set O_CLOEXEC.
flags |= unix.FSOPEN_CLOEXEC
fd, err := unix.Fsopen(fsName, flags)
if err != nil {
return nil, os.NewSyscallError("fsopen "+fsName, err)
}
return os.NewFile(uintptr(fd), "fscontext:"+fsName), nil
}
// Fsmount is an [Fd]-based wrapper around unix.Fsmount.
func Fsmount(ctx Fd, flags, mountAttrs int) (*os.File, error) {
// Make sure we always set O_CLOEXEC.
flags |= unix.FSMOUNT_CLOEXEC
fd, err := unix.Fsmount(int(ctx.Fd()), flags, mountAttrs)
if err != nil {
return nil, os.NewSyscallError("fsmount "+ctx.Name(), err)
}
return os.NewFile(uintptr(fd), "fsmount:"+ctx.Name()), nil
}
// OpenTree is an [Fd]-based wrapper around unix.OpenTree.
func OpenTree(dir Fd, path string, flags uint) (*os.File, error) {
dirFd, fullPath := prepareAt(dir, path)
// Make sure we always set O_CLOEXEC.
flags |= unix.OPEN_TREE_CLOEXEC
fd, err := unix.OpenTree(dirFd, path, flags)
if err != nil {
return nil, &os.PathError{Op: "open_tree", Path: fullPath, Err: err}
}
runtime.KeepAlive(dir)
return os.NewFile(uintptr(fd), fullPath), nil
}

Some files were not shown because too many files have changed in this diff Show More