Skip to content

Commit f294831

Browse files
BagToadCopilot
authored andcommitted
Upgrade to huh/v2 and fix selection persistence in MultiSelectWithSearch
Migrate from github.com/charmbracelet/huh v1 to charm.land/huh/v2, updating ThemeBase16 to the new ThemeFunc API. Fix selected options being lost across searches in the huhPrompter's MultiSelectWithSearch. The root cause was huh's internal Eval cache: when the user returned to a previously-seen search query, cached options with stale .selected state overwrote the current selections via updateValue(). The fix includes selectedValues in the OptionsFunc binding hash (via searchOptionsBinding) so the cache key changes whenever selections change, preventing stale cache hits. A local searchFunc result cache avoids redundant API calls when only the selection state (not the query) has changed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4661c05 commit f294831

4 files changed

Lines changed: 82 additions & 49 deletions

File tree

go.mod

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/cli/cli/v2
33
go 1.26.1
44

55
require (
6+
charm.land/huh/v2 v2.0.3
67
github.com/AlecAivazis/survey/v2 v2.3.7
78
github.com/MakeNowJust/heredoc v1.0.0
89
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
@@ -11,7 +12,6 @@ require (
1112
github.com/cenkalti/backoff/v4 v4.3.0
1213
github.com/cenkalti/backoff/v5 v5.0.3
1314
github.com/charmbracelet/glamour v0.10.0
14-
github.com/charmbracelet/huh v0.8.0
1515
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
1616
github.com/cli/go-gh/v2 v2.13.0
1717
github.com/cli/go-internal v0.0.0-20241025142207-6c48bcd5ce24
@@ -61,6 +61,9 @@ require (
6161
)
6262

6363
require (
64+
charm.land/bubbles/v2 v2.0.0 // indirect
65+
charm.land/bubbletea/v2 v2.0.2 // indirect
66+
charm.land/lipgloss/v2 v2.0.2 // indirect
6467
dario.cat/mergo v1.0.2 // indirect
6568
github.com/Masterminds/goutils v1.1.1 // indirect
6669
github.com/Masterminds/semver/v3 v3.4.0 // indirect
@@ -72,16 +75,20 @@ require (
7275
github.com/blang/semver v3.5.1+incompatible // indirect
7376
github.com/catppuccin/go v0.3.0 // indirect
7477
github.com/cespare/xxhash/v2 v2.3.0 // indirect
75-
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
76-
github.com/charmbracelet/bubbletea v1.3.10 // indirect
77-
github.com/charmbracelet/colorprofile v0.3.1 // indirect
78-
github.com/charmbracelet/x/ansi v0.10.2 // indirect
79-
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
78+
github.com/charmbracelet/colorprofile v0.4.2 // indirect
79+
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
80+
github.com/charmbracelet/x/ansi v0.11.6 // indirect
81+
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
82+
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
8083
github.com/charmbracelet/x/exp/slice v0.0.0-20250630141444-821143405392 // indirect
8184
github.com/charmbracelet/x/exp/strings v0.0.0-20250630141444-821143405392 // indirect
82-
github.com/charmbracelet/x/term v0.2.1 // indirect
85+
github.com/charmbracelet/x/term v0.2.2 // indirect
86+
github.com/charmbracelet/x/termios v0.1.1 // indirect
87+
github.com/charmbracelet/x/windows v0.2.2 // indirect
8388
github.com/cli/browser v1.3.0 // indirect
8489
github.com/cli/shurcooL-graphql v0.0.4 // indirect
90+
github.com/clipperhouse/displaywidth v0.11.0 // indirect
91+
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
8592
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
8693
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
8794
github.com/danieljoos/wincred v1.2.3 // indirect
@@ -92,7 +99,6 @@ require (
9299
github.com/docker/distribution v2.8.3+incompatible // indirect
93100
github.com/docker/docker-credential-helpers v0.9.3 // indirect
94101
github.com/dustin/go-humanize v1.0.1 // indirect
95-
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
96102
github.com/fatih/color v1.18.0 // indirect
97103
github.com/gdamore/encoding v1.0.1 // indirect
98104
github.com/go-logr/logr v1.4.3 // indirect
@@ -134,14 +140,12 @@ require (
134140
github.com/jedisct1/go-minisign v0.0.0-20241212093149-d2f9f49435c7 // indirect
135141
github.com/klauspost/compress v1.18.4 // indirect
136142
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
137-
github.com/mattn/go-localereader v0.0.1 // indirect
138-
github.com/mattn/go-runewidth v0.0.17 // indirect
143+
github.com/mattn/go-runewidth v0.0.20 // indirect
139144
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
140145
github.com/mitchellh/copystructure v1.2.0 // indirect
141146
github.com/mitchellh/go-homedir v1.1.0 // indirect
142147
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
143148
github.com/mitchellh/reflectwalk v1.0.2 // indirect
144-
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
145149
github.com/muesli/cancelreader v0.2.2 // indirect
146150
github.com/muesli/reflow v0.3.0 // indirect
147151
github.com/muesli/termenv v0.16.0 // indirect

go.sum

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
2+
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
3+
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
4+
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
5+
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
6+
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
7+
charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
8+
charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
19
cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c=
210
cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI=
311
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
@@ -86,8 +94,8 @@ github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk=
8694
github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
8795
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
8896
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
89-
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
90-
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
97+
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
98+
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
9199
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
92100
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
93101
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
@@ -102,38 +110,38 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
102110
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
103111
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
104112
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
105-
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
106-
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
107-
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
108-
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
109-
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
110-
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
113+
github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
114+
github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
111115
github.com/charmbracelet/glamour v0.10.0 h1:MtZvfwsYCx8jEPFJm3rIBFIMZUfUJ765oX8V6kXldcY=
112116
github.com/charmbracelet/glamour v0.10.0/go.mod h1:f+uf+I/ChNmqo087elLnVdCiVgjSKWuXa/l6NU2ndYk=
113-
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
114-
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
115117
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 h1:ZR7e0ro+SZZiIZD7msJyA+NjkCNNavuiPBLgerbOziE=
116118
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834/go.mod h1:aKC/t2arECF6rNOnaKaVU6y4t4ZeHQzqfxedE/VkVhA=
117-
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
118-
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
119-
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
120-
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
121-
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
122-
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
119+
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
120+
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
121+
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
122+
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
123+
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
124+
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
125+
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
126+
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
123127
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
124128
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
125-
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
126-
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
129+
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
130+
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
131+
github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
132+
github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
127133
github.com/charmbracelet/x/exp/slice v0.0.0-20250630141444-821143405392 h1:VHLoEcL+kH60a4F8qMsPfOIfWjFE3ciaW4gge2YR3sA=
128134
github.com/charmbracelet/x/exp/slice v0.0.0-20250630141444-821143405392/go.mod h1:vI5nDVMWi6veaYH+0Fmvpbe/+cv/iJfMntdh+N0+Tms=
129135
github.com/charmbracelet/x/exp/strings v0.0.0-20250630141444-821143405392 h1:6ipGA1NEA0AZG2UEf81RQGJvEPvYLn/M18mZcdt4J8g=
130136
github.com/charmbracelet/x/exp/strings v0.0.0-20250630141444-821143405392/go.mod h1:Rgw3/F+xlcUc5XygUtimVSxAqCOsqyvJjqF5UHRvc5k=
131-
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
132-
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
137+
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
138+
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
133139
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
134140
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
135-
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
136-
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
141+
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
142+
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
143+
github.com/charmbracelet/x/xpty v0.1.3 h1:eGSitii4suhzrISYH50ZfufV3v085BXQwIytcOdFSsw=
144+
github.com/charmbracelet/x/xpty v0.1.3/go.mod h1:poPYpWuLDBFCKmKLDnhBp51ATa0ooD8FhypRwEFtH3Y=
137145
github.com/cli/browser v1.0.0/go.mod h1:IEWkHYbLjkhtjwwWlwTHW2lGxeS5gezEQBMLTwDHf5Q=
138146
github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
139147
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
@@ -148,6 +156,10 @@ github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00=
148156
github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
149157
github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY=
150158
github.com/cli/shurcooL-graphql v0.0.4/go.mod h1:3waN4u02FiZivIV+p1y4d0Jo1jc6BViMA73C+sZo2fk=
159+
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
160+
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
161+
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
162+
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
151163
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
152164
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
153165
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
@@ -185,8 +197,6 @@ github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqI
185197
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
186198
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
187199
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
188-
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
189-
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
190200
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
191201
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
192202
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -380,12 +390,10 @@ github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stg
380390
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
381391
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
382392
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
383-
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
384-
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
385393
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
386394
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
387-
github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
388-
github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
395+
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
396+
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
389397
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
390398
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
391399
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
@@ -403,8 +411,6 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
403411
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
404412
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
405413
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
406-
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
407-
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
408414
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
409415
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
410416
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
@@ -596,7 +602,6 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
596602
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
597603
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
598604
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
599-
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
600605
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
601606
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
602607
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

internal/prompter/huh_prompter.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"fmt"
55
"slices"
66

7-
"github.com/charmbracelet/huh"
7+
"charm.land/huh/v2"
88
"github.com/cli/cli/v2/internal/ghinstance"
99
"github.com/cli/cli/v2/pkg/surveyext"
1010
ghPrompter "github.com/cli/go-gh/v2/pkg/prompter"
@@ -19,7 +19,7 @@ type huhPrompter struct {
1919

2020
func (p *huhPrompter) newForm(groups ...*huh.Group) *huh.Form {
2121
return huh.NewForm(groups...).
22-
WithTheme(huh.ThemeBase16()).
22+
WithTheme(huh.ThemeFunc(huh.ThemeBase16)).
2323
WithInput(p.stdin).
2424
WithOutput(p.stdout)
2525
}
@@ -82,6 +82,15 @@ func (p *huhPrompter) MultiSelect(prompt string, defaults []string, options []st
8282
return result, nil
8383
}
8484

85+
// searchOptionsBinding is used as the OptionsFunc binding for MultiSelectWithSearch.
86+
// By including both the search query and selected values, the binding hash changes
87+
// whenever either changes. This prevents huh's internal Eval cache from serving
88+
// stale option sets that would overwrite the user's current selections.
89+
type searchOptionsBinding struct {
90+
Query *string
91+
Selected *[]string
92+
}
93+
8594
func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, defaultValues, persistentValues []string, searchFunc func(string) MultiSelectSearchResult) ([]string, error) {
8695
selectedValues := make([]string, len(defaultValues))
8796
copy(selectedValues, defaultValues)
@@ -91,8 +100,19 @@ func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, default
91100
optionKeyLabels[k] = k
92101
}
93102

103+
// Cache searchFunc results locally keyed by query string.
104+
// This avoids redundant calls when the OptionsFunc binding hash changes
105+
// due to selection changes (not query changes).
106+
var cachedSearchQuery string
107+
var cachedSearchResult MultiSelectSearchResult
108+
94109
buildOptions := func(query string) []huh.Option[string] {
95-
result := searchFunc(query)
110+
if query != cachedSearchQuery || cachedSearchResult.Err != nil {
111+
cachedSearchResult = searchFunc(query)
112+
cachedSearchQuery = query
113+
}
114+
result := cachedSearchResult
115+
96116
if result.Err != nil {
97117
return nil
98118
}
@@ -113,7 +133,7 @@ func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, default
113133
if l == "" {
114134
l = k
115135
}
116-
formOptions = append(formOptions, huh.NewOption(l, k))
136+
formOptions = append(formOptions, huh.NewOption(l, k).Selected(true))
117137
}
118138

119139
// 2. Search results.
@@ -150,6 +170,10 @@ func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, default
150170
}
151171

152172
var searchQuery string
173+
binding := &searchOptionsBinding{
174+
Query: &searchQuery,
175+
Selected: &selectedValues,
176+
}
153177

154178
err := p.newForm(
155179
huh.NewGroup(
@@ -161,7 +185,7 @@ func (p *huhPrompter) MultiSelectWithSearch(prompt, searchPrompt string, default
161185
Options(buildOptions("")...).
162186
OptionsFunc(func() []huh.Option[string] {
163187
return buildOptions(searchQuery)
164-
}, &searchQuery).
188+
}, binding).
165189
Value(&selectedValues).
166190
Limit(0),
167191
),

internal/prompter/prompter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"strings"
77

88
"github.com/AlecAivazis/survey/v2"
9-
"github.com/charmbracelet/huh"
9+
"charm.land/huh/v2"
1010
"github.com/cli/cli/v2/internal/ghinstance"
1111
"github.com/cli/cli/v2/pkg/iostreams"
1212
"github.com/cli/cli/v2/pkg/surveyext"
@@ -89,7 +89,7 @@ type accessiblePrompter struct {
8989

9090
func (p *accessiblePrompter) newForm(groups ...*huh.Group) *huh.Form {
9191
return huh.NewForm(groups...).
92-
WithTheme(huh.ThemeBase16()).
92+
WithTheme(huh.ThemeFunc(huh.ThemeBase16)).
9393
WithAccessible(true).
9494
WithInput(p.stdin).
9595
WithOutput(p.stdout)

0 commit comments

Comments
 (0)