@@ -300,9 +300,10 @@ func (m selectModel) filteredOptions() []string {
300300}
301301
302302type 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
308309func 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
372374type 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
381386func (multiSelectModel ) Init () tea.Cmd {
@@ -386,6 +391,10 @@ func (multiSelectModel) Init() tea.Cmd {
386391func (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+
460559func (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\n Enter 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
0 commit comments