forked from toolshed/abra
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
4ef849767f
|
@ -260,7 +260,6 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
app.Name,
|
app.Name,
|
||||||
app.Server,
|
app.Server,
|
||||||
internal.DontWaitConverge,
|
internal.DontWaitConverge,
|
||||||
internal.NoInput,
|
|
||||||
f,
|
f,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@ -128,7 +128,6 @@ Pass "--all-services/-a" to restart all services.`),
|
|||||||
AppName: app.Name,
|
AppName: app.Name,
|
||||||
ServerName: app.Server,
|
ServerName: app.Server,
|
||||||
Filters: f,
|
Filters: f,
|
||||||
NoInput: internal.NoInput,
|
|
||||||
NoLog: true,
|
NoLog: true,
|
||||||
Quiet: true,
|
Quiet: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -246,7 +246,6 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
stackName,
|
stackName,
|
||||||
app.Server,
|
app.Server,
|
||||||
internal.DontWaitConverge,
|
internal.DontWaitConverge,
|
||||||
internal.NoInput,
|
|
||||||
f,
|
f,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@ -282,7 +282,6 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
stackName,
|
stackName,
|
||||||
app.Server,
|
app.Server,
|
||||||
internal.DontWaitConverge,
|
internal.DontWaitConverge,
|
||||||
internal.NoInput,
|
|
||||||
f,
|
f,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
3
go.mod
3
go.mod
@ -16,7 +16,6 @@ require (
|
|||||||
github.com/docker/cli v28.4.0+incompatible
|
github.com/docker/cli v28.4.0+incompatible
|
||||||
github.com/docker/docker v28.4.0+incompatible
|
github.com/docker/docker v28.4.0+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/evertras/bubble-table v0.19.2
|
|
||||||
github.com/go-git/go-git/v5 v5.16.2
|
github.com/go-git/go-git/v5 v5.16.2
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/leonelquinteros/gotext v1.7.2
|
github.com/leonelquinteros/gotext v1.7.2
|
||||||
@ -35,7 +34,6 @@ require (
|
|||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
@ -94,7 +92,6 @@ require (
|
|||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
|
|||||||
9
go.sum
9
go.sum
@ -99,8 +99,6 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
|
|||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
@ -373,8 +371,6 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
|
|||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
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/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/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/evertras/bubble-table v0.19.2 h1:u77oiM6JlRR+CvS5FZc3Hz+J6iEsvEDcR5kO8OFb1Yw=
|
|
||||||
github.com/evertras/bubble-table v0.19.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
@ -640,7 +636,6 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
|
||||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
@ -702,8 +697,6 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D
|
|||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
@ -818,8 +811,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
|||||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
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/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/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
|
|||||||
@ -633,11 +633,6 @@ func (a App) WipeRecipeVersion() error {
|
|||||||
|
|
||||||
// WriteRecipeVersion writes the recipe version to the app .env file.
|
// WriteRecipeVersion writes the recipe version to the app .env file.
|
||||||
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
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)
|
file, err := os.Open(a.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -224,16 +224,3 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
|
|||||||
|
|
||||||
assert.Equal(t, "foo", app.Recipe.EnvVersion)
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@ -37,27 +37,18 @@ func WithTimeout(timeout int) Opt {
|
|||||||
// New initiates a new Docker client. New client connections are validated so
|
// 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
|
// 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.
|
// 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) {
|
func New(serverName string, opts ...Opt) (*client.Client, error) {
|
||||||
var clientOpts []client.Opt
|
var clientOpts []client.Opt
|
||||||
|
|
||||||
ctx, err := GetContext(serverName)
|
ctx, err := GetContext(serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serverDir := path.Join(config.SERVERS_DIR, serverName)
|
serverDir := path.Join(config.SERVERS_DIR, serverName)
|
||||||
if _, err := os.Stat(serverDir); err != nil {
|
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 {
|
|
||||||
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
|
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)
|
ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr "Project-Id-Version: \n"
|
msgstr "Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||||
"POT-Creation-Date: 2025-11-04 15:34+0100\n"
|
"POT-Creation-Date: 2025-11-02 14:13+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@ -274,12 +274,12 @@ msgstr ""
|
|||||||
msgid "%s has been detected as not deployed"
|
msgid "%s has been detected as not deployed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/restart.go:140
|
#: ./cli/app/restart.go:139
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s has been scaled to 0"
|
msgid "%s has been scaled to 0"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/restart.go:151
|
#: ./cli/app/restart.go:150
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s has been scaled to 1"
|
msgid "%s has been scaled to 1"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -339,17 +339,17 @@ msgstr ""
|
|||||||
msgid "%s is missing the TYPE env var?"
|
msgid "%s is missing the TYPE env var?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:309 ./cli/app/rollback.go:313
|
#: ./cli/app/rollback.go:308 ./cli/app/rollback.go:312
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not a downgrade for %s?"
|
msgid "%s is not a downgrade for %s?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:429 ./cli/app/upgrade.go:433
|
#: ./cli/app/upgrade.go:428 ./cli/app/upgrade.go:432
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not an upgrade for %s?"
|
msgid "%s is not an upgrade for %s?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:450
|
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:449
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s is not deployed?"
|
msgid "%s is not deployed?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -424,7 +424,7 @@ msgstr ""
|
|||||||
msgid "%s service is missing image tag?"
|
msgid "%s service is missing image tag?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/restart.go:152
|
#: ./cli/app/restart.go:151
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s service successfully restarted"
|
msgid "%s service successfully restarted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -459,7 +459,7 @@ msgstr ""
|
|||||||
msgid "%s/example.git"
|
msgid "%s/example.git"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:613
|
#: ./pkg/upstream/stack/stack.go:602
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s: %s"
|
msgid "%s: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -469,7 +469,7 @@ msgstr ""
|
|||||||
msgid "%s: %s (new)"
|
msgid "%s: %s (new)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:344
|
#: ./pkg/ui/deploy.go:406
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "%s: %s (retries: %v, healthcheck: %s)"
|
msgid "%s: %s (retries: %v, healthcheck: %s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -549,12 +549,12 @@ msgstr ""
|
|||||||
msgid "%s: waiting %d seconds before next retry"
|
msgid "%s: waiting %d seconds before next retry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:424
|
#: ./cli/app/upgrade.go:423
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "'%s' is not a known version"
|
msgid "'%s' is not a known version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:304 ./cli/app/upgrade.go:419
|
#: ./cli/app/rollback.go:303 ./cli/app/upgrade.go:418
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "'%s' is not a known version for %s"
|
msgid "'%s' is not a known version for %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -621,7 +621,7 @@ msgstr ""
|
|||||||
msgid "Both local recipe and live deployment labels are shown."
|
msgid "Both local recipe and live deployment labels are shown."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:396 ./cli/app/labels.go:143 ./cli/app/new.go:397 ./cli/app/ps.go:213 ./cli/app/restart.go:163 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
|
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:395 ./cli/app/labels.go:143 ./cli/app/new.go:397 ./cli/app/ps.go:213 ./cli/app/restart.go:162 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
|
||||||
msgid "C"
|
msgid "C"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -762,7 +762,7 @@ msgid "Creates a new app from a default recipe.\n"
|
|||||||
"on your $PATH."
|
"on your $PATH."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:412 ./cli/app/new.go:373 ./cli/app/rollback.go:361 ./cli/app/upgrade.go:470
|
#: ./cli/app/deploy.go:411 ./cli/app/new.go:373 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469
|
||||||
msgid "D"
|
msgid "D"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1469,7 +1469,7 @@ msgid "To load completions:\n"
|
|||||||
" # and source this file from your PowerShell profile."
|
" # and source this file from your PowerShell profile."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:436 ./cli/app/rollback.go:377 ./cli/app/upgrade.go:494
|
#: ./cli/app/deploy.go:435 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493
|
||||||
msgid "U"
|
msgid "U"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1659,7 +1659,7 @@ msgid "\n"
|
|||||||
"lint %s: %s"
|
"lint %s: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:121
|
#: ./pkg/ui/deploy.go:132
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "^%s"
|
msgid "^%s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1676,7 +1676,7 @@ msgctxt "app backup list"
|
|||||||
msgid "a"
|
msgid "a"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/restart.go:170
|
#: ./cli/app/restart.go:169
|
||||||
msgctxt "app restart"
|
msgctxt "app restart"
|
||||||
msgid "a"
|
msgid "a"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1785,7 +1785,7 @@ msgstr ""
|
|||||||
msgid "all tasks reached terminal state"
|
msgid "all tasks reached terminal state"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/restart.go:169
|
#: ./cli/app/restart.go:168
|
||||||
msgid "all-services"
|
msgid "all-services"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1844,7 +1844,7 @@ msgstr ""
|
|||||||
msgid "attempting to run %s"
|
msgid "attempting to run %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:273 ./cli/app/upgrade.go:296
|
#: ./cli/app/deploy.go:272 ./cli/app/upgrade.go:295
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "attempting to run post deploy commands, saw: %s"
|
msgid "attempting to run post deploy commands, saw: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1854,7 +1854,7 @@ msgstr ""
|
|||||||
msgid "attempting to scale %s to 0"
|
msgid "attempting to scale %s to 0"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/restart.go:141
|
#: ./cli/app/restart.go:140
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "attempting to scale %s to 1"
|
msgid "attempting to scale %s to 1"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1924,7 +1924,7 @@ msgstr ""
|
|||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#. translators: `abra app cp` aliases. use a comma separated list of aliases with
|
#. translators: `abra app cp` aliases. use a comma separated list of aliases with
|
||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:420 ./cli/app/rollback.go:369 ./cli/app/upgrade.go:478
|
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:419 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477
|
||||||
msgid "c"
|
msgid "c"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -1965,7 +1965,7 @@ msgstr ""
|
|||||||
msgid "cannot find app with name %s"
|
msgid "cannot find app with name %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:668
|
#: ./pkg/upstream/stack/stack.go:657
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "cannot get label %s for %s"
|
msgid "cannot get label %s for %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1980,7 +1980,7 @@ msgstr ""
|
|||||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:370
|
#: ./cli/app/deploy.go:369
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
|
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
|
||||||
" to return to a regular release, specify a release tag, commit SHA or use \"--latest\""
|
" to return to a regular release, specify a release tag, commit SHA or use \"--latest\""
|
||||||
@ -1999,7 +1999,7 @@ msgstr ""
|
|||||||
msgid "cannot use '[secret] [version]' and '--all' together"
|
msgid "cannot use '[secret] [version]' and '--all' together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:312
|
#: ./cli/app/deploy.go:311
|
||||||
msgid "cannot use --chaos and --latest together"
|
msgid "cannot use --chaos and --latest together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2023,11 +2023,11 @@ msgstr ""
|
|||||||
msgid "cannot use [service] and --all-services/-a together"
|
msgid "cannot use [service] and --all-services/-a together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:304 ./cli/app/new.go:76
|
#: ./cli/app/deploy.go:303 ./cli/app/new.go:76
|
||||||
msgid "cannot use [version] and --chaos together"
|
msgid "cannot use [version] and --chaos together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:308
|
#: ./cli/app/deploy.go:307
|
||||||
msgid "cannot use [version] and --latest together"
|
msgid "cannot use [version] and --latest together"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2059,7 +2059,7 @@ msgstr ""
|
|||||||
msgid "cfg"
|
msgid "cfg"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:395 ./cli/app/labels.go:142 ./cli/app/new.go:396 ./cli/app/ps.go:212 ./cli/app/restart.go:162 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
|
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:394 ./cli/app/labels.go:142 ./cli/app/new.go:396 ./cli/app/ps.go:212 ./cli/app/restart.go:161 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
|
||||||
msgid "chaos"
|
msgid "chaos"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2068,7 +2068,7 @@ msgstr ""
|
|||||||
msgid "check <domain> [flags]"
|
msgid "check <domain> [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:94 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:442
|
#: ./cli/app/deploy.go:94 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:441
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "checking whether %s is already deployed"
|
msgid "checking whether %s is already deployed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2291,7 +2291,7 @@ msgstr ""
|
|||||||
msgid "create remote directory: %s"
|
msgid "create remote directory: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/client/client.go:111
|
#: ./pkg/client/client.go:102
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "created client for %s"
|
msgid "created client for %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2311,7 +2311,7 @@ msgstr ""
|
|||||||
msgid "created the %s context"
|
msgid "created the %s context"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:524
|
#: ./pkg/upstream/stack/stack.go:520
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "creating %s"
|
msgid "creating %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2326,12 +2326,12 @@ msgstr ""
|
|||||||
msgid "creating context with domain %s"
|
msgid "creating context with domain %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:426
|
#: ./pkg/upstream/stack/stack.go:422
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "creating network %s"
|
msgid "creating network %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:373
|
#: ./pkg/upstream/stack/stack.go:369
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "creating secret %s"
|
msgid "creating secret %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2350,7 +2350,7 @@ msgstr ""
|
|||||||
msgid "critical errors present in %s config"
|
msgid "critical errors present in %s config"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:299
|
#: ./cli/app/rollback.go:298
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "current deployment '%s' is not a known version for %s"
|
msgid "current deployment '%s' is not a known version for %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2390,11 +2390,11 @@ msgstr ""
|
|||||||
msgid "deploy <domain> [version] [flags]"
|
msgid "deploy <domain> [version] [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:604
|
#: ./pkg/upstream/stack/stack.go:593
|
||||||
msgid "deploy failed 🛑"
|
msgid "deploy failed 🛑"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:608
|
#: ./pkg/upstream/stack/stack.go:597
|
||||||
msgid "deploy in progress 🟠"
|
msgid "deploy in progress 🟠"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2402,15 +2402,15 @@ msgstr ""
|
|||||||
msgid "deploy labels stanza present"
|
msgid "deploy labels stanza present"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:430
|
#: ./cli/app/deploy.go:429
|
||||||
msgid "deploy latest recipe version"
|
msgid "deploy latest recipe version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:648
|
#: ./pkg/upstream/stack/stack.go:637
|
||||||
msgid "deploy succeeded 🟢"
|
msgid "deploy succeeded 🟢"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:606
|
#: ./pkg/upstream/stack/stack.go:595
|
||||||
msgid "deploy timed out 🟠"
|
msgid "deploy timed out 🟠"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2504,11 +2504,11 @@ msgstr ""
|
|||||||
msgid "dirty: %v, "
|
msgid "dirty: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:422 ./cli/app/rollback.go:371 ./cli/app/upgrade.go:480
|
#: ./cli/app/deploy.go:421 ./cli/app/rollback.go:370 ./cli/app/upgrade.go:479
|
||||||
msgid "disable converge logic checks"
|
msgid "disable converge logic checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:414 ./cli/app/rollback.go:363 ./cli/app/upgrade.go:472
|
#: ./cli/app/deploy.go:413 ./cli/app/rollback.go:362 ./cli/app/upgrade.go:471
|
||||||
msgid "disable public DNS checks"
|
msgid "disable public DNS checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2662,7 +2662,7 @@ msgstr ""
|
|||||||
msgid "env file for %s has issues: %s"
|
msgid "env file for %s has issues: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:83
|
#: ./pkg/ui/deploy.go:94
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "err: %v, "
|
msgid "err: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2726,7 +2726,7 @@ msgstr ""
|
|||||||
|
|
||||||
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: ./cli/app/deploy.go:404 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:353 ./cli/app/secret.go:593 ./cli/app/upgrade.go:462 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
|
#: ./cli/app/deploy.go:403 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:352 ./cli/app/secret.go:593 ./cli/app/upgrade.go:461 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
|
||||||
msgid "f"
|
msgid "f"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2760,22 +2760,22 @@ msgstr ""
|
|||||||
msgid "failed to copy %s from local machine to %s: output:%s err:%s"
|
msgid "failed to copy %s from local machine to %s: output:%s err:%s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:535
|
#: ./pkg/upstream/stack/stack.go:531
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to create %s"
|
msgid "failed to create %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:397
|
#: ./pkg/upstream/stack/stack.go:393
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to create config %s"
|
msgid "failed to create config %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:428
|
#: ./pkg/upstream/stack/stack.go:424
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to create network %s"
|
msgid "failed to create network %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:375
|
#: ./pkg/upstream/stack/stack.go:371
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to create secret %s"
|
msgid "failed to create secret %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2872,7 +2872,7 @@ msgstr ""
|
|||||||
msgid "failed to retrieve latest commit for %s: %s"
|
msgid "failed to retrieve latest commit for %s: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:472
|
#: ./pkg/upstream/stack/stack.go:468
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to retrieve registry auth for image %s: %s"
|
msgid "failed to retrieve registry auth for image %s: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2892,17 +2892,17 @@ msgstr ""
|
|||||||
msgid "failed to tag release: %s"
|
msgid "failed to tag release: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:512
|
#: ./pkg/upstream/stack/stack.go:508
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to update %s"
|
msgid "failed to update %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:391
|
#: ./pkg/upstream/stack/stack.go:387
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to update config %s"
|
msgid "failed to update config %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:369
|
#: ./pkg/upstream/stack/stack.go:365
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "failed to update secret %s"
|
msgid "failed to update secret %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -2957,7 +2957,7 @@ msgstr ""
|
|||||||
msgid "final merged env values for %s are: %s"
|
msgid "final merged env values for %s are: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:403 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:352 ./cli/app/upgrade.go:461 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
|
#: ./cli/app/deploy.go:402 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:351 ./cli/app/upgrade.go:460 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
|
||||||
msgid "force"
|
msgid "force"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3166,12 +3166,12 @@ msgstr ""
|
|||||||
msgid "i"
|
msgid "i"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:84
|
#: ./pkg/ui/deploy.go:95
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "id: %s, "
|
msgid "id: %s, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:398 ./cli/app/labels.go:145 ./cli/app/new.go:399 ./cli/app/ps.go:215 ./cli/app/restart.go:165 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
|
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:397 ./cli/app/labels.go:145 ./cli/app/new.go:399 ./cli/app/ps.go:215 ./cli/app/restart.go:164 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
|
||||||
msgid "ignore uncommitted recipes changes"
|
msgid "ignore uncommitted recipes changes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3282,7 +3282,7 @@ msgstr ""
|
|||||||
msgid "initialised new git repo in %s"
|
msgid "initialised new git repo in %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:207
|
#: ./pkg/upstream/stack/stack.go:206
|
||||||
msgid "initialising deployment"
|
msgid "initialising deployment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3346,7 +3346,7 @@ msgstr ""
|
|||||||
msgid "invalid npipe source, source cannot be empty"
|
msgid "invalid npipe source, source cannot be empty"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:241
|
#: ./pkg/upstream/stack/stack.go:239
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "invalid option %s for flag --resolve-image"
|
msgid "invalid option %s for flag --resolve-image"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3369,7 +3369,7 @@ msgstr ""
|
|||||||
#. no spaces in between
|
#. no spaces in between
|
||||||
#. translators: `abra recipe lint` aliases. use a comma separated list of
|
#. translators: `abra recipe lint` aliases. use a comma separated list of
|
||||||
#. aliases with no spaces in between
|
#. aliases with no spaces in between
|
||||||
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:428 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
|
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:427 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
|
||||||
msgid "l"
|
msgid "l"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3384,7 +3384,7 @@ msgstr ""
|
|||||||
msgid "labels <domain> [flags]"
|
msgid "labels <domain> [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:427 ./cli/app/list.go:182
|
#: ./cli/app/deploy.go:426 ./cli/app/list.go:182
|
||||||
msgid "latest"
|
msgid "latest"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3473,12 +3473,12 @@ msgstr ""
|
|||||||
msgid "logs <domain> [service] [flags]"
|
msgid "logs <domain> [service] [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:639
|
#: ./pkg/upstream/stack/stack.go:628
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "logs: %s"
|
msgid "logs: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:641
|
#: ./pkg/upstream/stack/stack.go:630
|
||||||
msgid "logs: no log output received from deployment"
|
msgid "logs: no log output received from deployment"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3605,7 +3605,7 @@ msgstr ""
|
|||||||
msgid "name a servce 'app'"
|
msgid "name a servce 'app'"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:85
|
#: ./pkg/ui/deploy.go:96
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "name: %s, "
|
msgid "name: %s, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3614,12 +3614,12 @@ msgstr ""
|
|||||||
msgid "need 3 or 4 arguments"
|
msgid "need 3 or 4 arguments"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:352
|
#: ./pkg/upstream/stack/stack.go:348
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy"
|
msgid "network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:356
|
#: ./pkg/upstream/stack/stack.go:352
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\""
|
msgid "network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3842,11 +3842,11 @@ msgstr ""
|
|||||||
msgid "no volumes to remove"
|
msgid "no volumes to remove"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:419 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477
|
#: ./cli/app/deploy.go:418 ./cli/app/rollback.go:367 ./cli/app/upgrade.go:476
|
||||||
msgid "no-converge-checks"
|
msgid "no-converge-checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:411 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469
|
#: ./cli/app/deploy.go:410 ./cli/app/rollback.go:359 ./cli/app/upgrade.go:468
|
||||||
msgid "no-domain-checks"
|
msgid "no-domain-checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3902,7 +3902,7 @@ msgstr ""
|
|||||||
msgid "only show errors"
|
msgid "only show errors"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:488
|
#: ./cli/app/upgrade.go:487
|
||||||
msgid "only show release notes"
|
msgid "only show release notes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3933,22 +3933,22 @@ msgstr ""
|
|||||||
msgid "parsed following command arguments: %s"
|
msgid "parsed following command arguments: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:345
|
#: ./cli/app/upgrade.go:344
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing chosen upgrade version failed: %s"
|
msgid "parsing chosen upgrade version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:389
|
#: ./cli/app/upgrade.go:388
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing deployed version failed: %s"
|
msgid "parsing deployed version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:350
|
#: ./cli/app/upgrade.go:349
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing deployment version failed: %s"
|
msgid "parsing deployment version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:356 ./cli/app/upgrade.go:395
|
#: ./cli/app/upgrade.go:355 ./cli/app/upgrade.go:394
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "parsing recipe version failed: %s"
|
msgid "parsing recipe version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3973,7 +3973,7 @@ msgstr ""
|
|||||||
msgid "pattern"
|
msgid "pattern"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:406 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:355 ./cli/app/upgrade.go:464 ./cli/app/volume.go:219
|
#: ./cli/app/deploy.go:405 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:354 ./cli/app/upgrade.go:463 ./cli/app/volume.go:219
|
||||||
msgid "perform action without further prompt"
|
msgid "perform action without further prompt"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -3988,27 +3988,27 @@ msgstr ""
|
|||||||
msgid "please fix your synced label for %s and re-run this command"
|
msgid "please fix your synced label for %s and re-run this command"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:267
|
#: ./cli/app/rollback.go:266
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select a downgrade (version: %s):"
|
msgid "please select a downgrade (version: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/rollback.go:272
|
#: ./cli/app/rollback.go:271
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select a downgrade (version: %s, chaos: %s):"
|
msgid "please select a downgrade (version: %s, chaos: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:312
|
#: ./cli/app/upgrade.go:311
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select an upgrade (version: %s):"
|
msgid "please select an upgrade (version: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:317
|
#: ./cli/app/upgrade.go:316
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "please select an upgrade (version: %s, chaos: %s):"
|
msgid "please select an upgrade (version: %s, chaos: %s):"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:587
|
#: ./pkg/upstream/stack/stack.go:576
|
||||||
msgid "polling deployment status"
|
msgid "polling deployment status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4094,7 +4094,7 @@ msgstr ""
|
|||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#. translators: `abra recipe` aliases. use a comma separated list of aliases
|
#. translators: `abra recipe` aliases. use a comma separated list of aliases
|
||||||
#. with no spaces in between
|
#. with no spaces in between
|
||||||
#: ./cli/app/backup.go:327 ./cli/app/list.go:300 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:486 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:649 ./cli/recipe/sync.go:272
|
#: ./cli/app/backup.go:327 ./cli/app/list.go:300 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:485 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:649 ./cli/recipe/sync.go:272
|
||||||
msgid "r"
|
msgid "r"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4147,7 +4147,7 @@ msgstr ""
|
|||||||
msgid "read v:%s k: %s"
|
msgid "read v:%s k: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:86
|
#: ./pkg/ui/deploy.go:97
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "reader: %v, "
|
msgid "reader: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -4210,7 +4210,7 @@ msgstr ""
|
|||||||
msgid "release <recipe> [version] [flags]"
|
msgid "release <recipe> [version] [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/upgrade.go:485
|
#: ./cli/app/upgrade.go:484
|
||||||
msgid "releasenotes"
|
msgid "releasenotes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4376,7 +4376,7 @@ msgstr ""
|
|||||||
msgid "restart <domain> [[service] | --all-services] [flags]"
|
msgid "restart <domain> [[service] | --all-services] [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/restart.go:172
|
#: ./cli/app/restart.go:171
|
||||||
msgid "restart all services"
|
msgid "restart all services"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4441,7 +4441,7 @@ msgstr ""
|
|||||||
msgid "retrieved versions from local recipe repository"
|
msgid "retrieved versions from local recipe repository"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:468
|
#: ./pkg/upstream/stack/stack.go:464
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "retrieving docker auth token: failed create docker cli: %s"
|
msgid "retrieving docker auth token: failed create docker cli: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -4475,7 +4475,7 @@ msgstr ""
|
|||||||
msgid "rollback <domain> [version] [flags]"
|
msgid "rollback <domain> [version] [flags]"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:336
|
#: ./pkg/ui/deploy.go:398
|
||||||
msgid "rolled back"
|
msgid "rolled back"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4514,7 +4514,7 @@ msgstr ""
|
|||||||
msgid "run command locally"
|
msgid "run command locally"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:271 ./cli/app/upgrade.go:293
|
#: ./cli/app/deploy.go:270 ./cli/app/upgrade.go:292
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "run the following post-deploy commands: %s"
|
msgid "run the following post-deploy commands: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -4599,12 +4599,12 @@ msgstr ""
|
|||||||
msgid "secret not found: %s"
|
msgid "secret not found: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:340
|
#: ./cli/app/deploy.go:339
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secret not generated: %s"
|
msgid "secret not generated: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:338
|
#: ./cli/app/deploy.go:337
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "secret not inserted (#generate=false): %s"
|
msgid "secret not inserted (#generate=false): %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -4651,19 +4651,9 @@ msgstr ""
|
|||||||
msgid "server doesn't exist?"
|
msgid "server doesn't exist?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/client/client.go:54
|
|
||||||
#, c-format
|
|
||||||
msgid "server missing context, context creation failed: %s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ./pkg/client/client.go:59
|
|
||||||
#, c-format
|
|
||||||
msgid "server missing context, run \"abra server add %s\"?"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ./pkg/client/client.go:48
|
#: ./pkg/client/client.go:48
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "server missing, run \"abra server add %s\"?"
|
msgid "server missing context, run \"abra server add %s\"?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/server/add.go:148
|
#: ./cli/server/add.go:148
|
||||||
@ -4738,7 +4728,7 @@ msgstr ""
|
|||||||
msgid "severity"
|
msgid "severity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:438 ./cli/app/rollback.go:379 ./cli/app/upgrade.go:496
|
#: ./cli/app/deploy.go:437 ./cli/app/rollback.go:378 ./cli/app/upgrade.go:495
|
||||||
msgid "show all configs & images, including unchanged ones"
|
msgid "show all configs & images, including unchanged ones"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4762,7 +4752,7 @@ msgstr ""
|
|||||||
msgid "show debug messages"
|
msgid "show debug messages"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:435 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493
|
#: ./cli/app/deploy.go:434 ./cli/app/rollback.go:375 ./cli/app/upgrade.go:492
|
||||||
msgid "show-unchanged"
|
msgid "show-unchanged"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4806,7 +4796,7 @@ msgstr ""
|
|||||||
msgid "skipping as requested, undeploy still in progress 🟠"
|
msgid "skipping as requested, undeploy still in progress 🟠"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:309
|
#: ./pkg/upstream/stack/stack.go:306
|
||||||
msgid "skipping converge logic checks"
|
msgid "skipping converge logic checks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4828,12 +4818,12 @@ msgstr ""
|
|||||||
msgid "skipping secret (because it already exists) on %s: %s"
|
msgid "skipping secret (because it already exists) on %s: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/app/app.go:697
|
#: ./pkg/app/app.go:692
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "skipping version %s write as already exists in %s.env"
|
msgid "skipping version %s write as already exists in %s.env"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/app/app.go:691
|
#: ./pkg/app/app.go:686
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "skipping writing version %s because dry run"
|
msgid "skipping writing version %s because dry run"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -4900,7 +4890,7 @@ msgstr ""
|
|||||||
msgid "status"
|
msgid "status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:88
|
#: ./pkg/ui/deploy.go:99
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "status: %s}"
|
msgid "status: %s}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -4922,7 +4912,7 @@ msgstr ""
|
|||||||
msgid "stripped %s to %s for parsing"
|
msgid "stripped %s to %s for parsing"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:333
|
#: ./pkg/ui/deploy.go:395
|
||||||
msgid "succeeded"
|
msgid "succeeded"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4941,12 +4931,12 @@ msgstr ""
|
|||||||
msgid "successfully created %s"
|
msgid "successfully created %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/client/client.go:120
|
#: ./pkg/client/client.go:111
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "swarm mode not enabled on %s?"
|
msgid "swarm mode not enabled on %s?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/client/client.go:123
|
#: ./pkg/client/client.go:114
|
||||||
msgid "swarm mode not enabled on local server?"
|
msgid "swarm mode not enabled on local server?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -5022,7 +5012,7 @@ msgstr ""
|
|||||||
msgid "timeout label: %s"
|
msgid "timeout label: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/remove.go:29 ./pkg/upstream/stack/stack.go:210
|
#: ./pkg/upstream/stack/remove.go:29 ./pkg/upstream/stack/stack.go:209
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "timeout: set to %d second(s)"
|
msgid "timeout: set to %d second(s)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5445,6 +5435,11 @@ msgstr ""
|
|||||||
msgid "unknown server %s, run \"abra server add %s\"?"
|
msgid "unknown server %s, run \"abra server add %s\"?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: ./pkg/client/client.go:51
|
||||||
|
#, c-format
|
||||||
|
msgid "unknown server, run \"abra server add %s\"?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/cp.go:259
|
#: ./cli/app/cp.go:259
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "untar: %s"
|
msgid "untar: %s"
|
||||||
@ -5456,7 +5451,7 @@ msgstr ""
|
|||||||
msgid "up"
|
msgid "up"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:477
|
#: ./pkg/upstream/stack/stack.go:473
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "updating %s"
|
msgid "updating %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5568,7 +5563,7 @@ msgstr ""
|
|||||||
msgid "version"
|
msgid "version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/app/app.go:695
|
#: ./pkg/app/app.go:690
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version %s saved to %s.env"
|
msgid "version %s saved to %s.env"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5587,10 +5582,6 @@ msgstr ""
|
|||||||
msgid "version for abra"
|
msgid "version for abra"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/app/app.go:637
|
|
||||||
msgid "version is unknown, skipping env write"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ./pkg/recipe/recipe.go:130
|
#: ./pkg/recipe/recipe.go:130
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version seems invalid: %s"
|
msgid "version seems invalid: %s"
|
||||||
@ -5601,27 +5592,27 @@ msgstr ""
|
|||||||
msgid "version wiped from %s.env"
|
msgid "version wiped from %s.env"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:354
|
#: ./cli/app/deploy.go:353
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking chaos version: %s"
|
msgid "version: taking chaos version: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:380
|
#: ./cli/app/deploy.go:379
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking deployed version: %s"
|
msgid "version: taking deployed version: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:385
|
#: ./cli/app/deploy.go:384
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking new recipe version: %s"
|
msgid "version: taking new recipe version: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:374
|
#: ./cli/app/deploy.go:373
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking version from .env file: %s"
|
msgid "version: taking version from .env file: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:360
|
#: ./cli/app/deploy.go:359
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "version: taking version from cli arg: %s"
|
msgid "version: taking version from cli arg: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5681,22 +5672,22 @@ msgstr ""
|
|||||||
msgid "volumes pruned: %d; space reclaimed: %s"
|
msgid "volumes pruned: %d; space reclaimed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:625
|
#: ./pkg/upstream/stack/stack.go:614
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "waitOnServices: error creating log dir: %s"
|
msgid "waitOnServices: error creating log dir: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:630
|
#: ./pkg/upstream/stack/stack.go:619
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "waitOnServices: error opening file: %s"
|
msgid "waitOnServices: error opening file: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:596
|
#: ./pkg/upstream/stack/stack.go:585
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "waitOnServices: error running TUI: %s"
|
msgid "waitOnServices: error running TUI: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/upstream/stack/stack.go:636
|
#: ./pkg/upstream/stack/stack.go:625
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "waitOnServices: writeFile: %s"
|
msgid "waitOnServices: writeFile: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5739,12 +5730,12 @@ msgstr ""
|
|||||||
msgid "wire up healthchecks"
|
msgid "wire up healthchecks"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:87
|
#: ./pkg/ui/deploy.go:98
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "writer: %v, "
|
msgid "writer: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./cli/app/deploy.go:278 ./cli/app/new.go:241 ./cli/app/rollback.go:256 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:301
|
#: ./cli/app/deploy.go:277 ./cli/app/new.go:241 ./cli/app/rollback.go:255 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:300
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "writing recipe version failed: %s"
|
msgid "writing recipe version failed: %s"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -5773,7 +5764,7 @@ msgstr ""
|
|||||||
msgid "z"
|
msgid "z"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ./pkg/ui/deploy.go:82
|
#: ./pkg/ui/deploy.go:93
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid "{decoder: %v, "
|
msgid "{decoder: %v, "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -11,6 +11,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
"coopcloud.tech/abra/pkg/i18n"
|
||||||
"coopcloud.tech/abra/pkg/logs"
|
"coopcloud.tech/abra/pkg/logs"
|
||||||
|
"github.com/charmbracelet/bubbles/viewport"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
"github.com/docker/cli/cli/command/service/progress"
|
"github.com/docker/cli/cli/command/service/progress"
|
||||||
containerTypes "github.com/docker/docker/api/types/container"
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
@ -41,6 +42,12 @@ type ServiceMeta struct {
|
|||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
statusMode = iota
|
||||||
|
logsMode = iota
|
||||||
|
errorsMode = iota
|
||||||
|
)
|
||||||
|
|
||||||
type Model struct {
|
type Model struct {
|
||||||
appName string
|
appName string
|
||||||
cl *dockerClient.Client
|
cl *dockerClient.Client
|
||||||
@ -49,6 +56,10 @@ type Model struct {
|
|||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
width int
|
width int
|
||||||
filters filters.Args
|
filters filters.Args
|
||||||
|
mode int
|
||||||
|
|
||||||
|
logsViewport viewport.Model
|
||||||
|
logsViewportReady bool
|
||||||
|
|
||||||
Streams *[]stream
|
Streams *[]stream
|
||||||
Logs *[]string
|
Logs *[]string
|
||||||
@ -236,7 +247,10 @@ func deployTimeout(m Model) tea.Msg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
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) {
|
switch msg := msg.(type) {
|
||||||
case tea.KeyMsg:
|
case tea.KeyMsg:
|
||||||
@ -244,11 +258,25 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case "ctrl+c", "q":
|
case "ctrl+c", "q":
|
||||||
m.Quit = true
|
m.Quit = true
|
||||||
return m, tea.Quit
|
return m, tea.Quit
|
||||||
|
case "s":
|
||||||
|
m.mode = statusMode
|
||||||
|
case "l":
|
||||||
|
m.mode = logsMode
|
||||||
|
case "e":
|
||||||
|
m.mode = errorsMode
|
||||||
}
|
}
|
||||||
|
|
||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
m.width = msg.Width
|
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:
|
case progressCompleteMsg:
|
||||||
if msg.failed {
|
if msg.failed {
|
||||||
m.Failed = true
|
m.Failed = true
|
||||||
@ -256,9 +284,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
m.count += 1
|
m.count += 1
|
||||||
|
|
||||||
if m.complete() {
|
// if m.complete() {
|
||||||
return m, tea.Quit
|
// return m, tea.Quit
|
||||||
}
|
// }
|
||||||
|
|
||||||
case timeoutMsg:
|
case timeoutMsg:
|
||||||
m.TimedOut = true
|
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...)
|
return m, tea.Batch(cmds...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m Model) View() string {
|
func (m Model) View() string {
|
||||||
body := strings.Builder{}
|
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 {
|
for _, stream := range *m.Streams {
|
||||||
split := strings.Split(stream.Name, "_")
|
split := strings.Split(stream.Name, "_")
|
||||||
short := split[len(split)-1]
|
short := split[len(split)-1]
|
||||||
|
|||||||
@ -201,7 +201,6 @@ func RunDeploy(
|
|||||||
appName string,
|
appName string,
|
||||||
serverName string,
|
serverName string,
|
||||||
dontWait bool,
|
dontWait bool,
|
||||||
noInput bool,
|
|
||||||
filters filters.Args,
|
filters filters.Args,
|
||||||
) error {
|
) error {
|
||||||
log.Info(i18n.G("initialising deployment"))
|
log.Info(i18n.G("initialising deployment"))
|
||||||
@ -227,7 +226,6 @@ func RunDeploy(
|
|||||||
appName,
|
appName,
|
||||||
serverName,
|
serverName,
|
||||||
dontWait,
|
dontWait,
|
||||||
noInput,
|
|
||||||
filters,
|
filters,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -250,7 +248,6 @@ func deployCompose(
|
|||||||
appName string,
|
appName string,
|
||||||
serverName string,
|
serverName string,
|
||||||
dontWait bool,
|
dontWait bool,
|
||||||
noInput bool,
|
|
||||||
filters filters.Args,
|
filters filters.Args,
|
||||||
) error {
|
) error {
|
||||||
namespace := convert.NewNamespace(opts.Namespace)
|
namespace := convert.NewNamespace(opts.Namespace)
|
||||||
@ -314,7 +311,6 @@ func deployCompose(
|
|||||||
Services: serviceIDs,
|
Services: serviceIDs,
|
||||||
AppName: appName,
|
AppName: appName,
|
||||||
ServerName: serverName,
|
ServerName: serverName,
|
||||||
NoInput: noInput,
|
|
||||||
Filters: filters,
|
Filters: filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,7 +561,6 @@ func timestamp() string {
|
|||||||
type WaitOpts struct {
|
type WaitOpts struct {
|
||||||
AppName string
|
AppName string
|
||||||
Filters filters.Args
|
Filters filters.Args
|
||||||
NoInput bool
|
|
||||||
NoLog bool
|
NoLog bool
|
||||||
Quiet bool
|
Quiet bool
|
||||||
ServerName string
|
ServerName string
|
||||||
@ -575,13 +570,7 @@ type WaitOpts struct {
|
|||||||
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
|
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
|
||||||
timeout := time.Duration(WaitTimeout) * time.Second
|
timeout := time.Duration(WaitTimeout) * time.Second
|
||||||
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
|
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
|
||||||
|
tui := tea.NewProgram(model)
|
||||||
var tui *tea.Program
|
|
||||||
if opts.NoInput {
|
|
||||||
tui = tea.NewProgram(model, tea.WithoutRenderer(), tea.WithInput(nil))
|
|
||||||
} else {
|
|
||||||
tui = tea.NewProgram(model)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !opts.Quiet {
|
if !opts.Quiet {
|
||||||
log.Info(i18n.G("polling deployment status"))
|
log.Info(i18n.G("polling deployment status"))
|
||||||
|
|||||||
@ -14,15 +14,15 @@ done
|
|||||||
|
|
||||||
function show_banner {
|
function show_banner {
|
||||||
echo ""
|
echo ""
|
||||||
echo " ____ ____ _ _ "
|
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 ""
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
|
|||||||
@ -577,3 +577,18 @@ teardown(){
|
|||||||
assert_success
|
assert_success
|
||||||
refute_output --partial "IMAGES"
|
refute_output --partial "IMAGES"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# bats test_tags=slow
|
||||||
|
@test "manually created server without context bails gracefully" {
|
||||||
|
run mkdir -p "$ABRA_DIR/servers/default2"
|
||||||
|
assert_success
|
||||||
|
assert_exists "$ABRA_DIR/servers/default2"
|
||||||
|
|
||||||
|
run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
|
||||||
|
assert_success
|
||||||
|
assert_exists "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
|
||||||
|
|
||||||
|
run $ABRA app deploy "$TEST_APP_DOMAIN_2" --no-input --no-converge-checks
|
||||||
|
assert_failure
|
||||||
|
assert_output --partial "server missing context"
|
||||||
|
}
|
||||||
|
|||||||
@ -160,6 +160,23 @@ teardown(){
|
|||||||
assert_not_exists "$ABRA_DIR/servers/foo.com"
|
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
|
# bats test_tags=slow
|
||||||
@test "list does not fail if missing .env" {
|
@test "list does not fail if missing .env" {
|
||||||
_deploy_app
|
_deploy_app
|
||||||
|
|||||||
22
vendor/github.com/atotto/clipboard/.travis.yml
generated
vendored
22
vendor/github.com/atotto/clipboard/.travis.yml
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
- windows
|
|
||||||
|
|
||||||
go:
|
|
||||||
- go1.13.x
|
|
||||||
- go1.x
|
|
||||||
|
|
||||||
services:
|
|
||||||
- xvfb
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- export DISPLAY=:99.0
|
|
||||||
|
|
||||||
script:
|
|
||||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi
|
|
||||||
- go test -v .
|
|
||||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi
|
|
||||||
- go test -v .
|
|
||||||
27
vendor/github.com/atotto/clipboard/LICENSE
generated
vendored
27
vendor/github.com/atotto/clipboard/LICENSE
generated
vendored
@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2013 Ato Araki. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of @atotto. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
48
vendor/github.com/atotto/clipboard/README.md
generated
vendored
48
vendor/github.com/atotto/clipboard/README.md
generated
vendored
@ -1,48 +0,0 @@
|
|||||||
[](https://travis-ci.org/atotto/clipboard)
|
|
||||||
|
|
||||||
[](http://godoc.org/github.com/atotto/clipboard)
|
|
||||||
|
|
||||||
# Clipboard for Go
|
|
||||||
|
|
||||||
Provide copying and pasting to the Clipboard for Go.
|
|
||||||
|
|
||||||
Build:
|
|
||||||
|
|
||||||
$ go get github.com/atotto/clipboard
|
|
||||||
|
|
||||||
Platforms:
|
|
||||||
|
|
||||||
* OSX
|
|
||||||
* Windows 7 (probably work on other Windows)
|
|
||||||
* Linux, Unix (requires 'xclip' or 'xsel' command to be installed)
|
|
||||||
|
|
||||||
|
|
||||||
Document:
|
|
||||||
|
|
||||||
* http://godoc.org/github.com/atotto/clipboard
|
|
||||||
|
|
||||||
Notes:
|
|
||||||
|
|
||||||
* Text string only
|
|
||||||
* UTF-8 text encoding only (no conversion)
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
|
|
||||||
* Clipboard watcher(?)
|
|
||||||
|
|
||||||
## Commands:
|
|
||||||
|
|
||||||
paste shell command:
|
|
||||||
|
|
||||||
$ go get github.com/atotto/clipboard/cmd/gopaste
|
|
||||||
$ # example:
|
|
||||||
$ gopaste > document.txt
|
|
||||||
|
|
||||||
copy shell command:
|
|
||||||
|
|
||||||
$ go get github.com/atotto/clipboard/cmd/gocopy
|
|
||||||
$ # example:
|
|
||||||
$ cat document.txt | gocopy
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
20
vendor/github.com/atotto/clipboard/clipboard.go
generated
vendored
20
vendor/github.com/atotto/clipboard/clipboard.go
generated
vendored
@ -1,20 +0,0 @@
|
|||||||
// Copyright 2013 @atotto. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package clipboard read/write on clipboard
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
// ReadAll read string from clipboard
|
|
||||||
func ReadAll() (string, error) {
|
|
||||||
return readAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteAll write string to clipboard
|
|
||||||
func WriteAll(text string) error {
|
|
||||||
return writeAll(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsupported might be set true during clipboard init, to help callers decide
|
|
||||||
// whether or not to offer clipboard options.
|
|
||||||
var Unsupported bool
|
|
||||||
52
vendor/github.com/atotto/clipboard/clipboard_darwin.go
generated
vendored
52
vendor/github.com/atotto/clipboard/clipboard_darwin.go
generated
vendored
@ -1,52 +0,0 @@
|
|||||||
// Copyright 2013 @atotto. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build darwin
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
pasteCmdArgs = "pbpaste"
|
|
||||||
copyCmdArgs = "pbcopy"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getPasteCommand() *exec.Cmd {
|
|
||||||
return exec.Command(pasteCmdArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCopyCommand() *exec.Cmd {
|
|
||||||
return exec.Command(copyCmdArgs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAll() (string, error) {
|
|
||||||
pasteCmd := getPasteCommand()
|
|
||||||
out, err := pasteCmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(out), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeAll(text string) error {
|
|
||||||
copyCmd := getCopyCommand()
|
|
||||||
in, err := copyCmd.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := copyCmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := in.Write([]byte(text)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := in.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return copyCmd.Wait()
|
|
||||||
}
|
|
||||||
42
vendor/github.com/atotto/clipboard/clipboard_plan9.go
generated
vendored
42
vendor/github.com/atotto/clipboard/clipboard_plan9.go
generated
vendored
@ -1,42 +0,0 @@
|
|||||||
// Copyright 2013 @atotto. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build plan9
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"io/ioutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readAll() (string, error) {
|
|
||||||
f, err := os.Open("/dev/snarf")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
str, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(str), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeAll(text string) error {
|
|
||||||
f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
_, err = f.Write([]byte(text))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
149
vendor/github.com/atotto/clipboard/clipboard_unix.go
generated
vendored
149
vendor/github.com/atotto/clipboard/clipboard_unix.go
generated
vendored
@ -1,149 +0,0 @@
|
|||||||
// Copyright 2013 @atotto. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build freebsd linux netbsd openbsd solaris dragonfly
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
xsel = "xsel"
|
|
||||||
xclip = "xclip"
|
|
||||||
powershellExe = "powershell.exe"
|
|
||||||
clipExe = "clip.exe"
|
|
||||||
wlcopy = "wl-copy"
|
|
||||||
wlpaste = "wl-paste"
|
|
||||||
termuxClipboardGet = "termux-clipboard-get"
|
|
||||||
termuxClipboardSet = "termux-clipboard-set"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Primary bool
|
|
||||||
trimDos bool
|
|
||||||
|
|
||||||
pasteCmdArgs []string
|
|
||||||
copyCmdArgs []string
|
|
||||||
|
|
||||||
xselPasteArgs = []string{xsel, "--output", "--clipboard"}
|
|
||||||
xselCopyArgs = []string{xsel, "--input", "--clipboard"}
|
|
||||||
|
|
||||||
xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"}
|
|
||||||
xclipCopyArgs = []string{xclip, "-in", "-selection", "clipboard"}
|
|
||||||
|
|
||||||
powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"}
|
|
||||||
clipExeCopyArgs = []string{clipExe}
|
|
||||||
|
|
||||||
wlpasteArgs = []string{wlpaste, "--no-newline"}
|
|
||||||
wlcopyArgs = []string{wlcopy}
|
|
||||||
|
|
||||||
termuxPasteArgs = []string{termuxClipboardGet}
|
|
||||||
termuxCopyArgs = []string{termuxClipboardSet}
|
|
||||||
|
|
||||||
missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.")
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if os.Getenv("WAYLAND_DISPLAY") != "" {
|
|
||||||
pasteCmdArgs = wlpasteArgs
|
|
||||||
copyCmdArgs = wlcopyArgs
|
|
||||||
|
|
||||||
if _, err := exec.LookPath(wlcopy); err == nil {
|
|
||||||
if _, err := exec.LookPath(wlpaste); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pasteCmdArgs = xclipPasteArgs
|
|
||||||
copyCmdArgs = xclipCopyArgs
|
|
||||||
|
|
||||||
if _, err := exec.LookPath(xclip); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pasteCmdArgs = xselPasteArgs
|
|
||||||
copyCmdArgs = xselCopyArgs
|
|
||||||
|
|
||||||
if _, err := exec.LookPath(xsel); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pasteCmdArgs = termuxPasteArgs
|
|
||||||
copyCmdArgs = termuxCopyArgs
|
|
||||||
|
|
||||||
if _, err := exec.LookPath(termuxClipboardSet); err == nil {
|
|
||||||
if _, err := exec.LookPath(termuxClipboardGet); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pasteCmdArgs = powershellExePasteArgs
|
|
||||||
copyCmdArgs = clipExeCopyArgs
|
|
||||||
trimDos = true
|
|
||||||
|
|
||||||
if _, err := exec.LookPath(clipExe); err == nil {
|
|
||||||
if _, err := exec.LookPath(powershellExe); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Unsupported = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPasteCommand() *exec.Cmd {
|
|
||||||
if Primary {
|
|
||||||
pasteCmdArgs = pasteCmdArgs[:1]
|
|
||||||
}
|
|
||||||
return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCopyCommand() *exec.Cmd {
|
|
||||||
if Primary {
|
|
||||||
copyCmdArgs = copyCmdArgs[:1]
|
|
||||||
}
|
|
||||||
return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAll() (string, error) {
|
|
||||||
if Unsupported {
|
|
||||||
return "", missingCommands
|
|
||||||
}
|
|
||||||
pasteCmd := getPasteCommand()
|
|
||||||
out, err := pasteCmd.Output()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
result := string(out)
|
|
||||||
if trimDos && len(result) > 1 {
|
|
||||||
result = result[:len(result)-2]
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeAll(text string) error {
|
|
||||||
if Unsupported {
|
|
||||||
return missingCommands
|
|
||||||
}
|
|
||||||
copyCmd := getCopyCommand()
|
|
||||||
in, err := copyCmd.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := copyCmd.Start(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := in.Write([]byte(text)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := in.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return copyCmd.Wait()
|
|
||||||
}
|
|
||||||
157
vendor/github.com/atotto/clipboard/clipboard_windows.go
generated
vendored
157
vendor/github.com/atotto/clipboard/clipboard_windows.go
generated
vendored
@ -1,157 +0,0 @@
|
|||||||
// Copyright 2013 @atotto. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package clipboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
cfUnicodetext = 13
|
|
||||||
gmemMoveable = 0x0002
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
user32 = syscall.MustLoadDLL("user32")
|
|
||||||
isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
|
|
||||||
openClipboard = user32.MustFindProc("OpenClipboard")
|
|
||||||
closeClipboard = user32.MustFindProc("CloseClipboard")
|
|
||||||
emptyClipboard = user32.MustFindProc("EmptyClipboard")
|
|
||||||
getClipboardData = user32.MustFindProc("GetClipboardData")
|
|
||||||
setClipboardData = user32.MustFindProc("SetClipboardData")
|
|
||||||
|
|
||||||
kernel32 = syscall.NewLazyDLL("kernel32")
|
|
||||||
globalAlloc = kernel32.NewProc("GlobalAlloc")
|
|
||||||
globalFree = kernel32.NewProc("GlobalFree")
|
|
||||||
globalLock = kernel32.NewProc("GlobalLock")
|
|
||||||
globalUnlock = kernel32.NewProc("GlobalUnlock")
|
|
||||||
lstrcpy = kernel32.NewProc("lstrcpyW")
|
|
||||||
)
|
|
||||||
|
|
||||||
// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
|
|
||||||
func waitOpenClipboard() error {
|
|
||||||
started := time.Now()
|
|
||||||
limit := started.Add(time.Second)
|
|
||||||
var r uintptr
|
|
||||||
var err error
|
|
||||||
for time.Now().Before(limit) {
|
|
||||||
r, _, err = openClipboard.Call(0)
|
|
||||||
if r != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
time.Sleep(time.Millisecond)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func readAll() (string, error) {
|
|
||||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
|
||||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err := waitOpenClipboard()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
h, _, err := getClipboardData.Call(cfUnicodetext)
|
|
||||||
if h == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
l, _, err := globalLock.Call(h)
|
|
||||||
if l == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
|
|
||||||
|
|
||||||
r, _, err := globalUnlock.Call(h)
|
|
||||||
if r == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
closed, _, err := closeClipboard.Call()
|
|
||||||
if closed == 0 {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return text, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeAll(text string) error {
|
|
||||||
// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution).
|
|
||||||
// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock.
|
|
||||||
runtime.LockOSThread()
|
|
||||||
defer runtime.UnlockOSThread()
|
|
||||||
|
|
||||||
err := waitOpenClipboard()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err := emptyClipboard.Call(0)
|
|
||||||
if r == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
data := syscall.StringToUTF16(text)
|
|
||||||
|
|
||||||
// "If the hMem parameter identifies a memory object, the object must have
|
|
||||||
// been allocated using the function with the GMEM_MOVEABLE flag."
|
|
||||||
h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
|
||||||
if h == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if h != 0 {
|
|
||||||
globalFree.Call(h)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
l, _, err := globalLock.Call(h)
|
|
||||||
if l == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
|
|
||||||
if r == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err = globalUnlock.Call(h)
|
|
||||||
if r == 0 {
|
|
||||||
if err.(syscall.Errno) != 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _, err = setClipboardData.Call(cfUnicodetext, h)
|
|
||||||
if r == 0 {
|
|
||||||
_, _, _ = closeClipboard.Call()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
h = 0 // suppress deferred cleanup
|
|
||||||
closed, _, err := closeClipboard.Call()
|
|
||||||
if closed == 0 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
219
vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
generated
vendored
219
vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
generated
vendored
@ -1,219 +0,0 @@
|
|||||||
// Package cursor provides cursor functionality for Bubble Tea applications.
|
|
||||||
package cursor
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
const defaultBlinkSpeed = time.Millisecond * 530
|
|
||||||
|
|
||||||
// initialBlinkMsg initializes cursor blinking.
|
|
||||||
type initialBlinkMsg struct{}
|
|
||||||
|
|
||||||
// BlinkMsg signals that the cursor should blink. It contains metadata that
|
|
||||||
// allows us to tell if the blink message is the one we're expecting.
|
|
||||||
type BlinkMsg struct {
|
|
||||||
id int
|
|
||||||
tag int
|
|
||||||
}
|
|
||||||
|
|
||||||
// blinkCanceled is sent when a blink operation is canceled.
|
|
||||||
type blinkCanceled struct{}
|
|
||||||
|
|
||||||
// blinkCtx manages cursor blinking.
|
|
||||||
type blinkCtx struct {
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode describes the behavior of the cursor.
|
|
||||||
type Mode int
|
|
||||||
|
|
||||||
// Available cursor modes.
|
|
||||||
const (
|
|
||||||
CursorBlink Mode = iota
|
|
||||||
CursorStatic
|
|
||||||
CursorHide
|
|
||||||
)
|
|
||||||
|
|
||||||
// String returns the cursor mode in a human-readable format. This method is
|
|
||||||
// provisional and for informational purposes only.
|
|
||||||
func (c Mode) String() string {
|
|
||||||
return [...]string{
|
|
||||||
"blink",
|
|
||||||
"static",
|
|
||||||
"hidden",
|
|
||||||
}[c]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model is the Bubble Tea model for this cursor element.
|
|
||||||
type Model struct {
|
|
||||||
BlinkSpeed time.Duration
|
|
||||||
// Style for styling the cursor block.
|
|
||||||
Style lipgloss.Style
|
|
||||||
// TextStyle is the style used for the cursor when it is hidden (when blinking).
|
|
||||||
// I.e. displaying normal text.
|
|
||||||
TextStyle lipgloss.Style
|
|
||||||
|
|
||||||
// char is the character under the cursor
|
|
||||||
char string
|
|
||||||
// The ID of this Model as it relates to other cursors
|
|
||||||
id int
|
|
||||||
// focus indicates whether the containing input is focused
|
|
||||||
focus bool
|
|
||||||
// Cursor Blink state.
|
|
||||||
Blink bool
|
|
||||||
// Used to manage cursor blink
|
|
||||||
blinkCtx *blinkCtx
|
|
||||||
// The ID of the blink message we're expecting to receive.
|
|
||||||
blinkTag int
|
|
||||||
// mode determines the behavior of the cursor
|
|
||||||
mode Mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new model with default settings.
|
|
||||||
func New() Model {
|
|
||||||
return Model{
|
|
||||||
BlinkSpeed: defaultBlinkSpeed,
|
|
||||||
|
|
||||||
Blink: true,
|
|
||||||
mode: CursorBlink,
|
|
||||||
|
|
||||||
blinkCtx: &blinkCtx{
|
|
||||||
ctx: context.Background(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates the cursor.
|
|
||||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case initialBlinkMsg:
|
|
||||||
// We accept all initialBlinkMsgs generated by the Blink command.
|
|
||||||
|
|
||||||
if m.mode != CursorBlink || !m.focus {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := m.BlinkCmd()
|
|
||||||
return m, cmd
|
|
||||||
|
|
||||||
case tea.FocusMsg:
|
|
||||||
return m, m.Focus()
|
|
||||||
|
|
||||||
case tea.BlurMsg:
|
|
||||||
m.Blur()
|
|
||||||
return m, nil
|
|
||||||
|
|
||||||
case BlinkMsg:
|
|
||||||
// We're choosy about whether to accept blinkMsgs so that our cursor
|
|
||||||
// only exactly when it should.
|
|
||||||
|
|
||||||
// Is this model blink-able?
|
|
||||||
if m.mode != CursorBlink || !m.focus {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Were we expecting this blink message?
|
|
||||||
if msg.id != m.id || msg.tag != m.blinkTag {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmd tea.Cmd
|
|
||||||
if m.mode == CursorBlink {
|
|
||||||
m.Blink = !m.Blink
|
|
||||||
cmd = m.BlinkCmd()
|
|
||||||
}
|
|
||||||
return m, cmd
|
|
||||||
|
|
||||||
case blinkCanceled: // no-op
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode returns the model's cursor mode. For available cursor modes, see
|
|
||||||
// type Mode.
|
|
||||||
func (m Model) Mode() Mode {
|
|
||||||
return m.mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMode sets the model's cursor mode. This method returns a command.
|
|
||||||
//
|
|
||||||
// For available cursor modes, see type CursorMode.
|
|
||||||
func (m *Model) SetMode(mode Mode) tea.Cmd {
|
|
||||||
// Adjust the mode value if it's value is out of range
|
|
||||||
if mode < CursorBlink || mode > CursorHide {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.mode = mode
|
|
||||||
m.Blink = m.mode == CursorHide || !m.focus
|
|
||||||
if mode == CursorBlink {
|
|
||||||
return Blink
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlinkCmd is a command used to manage cursor blinking.
|
|
||||||
func (m *Model) BlinkCmd() tea.Cmd {
|
|
||||||
if m.mode != CursorBlink {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.blinkCtx != nil && m.blinkCtx.cancel != nil {
|
|
||||||
m.blinkCtx.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed)
|
|
||||||
m.blinkCtx.cancel = cancel
|
|
||||||
|
|
||||||
m.blinkTag++
|
|
||||||
|
|
||||||
return func() tea.Msg {
|
|
||||||
defer cancel()
|
|
||||||
<-ctx.Done()
|
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
return BlinkMsg{id: m.id, tag: m.blinkTag}
|
|
||||||
}
|
|
||||||
return blinkCanceled{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blink is a command used to initialize cursor blinking.
|
|
||||||
func Blink() tea.Msg {
|
|
||||||
return initialBlinkMsg{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus focuses the cursor to allow it to blink if desired.
|
|
||||||
func (m *Model) Focus() tea.Cmd {
|
|
||||||
m.focus = true
|
|
||||||
m.Blink = m.mode == CursorHide // show the cursor unless we've explicitly hidden it
|
|
||||||
|
|
||||||
if m.mode == CursorBlink && m.focus {
|
|
||||||
return m.BlinkCmd()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blur blurs the cursor.
|
|
||||||
func (m *Model) Blur() {
|
|
||||||
m.focus = false
|
|
||||||
m.Blink = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetChar sets the character under the cursor.
|
|
||||||
func (m *Model) SetChar(char string) {
|
|
||||||
m.char = char
|
|
||||||
}
|
|
||||||
|
|
||||||
// View displays the cursor.
|
|
||||||
func (m Model) View() string {
|
|
||||||
if m.Blink {
|
|
||||||
return m.TextStyle.Inline(true).Render(m.char)
|
|
||||||
}
|
|
||||||
return m.Style.Inline(true).Reverse(true).Render(m.char)
|
|
||||||
}
|
|
||||||
102
vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
generated
vendored
102
vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
generated
vendored
@ -1,102 +0,0 @@
|
|||||||
// Package runeutil provides a utility function for use in Bubbles
|
|
||||||
// that can process Key messages containing runes.
|
|
||||||
package runeutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sanitizer is a helper for bubble widgets that want to process
|
|
||||||
// Runes from input key messages.
|
|
||||||
type Sanitizer interface {
|
|
||||||
// Sanitize removes control characters from runes in a KeyRunes
|
|
||||||
// message, and optionally replaces newline/carriage return/tabs by a
|
|
||||||
// specified character.
|
|
||||||
//
|
|
||||||
// The rune array is modified in-place if possible. In that case, the
|
|
||||||
// returned slice is the original slice shortened after the control
|
|
||||||
// characters have been removed/translated.
|
|
||||||
Sanitize(runes []rune) []rune
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSanitizer constructs a rune sanitizer.
|
|
||||||
func NewSanitizer(opts ...Option) Sanitizer {
|
|
||||||
s := sanitizer{
|
|
||||||
replaceNewLine: []rune("\n"),
|
|
||||||
replaceTab: []rune(" "),
|
|
||||||
}
|
|
||||||
for _, o := range opts {
|
|
||||||
s = o(s)
|
|
||||||
}
|
|
||||||
return &s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option is the type of option that can be passed to Sanitize().
|
|
||||||
type Option func(sanitizer) sanitizer
|
|
||||||
|
|
||||||
// ReplaceTabs replaces tabs by the specified string.
|
|
||||||
func ReplaceTabs(tabRepl string) Option {
|
|
||||||
return func(s sanitizer) sanitizer {
|
|
||||||
s.replaceTab = []rune(tabRepl)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReplaceNewlines replaces newline characters by the specified string.
|
|
||||||
func ReplaceNewlines(nlRepl string) Option {
|
|
||||||
return func(s sanitizer) sanitizer {
|
|
||||||
s.replaceNewLine = []rune(nlRepl)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sanitizer) Sanitize(runes []rune) []rune {
|
|
||||||
// dstrunes are where we are storing the result.
|
|
||||||
dstrunes := runes[:0:len(runes)]
|
|
||||||
// copied indicates whether dstrunes is an alias of runes
|
|
||||||
// or a copy. We need a copy when dst moves past src.
|
|
||||||
// We use this as an optimization to avoid allocating
|
|
||||||
// a new rune slice in the common case where the output
|
|
||||||
// is smaller or equal to the input.
|
|
||||||
copied := false
|
|
||||||
|
|
||||||
for src := 0; src < len(runes); src++ {
|
|
||||||
r := runes[src]
|
|
||||||
switch {
|
|
||||||
case r == utf8.RuneError:
|
|
||||||
// skip
|
|
||||||
|
|
||||||
case r == '\r' || r == '\n':
|
|
||||||
if len(dstrunes)+len(s.replaceNewLine) > src && !copied {
|
|
||||||
dst := len(dstrunes)
|
|
||||||
dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine))
|
|
||||||
copy(dstrunes, runes[:dst])
|
|
||||||
copied = true
|
|
||||||
}
|
|
||||||
dstrunes = append(dstrunes, s.replaceNewLine...)
|
|
||||||
|
|
||||||
case r == '\t':
|
|
||||||
if len(dstrunes)+len(s.replaceTab) > src && !copied {
|
|
||||||
dst := len(dstrunes)
|
|
||||||
dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab))
|
|
||||||
copy(dstrunes, runes[:dst])
|
|
||||||
copied = true
|
|
||||||
}
|
|
||||||
dstrunes = append(dstrunes, s.replaceTab...)
|
|
||||||
|
|
||||||
case unicode.IsControl(r):
|
|
||||||
// Other control characters: skip.
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Keep the character.
|
|
||||||
dstrunes = append(dstrunes, runes[src])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dstrunes
|
|
||||||
}
|
|
||||||
|
|
||||||
type sanitizer struct {
|
|
||||||
replaceNewLine []rune
|
|
||||||
replaceTab []rune
|
|
||||||
}
|
|
||||||
224
vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
generated
vendored
224
vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
generated
vendored
@ -1,224 +0,0 @@
|
|||||||
// Package spinner provides a spinner component for Bubble Tea applications.
|
|
||||||
package spinner
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Internal ID management. Used during animating to ensure that frame messages
|
|
||||||
// are received only by spinner components that sent them.
|
|
||||||
var lastID int64
|
|
||||||
|
|
||||||
func nextID() int {
|
|
||||||
return int(atomic.AddInt64(&lastID, 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spinner is a set of frames used in animating the spinner.
|
|
||||||
type Spinner struct {
|
|
||||||
Frames []string
|
|
||||||
FPS time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some spinners to choose from. You could also make your own.
|
|
||||||
var (
|
|
||||||
Line = Spinner{
|
|
||||||
Frames: []string{"|", "/", "-", "\\"},
|
|
||||||
FPS: time.Second / 10, //nolint:mnd
|
|
||||||
}
|
|
||||||
Dot = Spinner{
|
|
||||||
Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "},
|
|
||||||
FPS: time.Second / 10, //nolint:mnd
|
|
||||||
}
|
|
||||||
MiniDot = Spinner{
|
|
||||||
Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"},
|
|
||||||
FPS: time.Second / 12, //nolint:mnd
|
|
||||||
}
|
|
||||||
Jump = Spinner{
|
|
||||||
Frames: []string{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"},
|
|
||||||
FPS: time.Second / 10, //nolint:mnd
|
|
||||||
}
|
|
||||||
Pulse = Spinner{
|
|
||||||
Frames: []string{"█", "▓", "▒", "░"},
|
|
||||||
FPS: time.Second / 8, //nolint:mnd
|
|
||||||
}
|
|
||||||
Points = Spinner{
|
|
||||||
Frames: []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●"},
|
|
||||||
FPS: time.Second / 7, //nolint:mnd
|
|
||||||
}
|
|
||||||
Globe = Spinner{
|
|
||||||
Frames: []string{"🌍", "🌎", "🌏"},
|
|
||||||
FPS: time.Second / 4, //nolint:mnd
|
|
||||||
}
|
|
||||||
Moon = Spinner{
|
|
||||||
Frames: []string{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"},
|
|
||||||
FPS: time.Second / 8, //nolint:mnd
|
|
||||||
}
|
|
||||||
Monkey = Spinner{
|
|
||||||
Frames: []string{"🙈", "🙉", "🙊"},
|
|
||||||
FPS: time.Second / 3, //nolint:mnd
|
|
||||||
}
|
|
||||||
Meter = Spinner{
|
|
||||||
Frames: []string{
|
|
||||||
"▱▱▱",
|
|
||||||
"▰▱▱",
|
|
||||||
"▰▰▱",
|
|
||||||
"▰▰▰",
|
|
||||||
"▰▰▱",
|
|
||||||
"▰▱▱",
|
|
||||||
"▱▱▱",
|
|
||||||
},
|
|
||||||
FPS: time.Second / 7, //nolint:mnd
|
|
||||||
}
|
|
||||||
Hamburger = Spinner{
|
|
||||||
Frames: []string{"☱", "☲", "☴", "☲"},
|
|
||||||
FPS: time.Second / 3, //nolint:mnd
|
|
||||||
}
|
|
||||||
Ellipsis = Spinner{
|
|
||||||
Frames: []string{"", ".", "..", "..."},
|
|
||||||
FPS: time.Second / 3, //nolint:mnd
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Model contains the state for the spinner. Use New to create new models
|
|
||||||
// rather than using Model as a struct literal.
|
|
||||||
type Model struct {
|
|
||||||
// Spinner settings to use. See type Spinner.
|
|
||||||
Spinner Spinner
|
|
||||||
|
|
||||||
// Style sets the styling for the spinner. Most of the time you'll just
|
|
||||||
// want foreground and background coloring, and potentially some padding.
|
|
||||||
//
|
|
||||||
// For an introduction to styling with Lip Gloss see:
|
|
||||||
// https://github.com/charmbracelet/lipgloss
|
|
||||||
Style lipgloss.Style
|
|
||||||
|
|
||||||
frame int
|
|
||||||
id int
|
|
||||||
tag int
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the spinner's unique ID.
|
|
||||||
func (m Model) ID() int {
|
|
||||||
return m.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a model with default values.
|
|
||||||
func New(opts ...Option) Model {
|
|
||||||
m := Model{
|
|
||||||
Spinner: Line,
|
|
||||||
id: nextID(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&m)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewModel returns a model with default values.
|
|
||||||
//
|
|
||||||
// Deprecated: use [New] instead.
|
|
||||||
var NewModel = New
|
|
||||||
|
|
||||||
// TickMsg indicates that the timer has ticked and we should render a frame.
|
|
||||||
type TickMsg struct {
|
|
||||||
Time time.Time
|
|
||||||
tag int
|
|
||||||
ID int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update is the Tea update function.
|
|
||||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case TickMsg:
|
|
||||||
// If an ID is set, and the ID doesn't belong to this spinner, reject
|
|
||||||
// the message.
|
|
||||||
if msg.ID > 0 && msg.ID != m.id {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a tag is set, and it's not the one we expect, reject the message.
|
|
||||||
// This prevents the spinner from receiving too many messages and
|
|
||||||
// thus spinning too fast.
|
|
||||||
if msg.tag > 0 && msg.tag != m.tag {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
m.frame++
|
|
||||||
if m.frame >= len(m.Spinner.Frames) {
|
|
||||||
m.frame = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
m.tag++
|
|
||||||
return m, m.tick(m.id, m.tag)
|
|
||||||
default:
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// View renders the model's view.
|
|
||||||
func (m Model) View() string {
|
|
||||||
if m.frame >= len(m.Spinner.Frames) {
|
|
||||||
return "(error)"
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.Style.Render(m.Spinner.Frames[m.frame])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tick is the command used to advance the spinner one frame. Use this command
|
|
||||||
// to effectively start the spinner.
|
|
||||||
func (m Model) Tick() tea.Msg {
|
|
||||||
return TickMsg{
|
|
||||||
// The time at which the tick occurred.
|
|
||||||
Time: time.Now(),
|
|
||||||
|
|
||||||
// The ID of the spinner that this message belongs to. This can be
|
|
||||||
// helpful when routing messages, however bear in mind that spinners
|
|
||||||
// will ignore messages that don't contain ID by default.
|
|
||||||
ID: m.id,
|
|
||||||
|
|
||||||
tag: m.tag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) tick(id, tag int) tea.Cmd {
|
|
||||||
return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg {
|
|
||||||
return TickMsg{
|
|
||||||
Time: t,
|
|
||||||
ID: id,
|
|
||||||
tag: tag,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tick is the command used to advance the spinner one frame. Use this command
|
|
||||||
// to effectively start the spinner.
|
|
||||||
//
|
|
||||||
// Deprecated: Use [Model.Tick] instead.
|
|
||||||
func Tick() tea.Msg {
|
|
||||||
return TickMsg{Time: time.Now()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Option is used to set options in New. For example:
|
|
||||||
//
|
|
||||||
// spinner := New(WithSpinner(Dot))
|
|
||||||
type Option func(*Model)
|
|
||||||
|
|
||||||
// WithSpinner is an option to set the spinner.
|
|
||||||
func WithSpinner(spinner Spinner) Option {
|
|
||||||
return func(m *Model) {
|
|
||||||
m.Spinner = spinner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStyle is an option to set the spinner style.
|
|
||||||
func WithStyle(style lipgloss.Style) Option {
|
|
||||||
return func(m *Model) {
|
|
||||||
m.Style = style
|
|
||||||
}
|
|
||||||
}
|
|
||||||
898
vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
generated
vendored
898
vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
generated
vendored
@ -1,898 +0,0 @@
|
|||||||
// Package textinput provides a text input component for Bubble Tea
|
|
||||||
// applications.
|
|
||||||
package textinput
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/atotto/clipboard"
|
|
||||||
"github.com/charmbracelet/bubbles/cursor"
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
|
||||||
"github.com/charmbracelet/bubbles/runeutil"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
rw "github.com/mattn/go-runewidth"
|
|
||||||
"github.com/rivo/uniseg"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Internal messages for clipboard operations.
|
|
||||||
type (
|
|
||||||
pasteMsg string
|
|
||||||
pasteErrMsg struct{ error }
|
|
||||||
)
|
|
||||||
|
|
||||||
// EchoMode sets the input behavior of the text input field.
|
|
||||||
type EchoMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// EchoNormal displays text as is. This is the default behavior.
|
|
||||||
EchoNormal EchoMode = iota
|
|
||||||
|
|
||||||
// EchoPassword displays the EchoCharacter mask instead of actual
|
|
||||||
// characters. This is commonly used for password fields.
|
|
||||||
EchoPassword
|
|
||||||
|
|
||||||
// EchoNone displays nothing as characters are entered. This is commonly
|
|
||||||
// seen for password fields on the command line.
|
|
||||||
EchoNone
|
|
||||||
)
|
|
||||||
|
|
||||||
// ValidateFunc is a function that returns an error if the input is invalid.
|
|
||||||
type ValidateFunc func(string) error
|
|
||||||
|
|
||||||
// KeyMap is the key bindings for different actions within the textinput.
|
|
||||||
type KeyMap struct {
|
|
||||||
CharacterForward key.Binding
|
|
||||||
CharacterBackward key.Binding
|
|
||||||
WordForward key.Binding
|
|
||||||
WordBackward key.Binding
|
|
||||||
DeleteWordBackward key.Binding
|
|
||||||
DeleteWordForward key.Binding
|
|
||||||
DeleteAfterCursor key.Binding
|
|
||||||
DeleteBeforeCursor key.Binding
|
|
||||||
DeleteCharacterBackward key.Binding
|
|
||||||
DeleteCharacterForward key.Binding
|
|
||||||
LineStart key.Binding
|
|
||||||
LineEnd key.Binding
|
|
||||||
Paste key.Binding
|
|
||||||
AcceptSuggestion key.Binding
|
|
||||||
NextSuggestion key.Binding
|
|
||||||
PrevSuggestion key.Binding
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultKeyMap is the default set of key bindings for navigating and acting
|
|
||||||
// upon the textinput.
|
|
||||||
var DefaultKeyMap = KeyMap{
|
|
||||||
CharacterForward: key.NewBinding(key.WithKeys("right", "ctrl+f")),
|
|
||||||
CharacterBackward: key.NewBinding(key.WithKeys("left", "ctrl+b")),
|
|
||||||
WordForward: key.NewBinding(key.WithKeys("alt+right", "ctrl+right", "alt+f")),
|
|
||||||
WordBackward: key.NewBinding(key.WithKeys("alt+left", "ctrl+left", "alt+b")),
|
|
||||||
DeleteWordBackward: key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w")),
|
|
||||||
DeleteWordForward: key.NewBinding(key.WithKeys("alt+delete", "alt+d")),
|
|
||||||
DeleteAfterCursor: key.NewBinding(key.WithKeys("ctrl+k")),
|
|
||||||
DeleteBeforeCursor: key.NewBinding(key.WithKeys("ctrl+u")),
|
|
||||||
DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h")),
|
|
||||||
DeleteCharacterForward: key.NewBinding(key.WithKeys("delete", "ctrl+d")),
|
|
||||||
LineStart: key.NewBinding(key.WithKeys("home", "ctrl+a")),
|
|
||||||
LineEnd: key.NewBinding(key.WithKeys("end", "ctrl+e")),
|
|
||||||
Paste: key.NewBinding(key.WithKeys("ctrl+v")),
|
|
||||||
AcceptSuggestion: key.NewBinding(key.WithKeys("tab")),
|
|
||||||
NextSuggestion: key.NewBinding(key.WithKeys("down", "ctrl+n")),
|
|
||||||
PrevSuggestion: key.NewBinding(key.WithKeys("up", "ctrl+p")),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model is the Bubble Tea model for this text input element.
|
|
||||||
type Model struct {
|
|
||||||
Err error
|
|
||||||
|
|
||||||
// General settings.
|
|
||||||
Prompt string
|
|
||||||
Placeholder string
|
|
||||||
EchoMode EchoMode
|
|
||||||
EchoCharacter rune
|
|
||||||
Cursor cursor.Model
|
|
||||||
|
|
||||||
// Deprecated: use [cursor.BlinkSpeed] instead.
|
|
||||||
BlinkSpeed time.Duration
|
|
||||||
|
|
||||||
// Styles. These will be applied as inline styles.
|
|
||||||
//
|
|
||||||
// For an introduction to styling with Lip Gloss see:
|
|
||||||
// https://github.com/charmbracelet/lipgloss
|
|
||||||
PromptStyle lipgloss.Style
|
|
||||||
TextStyle lipgloss.Style
|
|
||||||
PlaceholderStyle lipgloss.Style
|
|
||||||
CompletionStyle lipgloss.Style
|
|
||||||
|
|
||||||
// Deprecated: use Cursor.Style instead.
|
|
||||||
CursorStyle lipgloss.Style
|
|
||||||
|
|
||||||
// CharLimit is the maximum amount of characters this input element will
|
|
||||||
// accept. If 0 or less, there's no limit.
|
|
||||||
CharLimit int
|
|
||||||
|
|
||||||
// Width is the maximum number of characters that can be displayed at once.
|
|
||||||
// It essentially treats the text field like a horizontally scrolling
|
|
||||||
// viewport. If 0 or less this setting is ignored.
|
|
||||||
Width int
|
|
||||||
|
|
||||||
// KeyMap encodes the keybindings recognized by the widget.
|
|
||||||
KeyMap KeyMap
|
|
||||||
|
|
||||||
// Underlying text value.
|
|
||||||
value []rune
|
|
||||||
|
|
||||||
// focus indicates whether user input focus should be on this input
|
|
||||||
// component. When false, ignore keyboard input and hide the cursor.
|
|
||||||
focus bool
|
|
||||||
|
|
||||||
// Cursor position.
|
|
||||||
pos int
|
|
||||||
|
|
||||||
// Used to emulate a viewport when width is set and the content is
|
|
||||||
// overflowing.
|
|
||||||
offset int
|
|
||||||
offsetRight int
|
|
||||||
|
|
||||||
// Validate is a function that checks whether or not the text within the
|
|
||||||
// input is valid. If it is not valid, the `Err` field will be set to the
|
|
||||||
// error returned by the function. If the function is not defined, all
|
|
||||||
// input is considered valid.
|
|
||||||
Validate ValidateFunc
|
|
||||||
|
|
||||||
// rune sanitizer for input.
|
|
||||||
rsan runeutil.Sanitizer
|
|
||||||
|
|
||||||
// Should the input suggest to complete
|
|
||||||
ShowSuggestions bool
|
|
||||||
|
|
||||||
// suggestions is a list of suggestions that may be used to complete the
|
|
||||||
// input.
|
|
||||||
suggestions [][]rune
|
|
||||||
matchedSuggestions [][]rune
|
|
||||||
currentSuggestionIndex int
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new model with default settings.
|
|
||||||
func New() Model {
|
|
||||||
return Model{
|
|
||||||
Prompt: "> ",
|
|
||||||
EchoCharacter: '*',
|
|
||||||
CharLimit: 0,
|
|
||||||
PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
|
|
||||||
ShowSuggestions: false,
|
|
||||||
CompletionStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")),
|
|
||||||
Cursor: cursor.New(),
|
|
||||||
KeyMap: DefaultKeyMap,
|
|
||||||
|
|
||||||
suggestions: [][]rune{},
|
|
||||||
value: nil,
|
|
||||||
focus: false,
|
|
||||||
pos: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewModel creates a new model with default settings.
|
|
||||||
//
|
|
||||||
// Deprecated: Use [New] instead.
|
|
||||||
var NewModel = New
|
|
||||||
|
|
||||||
// SetValue sets the value of the text input.
|
|
||||||
func (m *Model) SetValue(s string) {
|
|
||||||
// Clean up any special characters in the input provided by the
|
|
||||||
// caller. This avoids bugs due to e.g. tab characters and whatnot.
|
|
||||||
runes := m.san().Sanitize([]rune(s))
|
|
||||||
err := m.validate(runes)
|
|
||||||
m.setValueInternal(runes, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) setValueInternal(runes []rune, err error) {
|
|
||||||
m.Err = err
|
|
||||||
|
|
||||||
empty := len(m.value) == 0
|
|
||||||
|
|
||||||
if m.CharLimit > 0 && len(runes) > m.CharLimit {
|
|
||||||
m.value = runes[:m.CharLimit]
|
|
||||||
} else {
|
|
||||||
m.value = runes
|
|
||||||
}
|
|
||||||
if (m.pos == 0 && empty) || m.pos > len(m.value) {
|
|
||||||
m.SetCursor(len(m.value))
|
|
||||||
}
|
|
||||||
m.handleOverflow()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value returns the value of the text input.
|
|
||||||
func (m Model) Value() string {
|
|
||||||
return string(m.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Position returns the cursor position.
|
|
||||||
func (m Model) Position() int {
|
|
||||||
return m.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCursor moves the cursor to the given position. If the position is
|
|
||||||
// out of bounds the cursor will be moved to the start or end accordingly.
|
|
||||||
func (m *Model) SetCursor(pos int) {
|
|
||||||
m.pos = clamp(pos, 0, len(m.value))
|
|
||||||
m.handleOverflow()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CursorStart moves the cursor to the start of the input field.
|
|
||||||
func (m *Model) CursorStart() {
|
|
||||||
m.SetCursor(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CursorEnd moves the cursor to the end of the input field.
|
|
||||||
func (m *Model) CursorEnd() {
|
|
||||||
m.SetCursor(len(m.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focused returns the focus state on the model.
|
|
||||||
func (m Model) Focused() bool {
|
|
||||||
return m.focus
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus sets the focus state on the model. When the model is in focus it can
|
|
||||||
// receive keyboard input and the cursor will be shown.
|
|
||||||
func (m *Model) Focus() tea.Cmd {
|
|
||||||
m.focus = true
|
|
||||||
return m.Cursor.Focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blur removes the focus state on the model. When the model is blurred it can
|
|
||||||
// not receive keyboard input and the cursor will be hidden.
|
|
||||||
func (m *Model) Blur() {
|
|
||||||
m.focus = false
|
|
||||||
m.Cursor.Blur()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset sets the input to its default state with no input.
|
|
||||||
func (m *Model) Reset() {
|
|
||||||
m.value = nil
|
|
||||||
m.SetCursor(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetSuggestions sets the suggestions for the input.
|
|
||||||
func (m *Model) SetSuggestions(suggestions []string) {
|
|
||||||
m.suggestions = make([][]rune, len(suggestions))
|
|
||||||
for i, s := range suggestions {
|
|
||||||
m.suggestions[i] = []rune(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.updateSuggestions()
|
|
||||||
}
|
|
||||||
|
|
||||||
// rsan initializes or retrieves the rune sanitizer.
|
|
||||||
func (m *Model) san() runeutil.Sanitizer {
|
|
||||||
if m.rsan == nil {
|
|
||||||
// Textinput has all its input on a single line so collapse
|
|
||||||
// newlines/tabs to single spaces.
|
|
||||||
m.rsan = runeutil.NewSanitizer(
|
|
||||||
runeutil.ReplaceTabs(" "), runeutil.ReplaceNewlines(" "))
|
|
||||||
}
|
|
||||||
return m.rsan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) insertRunesFromUserInput(v []rune) {
|
|
||||||
// Clean up any special characters in the input provided by the
|
|
||||||
// clipboard. This avoids bugs due to e.g. tab characters and
|
|
||||||
// whatnot.
|
|
||||||
paste := m.san().Sanitize(v)
|
|
||||||
|
|
||||||
var availSpace int
|
|
||||||
if m.CharLimit > 0 {
|
|
||||||
availSpace = m.CharLimit - len(m.value)
|
|
||||||
|
|
||||||
// If the char limit's been reached, cancel.
|
|
||||||
if availSpace <= 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's not enough space to paste the whole thing cut the pasted
|
|
||||||
// runes down so they'll fit.
|
|
||||||
if availSpace < len(paste) {
|
|
||||||
paste = paste[:availSpace]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stuff before and after the cursor
|
|
||||||
head := m.value[:m.pos]
|
|
||||||
tailSrc := m.value[m.pos:]
|
|
||||||
tail := make([]rune, len(tailSrc))
|
|
||||||
copy(tail, tailSrc)
|
|
||||||
|
|
||||||
// Insert pasted runes
|
|
||||||
for _, r := range paste {
|
|
||||||
head = append(head, r)
|
|
||||||
m.pos++
|
|
||||||
if m.CharLimit > 0 {
|
|
||||||
availSpace--
|
|
||||||
if availSpace <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put it all back together
|
|
||||||
value := append(head, tail...)
|
|
||||||
inputErr := m.validate(value)
|
|
||||||
m.setValueInternal(value, inputErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a max width is defined, perform some logic to treat the visible area
|
|
||||||
// as a horizontally scrolling viewport.
|
|
||||||
func (m *Model) handleOverflow() {
|
|
||||||
if m.Width <= 0 || uniseg.StringWidth(string(m.value)) <= m.Width {
|
|
||||||
m.offset = 0
|
|
||||||
m.offsetRight = len(m.value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Correct right offset if we've deleted characters
|
|
||||||
m.offsetRight = min(m.offsetRight, len(m.value))
|
|
||||||
|
|
||||||
if m.pos < m.offset {
|
|
||||||
m.offset = m.pos
|
|
||||||
|
|
||||||
w := 0
|
|
||||||
i := 0
|
|
||||||
runes := m.value[m.offset:]
|
|
||||||
|
|
||||||
for i < len(runes) && w <= m.Width {
|
|
||||||
w += rw.RuneWidth(runes[i])
|
|
||||||
if w <= m.Width+1 {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.offsetRight = m.offset + i
|
|
||||||
} else if m.pos >= m.offsetRight {
|
|
||||||
m.offsetRight = m.pos
|
|
||||||
|
|
||||||
w := 0
|
|
||||||
runes := m.value[:m.offsetRight]
|
|
||||||
i := len(runes) - 1
|
|
||||||
|
|
||||||
for i > 0 && w < m.Width {
|
|
||||||
w += rw.RuneWidth(runes[i])
|
|
||||||
if w <= m.Width {
|
|
||||||
i--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.offset = m.offsetRight - (len(runes) - 1 - i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteBeforeCursor deletes all text before the cursor.
|
|
||||||
func (m *Model) deleteBeforeCursor() {
|
|
||||||
m.value = m.value[m.pos:]
|
|
||||||
m.Err = m.validate(m.value)
|
|
||||||
m.offset = 0
|
|
||||||
m.SetCursor(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteAfterCursor deletes all text after the cursor. If input is masked
|
|
||||||
// delete everything after the cursor so as not to reveal word breaks in the
|
|
||||||
// masked input.
|
|
||||||
func (m *Model) deleteAfterCursor() {
|
|
||||||
m.value = m.value[:m.pos]
|
|
||||||
m.Err = m.validate(m.value)
|
|
||||||
m.SetCursor(len(m.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteWordBackward deletes the word left to the cursor.
|
|
||||||
func (m *Model) deleteWordBackward() {
|
|
||||||
if m.pos == 0 || len(m.value) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.EchoMode != EchoNormal {
|
|
||||||
m.deleteBeforeCursor()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linter note: it's critical that we acquire the initial cursor position
|
|
||||||
// here prior to altering it via SetCursor() below. As such, moving this
|
|
||||||
// call into the corresponding if clause does not apply here.
|
|
||||||
oldPos := m.pos //nolint:ifshort
|
|
||||||
|
|
||||||
m.SetCursor(m.pos - 1)
|
|
||||||
for unicode.IsSpace(m.value[m.pos]) {
|
|
||||||
if m.pos <= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// ignore series of whitespace before cursor
|
|
||||||
m.SetCursor(m.pos - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for m.pos > 0 {
|
|
||||||
if !unicode.IsSpace(m.value[m.pos]) {
|
|
||||||
m.SetCursor(m.pos - 1)
|
|
||||||
} else {
|
|
||||||
if m.pos > 0 {
|
|
||||||
// keep the previous space
|
|
||||||
m.SetCursor(m.pos + 1)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldPos > len(m.value) {
|
|
||||||
m.value = m.value[:m.pos]
|
|
||||||
} else {
|
|
||||||
m.value = append(m.value[:m.pos], m.value[oldPos:]...)
|
|
||||||
}
|
|
||||||
m.Err = m.validate(m.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteWordForward deletes the word right to the cursor. If input is masked
|
|
||||||
// delete everything after the cursor so as not to reveal word breaks in the
|
|
||||||
// masked input.
|
|
||||||
func (m *Model) deleteWordForward() {
|
|
||||||
if m.pos >= len(m.value) || len(m.value) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.EchoMode != EchoNormal {
|
|
||||||
m.deleteAfterCursor()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
oldPos := m.pos
|
|
||||||
m.SetCursor(m.pos + 1)
|
|
||||||
for unicode.IsSpace(m.value[m.pos]) {
|
|
||||||
// ignore series of whitespace after cursor
|
|
||||||
m.SetCursor(m.pos + 1)
|
|
||||||
|
|
||||||
if m.pos >= len(m.value) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for m.pos < len(m.value) {
|
|
||||||
if !unicode.IsSpace(m.value[m.pos]) {
|
|
||||||
m.SetCursor(m.pos + 1)
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.pos > len(m.value) {
|
|
||||||
m.value = m.value[:oldPos]
|
|
||||||
} else {
|
|
||||||
m.value = append(m.value[:oldPos], m.value[m.pos:]...)
|
|
||||||
}
|
|
||||||
m.Err = m.validate(m.value)
|
|
||||||
|
|
||||||
m.SetCursor(oldPos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wordBackward moves the cursor one word to the left. If input is masked, move
|
|
||||||
// input to the start so as not to reveal word breaks in the masked input.
|
|
||||||
func (m *Model) wordBackward() {
|
|
||||||
if m.pos == 0 || len(m.value) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.EchoMode != EchoNormal {
|
|
||||||
m.CursorStart()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
i := m.pos - 1
|
|
||||||
for i >= 0 {
|
|
||||||
if unicode.IsSpace(m.value[i]) {
|
|
||||||
m.SetCursor(m.pos - 1)
|
|
||||||
i--
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i >= 0 {
|
|
||||||
if !unicode.IsSpace(m.value[i]) {
|
|
||||||
m.SetCursor(m.pos - 1)
|
|
||||||
i--
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wordForward moves the cursor one word to the right. If the input is masked,
|
|
||||||
// move input to the end so as not to reveal word breaks in the masked input.
|
|
||||||
func (m *Model) wordForward() {
|
|
||||||
if m.pos >= len(m.value) || len(m.value) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.EchoMode != EchoNormal {
|
|
||||||
m.CursorEnd()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
i := m.pos
|
|
||||||
for i < len(m.value) {
|
|
||||||
if unicode.IsSpace(m.value[i]) {
|
|
||||||
m.SetCursor(m.pos + 1)
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i < len(m.value) {
|
|
||||||
if !unicode.IsSpace(m.value[i]) {
|
|
||||||
m.SetCursor(m.pos + 1)
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) echoTransform(v string) string {
|
|
||||||
switch m.EchoMode {
|
|
||||||
case EchoPassword:
|
|
||||||
return strings.Repeat(string(m.EchoCharacter), uniseg.StringWidth(v))
|
|
||||||
case EchoNone:
|
|
||||||
return ""
|
|
||||||
case EchoNormal:
|
|
||||||
return v
|
|
||||||
default:
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update is the Bubble Tea update loop.
|
|
||||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
if !m.focus {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Need to check for completion before, because key is configurable and might be double assigned
|
|
||||||
keyMsg, ok := msg.(tea.KeyMsg)
|
|
||||||
if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) {
|
|
||||||
if m.canAcceptSuggestion() {
|
|
||||||
m.value = append(m.value, m.matchedSuggestions[m.currentSuggestionIndex][len(m.value):]...)
|
|
||||||
m.CursorEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let's remember where the position of the cursor currently is so that if
|
|
||||||
// the cursor position changes, we can reset the blink.
|
|
||||||
oldPos := m.pos
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
switch {
|
|
||||||
case key.Matches(msg, m.KeyMap.DeleteWordBackward):
|
|
||||||
m.deleteWordBackward()
|
|
||||||
case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
|
|
||||||
m.Err = nil
|
|
||||||
if len(m.value) > 0 {
|
|
||||||
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
|
|
||||||
m.Err = m.validate(m.value)
|
|
||||||
if m.pos > 0 {
|
|
||||||
m.SetCursor(m.pos - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case key.Matches(msg, m.KeyMap.WordBackward):
|
|
||||||
m.wordBackward()
|
|
||||||
case key.Matches(msg, m.KeyMap.CharacterBackward):
|
|
||||||
if m.pos > 0 {
|
|
||||||
m.SetCursor(m.pos - 1)
|
|
||||||
}
|
|
||||||
case key.Matches(msg, m.KeyMap.WordForward):
|
|
||||||
m.wordForward()
|
|
||||||
case key.Matches(msg, m.KeyMap.CharacterForward):
|
|
||||||
if m.pos < len(m.value) {
|
|
||||||
m.SetCursor(m.pos + 1)
|
|
||||||
}
|
|
||||||
case key.Matches(msg, m.KeyMap.LineStart):
|
|
||||||
m.CursorStart()
|
|
||||||
case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
|
|
||||||
if len(m.value) > 0 && m.pos < len(m.value) {
|
|
||||||
m.value = append(m.value[:m.pos], m.value[m.pos+1:]...)
|
|
||||||
m.Err = m.validate(m.value)
|
|
||||||
}
|
|
||||||
case key.Matches(msg, m.KeyMap.LineEnd):
|
|
||||||
m.CursorEnd()
|
|
||||||
case key.Matches(msg, m.KeyMap.DeleteAfterCursor):
|
|
||||||
m.deleteAfterCursor()
|
|
||||||
case key.Matches(msg, m.KeyMap.DeleteBeforeCursor):
|
|
||||||
m.deleteBeforeCursor()
|
|
||||||
case key.Matches(msg, m.KeyMap.Paste):
|
|
||||||
return m, Paste
|
|
||||||
case key.Matches(msg, m.KeyMap.DeleteWordForward):
|
|
||||||
m.deleteWordForward()
|
|
||||||
case key.Matches(msg, m.KeyMap.NextSuggestion):
|
|
||||||
m.nextSuggestion()
|
|
||||||
case key.Matches(msg, m.KeyMap.PrevSuggestion):
|
|
||||||
m.previousSuggestion()
|
|
||||||
default:
|
|
||||||
// Input one or more regular characters.
|
|
||||||
m.insertRunesFromUserInput(msg.Runes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check again if can be completed
|
|
||||||
// because value might be something that does not match the completion prefix
|
|
||||||
m.updateSuggestions()
|
|
||||||
|
|
||||||
case pasteMsg:
|
|
||||||
m.insertRunesFromUserInput([]rune(msg))
|
|
||||||
|
|
||||||
case pasteErrMsg:
|
|
||||||
m.Err = msg
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmds []tea.Cmd
|
|
||||||
var cmd tea.Cmd
|
|
||||||
|
|
||||||
m.Cursor, cmd = m.Cursor.Update(msg)
|
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
|
|
||||||
if oldPos != m.pos && m.Cursor.Mode() == cursor.CursorBlink {
|
|
||||||
m.Cursor.Blink = false
|
|
||||||
cmds = append(cmds, m.Cursor.BlinkCmd())
|
|
||||||
}
|
|
||||||
|
|
||||||
m.handleOverflow()
|
|
||||||
return m, tea.Batch(cmds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// View renders the textinput in its current state.
|
|
||||||
func (m Model) View() string {
|
|
||||||
// Placeholder text
|
|
||||||
if len(m.value) == 0 && m.Placeholder != "" {
|
|
||||||
return m.placeholderView()
|
|
||||||
}
|
|
||||||
|
|
||||||
styleText := m.TextStyle.Inline(true).Render
|
|
||||||
|
|
||||||
value := m.value[m.offset:m.offsetRight]
|
|
||||||
pos := max(0, m.pos-m.offset)
|
|
||||||
v := styleText(m.echoTransform(string(value[:pos])))
|
|
||||||
|
|
||||||
if pos < len(value) { //nolint:nestif
|
|
||||||
char := m.echoTransform(string(value[pos]))
|
|
||||||
m.Cursor.SetChar(char)
|
|
||||||
v += m.Cursor.View() // cursor and text under it
|
|
||||||
v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor
|
|
||||||
v += m.completionView(0) // suggested completion
|
|
||||||
} else {
|
|
||||||
if m.focus && m.canAcceptSuggestion() {
|
|
||||||
suggestion := m.matchedSuggestions[m.currentSuggestionIndex]
|
|
||||||
if len(value) < len(suggestion) {
|
|
||||||
m.Cursor.TextStyle = m.CompletionStyle
|
|
||||||
m.Cursor.SetChar(m.echoTransform(string(suggestion[pos])))
|
|
||||||
v += m.Cursor.View()
|
|
||||||
v += m.completionView(1)
|
|
||||||
} else {
|
|
||||||
m.Cursor.SetChar(" ")
|
|
||||||
v += m.Cursor.View()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
m.Cursor.SetChar(" ")
|
|
||||||
v += m.Cursor.View()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a max width and background color were set fill the empty spaces with
|
|
||||||
// the background color.
|
|
||||||
valWidth := uniseg.StringWidth(string(value))
|
|
||||||
if m.Width > 0 && valWidth <= m.Width {
|
|
||||||
padding := max(0, m.Width-valWidth)
|
|
||||||
if valWidth+padding <= m.Width && pos < len(value) {
|
|
||||||
padding++
|
|
||||||
}
|
|
||||||
v += styleText(strings.Repeat(" ", padding))
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.PromptStyle.Render(m.Prompt) + v
|
|
||||||
}
|
|
||||||
|
|
||||||
// placeholderView returns the prompt and placeholder view, if any.
|
|
||||||
func (m Model) placeholderView() string {
|
|
||||||
var (
|
|
||||||
v string
|
|
||||||
style = m.PlaceholderStyle.Inline(true).Render
|
|
||||||
)
|
|
||||||
|
|
||||||
p := make([]rune, m.Width+1)
|
|
||||||
copy(p, []rune(m.Placeholder))
|
|
||||||
|
|
||||||
m.Cursor.TextStyle = m.PlaceholderStyle
|
|
||||||
m.Cursor.SetChar(string(p[:1]))
|
|
||||||
v += m.Cursor.View()
|
|
||||||
|
|
||||||
// If the entire placeholder is already set and no padding is needed, finish
|
|
||||||
if m.Width < 1 && len(p) <= 1 {
|
|
||||||
return m.PromptStyle.Render(m.Prompt) + v
|
|
||||||
}
|
|
||||||
|
|
||||||
// If Width is set then size placeholder accordingly
|
|
||||||
if m.Width > 0 {
|
|
||||||
// available width is width - len + cursor offset of 1
|
|
||||||
minWidth := lipgloss.Width(m.Placeholder)
|
|
||||||
availWidth := m.Width - minWidth + 1
|
|
||||||
|
|
||||||
// if width < len, 'subtract'(add) number to len and dont add padding
|
|
||||||
if availWidth < 0 {
|
|
||||||
minWidth += availWidth
|
|
||||||
availWidth = 0
|
|
||||||
}
|
|
||||||
// append placeholder[len] - cursor, append padding
|
|
||||||
v += style(string(p[1:minWidth]))
|
|
||||||
v += style(strings.Repeat(" ", availWidth))
|
|
||||||
} else {
|
|
||||||
// if there is no width, the placeholder can be any length
|
|
||||||
v += style(string(p[1:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.PromptStyle.Render(m.Prompt) + v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Blink is a command used to initialize cursor blinking.
|
|
||||||
func Blink() tea.Msg {
|
|
||||||
return cursor.Blink()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paste is a command for pasting from the clipboard into the text input.
|
|
||||||
func Paste() tea.Msg {
|
|
||||||
str, err := clipboard.ReadAll()
|
|
||||||
if err != nil {
|
|
||||||
return pasteErrMsg{err}
|
|
||||||
}
|
|
||||||
return pasteMsg(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clamp(v, low, high int) int {
|
|
||||||
if high < low {
|
|
||||||
low, high = high, low
|
|
||||||
}
|
|
||||||
return min(high, max(low, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated.
|
|
||||||
|
|
||||||
// Deprecated: use [cursor.Mode].
|
|
||||||
//
|
|
||||||
//nolint:revive
|
|
||||||
type CursorMode int
|
|
||||||
|
|
||||||
//nolint:revive
|
|
||||||
const (
|
|
||||||
// Deprecated: use [cursor.CursorBlink].
|
|
||||||
CursorBlink = CursorMode(cursor.CursorBlink)
|
|
||||||
// Deprecated: use [cursor.CursorStatic].
|
|
||||||
CursorStatic = CursorMode(cursor.CursorStatic)
|
|
||||||
// Deprecated: use [cursor.CursorHide].
|
|
||||||
CursorHide = CursorMode(cursor.CursorHide)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c CursorMode) String() string {
|
|
||||||
return cursor.Mode(c).String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: use [cursor.Mode].
|
|
||||||
//
|
|
||||||
//nolint:revive
|
|
||||||
func (m Model) CursorMode() CursorMode {
|
|
||||||
return CursorMode(m.Cursor.Mode())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deprecated: use cursor.SetMode().
|
|
||||||
//
|
|
||||||
//nolint:revive
|
|
||||||
func (m *Model) SetCursorMode(mode CursorMode) tea.Cmd {
|
|
||||||
return m.Cursor.SetMode(cursor.Mode(mode))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) completionView(offset int) string {
|
|
||||||
var (
|
|
||||||
value = m.value
|
|
||||||
style = m.PlaceholderStyle.Inline(true).Render
|
|
||||||
)
|
|
||||||
|
|
||||||
if m.canAcceptSuggestion() {
|
|
||||||
suggestion := m.matchedSuggestions[m.currentSuggestionIndex]
|
|
||||||
if len(value) < len(suggestion) {
|
|
||||||
return style(string(suggestion[len(value)+offset:]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) getSuggestions(sugs [][]rune) []string {
|
|
||||||
suggestions := make([]string, len(sugs))
|
|
||||||
for i, s := range sugs {
|
|
||||||
suggestions[i] = string(s)
|
|
||||||
}
|
|
||||||
return suggestions
|
|
||||||
}
|
|
||||||
|
|
||||||
// AvailableSuggestions returns the list of available suggestions.
|
|
||||||
func (m *Model) AvailableSuggestions() []string {
|
|
||||||
return m.getSuggestions(m.suggestions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchedSuggestions returns the list of matched suggestions.
|
|
||||||
func (m *Model) MatchedSuggestions() []string {
|
|
||||||
return m.getSuggestions(m.matchedSuggestions)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentSuggestionIndex returns the currently selected suggestion index.
|
|
||||||
func (m *Model) CurrentSuggestionIndex() int {
|
|
||||||
return m.currentSuggestionIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentSuggestion returns the currently selected suggestion.
|
|
||||||
func (m *Model) CurrentSuggestion() string {
|
|
||||||
if m.currentSuggestionIndex >= len(m.matchedSuggestions) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(m.matchedSuggestions[m.currentSuggestionIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
// canAcceptSuggestion returns whether there is an acceptable suggestion to
|
|
||||||
// autocomplete the current value.
|
|
||||||
func (m *Model) canAcceptSuggestion() bool {
|
|
||||||
return len(m.matchedSuggestions) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateSuggestions refreshes the list of matching suggestions.
|
|
||||||
func (m *Model) updateSuggestions() {
|
|
||||||
if !m.ShowSuggestions {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(m.value) <= 0 || len(m.suggestions) <= 0 {
|
|
||||||
m.matchedSuggestions = [][]rune{}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := [][]rune{}
|
|
||||||
for _, s := range m.suggestions {
|
|
||||||
suggestion := string(s)
|
|
||||||
|
|
||||||
if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(string(m.value))) {
|
|
||||||
matches = append(matches, []rune(suggestion))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(matches, m.matchedSuggestions) {
|
|
||||||
m.currentSuggestionIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
m.matchedSuggestions = matches
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextSuggestion selects the next suggestion.
|
|
||||||
func (m *Model) nextSuggestion() {
|
|
||||||
m.currentSuggestionIndex = (m.currentSuggestionIndex + 1)
|
|
||||||
if m.currentSuggestionIndex >= len(m.matchedSuggestions) {
|
|
||||||
m.currentSuggestionIndex = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// previousSuggestion selects the previous suggestion.
|
|
||||||
func (m *Model) previousSuggestion() {
|
|
||||||
m.currentSuggestionIndex = (m.currentSuggestionIndex - 1)
|
|
||||||
if m.currentSuggestionIndex < 0 {
|
|
||||||
m.currentSuggestionIndex = len(m.matchedSuggestions) - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) validate(v []rune) error {
|
|
||||||
if m.Validate != nil {
|
|
||||||
return m.Validate(string(v))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
60
vendor/github.com/charmbracelet/bubbles/viewport/keymap.go
generated
vendored
Normal file
60
vendor/github.com/charmbracelet/bubbles/viewport/keymap.go
generated
vendored
Normal 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"),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
544
vendor/github.com/charmbracelet/bubbles/viewport/viewport.go
generated
vendored
Normal file
544
vendor/github.com/charmbracelet/bubbles/viewport/viewport.go
generated
vendored
Normal 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
|
||||||
|
}
|
||||||
21
vendor/github.com/evertras/bubble-table/LICENSE
generated
vendored
21
vendor/github.com/evertras/bubble-table/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2022 Brandon Fulljames
|
|
||||||
|
|
||||||
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.
|
|
||||||
439
vendor/github.com/evertras/bubble-table/table/border.go
generated
vendored
439
vendor/github.com/evertras/bubble-table/table/border.go
generated
vendored
@ -1,439 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import "github.com/charmbracelet/lipgloss"
|
|
||||||
|
|
||||||
// Border defines the borders in and around the table.
|
|
||||||
type Border struct {
|
|
||||||
Top string
|
|
||||||
Left string
|
|
||||||
Right string
|
|
||||||
Bottom string
|
|
||||||
TopRight string
|
|
||||||
TopLeft string
|
|
||||||
BottomRight string
|
|
||||||
BottomLeft string
|
|
||||||
|
|
||||||
TopJunction string
|
|
||||||
LeftJunction string
|
|
||||||
RightJunction string
|
|
||||||
BottomJunction string
|
|
||||||
|
|
||||||
InnerJunction string
|
|
||||||
|
|
||||||
InnerDivider string
|
|
||||||
|
|
||||||
// Styles for 2x2 tables and larger
|
|
||||||
styleMultiTopLeft lipgloss.Style
|
|
||||||
styleMultiTop lipgloss.Style
|
|
||||||
styleMultiTopRight lipgloss.Style
|
|
||||||
styleMultiRight lipgloss.Style
|
|
||||||
styleMultiBottomRight lipgloss.Style
|
|
||||||
styleMultiBottom lipgloss.Style
|
|
||||||
styleMultiBottomLeft lipgloss.Style
|
|
||||||
styleMultiLeft lipgloss.Style
|
|
||||||
styleMultiInner lipgloss.Style
|
|
||||||
|
|
||||||
// Styles for a single column table
|
|
||||||
styleSingleColumnTop lipgloss.Style
|
|
||||||
styleSingleColumnInner lipgloss.Style
|
|
||||||
styleSingleColumnBottom lipgloss.Style
|
|
||||||
|
|
||||||
// Styles for a single row table
|
|
||||||
styleSingleRowLeft lipgloss.Style
|
|
||||||
styleSingleRowInner lipgloss.Style
|
|
||||||
styleSingleRowRight lipgloss.Style
|
|
||||||
|
|
||||||
// Style for a table with only one cell
|
|
||||||
styleSingleCell lipgloss.Style
|
|
||||||
|
|
||||||
// Style for the footer
|
|
||||||
styleFooter lipgloss.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// https://www.w3.org/TR/xml-entity-names/025.html
|
|
||||||
|
|
||||||
borderDefault = Border{
|
|
||||||
Top: "━",
|
|
||||||
Left: "┃",
|
|
||||||
Right: "┃",
|
|
||||||
Bottom: "━",
|
|
||||||
|
|
||||||
TopRight: "┓",
|
|
||||||
TopLeft: "┏",
|
|
||||||
BottomRight: "┛",
|
|
||||||
BottomLeft: "┗",
|
|
||||||
|
|
||||||
TopJunction: "┳",
|
|
||||||
LeftJunction: "┣",
|
|
||||||
RightJunction: "┫",
|
|
||||||
BottomJunction: "┻",
|
|
||||||
InnerJunction: "╋",
|
|
||||||
|
|
||||||
InnerDivider: "┃",
|
|
||||||
}
|
|
||||||
|
|
||||||
borderRounded = Border{
|
|
||||||
Top: "─",
|
|
||||||
Left: "│",
|
|
||||||
Right: "│",
|
|
||||||
Bottom: "─",
|
|
||||||
|
|
||||||
TopRight: "╮",
|
|
||||||
TopLeft: "╭",
|
|
||||||
BottomRight: "╯",
|
|
||||||
BottomLeft: "╰",
|
|
||||||
|
|
||||||
TopJunction: "┬",
|
|
||||||
LeftJunction: "├",
|
|
||||||
RightJunction: "┤",
|
|
||||||
BottomJunction: "┴",
|
|
||||||
InnerJunction: "┼",
|
|
||||||
|
|
||||||
InnerDivider: "│",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
borderDefault.generateStyles()
|
|
||||||
borderRounded.generateStyles()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Border) generateStyles() {
|
|
||||||
b.generateMultiStyles()
|
|
||||||
b.generateSingleColumnStyles()
|
|
||||||
b.generateSingleRowStyles()
|
|
||||||
b.generateSingleCellStyle()
|
|
||||||
|
|
||||||
// The footer is a single cell with the top taken off... usually. We can
|
|
||||||
// re-enable the top if needed this way for certain format configurations.
|
|
||||||
b.styleFooter = b.styleSingleCell.Copy().
|
|
||||||
Align(lipgloss.Right).
|
|
||||||
BorderBottom(true).
|
|
||||||
BorderRight(true).
|
|
||||||
BorderLeft(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Border) styleLeftWithFooter(original lipgloss.Style) lipgloss.Style {
|
|
||||||
border := original.GetBorderStyle()
|
|
||||||
|
|
||||||
border.BottomLeft = b.LeftJunction
|
|
||||||
|
|
||||||
return original.Copy().BorderStyle(border)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Border) styleRightWithFooter(original lipgloss.Style) lipgloss.Style {
|
|
||||||
border := original.GetBorderStyle()
|
|
||||||
|
|
||||||
border.BottomRight = b.RightJunction
|
|
||||||
|
|
||||||
return original.Copy().BorderStyle(border)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Border) styleBothWithFooter(original lipgloss.Style) lipgloss.Style {
|
|
||||||
border := original.GetBorderStyle()
|
|
||||||
|
|
||||||
border.BottomLeft = b.LeftJunction
|
|
||||||
border.BottomRight = b.RightJunction
|
|
||||||
|
|
||||||
return original.Copy().BorderStyle(border)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function is long, but it's just repetitive...
|
|
||||||
//
|
|
||||||
//nolint:funlen
|
|
||||||
func (b *Border) generateMultiStyles() {
|
|
||||||
b.styleMultiTopLeft = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
TopLeft: b.TopLeft,
|
|
||||||
Top: b.Top,
|
|
||||||
TopRight: b.TopJunction,
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
BottomRight: b.InnerJunction,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
BottomLeft: b.LeftJunction,
|
|
||||||
Left: b.Left,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
b.styleMultiTop = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Top: b.Top,
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
TopRight: b.TopJunction,
|
|
||||||
BottomRight: b.InnerJunction,
|
|
||||||
},
|
|
||||||
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
||||||
|
|
||||||
b.styleMultiTopRight = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Top: b.Top,
|
|
||||||
Right: b.Right,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
TopRight: b.TopRight,
|
|
||||||
BottomRight: b.RightJunction,
|
|
||||||
},
|
|
||||||
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
||||||
|
|
||||||
b.styleMultiLeft = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Left: b.Left,
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
},
|
|
||||||
).BorderRight(true).BorderLeft(true)
|
|
||||||
|
|
||||||
b.styleMultiRight = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Right: b.Right,
|
|
||||||
},
|
|
||||||
).BorderRight(true)
|
|
||||||
|
|
||||||
b.styleMultiInner = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
},
|
|
||||||
).BorderRight(true)
|
|
||||||
|
|
||||||
b.styleMultiBottomLeft = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Left: b.Left,
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomLeft: b.BottomLeft,
|
|
||||||
BottomRight: b.BottomJunction,
|
|
||||||
},
|
|
||||||
).BorderLeft(true).BorderBottom(true).BorderRight(true)
|
|
||||||
|
|
||||||
b.styleMultiBottom = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomRight: b.BottomJunction,
|
|
||||||
},
|
|
||||||
).BorderBottom(true).BorderRight(true)
|
|
||||||
|
|
||||||
b.styleMultiBottomRight = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Right: b.Right,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomRight: b.BottomRight,
|
|
||||||
},
|
|
||||||
).BorderBottom(true).BorderRight(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Border) generateSingleColumnStyles() {
|
|
||||||
b.styleSingleColumnTop = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Top: b.Top,
|
|
||||||
Left: b.Left,
|
|
||||||
Right: b.Right,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
TopLeft: b.TopLeft,
|
|
||||||
TopRight: b.TopRight,
|
|
||||||
BottomLeft: b.LeftJunction,
|
|
||||||
BottomRight: b.RightJunction,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
b.styleSingleColumnInner = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Left: b.Left,
|
|
||||||
Right: b.Right,
|
|
||||||
},
|
|
||||||
).BorderRight(true).BorderLeft(true)
|
|
||||||
|
|
||||||
b.styleSingleColumnBottom = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Left: b.Left,
|
|
||||||
Right: b.Right,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomLeft: b.BottomLeft,
|
|
||||||
BottomRight: b.BottomRight,
|
|
||||||
},
|
|
||||||
).BorderRight(true).BorderLeft(true).BorderBottom(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Border) generateSingleRowStyles() {
|
|
||||||
b.styleSingleRowLeft = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Top: b.Top,
|
|
||||||
Left: b.Left,
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomLeft: b.BottomLeft,
|
|
||||||
BottomRight: b.BottomJunction,
|
|
||||||
TopRight: b.TopJunction,
|
|
||||||
TopLeft: b.TopLeft,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
b.styleSingleRowInner = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Top: b.Top,
|
|
||||||
Right: b.InnerDivider,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomRight: b.BottomJunction,
|
|
||||||
TopRight: b.TopJunction,
|
|
||||||
},
|
|
||||||
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
||||||
|
|
||||||
b.styleSingleRowRight = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Top: b.Top,
|
|
||||||
Right: b.Right,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomRight: b.BottomRight,
|
|
||||||
TopRight: b.TopRight,
|
|
||||||
},
|
|
||||||
).BorderTop(true).BorderBottom(true).BorderRight(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Border) generateSingleCellStyle() {
|
|
||||||
b.styleSingleCell = lipgloss.NewStyle().BorderStyle(
|
|
||||||
lipgloss.Border{
|
|
||||||
Top: b.Top,
|
|
||||||
Left: b.Left,
|
|
||||||
Right: b.Right,
|
|
||||||
Bottom: b.Bottom,
|
|
||||||
|
|
||||||
BottomLeft: b.BottomLeft,
|
|
||||||
BottomRight: b.BottomRight,
|
|
||||||
TopRight: b.TopRight,
|
|
||||||
TopLeft: b.TopLeft,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BorderDefault uses the basic square border, useful to reset the border if
|
|
||||||
// it was changed somehow.
|
|
||||||
func (m Model) BorderDefault() Model {
|
|
||||||
// Already generated styles
|
|
||||||
m.border = borderDefault
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// BorderRounded uses a thin, rounded border.
|
|
||||||
func (m Model) BorderRounded() Model {
|
|
||||||
// Already generated styles
|
|
||||||
m.border = borderRounded
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Border uses the given border components to render the table.
|
|
||||||
func (m Model) Border(border Border) Model {
|
|
||||||
border.generateStyles()
|
|
||||||
|
|
||||||
m.border = border
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type borderStyleRow struct {
|
|
||||||
left lipgloss.Style
|
|
||||||
inner lipgloss.Style
|
|
||||||
right lipgloss.Style
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *borderStyleRow) inherit(s lipgloss.Style) {
|
|
||||||
b.left = b.left.Copy().Inherit(s)
|
|
||||||
b.inner = b.inner.Copy().Inherit(s)
|
|
||||||
b.right = b.right.Copy().Inherit(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// There's a lot of branches here, but splitting it up further would make it
|
|
||||||
// harder to follow. So just be careful with comments and make sure it's tested!
|
|
||||||
//
|
|
||||||
//nolint:nestif
|
|
||||||
func (m Model) styleHeaders() borderStyleRow {
|
|
||||||
hasRows := len(m.GetVisibleRows()) > 0 || m.calculatePadding(0) > 0
|
|
||||||
singleColumn := len(m.columns) == 1
|
|
||||||
styles := borderStyleRow{}
|
|
||||||
|
|
||||||
// Possible configurations:
|
|
||||||
// - Single cell
|
|
||||||
// - Single row
|
|
||||||
// - Single column
|
|
||||||
// - Multi
|
|
||||||
|
|
||||||
if singleColumn {
|
|
||||||
if hasRows {
|
|
||||||
// Single column
|
|
||||||
styles.left = m.border.styleSingleColumnTop
|
|
||||||
styles.inner = styles.left
|
|
||||||
styles.right = styles.left
|
|
||||||
} else {
|
|
||||||
// Single cell
|
|
||||||
styles.left = m.border.styleSingleCell
|
|
||||||
styles.inner = styles.left
|
|
||||||
styles.right = styles.left
|
|
||||||
|
|
||||||
if m.hasFooter() {
|
|
||||||
styles.left = m.border.styleBothWithFooter(styles.left)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !hasRows {
|
|
||||||
// Single row
|
|
||||||
styles.left = m.border.styleSingleRowLeft
|
|
||||||
styles.inner = m.border.styleSingleRowInner
|
|
||||||
styles.right = m.border.styleSingleRowRight
|
|
||||||
|
|
||||||
if m.hasFooter() {
|
|
||||||
styles.left = m.border.styleLeftWithFooter(styles.left)
|
|
||||||
styles.right = m.border.styleRightWithFooter(styles.right)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Multi
|
|
||||||
styles.left = m.border.styleMultiTopLeft
|
|
||||||
styles.inner = m.border.styleMultiTop
|
|
||||||
styles.right = m.border.styleMultiTopRight
|
|
||||||
}
|
|
||||||
|
|
||||||
styles.inherit(m.headerStyle)
|
|
||||||
|
|
||||||
return styles
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) styleRows() (inner borderStyleRow, last borderStyleRow) {
|
|
||||||
if len(m.columns) == 1 {
|
|
||||||
inner.left = m.border.styleSingleColumnInner
|
|
||||||
inner.inner = inner.left
|
|
||||||
inner.right = inner.left
|
|
||||||
|
|
||||||
last.left = m.border.styleSingleColumnBottom
|
|
||||||
|
|
||||||
if m.hasFooter() {
|
|
||||||
last.left = m.border.styleBothWithFooter(last.left)
|
|
||||||
}
|
|
||||||
|
|
||||||
last.inner = last.left
|
|
||||||
last.right = last.left
|
|
||||||
} else {
|
|
||||||
inner.left = m.border.styleMultiLeft
|
|
||||||
inner.inner = m.border.styleMultiInner
|
|
||||||
inner.right = m.border.styleMultiRight
|
|
||||||
|
|
||||||
last.left = m.border.styleMultiBottomLeft
|
|
||||||
last.inner = m.border.styleMultiBottom
|
|
||||||
last.right = m.border.styleMultiBottomRight
|
|
||||||
|
|
||||||
if m.hasFooter() {
|
|
||||||
last.left = m.border.styleLeftWithFooter(last.left)
|
|
||||||
last.right = m.border.styleRightWithFooter(last.right)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inner, last
|
|
||||||
}
|
|
||||||
36
vendor/github.com/evertras/bubble-table/table/calc.go
generated
vendored
36
vendor/github.com/evertras/bubble-table/table/calc.go
generated
vendored
@ -1,36 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
// Keep compatibility with Go 1.21 by re-declaring min.
|
|
||||||
//
|
|
||||||
//nolint:predeclared
|
|
||||||
func min(x, y int) int {
|
|
||||||
if x < y {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep compatibility with Go 1.21 by re-declaring max.
|
|
||||||
//
|
|
||||||
//nolint:predeclared
|
|
||||||
func max(x, y int) int {
|
|
||||||
if x > y {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
return y
|
|
||||||
}
|
|
||||||
|
|
||||||
// These var names are fine for this little function
|
|
||||||
//
|
|
||||||
//nolint:varnamelen
|
|
||||||
func gcd(x, y int) int {
|
|
||||||
if x == 0 {
|
|
||||||
return y
|
|
||||||
} else if y == 0 {
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
return gcd(y%x, x)
|
|
||||||
}
|
|
||||||
60
vendor/github.com/evertras/bubble-table/table/cell.go
generated
vendored
60
vendor/github.com/evertras/bubble-table/table/cell.go
generated
vendored
@ -1,60 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import "github.com/charmbracelet/lipgloss"
|
|
||||||
|
|
||||||
// StyledCell represents a cell in the table that has a particular style applied.
|
|
||||||
// The cell style takes highest precedence and will overwrite more general styles
|
|
||||||
// from the row, column, or table as a whole. This style should be generally
|
|
||||||
// limited to colors, font style, and alignments - spacing style such as margin
|
|
||||||
// will break the table format.
|
|
||||||
type StyledCell struct {
|
|
||||||
// Data is the content of the cell.
|
|
||||||
Data any
|
|
||||||
|
|
||||||
// Style is the specific style to apply. This is ignored if StyleFunc is not nil.
|
|
||||||
Style lipgloss.Style
|
|
||||||
|
|
||||||
// StyleFunc is a function that takes the row/column of the cell and
|
|
||||||
// returns a lipgloss.Style allowing for dynamic styling based on the cell's
|
|
||||||
// content or position. Overrides Style if set.
|
|
||||||
StyleFunc StyledCellFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// StyledCellFuncInput is the input to the StyledCellFunc. Sent as a struct
|
|
||||||
// to allow for future additions without breaking changes.
|
|
||||||
type StyledCellFuncInput struct {
|
|
||||||
// Data is the data in the cell.
|
|
||||||
Data any
|
|
||||||
|
|
||||||
// Column is the column that the cell belongs to.
|
|
||||||
Column Column
|
|
||||||
|
|
||||||
// Row is the row that the cell belongs to.
|
|
||||||
Row Row
|
|
||||||
|
|
||||||
// GlobalMetadata is the global table metadata that's been set by WithGlobalMetadata
|
|
||||||
GlobalMetadata map[string]any
|
|
||||||
}
|
|
||||||
|
|
||||||
// StyledCellFunc is a function that takes various information about the cell and
|
|
||||||
// returns a lipgloss.Style allowing for easier dynamic styling based on the cell's
|
|
||||||
// content or position.
|
|
||||||
type StyledCellFunc = func(input StyledCellFuncInput) lipgloss.Style
|
|
||||||
|
|
||||||
// NewStyledCell creates an entry that can be set in the row data and show as
|
|
||||||
// styled with the given style.
|
|
||||||
func NewStyledCell(data any, style lipgloss.Style) StyledCell {
|
|
||||||
return StyledCell{
|
|
||||||
Data: data,
|
|
||||||
Style: style,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStyledCellWithStyleFunc creates an entry that can be set in the row data and show as
|
|
||||||
// styled with the given style function.
|
|
||||||
func NewStyledCellWithStyleFunc(data any, styleFunc StyledCellFunc) StyledCell {
|
|
||||||
return StyledCell{
|
|
||||||
Data: data,
|
|
||||||
StyleFunc: styleFunc,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
118
vendor/github.com/evertras/bubble-table/table/column.go
generated
vendored
118
vendor/github.com/evertras/bubble-table/table/column.go
generated
vendored
@ -1,118 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Column is a column in the table.
|
|
||||||
type Column struct {
|
|
||||||
title string
|
|
||||||
key string
|
|
||||||
width int
|
|
||||||
|
|
||||||
flexFactor int
|
|
||||||
|
|
||||||
filterable bool
|
|
||||||
style lipgloss.Style
|
|
||||||
|
|
||||||
fmtString string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewColumn creates a new fixed-width column with the given information.
|
|
||||||
func NewColumn(key, title string, width int) Column {
|
|
||||||
return Column{
|
|
||||||
key: key,
|
|
||||||
title: title,
|
|
||||||
width: width,
|
|
||||||
|
|
||||||
filterable: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFlexColumn creates a new flexible width column that tries to fill in the
|
|
||||||
// total table width. If multiple flex columns exist, each will measure against
|
|
||||||
// each other depending on their flexFactor. For example, if both have a flexFactor
|
|
||||||
// of 1, they will have equal width. If one has a flexFactor of 1 and the other
|
|
||||||
// has a flexFactor of 3, the second will be 3 times larger than the first. You
|
|
||||||
// must use WithTargetWidth if you have any flex columns, so that the table knows
|
|
||||||
// how much width it should fill.
|
|
||||||
func NewFlexColumn(key, title string, flexFactor int) Column {
|
|
||||||
return Column{
|
|
||||||
key: key,
|
|
||||||
title: title,
|
|
||||||
|
|
||||||
flexFactor: max(flexFactor, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStyle applies a style to the column as a whole.
|
|
||||||
func (c Column) WithStyle(style lipgloss.Style) Column {
|
|
||||||
c.style = style.Copy().Width(c.width)
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFiltered sets whether the column should be considered for filtering (true)
|
|
||||||
// or not (false).
|
|
||||||
func (c Column) WithFiltered(filterable bool) Column {
|
|
||||||
c.filterable = filterable
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFormatString sets the format string used by fmt.Sprintf to display the data.
|
|
||||||
// If not set, the default is "%v" for all data types. Intended mainly for
|
|
||||||
// numeric formatting.
|
|
||||||
//
|
|
||||||
// Since data is of the any type, make sure that all data in the column
|
|
||||||
// is of the expected type or the format may fail. For example, hardcoding '3'
|
|
||||||
// instead of '3.0' and using '%.2f' will fail because '3' is an integer.
|
|
||||||
func (c Column) WithFormatString(fmtString string) Column {
|
|
||||||
c.fmtString = fmtString
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Column) isFlex() bool {
|
|
||||||
return c.flexFactor != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title returns the title of the column.
|
|
||||||
func (c Column) Title() string {
|
|
||||||
return c.title
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key returns the key of the column.
|
|
||||||
func (c Column) Key() string {
|
|
||||||
return c.key
|
|
||||||
}
|
|
||||||
|
|
||||||
// Width returns the width of the column.
|
|
||||||
func (c Column) Width() int {
|
|
||||||
return c.width
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlexFactor returns the flex factor of the column.
|
|
||||||
func (c Column) FlexFactor() int {
|
|
||||||
return c.flexFactor
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsFlex returns whether the column is a flex column.
|
|
||||||
func (c Column) IsFlex() bool {
|
|
||||||
return c.isFlex()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filterable returns whether the column is filterable.
|
|
||||||
func (c Column) Filterable() bool {
|
|
||||||
return c.filterable
|
|
||||||
}
|
|
||||||
|
|
||||||
// Style returns the style of the column.
|
|
||||||
func (c Column) Style() lipgloss.Style {
|
|
||||||
return c.style
|
|
||||||
}
|
|
||||||
|
|
||||||
// FmtString returns the format string of the column.
|
|
||||||
func (c Column) FmtString() string {
|
|
||||||
return c.fmtString
|
|
||||||
}
|
|
||||||
67
vendor/github.com/evertras/bubble-table/table/data.go
generated
vendored
67
vendor/github.com/evertras/bubble-table/table/data.go
generated
vendored
@ -1,67 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// This is just a bunch of data type checks, so... no linting here
|
|
||||||
//
|
|
||||||
//nolint:cyclop
|
|
||||||
func asInt(data any) (int64, bool) {
|
|
||||||
switch val := data.(type) {
|
|
||||||
case int:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case int8:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case int16:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case int32:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case int64:
|
|
||||||
return val, true
|
|
||||||
|
|
||||||
case uint:
|
|
||||||
// #nosec: G115
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case uint8:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case uint16:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case uint32:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case uint64:
|
|
||||||
// #nosec: G115
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case time.Duration:
|
|
||||||
return int64(val), true
|
|
||||||
|
|
||||||
case StyledCell:
|
|
||||||
return asInt(val.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func asNumber(data any) (float64, bool) {
|
|
||||||
switch val := data.(type) {
|
|
||||||
case float32:
|
|
||||||
return float64(val), true
|
|
||||||
|
|
||||||
case float64:
|
|
||||||
return val, true
|
|
||||||
|
|
||||||
case StyledCell:
|
|
||||||
return asNumber(val.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
intVal, isInt := asInt(data)
|
|
||||||
|
|
||||||
return float64(intVal), isInt
|
|
||||||
}
|
|
||||||
116
vendor/github.com/evertras/bubble-table/table/dimensions.go
generated
vendored
116
vendor/github.com/evertras/bubble-table/table/dimensions.go
generated
vendored
@ -1,116 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m *Model) recalculateWidth() {
|
|
||||||
if m.targetTotalWidth != 0 {
|
|
||||||
m.totalWidth = m.targetTotalWidth
|
|
||||||
} else {
|
|
||||||
total := 0
|
|
||||||
|
|
||||||
for _, column := range m.columns {
|
|
||||||
total += column.width
|
|
||||||
}
|
|
||||||
|
|
||||||
m.totalWidth = total + len(m.columns) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
updateColumnWidths(m.columns, m.targetTotalWidth)
|
|
||||||
|
|
||||||
m.recalculateLastHorizontalColumn()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updates column width in-place. This could be optimized but should be called
|
|
||||||
// very rarely so we prioritize simplicity over performance here.
|
|
||||||
func updateColumnWidths(cols []Column, totalWidth int) {
|
|
||||||
totalFlexWidth := totalWidth - len(cols) - 1
|
|
||||||
totalFlexFactor := 0
|
|
||||||
flexGCD := 0
|
|
||||||
|
|
||||||
for index, col := range cols {
|
|
||||||
if !col.isFlex() {
|
|
||||||
totalFlexWidth -= col.width
|
|
||||||
cols[index].style = col.style.Width(col.width)
|
|
||||||
} else {
|
|
||||||
totalFlexFactor += col.flexFactor
|
|
||||||
flexGCD = gcd(flexGCD, col.flexFactor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalFlexFactor == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use the GCD here because otherwise very large values won't divide
|
|
||||||
// nicely as ints
|
|
||||||
totalFlexFactor /= flexGCD
|
|
||||||
|
|
||||||
flexUnit := totalFlexWidth / totalFlexFactor
|
|
||||||
leftoverWidth := totalFlexWidth % totalFlexFactor
|
|
||||||
|
|
||||||
for index := range cols {
|
|
||||||
if !cols[index].isFlex() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
width := flexUnit * (cols[index].flexFactor / flexGCD)
|
|
||||||
|
|
||||||
if leftoverWidth > 0 {
|
|
||||||
width++
|
|
||||||
leftoverWidth--
|
|
||||||
}
|
|
||||||
|
|
||||||
if index == len(cols)-1 {
|
|
||||||
width += leftoverWidth
|
|
||||||
leftoverWidth = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
width = max(width, 1)
|
|
||||||
|
|
||||||
cols[index].width = width
|
|
||||||
|
|
||||||
// Take borders into account for the actual style
|
|
||||||
cols[index].style = cols[index].style.Width(width)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) recalculateHeight() {
|
|
||||||
header := m.renderHeaders()
|
|
||||||
headerHeight := 1 // Header always has the top border
|
|
||||||
if m.headerVisible {
|
|
||||||
headerHeight = lipgloss.Height(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
footer := m.renderFooter(lipgloss.Width(header), false)
|
|
||||||
var footerHeight int
|
|
||||||
if footer != "" {
|
|
||||||
footerHeight = lipgloss.Height(footer)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.metaHeight = headerHeight + footerHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) calculatePadding(numRows int) int {
|
|
||||||
if m.minimumHeight == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
padding := m.minimumHeight - m.metaHeight - numRows - 1 // additional 1 for bottom border
|
|
||||||
|
|
||||||
if padding == 0 && numRows == 0 {
|
|
||||||
// This is an edge case where we want to add 1 additional line of height, i.e.
|
|
||||||
// add a border without an empty row. However, this is not possible, so we need
|
|
||||||
// to add an extra row which will result in the table being 1 row taller than
|
|
||||||
// the requested minimum height.
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if padding < 0 {
|
|
||||||
// Table is already larger than minimum height, do nothing.
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return padding
|
|
||||||
}
|
|
||||||
39
vendor/github.com/evertras/bubble-table/table/doc.go
generated
vendored
39
vendor/github.com/evertras/bubble-table/table/doc.go
generated
vendored
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
Package table contains a Bubble Tea component for an interactive and customizable
|
|
||||||
table.
|
|
||||||
|
|
||||||
The simplest useful table can be created with table.New(...).WithRows(...). Row
|
|
||||||
data should map to the column keys, as shown below. Note that extra data will
|
|
||||||
simply not be shown, while missing data will be safely blank in the row's cell.
|
|
||||||
|
|
||||||
const (
|
|
||||||
// This is not necessary, but recommended to avoid typos
|
|
||||||
columnKeyName = "name"
|
|
||||||
columnKeyCount = "count"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Define the columns and how they appear
|
|
||||||
columns := []table.Column{
|
|
||||||
table.NewColumn(columnKeyName, "Name", 10),
|
|
||||||
table.NewColumn(columnKeyCount, "Count", 6),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the data that will be in the table, mapping to the column keys
|
|
||||||
rows := []table.Row{
|
|
||||||
table.NewRow(table.RowData{
|
|
||||||
columnKeyName: "Cheeseburger",
|
|
||||||
columnKeyCount: 3,
|
|
||||||
}),
|
|
||||||
table.NewRow(table.RowData{
|
|
||||||
columnKeyName: "Fries",
|
|
||||||
columnKeyCount: 2,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the table
|
|
||||||
tbl := table.New(columns).WithRows(rows)
|
|
||||||
|
|
||||||
// Use it like any Bubble Tea component in your view
|
|
||||||
tbl.View()
|
|
||||||
*/
|
|
||||||
package table
|
|
||||||
60
vendor/github.com/evertras/bubble-table/table/events.go
generated
vendored
60
vendor/github.com/evertras/bubble-table/table/events.go
generated
vendored
@ -1,60 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
// UserEvent is some state change that has occurred due to user input. These will
|
|
||||||
// ONLY be generated when a user has interacted directly with the table. These
|
|
||||||
// will NOT be generated when code programmatically changes values in the table.
|
|
||||||
type UserEvent any
|
|
||||||
|
|
||||||
func (m *Model) appendUserEvent(e UserEvent) {
|
|
||||||
m.lastUpdateUserEvents = append(m.lastUpdateUserEvents, e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) clearUserEvents() {
|
|
||||||
m.lastUpdateUserEvents = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLastUpdateUserEvents returns a list of events that happened due to user
|
|
||||||
// input in the last Update call. This is useful to look for triggers such as
|
|
||||||
// whether the user moved to a new highlighted row.
|
|
||||||
func (m *Model) GetLastUpdateUserEvents() []UserEvent {
|
|
||||||
// Most common case
|
|
||||||
if len(m.lastUpdateUserEvents) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
returned := make([]UserEvent, len(m.lastUpdateUserEvents))
|
|
||||||
|
|
||||||
// Slightly wasteful but helps guarantee immutability, and this should only
|
|
||||||
// have data very rarely so this is fine
|
|
||||||
copy(returned, m.lastUpdateUserEvents)
|
|
||||||
|
|
||||||
return returned
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserEventHighlightedIndexChanged indicates that the user has scrolled to a new
|
|
||||||
// row.
|
|
||||||
type UserEventHighlightedIndexChanged struct {
|
|
||||||
// PreviousRow is the row that was selected before the change.
|
|
||||||
PreviousRowIndex int
|
|
||||||
|
|
||||||
// SelectedRow is the row index that is now selected
|
|
||||||
SelectedRowIndex int
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserEventRowSelectToggled indicates that the user has either selected or
|
|
||||||
// deselected a row by toggling the selection. The event contains information
|
|
||||||
// about which row index was selected and whether it was selected or deselected.
|
|
||||||
type UserEventRowSelectToggled struct {
|
|
||||||
RowIndex int
|
|
||||||
IsSelected bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserEventFilterInputFocused indicates that the user has focused the filter
|
|
||||||
// text input, so that any other typing will type into the filter field. Only
|
|
||||||
// activates for the built-in filter text box.
|
|
||||||
type UserEventFilterInputFocused struct{}
|
|
||||||
|
|
||||||
// UserEventFilterInputUnfocused indicates that the user has unfocused the filter
|
|
||||||
// text input, which means the user is done typing into the filter field. Only
|
|
||||||
// activates for the built-in filter text box.
|
|
||||||
type UserEventFilterInputUnfocused struct{}
|
|
||||||
164
vendor/github.com/evertras/bubble-table/table/filter.go
generated
vendored
164
vendor/github.com/evertras/bubble-table/table/filter.go
generated
vendored
@ -1,164 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FilterFuncInput is the input to a FilterFunc. It's a struct so we can add more things later
|
|
||||||
// without breaking compatibility.
|
|
||||||
type FilterFuncInput struct {
|
|
||||||
// Columns is a list of the columns of the table
|
|
||||||
Columns []Column
|
|
||||||
|
|
||||||
// Row is the row that's being considered for filtering
|
|
||||||
Row Row
|
|
||||||
|
|
||||||
// GlobalMetadata is an arbitrary set of metadata from the table set by WithGlobalMetadata
|
|
||||||
GlobalMetadata map[string]any
|
|
||||||
|
|
||||||
// Filter is the filter string input to consider
|
|
||||||
Filter string
|
|
||||||
}
|
|
||||||
|
|
||||||
// FilterFunc takes a FilterFuncInput and returns true if the row should be visible,
|
|
||||||
// or false if the row should be hidden.
|
|
||||||
type FilterFunc func(FilterFuncInput) bool
|
|
||||||
|
|
||||||
func (m Model) getFilteredRows(rows []Row) []Row {
|
|
||||||
filterInputValue := m.filterTextInput.Value()
|
|
||||||
if !m.filtered || filterInputValue == "" {
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
|
|
||||||
filteredRows := make([]Row, 0)
|
|
||||||
|
|
||||||
for _, row := range rows {
|
|
||||||
var availableFilterFunc FilterFunc
|
|
||||||
|
|
||||||
if m.filterFunc != nil {
|
|
||||||
availableFilterFunc = m.filterFunc
|
|
||||||
} else {
|
|
||||||
availableFilterFunc = filterFuncContains
|
|
||||||
}
|
|
||||||
|
|
||||||
if availableFilterFunc(FilterFuncInput{
|
|
||||||
Columns: m.columns,
|
|
||||||
Row: row,
|
|
||||||
Filter: filterInputValue,
|
|
||||||
GlobalMetadata: m.metadata,
|
|
||||||
}) {
|
|
||||||
filteredRows = append(filteredRows, row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredRows
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterFuncContains returns a filterFunc that performs case-insensitive
|
|
||||||
// "contains" matching over all filterable columns in a row.
|
|
||||||
func filterFuncContains(input FilterFuncInput) bool {
|
|
||||||
if input.Filter == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
checkedAny := false
|
|
||||||
|
|
||||||
filterLower := strings.ToLower(input.Filter)
|
|
||||||
|
|
||||||
for _, column := range input.Columns {
|
|
||||||
if !column.filterable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
checkedAny = true
|
|
||||||
|
|
||||||
data, ok := input.Row.Data[column.key]
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract internal StyledCell data
|
|
||||||
switch dataV := data.(type) {
|
|
||||||
case StyledCell:
|
|
||||||
data = dataV.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
var target string
|
|
||||||
switch dataV := data.(type) {
|
|
||||||
case string:
|
|
||||||
target = dataV
|
|
||||||
|
|
||||||
case fmt.Stringer:
|
|
||||||
target = dataV.String()
|
|
||||||
|
|
||||||
default:
|
|
||||||
target = fmt.Sprintf("%v", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(strings.ToLower(target), filterLower) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !checkedAny
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterFuncFuzzy returns a filterFunc that performs case-insensitive fuzzy
|
|
||||||
// matching (subsequence) over the concatenation of all filterable column values.
|
|
||||||
func filterFuncFuzzy(input FilterFuncInput) bool {
|
|
||||||
filter := strings.TrimSpace(input.Filter)
|
|
||||||
if filter == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var builder strings.Builder
|
|
||||||
for _, col := range input.Columns {
|
|
||||||
if !col.filterable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value, ok := input.Row.Data[col.key]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if sc, ok := value.(StyledCell); ok {
|
|
||||||
value = sc.Data
|
|
||||||
}
|
|
||||||
builder.WriteString(fmt.Sprint(value)) // uses Stringer if implemented
|
|
||||||
builder.WriteByte(' ')
|
|
||||||
}
|
|
||||||
|
|
||||||
haystack := strings.ToLower(builder.String())
|
|
||||||
if haystack == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, token := range strings.Fields(strings.ToLower(filter)) {
|
|
||||||
if !fuzzySubsequenceMatch(haystack, token) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// fuzzySubsequenceMatch returns true if all runes in needle appear in order
|
|
||||||
// within haystack (not necessarily contiguously). Case must be normalized by caller.
|
|
||||||
func fuzzySubsequenceMatch(haystack, needle string) bool {
|
|
||||||
if needle == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
haystackIndex, needleIndex := 0, 0
|
|
||||||
haystackRunes := []rune(haystack)
|
|
||||||
needleRunes := []rune(needle)
|
|
||||||
|
|
||||||
for haystackIndex < len(haystackRunes) && needleIndex < len(needleRunes) {
|
|
||||||
if haystackRunes[haystackIndex] == needleRunes[needleIndex] {
|
|
||||||
needleIndex++
|
|
||||||
}
|
|
||||||
haystackIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
return needleIndex == len(needleRunes)
|
|
||||||
}
|
|
||||||
51
vendor/github.com/evertras/bubble-table/table/footer.go
generated
vendored
51
vendor/github.com/evertras/bubble-table/table/footer.go
generated
vendored
@ -1,51 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m Model) hasFooter() bool {
|
|
||||||
return m.footerVisible && (m.staticFooter != "" || m.pageSize != 0 || m.filtered)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) renderFooter(width int, includeTop bool) string {
|
|
||||||
if !m.hasFooter() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const borderAdjustment = 2
|
|
||||||
|
|
||||||
styleFooter := m.baseStyle.Copy().Inherit(m.border.styleFooter).Width(width - borderAdjustment)
|
|
||||||
|
|
||||||
if includeTop {
|
|
||||||
styleFooter = styleFooter.BorderTop(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.staticFooter != "" {
|
|
||||||
return styleFooter.Render(m.staticFooter)
|
|
||||||
}
|
|
||||||
|
|
||||||
sections := []string{}
|
|
||||||
|
|
||||||
if m.filtered && (m.filterTextInput.Focused() || m.filterTextInput.Value() != "") {
|
|
||||||
sections = append(sections, m.filterTextInput.View())
|
|
||||||
}
|
|
||||||
|
|
||||||
// paged feature enabled
|
|
||||||
if m.pageSize != 0 {
|
|
||||||
str := fmt.Sprintf("%d/%d", m.CurrentPage(), m.MaxPages())
|
|
||||||
if m.filtered && m.filterTextInput.Focused() {
|
|
||||||
// Need to apply inline style here in case of filter input cursor, because
|
|
||||||
// the input cursor resets the style after rendering. Note that Inline(true)
|
|
||||||
// creates a copy, so it's safe to use here without mutating the underlying
|
|
||||||
// base style.
|
|
||||||
str = m.baseStyle.Inline(true).Render(str)
|
|
||||||
}
|
|
||||||
sections = append(sections, str)
|
|
||||||
}
|
|
||||||
|
|
||||||
footerText := strings.Join(sections, " ")
|
|
||||||
|
|
||||||
return styleFooter.Render(footerText)
|
|
||||||
}
|
|
||||||
93
vendor/github.com/evertras/bubble-table/table/header.go
generated
vendored
93
vendor/github.com/evertras/bubble-table/table/header.go
generated
vendored
@ -1,93 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import "github.com/charmbracelet/lipgloss"
|
|
||||||
|
|
||||||
// This is long and could use some refactoring in the future, but unsure of how
|
|
||||||
// to pick it apart right now.
|
|
||||||
//
|
|
||||||
//nolint:funlen,cyclop
|
|
||||||
func (m Model) renderHeaders() string {
|
|
||||||
headerStrings := []string{}
|
|
||||||
|
|
||||||
totalRenderedWidth := 0
|
|
||||||
|
|
||||||
headerStyles := m.styleHeaders()
|
|
||||||
|
|
||||||
renderHeader := func(column Column, borderStyle lipgloss.Style) string {
|
|
||||||
borderStyle = borderStyle.Inherit(column.style).Inherit(m.baseStyle)
|
|
||||||
|
|
||||||
headerSection := limitStr(column.title, column.width)
|
|
||||||
|
|
||||||
return borderStyle.Render(headerSection)
|
|
||||||
}
|
|
||||||
|
|
||||||
for columnIndex, column := range m.columns {
|
|
||||||
var borderStyle lipgloss.Style
|
|
||||||
|
|
||||||
if m.horizontalScrollOffsetCol > 0 && columnIndex == m.horizontalScrollFreezeColumnsCount {
|
|
||||||
if columnIndex == 0 {
|
|
||||||
borderStyle = headerStyles.left.Copy()
|
|
||||||
} else {
|
|
||||||
borderStyle = headerStyles.inner.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
rendered := renderHeader(genOverflowColumnLeft(1), borderStyle)
|
|
||||||
|
|
||||||
totalRenderedWidth += lipgloss.Width(rendered)
|
|
||||||
|
|
||||||
headerStrings = append(headerStrings, rendered)
|
|
||||||
}
|
|
||||||
|
|
||||||
if columnIndex >= m.horizontalScrollFreezeColumnsCount &&
|
|
||||||
columnIndex < m.horizontalScrollOffsetCol+m.horizontalScrollFreezeColumnsCount {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(headerStrings) == 0 {
|
|
||||||
borderStyle = headerStyles.left.Copy()
|
|
||||||
} else if columnIndex < len(m.columns)-1 {
|
|
||||||
borderStyle = headerStyles.inner.Copy()
|
|
||||||
} else {
|
|
||||||
borderStyle = headerStyles.right.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
rendered := renderHeader(column, borderStyle)
|
|
||||||
|
|
||||||
if m.maxTotalWidth != 0 {
|
|
||||||
renderedWidth := lipgloss.Width(rendered)
|
|
||||||
|
|
||||||
const (
|
|
||||||
borderAdjustment = 1
|
|
||||||
overflowColWidth = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
targetWidth := m.maxTotalWidth - overflowColWidth
|
|
||||||
|
|
||||||
if columnIndex == len(m.columns)-1 {
|
|
||||||
// If this is the last header, we don't need to account for the
|
|
||||||
// overflow arrow column
|
|
||||||
targetWidth = m.maxTotalWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalRenderedWidth+renderedWidth > targetWidth {
|
|
||||||
overflowWidth := m.maxTotalWidth - totalRenderedWidth - borderAdjustment
|
|
||||||
overflowStyle := genOverflowStyle(headerStyles.right, overflowWidth)
|
|
||||||
overflowColumn := genOverflowColumnRight(overflowWidth)
|
|
||||||
|
|
||||||
overflowStr := renderHeader(overflowColumn, overflowStyle)
|
|
||||||
|
|
||||||
headerStrings = append(headerStrings, overflowStr)
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
totalRenderedWidth += renderedWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
headerStrings = append(headerStrings, rendered)
|
|
||||||
}
|
|
||||||
|
|
||||||
headerBlock := lipgloss.JoinHorizontal(lipgloss.Bottom, headerStrings...)
|
|
||||||
|
|
||||||
return headerBlock
|
|
||||||
}
|
|
||||||
120
vendor/github.com/evertras/bubble-table/table/keys.go
generated
vendored
120
vendor/github.com/evertras/bubble-table/table/keys.go
generated
vendored
@ -1,120 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import "github.com/charmbracelet/bubbles/key"
|
|
||||||
|
|
||||||
// KeyMap defines the keybindings for the table when it's focused.
|
|
||||||
type KeyMap struct {
|
|
||||||
RowDown key.Binding
|
|
||||||
RowUp key.Binding
|
|
||||||
|
|
||||||
RowSelectToggle key.Binding
|
|
||||||
|
|
||||||
PageDown key.Binding
|
|
||||||
PageUp key.Binding
|
|
||||||
PageFirst key.Binding
|
|
||||||
PageLast key.Binding
|
|
||||||
|
|
||||||
// Filter allows the user to start typing and filter the rows.
|
|
||||||
Filter key.Binding
|
|
||||||
|
|
||||||
// FilterBlur is the key that stops the user's input from typing into the filter.
|
|
||||||
FilterBlur key.Binding
|
|
||||||
|
|
||||||
// FilterClear will clear the filter while it's blurred.
|
|
||||||
FilterClear key.Binding
|
|
||||||
|
|
||||||
// ScrollRight will move one column to the right when overflow occurs.
|
|
||||||
ScrollRight key.Binding
|
|
||||||
|
|
||||||
// ScrollLeft will move one column to the left when overflow occurs.
|
|
||||||
ScrollLeft key.Binding
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultKeyMap returns a set of sensible defaults for controlling a focused table with help text.
|
|
||||||
func DefaultKeyMap() KeyMap {
|
|
||||||
return KeyMap{
|
|
||||||
RowDown: key.NewBinding(
|
|
||||||
key.WithKeys("down", "j"),
|
|
||||||
key.WithHelp("↓/j", "move down"),
|
|
||||||
),
|
|
||||||
RowUp: key.NewBinding(
|
|
||||||
key.WithKeys("up", "k"),
|
|
||||||
key.WithHelp("↑/k", "move up"),
|
|
||||||
),
|
|
||||||
RowSelectToggle: key.NewBinding(
|
|
||||||
key.WithKeys(" ", "enter"),
|
|
||||||
key.WithHelp("<space>/enter", "select row"),
|
|
||||||
),
|
|
||||||
PageDown: key.NewBinding(
|
|
||||||
key.WithKeys("right", "l", "pgdown"),
|
|
||||||
key.WithHelp("→/h/page down", "next page"),
|
|
||||||
),
|
|
||||||
PageUp: key.NewBinding(
|
|
||||||
key.WithKeys("left", "h", "pgup"),
|
|
||||||
key.WithHelp("←/h/page up", "previous page"),
|
|
||||||
),
|
|
||||||
PageFirst: key.NewBinding(
|
|
||||||
key.WithKeys("home", "g"),
|
|
||||||
key.WithHelp("home/g", "first page"),
|
|
||||||
),
|
|
||||||
PageLast: key.NewBinding(
|
|
||||||
key.WithKeys("end", "G"),
|
|
||||||
key.WithHelp("end/G", "last page"),
|
|
||||||
),
|
|
||||||
Filter: key.NewBinding(
|
|
||||||
key.WithKeys("/"),
|
|
||||||
key.WithHelp("/", "filter"),
|
|
||||||
),
|
|
||||||
FilterBlur: key.NewBinding(
|
|
||||||
key.WithKeys("enter", "esc"),
|
|
||||||
key.WithHelp("enter/esc", "unfocus"),
|
|
||||||
),
|
|
||||||
FilterClear: key.NewBinding(
|
|
||||||
key.WithKeys("esc"),
|
|
||||||
key.WithHelp("esc", "clear filter"),
|
|
||||||
),
|
|
||||||
ScrollRight: key.NewBinding(
|
|
||||||
key.WithKeys("shift+right"),
|
|
||||||
key.WithHelp("shift+→", "scroll right"),
|
|
||||||
),
|
|
||||||
ScrollLeft: key.NewBinding(
|
|
||||||
key.WithKeys("shift+left"),
|
|
||||||
key.WithHelp("shift+←", "scroll left"),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FullHelp returns a multi row view of all the helpkeys that are defined. Needed to fullfil the 'help.Model' interface.
|
|
||||||
// Also appends all user defined extra keys to the help.
|
|
||||||
func (m Model) FullHelp() [][]key.Binding {
|
|
||||||
keyBinds := [][]key.Binding{
|
|
||||||
{m.keyMap.RowDown, m.keyMap.RowUp, m.keyMap.RowSelectToggle},
|
|
||||||
{m.keyMap.PageDown, m.keyMap.PageUp, m.keyMap.PageFirst, m.keyMap.PageLast},
|
|
||||||
{m.keyMap.Filter, m.keyMap.FilterBlur, m.keyMap.FilterClear, m.keyMap.ScrollRight, m.keyMap.ScrollLeft},
|
|
||||||
}
|
|
||||||
if m.additionalFullHelpKeys != nil {
|
|
||||||
keyBinds = append(keyBinds, m.additionalFullHelpKeys())
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyBinds
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShortHelp just returns a single row of help views. Needed to fullfil the 'help.Model' interface.
|
|
||||||
// Also appends all user defined extra keys to the help.
|
|
||||||
func (m Model) ShortHelp() []key.Binding {
|
|
||||||
keyBinds := []key.Binding{
|
|
||||||
m.keyMap.RowDown,
|
|
||||||
m.keyMap.RowUp,
|
|
||||||
m.keyMap.RowSelectToggle,
|
|
||||||
m.keyMap.PageDown,
|
|
||||||
m.keyMap.PageUp,
|
|
||||||
m.keyMap.Filter,
|
|
||||||
m.keyMap.FilterBlur,
|
|
||||||
m.keyMap.FilterClear,
|
|
||||||
}
|
|
||||||
if m.additionalShortHelpKeys != nil {
|
|
||||||
keyBinds = append(keyBinds, m.additionalShortHelpKeys()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyBinds
|
|
||||||
}
|
|
||||||
148
vendor/github.com/evertras/bubble-table/table/model.go
generated
vendored
148
vendor/github.com/evertras/bubble-table/table/model.go
generated
vendored
@ -1,148 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
columnKeySelect = "___select___"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultHighlightStyle = lipgloss.NewStyle().Background(lipgloss.Color("#334"))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Model is the main table model. Create using New().
|
|
||||||
type Model struct {
|
|
||||||
// Data
|
|
||||||
columns []Column
|
|
||||||
rows []Row
|
|
||||||
metadata map[string]any
|
|
||||||
|
|
||||||
// Caches for optimizations
|
|
||||||
visibleRowCacheUpdated bool
|
|
||||||
visibleRowCache []Row
|
|
||||||
|
|
||||||
// Shown when data is missing from a row
|
|
||||||
missingDataIndicator any
|
|
||||||
|
|
||||||
// Interaction
|
|
||||||
focused bool
|
|
||||||
keyMap KeyMap
|
|
||||||
|
|
||||||
// Taken from: 'Bubbles/List'
|
|
||||||
// Additional key mappings for the short and full help views. This allows
|
|
||||||
// you to add additional key mappings to the help menu without
|
|
||||||
// re-implementing the help component. Of course, you can also disable the
|
|
||||||
// list's help component and implement a new one if you need more
|
|
||||||
// flexibility.
|
|
||||||
// You have to supply a keybinding like this:
|
|
||||||
// key.NewBinding( key.WithKeys("shift+left"), key.WithHelp("shift+←", "scroll left"))
|
|
||||||
// It needs both 'WithKeys' and 'WithHelp'
|
|
||||||
additionalShortHelpKeys func() []key.Binding
|
|
||||||
additionalFullHelpKeys func() []key.Binding
|
|
||||||
|
|
||||||
selectableRows bool
|
|
||||||
rowCursorIndex int
|
|
||||||
|
|
||||||
// Events
|
|
||||||
lastUpdateUserEvents []UserEvent
|
|
||||||
|
|
||||||
// Styles
|
|
||||||
baseStyle lipgloss.Style
|
|
||||||
highlightStyle lipgloss.Style
|
|
||||||
headerStyle lipgloss.Style
|
|
||||||
rowStyleFunc func(RowStyleFuncInput) lipgloss.Style
|
|
||||||
border Border
|
|
||||||
selectedText string
|
|
||||||
unselectedText string
|
|
||||||
|
|
||||||
// Header
|
|
||||||
headerVisible bool
|
|
||||||
|
|
||||||
// Footers
|
|
||||||
footerVisible bool
|
|
||||||
staticFooter string
|
|
||||||
|
|
||||||
// Pagination
|
|
||||||
pageSize int
|
|
||||||
currentPage int
|
|
||||||
paginationWrapping bool
|
|
||||||
|
|
||||||
// Sorting, where a stable sort is applied from first element to last so
|
|
||||||
// that elements are grouped by the later elements.
|
|
||||||
sortOrder []SortColumn
|
|
||||||
|
|
||||||
// Filter
|
|
||||||
filtered bool
|
|
||||||
filterTextInput textinput.Model
|
|
||||||
filterFunc FilterFunc
|
|
||||||
|
|
||||||
// For flex columns
|
|
||||||
targetTotalWidth int
|
|
||||||
|
|
||||||
// The maximum total width for overflow/scrolling
|
|
||||||
maxTotalWidth int
|
|
||||||
|
|
||||||
// Internal cached calculations for reference, may be higher than
|
|
||||||
// maxTotalWidth. If this is the case, we need to adjust the view
|
|
||||||
totalWidth int
|
|
||||||
|
|
||||||
// How far to scroll to the right, in columns
|
|
||||||
horizontalScrollOffsetCol int
|
|
||||||
|
|
||||||
// How many columns to freeze when scrolling horizontally
|
|
||||||
horizontalScrollFreezeColumnsCount int
|
|
||||||
|
|
||||||
// Calculated maximum column we can scroll to before the last is displayed
|
|
||||||
maxHorizontalColumnIndex int
|
|
||||||
|
|
||||||
// Minimum total height of the table
|
|
||||||
minimumHeight int
|
|
||||||
|
|
||||||
// Internal cached calculation, the height of the header and footer
|
|
||||||
// including borders. Used to determine how many padding rows to add.
|
|
||||||
metaHeight int
|
|
||||||
|
|
||||||
// If true, the table will be multiline
|
|
||||||
multiline bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// New creates a new table ready for further modifications.
|
|
||||||
func New(columns []Column) Model {
|
|
||||||
filterInput := textinput.New()
|
|
||||||
filterInput.Prompt = "/"
|
|
||||||
model := Model{
|
|
||||||
columns: make([]Column, len(columns)),
|
|
||||||
metadata: make(map[string]any),
|
|
||||||
highlightStyle: defaultHighlightStyle.Copy(),
|
|
||||||
border: borderDefault,
|
|
||||||
headerVisible: true,
|
|
||||||
footerVisible: true,
|
|
||||||
keyMap: DefaultKeyMap(),
|
|
||||||
|
|
||||||
selectedText: "[x]",
|
|
||||||
unselectedText: "[ ]",
|
|
||||||
|
|
||||||
filterTextInput: filterInput,
|
|
||||||
filterFunc: filterFuncContains,
|
|
||||||
baseStyle: lipgloss.NewStyle().Align(lipgloss.Right),
|
|
||||||
|
|
||||||
paginationWrapping: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do a full deep copy to avoid unexpected edits
|
|
||||||
copy(model.columns, columns)
|
|
||||||
|
|
||||||
model.recalculateWidth()
|
|
||||||
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init initializes the table per the Bubble Tea architecture.
|
|
||||||
func (m Model) Init() tea.Cmd {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
510
vendor/github.com/evertras/bubble-table/table/options.go
generated
vendored
510
vendor/github.com/evertras/bubble-table/table/options.go
generated
vendored
@ -1,510 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RowStyleFuncInput is the input to the style function that can
|
|
||||||
// be applied to each row. This is useful for things like zebra
|
|
||||||
// striping or other data-based styles.
|
|
||||||
//
|
|
||||||
// Note that we use a struct here to allow for future expansion
|
|
||||||
// while keeping backwards compatibility.
|
|
||||||
type RowStyleFuncInput struct {
|
|
||||||
// Index is the index of the row, starting at 0.
|
|
||||||
Index int
|
|
||||||
|
|
||||||
// Row is the full row data.
|
|
||||||
Row Row
|
|
||||||
|
|
||||||
// IsHighlighted is true if the row is currently highlighted.
|
|
||||||
IsHighlighted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRowStyleFunc sets a function that can be used to apply a style to each row
|
|
||||||
// based on the row data. This is useful for things like zebra striping or other
|
|
||||||
// data-based styles. It can be safely set to nil to remove it later.
|
|
||||||
// This style is applied after the base style and before individual row styles.
|
|
||||||
// This will override any HighlightStyle settings.
|
|
||||||
func (m Model) WithRowStyleFunc(f func(RowStyleFuncInput) lipgloss.Style) Model {
|
|
||||||
m.rowStyleFunc = f
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHighlightedRow sets the highlighted row to the given index.
|
|
||||||
func (m Model) WithHighlightedRow(index int) Model {
|
|
||||||
m.rowCursorIndex = index
|
|
||||||
|
|
||||||
if m.rowCursorIndex >= len(m.GetVisibleRows()) {
|
|
||||||
m.rowCursorIndex = len(m.GetVisibleRows()) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.rowCursorIndex < 0 {
|
|
||||||
m.rowCursorIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
m.currentPage = m.expectedPageForRowIndex(m.rowCursorIndex)
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderStyle sets the style to apply to the header text, such as color or bold.
|
|
||||||
func (m Model) HeaderStyle(style lipgloss.Style) Model {
|
|
||||||
m.headerStyle = style.Copy()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithRows sets the rows to show as data in the table.
|
|
||||||
func (m Model) WithRows(rows []Row) Model {
|
|
||||||
m.rows = rows
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
if m.rowCursorIndex >= len(m.rows) {
|
|
||||||
m.rowCursorIndex = len(m.rows) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.rowCursorIndex < 0 {
|
|
||||||
m.rowCursorIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.pageSize != 0 {
|
|
||||||
maxPage := m.MaxPages()
|
|
||||||
|
|
||||||
// MaxPages is 1-index, currentPage is 0 index
|
|
||||||
if maxPage <= m.currentPage {
|
|
||||||
m.pageLast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithKeyMap sets the key map to use for controls when focused.
|
|
||||||
func (m Model) WithKeyMap(keyMap KeyMap) Model {
|
|
||||||
m.keyMap = keyMap
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeyMap returns a copy of the current key map in use.
|
|
||||||
func (m Model) KeyMap() KeyMap {
|
|
||||||
return m.keyMap
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectableRows sets whether or not rows are selectable. If set, adds a column
|
|
||||||
// in the front that acts as a checkbox and responds to controls if Focused.
|
|
||||||
func (m Model) SelectableRows(selectable bool) Model {
|
|
||||||
m.selectableRows = selectable
|
|
||||||
|
|
||||||
hasSelectColumn := len(m.columns) > 0 && m.columns[0].key == columnKeySelect
|
|
||||||
|
|
||||||
if hasSelectColumn != selectable {
|
|
||||||
if selectable {
|
|
||||||
m.columns = append([]Column{
|
|
||||||
NewColumn(columnKeySelect, m.selectedText, len([]rune(m.selectedText))),
|
|
||||||
}, m.columns...)
|
|
||||||
} else {
|
|
||||||
m.columns = m.columns[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.recalculateWidth()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// HighlightedRow returns the full Row that's currently highlighted by the user.
|
|
||||||
func (m Model) HighlightedRow() Row {
|
|
||||||
if len(m.GetVisibleRows()) > 0 {
|
|
||||||
return m.GetVisibleRows()[m.rowCursorIndex]
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Better way to do this without pointers/nil? Or should it be nil?
|
|
||||||
return Row{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelectedRows returns all rows that have been set as selected by the user.
|
|
||||||
func (m Model) SelectedRows() []Row {
|
|
||||||
selectedRows := []Row{}
|
|
||||||
|
|
||||||
for _, row := range m.GetVisibleRows() {
|
|
||||||
if row.selected {
|
|
||||||
selectedRows = append(selectedRows, row)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return selectedRows
|
|
||||||
}
|
|
||||||
|
|
||||||
// HighlightStyle sets a custom style to use when the row is being highlighted
|
|
||||||
// by the cursor. This should not be used with WithRowStyleFunc. Instead, use
|
|
||||||
// the IsHighlighted field in the style function.
|
|
||||||
func (m Model) HighlightStyle(style lipgloss.Style) Model {
|
|
||||||
m.highlightStyle = style
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focused allows the table to show highlighted rows and take in controls of
|
|
||||||
// up/down/space/etc to let the user navigate the table and interact with it.
|
|
||||||
func (m Model) Focused(focused bool) Model {
|
|
||||||
m.focused = focused
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filtered allows the table to show rows that match the filter.
|
|
||||||
func (m Model) Filtered(filtered bool) Model {
|
|
||||||
m.filtered = filtered
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
if m.minimumHeight > 0 {
|
|
||||||
m.recalculateHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartFilterTyping focuses the text input to allow user typing to filter.
|
|
||||||
func (m Model) StartFilterTyping() Model {
|
|
||||||
m.filterTextInput.Focus()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStaticFooter adds a footer that only displays the given text.
|
|
||||||
func (m Model) WithStaticFooter(footer string) Model {
|
|
||||||
m.staticFooter = footer
|
|
||||||
|
|
||||||
if m.minimumHeight > 0 {
|
|
||||||
m.recalculateHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPageSize enables pagination using the given page size. This can be called
|
|
||||||
// again at any point to resize the height of the table.
|
|
||||||
func (m Model) WithPageSize(pageSize int) Model {
|
|
||||||
m.pageSize = pageSize
|
|
||||||
|
|
||||||
maxPages := m.MaxPages()
|
|
||||||
|
|
||||||
if m.currentPage >= maxPages {
|
|
||||||
m.currentPage = maxPages - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.minimumHeight > 0 {
|
|
||||||
m.recalculateHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNoPagination disables pagination in the table.
|
|
||||||
func (m Model) WithNoPagination() Model {
|
|
||||||
m.pageSize = 0
|
|
||||||
|
|
||||||
if m.minimumHeight > 0 {
|
|
||||||
m.recalculateHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithPaginationWrapping sets whether to wrap around from the beginning to the
|
|
||||||
// end when navigating through pages. Defaults to true.
|
|
||||||
func (m Model) WithPaginationWrapping(wrapping bool) Model {
|
|
||||||
m.paginationWrapping = wrapping
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithSelectedText describes what text to show when selectable rows are enabled.
|
|
||||||
// The selectable column header will use the selected text string.
|
|
||||||
func (m Model) WithSelectedText(unselected, selected string) Model {
|
|
||||||
m.selectedText = selected
|
|
||||||
m.unselectedText = unselected
|
|
||||||
|
|
||||||
if len(m.columns) > 0 && m.columns[0].key == columnKeySelect {
|
|
||||||
m.columns[0] = NewColumn(columnKeySelect, m.selectedText, len([]rune(m.selectedText)))
|
|
||||||
m.recalculateWidth()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithBaseStyle applies a base style as the default for everything in the table.
|
|
||||||
// This is useful for border colors, default alignment, default color, etc.
|
|
||||||
func (m Model) WithBaseStyle(style lipgloss.Style) Model {
|
|
||||||
m.baseStyle = style
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithTargetWidth sets the total target width of the table, including borders.
|
|
||||||
// This only takes effect when using flex columns. When using flex columns,
|
|
||||||
// columns will stretch to fill out to the total width given here.
|
|
||||||
func (m Model) WithTargetWidth(totalWidth int) Model {
|
|
||||||
m.targetTotalWidth = totalWidth
|
|
||||||
|
|
||||||
m.recalculateWidth()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMinimumHeight sets the minimum total height of the table, including borders.
|
|
||||||
func (m Model) WithMinimumHeight(minimumHeight int) Model {
|
|
||||||
m.minimumHeight = minimumHeight
|
|
||||||
|
|
||||||
m.recalculateHeight()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageDown goes to the next page of a paginated table, wrapping to the first
|
|
||||||
// page if the table is already on the last page.
|
|
||||||
func (m Model) PageDown() Model {
|
|
||||||
m.pageDown()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageUp goes to the previous page of a paginated table, wrapping to the
|
|
||||||
// last page if the table is already on the first page.
|
|
||||||
func (m Model) PageUp() Model {
|
|
||||||
m.pageUp()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageLast goes to the last page of a paginated table.
|
|
||||||
func (m Model) PageLast() Model {
|
|
||||||
m.pageLast()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageFirst goes to the first page of a paginated table.
|
|
||||||
func (m Model) PageFirst() Model {
|
|
||||||
m.pageFirst()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithCurrentPage sets the current page (1 as the first page) of a paginated
|
|
||||||
// table, bounded to the total number of pages. The current selected row will
|
|
||||||
// be set to the top row of the page if the page changed.
|
|
||||||
func (m Model) WithCurrentPage(currentPage int) Model {
|
|
||||||
if m.pageSize == 0 || currentPage == m.CurrentPage() {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
if currentPage < 1 {
|
|
||||||
currentPage = 1
|
|
||||||
} else {
|
|
||||||
maxPages := m.MaxPages()
|
|
||||||
|
|
||||||
if currentPage > maxPages {
|
|
||||||
currentPage = maxPages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.currentPage = currentPage - 1
|
|
||||||
m.rowCursorIndex = m.currentPage * m.pageSize
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithColumns sets the visible columns for the table, so that columns can be
|
|
||||||
// added/removed/resized or headers rewritten.
|
|
||||||
func (m Model) WithColumns(columns []Column) Model {
|
|
||||||
// Deep copy to avoid edits
|
|
||||||
m.columns = make([]Column, len(columns))
|
|
||||||
copy(m.columns, columns)
|
|
||||||
|
|
||||||
m.recalculateWidth()
|
|
||||||
|
|
||||||
if m.selectableRows {
|
|
||||||
// Re-add the selectable column
|
|
||||||
m = m.SelectableRows(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFilterInput makes the table use the provided text input bubble for
|
|
||||||
// filtering rather than using the built-in default. This allows for external
|
|
||||||
// text input controls to be used.
|
|
||||||
func (m Model) WithFilterInput(input textinput.Model) Model {
|
|
||||||
if m.filterTextInput.Value() != input.Value() {
|
|
||||||
m.pageFirst()
|
|
||||||
}
|
|
||||||
|
|
||||||
m.filterTextInput = input
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFilterInputValue sets the filter value to the given string, immediately
|
|
||||||
// applying it as if the user had typed it in. Useful for external filter inputs
|
|
||||||
// that are not necessarily a text input.
|
|
||||||
func (m Model) WithFilterInputValue(value string) Model {
|
|
||||||
if m.filterTextInput.Value() != value {
|
|
||||||
m.pageFirst()
|
|
||||||
}
|
|
||||||
|
|
||||||
m.filterTextInput.SetValue(value)
|
|
||||||
m.filterTextInput.Blur()
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFilterFunc adds a filter function to the model. If the function returns
|
|
||||||
// true, the row will be included in the filtered results. If the function
|
|
||||||
// is nil, the function won't be used and instead the default filtering will be applied,
|
|
||||||
// if any.
|
|
||||||
func (m Model) WithFilterFunc(shouldInclude FilterFunc) Model {
|
|
||||||
m.filterFunc = shouldInclude
|
|
||||||
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFuzzyFilter enables fuzzy filtering for the table.
|
|
||||||
func (m Model) WithFuzzyFilter() Model {
|
|
||||||
return m.WithFilterFunc(filterFuncFuzzy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFooterVisibility sets the visibility of the footer.
|
|
||||||
func (m Model) WithFooterVisibility(visibility bool) Model {
|
|
||||||
m.footerVisible = visibility
|
|
||||||
|
|
||||||
if m.minimumHeight > 0 {
|
|
||||||
m.recalculateHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHeaderVisibility sets the visibility of the header.
|
|
||||||
func (m Model) WithHeaderVisibility(visibility bool) Model {
|
|
||||||
m.headerVisible = visibility
|
|
||||||
|
|
||||||
if m.minimumHeight > 0 {
|
|
||||||
m.recalculateHeight()
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxTotalWidth sets the maximum total width that the table should render.
|
|
||||||
// If this width is exceeded by either the target width or by the total width
|
|
||||||
// of all the columns (including borders!), anything extra will be treated as
|
|
||||||
// overflow and horizontal scrolling will be enabled to see the rest.
|
|
||||||
func (m Model) WithMaxTotalWidth(maxTotalWidth int) Model {
|
|
||||||
m.maxTotalWidth = maxTotalWidth
|
|
||||||
|
|
||||||
m.recalculateWidth()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithHorizontalFreezeColumnCount freezes the given number of columns to the
|
|
||||||
// left side. This is useful for things like ID or Name columns that should
|
|
||||||
// always be visible even when scrolling.
|
|
||||||
func (m Model) WithHorizontalFreezeColumnCount(columnsToFreeze int) Model {
|
|
||||||
m.horizontalScrollFreezeColumnsCount = columnsToFreeze
|
|
||||||
|
|
||||||
m.recalculateWidth()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScrollRight moves one column to the right. Use with WithMaxTotalWidth.
|
|
||||||
func (m Model) ScrollRight() Model {
|
|
||||||
m.scrollRight()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScrollLeft moves one column to the left. Use with WithMaxTotalWidth.
|
|
||||||
func (m Model) ScrollLeft() Model {
|
|
||||||
m.scrollLeft()
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMissingDataIndicator sets an indicator to use when data for a column is
|
|
||||||
// not found in a given row. Note that this is for completely missing data,
|
|
||||||
// an empty string or other zero value that is explicitly set is not considered
|
|
||||||
// to be missing.
|
|
||||||
func (m Model) WithMissingDataIndicator(str string) Model {
|
|
||||||
m.missingDataIndicator = str
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMissingDataIndicatorStyled sets a styled indicator to use when data for
|
|
||||||
// a column is not found in a given row. Note that this is for completely
|
|
||||||
// missing data, an empty string or other zero value that is explicitly set is
|
|
||||||
// not considered to be missing.
|
|
||||||
func (m Model) WithMissingDataIndicatorStyled(styled StyledCell) Model {
|
|
||||||
m.missingDataIndicator = styled
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAllRowsDeselected deselects any rows that are currently selected.
|
|
||||||
func (m Model) WithAllRowsDeselected() Model {
|
|
||||||
rows := m.GetVisibleRows()
|
|
||||||
|
|
||||||
for i, row := range rows {
|
|
||||||
if row.selected {
|
|
||||||
rows[i] = row.Selected(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.rows = rows
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMultiline sets whether or not to wrap text in cells to multiple lines.
|
|
||||||
func (m Model) WithMultiline(multiline bool) Model {
|
|
||||||
m.multiline = multiline
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAdditionalShortHelpKeys enables you to add more keybindings to the 'short help' view.
|
|
||||||
func (m Model) WithAdditionalShortHelpKeys(keys []key.Binding) Model {
|
|
||||||
m.additionalShortHelpKeys = func() []key.Binding {
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithAdditionalFullHelpKeys enables you to add more keybindings to the 'full help' view.
|
|
||||||
func (m Model) WithAdditionalFullHelpKeys(keys []key.Binding) Model {
|
|
||||||
m.additionalFullHelpKeys = func() []key.Binding {
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithGlobalMetadata applies the given metadata to the table. This metadata is passed to
|
|
||||||
// some functions in FilterFuncInput and StyleFuncInput to enable more advanced decisions,
|
|
||||||
// such as setting some global theme variable to reference, etc. Has no effect otherwise.
|
|
||||||
func (m Model) WithGlobalMetadata(metadata map[string]any) Model {
|
|
||||||
m.metadata = metadata
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
18
vendor/github.com/evertras/bubble-table/table/overflow.go
generated
vendored
18
vendor/github.com/evertras/bubble-table/table/overflow.go
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import "github.com/charmbracelet/lipgloss"
|
|
||||||
|
|
||||||
const columnKeyOverflowRight = "___overflow_r___"
|
|
||||||
const columnKeyOverflowLeft = "___overflow_l__"
|
|
||||||
|
|
||||||
func genOverflowStyle(base lipgloss.Style, width int) lipgloss.Style {
|
|
||||||
return base.Width(width).Align(lipgloss.Right)
|
|
||||||
}
|
|
||||||
|
|
||||||
func genOverflowColumnRight(width int) Column {
|
|
||||||
return NewColumn(columnKeyOverflowRight, ">", width)
|
|
||||||
}
|
|
||||||
|
|
||||||
func genOverflowColumnLeft(width int) Column {
|
|
||||||
return NewColumn(columnKeyOverflowLeft, "<", width)
|
|
||||||
}
|
|
||||||
112
vendor/github.com/evertras/bubble-table/table/pagination.go
generated
vendored
112
vendor/github.com/evertras/bubble-table/table/pagination.go
generated
vendored
@ -1,112 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
// PageSize returns the current page size for the table, or 0 if there is no
|
|
||||||
// pagination enabled.
|
|
||||||
func (m *Model) PageSize() int {
|
|
||||||
return m.pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentPage returns the current page that the table is on, starting from an
|
|
||||||
// index of 1.
|
|
||||||
func (m *Model) CurrentPage() int {
|
|
||||||
return m.currentPage + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxPages returns the maximum number of pages that are visible.
|
|
||||||
func (m *Model) MaxPages() int {
|
|
||||||
totalRows := len(m.GetVisibleRows())
|
|
||||||
|
|
||||||
if m.pageSize == 0 || totalRows == 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return (totalRows-1)/m.pageSize + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// TotalRows returns the current total row count of the table. If the table is
|
|
||||||
// paginated, this is the total number of rows across all pages.
|
|
||||||
func (m *Model) TotalRows() int {
|
|
||||||
return len(m.GetVisibleRows())
|
|
||||||
}
|
|
||||||
|
|
||||||
// VisibleIndices returns the current visible rows by their 0 based index.
|
|
||||||
// Useful for custom pagination footers.
|
|
||||||
func (m *Model) VisibleIndices() (start, end int) {
|
|
||||||
totalRows := len(m.GetVisibleRows())
|
|
||||||
|
|
||||||
if m.pageSize == 0 {
|
|
||||||
start = 0
|
|
||||||
end = totalRows - 1
|
|
||||||
|
|
||||||
return start, end
|
|
||||||
}
|
|
||||||
|
|
||||||
start = m.pageSize * m.currentPage
|
|
||||||
end = start + m.pageSize - 1
|
|
||||||
|
|
||||||
if end >= totalRows {
|
|
||||||
end = totalRows - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return start, end
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) pageDown() {
|
|
||||||
if m.pageSize == 0 || len(m.GetVisibleRows()) <= m.pageSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.currentPage++
|
|
||||||
|
|
||||||
maxPageIndex := m.MaxPages() - 1
|
|
||||||
|
|
||||||
if m.currentPage > maxPageIndex {
|
|
||||||
if m.paginationWrapping {
|
|
||||||
m.currentPage = 0
|
|
||||||
} else {
|
|
||||||
m.currentPage = maxPageIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.rowCursorIndex = m.currentPage * m.pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) pageUp() {
|
|
||||||
if m.pageSize == 0 || len(m.GetVisibleRows()) <= m.pageSize {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
m.currentPage--
|
|
||||||
|
|
||||||
maxPageIndex := m.MaxPages() - 1
|
|
||||||
|
|
||||||
if m.currentPage < 0 {
|
|
||||||
if m.paginationWrapping {
|
|
||||||
m.currentPage = maxPageIndex
|
|
||||||
} else {
|
|
||||||
m.currentPage = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.rowCursorIndex = m.currentPage * m.pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) pageFirst() {
|
|
||||||
m.currentPage = 0
|
|
||||||
m.rowCursorIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) pageLast() {
|
|
||||||
m.currentPage = m.MaxPages() - 1
|
|
||||||
m.rowCursorIndex = m.currentPage * m.pageSize
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) expectedPageForRowIndex(rowIndex int) int {
|
|
||||||
if m.pageSize == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedPage := rowIndex / m.pageSize
|
|
||||||
|
|
||||||
return expectedPage
|
|
||||||
}
|
|
||||||
96
vendor/github.com/evertras/bubble-table/table/query.go
generated
vendored
96
vendor/github.com/evertras/bubble-table/table/query.go
generated
vendored
@ -1,96 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
// GetColumnSorting returns the current sorting rules for the table as a list of
|
|
||||||
// SortColumns, which are applied from first to last. This means that data will
|
|
||||||
// be grouped by the later elements in the list. The returned list is a copy
|
|
||||||
// and modifications will have no effect.
|
|
||||||
func (m *Model) GetColumnSorting() []SortColumn {
|
|
||||||
c := make([]SortColumn, len(m.sortOrder))
|
|
||||||
|
|
||||||
copy(c, m.sortOrder)
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCanFilter returns true if the table enables filtering at all. This does
|
|
||||||
// not say whether a filter is currently active, only that the feature is enabled.
|
|
||||||
func (m *Model) GetCanFilter() bool {
|
|
||||||
return m.filtered
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIsFilterActive returns true if the table is currently being filtered. This
|
|
||||||
// does not say whether the table CAN be filtered, only whether or not a filter
|
|
||||||
// is actually currently being applied.
|
|
||||||
func (m *Model) GetIsFilterActive() bool {
|
|
||||||
return m.filterTextInput.Value() != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetIsFilterInputFocused returns true if the table's built-in filter input is
|
|
||||||
// currently focused.
|
|
||||||
func (m *Model) GetIsFilterInputFocused() bool {
|
|
||||||
return m.filterTextInput.Focused()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentFilter returns the current filter text being applied, or an empty
|
|
||||||
// string if none is applied.
|
|
||||||
func (m *Model) GetCurrentFilter() string {
|
|
||||||
return m.filterTextInput.Value()
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVisibleRows returns sorted and filtered rows.
|
|
||||||
func (m *Model) GetVisibleRows() []Row {
|
|
||||||
if m.visibleRowCacheUpdated {
|
|
||||||
return m.visibleRowCache
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := make([]Row, len(m.rows))
|
|
||||||
copy(rows, m.rows)
|
|
||||||
if m.filtered {
|
|
||||||
rows = m.getFilteredRows(rows)
|
|
||||||
}
|
|
||||||
rows = getSortedRows(m.sortOrder, rows)
|
|
||||||
|
|
||||||
m.visibleRowCache = rows
|
|
||||||
m.visibleRowCacheUpdated = true
|
|
||||||
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHighlightedRowIndex returns the index of the Row that's currently highlighted
|
|
||||||
// by the user.
|
|
||||||
func (m *Model) GetHighlightedRowIndex() int {
|
|
||||||
return m.rowCursorIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFocused returns whether or not the table is focused and is receiving inputs.
|
|
||||||
func (m *Model) GetFocused() bool {
|
|
||||||
return m.focused
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHorizontalScrollColumnOffset returns how many columns to the right the table
|
|
||||||
// has been scrolled. 0 means the table is all the way to the left, which is
|
|
||||||
// the starting default.
|
|
||||||
func (m *Model) GetHorizontalScrollColumnOffset() int {
|
|
||||||
return m.horizontalScrollOffsetCol
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeaderVisibility returns true if the header has been set to visible (default)
|
|
||||||
// or false if the header has been set to hidden.
|
|
||||||
func (m *Model) GetHeaderVisibility() bool {
|
|
||||||
return m.headerVisible
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFooterVisibility returns true if the footer has been set to
|
|
||||||
// visible (default) or false if the footer has been set to hidden.
|
|
||||||
// Note that even if the footer is visible it will only be rendered if
|
|
||||||
// it has contents.
|
|
||||||
func (m *Model) GetFooterVisibility() bool {
|
|
||||||
return m.footerVisible
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPaginationWrapping returns true if pagination wrapping is enabled, or false
|
|
||||||
// if disabled. If disabled, navigating through pages will stop at the first
|
|
||||||
// and last pages.
|
|
||||||
func (m *Model) GetPaginationWrapping() bool {
|
|
||||||
return m.paginationWrapping
|
|
||||||
}
|
|
||||||
252
vendor/github.com/evertras/bubble-table/table/row.go
generated
vendored
252
vendor/github.com/evertras/bubble-table/table/row.go
generated
vendored
@ -1,252 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
"github.com/muesli/reflow/wordwrap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RowData is a map of string column keys to arbitrary data. Data with a key
|
|
||||||
// that matches a column key will be displayed. Data with a key that does not
|
|
||||||
// match a column key will not be displayed, but will remain attached to the Row.
|
|
||||||
// This can be useful for attaching hidden metadata for future reference when
|
|
||||||
// retrieving rows.
|
|
||||||
type RowData map[string]any
|
|
||||||
|
|
||||||
// Row represents a row in the table with some data keyed to the table columns>
|
|
||||||
// Can have a style applied to it such as color/bold. Create using NewRow().
|
|
||||||
type Row struct {
|
|
||||||
Style lipgloss.Style
|
|
||||||
Data RowData
|
|
||||||
|
|
||||||
selected bool
|
|
||||||
|
|
||||||
// id is an internal unique ID to match rows after they're copied
|
|
||||||
id uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastRowID uint32 = 1
|
|
||||||
|
|
||||||
// NewRow creates a new row and copies the given row data.
|
|
||||||
func NewRow(data RowData) Row {
|
|
||||||
row := Row{
|
|
||||||
Data: make(map[string]any),
|
|
||||||
id: lastRowID,
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddUint32(&lastRowID, 1)
|
|
||||||
|
|
||||||
for key, val := range data {
|
|
||||||
// Doesn't deep copy val, but close enough for now...
|
|
||||||
row.Data[key] = val
|
|
||||||
}
|
|
||||||
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithStyle uses the given style for the text in the row.
|
|
||||||
func (r Row) WithStyle(style lipgloss.Style) Row {
|
|
||||||
r.Style = style.Copy()
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
//nolint:cyclop,funlen // Breaking this up will be more complicated than it's worth for now
|
|
||||||
func (m Model) renderRowColumnData(row Row, column Column, rowStyle lipgloss.Style, borderStyle lipgloss.Style) string {
|
|
||||||
cellStyle := rowStyle.Copy().Inherit(column.style).Inherit(m.baseStyle)
|
|
||||||
|
|
||||||
var str string
|
|
||||||
|
|
||||||
switch column.key {
|
|
||||||
case columnKeySelect:
|
|
||||||
if row.selected {
|
|
||||||
str = m.selectedText
|
|
||||||
} else {
|
|
||||||
str = m.unselectedText
|
|
||||||
}
|
|
||||||
case columnKeyOverflowRight:
|
|
||||||
cellStyle = cellStyle.Align(lipgloss.Right)
|
|
||||||
str = ">"
|
|
||||||
case columnKeyOverflowLeft:
|
|
||||||
str = "<"
|
|
||||||
default:
|
|
||||||
fmtString := "%v"
|
|
||||||
|
|
||||||
var data any
|
|
||||||
|
|
||||||
if entry, exists := row.Data[column.key]; exists {
|
|
||||||
data = entry
|
|
||||||
|
|
||||||
if column.fmtString != "" {
|
|
||||||
fmtString = column.fmtString
|
|
||||||
}
|
|
||||||
} else if m.missingDataIndicator != nil {
|
|
||||||
data = m.missingDataIndicator
|
|
||||||
} else {
|
|
||||||
data = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
switch entry := data.(type) {
|
|
||||||
case StyledCell:
|
|
||||||
str = fmt.Sprintf(fmtString, entry.Data)
|
|
||||||
|
|
||||||
if entry.StyleFunc != nil {
|
|
||||||
cellStyle = entry.StyleFunc(StyledCellFuncInput{
|
|
||||||
Column: column,
|
|
||||||
Data: entry.Data,
|
|
||||||
Row: row,
|
|
||||||
GlobalMetadata: m.metadata,
|
|
||||||
}).Copy().Inherit(cellStyle)
|
|
||||||
} else {
|
|
||||||
cellStyle = entry.Style.Copy().Inherit(cellStyle)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
str = fmt.Sprintf(fmtString, entry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.multiline {
|
|
||||||
str = wordwrap.String(str, column.width)
|
|
||||||
cellStyle = cellStyle.Align(lipgloss.Top)
|
|
||||||
} else {
|
|
||||||
str = limitStr(str, column.width)
|
|
||||||
}
|
|
||||||
|
|
||||||
cellStyle = cellStyle.Inherit(borderStyle)
|
|
||||||
cellStr := cellStyle.Render(str)
|
|
||||||
|
|
||||||
return cellStr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) renderRow(rowIndex int, last bool) string {
|
|
||||||
row := m.GetVisibleRows()[rowIndex]
|
|
||||||
highlighted := rowIndex == m.rowCursorIndex
|
|
||||||
|
|
||||||
rowStyle := row.Style.Copy()
|
|
||||||
|
|
||||||
if m.rowStyleFunc != nil {
|
|
||||||
styleResult := m.rowStyleFunc(RowStyleFuncInput{
|
|
||||||
Index: rowIndex,
|
|
||||||
Row: row,
|
|
||||||
IsHighlighted: m.focused && highlighted,
|
|
||||||
})
|
|
||||||
|
|
||||||
rowStyle = rowStyle.Inherit(styleResult)
|
|
||||||
} else if m.focused && highlighted {
|
|
||||||
rowStyle = rowStyle.Inherit(m.highlightStyle)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.renderRowData(row, rowStyle, last)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) renderBlankRow(last bool) string {
|
|
||||||
return m.renderRowData(NewRow(nil), lipgloss.NewStyle(), last)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is long and could use some refactoring in the future, but not quite sure
|
|
||||||
// how to pick it apart yet.
|
|
||||||
//
|
|
||||||
//nolint:funlen, cyclop
|
|
||||||
func (m Model) renderRowData(row Row, rowStyle lipgloss.Style, last bool) string {
|
|
||||||
numColumns := len(m.columns)
|
|
||||||
|
|
||||||
columnStrings := []string{}
|
|
||||||
totalRenderedWidth := 0
|
|
||||||
|
|
||||||
stylesInner, stylesLast := m.styleRows()
|
|
||||||
|
|
||||||
maxCellHeight := 1
|
|
||||||
if m.multiline {
|
|
||||||
for _, column := range m.columns {
|
|
||||||
cellStr := m.renderRowColumnData(row, column, rowStyle, lipgloss.NewStyle())
|
|
||||||
maxCellHeight = max(maxCellHeight, lipgloss.Height(cellStr))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for columnIndex, column := range m.columns {
|
|
||||||
var borderStyle lipgloss.Style
|
|
||||||
var rowStyles borderStyleRow
|
|
||||||
|
|
||||||
if !last {
|
|
||||||
rowStyles = stylesInner
|
|
||||||
} else {
|
|
||||||
rowStyles = stylesLast
|
|
||||||
}
|
|
||||||
rowStyle = rowStyle.Copy().Height(maxCellHeight)
|
|
||||||
|
|
||||||
if m.horizontalScrollOffsetCol > 0 && columnIndex == m.horizontalScrollFreezeColumnsCount {
|
|
||||||
var borderStyle lipgloss.Style
|
|
||||||
|
|
||||||
if columnIndex == 0 {
|
|
||||||
borderStyle = rowStyles.left.Copy()
|
|
||||||
} else {
|
|
||||||
borderStyle = rowStyles.inner.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
rendered := m.renderRowColumnData(row, genOverflowColumnLeft(1), rowStyle, borderStyle)
|
|
||||||
|
|
||||||
totalRenderedWidth += lipgloss.Width(rendered)
|
|
||||||
|
|
||||||
columnStrings = append(columnStrings, rendered)
|
|
||||||
}
|
|
||||||
|
|
||||||
if columnIndex >= m.horizontalScrollFreezeColumnsCount &&
|
|
||||||
columnIndex < m.horizontalScrollOffsetCol+m.horizontalScrollFreezeColumnsCount {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(columnStrings) == 0 {
|
|
||||||
borderStyle = rowStyles.left
|
|
||||||
} else if columnIndex < numColumns-1 {
|
|
||||||
borderStyle = rowStyles.inner
|
|
||||||
} else {
|
|
||||||
borderStyle = rowStyles.right
|
|
||||||
}
|
|
||||||
|
|
||||||
cellStr := m.renderRowColumnData(row, column, rowStyle, borderStyle)
|
|
||||||
|
|
||||||
if m.maxTotalWidth != 0 {
|
|
||||||
renderedWidth := lipgloss.Width(cellStr)
|
|
||||||
|
|
||||||
const (
|
|
||||||
borderAdjustment = 1
|
|
||||||
overflowColWidth = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
targetWidth := m.maxTotalWidth - overflowColWidth
|
|
||||||
|
|
||||||
if columnIndex == len(m.columns)-1 {
|
|
||||||
// If this is the last header, we don't need to account for the
|
|
||||||
// overflow arrow column
|
|
||||||
targetWidth = m.maxTotalWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
if totalRenderedWidth+renderedWidth > targetWidth {
|
|
||||||
overflowWidth := m.maxTotalWidth - totalRenderedWidth - borderAdjustment
|
|
||||||
overflowStyle := genOverflowStyle(rowStyles.right, overflowWidth)
|
|
||||||
overflowColumn := genOverflowColumnRight(overflowWidth)
|
|
||||||
overflowStr := m.renderRowColumnData(row, overflowColumn, rowStyle, overflowStyle)
|
|
||||||
|
|
||||||
columnStrings = append(columnStrings, overflowStr)
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
totalRenderedWidth += renderedWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
columnStrings = append(columnStrings, cellStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lipgloss.JoinHorizontal(lipgloss.Bottom, columnStrings...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selected returns a copy of the row that's set to be selected or deselected.
|
|
||||||
// The old row is not changed in-place.
|
|
||||||
func (r Row) Selected(selected bool) Row {
|
|
||||||
r.selected = selected
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
50
vendor/github.com/evertras/bubble-table/table/scrolling.go
generated
vendored
50
vendor/github.com/evertras/bubble-table/table/scrolling.go
generated
vendored
@ -1,50 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
func (m *Model) scrollRight() {
|
|
||||||
if m.horizontalScrollOffsetCol < m.maxHorizontalColumnIndex {
|
|
||||||
m.horizontalScrollOffsetCol++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) scrollLeft() {
|
|
||||||
if m.horizontalScrollOffsetCol > 0 {
|
|
||||||
m.horizontalScrollOffsetCol--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) recalculateLastHorizontalColumn() {
|
|
||||||
if m.horizontalScrollFreezeColumnsCount >= len(m.columns) {
|
|
||||||
m.maxHorizontalColumnIndex = 0
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.totalWidth <= m.maxTotalWidth {
|
|
||||||
m.maxHorizontalColumnIndex = 0
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
leftOverflowWidth = 2
|
|
||||||
borderAdjustment = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// Always have left border
|
|
||||||
visibleWidth := borderAdjustment + leftOverflowWidth
|
|
||||||
|
|
||||||
for i := 0; i < m.horizontalScrollFreezeColumnsCount; i++ {
|
|
||||||
visibleWidth += m.columns[i].width + borderAdjustment
|
|
||||||
}
|
|
||||||
|
|
||||||
m.maxHorizontalColumnIndex = len(m.columns) - 1
|
|
||||||
|
|
||||||
// Work backwards from the right
|
|
||||||
for i := len(m.columns) - 1; i >= m.horizontalScrollFreezeColumnsCount && visibleWidth <= m.maxTotalWidth; i-- {
|
|
||||||
visibleWidth += m.columns[i].width + borderAdjustment
|
|
||||||
|
|
||||||
if visibleWidth <= m.maxTotalWidth {
|
|
||||||
m.maxHorizontalColumnIndex = i - m.horizontalScrollFreezeColumnsCount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
178
vendor/github.com/evertras/bubble-table/table/sort.go
generated
vendored
178
vendor/github.com/evertras/bubble-table/table/sort.go
generated
vendored
@ -1,178 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SortDirection indicates whether a column should sort by ascending or descending.
|
|
||||||
type SortDirection int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// SortDirectionAsc indicates the column should be in ascending order.
|
|
||||||
SortDirectionAsc SortDirection = iota
|
|
||||||
|
|
||||||
// SortDirectionDesc indicates the column should be in descending order.
|
|
||||||
SortDirectionDesc
|
|
||||||
)
|
|
||||||
|
|
||||||
// SortColumn describes which column should be sorted and how.
|
|
||||||
type SortColumn struct {
|
|
||||||
ColumnKey string
|
|
||||||
Direction SortDirection
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortByAsc sets the main sorting column to the given key, in ascending order.
|
|
||||||
// If a previous sort was used, it is replaced by the given column each time
|
|
||||||
// this function is called. Values are sorted as numbers if possible, or just
|
|
||||||
// as simple string comparisons if not numbers.
|
|
||||||
func (m Model) SortByAsc(columnKey string) Model {
|
|
||||||
m.sortOrder = []SortColumn{
|
|
||||||
{
|
|
||||||
ColumnKey: columnKey,
|
|
||||||
Direction: SortDirectionAsc,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// SortByDesc sets the main sorting column to the given key, in descending order.
|
|
||||||
// If a previous sort was used, it is replaced by the given column each time
|
|
||||||
// this function is called. Values are sorted as numbers if possible, or just
|
|
||||||
// as simple string comparisons if not numbers.
|
|
||||||
func (m Model) SortByDesc(columnKey string) Model {
|
|
||||||
m.sortOrder = []SortColumn{
|
|
||||||
{
|
|
||||||
ColumnKey: columnKey,
|
|
||||||
Direction: SortDirectionDesc,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThenSortByAsc provides a secondary sort after the first, in ascending order.
|
|
||||||
// Can be chained multiple times, applying to smaller subgroups each time.
|
|
||||||
func (m Model) ThenSortByAsc(columnKey string) Model {
|
|
||||||
m.sortOrder = append([]SortColumn{
|
|
||||||
{
|
|
||||||
ColumnKey: columnKey,
|
|
||||||
Direction: SortDirectionAsc,
|
|
||||||
},
|
|
||||||
}, m.sortOrder...)
|
|
||||||
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ThenSortByDesc provides a secondary sort after the first, in descending order.
|
|
||||||
// Can be chained multiple times, applying to smaller subgroups each time.
|
|
||||||
func (m Model) ThenSortByDesc(columnKey string) Model {
|
|
||||||
m.sortOrder = append([]SortColumn{
|
|
||||||
{
|
|
||||||
ColumnKey: columnKey,
|
|
||||||
Direction: SortDirectionDesc,
|
|
||||||
},
|
|
||||||
}, m.sortOrder...)
|
|
||||||
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
type sortableTable struct {
|
|
||||||
rows []Row
|
|
||||||
byColumn SortColumn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sortableTable) Len() int {
|
|
||||||
return len(s.rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sortableTable) Swap(i, j int) {
|
|
||||||
old := s.rows[i]
|
|
||||||
s.rows[i] = s.rows[j]
|
|
||||||
s.rows[j] = old
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sortableTable) extractString(i int, column string) string {
|
|
||||||
iData, exists := s.rows[i].Data[column]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
switch iData := iData.(type) {
|
|
||||||
case StyledCell:
|
|
||||||
return fmt.Sprintf("%v", iData.Data)
|
|
||||||
|
|
||||||
case string:
|
|
||||||
return iData
|
|
||||||
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("%v", iData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sortableTable) extractNumber(i int, column string) (float64, bool) {
|
|
||||||
iData, exists := s.rows[i].Data[column]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
return asNumber(iData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sortableTable) Less(first, second int) bool {
|
|
||||||
firstNum, firstNumIsValid := s.extractNumber(first, s.byColumn.ColumnKey)
|
|
||||||
secondNum, secondNumIsValid := s.extractNumber(second, s.byColumn.ColumnKey)
|
|
||||||
|
|
||||||
if firstNumIsValid && secondNumIsValid {
|
|
||||||
if s.byColumn.Direction == SortDirectionAsc {
|
|
||||||
return firstNum < secondNum
|
|
||||||
}
|
|
||||||
|
|
||||||
return firstNum > secondNum
|
|
||||||
}
|
|
||||||
|
|
||||||
firstVal := s.extractString(first, s.byColumn.ColumnKey)
|
|
||||||
secondVal := s.extractString(second, s.byColumn.ColumnKey)
|
|
||||||
|
|
||||||
if s.byColumn.Direction == SortDirectionAsc {
|
|
||||||
return firstVal < secondVal
|
|
||||||
}
|
|
||||||
|
|
||||||
return firstVal > secondVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSortedRows(sortOrder []SortColumn, rows []Row) []Row {
|
|
||||||
var sortedRows []Row
|
|
||||||
if len(sortOrder) == 0 {
|
|
||||||
sortedRows = rows
|
|
||||||
|
|
||||||
return sortedRows
|
|
||||||
}
|
|
||||||
|
|
||||||
sortedRows = make([]Row, len(rows))
|
|
||||||
copy(sortedRows, rows)
|
|
||||||
|
|
||||||
for _, byColumn := range sortOrder {
|
|
||||||
sorted := &sortableTable{
|
|
||||||
rows: sortedRows,
|
|
||||||
byColumn: byColumn,
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Stable(sorted)
|
|
||||||
|
|
||||||
sortedRows = sorted.rows
|
|
||||||
}
|
|
||||||
|
|
||||||
return sortedRows
|
|
||||||
}
|
|
||||||
26
vendor/github.com/evertras/bubble-table/table/strlimit.go
generated
vendored
26
vendor/github.com/evertras/bubble-table/table/strlimit.go
generated
vendored
@ -1,26 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/muesli/reflow/ansi"
|
|
||||||
"github.com/muesli/reflow/truncate"
|
|
||||||
)
|
|
||||||
|
|
||||||
func limitStr(str string, maxLen int) string {
|
|
||||||
if maxLen == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
newLineIndex := strings.Index(str, "\n")
|
|
||||||
if newLineIndex > -1 {
|
|
||||||
str = str[:newLineIndex] + "…"
|
|
||||||
}
|
|
||||||
|
|
||||||
if ansi.PrintableRuneWidth(str) > maxLen {
|
|
||||||
// #nosec: G115
|
|
||||||
return truncate.StringWithTail(str, uint(maxLen), "…")
|
|
||||||
}
|
|
||||||
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
154
vendor/github.com/evertras/bubble-table/table/update.go
generated
vendored
154
vendor/github.com/evertras/bubble-table/table/update.go
generated
vendored
@ -1,154 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/charmbracelet/bubbles/key"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (m *Model) moveHighlightUp() {
|
|
||||||
m.rowCursorIndex--
|
|
||||||
|
|
||||||
if m.rowCursorIndex < 0 {
|
|
||||||
m.rowCursorIndex = len(m.GetVisibleRows()) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
m.currentPage = m.expectedPageForRowIndex(m.rowCursorIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) moveHighlightDown() {
|
|
||||||
m.rowCursorIndex++
|
|
||||||
|
|
||||||
if m.rowCursorIndex >= len(m.GetVisibleRows()) {
|
|
||||||
m.rowCursorIndex = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
m.currentPage = m.expectedPageForRowIndex(m.rowCursorIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Model) toggleSelect() {
|
|
||||||
if !m.selectableRows || len(m.GetVisibleRows()) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := m.GetVisibleRows()
|
|
||||||
|
|
||||||
rowID := rows[m.rowCursorIndex].id
|
|
||||||
|
|
||||||
currentSelectedState := false
|
|
||||||
|
|
||||||
for i := range m.rows {
|
|
||||||
if m.rows[i].id == rowID {
|
|
||||||
currentSelectedState = m.rows[i].selected
|
|
||||||
m.rows[i].selected = !m.rows[i].selected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
m.appendUserEvent(UserEventRowSelectToggled{
|
|
||||||
RowIndex: m.rowCursorIndex,
|
|
||||||
IsSelected: !currentSelectedState,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) updateFilterTextInput(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
var cmd tea.Cmd
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
if key.Matches(msg, m.keyMap.FilterBlur) {
|
|
||||||
m.filterTextInput.Blur()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m.filterTextInput, cmd = m.filterTextInput.Update(msg)
|
|
||||||
m.pageFirst()
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a series of Matches tests with minimal logic
|
|
||||||
//
|
|
||||||
//nolint:cyclop
|
|
||||||
func (m *Model) handleKeypress(msg tea.KeyMsg) {
|
|
||||||
previousRowIndex := m.rowCursorIndex
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.RowDown) {
|
|
||||||
m.moveHighlightDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.RowUp) {
|
|
||||||
m.moveHighlightUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.RowSelectToggle) {
|
|
||||||
m.toggleSelect()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.PageDown) {
|
|
||||||
m.pageDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.PageUp) {
|
|
||||||
m.pageUp()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.PageFirst) {
|
|
||||||
m.pageFirst()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.PageLast) {
|
|
||||||
m.pageLast()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.Filter) {
|
|
||||||
m.filterTextInput.Focus()
|
|
||||||
m.appendUserEvent(UserEventFilterInputFocused{})
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.FilterClear) {
|
|
||||||
m.visibleRowCacheUpdated = false
|
|
||||||
m.filterTextInput.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.ScrollRight) {
|
|
||||||
m.scrollRight()
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Matches(msg, m.keyMap.ScrollLeft) {
|
|
||||||
m.scrollLeft()
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.rowCursorIndex != previousRowIndex {
|
|
||||||
m.appendUserEvent(UserEventHighlightedIndexChanged{
|
|
||||||
PreviousRowIndex: previousRowIndex,
|
|
||||||
SelectedRowIndex: m.rowCursorIndex,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update responds to input from the user or other messages from Bubble Tea.
|
|
||||||
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
|
||||||
m.clearUserEvents()
|
|
||||||
|
|
||||||
if !m.focused {
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if m.filterTextInput.Focused() {
|
|
||||||
var cmd tea.Cmd
|
|
||||||
m, cmd = m.updateFilterTextInput(msg)
|
|
||||||
|
|
||||||
if !m.filterTextInput.Focused() {
|
|
||||||
m.appendUserEvent(UserEventFilterInputUnfocused{})
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
m.handleKeypress(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
65
vendor/github.com/evertras/bubble-table/table/view.go
generated
vendored
65
vendor/github.com/evertras/bubble-table/table/view.go
generated
vendored
@ -1,65 +0,0 @@
|
|||||||
package table
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
|
||||||
)
|
|
||||||
|
|
||||||
// View renders the table. It does not end in a newline, so that it can be
|
|
||||||
// composed with other elements more consistently.
|
|
||||||
//
|
|
||||||
//nolint:cyclop
|
|
||||||
func (m Model) View() string {
|
|
||||||
// Safety valve for empty tables
|
|
||||||
if len(m.columns) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
body := strings.Builder{}
|
|
||||||
|
|
||||||
rowStrs := make([]string, 0, 1)
|
|
||||||
|
|
||||||
headers := m.renderHeaders()
|
|
||||||
|
|
||||||
startRowIndex, endRowIndex := m.VisibleIndices()
|
|
||||||
numRows := endRowIndex - startRowIndex + 1
|
|
||||||
|
|
||||||
padding := m.calculatePadding(numRows)
|
|
||||||
|
|
||||||
if m.headerVisible {
|
|
||||||
rowStrs = append(rowStrs, headers)
|
|
||||||
} else if numRows > 0 || padding > 0 {
|
|
||||||
//nolint: mnd // This is just getting the first newlined substring
|
|
||||||
split := strings.SplitN(headers, "\n", 2)
|
|
||||||
rowStrs = append(rowStrs, split[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := startRowIndex; i <= endRowIndex; i++ {
|
|
||||||
rowStrs = append(rowStrs, m.renderRow(i, padding == 0 && i == endRowIndex))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i <= padding; i++ {
|
|
||||||
rowStrs = append(rowStrs, m.renderBlankRow(i == padding))
|
|
||||||
}
|
|
||||||
|
|
||||||
var footer string
|
|
||||||
|
|
||||||
if len(rowStrs) > 0 {
|
|
||||||
footer = m.renderFooter(lipgloss.Width(rowStrs[0]), false)
|
|
||||||
} else {
|
|
||||||
footer = m.renderFooter(lipgloss.Width(headers), true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if footer != "" {
|
|
||||||
rowStrs = append(rowStrs, footer)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rowStrs) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
body.WriteString(lipgloss.JoinVertical(lipgloss.Left, rowStrs...))
|
|
||||||
|
|
||||||
return body.String()
|
|
||||||
}
|
|
||||||
21
vendor/github.com/muesli/reflow/LICENSE
generated
vendored
21
vendor/github.com/muesli/reflow/LICENSE
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2019 Christian Muehlhaeuser
|
|
||||||
|
|
||||||
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.
|
|
||||||
7
vendor/github.com/muesli/reflow/ansi/ansi.go
generated
vendored
7
vendor/github.com/muesli/reflow/ansi/ansi.go
generated
vendored
@ -1,7 +0,0 @@
|
|||||||
package ansi
|
|
||||||
|
|
||||||
const Marker = '\x1B'
|
|
||||||
|
|
||||||
func IsTerminator(c rune) bool {
|
|
||||||
return (c >= 0x40 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a)
|
|
||||||
}
|
|
||||||
40
vendor/github.com/muesli/reflow/ansi/buffer.go
generated
vendored
40
vendor/github.com/muesli/reflow/ansi/buffer.go
generated
vendored
@ -1,40 +0,0 @@
|
|||||||
package ansi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buffer is a buffer aware of ANSI escape sequences.
|
|
||||||
type Buffer struct {
|
|
||||||
bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintableRuneWidth returns the cell width of all printable runes in the
|
|
||||||
// buffer.
|
|
||||||
func (w Buffer) PrintableRuneWidth() int {
|
|
||||||
return PrintableRuneWidth(w.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintableRuneWidth returns the cell width of the given string.
|
|
||||||
func PrintableRuneWidth(s string) int {
|
|
||||||
var n int
|
|
||||||
var ansi bool
|
|
||||||
|
|
||||||
for _, c := range s {
|
|
||||||
if c == Marker {
|
|
||||||
// ANSI escape sequence
|
|
||||||
ansi = true
|
|
||||||
} else if ansi {
|
|
||||||
if IsTerminator(c) {
|
|
||||||
// ANSI sequence terminated
|
|
||||||
ansi = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
n += runewidth.RuneWidth(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
76
vendor/github.com/muesli/reflow/ansi/writer.go
generated
vendored
76
vendor/github.com/muesli/reflow/ansi/writer.go
generated
vendored
@ -1,76 +0,0 @@
|
|||||||
package ansi
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Writer struct {
|
|
||||||
Forward io.Writer
|
|
||||||
|
|
||||||
ansi bool
|
|
||||||
ansiseq bytes.Buffer
|
|
||||||
lastseq bytes.Buffer
|
|
||||||
seqchanged bool
|
|
||||||
runeBuf []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write is used to write content to the ANSI buffer.
|
|
||||||
func (w *Writer) Write(b []byte) (int, error) {
|
|
||||||
for _, c := range string(b) {
|
|
||||||
if c == Marker {
|
|
||||||
// ANSI escape sequence
|
|
||||||
w.ansi = true
|
|
||||||
w.seqchanged = true
|
|
||||||
_, _ = w.ansiseq.WriteRune(c)
|
|
||||||
} else if w.ansi {
|
|
||||||
_, _ = w.ansiseq.WriteRune(c)
|
|
||||||
if IsTerminator(c) {
|
|
||||||
// ANSI sequence terminated
|
|
||||||
w.ansi = false
|
|
||||||
|
|
||||||
if bytes.HasSuffix(w.ansiseq.Bytes(), []byte("[0m")) {
|
|
||||||
// reset sequence
|
|
||||||
w.lastseq.Reset()
|
|
||||||
w.seqchanged = false
|
|
||||||
} else if c == 'm' {
|
|
||||||
// color code
|
|
||||||
_, _ = w.lastseq.Write(w.ansiseq.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = w.ansiseq.WriteTo(w.Forward)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err := w.writeRune(c)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) writeRune(r rune) (int, error) {
|
|
||||||
if w.runeBuf == nil {
|
|
||||||
w.runeBuf = make([]byte, utf8.UTFMax)
|
|
||||||
}
|
|
||||||
n := utf8.EncodeRune(w.runeBuf, r)
|
|
||||||
return w.Forward.Write(w.runeBuf[:n])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) LastSequence() string {
|
|
||||||
return w.lastseq.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) ResetAnsi() {
|
|
||||||
if !w.seqchanged {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, _ = w.Forward.Write([]byte("\x1b[0m"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) RestoreAnsi() {
|
|
||||||
_, _ = w.Forward.Write(w.lastseq.Bytes())
|
|
||||||
}
|
|
||||||
120
vendor/github.com/muesli/reflow/truncate/truncate.go
generated
vendored
120
vendor/github.com/muesli/reflow/truncate/truncate.go
generated
vendored
@ -1,120 +0,0 @@
|
|||||||
package truncate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
|
|
||||||
"github.com/muesli/reflow/ansi"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Writer struct {
|
|
||||||
width uint
|
|
||||||
tail string
|
|
||||||
|
|
||||||
ansiWriter *ansi.Writer
|
|
||||||
buf bytes.Buffer
|
|
||||||
ansi bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWriter(width uint, tail string) *Writer {
|
|
||||||
w := &Writer{
|
|
||||||
width: width,
|
|
||||||
tail: tail,
|
|
||||||
}
|
|
||||||
w.ansiWriter = &ansi.Writer{
|
|
||||||
Forward: &w.buf,
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWriterPipe(forward io.Writer, width uint, tail string) *Writer {
|
|
||||||
return &Writer{
|
|
||||||
width: width,
|
|
||||||
tail: tail,
|
|
||||||
ansiWriter: &ansi.Writer{
|
|
||||||
Forward: forward,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes is shorthand for declaring a new default truncate-writer instance,
|
|
||||||
// used to immediately truncate a byte slice.
|
|
||||||
func Bytes(b []byte, width uint) []byte {
|
|
||||||
return BytesWithTail(b, width, []byte(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes is shorthand for declaring a new default truncate-writer instance,
|
|
||||||
// used to immediately truncate a byte slice. A tail is then added to the
|
|
||||||
// end of the byte slice.
|
|
||||||
func BytesWithTail(b []byte, width uint, tail []byte) []byte {
|
|
||||||
f := NewWriter(width, string(tail))
|
|
||||||
_, _ = f.Write(b)
|
|
||||||
|
|
||||||
return f.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// String is shorthand for declaring a new default truncate-writer instance,
|
|
||||||
// used to immediately truncate a string.
|
|
||||||
func String(s string, width uint) string {
|
|
||||||
return StringWithTail(s, width, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringWithTail is shorthand for declaring a new default truncate-writer instance,
|
|
||||||
// used to immediately truncate a string. A tail is then added to the end of the
|
|
||||||
// string.
|
|
||||||
func StringWithTail(s string, width uint, tail string) string {
|
|
||||||
return string(BytesWithTail([]byte(s), width, []byte(tail)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write truncates content at the given printable cell width, leaving any
|
|
||||||
// ansi sequences intact.
|
|
||||||
func (w *Writer) Write(b []byte) (int, error) {
|
|
||||||
tw := ansi.PrintableRuneWidth(w.tail)
|
|
||||||
if w.width < uint(tw) {
|
|
||||||
return w.buf.WriteString(w.tail)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.width -= uint(tw)
|
|
||||||
var curWidth uint
|
|
||||||
|
|
||||||
for _, c := range string(b) {
|
|
||||||
if c == ansi.Marker {
|
|
||||||
// ANSI escape sequence
|
|
||||||
w.ansi = true
|
|
||||||
} else if w.ansi {
|
|
||||||
if ansi.IsTerminator(c) {
|
|
||||||
// ANSI sequence terminated
|
|
||||||
w.ansi = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
curWidth += uint(runewidth.RuneWidth(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
if curWidth > w.width {
|
|
||||||
n, err := w.buf.WriteString(w.tail)
|
|
||||||
if w.ansiWriter.LastSequence() != "" {
|
|
||||||
w.ansiWriter.ResetAnsi()
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := w.ansiWriter.Write([]byte(string(c)))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the truncated result as a byte slice.
|
|
||||||
func (w *Writer) Bytes() []byte {
|
|
||||||
return w.buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the truncated result as a string.
|
|
||||||
func (w *Writer) String() string {
|
|
||||||
return w.buf.String()
|
|
||||||
}
|
|
||||||
167
vendor/github.com/muesli/reflow/wordwrap/wordwrap.go
generated
vendored
167
vendor/github.com/muesli/reflow/wordwrap/wordwrap.go
generated
vendored
@ -1,167 +0,0 @@
|
|||||||
package wordwrap
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/muesli/reflow/ansi"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultBreakpoints = []rune{'-'}
|
|
||||||
defaultNewline = []rune{'\n'}
|
|
||||||
)
|
|
||||||
|
|
||||||
// WordWrap contains settings and state for customisable text reflowing with
|
|
||||||
// support for ANSI escape sequences. This means you can style your terminal
|
|
||||||
// output without affecting the word wrapping algorithm.
|
|
||||||
type WordWrap struct {
|
|
||||||
Limit int
|
|
||||||
Breakpoints []rune
|
|
||||||
Newline []rune
|
|
||||||
KeepNewlines bool
|
|
||||||
|
|
||||||
buf bytes.Buffer
|
|
||||||
space bytes.Buffer
|
|
||||||
word ansi.Buffer
|
|
||||||
|
|
||||||
lineLen int
|
|
||||||
ansi bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWriter returns a new instance of a word-wrapping writer, initialized with
|
|
||||||
// default settings.
|
|
||||||
func NewWriter(limit int) *WordWrap {
|
|
||||||
return &WordWrap{
|
|
||||||
Limit: limit,
|
|
||||||
Breakpoints: defaultBreakpoints,
|
|
||||||
Newline: defaultNewline,
|
|
||||||
KeepNewlines: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes is shorthand for declaring a new default WordWrap instance,
|
|
||||||
// used to immediately word-wrap a byte slice.
|
|
||||||
func Bytes(b []byte, limit int) []byte {
|
|
||||||
f := NewWriter(limit)
|
|
||||||
_, _ = f.Write(b)
|
|
||||||
_ = f.Close()
|
|
||||||
|
|
||||||
return f.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// String is shorthand for declaring a new default WordWrap instance,
|
|
||||||
// used to immediately word-wrap a string.
|
|
||||||
func String(s string, limit int) string {
|
|
||||||
return string(Bytes([]byte(s), limit))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WordWrap) addSpace() {
|
|
||||||
w.lineLen += w.space.Len()
|
|
||||||
_, _ = w.buf.Write(w.space.Bytes())
|
|
||||||
w.space.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WordWrap) addWord() {
|
|
||||||
if w.word.Len() > 0 {
|
|
||||||
w.addSpace()
|
|
||||||
w.lineLen += w.word.PrintableRuneWidth()
|
|
||||||
_, _ = w.buf.Write(w.word.Bytes())
|
|
||||||
w.word.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *WordWrap) addNewLine() {
|
|
||||||
_, _ = w.buf.WriteRune('\n')
|
|
||||||
w.lineLen = 0
|
|
||||||
w.space.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func inGroup(a []rune, c rune) bool {
|
|
||||||
for _, v := range a {
|
|
||||||
if v == c {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write is used to write more content to the word-wrap buffer.
|
|
||||||
func (w *WordWrap) Write(b []byte) (int, error) {
|
|
||||||
if w.Limit == 0 {
|
|
||||||
return w.buf.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
s := string(b)
|
|
||||||
if !w.KeepNewlines {
|
|
||||||
s = strings.Replace(strings.TrimSpace(s), "\n", " ", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range s {
|
|
||||||
if c == '\x1B' {
|
|
||||||
// ANSI escape sequence
|
|
||||||
_, _ = w.word.WriteRune(c)
|
|
||||||
w.ansi = true
|
|
||||||
} else if w.ansi {
|
|
||||||
_, _ = w.word.WriteRune(c)
|
|
||||||
if (c >= 0x40 && c <= 0x5a) || (c >= 0x61 && c <= 0x7a) {
|
|
||||||
// ANSI sequence terminated
|
|
||||||
w.ansi = false
|
|
||||||
}
|
|
||||||
} else if inGroup(w.Newline, c) {
|
|
||||||
// end of current line
|
|
||||||
// see if we can add the content of the space buffer to the current line
|
|
||||||
if w.word.Len() == 0 {
|
|
||||||
if w.lineLen+w.space.Len() > w.Limit {
|
|
||||||
w.lineLen = 0
|
|
||||||
} else {
|
|
||||||
// preserve whitespace
|
|
||||||
_, _ = w.buf.Write(w.space.Bytes())
|
|
||||||
}
|
|
||||||
w.space.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
w.addWord()
|
|
||||||
w.addNewLine()
|
|
||||||
} else if unicode.IsSpace(c) {
|
|
||||||
// end of current word
|
|
||||||
w.addWord()
|
|
||||||
_, _ = w.space.WriteRune(c)
|
|
||||||
} else if inGroup(w.Breakpoints, c) {
|
|
||||||
// valid breakpoint
|
|
||||||
w.addSpace()
|
|
||||||
w.addWord()
|
|
||||||
_, _ = w.buf.WriteRune(c)
|
|
||||||
} else {
|
|
||||||
// any other character
|
|
||||||
_, _ = w.word.WriteRune(c)
|
|
||||||
|
|
||||||
// add a line break if the current word would exceed the line's
|
|
||||||
// character limit
|
|
||||||
if w.lineLen+w.space.Len()+w.word.PrintableRuneWidth() > w.Limit &&
|
|
||||||
w.word.PrintableRuneWidth() < w.Limit {
|
|
||||||
w.addNewLine()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close will finish the word-wrap operation. Always call it before trying to
|
|
||||||
// retrieve the final result.
|
|
||||||
func (w *WordWrap) Close() error {
|
|
||||||
w.addWord()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes returns the word-wrapped result as a byte slice.
|
|
||||||
func (w *WordWrap) Bytes() []byte {
|
|
||||||
return w.buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the word-wrapped result as a string.
|
|
||||||
func (w *WordWrap) String() string {
|
|
||||||
return w.buf.String()
|
|
||||||
}
|
|
||||||
16
vendor/modules.txt
vendored
16
vendor/modules.txt
vendored
@ -51,9 +51,6 @@ github.com/ProtonMail/go-crypto/openpgp/packet
|
|||||||
github.com/ProtonMail/go-crypto/openpgp/s2k
|
github.com/ProtonMail/go-crypto/openpgp/s2k
|
||||||
github.com/ProtonMail/go-crypto/openpgp/x25519
|
github.com/ProtonMail/go-crypto/openpgp/x25519
|
||||||
github.com/ProtonMail/go-crypto/openpgp/x448
|
github.com/ProtonMail/go-crypto/openpgp/x448
|
||||||
# github.com/atotto/clipboard v0.1.4
|
|
||||||
## explicit
|
|
||||||
github.com/atotto/clipboard
|
|
||||||
# github.com/aymanbagabas/go-osc52/v2 v2.0.1
|
# github.com/aymanbagabas/go-osc52/v2 v2.0.1
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/aymanbagabas/go-osc52/v2
|
github.com/aymanbagabas/go-osc52/v2
|
||||||
@ -70,11 +67,8 @@ github.com/cenkalti/backoff/v5
|
|||||||
github.com/cespare/xxhash/v2
|
github.com/cespare/xxhash/v2
|
||||||
# github.com/charmbracelet/bubbles v0.21.0
|
# github.com/charmbracelet/bubbles v0.21.0
|
||||||
## explicit; go 1.23.0
|
## explicit; go 1.23.0
|
||||||
github.com/charmbracelet/bubbles/cursor
|
|
||||||
github.com/charmbracelet/bubbles/key
|
github.com/charmbracelet/bubbles/key
|
||||||
github.com/charmbracelet/bubbles/runeutil
|
github.com/charmbracelet/bubbles/viewport
|
||||||
github.com/charmbracelet/bubbles/spinner
|
|
||||||
github.com/charmbracelet/bubbles/textinput
|
|
||||||
# github.com/charmbracelet/bubbletea v1.3.10
|
# github.com/charmbracelet/bubbletea v1.3.10
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
github.com/charmbracelet/bubbletea
|
github.com/charmbracelet/bubbletea
|
||||||
@ -283,9 +277,6 @@ github.com/emirpasic/gods/utils
|
|||||||
# github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f
|
# github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f
|
||||||
## explicit; go 1.16
|
## explicit; go 1.16
|
||||||
github.com/erikgeiser/coninput
|
github.com/erikgeiser/coninput
|
||||||
# github.com/evertras/bubble-table v0.19.2
|
|
||||||
## explicit; go 1.18
|
|
||||||
github.com/evertras/bubble-table/table
|
|
||||||
# github.com/felixge/httpsnoop v1.0.4
|
# github.com/felixge/httpsnoop v1.0.4
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/felixge/httpsnoop
|
github.com/felixge/httpsnoop
|
||||||
@ -493,11 +484,6 @@ github.com/muesli/ansi/compressor
|
|||||||
# github.com/muesli/cancelreader v0.2.2
|
# github.com/muesli/cancelreader v0.2.2
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/muesli/cancelreader
|
github.com/muesli/cancelreader
|
||||||
# github.com/muesli/reflow v0.3.0
|
|
||||||
## explicit; go 1.13
|
|
||||||
github.com/muesli/reflow/ansi
|
|
||||||
github.com/muesli/reflow/truncate
|
|
||||||
github.com/muesli/reflow/wordwrap
|
|
||||||
# github.com/muesli/termenv v0.16.0
|
# github.com/muesli/termenv v0.16.0
|
||||||
## explicit; go 1.17
|
## explicit; go 1.17
|
||||||
github.com/muesli/termenv
|
github.com/muesli/termenv
|
||||||
|
|||||||
Reference in New Issue
Block a user