Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita7fac30

Browse files
authored
feat: implement rich multi-selector (#19201)
Fixes:#19182
1 parent9505ecc commita7fac30

File tree

4 files changed

+191
-59
lines changed

4 files changed

+191
-59
lines changed

‎cli/cliui/parameter.go‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,16 @@ func RichParameter(inv *serpent.Invocation, templateVersionParameter codersdk.Te
3838
// Move the cursor up a single line for nicer display!
3939
_,_=fmt.Fprint(inv.Stdout,"\033[1A")
4040

41-
varoptions []string
42-
err=json.Unmarshal([]byte(templateVersionParameter.DefaultValue),&options)
41+
vardefaults []string
42+
err=json.Unmarshal([]byte(templateVersionParameter.DefaultValue),&defaults)
4343
iferr!=nil {
4444
return"",err
4545
}
4646

47-
values,err:=MultiSelect(inv,MultiSelectOptions{
48-
Options:options,
49-
Defaults:options,
47+
values,err:=RichMultiSelect(inv,RichMultiSelectOptions{
48+
Options:templateVersionParameter.Options,
49+
Defaults:defaults,
50+
EnableCustomInput:templateVersionParameter.FormType=="tag-select",
5051
})
5152
iferr==nil {
5253
v,err:=json.Marshal(&values)

‎cli/cliui/select.go‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"os/signal"
8+
"slices"
89
"strings"
910
"syscall"
1011

@@ -299,6 +300,73 @@ func (m selectModel) filteredOptions() []string {
299300
returnoptions
300301
}
301302

303+
typeRichMultiSelectOptionsstruct {
304+
Messagestring
305+
Options []codersdk.TemplateVersionParameterOption
306+
Defaults []string
307+
EnableCustomInputbool
308+
}
309+
310+
funcRichMultiSelect(inv*serpent.Invocation,richOptionsRichMultiSelectOptions) ([]string,error) {
311+
varopts []string
312+
vardefaultOpts []string
313+
314+
asLine:=func(option codersdk.TemplateVersionParameterOption)string {
315+
line:=option.Name
316+
iflen(option.Description)>0 {
317+
line+=": "+option.Description
318+
}
319+
returnline
320+
}
321+
322+
varpredefinedOpts []string
323+
fori,option:=rangerichOptions.Options {
324+
opts=append(opts,asLine(option))// Some options may have description defined.
325+
326+
// Check if option is selected by default
327+
ifslices.Contains(richOptions.Defaults,option.Value) {
328+
defaultOpts=append(defaultOpts,opts[i])
329+
predefinedOpts=append(predefinedOpts,option.Value)
330+
}
331+
}
332+
333+
// Check if "defaults" contains extra/custom options, user could select them.
334+
for_,def:=rangerichOptions.Defaults {
335+
if!slices.Contains(predefinedOpts,def) {
336+
opts=append(opts,def)
337+
defaultOpts=append(defaultOpts,def)
338+
}
339+
}
340+
341+
selected,err:=MultiSelect(inv,MultiSelectOptions{
342+
Message:richOptions.Message,
343+
Options:opts,
344+
Defaults:defaultOpts,
345+
EnableCustomInput:richOptions.EnableCustomInput,
346+
})
347+
iferr!=nil {
348+
returnnil,err
349+
}
350+
351+
// Check selected option, convert descriptions (line) to values
352+
varresults []string
353+
for_,sel:=rangeselected {
354+
custom:=true
355+
fori,option:=rangerichOptions.Options {
356+
ifasLine(option)==sel {
357+
results=append(results,richOptions.Options[i].Value)
358+
custom=false
359+
break
360+
}
361+
}
362+
363+
ifcustom {
364+
results=append(results,sel)
365+
}
366+
}
367+
returnresults,nil
368+
}
369+
302370
typeMultiSelectOptionsstruct {
303371
Messagestring
304372
Options []string

‎cli/cliui/select_test.go‎

Lines changed: 103 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,8 @@ func TestRichSelect(t *testing.T) {
5252
gofunc() {
5353
resp,err:=newRichSelect(ptty, cliui.RichSelectOptions{
5454
Options: []codersdk.TemplateVersionParameterOption{
55-
{
56-
Name:"A-Name",
57-
Value:"A-Value",
58-
Description:"A-Description.",
59-
}, {
60-
Name:"B-Name",
61-
Value:"B-Value",
62-
Description:"B-Description.",
63-
},
55+
{Name:"A-Name",Value:"A-Value",Description:"A-Description."},
56+
{Name:"B-Name",Value:"B-Value",Description:"B-Description."},
6457
},
6558
})
6659
assert.NoError(t,err)
@@ -86,63 +79,119 @@ func newRichSelect(ptty *ptytest.PTY, opts cliui.RichSelectOptions) (string, err
8679
returnvalue,inv.Run()
8780
}
8881

89-
funcTestMultiSelect(t*testing.T) {
82+
funcTestRichMultiSelect(t*testing.T) {
9083
t.Parallel()
91-
t.Run("MultiSelect",func(t*testing.T) {
92-
items:= []string{"aaa","bbb","ccc"}
9384

94-
t.Parallel()
95-
ptty:=ptytest.New(t)
96-
msgChan:=make(chan []string)
97-
gofunc() {
98-
resp,err:=newMultiSelect(ptty,items)
99-
assert.NoError(t,err)
100-
msgChan<-resp
101-
}()
102-
require.Equal(t,items,<-msgChan)
103-
})
85+
tests:= []struct {
86+
namestring
87+
options []codersdk.TemplateVersionParameterOption
88+
defaults []string
89+
allowCustombool
90+
want []string
91+
}{
92+
{
93+
name:"Predefined",
94+
options: []codersdk.TemplateVersionParameterOption{
95+
{Name:"AAA",Description:"This is AAA",Value:"aaa"},
96+
{Name:"BBB",Description:"This is BBB",Value:"bbb"},
97+
{Name:"CCC",Description:"This is CCC",Value:"ccc"},
98+
},
99+
defaults: []string{"bbb","ccc"},
100+
allowCustom:false,
101+
want: []string{"bbb","ccc"},
102+
},
103+
{
104+
name:"Custom",
105+
options: []codersdk.TemplateVersionParameterOption{
106+
{Name:"AAA",Description:"This is AAA",Value:"aaa"},
107+
{Name:"BBB",Description:"This is BBB",Value:"bbb"},
108+
{Name:"CCC",Description:"This is CCC",Value:"ccc"},
109+
},
110+
defaults: []string{"aaa","bbb"},
111+
allowCustom:true,
112+
want: []string{"aaa","bbb"},
113+
},
114+
}
104115

105-
t.Run("MultiSelectWithCustomInput",func(t*testing.T) {
106-
t.Parallel()
107-
items:= []string{"Code","Chairs","Whale","Diamond","Carrot"}
108-
ptty:=ptytest.New(t)
109-
msgChan:=make(chan []string)
110-
gofunc() {
111-
resp,err:=newMultiSelectWithCustomInput(ptty,items)
112-
assert.NoError(t,err)
113-
msgChan<-resp
114-
}()
115-
require.Equal(t,items,<-msgChan)
116-
})
117-
}
116+
for_,tt:=rangetests {
117+
t.Run(tt.name,func(t*testing.T) {
118+
t.Parallel()
118119

119-
funcnewMultiSelectWithCustomInput(ptty*ptytest.PTY,items[]string) ([]string,error) {
120-
varvalues []string
121-
cmd:=&serpent.Command{
122-
Handler:func(inv*serpent.Invocation)error {
123-
selectedItems,err:=cliui.MultiSelect(inv, cliui.MultiSelectOptions{
124-
Options:items,
125-
Defaults:items,
126-
EnableCustomInput:true,
127-
})
128-
iferr==nil {
129-
values=selectedItems
120+
varselectedItems[]string
121+
varerrerror
122+
cmd:=&serpent.Command{
123+
Handler:func(inv*serpent.Invocation)error {
124+
selectedItems,err=cliui.RichMultiSelect(inv, cliui.RichMultiSelectOptions{
125+
Options:tt.options,
126+
Defaults:tt.defaults,
127+
EnableCustomInput:tt.allowCustom,
128+
})
129+
returnerr
130+
},
130131
}
131-
returnerr
132+
133+
doneChan:=make(chanstruct{})
134+
gofunc() {
135+
deferclose(doneChan)
136+
err:=cmd.Invoke().Run()
137+
assert.NoError(t,err)
138+
}()
139+
<-doneChan
140+
141+
require.Equal(t,tt.want,selectedItems)
142+
})
143+
}
144+
}
145+
146+
funcTestMultiSelect(t*testing.T) {
147+
t.Parallel()
148+
149+
tests:= []struct {
150+
namestring
151+
items []string
152+
allowCustombool
153+
want []string
154+
}{
155+
{
156+
name:"MultiSelect",
157+
items: []string{"aaa","bbb","ccc"},
158+
allowCustom:false,
159+
want: []string{"aaa","bbb","ccc"},
160+
},
161+
{
162+
name:"MultiSelectWithCustomInput",
163+
items: []string{"Code","Chairs","Whale","Diamond","Carrot"},
164+
allowCustom:true,
165+
want: []string{"Code","Chairs","Whale","Diamond","Carrot"},
132166
},
133167
}
134-
inv:=cmd.Invoke()
135-
ptty.Attach(inv)
136-
returnvalues,inv.Run()
168+
169+
for_,tt:=rangetests {
170+
t.Run(tt.name,func(t*testing.T) {
171+
t.Parallel()
172+
173+
ptty:=ptytest.New(t)
174+
msgChan:=make(chan []string)
175+
176+
gofunc() {
177+
resp,err:=newMultiSelect(ptty,tt.items,tt.allowCustom)
178+
assert.NoError(t,err)
179+
msgChan<-resp
180+
}()
181+
182+
require.Equal(t,tt.want,<-msgChan)
183+
})
184+
}
137185
}
138186

139-
funcnewMultiSelect(ptty*ptytest.PTY,items []string) ([]string,error) {
187+
funcnewMultiSelect(pty*ptytest.PTY,items []string,custombool) ([]string,error) {
140188
varvalues []string
141189
cmd:=&serpent.Command{
142190
Handler:func(inv*serpent.Invocation)error {
143191
selectedItems,err:=cliui.MultiSelect(inv, cliui.MultiSelectOptions{
144-
Options:items,
145-
Defaults:items,
192+
Options:items,
193+
Defaults:items,
194+
EnableCustomInput:custom,
146195
})
147196
iferr==nil {
148197
values=selectedItems
@@ -151,6 +200,6 @@ func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {
151200
},
152201
}
153202
inv:=cmd.Invoke()
154-
ptty.Attach(inv)
203+
pty.Attach(inv)
155204
returnvalues,inv.Run()
156205
}

‎cli/exp_prompts.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,20 @@ func (RootCmd) promptExample() *serpent.Command {
174174
_,_=fmt.Fprintf(inv.Stdout,"%q are nice choices.\n",strings.Join(multiSelectValues,", "))
175175
returnmultiSelectError
176176
},useThingsOption,enableCustomInputOption),
177+
promptCmd("rich-multi-select",func(inv*serpent.Invocation)error {
178+
iflen(multiSelectValues)==0 {
179+
multiSelectValues,multiSelectError=cliui.MultiSelect(inv, cliui.MultiSelectOptions{
180+
Message:"Select some things:",
181+
Options: []string{
182+
"Apples","Plums","Grapes","Oranges","Bananas",
183+
},
184+
Defaults: []string{"Grapes","Plums"},
185+
EnableCustomInput:enableCustomInput,
186+
})
187+
}
188+
_,_=fmt.Fprintf(inv.Stdout,"%q are nice choices.\n",strings.Join(multiSelectValues,", "))
189+
returnmultiSelectError
190+
},useThingsOption,enableCustomInputOption),
177191
promptCmd("rich-parameter",func(inv*serpent.Invocation)error {
178192
value,err:=cliui.RichSelect(inv, cliui.RichSelectOptions{
179193
Options: []codersdk.TemplateVersionParameterOption{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp