@@ -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
330331initialModel := 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
336338initialModel .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 ) {
387392var cmd tea.Cmd
388393
394+ if m .isCustomInputMode {
395+ return m .handleCustomInputMode (msg )
396+ }
397+
389398switch msg := msg .(type ) {
390399case terminateMsg :
391400m .canceled = true
@@ -398,6 +407,11 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
398407return m ,tea .Quit
399408
400409case 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+ }
401415if len (m .options )!= 0 {
402416m .selected = true
403417return m ,tea .Quit
@@ -413,16 +427,16 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
413427return m ,nil
414428
415429case tea .KeyUp :
416- options := m .filteredOptions ()
430+ maxIndex := m .getMaxIndex ()
417431if m .cursor > 0 {
418432m .cursor --
419433}else {
420- m .cursor = len ( options ) - 1
434+ m .cursor = maxIndex
421435}
422436
423437case tea .KeyDown :
424- options := m .filteredOptions ()
425- if m .cursor < len ( options ) - 1 {
438+ maxIndex := m .getMaxIndex ()
439+ if m .cursor < maxIndex {
426440m .cursor ++
427441}else {
428442m .cursor = 0
@@ -457,6 +471,91 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
457471return 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 {
461560var s strings.Builder
462561
@@ -469,13 +568,19 @@ func (m multiSelectModel) View() string {
469568return 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 " ,
474578msg ,
475579m .search .View (),
476580))
477581
478- for i ,option := range m .filteredOptions () {
582+ options := m .filteredOptions ()
583+ for i ,option := range options {
479584cursor := " "
480585chosen := "[ ]"
481586o := 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+ }
501616return s .String ()
502617}
503618