Skip to content

Commit d15c470

Browse files
stirbyjohnstcnjoobisb
authored
chore: add cherry-picks for patch 2.18.2 (coder#16061)
Co-authored-by: Cian Johnston <cian@coder.com> Co-authored-by: Joobi S B <joobisb@gmail.com>
1 parent 765d99c commit d15c470

File tree

11 files changed

+854
-175
lines changed

11 files changed

+854
-175
lines changed

cli/cliui/select.go

Lines changed: 132 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,10 @@ func (m selectModel) filteredOptions() []string {
300300
}
301301

302302
type MultiSelectOptions struct {
303-
Message string
304-
Options []string
305-
Defaults []string
303+
Message string
304+
Options []string
305+
Defaults []string
306+
EnableCustomInput bool
306307
}
307308

308309
func MultiSelect(inv *serpent.Invocation, opts MultiSelectOptions) ([]string, error) {
@@ -328,9 +329,10 @@ func MultiSelect(inv *serpent.Invocation, opts MultiSelectOptions) ([]string, er
328329
}
329330

330331
initialModel := multiSelectModel{
331-
search: textinput.New(),
332-
options: options,
333-
message: opts.Message,
332+
search: textinput.New(),
333+
options: options,
334+
message: opts.Message,
335+
enableCustomInput: opts.EnableCustomInput,
334336
}
335337

336338
initialModel.search.Prompt = ""
@@ -370,12 +372,15 @@ type multiSelectOption struct {
370372
}
371373

372374
type multiSelectModel struct {
373-
search textinput.Model
374-
options []*multiSelectOption
375-
cursor int
376-
message string
377-
canceled bool
378-
selected bool
375+
search textinput.Model
376+
options []*multiSelectOption
377+
cursor int
378+
message string
379+
canceled bool
380+
selected bool
381+
isCustomInputMode bool // track if we're adding a custom option
382+
customInput string // store custom input
383+
enableCustomInput bool // control whether custom input is allowed
379384
}
380385

381386
func (multiSelectModel) Init() tea.Cmd {
@@ -386,6 +391,10 @@ func (multiSelectModel) Init() tea.Cmd {
386391
func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
387392
var cmd tea.Cmd
388393

394+
if m.isCustomInputMode {
395+
return m.handleCustomInputMode(msg)
396+
}
397+
389398
switch msg := msg.(type) {
390399
case terminateMsg:
391400
m.canceled = true
@@ -398,6 +407,11 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
398407
return m, tea.Quit
399408

400409
case tea.KeyEnter:
410+
// Switch to custom input mode if we're on the "+ Add custom value:" option
411+
if m.enableCustomInput && m.cursor == len(m.filteredOptions()) {
412+
m.isCustomInputMode = true
413+
return m, nil
414+
}
401415
if len(m.options) != 0 {
402416
m.selected = true
403417
return m, tea.Quit
@@ -413,16 +427,16 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
413427
return m, nil
414428

415429
case tea.KeyUp:
416-
options := m.filteredOptions()
430+
maxIndex := m.getMaxIndex()
417431
if m.cursor > 0 {
418432
m.cursor--
419433
} else {
420-
m.cursor = len(options) - 1
434+
m.cursor = maxIndex
421435
}
422436

423437
case tea.KeyDown:
424-
options := m.filteredOptions()
425-
if m.cursor < len(options)-1 {
438+
maxIndex := m.getMaxIndex()
439+
if m.cursor < maxIndex {
426440
m.cursor++
427441
} else {
428442
m.cursor = 0
@@ -457,6 +471,91 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
457471
return m, cmd
458472
}
459473

474+
func (m multiSelectModel) getMaxIndex() int {
475+
options := m.filteredOptions()
476+
if m.enableCustomInput {
477+
// Include the "+ Add custom value" entry
478+
return len(options)
479+
}
480+
// Includes only the actual options
481+
return len(options) - 1
482+
}
483+
484+
// handleCustomInputMode manages keyboard interactions when in custom input mode
485+
func (m *multiSelectModel) handleCustomInputMode(msg tea.Msg) (tea.Model, tea.Cmd) {
486+
keyMsg, ok := msg.(tea.KeyMsg)
487+
if !ok {
488+
return m, nil
489+
}
490+
491+
switch keyMsg.Type {
492+
case tea.KeyEnter:
493+
return m.handleCustomInputSubmission()
494+
495+
case tea.KeyCtrlC:
496+
m.canceled = true
497+
return m, tea.Quit
498+
499+
case tea.KeyBackspace:
500+
return m.handleCustomInputBackspace()
501+
502+
default:
503+
m.customInput += keyMsg.String()
504+
return m, nil
505+
}
506+
}
507+
508+
// handleCustomInputSubmission processes the submission of custom input
509+
func (m *multiSelectModel) handleCustomInputSubmission() (tea.Model, tea.Cmd) {
510+
if m.customInput == "" {
511+
m.isCustomInputMode = false
512+
return m, nil
513+
}
514+
515+
// Clear search to ensure option is visible and cursor points to the new option
516+
m.search.SetValue("")
517+
518+
// Check for duplicates
519+
for i, opt := range m.options {
520+
if opt.option == m.customInput {
521+
// If the option exists but isn't chosen, select it
522+
if !opt.chosen {
523+
opt.chosen = true
524+
}
525+
526+
// Point cursor to the new option
527+
m.cursor = i
528+
529+
// Reset custom input mode to disabled
530+
m.isCustomInputMode = false
531+
m.customInput = ""
532+
return m, nil
533+
}
534+
}
535+
536+
// Add new unique option
537+
m.options = append(m.options, &multiSelectOption{
538+
option: m.customInput,
539+
chosen: true,
540+
})
541+
542+
// Point cursor to the newly added option
543+
m.cursor = len(m.options) - 1
544+
545+
// Reset custom input mode to disabled
546+
m.customInput = ""
547+
m.isCustomInputMode = false
548+
return m, nil
549+
}
550+
551+
// handleCustomInputBackspace handles backspace in custom input mode
552+
func (m *multiSelectModel) handleCustomInputBackspace() (tea.Model, tea.Cmd) {
553+
if len(m.customInput) > 0 {
554+
m.customInput = m.customInput[:len(m.customInput)-1]
555+
}
556+
return m, nil
557+
}
558+
460559
func (m multiSelectModel) View() string {
461560
var s strings.Builder
462561

@@ -469,13 +568,19 @@ func (m multiSelectModel) View() string {
469568
return s.String()
470569
}
471570

571+
if m.isCustomInputMode {
572+
_, _ = s.WriteString(fmt.Sprintf("%s\nEnter custom value: %s\n", msg, m.customInput))
573+
return s.String()
574+
}
575+
472576
_, _ = s.WriteString(fmt.Sprintf(
473577
"%s %s[Use arrows to move, space to select, <right> to all, <left> to none, type to filter]\n",
474578
msg,
475579
m.search.View(),
476580
))
477581

478-
for i, option := range m.filteredOptions() {
582+
options := m.filteredOptions()
583+
for i, option := range options {
479584
cursor := " "
480585
chosen := "[ ]"
481586
o := option.option
@@ -498,6 +603,16 @@ func (m multiSelectModel) View() string {
498603
))
499604
}
500605

606+
if m.enableCustomInput {
607+
// Add the "+ Add custom value" option at the bottom
608+
cursor := " "
609+
text := " + Add custom value"
610+
if m.cursor == len(options) {
611+
cursor = pretty.Sprint(DefaultStyles.Keyword, "> ")
612+
text = pretty.Sprint(DefaultStyles.Keyword, text)
613+
}
614+
_, _ = s.WriteString(fmt.Sprintf("%s%s\n", cursor, text))
615+
}
501616
return s.String()
502617
}
503618

cli/cliui/select_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,39 @@ func TestMultiSelect(t *testing.T) {
101101
}()
102102
require.Equal(t, items, <-msgChan)
103103
})
104+
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+
go func() {
111+
resp, err := newMultiSelectWithCustomInput(ptty, items)
112+
assert.NoError(t, err)
113+
msgChan <- resp
114+
}()
115+
require.Equal(t, items, <-msgChan)
116+
})
117+
}
118+
119+
func newMultiSelectWithCustomInput(ptty *ptytest.PTY, items []string) ([]string, error) {
120+
var values []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+
if err == nil {
129+
values = selectedItems
130+
}
131+
return err
132+
},
133+
}
134+
inv := cmd.Invoke()
135+
ptty.Attach(inv)
136+
return values, inv.Run()
104137
}
105138

106139
func newMultiSelect(ptty *ptytest.PTY, items []string) ([]string, error) {

cli/prompts.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ func (RootCmd) promptExample() *serpent.Command {
4141
Default: "",
4242
Value: serpent.StringArrayOf(&multiSelectValues),
4343
}
44+
45+
enableCustomInput bool
46+
enableCustomInputOption = serpent.Option{
47+
Name: "enable-custom-input",
48+
Description: "Enable custom input option in multi-select.",
49+
Required: false,
50+
Flag: "enable-custom-input",
51+
Value: serpent.BoolOf(&enableCustomInput),
52+
}
4453
)
4554
cmd := &serpent.Command{
4655
Use: "prompt-example",
@@ -156,14 +165,15 @@ func (RootCmd) promptExample() *serpent.Command {
156165
multiSelectValues, multiSelectError = cliui.MultiSelect(inv, cliui.MultiSelectOptions{
157166
Message: "Select some things:",
158167
Options: []string{
159-
"Code", "Chair", "Whale", "Diamond", "Carrot",
168+
"Code", "Chairs", "Whale", "Diamond", "Carrot",
160169
},
161-
Defaults: []string{"Code"},
170+
Defaults: []string{"Code"},
171+
EnableCustomInput: enableCustomInput,
162172
})
163173
}
164174
_, _ = fmt.Fprintf(inv.Stdout, "%q are nice choices.\n", strings.Join(multiSelectValues, ", "))
165175
return multiSelectError
166-
}, useThingsOption),
176+
}, useThingsOption, enableCustomInputOption),
167177
promptCmd("rich-parameter", func(inv *serpent.Invocation) error {
168178
value, err := cliui.RichSelect(inv, cliui.RichSelectOptions{
169179
Options: []codersdk.TemplateVersionParameterOption{

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy