Skip to content

Commit 0e7b9d7

Browse files
Changed prefix closure to interface
- added interfaces Prefixer and Suffixer for handeling line pre- and suffix more go-idomatic - added ScreenInfo struct to transfer informationa about the current screen settings better/shorter - edited example strings
1 parent d18de4c commit 0e7b9d7

File tree

2 files changed

+162
-93
lines changed

2 files changed

+162
-93
lines changed

list/example/main.go

+12-7
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,19 @@ func main() {
2929
"Use 'q' or 'ctrl-c' to quit!",
3030
"You can move the highlighted index up and down with the (arrow keys 'k' and 'j'.",
3131
"Move to the beginning with 'g' and to the end with 'G'.",
32-
"Sort the entrys with 's', but be carefull you can't unsort it again.",
32+
"Sort the entrys with 's', but be carefull you can't restore the former order again.",
3333
"The list can handel linebreaks,\nand has wordwrap enabled if the line gets to long.",
3434
"You can select items with the space key which will select the line and mark it as such.",
3535
"Ones you hit 'enter', the selected lines will be printed to StdOut and the program exits.",
3636
"When you print the items there will be a loss of information,",
3737
"since one can not say what was a line break within an item or what is a new item",
3838
"Use '+' or '-' to move the item under the curser up and down.",
3939
"The key 'v' inverts the selected state of each item.",
40-
//"To toggle betwen only absolute itemnumbers and also relativ numbers, the 'r' key is your friend.",
40+
"To toggle betwen only absolute item numbers and relativ numbers, the 'r' key is your friend.",
4141
}
4242
stringerList := list.MakeStringerList(itemList)
4343

4444
endResult := make(chan string, 1)
45-
prefixFunc := list.AbsolutLinePrefix
4645
list := list.NewModel()
4746
list.AddItems(stringerList)
4847

@@ -52,7 +51,6 @@ func main() {
5251
list.SetEquals(func(first, second fmt.Stringer) bool { return first.String() == second.String() })
5352
m := model{}
5453
m.list = list
55-
m.list.PrefixFunc = prefixFunc
5654

5755
m.endResult = endResult
5856

@@ -95,6 +93,10 @@ func (m model) View() string {
9593

9694
// update recives messages and the model and changes the model accordingly to the messages
9795
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
96+
if m.list.PrefixGen == nil {
97+
// use default
98+
m.list.PrefixGen = list.NewDefault()
99+
}
98100

99101
switch msg := msg.(type) {
100102
case tea.KeyMsg:
@@ -130,9 +132,12 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
130132
}
131133
m.list.Move(-j)
132134
return m, nil
133-
//case "r":
134-
// m.list.NumberRelative = !m.list.NumberRelative
135-
// return m, nil
135+
case "r":
136+
d, ok := m.list.PrefixGen.(*list.DefaultPrefixer)
137+
if ok {
138+
d.NumberRelative = !d.NumberRelative
139+
}
140+
return m, nil
136141
case "m":
137142
j := 1
138143
if m.jump != "" {

list/list.go

+150-86
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ type ViewPos struct {
3232
LineOffset int
3333
}
3434

35+
// ScreenInfo holds all information about the screen Area
36+
type ScreenInfo struct {
37+
Width int
38+
Height int
39+
Profile termenv.Profile
40+
}
41+
42+
// Prefixer is used to prefix all visible Lines.
43+
// Init gets called ones on the beginning of the Lines methode
44+
// and then Prefix ones, per line to draw, to generate according prefixes.
45+
type Prefixer interface {
46+
InitPrefixer(ViewPos, ScreenInfo) int
47+
Prefix(currentItem, currentLine int, selected, lastLine bool) string
48+
}
49+
50+
// Suffixer is used to suffix all visible Lines.
51+
// InitSuffixer gets called ones on the beginning of the Lines methode
52+
// and then Suffix ones, per line to draw, to generate according suffixes.
53+
type Suffixer interface {
54+
InitSuffixer(ViewPos, ScreenInfo) int
55+
Suffix(currentItem, currentLine int, selected, lastLine bool) string
56+
}
57+
3558
// Model is a bubbletea List of strings
3659
type Model struct {
3760
focus bool
@@ -51,11 +74,8 @@ type Model struct {
5174

5275
Wrap bool
5376

54-
PrefixFunc func(position ViewPos, height int) (func(currentIndex int, selected bool, wrapedIndex int) string, int)
55-
// PrefixFunc func(value fmt.Stringer, position ViewPos, selected bool) string
56-
// PrefixWrapFunc func(value fmt.Stringer, position ViewPos, selected bool, wrapedIndex int) string
57-
// SuffixFunc func(value fmt.Stringer, position ViewPos, selected bool) string
58-
// SuffixWrapFunc func(value fmt.Stringer, position ViewPos, selected bool, wrapedIndex int) string
77+
// PrefixFunc func(position ViewPos, height int) (func(currentIndex int, selected bool, wrapedIndex int) string, int)
78+
PrefixGen Prefixer
5979

6080
LineStyle termenv.Style
6181
SelectedStyle termenv.Style
@@ -113,13 +133,10 @@ func (m *Model) Lines() []string {
113133
panic("Can't display with zero width or hight of Viewport")
114134
}
115135

116-
var prefixFunc func(int, bool, int) string
136+
// This is only used if the Lines methode is called befor the Update methode is called the first time
117137
var holePrefixWidth int
118-
if m.PrefixFunc != nil {
119-
prefixFunc, holePrefixWidth = m.PrefixFunc(m.viewPos, height)
120-
} else {
121-
// use default
122-
prefixFunc, holePrefixWidth = AbsolutLinePrefix(m.viewPos, height)
138+
if m.PrefixGen != nil {
139+
holePrefixWidth = m.PrefixGen.InitPrefixer(m.viewPos, ScreenInfo{Height: m.Height, Width: m.Width, Profile: termenv.ColorProfile()})
123140
}
124141

125142
// Get actual content width
@@ -171,10 +188,9 @@ out:
171188
// TODO hard limit the string length
172189
}
173190

174-
// retrieve line prefix from prefix closure
175191
var linePrefix string
176-
if prefixFunc != nil {
177-
linePrefix = prefixFunc(index, item.selected, 0)
192+
if m.PrefixGen != nil {
193+
linePrefix = m.PrefixGen.Prefix(index, 0, item.selected, item.wrapedLenght == 0)
178194
}
179195

180196
// join pad and first line content
@@ -221,8 +237,8 @@ out:
221237
// Pad left of line
222238
// NOTE line break is not added here because it would mess with the highlighting
223239
var wrapPrefix string
224-
if prefixFunc != nil {
225-
wrapPrefix = prefixFunc(index, item.selected, i+1)
240+
if m.PrefixGen != nil {
241+
wrapPrefix = m.PrefixGen.Prefix(index, i+1, item.selected, i == item.wrapedLenght-2)
226242
}
227243
padLine := fmt.Sprintf("%s%s", wrapPrefix, line)
228244

@@ -249,6 +265,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
249265
if !m.focus {
250266
return m, nil
251267
}
268+
269+
if m.PrefixGen == nil {
270+
// use default
271+
m.PrefixGen = NewDefault()
272+
}
273+
252274
var cmd tea.Cmd
253275

254276
switch msg := msg.(type) {
@@ -780,30 +802,72 @@ func (m *Model) keepVisibleWrap(target int) (ViewPos, error) {
780802
return ViewPos{ItemOffset: m.viewPos.ItemOffset, LineOffset: m.viewPos.LineOffset, Cursor: target}, nil
781803
}
782804

783-
// AbsolutLinePrefix returns a function which will be used for prefix generation
784-
func AbsolutLinePrefix(position ViewPos, height int) (func(currentLine int, selected bool, wrapIndex int) string, int) {
785-
786-
PrefixWrap := false
805+
// DefaultPrefixer is the default struct used for Prefixing a line
806+
type DefaultPrefixer struct {
807+
PrefixWrap bool
787808

788809
// Make clear where a item begins and where it ends
789-
Seperator := "╭"
790-
SeperatorWrap := "│"
810+
Seperator string
811+
SeperatorWrap string
791812

792813
// Mark it so that even without color support all is explicit
793-
CurrentMarker := ">"
794-
SelectedPrefix := "*"
814+
CurrentMarker string
815+
SelectedPrefix string
795816

796817
// enable Linenumber
797-
Number := true
798-
NumberRelative := false
818+
Number bool
819+
NumberRelative bool
820+
821+
UnSelectedPrefix string
799822

800-
UnSelectedPrefix := ""
823+
prefixWidth int
824+
viewPos ViewPos
825+
826+
markWidth int
827+
numWidth int
828+
829+
unmark string
830+
mark string
831+
832+
selectedString string
833+
unselect string
834+
835+
wrapSelectPad string
836+
wrapUnSelePad string
837+
838+
sepItem string
839+
sepWrap string
840+
}
841+
842+
// NewDefault returns a DefautPrefixer with default values
843+
func NewDefault() *DefaultPrefixer {
844+
return &DefaultPrefixer{
845+
PrefixWrap: false,
846+
847+
// Make clear where a item begins and where it ends
848+
Seperator: "╭",
849+
SeperatorWrap: "│",
850+
851+
// Mark it so that even without color support all is explicit
852+
CurrentMarker: ">",
853+
SelectedPrefix: "*",
854+
UnSelectedPrefix: "",
855+
856+
// enable Linenumber
857+
Number: true,
858+
NumberRelative: false,
859+
}
860+
}
861+
862+
// InitPrefixer returns a function which will be used for prefix generation
863+
func (d *DefaultPrefixer) InitPrefixer(position ViewPos, screen ScreenInfo) int {
864+
d.viewPos = position
801865

802866
offset := position.ItemOffset
803867

804868
// Get separators width
805-
widthItem := ansi.PrintableRuneWidth(Seperator)
806-
widthWrap := ansi.PrintableRuneWidth(SeperatorWrap)
869+
widthItem := ansi.PrintableRuneWidth(d.Seperator)
870+
widthWrap := ansi.PrintableRuneWidth(d.SeperatorWrap)
807871

808872
// Find max width
809873
sepWidth := widthItem
@@ -812,83 +876,83 @@ func AbsolutLinePrefix(position ViewPos, height int) (func(currentLine int, sele
812876
}
813877

814878
// get widest possible number, for padding
815-
numWidth := len(fmt.Sprintf("%d", offset+height-1))
879+
d.numWidth = len(fmt.Sprintf("%d", offset+screen.Height))
816880

817881
// pad all prefixes to the same width for easy exchange
818-
selectedString := SelectedPrefix
819-
unselect := UnSelectedPrefix
820-
selWid := ansi.PrintableRuneWidth(selectedString)
821-
tmpWid := ansi.PrintableRuneWidth(unselect)
882+
d.selectedString = d.SelectedPrefix
883+
d.unselect = d.UnSelectedPrefix
884+
selWid := ansi.PrintableRuneWidth(d.selectedString)
885+
tmpWid := ansi.PrintableRuneWidth(d.unselect)
822886

823887
selectWidth := selWid
824888
if tmpWid > selectWidth {
825889
selectWidth = tmpWid
826890
}
827-
selectedString = strings.Repeat(" ", selectWidth-selWid) + selectedString
891+
d.selectedString = strings.Repeat(" ", selectWidth-selWid) + d.selectedString
828892

829-
wrapSelectPad := strings.Repeat(" ", selectWidth)
830-
wrapUnSelePad := strings.Repeat(" ", selectWidth)
831-
if PrefixWrap {
832-
wrapSelectPad = strings.Repeat(" ", selectWidth-selWid) + selectedString
833-
wrapUnSelePad = strings.Repeat(" ", selectWidth-tmpWid) + unselect
893+
d.wrapSelectPad = strings.Repeat(" ", selectWidth)
894+
d.wrapUnSelePad = strings.Repeat(" ", selectWidth)
895+
if d.PrefixWrap {
896+
d.wrapSelectPad = strings.Repeat(" ", selectWidth-selWid) + d.selectedString
897+
d.wrapUnSelePad = strings.Repeat(" ", selectWidth-tmpWid) + d.unselect
834898
}
835899

836-
unselect = strings.Repeat(" ", selectWidth-tmpWid) + unselect
900+
d.unselect = strings.Repeat(" ", selectWidth-tmpWid) + d.unselect
837901

838902
// pad all separators to the same width for easy exchange
839-
sepItem := strings.Repeat(" ", sepWidth-widthItem) + Seperator
840-
sepWrap := strings.Repeat(" ", sepWidth-widthWrap) + SeperatorWrap
903+
d.sepItem = strings.Repeat(" ", sepWidth-widthItem) + d.Seperator
904+
d.sepWrap = strings.Repeat(" ", sepWidth-widthWrap) + d.SeperatorWrap
841905

842906
// pad right of prefix, with length of current pointer
843-
mark := CurrentMarker
844-
markWidth := ansi.PrintableRuneWidth(mark)
845-
unmark := strings.Repeat(" ", markWidth)
907+
d.mark = d.CurrentMarker
908+
d.markWidth = ansi.PrintableRuneWidth(d.mark)
909+
d.unmark = strings.Repeat(" ", d.markWidth)
846910

847911
// Get the hole prefix width
848-
holePrefixWidth := numWidth + selectWidth + sepWidth + markWidth
849-
850-
// Closure varibale
851-
currentCursor := position.Cursor
852-
853-
return func(currentIndex int, selected bool, wrapIndex int) string {
854-
// if a number is set, prepend first line with number and both with enough spaces
855-
firstPad := strings.Repeat(" ", numWidth)
856-
var wrapPad string
857-
var lineNum int
858-
if Number {
859-
lineNum = lineNumber(NumberRelative, currentCursor, currentIndex)
860-
}
861-
number := fmt.Sprintf("%d", lineNum)
862-
// since digits are only single bytes, len is sufficient:
863-
firstPad = strings.Repeat(" ", numWidth-len(number)) + number
864-
// pad wrapped lines
865-
wrapPad = strings.Repeat(" ", numWidth)
866-
// Selecting: handle highlighting and prefixing of selected lines
867-
selString := unselect
868-
869-
wrapPrePad := wrapUnSelePad
870-
if selected {
871-
selString = selectedString
872-
wrapPrePad = wrapSelectPad
873-
}
912+
d.prefixWidth = d.numWidth + selectWidth + sepWidth + d.markWidth
874913

875-
// Current: handle highlighting of current item/first-line
876-
curPad := unmark
877-
if currentIndex == position.Cursor {
878-
curPad = mark
879-
}
914+
return d.prefixWidth
915+
}
880916

881-
// join all prefixes
882-
var wrapPrefix, linePrefix string
917+
// Prefix prefixes a given line
918+
func (d *DefaultPrefixer) Prefix(currentIndex int, wrapIndex int, selected, lastLine bool) string {
919+
// if a number is set, prepend first line with number and both with enough spaces
920+
firstPad := strings.Repeat(" ", d.numWidth)
921+
var wrapPad string
922+
var lineNum int
923+
if d.Number {
924+
lineNum = lineNumber(d.NumberRelative, d.viewPos.Cursor, currentIndex)
925+
}
926+
number := fmt.Sprintf("%d", lineNum)
927+
// since digits are only single bytes, len is sufficient:
928+
firstPad = strings.Repeat(" ", d.numWidth-len(number)) + number
929+
// pad wrapped lines
930+
wrapPad = strings.Repeat(" ", d.numWidth)
931+
// Selecting: handle highlighting and prefixing of selected lines
932+
selString := d.unselect
883933

884-
linePrefix = strings.Join([]string{firstPad, selString, sepItem, curPad}, "")
885-
if wrapIndex > 0 {
886-
wrapPrefix = strings.Join([]string{wrapPad, wrapPrePad, sepWrap, unmark}, "") // don't prefix wrap lines with CurrentMarker (unmark)
887-
return wrapPrefix
888-
}
934+
wrapPrePad := d.wrapUnSelePad
935+
if selected {
936+
selString = d.selectedString
937+
wrapPrePad = d.wrapSelectPad
938+
}
939+
940+
// Current: handle highlighting of current item/first-line
941+
curPad := d.unmark
942+
if currentIndex == d.viewPos.Cursor {
943+
curPad = d.mark
944+
}
945+
946+
// join all prefixes
947+
var wrapPrefix, linePrefix string
948+
949+
linePrefix = strings.Join([]string{firstPad, selString, d.sepItem, curPad}, "")
950+
if wrapIndex > 0 {
951+
wrapPrefix = strings.Join([]string{wrapPad, wrapPrePad, d.sepWrap, d.unmark}, "") // don't prefix wrap lines with CurrentMarker (unmark)
952+
return wrapPrefix
953+
}
889954

890-
return linePrefix
891-
}, holePrefixWidth
955+
return linePrefix
892956
}
893957

894958
// lineNumber returns line number of the given index

0 commit comments

Comments
 (0)