Skip to content

Commit bc6f231

Browse files
author
Kilian Drechsler
committed
fixed offrunning bug, added and changed tests
- added new test for down movement when next to curserborder. - applied changes to test, according to previous change of line- and wrap- prefix - changed to not fullscreen example - fixed offrunning curser bug through removing redundent adding of offset - changed Up and Down to set the curser to the right place when entering the curserborder
1 parent afb04d9 commit bc6f231

File tree

3 files changed

+153
-94
lines changed

3 files changed

+153
-94
lines changed

list/example/main.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ func main() {
3333
"Ones you hit 'enter', the selected lines will be printed to StdOut and the program exits.",
3434
"When you print the items there will be a loss of information,",
3535
"since one can not say what was a line break within an item or what is a new item",
36-
3736
}
3837
endResult := make(chan string, 1)
3938

@@ -77,10 +76,10 @@ func view(mdl tea.Model) string {
7776
return "\n Initalizing...\n\n Waiting for info about window size."
7877
}
7978

80-
return list.View(m.list)
79+
listString := list.View(m.list)
80+
return listString
8181
}
8282

83-
8483
// update recives messages and the model and changes the model accordingly to the messages
8584
func update(msg tea.Msg, mdl tea.Model) (tea.Model, tea.Cmd) {
8685
m, _ := mdl.(model)

list/list.go

+70-80
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@ import (
55
"fmt"
66
"github.com/charmbracelet/bubbles/viewport"
77
tea "github.com/charmbracelet/bubbletea"
8-
runewidth "github.com/mattn/go-runewidth"
9-
"github.com/muesli/reflow/wordwrap"
108
"github.com/muesli/reflow/ansi"
9+
"github.com/muesli/reflow/wordwrap"
1110
"github.com/muesli/termenv"
1211
"sort"
1312
"strings"
@@ -18,23 +17,23 @@ type Model struct {
1817
focus bool
1918

2019
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
2524

2625
jump int // maybe buffer for jumping multiple lines
2726

2827
Viewport viewport.Model
2928
Wrap bool
3029

31-
SelectedPrefix string
32-
Seperator string
33-
SeperatorWrap string
34-
CurrentMarker string
30+
SelectedPrefix string
31+
Seperator string
32+
SeperatorWrap string
33+
CurrentMarker string
3534

36-
Number bool
37-
NumberRelative bool
35+
Number bool
36+
NumberRelative bool
3837

3938
LineForeGroundStyle termenv.Style
4039
LineBackGroundStyle termenv.Style
@@ -70,27 +69,49 @@ func View(model tea.Model) string {
7069
}
7170

7271
// check visible area
73-
height := m.Viewport.Height
72+
height := m.Viewport.Height - 1 // TODO question: why -1, otherwise firstline gets cut of
7473
width := m.Viewport.Width
74+
offset := m.visibleOffset
7575
if height*width <= 0 {
7676
panic("Can't display with zero width or hight of Viewport")
7777
}
7878

79-
8079
// Get max seperator width
8180
widthItem := ansi.PrintableRuneWidth(m.Seperator)
8281
widthWrap := ansi.PrintableRuneWidth(m.SeperatorWrap)
8382

84-
8583
// Find max width
8684
sepWidth := widthItem
8785
if widthWrap > sepWidth {
8886
sepWidth = widthWrap
8987
}
9088

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+
91113
// Get actual content width
92-
numWidth := len(m.listItems)
93-
contentWidth := width - (numWidth + sepWidth)
114+
contentWidth := width - holePrefixWidth
94115

95116
// Check if there is space for the content left
96117
if contentWidth <= 0 {
@@ -100,73 +121,54 @@ func View(model tea.Model) string {
100121
// renew wrap of all items TODO check if to slow
101122
for i := range m.listItems {
102123
m.listItems[i] = m.listItems[i].genVisLines(contentWidth)
103-
104124
}
105125

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-
119126
var visLines int
120127
var holeString bytes.Buffer
121128
out:
122129
// 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++ {
124131
if index >= len(m.listItems) || index < 0 {
132+
// TODO log error
125133
break
126134
}
127135

128136
item := m.listItems[index]
129-
if item.wrapedLenght == 0 {
137+
if item.wrapedLenght <= 0 {
130138
panic("cant display item with no visible content")
131139
}
132140

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
137142
firstPad := strings.Repeat(" ", numWidth)
138143
var wrapPad string
139144
if m.Number {
140-
lineNum := lineNumber(m.NumberRelative, m.curIndex, m.visibleOffset+index)
145+
lineNum := lineNumber(m.NumberRelative, m.curIndex, index)
141146
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:
143148
firstPad = strings.Repeat(" ", numWidth-len(number)) + number
144149
// pad wraped lines
145150
wrapPad = strings.Repeat(" ", numWidth)
146151
}
147152

148-
149153
// Selecting: handel highlighting and prefixing of selected lines
150-
selString := prepad // assume not selected
154+
selString := prepad // assume not selected
151155
style := termenv.String() // create empty style
152156

153157
if item.selected {
154158
style = m.SelectedBackGroundStyle // fill style
155-
selString = prefix // change if selected
159+
selString = prefix // change if selected
156160
}
157161

158-
159162
// Current: handel highlighting of current item/first-line
160163
curPad := sufpad
161-
if index+m.visibleOffset == m.curIndex {
164+
if index == m.curIndex {
162165
style = style.Reverse()
163166
curPad = suffix
164167
}
165168

166169
// 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)
170172

171173
// join pad and first line content
172174
// NOTE linebreak is not added here because it would mess with the highlighting
@@ -177,16 +179,16 @@ out:
177179
holeString.WriteString("\n")
178180
visLines++
179181

180-
// Dont write wraped lines if not set
181-
if !m.Wrap || item.wrapedLenght <= 1 {
182-
continue
183-
}
184-
185182
// Only write lines that are visible
186183
if visLines >= height {
187184
break out
188185
}
189186

187+
// Dont write wraped lines if not set
188+
if !m.Wrap || item.wrapedLenght <= 1 {
189+
continue
190+
}
191+
190192
// Write wraped lines
191193
for _, line := range item.wrapedLines[1:] {
192194
// Pad left of line
@@ -241,13 +243,11 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
241243
return m, nil
242244
}
243245

244-
245246
case tea.WindowSizeMsg:
246247

247248
m.Viewport.Width = msg.Width
248249
m.Viewport.Height = msg.Height
249250

250-
251251
// Because we're using the viewport's default update function (with pager-
252252
// style navigation) it's important that the viewport's update function:
253253
//
@@ -284,8 +284,8 @@ func (m *Model) Down() error {
284284
m.curIndex++
285285
// move visible part of list if Curser is going beyond border.
286286
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)
289289
}
290290
return nil
291291
}
@@ -301,7 +301,7 @@ func (m *Model) Up() error {
301301
// move visible part of list if Curser is going beyond border.
302302
upperBorder := m.visibleOffset + m.lineCurserOffset
303303
if m.visibleOffset > 0 && m.curIndex <= upperBorder {
304-
m.visibleOffset--
304+
m.visibleOffset = m.curIndex - m.lineCurserOffset
305305
}
306306
return nil
307307
}
@@ -320,11 +320,12 @@ func NewModel() Model {
320320

321321
Wrap: true,
322322

323-
Seperator: "╭",
324-
SeperatorWrap: "│",
325-
CurrentMarker: ">",
326-
SelectedPrefix: "*",
327-
Number: true,
323+
Seperator: "╭",
324+
SeperatorWrap: "│",
325+
CurrentMarker: ">",
326+
SelectedPrefix: "*",
327+
Number: true,
328+
328329
less: func(k, l string) bool {
329330
return k < l
330331
},
@@ -341,9 +342,9 @@ func (m *Model) Top() {
341342

342343
// Bottom moves the cursor to the last line
343344
func (m *Model) Bottom() {
344-
end := len(m.listItems)-1
345+
end := len(m.listItems) - 1
345346
m.curIndex = end
346-
maxVisItems := m.Viewport.Height-m.lineCurserOffset
347+
maxVisItems := m.Viewport.Height - m.lineCurserOffset
347348
var visLines, smallestVisIndex int
348349
for c := end; visLines < maxVisItems; c-- {
349350
if c < 0 {
@@ -355,21 +356,8 @@ func (m *Model) Bottom() {
355356
m.visibleOffset = smallestVisIndex
356357
}
357358

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
373361
func (m *Model) GetSelected() []string {
374362
var selected []string
375363
for _, item := range m.listItems {
@@ -406,12 +394,14 @@ func (m *Model) Sort() {
406394
sort.Sort(m)
407395
}
408396

397+
// lineNumber returns line number of the given index
398+
// and if relative is true the absolute difference to the curser
409399
func lineNumber(relativ bool, curser, current int) int {
410400
if !relativ || curser == current {
411401
return current
412402
}
413403

414-
diff := curser - current
404+
diff := curser - current
415405
if diff < 0 {
416406
diff *= -1
417407
}

0 commit comments

Comments
 (0)