@@ -10,14 +10,27 @@ import (
10
10
"strings"
11
11
)
12
12
13
+ // NotFound gets return if the search does not yield a result
14
+ type NotFound error
15
+
16
+ // OutOfBounds is return if and index is outside the list bounderys
17
+ type OutOfBounds error
18
+
19
+ // MultipleMatches gets return if the search yield more result
20
+ type MultipleMatches error
21
+
22
+ // ConfigError is return if there is a error with the configuration of the list Modul
23
+ type ConfigError error
24
+
13
25
// Model is a bubbletea List of strings
14
26
type Model struct {
15
27
focus bool
16
28
17
29
listItems []item
18
- curIndex int // cursor
19
- visibleOffset int // begin of the visible lines
20
- less func (k , l string ) bool // function used for sorting
30
+ curIndex int // cursor
31
+ visibleOffset int // begin of the visible lines
32
+ less func (string , string ) bool // function used for sorting
33
+ equals func (fmt.Stringer , fmt.Stringer ) bool // to be set from the user
21
34
22
35
CursorOffset int // offset or margin between the cursor and the viewport(visible) border
23
36
@@ -32,7 +45,7 @@ type Model struct {
32
45
SeperatorWrap string
33
46
CurrentMarker string
34
47
35
- WrapPrefix bool
48
+ PrefixWrap bool
36
49
37
50
Number bool
38
51
NumberRelative bool
@@ -128,9 +141,11 @@ func (m *Model) Lines() []string {
128
141
}
129
142
selected = strings .Repeat (" " , selectWidth - selWid ) + selected
130
143
131
- wrapPrePad := unselect
132
- if ! m .WrapPrefix {
133
- wrapPrePad = strings .Repeat (" " , selectWidth )
144
+ wrapSelectPad := strings .Repeat (" " , selectWidth )
145
+ wrapUnSelePad := strings .Repeat (" " , selectWidth )
146
+ if m .PrefixWrap {
147
+ wrapSelectPad = strings .Repeat (" " , selectWidth - selWid ) + selected
148
+ wrapUnSelePad = strings .Repeat (" " , selectWidth - tmpWid ) + unselect
134
149
}
135
150
136
151
unselect = strings .Repeat (" " , selectWidth - tmpWid ) + unselect
@@ -195,9 +210,11 @@ out:
195
210
selString := unselect
196
211
style := m .LineStyle
197
212
213
+ wrapPrePad := wrapUnSelePad
198
214
if item .selected {
199
215
style = m .SelectedStyle
200
216
selString = selected
217
+ wrapPrePad = wrapSelectPad
201
218
}
202
219
203
220
// Current: handle highlighting of current item/first-line
260
277
261
278
// lineNumber returns line number of the given index
262
279
// and if relative is true the absolute difference to the cursor
280
+ // or if on the cursor the absolute line number
263
281
func lineNumber (relativ bool , curser , current int ) int {
264
282
if ! relativ || curser == current {
265
283
return current
@@ -296,7 +314,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
296
314
return m , nil
297
315
case " " :
298
316
m .ToggleSelect (1 )
299
- m .Down ( )
317
+ m .Move ( 1 )
300
318
return m , nil
301
319
case "g" :
302
320
m .Top ()
@@ -334,10 +352,10 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
334
352
case tea.MouseMsg :
335
353
switch msg .Button {
336
354
case tea .MouseWheelUp :
337
- m .Up ( )
355
+ m .Move ( - 1 )
338
356
339
357
case tea .MouseWheelDown :
340
- m .Down ( )
358
+ m .Move ( 1 )
341
359
}
342
360
}
343
361
return m , nil
@@ -356,19 +374,19 @@ func (m *Model) AddItems(itemList []fmt.Stringer) {
356
374
357
375
// Down moves the "cursor" or current line down.
358
376
// If the end is already reached err is not nil.
359
- func (m * Model ) Down () error {
360
- return m .Move (1 )
361
- }
377
+ // func (m *Model) Down() error {
378
+ // return m.Move(1)
379
+ // }
362
380
363
381
// Up moves the "cursor" or current line up.
364
382
// If the start is already reached, err is not nil.
365
- func (m * Model ) Up () error {
366
- return m .Move (- 1 )
367
- }
383
+ // func (m *Model) Up() error {
384
+ // return m.Move(-1)
385
+ // }
368
386
369
387
// Move moves the cursor by amount, does nothing if amount is 0
370
- // and returns error != nil if amount go's beyond list borders
371
- // or if the CursorOffset is greater than half of the display height
388
+ // and returns OutOfBounds error if amount go's beyond list borders
389
+ // or if the CursorOffset is greater than half of the display height returns ConfigError
372
390
func (m * Model ) Move (amount int ) error {
373
391
// do nothing
374
392
if amount == 0 {
@@ -380,12 +398,12 @@ func (m *Model) Move(amount int) error {
380
398
height := m .Height
381
399
if curOff >= height / 2 {
382
400
curOff = 0
383
- err = fmt .Errorf ("cursor offset must be less than halfe of the display height: setting it to zero" )
401
+ err = ConfigError ( fmt .Errorf ("cursor offset must be less than halfe of the display height: setting it to zero" ) )
384
402
}
385
403
386
404
target := m .curIndex + amount
387
405
if ! m .CheckWithinBorder (target ) {
388
- return fmt .Errorf ("Cant move outside the list: %d" , target )
406
+ return OutOfBounds ( fmt .Errorf ("Cant move outside the list: %d" , target ) )
389
407
}
390
408
// move visible part of list if Cursor is going beyond border.
391
409
lowerBorder := height + visOff - curOff
@@ -414,6 +432,7 @@ func (m *Model) Move(amount int) error {
414
432
}
415
433
416
434
// NewModel returns a Model with some save/sane defaults
435
+ // design to transfer as much internal information to the user
417
436
func NewModel () Model {
418
437
p := termenv .ColorProfile ()
419
438
selStyle := termenv.Style {}.Background (p .Color ("#ff0000" ))
@@ -427,7 +446,8 @@ func NewModel() Model {
427
446
CursorOffset : 5 ,
428
447
429
448
// Wrap lines to have no loss of information
430
- Wrap : true ,
449
+ Wrap : true ,
450
+ PrefixWrap : true ,
431
451
432
452
// Make clear where a item begins and where it ends
433
453
Seperator : "╭" ,
@@ -467,7 +487,7 @@ func (m *Model) ToggleSelect(amount int) error {
467
487
cur := m .curIndex
468
488
target := cur + amount - direction
469
489
if ! m .CheckWithinBorder (target ) {
470
- return fmt .Errorf ("Cant go beyond list borders: %d" , target )
490
+ return OutOfBounds ( fmt .Errorf ("Cant go beyond list borders: %d" , target ) )
471
491
}
472
492
for c := 0 ; c < amount * direction ; c ++ {
473
493
m .listItems [cur + c ].selected = ! m .listItems [cur + c ].selected
@@ -479,8 +499,8 @@ func (m *Model) ToggleSelect(amount int) error {
479
499
480
500
// MarkSelected selects or unselects depending on 'mark'
481
501
// amount = 0 changes the current item but does not move the cursor
482
- // if amount would be outside the list error is not nil
483
- // else all items till but excluding the end cursor position
502
+ // if amount would be outside the list error is from type OutOfBounds
503
+ // else all items till but excluding the end cursor position gets (un-)marked
484
504
func (m * Model ) MarkSelected (amount int , mark bool ) error {
485
505
cur := m .curIndex
486
506
if amount == 0 {
@@ -494,7 +514,7 @@ func (m *Model) MarkSelected(amount int, mark bool) error {
494
514
495
515
target := cur + amount - direction
496
516
if ! m .CheckWithinBorder (target ) {
497
- return fmt .Errorf ("Cant go beyond list borders: %d" , target )
517
+ return OutOfBounds ( fmt .Errorf ("Cant go beyond list borders: %d" , target ) )
498
518
}
499
519
for c := 0 ; c < amount * direction ; c ++ {
500
520
m .listItems [cur + c ].selected = mark
@@ -546,7 +566,6 @@ func (m *Model) GetSelected() []fmt.Stringer {
546
566
}
547
567
548
568
// Less is a Proxy to the less function, set from the user.
549
- // Swap is used to fulfill the Sort-interface
550
569
func (m * Model ) Less (i , j int ) bool {
551
570
return m .less (m .listItems [i ].value .String (), m .listItems [j ].value .String ())
552
571
}
@@ -566,11 +585,26 @@ func (m *Model) SetLess(less func(string, string) bool) {
566
585
m .less = less
567
586
}
568
587
569
- // Sort sorts the listitems according to the set less function
570
- // The current Item will maybe change!
588
+ // Sort sorts the list items according to the set less- function
589
+ // If there is no Equals-function set (with SetEquals), the current Item will maybe change!
571
590
// Since the index of the current pointer does not change
572
591
func (m * Model ) Sort () {
592
+ equ := m .equals
593
+ var tmp item
594
+ if equ != nil {
595
+ tmp = m .listItems [m .curIndex ]
596
+ }
573
597
sort .Sort (m )
598
+ if equ == nil {
599
+ return
600
+ }
601
+ for i , item := range m .listItems {
602
+ if is := equ (item .value , tmp .value ); is {
603
+ m .curIndex = i
604
+ break // Stop when first (and hopefully only one) is found
605
+ }
606
+ }
607
+
574
608
}
575
609
576
610
// MoveItem moves the current item by amount to the end
@@ -585,7 +619,7 @@ func (m *Model) MoveItem(amount int) error {
585
619
cur := m .curIndex
586
620
target := cur + amount
587
621
if ! m .CheckWithinBorder (target ) {
588
- return fmt .Errorf ("Cant move outside the list: %d" , target )
622
+ return OutOfBounds ( fmt .Errorf ("Cant move outside the list: %d" , target ) )
589
623
}
590
624
m .Swap (cur , target )
591
625
m .curIndex = target
@@ -601,12 +635,6 @@ func (m *Model) CheckWithinBorder(index int) bool {
601
635
return true
602
636
}
603
637
604
- // AddDataItem adds a Item with the given interface{} value added to the List item
605
- // So that when sorting, the connection between the string and the interfave{} value stays.
606
- //func (m *Model) AddDataItem(content string, data interface{}) {
607
- // m.listItems = append(m.listItems, item{content: content, userValue: data})
608
- //}
609
-
610
638
// Focus sets the list Model focus so it accepts key input and responds to them
611
639
func (m * Model ) Focus () {
612
640
m .focus = true
@@ -621,3 +649,47 @@ func (m *Model) UnFocus() {
621
649
func (m * Model ) Focused () bool {
622
650
return m .focus
623
651
}
652
+
653
+ // SetEquals sets the internal equals methode used if provided to set the cursor again on the same item after sorting
654
+ func (m * Model ) SetEquals (equ func (first , second fmt.Stringer ) bool ) {
655
+ m .equals = equ
656
+ }
657
+
658
+ // GetEquals returns the internal equals methode
659
+ // used to set the curser after sorting on the same item again
660
+ func (m * Model ) GetEquals () func (first , second fmt.Stringer ) bool {
661
+ return m .equals
662
+ }
663
+
664
+ // GetIndex returns NotFound error if the Equals Methode is not set (SetEquals)
665
+ // or multiple items match the returns MultipleMatches error
666
+ // else it returns the index of the found found item
667
+ func (m * Model ) GetIndex (toSearch fmt.Stringer ) (int , error ) {
668
+ if m .equals == nil {
669
+ return - 1 , NotFound (fmt .Errorf ("no equals function provided. Use SetEquals to set it" ))
670
+ }
671
+ tmpList := m .listItems
672
+ matchList := make ([]chan bool , len (tmpList ))
673
+ equ := m .equals
674
+
675
+ for i , item := range tmpList {
676
+ resChan := make (chan bool )
677
+ matchList [i ] = resChan
678
+ go func (f , s fmt.Stringer , equ func (fmt.Stringer , fmt.Stringer ) bool , res chan <- bool ) {
679
+ res <- equ (f , s )
680
+ }(item .value , toSearch , equ , resChan )
681
+ }
682
+
683
+ var c , lastIndex int
684
+ for i , resChan := range matchList {
685
+ if <- resChan {
686
+ c ++
687
+ lastIndex = i
688
+ }
689
+ }
690
+ if c > 1 {
691
+ return - c , MultipleMatches (fmt .Errorf ("The provided equals function yields multiple matches betwen one and other fmt.Stringer's" ))
692
+ }
693
+ return lastIndex , nil
694
+
695
+ }
0 commit comments