@@ -195,7 +195,7 @@ func (m selectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
195
195
if m .cursor > 0 {
196
196
m .cursor --
197
197
} else {
198
- m .cursor = len (options ) - 1
198
+ m .cursor = len (options )
199
199
}
200
200
201
201
case tea .KeyDown :
@@ -300,9 +300,10 @@ func (m selectModel) filteredOptions() []string {
300
300
}
301
301
302
302
type MultiSelectOptions struct {
303
- Message string
304
- Options []string
305
- Defaults []string
303
+ Message string
304
+ Options []string
305
+ Defaults []string
306
+ EnableCustomInput bool
306
307
}
307
308
308
309
func MultiSelect (inv * serpent.Invocation , opts MultiSelectOptions ) ([]string , error ) {
@@ -328,9 +329,10 @@ func MultiSelect(inv *serpent.Invocation, opts MultiSelectOptions) ([]string, er
328
329
}
329
330
330
331
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 ,
334
336
}
335
337
336
338
initialModel .search .Prompt = ""
@@ -370,12 +372,15 @@ type multiSelectOption struct {
370
372
}
371
373
372
374
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
+ isInputMode bool // New field to track if we're adding a custom option
382
+ customInput string // New field to store custom input
383
+ enableCustomInput bool // New field to control whether custom input is allowed
379
384
}
380
385
381
386
func (multiSelectModel ) Init () tea.Cmd {
@@ -386,6 +391,10 @@ func (multiSelectModel) Init() tea.Cmd {
386
391
func (m multiSelectModel ) Update (msg tea.Msg ) (tea.Model , tea.Cmd ) {
387
392
var cmd tea.Cmd
388
393
394
+ if m .isInputMode {
395
+ return m .handleCustomInputMode (msg )
396
+ }
397
+
389
398
switch msg := msg .(type ) {
390
399
case terminateMsg :
391
400
m .canceled = true
@@ -398,6 +407,11 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
398
407
return m , tea .Quit
399
408
400
409
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 .isInputMode = true
413
+ return m , nil
414
+ }
401
415
if len (m .options ) != 0 {
402
416
m .selected = true
403
417
return m , tea .Quit
@@ -414,15 +428,17 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
414
428
415
429
case tea .KeyUp :
416
430
options := m .filteredOptions ()
431
+ maxIndex := len (options )
417
432
if m .cursor > 0 {
418
433
m .cursor --
419
434
} else {
420
- m .cursor = len ( options ) - 1
435
+ m .cursor = maxIndex
421
436
}
422
437
423
438
case tea .KeyDown :
424
439
options := m .filteredOptions ()
425
- if m .cursor < len (options )- 1 {
440
+ maxIndex := len (options )
441
+ if m .cursor < maxIndex {
426
442
m .cursor ++
427
443
} else {
428
444
m .cursor = 0
@@ -457,6 +473,52 @@ func (m multiSelectModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
457
473
return m , cmd
458
474
}
459
475
476
+ // handleCustomInputMode manages keyboard interactions when in custom input mode
477
+ func (m * multiSelectModel ) handleCustomInputMode (msg tea.Msg ) (tea.Model , tea.Cmd ) {
478
+ keyMsg , ok := msg .(tea.KeyMsg )
479
+ if ! ok {
480
+ return m , nil
481
+ }
482
+
483
+ switch keyMsg .Type {
484
+ case tea .KeyEnter :
485
+ return m .handleCustomInputSubmission ()
486
+
487
+ case tea .KeyCtrlC :
488
+ m .canceled = true
489
+ return m , tea .Quit
490
+
491
+ case tea .KeyBackspace :
492
+ return m .handleCustomInputBackspace ()
493
+
494
+ default :
495
+ m .customInput += keyMsg .String ()
496
+ return m , nil
497
+ }
498
+ }
499
+
500
+ // handleCustomInputSubmission processes the submission of custom input
501
+ func (m * multiSelectModel ) handleCustomInputSubmission () (tea.Model , tea.Cmd ) {
502
+ if m .customInput != "" {
503
+ m .options = append (m .options , & multiSelectOption {
504
+ option : m .customInput ,
505
+ chosen : true ,
506
+ })
507
+ }
508
+ // Reset input state regardless of whether input was empty
509
+ m .customInput = ""
510
+ m .isInputMode = false
511
+ return m , nil
512
+ }
513
+
514
+ // handleCustomInputBackspace handles backspace in custom input mode
515
+ func (m * multiSelectModel ) handleCustomInputBackspace () (tea.Model , tea.Cmd ) {
516
+ if len (m .customInput ) > 0 {
517
+ m .customInput = m .customInput [:len (m .customInput )- 1 ]
518
+ }
519
+ return m , nil
520
+ }
521
+
460
522
func (m multiSelectModel ) View () string {
461
523
var s strings.Builder
462
524
@@ -469,13 +531,19 @@ func (m multiSelectModel) View() string {
469
531
return s .String ()
470
532
}
471
533
534
+ if m .isInputMode {
535
+ _ , _ = s .WriteString (fmt .Sprintf ("%s\n Enter custom value: %s\n " , msg , m .customInput ))
536
+ return s .String ()
537
+ }
538
+
472
539
_ , _ = s .WriteString (fmt .Sprintf (
473
540
"%s %s[Use arrows to move, space to select, <right> to all, <left> to none, type to filter]\n " ,
474
541
msg ,
475
542
m .search .View (),
476
543
))
477
544
478
- for i , option := range m .filteredOptions () {
545
+ options := m .filteredOptions ()
546
+ for i , option := range options {
479
547
cursor := " "
480
548
chosen := "[ ]"
481
549
o := option .option
@@ -498,6 +566,16 @@ func (m multiSelectModel) View() string {
498
566
))
499
567
}
500
568
569
+ if m .enableCustomInput {
570
+ // Add the "+ Add custom value" option at the bottom
571
+ cursor := " "
572
+ text := " + Add custom value"
573
+ if m .cursor == len (options ) {
574
+ cursor = pretty .Sprint (DefaultStyles .Keyword , "> " )
575
+ text = pretty .Sprint (DefaultStyles .Keyword , text )
576
+ }
577
+ _ , _ = s .WriteString (fmt .Sprintf ("%s%s\n " , cursor , text ))
578
+ }
501
579
return s .String ()
502
580
}
503
581
0 commit comments