7
7
tea "github.com/charmbracelet/bubbletea"
8
8
runewidth "github.com/mattn/go-runewidth"
9
9
"github.com/muesli/reflow/wordwrap"
10
+ "github.com/muesli/reflow/ansi"
10
11
"github.com/muesli/termenv"
11
12
"sort"
12
13
"strings"
@@ -27,12 +28,13 @@ type Model struct {
27
28
Viewport viewport.Model
28
29
Wrap bool
29
30
31
+ SelectedPrefix string
30
32
Seperator string
31
33
SeperatorWrap string
32
- SeperatorCurrent string
33
- SelectedPrefix string
34
- RelativeNumber bool
35
- AbsoluteNumber bool
34
+ CurrentMarker string
35
+
36
+ Number bool
37
+ NumberRelative bool
36
38
37
39
LineForeGroundStyle termenv.Style
38
40
LineBackGroundStyle termenv.Style
@@ -74,31 +76,21 @@ func View(model tea.Model) string {
74
76
panic ("Can't display with zero width or hight of Viewport" )
75
77
}
76
78
77
- // if there is something to pad
78
- var relWidth , absWidth , padWidth int
79
79
80
- if m . RelativeNumber {
81
- relWidth = len ( fmt . Sprintf ( "%d" , height ) )
82
- }
80
+ // Get max seperator width
81
+ widthItem := ansi . PrintableRuneWidth ( m . Seperator )
82
+ widthWrap := ansi . PrintableRuneWidth ( m . SeperatorWrap )
83
83
84
- if m .AbsoluteNumber {
85
- absWidth = len (fmt .Sprintf ("%d" , len (m .listItems )))
86
- }
87
84
88
- // get widest number to pad
89
- padWidth = relWidth
90
- if padWidth < absWidth {
91
- padWidth = absWidth
85
+ // Find max width
86
+ sepWidth := widthItem
87
+ if widthWrap > sepWidth {
88
+ sepWidth = widthWrap
92
89
}
93
90
94
- // Get max seperator width
95
- sepWidth := maxRuneWidth (m .Seperator , m .SeperatorWrap , m .SeperatorCurrent ) + runewidth .StringWidth (m .SelectedPrefix )
96
-
97
- //Get hole Width
98
- holeWidth := sepWidth + padWidth
99
-
100
91
// Get actual content width
101
- contentWidth := width - (holeWidth + 1 )
92
+ numWidth := len (m .listItems )
93
+ contentWidth := width - (numWidth + sepWidth )
102
94
103
95
// Check if there is space for the content left
104
96
if contentWidth <= 0 {
@@ -111,6 +103,19 @@ func View(model tea.Model) string {
111
103
112
104
}
113
105
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
+
114
119
var visLines int
115
120
var holeString bytes.Buffer
116
121
out:
@@ -121,49 +126,59 @@ out:
121
126
}
122
127
123
128
item := m .listItems [index ]
129
+ if item .wrapedLenght == 0 {
130
+ panic ("cant display item with no visible content" )
131
+ }
124
132
125
- sepString := m .Seperator
126
- wrapString := m .SeperatorWrap
127
133
128
- // handel highlighting and prefixing of selected lines
129
- style := termenv .String ()
134
+ var linePrefix , wrapPrefix string
135
+
136
+ // if a number is set, prepend firstline with number and both with enough spaceses
137
+ firstPad := strings .Repeat (" " , numWidth )
138
+ var wrapPad string
139
+ if m .Number {
140
+ lineNum := lineNumber (m .NumberRelative , m .curIndex , m .visibleOffset + index )
141
+ number := fmt .Sprintf ("%d" , lineNum )
142
+ // since diggets are only singel bytes len is sufficent:
143
+ firstPad = strings .Repeat (" " , numWidth - len (number )) + number
144
+ // pad wraped lines
145
+ wrapPad = strings .Repeat (" " , numWidth )
146
+ }
147
+
148
+
149
+ // Selecting: handel highlighting and prefixing of selected lines
150
+ selString := prepad // assume not selected
151
+ style := termenv .String () // create empty style
152
+
130
153
if item .selected {
131
- style = m .SelectedBackGroundStyle
132
- sepString = m .SelectedPrefix + sepString
133
- wrapString = m .SelectedPrefix + wrapString
154
+ style = m .SelectedBackGroundStyle // fill style
155
+ selString = prefix // change if selected
134
156
}
135
157
136
- // handel highlighting of current line
158
+
159
+ // Current: handel highlighting of current item/first-line
160
+ curPad := sufpad
137
161
if index + m .visibleOffset == m .curIndex {
138
162
style = style .Reverse ()
139
- sepString = m . SeperatorCurrent
163
+ curPad = suffix
140
164
}
141
165
142
- // if set, prepend firstline with enough space for linenumber and seperator
143
- // This while first create a string like: "%3d%4s"
144
- // Which will be than filled with linenumber and seperator string
145
- var firstPad string
146
- if m .AbsoluteNumber || m .RelativeNumber {
147
- lineOffset := m .visibleOffset + index
148
- firstPad = fmt .Sprintf ("%" + fmt .Sprint (padWidth )+ "d%" + fmt .Sprint (sepWidth )+ "s" , lineOffset , sepString )
149
- }
166
+ // join all prefixes
167
+ linePrefix = strings .Join ([]string {firstPad , linePrefix , selString , sepItem , curPad }, "" )
168
+ wrapPrefix = strings .Join ([]string {wrapPad , linePrefix , selString , sepWrap , sufpad }, "" )
150
169
151
- if item .wrapedLenght == 0 {
152
- panic ("cant display item with no visible content" )
153
- }
154
170
155
- lineContent := item .wrapedLines [0 ]
156
- // join pad and line content
171
+ // join pad and first line content
157
172
// NOTE linebreak is not added here because it would mess with the highlighting
158
- line := fmt .Sprintf ("%s%s" , firstPad , lineContent )
173
+ line := fmt .Sprintf ("%s%s" , linePrefix , item . wrapedLines [ 0 ] )
159
174
160
175
// Highlight and write first line
161
176
holeString .WriteString (style .Styled (line ))
162
177
holeString .WriteString ("\n " )
163
178
visLines ++
164
179
165
180
// Dont write wraped lines if not set
166
- if ! m .Wrap || item .wrapedLenght < 1 {
181
+ if ! m .Wrap || item .wrapedLenght <= 1 {
167
182
continue
168
183
}
169
184
@@ -175,10 +190,8 @@ out:
175
190
// Write wraped lines
176
191
for _ , line := range item .wrapedLines [1 :] {
177
192
// Pad left of line
178
- // TODO performance: do stringlength and prepending befor loop
179
- pad := strings .Repeat (" " , holeWidth - runewidth .StringWidth (wrapString )) + wrapString
180
193
// NOTE linebreak is not added here because it would mess with the highlighting
181
- padLine := fmt .Sprintf ("%s%s" , pad , line )
194
+ padLine := fmt .Sprintf ("%s%s" , wrapPrefix , line )
182
195
183
196
// Highlight and write wraped line
184
197
holeString .WriteString (style .Styled (padLine ))
@@ -307,11 +320,11 @@ func NewModel() Model {
307
320
308
321
Wrap : true ,
309
322
310
- Seperator : " ╭ " ,
311
- SeperatorWrap : " │ " ,
312
- SeperatorCurrent : " ╭ >" ,
323
+ Seperator : "╭ " ,
324
+ SeperatorWrap : "│ " ,
325
+ CurrentMarker : ">" ,
313
326
SelectedPrefix : "*" ,
314
- AbsoluteNumber : true ,
327
+ Number : true ,
315
328
less : func (k , l string ) bool {
316
329
return k < l
317
330
},
@@ -392,3 +405,15 @@ func (m *Model) SetLess(less func(string, string) bool) {
392
405
func (m * Model ) Sort () {
393
406
sort .Sort (m )
394
407
}
408
+
409
+ func lineNumber (relativ bool , curser , current int ) int {
410
+ if ! relativ || curser == current {
411
+ return current
412
+ }
413
+
414
+ diff := curser - current
415
+ if diff < 0 {
416
+ diff *= - 1
417
+ }
418
+ return diff
419
+ }
0 commit comments