diff --git a/cmd/project/create_samples.go b/cmd/project/create_samples.go index 5ffc1f2a..e466dd05 100644 --- a/cmd/project/create_samples.go +++ b/cmd/project/create_samples.go @@ -21,7 +21,6 @@ import ( "sort" "strings" - "github.com/slackapi/slack-cli/internal/experiment" "github.com/slackapi/slack-cli/internal/iostreams" "github.com/slackapi/slack-cli/internal/pkg/create" "github.com/slackapi/slack-cli/internal/shared" @@ -67,21 +66,13 @@ func promptSampleSelection(ctx context.Context, clients *shared.ClientFactory, s sortedRepos := sortRepos(filteredRepos) selectOptions := make([]string, len(sortedRepos)) for i, r := range sortedRepos { - if !clients.Config.WithExperimentOn(experiment.Huh) { - selectOptions[i] = fmt.Sprint(i+1, ". ", r.Name) - } else { - selectOptions[i] = r.Name - } + selectOptions[i] = r.Name } var selectedTemplate string selection, err = clients.IO.SelectPrompt(ctx, "Select a sample to build upon:", selectOptions, iostreams.SelectPromptConfig{ Description: func(value string, index int) string { - desc := sortedRepos[index].Description - if !clients.Config.WithExperimentOn(experiment.Huh) { - desc += "\n https://github.com/" + sortedRepos[index].FullName - } - return desc + return sortedRepos[index].Description }, Flag: clients.Config.Flags.Lookup("template"), Help: fmt.Sprintf("Guided tutorials can be found at %s", style.LinkText("https://docs.slack.dev/samples")), diff --git a/go.mod b/go.mod index 0019c383..fbc118dc 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ require ( charm.land/bubbletea/v2 v2.0.2 charm.land/huh/v2 v2.0.3 charm.land/lipgloss/v2 v2.0.2 - github.com/AlecAivazis/survey/v2 v2.3.7 github.com/briandowns/spinner v1.23.2 github.com/charmbracelet/x/ansi v0.11.6 github.com/cli/safeexec v1.0.1 @@ -58,7 +57,6 @@ require ( github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.8.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.6.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -84,11 +82,9 @@ require ( github.com/fatih/color v1.18.0 // indirect github.com/go-git/go-git/v5 v5.17.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kubescape/go-git-url v0.0.31 github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/spf13/pflag v1.0.10 github.com/stretchr/objx v0.5.3 // indirect diff --git a/go.sum b/go.sum index 9265f431..2d5af0cf 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,6 @@ charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFv dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ= -github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -19,8 +17,6 @@ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6 github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= -github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= @@ -72,7 +68,6 @@ github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= @@ -113,9 +108,6 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= -github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02 h1:AgcIVYPa6XJnU3phs104wLj8l5GEththEw6+F79YsIY= -github.com/hinshun/vt10x v0.0.0-20220301184237-5011da428d02/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= @@ -123,8 +115,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= @@ -144,17 +134,12 @@ github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUp github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ= github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI= -github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= @@ -204,7 +189,6 @@ github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+Q github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= @@ -216,13 +200,11 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= @@ -238,22 +220,17 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -261,30 +238,22 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/experiment/experiment.go b/internal/experiment/experiment.go index 284b0a7a..375ba188 100644 --- a/internal/experiment/experiment.go +++ b/internal/experiment/experiment.go @@ -30,9 +30,6 @@ type Experiment string // e.g. --experiment=first-toggle,second-toggle const ( - // Huh experiment shows beautiful prompts. - Huh Experiment = "huh" - // Lipgloss experiment shows pretty styles. Lipgloss Experiment = "lipgloss" @@ -49,7 +46,6 @@ const ( // AllExperiments is a list of all available experiments that can be enabled // Please also add here 👇 var AllExperiments = []Experiment{ - Huh, Lipgloss, Placeholder, Sandboxes, diff --git a/internal/experiment/experiment_test.go b/internal/experiment/experiment_test.go index aac45d53..38fdbe3d 100644 --- a/internal/experiment/experiment_test.go +++ b/internal/experiment/experiment_test.go @@ -25,7 +25,6 @@ func Test_Includes(t *testing.T) { require.Equal(t, true, Includes(Experiment(Placeholder))) // Test expected experiments - require.Equal(t, true, Includes(Experiment("huh"))) require.Equal(t, true, Includes(Experiment("lipgloss"))) // Test invalid experiment diff --git a/internal/iostreams/forms.go b/internal/iostreams/forms.go index d5e43f6c..889a4899 100644 --- a/internal/iostreams/forms.go +++ b/internal/iostreams/forms.go @@ -34,6 +34,8 @@ func newForm(io *IOStreams, field huh.Field) *huh.Form { form := huh.NewForm(huh.NewGroup(field)) if io != nil && io.config.WithExperimentOn(experiment.Lipgloss) { form = form.WithTheme(style.ThemeSlack()) + } else { + form = form.WithTheme(style.ThemeSurvey()) } return form } @@ -90,7 +92,7 @@ func buildSelectForm(io *IOStreams, msg string, options []string, cfg SelectProm key := opt if cfg.Description != nil { if desc := style.RemoveEmoji(cfg.Description(opt, len(opts))); desc != "" { - key = opt + " - " + desc + key = style.Bright(opt) + " — " + style.Secondary(desc) } } opts = append(opts, huh.NewOption(key, opt)) diff --git a/internal/iostreams/forms_test.go b/internal/iostreams/forms_test.go index f0ba9144..c61d32da 100644 --- a/internal/iostreams/forms_test.go +++ b/internal/iostreams/forms_test.go @@ -195,6 +195,57 @@ func TestSelectForm(t *testing.T) { assert.Contains(t, view, "First letter") }) + t.Run("descriptions use em-dash separator with lipgloss enabled", func(t *testing.T) { + style.ToggleLipgloss(true) + style.ToggleStyles(true) + t.Cleanup(func() { + style.ToggleLipgloss(false) + style.ToggleStyles(false) + }) + + fsMock := slackdeps.NewFsMock() + osMock := slackdeps.NewOsMock() + osMock.AddDefaultMocks() + cfg := config.NewConfig(fsMock, osMock) + cfg.ExperimentsFlag = []string{"lipgloss"} + cfg.LoadExperiments(context.Background(), func(_ context.Context, _ string, _ ...any) {}) + io := NewIOStreams(cfg, fsMock, osMock) + + var selected string + options := []string{"Alpha", "Beta"} + selectCfg := SelectPromptConfig{ + Description: func(opt string, _ int) string { + if opt == "Alpha" { + return "First letter" + } + return "" + }, + } + f := buildSelectForm(io, "Choose", options, selectCfg, &selected) + f.Update(f.Init()) + + view := ansi.Strip(f.View()) + assert.Contains(t, view, " — First letter") + }) + + t.Run("descriptions use em-dash separator without lipgloss", func(t *testing.T) { + var selected string + options := []string{"Alpha", "Beta"} + selectCfg := SelectPromptConfig{ + Description: func(opt string, _ int) string { + if opt == "Alpha" { + return "First letter" + } + return "" + }, + } + f := buildSelectForm(nil, "Choose", options, selectCfg, &selected) + f.Update(f.Init()) + + view := ansi.Strip(f.View()) + assert.Contains(t, view, "Alpha — First letter") + }) + t.Run("page size sets field height", func(t *testing.T) { var selected string options := []string{"A", "B", "C", "D", "E", "F", "G", "H"} @@ -283,8 +334,8 @@ func TestMultiSelectForm(t *testing.T) { m, _ := f.Update(key('x')) view := ansi.Strip(m.View()) - // After toggle, the first item should show as selected (checkmark) - assert.Contains(t, view, "✓") + // After toggle, the first item should show as selected + assert.Contains(t, view, "[x]") }) t.Run("submit returns toggled items", func(t *testing.T) { @@ -364,14 +415,51 @@ func TestFormsUseSlackTheme(t *testing.T) { }) } -func TestFormsWithoutLipgloss(t *testing.T) { - t.Run("multi-select uses default prefix without lipgloss", func(t *testing.T) { +func TestFormsUseSurveyTheme(t *testing.T) { + t.Run("multi-select uses survey prefix without lipgloss", func(t *testing.T) { var selected []string f := buildMultiSelectForm(nil, "Pick", []string{"A", "B"}, &selected) f.Update(f.Init()) view := ansi.Strip(f.View()) - // Without lipgloss the Slack theme is not applied, so "[ ]" should not appear - assert.NotContains(t, view, "[ ]") + // ThemeSurvey uses "[ ] " as unselected prefix + assert.Contains(t, view, "[ ]") + }) + + t.Run("multi-select uses [x] for selected prefix", func(t *testing.T) { + var selected []string + f := buildMultiSelectForm(nil, "Pick", []string{"A", "B"}, &selected) + f.Update(f.Init()) + + // Toggle first item + m, _ := f.Update(key('x')) + view := ansi.Strip(m.View()) + assert.Contains(t, view, "[x]") + }) + + t.Run("select form renders chevron cursor", func(t *testing.T) { + var selected string + f := buildSelectForm(nil, "Pick", []string{"A", "B"}, SelectPromptConfig{}, &selected) + f.Update(f.Init()) + + view := ansi.Strip(f.View()) + assert.Contains(t, view, style.Chevron()+" A") + }) + + t.Run("all form builders apply ThemeSurvey without lipgloss", func(t *testing.T) { + var s string + var b bool + var ss []string + forms := []*huh.Form{ + buildInputForm(nil, "msg", InputPromptConfig{}, &s), + buildConfirmForm(nil, "msg", &b), + buildSelectForm(nil, "msg", []string{"a"}, SelectPromptConfig{}, &s), + buildPasswordForm(nil, "msg", PasswordPromptConfig{}, &s), + buildMultiSelectForm(nil, "msg", []string{"a"}, &ss), + } + for _, f := range forms { + f.Update(f.Init()) + assert.NotEmpty(t, f.View()) + } }) } diff --git a/internal/iostreams/prompts.go b/internal/iostreams/prompts.go index 0a758e53..845ccbcc 100644 --- a/internal/iostreams/prompts.go +++ b/internal/iostreams/prompts.go @@ -21,11 +21,21 @@ import ( "fmt" "strings" - "github.com/slackapi/slack-cli/internal/experiment" "github.com/slackapi/slack-cli/internal/slackerror" + "github.com/slackapi/slack-cli/internal/style" "github.com/spf13/pflag" ) +// MimicInputPrompt formats a message and value to appear as a prompted input +func MimicInputPrompt(message string, value string) string { + return fmt.Sprintf( + "%s %s %s", + style.Darken("?"), + style.Highlight(message), + style.Input(value), + ) +} + // PromptConfig contains general information about a prompt type PromptConfig interface { GetFlags() []*pflag.Flag // GetFlags returns all flags for the prompt @@ -193,28 +203,19 @@ func errInteractivityFlags(cfg PromptConfig) error { // ConfirmPrompt prompts the user for a "yes" or "no" (true or false) value for // the message func (io *IOStreams) ConfirmPrompt(ctx context.Context, message string, defaultValue bool) (bool, error) { - if io.config.WithExperimentOn(experiment.Huh) { - return confirmForm(io, ctx, message, defaultValue) - } - return surveyConfirmPrompt(io, ctx, message, defaultValue) + return confirmForm(io, ctx, message, defaultValue) } // InputPrompt prompts the user for a string value for the message, which can // optionally be made required func (io *IOStreams) InputPrompt(ctx context.Context, message string, cfg InputPromptConfig) (string, error) { - if io.config.WithExperimentOn(experiment.Huh) { - return inputForm(io, ctx, message, cfg) - } - return surveyInputPrompt(io, ctx, message, cfg) + return inputForm(io, ctx, message, cfg) } // MultiSelectPrompt prompts the user to select multiple values in a list and // returns the selected values func (io *IOStreams) MultiSelectPrompt(ctx context.Context, message string, options []string) ([]string, error) { - if io.config.WithExperimentOn(experiment.Huh) { - return multiSelectForm(io, ctx, message, options) - } - return surveyMultiSelectPrompt(io, ctx, message, options) + return multiSelectForm(io, ctx, message, options) } // PasswordPrompt prompts the user with a hidden text input for the message @@ -229,10 +230,7 @@ func (io *IOStreams) PasswordPrompt(ctx context.Context, message string, cfg Pas return PasswordPromptResponse{}, errInteractivityFlags(cfg) } - if io.config.WithExperimentOn(experiment.Huh) { - return passwordForm(io, ctx, message, cfg) - } - return surveyPasswordPrompt(io, ctx, message, cfg) + return passwordForm(io, ctx, message, cfg) } // SelectPrompt prompts the user to make a selection and returns the choice @@ -257,8 +255,5 @@ func (io *IOStreams) SelectPrompt(ctx context.Context, msg string, options []str } } - if io.config.WithExperimentOn(experiment.Huh) { - return selectForm(io, ctx, msg, options, cfg) - } - return surveySelectPrompt(io, ctx, msg, options, cfg) + return selectForm(io, ctx, msg, options, cfg) } diff --git a/internal/iostreams/survey.go b/internal/iostreams/survey.go deleted file mode 100644 index 1d6e282a..00000000 --- a/internal/iostreams/survey.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2022-2026 Salesforce, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package iostreams - -// Survey-based prompt implementations using the survey library. -// Templates, color helpers, and survey-specific configuration live here. -// Shared prompt types and IOStreams methods live in prompts.go. - -import ( - "context" - "fmt" - "runtime" - - "github.com/AlecAivazis/survey/v2" - "github.com/AlecAivazis/survey/v2/terminal" - "github.com/slackapi/slack-cli/internal/slackerror" - "github.com/slackapi/slack-cli/internal/style" -) - -// blue returns a color code for blue text that can be used in survey templates -// -// The code is compatible with the ANSI range supported by the OS and terminal -func blue() string { - if runtime.GOOS == "windows" { - return "cyan" - } - return "39" -} - -// gray returns a color code for gray text that can be used in survey templates -// -// The code is compatible with the ANSI range supported by the OS and terminal -func gray() string { - if runtime.GOOS == "windows" { - return "default" - } - return "246" -} - -// MimicInputPrompt formats a message and value to appear as a prompted input -func MimicInputPrompt(message string, value string) string { - return fmt.Sprintf( - "%s %s %s", - style.Darken("?"), - style.Highlight(message), - style.Input(value), - ) -} - -// SurveyOptions returns the current options applied to survey prompts -func SurveyOptions(cfg PromptConfig) []survey.AskOpt { - var filter survey.AskOpt - var icons survey.AskOpt - var validator survey.AskOpt - - // surveyFilterOff removes the filtering feature when typing on a prompt to - // guard against confusion from accidental keystrokes. The template must be - // changed to remove the text hint in the prompt. - surveyFilterOff := survey.WithFilter(func(filterValue string, optValue string, optIndex int) bool { - return true - }) - - // filter contains the filter applied to select-like prompts. - // off by default but should be extended to use a cfg filter. - filter = surveyFilterOff - icons = style.SurveyIcons() - if cfg.IsRequired() { - validator = survey.WithValidator(survey.Required) - } - - return []survey.AskOpt{ - filter, - icons, - validator, - survey.WithRemoveSelectAll(), - survey.WithRemoveSelectNone(), - } -} - -// ConfirmQuestionTemplate is the formatted template for the confirm prompt -// -// Reference: https://github.com/go-survey/survey/blob/fa37277e6394c29db7bcc94062cb30cd7785a126/confirm.go#L25 -var ConfirmQuestionTemplate = fmt.Sprintf(` -{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .Answer}} - {{- color "%s"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}} - {{- color "%s"}}{{if .Default}}(Y/n) {{else}}(y/N) {{end}}{{color "reset"}} -{{- end}}`, blue(), gray()) - -// surveyConfirmPrompt prompts for a yes/no confirmation using a survey form -func surveyConfirmPrompt(io *IOStreams, _ context.Context, message string, defaultValue bool) (bool, error) { - // Temporarily swap default template for custom one - defaultConfirmTemplate := survey.ConfirmQuestionTemplate - survey.ConfirmQuestionTemplate = ConfirmQuestionTemplate - defer func() { - survey.ConfirmQuestionTemplate = defaultConfirmTemplate - }() - - // TODO: move this config to the function parameter! - // NOTE: currently here as a placeholder for survey options - cfg := ConfirmPromptConfig{Required: true} - - var choice bool - err := survey.AskOne(&survey.Confirm{ - Message: message, - Default: defaultValue, - }, &choice, SurveyOptions(cfg)...) - - if err != nil { - if err == terminal.InterruptErr { - io.SetExitCode(ExitCancel) - return false, slackerror.New(slackerror.ErrProcessInterrupted) - } - return false, err - } - return choice, nil -} - -// InputQuestionTemplate is a formatted template for the a text based prompt -// -// Reference: https://github.com/go-survey/survey/blob/fa37277e6394c29db7bcc94062cb30cd7785a126/input.go#L43 -var InputQuestionTemplate = fmt.Sprintf(` -{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "reset"}} -{{- if .ShowAnswer}} - {{- color "%s"}}{{.Answer}}{{color "reset"}}{{"\n"}} -{{- else if .PageEntries -}} - {{- .Answer}} [Use arrows to move, enter to select, type to continue] - {{- "\n"}} - {{- range $ix, $choice := .PageEntries}} - {{- if eq $ix $.SelectedIndex }}{{color $.Config.Icons.SelectFocus.Format }}{{ $.Config.Icons.SelectFocus.Text }} {{else}}{{color "default"}} {{end}} - {{- $choice.Value}} - {{- color "reset"}}{{"\n"}} - {{- end}} -{{- else }} - {{- if or (and .Help (not .ShowHelp)) .Suggest }}{{color "cyan"}}[ - {{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}} - {{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}} - ]{{color "reset"}} {{end}} - {{- if .Default}}{{color "%s"}}({{.Default}}) {{color "reset"}}{{end}} -{{- end}}`, blue(), gray()) - -// surveyInputPrompt prompts for text input using a survey form -func surveyInputPrompt(io *IOStreams, _ context.Context, message string, cfg InputPromptConfig) (string, error) { - defaultInputTemplate := survey.InputQuestionTemplate - survey.InputQuestionTemplate = InputQuestionTemplate - defer func() { - survey.InputQuestionTemplate = defaultInputTemplate - }() - - var input string - err := survey.AskOne(&survey.Input{ - Message: message, - Default: cfg.Placeholder, - }, &input, SurveyOptions(cfg)...) - - if err != nil { - if err == terminal.InterruptErr { - io.SetExitCode(ExitCancel) - return "", slackerror.New(slackerror.ErrProcessInterrupted) - } - return "", err - } - return input, nil -} - -// MultiSelectQuestionTemplate represents a formatted template with all hints -// and filters removed -// -// Reference: https://github.com/go-survey/survey/blob/fa37277e6394c29db7bcc94062cb30cd7785a126/multiselect.go#L71 -var MultiSelectQuestionTemplate = fmt.Sprintf(` -{{- define "option"}} - {{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }}{{color "reset"}}{{else}} {{end}} - {{- if index .Checked .CurrentOpt.Index }}{{color .Config.Icons.MarkedOption.Format }} {{ .Config.Icons.MarkedOption.Text }} {{else}}{{color .Config.Icons.UnmarkedOption.Format }} {{ .Config.Icons.UnmarkedOption.Text }} {{end}} - {{- color "reset"}} - {{- " "}}{{- .CurrentOpt.Value}}{{ if ne ($.GetDescription .CurrentOpt) "" }} - {{color "cyan"}}{{ $.GetDescription .CurrentOpt }}{{color "reset"}}{{end}} -{{end}} -{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }}{{color "reset"}} -{{- if .ShowAnswer}}{{color "%s"}} {{.Answer}}{{color "reset"}}{{"\n"}} -{{- else }} - {{- " "}}{{- color "%s"}}[Space to select{{- if not .Config.RemoveSelectAll }}, to all{{end}}{{- if not .Config.RemoveSelectNone }}, to none{{end}}{{- if and .Help (not .ShowHelp)}}, {{ .Config.HelpInput }} for more help{{end}}]{{color "reset"}} - {{- "\n"}} - {{- range $ix, $option := .PageEntries}} - {{- template "option" $.IterateOption $ix $option}} - {{- end}} -{{- end}}`, blue(), blue()) - -// surveyMultiSelectPrompt prompts for multiple selections using a survey form -func surveyMultiSelectPrompt(io *IOStreams, _ context.Context, message string, options []string) ([]string, error) { - defaultMultiSelectTemplate := survey.MultiSelectQuestionTemplate - survey.MultiSelectQuestionTemplate = MultiSelectQuestionTemplate - defer func() { - survey.MultiSelectQuestionTemplate = defaultMultiSelectTemplate - }() - - // TODO: move this config to the function parameter! - // NOTE: currently here as a placeholder for survey options - cfg := MultiSelectPromptConfig{Required: true} - - // Collect the selected values - var values []string - err := survey.AskOne(&survey.MultiSelect{ - Message: message, - Options: options, - }, &values, SurveyOptions(cfg)...) - - if err != nil { - if err == terminal.InterruptErr { - io.SetExitCode(ExitCancel) - return []string{}, slackerror.New(slackerror.ErrProcessInterrupted) - } - return []string{}, err - } - return values, nil -} - -// passwordQuestionTemplate is a template with custom formatting -// -// Reference: https://github.com/go-survey/survey/blob/fa37277e6394c29db7bcc94062cb30cd7785a126/password.go#L32 -var passwordQuestionTemplate = fmt.Sprintf(` -{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }} {{color "%s"}} -{{- if and .Help (not .ShowHelp)}}{{color "cyan"}}[{{ .Config.HelpInput }} for help]{{color "reset"}} {{end}}`, gray()) - -// surveyPasswordPrompt prompts for a password (hidden input) using a survey form -func surveyPasswordPrompt(io *IOStreams, _ context.Context, message string, cfg PasswordPromptConfig) (PasswordPromptResponse, error) { - defaultPasswordTemplate := survey.PasswordQuestionTemplate - if cfg.Template != "" { - survey.PasswordQuestionTemplate = cfg.Template - } else { - survey.PasswordQuestionTemplate = passwordQuestionTemplate - } - defer func() { - survey.PasswordQuestionTemplate = defaultPasswordTemplate - if !io.config.NoColor { - _, _ = io.WriteOut().Write([]byte("\x1b[0m")) // Reset prompt format - } - }() - - var input string - err := survey.AskOne(&survey.Password{ - Message: message, - }, &input, SurveyOptions(cfg)...) - - if err != nil { - if err == terminal.InterruptErr { - io.SetExitCode(ExitCancel) - return PasswordPromptResponse{}, slackerror.New(slackerror.ErrProcessInterrupted) - } - return PasswordPromptResponse{}, err - } - return PasswordPromptResponse{Prompt: true, Value: input}, nil -} - -// selectQuestionTemplate represents a formatted template with all hints and -// filters removed -// -// Reference: https://github.com/go-survey/survey/blob/fa37277e6394c29db7bcc94062cb30cd7785a126/select.go#L69 -var selectQuestionTemplate = fmt.Sprintf(` -{{- define "option"}} - {{- if eq .SelectedIndex .CurrentIndex }}{{color .Config.Icons.SelectFocus.Format }}{{ .Config.Icons.SelectFocus.Text }}{{color "default+b"}} {{else}}{{color "default"}} {{end}} - {{- .CurrentOpt.Value}}{{color "reset"}}{{ if ne ($.GetDescription .CurrentOpt) "" }}{{"\n "}}{{color "250"}}{{ $.GetDescription .CurrentOpt }}{{"\n"}}{{end}} - {{- color "reset"}} -{{end}} -{{- if .ShowHelp }}{{- color .Config.Icons.Help.Format }}{{ .Config.Icons.Help.Text }} {{ .Help }}{{color "reset"}}{{"\n"}}{{end}} -{{- color .Config.Icons.Question.Format }}{{ .Config.Icons.Question.Text }} {{color "reset"}} -{{- color "default+hb"}}{{ .Message }}{{color "reset"}} -{{- if .ShowAnswer}}{{color "%s"}} {{.Answer}}{{color "reset"}}{{"\n"}} -{{- else}} - {{- "\n"}} - {{- range $ix, $option := .PageEntries}} - {{- template "option" $.IterateOption $ix $option}} - {{- end}} -{{- end}}`, blue()) - -// surveySelectPrompt prompts for a single selection using a survey form -func surveySelectPrompt(io *IOStreams, _ context.Context, msg string, options []string, cfg SelectPromptConfig) (SelectPromptResponse, error) { - defaultSelectTemplate := survey.SelectQuestionTemplate - if cfg.Template != "" { - survey.SelectQuestionTemplate = cfg.Template - } else { - survey.SelectQuestionTemplate = selectQuestionTemplate - } - defer func() { - survey.SelectQuestionTemplate = defaultSelectTemplate - }() - - prompt := &survey.Select{ - Message: msg, - Options: options, - } - - if cfg.Description != nil { - prompt.Description = cfg.Description - } - if cfg.PageSize != 0 { - prompt.PageSize = cfg.PageSize - } - - // Collect the selected index and return with the selected value - var index int - if err := survey.AskOne(prompt, &index, SurveyOptions(cfg)...); err != nil { - if err == terminal.InterruptErr { - io.SetExitCode(ExitCancel) - return SelectPromptResponse{}, slackerror.New(slackerror.ErrProcessInterrupted) - } - return SelectPromptResponse{}, err - } - return SelectPromptResponse{Prompt: true, Index: index, Option: options[index]}, nil -} diff --git a/internal/style/theme.go b/internal/style/theme.go index 100925dc..47cb90d3 100644 --- a/internal/style/theme.go +++ b/internal/style/theme.go @@ -18,14 +18,10 @@ package style // Uses official Slack brand colors defined in colors.go. import ( - "fmt" "runtime" huh "charm.land/huh/v2" lipgloss "charm.land/lipgloss/v2" - - "github.com/AlecAivazis/survey/v2" - "github.com/AlecAivazis/survey/v2/core" ) // ThemeSlack returns a huh Theme styled with Slack brand colors. @@ -126,20 +122,50 @@ func Chevron() string { return "❱" } -// SurveyIcons returns customizations to the appearance of survey prompts. -func SurveyIcons() survey.AskOpt { - if !isStyleEnabled { - core.DisableColor = true - } +// ThemeSurvey returns a huh Theme that matches the legacy survey prompt styling. +// Applied when experiment.Lipgloss is off. +func ThemeSurvey() huh.Theme { + return huh.ThemeFunc(themeSurvey) +} + +// themeSurvey builds huh styles matching the survey library's appearance. +func themeSurvey(isDark bool) *huh.Styles { + t := huh.ThemeBase(isDark) + + ansiBlue := lipgloss.ANSIColor(blue) + ansiGray := lipgloss.ANSIColor(gray) + ansiGreen := lipgloss.ANSIColor(green) + ansiRed := lipgloss.ANSIColor(red) + + t.Focused.Title = lipgloss.NewStyle(). + Foreground(ansiGray). + Bold(true) + t.Focused.ErrorIndicator = lipgloss.NewStyle(). + Foreground(ansiRed). + SetString(" *") + t.Focused.ErrorMessage = lipgloss.NewStyle(). + Foreground(ansiRed) - cursor := Chevron() + // Select styles + t.Focused.SelectSelector = lipgloss.NewStyle(). + Foreground(ansiBlue). + Bold(true). + SetString(Chevron() + " ") + t.Focused.SelectedOption = lipgloss.NewStyle(). + Foreground(ansiBlue). + Bold(true) - return survey.WithIcons(func(icons *survey.IconSet) { - icons.SelectFocus.Text = cursor - icons.SelectFocus.Format = fmt.Sprintf("%d+b", blue) - icons.MarkedOption.Format = fmt.Sprintf("%d+b", blue) + // Multi-select styles + t.Focused.MultiSelectSelector = lipgloss.NewStyle(). + Foreground(ansiBlue). + Bold(true). + SetString(Chevron() + " ") + t.Focused.SelectedPrefix = lipgloss.NewStyle(). + Foreground(ansiGreen). + SetString("[x] ") + t.Focused.UnselectedPrefix = lipgloss.NewStyle(). + Bold(true). + SetString("[ ] ") - icons.Question.Text = "?" - icons.Question.Format = fmt.Sprintf("%d+hb", gray) - }) + return t } diff --git a/internal/style/theme_test.go b/internal/style/theme_test.go index 142ee395..5240ab73 100644 --- a/internal/style/theme_test.go +++ b/internal/style/theme_test.go @@ -18,7 +18,6 @@ import ( "runtime" "testing" - "github.com/AlecAivazis/survey/v2/core" "github.com/stretchr/testify/assert" ) @@ -76,6 +75,46 @@ func TestThemeSlack(t *testing.T) { } } +func TestThemeSurvey(t *testing.T) { + theme := ThemeSurvey().Theme(false) + tests := map[string]struct { + rendered string + expected []string + unexpected []string + }{ + "focused title renders text": { + rendered: theme.Focused.Title.Render("x"), + expected: []string{"x"}, + }, + "focused error message renders text": { + rendered: theme.Focused.ErrorMessage.Render("err"), + expected: []string{"err"}, + }, + "focused select selector renders chevron": { + rendered: theme.Focused.SelectSelector.Render(), + expected: []string{Chevron()}, + }, + "focused multi-select selected prefix has [x]": { + rendered: theme.Focused.SelectedPrefix.Render(), + expected: []string{"[x]"}, + }, + "focused multi-select unselected prefix has brackets": { + rendered: theme.Focused.UnselectedPrefix.Render(), + expected: []string{"[ ]"}, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + for _, exp := range tc.expected { + assert.Contains(t, tc.rendered, exp) + } + for _, unexp := range tc.unexpected { + assert.NotContains(t, tc.rendered, unexp) + } + }) + } +} + func TestChevron(t *testing.T) { tests := map[string]struct { styleEnabled bool @@ -106,26 +145,3 @@ func TestChevron(t *testing.T) { }) } } - -func TestSurveyIcons(t *testing.T) { - tests := map[string]struct { - styleEnabled bool - }{ - "styles are not enabled": { - styleEnabled: false, - }, - "styles are enabled": { - styleEnabled: true, - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - core.DisableColor = false - isStyleEnabled = tc.styleEnabled - - _ = SurveyIcons() - assert.NotEqual(t, tc.styleEnabled, core.DisableColor) - }) - } -}