5
5
"fmt"
6
6
"github.com/charmbracelet/bubbles/viewport"
7
7
tea "github.com/charmbracelet/bubbletea"
8
- runewidth "github.com/mattn/go-runewidth"
9
- "github.com/muesli/reflow/wordwrap"
10
8
"github.com/muesli/reflow/ansi"
9
+ "github.com/muesli/reflow/wordwrap"
11
10
"github.com/muesli/termenv"
12
11
"sort"
13
12
"strings"
@@ -18,23 +17,23 @@ type Model struct {
18
17
focus bool
19
18
20
19
listItems []item
21
- curIndex int
22
- visibleOffset int
23
- lineCurserOffset int
24
- less func (k , l string ) bool
20
+ curIndex int // curser
21
+ visibleOffset int // begin of the visible lines
22
+ lineCurserOffset int // offset or margin between the cursor and the viewport(visible) border
23
+ less func (k , l string ) bool // function used for sorting
25
24
26
25
jump int // maybe buffer for jumping multiple lines
27
26
28
27
Viewport viewport.Model
29
28
Wrap bool
30
29
31
- SelectedPrefix string
32
- Seperator string
33
- SeperatorWrap string
34
- CurrentMarker string
30
+ SelectedPrefix string
31
+ Seperator string
32
+ SeperatorWrap string
33
+ CurrentMarker string
35
34
36
- Number bool
37
- NumberRelative bool
35
+ Number bool
36
+ NumberRelative bool
38
37
39
38
LineForeGroundStyle termenv.Style
40
39
LineBackGroundStyle termenv.Style
@@ -70,27 +69,49 @@ func View(model tea.Model) string {
70
69
}
71
70
72
71
// check visible area
73
- height := m .Viewport .Height
72
+ height := m .Viewport .Height - 1 // TODO question: why -1, otherwise firstline gets cut of
74
73
width := m .Viewport .Width
74
+ offset := m .visibleOffset
75
75
if height * width <= 0 {
76
76
panic ("Can't display with zero width or hight of Viewport" )
77
77
}
78
78
79
-
80
79
// Get max seperator width
81
80
widthItem := ansi .PrintableRuneWidth (m .Seperator )
82
81
widthWrap := ansi .PrintableRuneWidth (m .SeperatorWrap )
83
82
84
-
85
83
// Find max width
86
84
sepWidth := widthItem
87
85
if widthWrap > sepWidth {
88
86
sepWidth = widthWrap
89
87
}
90
88
89
+ // get widest *displayed* number, for padding
90
+ numWidth := len (fmt .Sprintf ("%d" , len (m .listItems )- 1 ))
91
+ localMaxWidth := len (fmt .Sprintf ("%d" , offset + height - 1 ))
92
+ if localMaxWidth < numWidth {
93
+ numWidth = localMaxWidth
94
+ }
95
+
96
+ // pad all prefixes to the same width for easy exchange
97
+ prefix := m .SelectedPrefix
98
+ preWidth := ansi .PrintableRuneWidth (prefix )
99
+ prepad := strings .Repeat (" " , preWidth )
100
+
101
+ // pad all seperators to the same width for easy exchange
102
+ sepItem := strings .Repeat (" " , sepWidth - widthItem ) + m .Seperator
103
+ sepWrap := strings .Repeat (" " , sepWidth - widthWrap ) + m .SeperatorWrap
104
+
105
+ // pad right of prefix, with lenght of current pointer
106
+ suffix := m .CurrentMarker
107
+ sufWidth := ansi .PrintableRuneWidth (suffix )
108
+ sufpad := strings .Repeat (" " , sufWidth )
109
+
110
+ // Get the hole prefix width
111
+ holePrefixWidth := numWidth + preWidth + sepWidth + sufWidth
112
+
91
113
// Get actual content width
92
- numWidth := len (m .listItems )
93
- contentWidth := width - (numWidth + sepWidth )
114
+ contentWidth := width - holePrefixWidth
94
115
95
116
// Check if there is space for the content left
96
117
if contentWidth <= 0 {
@@ -100,73 +121,54 @@ func View(model tea.Model) string {
100
121
// renew wrap of all items TODO check if to slow
101
122
for i := range m .listItems {
102
123
m .listItems [i ] = m .listItems [i ].genVisLines (contentWidth )
103
-
104
124
}
105
125
106
-
107
- // pad all prefixes to the same width for easy exchange
108
- prefix := m .SelectedPrefix
109
- prepad := strings .Repeat (" " , ansi .PrintableRuneWidth (m .SelectedPrefix ))
110
-
111
- // pad all seperators to the same width for easy exchange
112
- sepItem := strings .Repeat (" " , sepWidth - widthItem )+ m .Seperator
113
- sepWrap := strings .Repeat (" " , sepWidth - widthWrap )+ m .SeperatorWrap
114
-
115
- // pad right of prefix, with lenght of current pointer
116
- suffix := m .CurrentMarker
117
- sufpad := strings .Repeat (" " , ansi .PrintableRuneWidth (suffix ))
118
-
119
126
var visLines int
120
127
var holeString bytes.Buffer
121
128
out:
122
129
// Handle list items, start at first visible and go till end of list or visible (break)
123
- for index := m . visibleOffset ; index < len (m .listItems ); index ++ {
130
+ for index := offset ; index < len (m .listItems ); index ++ {
124
131
if index >= len (m .listItems ) || index < 0 {
132
+ // TODO log error
125
133
break
126
134
}
127
135
128
136
item := m .listItems [index ]
129
- if item .wrapedLenght = = 0 {
137
+ if item .wrapedLenght < = 0 {
130
138
panic ("cant display item with no visible content" )
131
139
}
132
140
133
-
134
- var linePrefix , wrapPrefix string
135
-
136
- // if a number is set, prepend firstline with number and both with enough spaceses
141
+ // if a number is set, prepend firstline with number and both with enough spaces
137
142
firstPad := strings .Repeat (" " , numWidth )
138
143
var wrapPad string
139
144
if m .Number {
140
- lineNum := lineNumber (m .NumberRelative , m .curIndex , m . visibleOffset + index )
145
+ lineNum := lineNumber (m .NumberRelative , m .curIndex , index )
141
146
number := fmt .Sprintf ("%d" , lineNum )
142
- // since diggets are only singel bytes len is sufficent:
147
+ // since diggets are only singel bytes, len is sufficent:
143
148
firstPad = strings .Repeat (" " , numWidth - len (number )) + number
144
149
// pad wraped lines
145
150
wrapPad = strings .Repeat (" " , numWidth )
146
151
}
147
152
148
-
149
153
// Selecting: handel highlighting and prefixing of selected lines
150
- selString := prepad // assume not selected
154
+ selString := prepad // assume not selected
151
155
style := termenv .String () // create empty style
152
156
153
157
if item .selected {
154
158
style = m .SelectedBackGroundStyle // fill style
155
- selString = prefix // change if selected
159
+ selString = prefix // change if selected
156
160
}
157
161
158
-
159
162
// Current: handel highlighting of current item/first-line
160
163
curPad := sufpad
161
- if index + m . visibleOffset == m .curIndex {
164
+ if index == m .curIndex {
162
165
style = style .Reverse ()
163
166
curPad = suffix
164
167
}
165
168
166
169
// join all prefixes
167
- linePrefix = strings .Join ([]string {firstPad , linePrefix , selString , sepItem , curPad }, "" )
168
- wrapPrefix = strings .Join ([]string {wrapPad , linePrefix , selString , sepWrap , sufpad }, "" )
169
-
170
+ linePrefix := strings .Join ([]string {firstPad , selString , sepItem , curPad }, "" )
171
+ wrapPrefix := strings .Join ([]string {wrapPad , selString , sepWrap , sufpad }, "" ) // dont prefix wrap lines with CurrentMarker (suffix)
170
172
171
173
// join pad and first line content
172
174
// NOTE linebreak is not added here because it would mess with the highlighting
@@ -177,16 +179,16 @@ out:
177
179
holeString .WriteString ("\n " )
178
180
visLines ++
179
181
180
- // Dont write wraped lines if not set
181
- if ! m .Wrap || item .wrapedLenght <= 1 {
182
- continue
183
- }
184
-
185
182
// Only write lines that are visible
186
183
if visLines >= height {
187
184
break out
188
185
}
189
186
187
+ // Dont write wraped lines if not set
188
+ if ! m .Wrap || item .wrapedLenght <= 1 {
189
+ continue
190
+ }
191
+
190
192
// Write wraped lines
191
193
for _ , line := range item .wrapedLines [1 :] {
192
194
// Pad left of line
@@ -241,13 +243,11 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
241
243
return m , nil
242
244
}
243
245
244
-
245
246
case tea.WindowSizeMsg :
246
247
247
248
m .Viewport .Width = msg .Width
248
249
m .Viewport .Height = msg .Height
249
250
250
-
251
251
// Because we're using the viewport's default update function (with pager-
252
252
// style navigation) it's important that the viewport's update function:
253
253
//
@@ -284,8 +284,8 @@ func (m *Model) Down() error {
284
284
m .curIndex ++
285
285
// move visible part of list if Curser is going beyond border.
286
286
lowerBorder := m .Viewport .Height + m .visibleOffset - m .lineCurserOffset
287
- if m .curIndex >= lowerBorder {
288
- m .visibleOffset ++
287
+ if m .curIndex > lowerBorder {
288
+ m .visibleOffset = m . curIndex - ( m . Viewport . Height - m . lineCurserOffset )
289
289
}
290
290
return nil
291
291
}
@@ -301,7 +301,7 @@ func (m *Model) Up() error {
301
301
// move visible part of list if Curser is going beyond border.
302
302
upperBorder := m .visibleOffset + m .lineCurserOffset
303
303
if m .visibleOffset > 0 && m .curIndex <= upperBorder {
304
- m .visibleOffset --
304
+ m .visibleOffset = m . curIndex - m . lineCurserOffset
305
305
}
306
306
return nil
307
307
}
@@ -320,11 +320,12 @@ func NewModel() Model {
320
320
321
321
Wrap : true ,
322
322
323
- Seperator : "╭" ,
324
- SeperatorWrap : "│" ,
325
- CurrentMarker : ">" ,
326
- SelectedPrefix : "*" ,
327
- Number : true ,
323
+ Seperator : "╭" ,
324
+ SeperatorWrap : "│" ,
325
+ CurrentMarker : ">" ,
326
+ SelectedPrefix : "*" ,
327
+ Number : true ,
328
+
328
329
less : func (k , l string ) bool {
329
330
return k < l
330
331
},
@@ -341,9 +342,9 @@ func (m *Model) Top() {
341
342
342
343
// Bottom moves the cursor to the last line
343
344
func (m * Model ) Bottom () {
344
- end := len (m .listItems )- 1
345
+ end := len (m .listItems ) - 1
345
346
m .curIndex = end
346
- maxVisItems := m .Viewport .Height - m .lineCurserOffset
347
+ maxVisItems := m .Viewport .Height - m .lineCurserOffset
347
348
var visLines , smallestVisIndex int
348
349
for c := end ; visLines < maxVisItems ; c -- {
349
350
if c < 0 {
@@ -355,21 +356,8 @@ func (m *Model) Bottom() {
355
356
m .visibleOffset = smallestVisIndex
356
357
}
357
358
358
- // maxRuneWidth returns the maximal lenght of occupied space
359
- // frome the given strings
360
- func maxRuneWidth (words ... string ) int {
361
- var max int
362
- for _ , w := range words {
363
- width := runewidth .StringWidth (w )
364
- if width > max {
365
- max = width
366
- }
367
- }
368
- return max
369
- }
370
-
371
- // GetSelected returns you a orderd list of all items
372
- // that are selected
359
+ // GetSelected returns you a list of all items
360
+ // that are selected in current (displayed) order
373
361
func (m * Model ) GetSelected () []string {
374
362
var selected []string
375
363
for _ , item := range m .listItems {
@@ -406,12 +394,14 @@ func (m *Model) Sort() {
406
394
sort .Sort (m )
407
395
}
408
396
397
+ // lineNumber returns line number of the given index
398
+ // and if relative is true the absolute difference to the curser
409
399
func lineNumber (relativ bool , curser , current int ) int {
410
400
if ! relativ || curser == current {
411
401
return current
412
402
}
413
403
414
- diff := curser - current
404
+ diff := curser - current
415
405
if diff < 0 {
416
406
diff *= - 1
417
407
}
0 commit comments