-
Notifications
You must be signed in to change notification settings - Fork 204
Commit
Lip Gloss ships with a list rendering sub-package. ```go import "github.com/charmbracelet/lipgloss/list" ``` Define a new list. ```go l := list.New("A", "B", "C") ``` Print the list. ```go fmt.Println(l) // • A // • B // • C ``` <!-- Lists have the ability to nest. ```go l := list.New( "A", list.New("Artichoke"), "B", list.New("Baking Flour", "Bananas", "Barley", "Bean Sprouts"), "C", list.New("Cashew Apple", "Cashews", "Coconut Milk", "Curry Paste", "Currywurst"), "D", list.New("Dill", "Dragonfruit", "Dried Shrimp"), "E", list.New("Eggs"), "F", list.New("Fish Cake", "Furikake"), "J", list.New("Jicama"), "K", list.New("Kohlrabi"), "L", list.New("Leeks", "Lentils", "Licorice Root"), ) ``` Print the list. ```go fmt.Println(l) ``` <p align="center"> <img width="600" alt="image" src="https://github.com/charmbracelet/lipgloss/assets/42545625/0dc9f440-0748-4151-a3b0-7dcf29dfcdb0"> </p> --> Lists can be customized via their enumeration function as well as using `lipgloss.Style`s. ```go enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("99")).MarginRight(1) itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")).MarginRight(1) l := list.New( "Glossier", "Claire’s Boutique", "Nyx", "Mac", "Milk", ). Enumerator(list.Roman). EnumeratorStyle(enumeratorStyle). ItemStyle(itemStyle) ``` Print the list. <p align="center"> <img width="600" alt="List example" src="https://github.com/charmbracelet/lipgloss/assets/42545625/360494f1-57fb-4e13-bc19-0006efe01561"> </p> In addition to the predefined enumerators (`Arabic`, `Alphabet`, `Roman`, `Bullet`, `Tree`), you may also define your own custom enumerator: ```go var DuckDuckGooseEnumerator Enumerator = func(l *List, i int) string { if l.At(i) == "Goose" { return "Honk →" } return "" } ``` Use it in a list: ```go l := list.New("Duck", "Duck", "Duck", "Duck", "Goose", "Duck", "Duck") l.Enumerator(DuckDuckGooseEnumerator) ``` Print the list: <p align="center"> <img width="600" alt="image" src="https://github.com/charmbracelet/lipgloss/assets/42545625/157aaf30-140d-4948-9bb4-dfba46e5b87e"> </p> If you need, you can also build lists incrementally: ```go l := list.New() for i := 0; i < repeat; i++ { l.Item("Lip Gloss") } ```
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/charmbracelet/lipgloss" | ||
"github.com/charmbracelet/lipgloss/list" | ||
) | ||
|
||
func main() { | ||
l := list.New("Duck", "Duck", "Duck", "Duck", "Goose", "Duck", "Duck") | ||
|
||
var DuckDuckGooseEnumerator = func(i int) string { | ||
if l.At(i) == "Goose" { | ||
return "Honk →" | ||
} | ||
return "" | ||
} | ||
|
||
l = l.Enumerator(DuckDuckGooseEnumerator).EnumeratorStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("48")).MarginRight(1)) | ||
|
||
fmt.Println(l) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/charmbracelet/lipgloss" | ||
"github.com/charmbracelet/lipgloss/list" | ||
) | ||
|
||
func main() { | ||
enumeratorStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("99")).MarginRight(1) | ||
itemStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("212")).MarginRight(1) | ||
|
||
l := list.New( | ||
"Glossier", | ||
"Claire’s Boutique", | ||
"Nyx", | ||
"Mac", | ||
"Milk", | ||
). | ||
Enumerator(list.Roman). | ||
EnumeratorStyle(enumeratorStyle). | ||
ItemStyle(itemStyle) | ||
|
||
fmt.Println(l.String()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package list | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// Enumerator defines a function that returns the correct prefix for the list | ||
// element at the given index. | ||
type Enumerator func(i int) string | ||
|
||
const abcLen = 26 | ||
|
||
// Alphabet is the enumeration for alphabetical listing. | ||
// | ||
// a. Foo | ||
// b. Bar | ||
// c. Baz | ||
// d. Qux. | ||
func Alphabet(i int) string { | ||
if i >= abcLen*abcLen+abcLen { | ||
return fmt.Sprintf("%c%c%c.", 'A'+i/abcLen/abcLen-1, 'A'+(i/abcLen)%abcLen-1, 'A'+i%abcLen) | ||
} | ||
if i >= abcLen { | ||
return fmt.Sprintf("%c%c.", 'A'+i/abcLen-1, 'A'+(i)%abcLen) | ||
} | ||
return fmt.Sprintf("%c.", 'A'+i%abcLen) | ||
} | ||
|
||
// Arabic is the enumeration for arabic numerals listing. | ||
// | ||
// 1. Foo | ||
// 2. Bar | ||
// 3. Baz | ||
// 4. Qux. | ||
func Arabic(i int) string { | ||
return fmt.Sprintf("%d.", i+1) | ||
} | ||
|
||
var ( | ||
roman = []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"} | ||
arabic = []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1} | ||
) | ||
|
||
// Roman is the enumeration for roman numerals listing. | ||
// | ||
// / I. Foo | ||
// / II. Bar | ||
// / III. Baz | ||
// / IV. Qux. | ||
func Roman(i int) string { | ||
var result strings.Builder | ||
|
||
for v, value := range arabic { | ||
for i >= value-1 { | ||
i -= value | ||
result.WriteString(roman[v]) | ||
} | ||
} | ||
result.WriteRune('.') | ||
return result.String() | ||
} | ||
|
||
// Bullet is the enumeration for bullet listing. | ||
// | ||
// • Foo | ||
// • Bar | ||
// • Baz | ||
// • Qux. | ||
func Bullet(_ int) string { | ||
return "•" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
// package list defines an API to build lists. | ||
Check failure on line 1 in list/list.go GitHub Actions / lint
|
||
// | ||
// A list is a enumerated collection of items. Items are rendered vertically | ||
// stacked on top of each other. Like the following: | ||
// | ||
// list.New("A", "B", "C"). | ||
// (list.Bullet). | ||
// String() | ||
// | ||
// • A | ||
// • B | ||
// • C | ||
package list | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/charmbracelet/lipgloss" | ||
) | ||
|
||
// Item is a list item. It allows the list to print it's elements, a list can | ||
// contain nested lists. | ||
type Item interface { | ||
any | List | ||
} | ||
|
||
// List is the representation of a lipgloss List. | ||
// | ||
// It can be printed to display a human-readable list. | ||
// | ||
// fmt.Println(list.New("A", "B", "C")) | ||
// | ||
// • A | ||
// • B | ||
// • C | ||
type List struct { | ||
indent int | ||
separator lipgloss.Style | ||
items []Item | ||
itemStyleFunc StyleFunc | ||
enumerator Enumerator | ||
enumeratorStyleFunc StyleFunc | ||
} | ||
|
||
// StyleFunc defines a list style function that returns the correct style for | ||
// the list element at the given index. | ||
type StyleFunc func(i int) lipgloss.Style | ||
|
||
// New returns a new list given the list items. | ||
// List items may be of any primative type and other Lists. | ||
Check failure on line 51 in list/list.go GitHub Actions / lint-soft
|
||
// | ||
// list.New( | ||
// "Foo", | ||
// "Bar", | ||
// NewBaz{}, | ||
// list.New( | ||
// "Qux", | ||
// "Quux", | ||
// ), | ||
// ) | ||
func New(items ...Item) List { | ||
return List{ | ||
items: items, | ||
separator: lipgloss.NewStyle().SetString("\n"), | ||
enumerator: func(i int) string { | ||
Check failure on line 66 in list/list.go GitHub Actions / lint
|
||
return "•" | ||
}, | ||
enumeratorStyleFunc: func(i int) lipgloss.Style { | ||
Check failure on line 69 in list/list.go GitHub Actions / lint
|
||
return lipgloss.NewStyle().MarginRight(1) | ||
}, | ||
} | ||
} | ||
|
||
func (l List) Item(item Item) List { | ||
Check failure on line 75 in list/list.go GitHub Actions / lint
|
||
l.items = append(l.items, item) | ||
return l | ||
} | ||
|
||
// Separator sets the list separator style, which separates list items. | ||
// The default separator is a newline. | ||
func (l List) Separator(s lipgloss.Style) List { | ||
l.separator = s | ||
return l | ||
} | ||
|
||
// Prefix sets a static enumerator. | ||
// | ||
// list.New(...).Prefix(s) is equivalent to: | ||
// | ||
// list.New(...).Enumerator(func(_ int) string { | ||
// return s | ||
// }) | ||
func (l List) Prefix(s string) List { | ||
l.enumerator = func(i int) string { | ||
Check failure on line 95 in list/list.go GitHub Actions / lint
|
||
return s | ||
} | ||
return l | ||
} | ||
|
||
// Enumerator sets list enumeration function. The function will be called with | ||
// the index of the item in the list. | ||
func (l List) Enumerator(e Enumerator) List { | ||
l.enumerator = e | ||
return l | ||
} | ||
|
||
// EnumeratorStyleFunc sets the style function for the list enumeration. The | ||
// function will be called with the index of the item in the list. | ||
func (l List) EnumeratorStyleFunc(f StyleFunc) List { | ||
l.enumeratorStyleFunc = f | ||
return l | ||
} | ||
|
||
// ItemStyleFunc sets the style function for the list enumeration. The | ||
// function will be called with the index of the item in the list. | ||
func (l List) ItemStyleFunc(f StyleFunc) List { | ||
l.itemStyleFunc = f | ||
return l | ||
} | ||
|
||
// EnumeratorStyle sets the style for the list enumeration. The function will be | ||
// called with the index of the item in the list. | ||
func (l List) EnumeratorStyle(style lipgloss.Style) List { | ||
l.enumeratorStyleFunc = func(_ int) lipgloss.Style { return style } | ||
return l | ||
} | ||
|
||
// ItemStyle sets the style function for the list enumeration. The | ||
// function will be called with the index of the item in the list. | ||
func (l List) ItemStyle(style lipgloss.Style) List { | ||
l.itemStyleFunc = func(_ int) lipgloss.Style { return style } | ||
return l | ||
} | ||
|
||
// At returns the item at index i. | ||
func (l List) At(i int) Item { | ||
return l.items[i] | ||
} | ||
|
||
// String returns a string representation of the list. | ||
func (l List) String() string { | ||
var s strings.Builder | ||
|
||
// find the longest enumerator value of this list. | ||
last := len(l.items) - 1 | ||
var maxLen int | ||
for i := 0; i <= last; i++ { | ||
enum := l.enumeratorStyleFunc(i).Render(l.enumerator(i)) | ||
maxLen = max(lipgloss.Width(enum), maxLen) | ||
} | ||
|
||
for i, item := range l.items { | ||
switch item := item.(type) { | ||
case List: | ||
item.indent = l.indent + 2 | ||
Check failure on line 156 in list/list.go GitHub Actions / lint-soft
|
||
s.WriteString(item.String()) | ||
if i != last { | ||
s.WriteString(l.separator.String()) | ||
} | ||
default: | ||
indent := strings.Repeat(" ", l.indent) | ||
enumerator := l.enumeratorStyleFunc(i). | ||
Width(maxLen - 1). | ||
Align(lipgloss.Right). | ||
Render(l.enumerator(i)) | ||
listItem := lipgloss.JoinHorizontal( | ||
lipgloss.Top, | ||
indent, | ||
enumerator, | ||
fmt.Sprintf("%v", item), | ||
) | ||
s.WriteString(listItem) | ||
if i != last { | ||
s.WriteString(l.separator.String()) | ||
} | ||
} | ||
|
||
} | ||
Check failure on line 179 in list/list.go GitHub Actions / lint
|
||
return s.String() | ||
} | ||
|
||
func max(a, b int) int { | ||
if a > b { | ||
return a | ||
} | ||
return b | ||
} |