From 1fb1fb96029dbcb717c028993a54e617ef46970c Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 5 Jul 2022 16:11:11 -0700 Subject: [PATCH 001/126] chore: drop go-colorful dependency (but it's a great lib) --- color.go | 57 ++++++++++++++++++++++++++++++++++++++------------- color_test.go | 34 ++++++++++++++++++++++++++++++ go.mod | 2 -- 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/color.go b/color.go index 52ec17fc..20dafea7 100644 --- a/color.go +++ b/color.go @@ -1,9 +1,9 @@ package lipgloss import ( + "image/color" "sync" - "github.com/lucasb-eyer/go-colorful" "github.com/muesli/termenv" ) @@ -144,17 +144,11 @@ func (c Color) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. // // This is inline with go-colorful's default behavior. func (c Color) RGBA() (r, g, b, a uint32) { - cf, err := colorful.Hex(c.value()) - if err != nil { - // If we ignore the return behavior and simply return what go-colorful - // give us for the color value we'd be returning exactly this, however - // we're being explicit here for the sake of clarity. - return colorful.Color{}.RGBA() - } + cf := hexToColor(c.value()) return cf.RGBA() } @@ -185,13 +179,48 @@ func (ac AdaptiveColor) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. // // This is inline with go-colorful's default behavior. func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { - cf, err := colorful.Hex(ac.value()) - if err != nil { - return colorful.Color{}.RGBA() - } + cf := hexToColor(ac.value()) return cf.RGBA() } + +// hexToColor translates a hex color string (#RRGGBB or #RGB) into a color.RGB, +// which satisfies the color.Color interface. If an invalid string is passed +// black with 100% opacity will be returned: or, in hex format, 0x000000FF. +func hexToColor(hex string) (c color.RGBA) { + c.A = 0xFF + + if hex == "" || hex[0] != '#' { + return c + } + + switch len(hex) { + case 7: // #RRGGBB + c.R = hexToByte(hex[1])<<4 + hexToByte(hex[2]) + c.G = hexToByte(hex[3])<<4 + hexToByte(hex[4]) + c.B = hexToByte(hex[5])<<4 + hexToByte(hex[6]) + case 4: // #RGB + const offset = 0x11 + c.R = hexToByte(hex[1]) * offset + c.G = hexToByte(hex[2]) * offset + c.B = hexToByte(hex[3]) * offset + } + + return c +} + +func hexToByte(b byte) byte { + switch { + case b >= '0' && b <= '9': + return b - '0' + case b >= 'a' && b <= 'f': + return b - 'a' + 10 + case b >= 'A' && b <= 'F': + return b - 'A' + 10 + } + // Invalid, but just return 0. + return 0 +} diff --git a/color_test.go b/color_test.go index ff522e29..83531e0e 100644 --- a/color_test.go +++ b/color_test.go @@ -51,3 +51,37 @@ func TestSetColorProfile(t *testing.T) { } } } + +func TestHexToColor(t *testing.T) { + t.Parallel() + + tt := []struct { + input string + expected uint + }{ + { + "#FF0000", + 0xFF0000, + }, + { + "#00F", + 0x0000FF, + }, + { + "#6B50FF", + 0x6B50FF, + }, + { + "invalid color", + 0x0, + }, + } + + for i, tc := range tt { + h := hexToColor(tc.input) + o := uint(h.R)<<16 + uint(h.G)<<8 + uint(h.B) + if o != tc.expected { + t.Errorf("expected %X, got %X (test #%d)", o, tc.expected, i+1) + } + } +} diff --git a/go.mod b/go.mod index c511c036..9e46b24a 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,7 @@ module github.com/charmbracelet/lipgloss go 1.15 require ( - github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mattn/go-runewidth v0.0.13 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 - golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c ) From 81744becf3e53f7408b2017689498c3a9348b0c1 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 5 Jul 2022 16:17:23 -0700 Subject: [PATCH 002/126] chore(lint): remove magic numbers --- color.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/color.go b/color.go index 20dafea7..112c4ca0 100644 --- a/color.go +++ b/color.go @@ -197,12 +197,18 @@ func hexToColor(hex string) (c color.RGBA) { return c } + const ( + fullFormat = 7 // #RRGGBB + shortFormat = 4 // #RGB + ) + switch len(hex) { - case 7: // #RRGGBB - c.R = hexToByte(hex[1])<<4 + hexToByte(hex[2]) - c.G = hexToByte(hex[3])<<4 + hexToByte(hex[4]) - c.B = hexToByte(hex[5])<<4 + hexToByte(hex[6]) - case 4: // #RGB + case fullFormat: + const offset = 4 + c.R = hexToByte(hex[1])<= '0' && b <= '9': return b - '0' case b >= 'a' && b <= 'f': - return b - 'a' + 10 + return b - 'a' + offset case b >= 'A' && b <= 'F': - return b - 'A' + 10 + return b - 'A' + offset } // Invalid, but just return 0. return 0 From f651363297fc28c85a95e48b68f6ffe0c1bf44ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Marku=C5=A1i=C4=87?= Date: Wed, 24 Aug 2022 17:45:34 +0200 Subject: [PATCH 003/126] Fix height doc (width -> height) --- set.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/set.go b/set.go index 2e7dfc8c..3b3b03e1 100644 --- a/set.go +++ b/set.go @@ -97,7 +97,7 @@ func (s Style) Width(i int) Style { return s } -// Height sets the width of the block before applying margins. If the height of +// Height sets the height of the block before applying margins. If the height of // the text block is less than this value after applying padding (or not), the // block will be set to this height. func (s Style) Height(i int) Style { From 2cb1d4a5c1814ab5ef2283d79e19ce82ce123c59 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 24 Aug 2022 09:05:18 -0700 Subject: [PATCH 004/126] docs(lint): update example code in GoDocs per new style standard This revision was made automatically via 'golangci-lint run --fix' --- color.go | 11 ++++------- join.go | 26 ++++++++++++-------------- set.go | 35 +++++++++++++++-------------------- 3 files changed, 31 insertions(+), 41 deletions(-) diff --git a/color.go b/color.go index 112c4ca0..d5724f84 100644 --- a/color.go +++ b/color.go @@ -103,8 +103,7 @@ type TerminalColor interface { // // Example usage: // -// var style = someStyle.Copy().Background(lipgloss.NoColor{}) -// +// var style = someStyle.Copy().Background(lipgloss.NoColor{}) type NoColor struct{} func (n NoColor) value() string { @@ -128,9 +127,8 @@ var noColor = NoColor{} // Color specifies a color by hex or ANSI value. For example: // -// ansiColor := lipgloss.Color("21") -// hexColor := lipgloss.Color("#0000ff") -// +// ansiColor := lipgloss.Color("21") +// hexColor := lipgloss.Color("#0000ff") type Color string func (c Color) value() string { @@ -158,8 +156,7 @@ func (c Color) RGBA() (r, g, b, a uint32) { // // Example usage: // -// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"} -// +// color := lipgloss.AdaptiveColor{Light: "#0000ff", Dark: "#000099"} type AdaptiveColor struct { Light string Dark string diff --git a/join.go b/join.go index 69ffdc9d..cc16600a 100644 --- a/join.go +++ b/join.go @@ -17,15 +17,14 @@ import ( // // Example: // -// blockB := "...\n...\n..." -// blockA := "...\n...\n...\n...\n..." +// blockB := "...\n...\n..." +// blockA := "...\n...\n...\n...\n..." // -// // Join 20% from the top -// str := lipgloss.JoinHorizontal(0.2, blockA, blockB) -// -// // Join on the top edge -// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB) +// // Join 20% from the top +// str := lipgloss.JoinHorizontal(0.2, blockA, blockB) // +// // Join on the top edge +// str := lipgloss.JoinHorizontal(lipgloss.Top, blockA, blockB) func JoinHorizontal(pos Position, strs ...string) string { if len(strs) == 0 { return "" @@ -106,15 +105,14 @@ func JoinHorizontal(pos Position, strs ...string) string { // // Example: // -// blockB := "...\n...\n..." -// blockA := "...\n...\n...\n...\n..." -// -// // Join 20% from the top -// str := lipgloss.JoinVertical(0.2, blockA, blockB) +// blockB := "...\n...\n..." +// blockA := "...\n...\n...\n...\n..." // -// // Join on the right edge -// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB) +// // Join 20% from the top +// str := lipgloss.JoinVertical(0.2, blockA, blockB) // +// // Join on the right edge +// str := lipgloss.JoinVertical(lipgloss.Right, blockA, blockB) func JoinVertical(pos Position, strs ...string) string { if len(strs) == 0 { return "" diff --git a/set.go b/set.go index 3b3b03e1..28d1b1cd 100644 --- a/set.go +++ b/set.go @@ -73,12 +73,11 @@ func (s Style) Faint(v bool) Style { // Foreground sets a foreground color. // -// // Sets the foreground to blue -// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff")) -// -// // Removes the foreground color -// s.Foreground(lipgloss.NoColor) +// // Sets the foreground to blue +// s := lipgloss.NewStyle().Foreground(lipgloss.Color("#0000ff")) // +// // Removes the foreground color +// s.Foreground(lipgloss.NoColor) func (s Style) Foreground(c TerminalColor) Style { s.set(foregroundKey, c) return s @@ -248,12 +247,11 @@ func (s Style) MarginBackground(c TerminalColor) Style { // // Examples: // -// // Applies borders to the top and bottom only -// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false) -// -// // Applies rounded borders to the right and bottom only -// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false) +// // Applies borders to the top and bottom only +// lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false) // +// // Applies rounded borders to the right and bottom only +// lipgloss.NewStyle().Border(lipgloss.RoundedBorder(), false, true, true, false) func (s Style) Border(b Border, sides ...bool) Style { s.set(borderStyleKey, b) @@ -285,8 +283,7 @@ func (s Style) Border(b Border, sides ...bool) Style { // // Example: // -// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder()) -// +// lipgloss.NewStyle().BorderStyle(lipgloss.ThickBorder()) func (s Style) BorderStyle(b Border) Style { s.set(borderStyleKey, b) return s @@ -445,10 +442,9 @@ func (s Style) BorderLeftBackground(c TerminalColor) Style { // // Example: // -// var userInput string = "..." -// var userStyle = text.Style{ /* ... */ } -// fmt.Println(userStyle.Inline(true).Render(userInput)) -// +// var userInput string = "..." +// var userStyle = text.Style{ /* ... */ } +// fmt.Println(userStyle.Inline(true).Render(userInput)) func (s Style) Inline(v bool) Style { o := s.Copy() o.set(inlineKey, v) @@ -464,10 +460,9 @@ func (s Style) Inline(v bool) Style { // // Example: // -// var userInput string = "..." -// var userStyle = text.Style{ /* ... */ } -// fmt.Println(userStyle.MaxWidth(16).Render(userInput)) -// +// var userInput string = "..." +// var userStyle = text.Style{ /* ... */ } +// fmt.Println(userStyle.MaxWidth(16).Render(userInput)) func (s Style) MaxWidth(n int) Style { o := s.Copy() o.set(maxWidthKey, n) From 0ee74a5a90f64f7ef3bdd30423d2cd11a6ca16e5 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 30 Aug 2022 13:38:15 -0700 Subject: [PATCH 005/126] feat: non-interpolated colors, aka CompleteColor (#100) * feat: add color types for specifying colors on a per-profile basis * chore: remove go-colourful references * fix: lint (#101) Co-authored-by: Ayman Bagabas --- color.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/color.go b/color.go index d5724f84..0e7de890 100644 --- a/color.go +++ b/color.go @@ -116,7 +116,7 @@ func (n NoColor) color() termenv.Color { // RGBA returns the RGBA value of this color. Because we have to return // something, despite this color being the absence of color, we're returning -// the same value that go-colorful returns on error: +// black with 100% opacity. // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. func (n NoColor) RGBA() (r, g, b, a uint32) { @@ -143,11 +143,8 @@ func (c Color) color() termenv.Color { // interface. Note that on error we return black with 100% opacity, or: // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. -// -// This is inline with go-colorful's default behavior. func (c Color) RGBA() (r, g, b, a uint32) { - cf := hexToColor(c.value()) - return cf.RGBA() + return hexToColor(c.value()).RGBA() } // AdaptiveColor provides color options for light and dark backgrounds. The @@ -177,13 +174,71 @@ func (ac AdaptiveColor) color() termenv.Color { // interface. Note that on error we return black with 100% opacity, or: // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. -// -// This is inline with go-colorful's default behavior. func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { cf := hexToColor(ac.value()) return cf.RGBA() } +// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color +// profiles. Automatic color degredation will not be performed. +type CompleteColor struct { + TrueColor string + ANSI256 string + ANSI string +} + +func (c CompleteColor) value() string { + switch ColorProfile() { + case termenv.TrueColor: + return c.TrueColor + case termenv.ANSI256: + return c.ANSI256 + case termenv.ANSI: + return c.ANSI + default: + return "" + } +} + +func (c CompleteColor) color() termenv.Color { + return colorProfile.Color(c.value()) +} + +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. Note that on error we return black with 100% opacity, or: +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +func (c CompleteColor) RGBA() (r, g, b, a uint32) { + return hexToColor(c.value()).RGBA() +} + +// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color +// profiles, with separate options for light and dark backgrounds. Automatic +// color degredation will not be performed. +type CompleteAdaptiveColor struct { + Light CompleteColor + Dark CompleteColor +} + +func (cac CompleteAdaptiveColor) value() string { + if HasDarkBackground() { + return cac.Dark.value() + } + return cac.Light.value() +} + +func (cac CompleteAdaptiveColor) color() termenv.Color { + return ColorProfile().Color(cac.value()) +} + +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. Note that on error we return black with 100% opacity, or: +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) { + return hexToColor(cac.value()).RGBA() +} + // hexToColor translates a hex color string (#RRGGBB or #RGB) into a color.RGB, // which satisfies the color.Color interface. If an invalid string is passed // black with 100% opacity will be returned: or, in hex format, 0x000000FF. From 9852bb301757ff1c8af8616c9400e486b67ba33f Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Tue, 30 Aug 2022 16:59:06 -0400 Subject: [PATCH 006/126] feat: `VerticalAlign` Add VerticalAlign, UnsetVerticalAlign, etc... --- align.go | 25 ++++++++++++++++++++++++- align_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ get.go | 26 +++++++++++++++++++++++--- set.go | 16 ++++++++++++++-- style.go | 17 ++++++++--------- unset.go | 17 +++++++++++++++-- 6 files changed, 125 insertions(+), 17 deletions(-) create mode 100644 align_test.go diff --git a/align.go b/align.go index 03e7889e..c3997038 100644 --- a/align.go +++ b/align.go @@ -10,7 +10,7 @@ import ( // Perform text alignment. If the string is multi-lined, we also make all lines // the same width by padding them with spaces. If a termenv style is passed, // use that to style the spaces added. -func alignText(str string, pos Position, width int, style *termenv.Style) string { +func alignTextHorizontal(str string, pos Position, width int, style *termenv.Style) string { lines, widestLine := getLines(str) var b strings.Builder @@ -57,3 +57,26 @@ func alignText(str string, pos Position, width int, style *termenv.Style) string return b.String() } + +func alignTextVertical(str string, pos Position, height int, _ *termenv.Style) string { + strHeight := strings.Count(str, "\n") + 1 + if height < strHeight { + return str + } + + switch pos { + case Top: + return str + strings.Repeat("\n", height-strHeight) + case Center: + var topPadding, bottomPadding = (height - strHeight) / 2, (height - strHeight) / 2 + if strHeight+topPadding+bottomPadding > height { + topPadding-- + } else if strHeight+topPadding+bottomPadding < height { + bottomPadding++ + } + return strings.Repeat("\n", topPadding) + str + strings.Repeat("\n", bottomPadding) + case Bottom: + return strings.Repeat("\n", height-strHeight) + str + } + return str +} diff --git a/align_test.go b/align_test.go new file mode 100644 index 00000000..dd5addb4 --- /dev/null +++ b/align_test.go @@ -0,0 +1,41 @@ +package lipgloss + +import "testing" + +func TestAlignTextVertical(t *testing.T) { + tests := []struct { + str string + pos Position + height int + want string + }{ + {str: "Foo", pos: Top, height: 2, want: "Foo\n"}, + {str: "Foo", pos: Center, height: 5, want: "\n\nFoo\n\n"}, + {str: "Foo", pos: Bottom, height: 5, want: "\n\n\n\nFoo"}, + + {str: "Foo\nBar", pos: Bottom, height: 5, want: "\n\n\nFoo\nBar"}, + {str: "Foo\nBar", pos: Center, height: 5, want: "\nFoo\nBar\n\n"}, + {str: "Foo\nBar", pos: Top, height: 5, want: "Foo\nBar\n\n\n"}, + + {str: "Foo\nBar\nBaz", pos: Bottom, height: 5, want: "\n\nFoo\nBar\nBaz"}, + {str: "Foo\nBar\nBaz", pos: Center, height: 5, want: "\nFoo\nBar\nBaz\n"}, + + {str: "Foo\nBar\nBaz", pos: Bottom, height: 3, want: "Foo\nBar\nBaz"}, + {str: "Foo\nBar\nBaz", pos: Center, height: 3, want: "Foo\nBar\nBaz"}, + {str: "Foo\nBar\nBaz", pos: Top, height: 3, want: "Foo\nBar\nBaz"}, + + {str: "Foo\n\n\n\nBar", pos: Bottom, height: 5, want: "Foo\n\n\n\nBar"}, + {str: "Foo\n\n\n\nBar", pos: Center, height: 5, want: "Foo\n\n\n\nBar"}, + {str: "Foo\n\n\n\nBar", pos: Top, height: 5, want: "Foo\n\n\n\nBar"}, + + {str: "Foo\nBar\nBaz", pos: Center, height: 9, want: "\n\n\nFoo\nBar\nBaz\n\n\n"}, + {str: "Foo\nBar\nBaz", pos: Center, height: 10, want: "\n\n\nFoo\nBar\nBaz\n\n\n\n"}, + } + + for _, test := range tests { + got := alignTextVertical(test.str, test.pos, test.height, nil) + if got != test.want { + t.Errorf("alignTextVertical(%q, %v, %d) = %q, want %q", test.str, test.pos, test.height, got, test.want) + } + } +} diff --git a/get.go b/get.go index 87906227..f01b8f15 100644 --- a/get.go +++ b/get.go @@ -71,16 +71,36 @@ func (s Style) GetHeight() int { return s.getAsInt(heightKey) } -// GetAlign returns the style's implicit alignment setting. If no alignment is -// set Position.AlignLeft is returned. +// GetAlign returns the style's implicit horizontal alignment setting. +// If no alignment is set Position.AlignLeft is returned. func (s Style) GetAlign() Position { - v := s.getAsPosition(alignKey) + v := s.getAsPosition(alignHorizontalKey) if v == Position(0) { return Left } return v } +// GetAlignHorizontal returns the style's implicit horizontal alignment setting. +// If no alignment is set Position.AlignLeft is returned. +func (s Style) GetAlignHorizontal() Position { + v := s.getAsPosition(alignHorizontalKey) + if v == Position(0) { + return Left + } + return v +} + +// GetAlignVertical returns the style's implicit vertical alignment setting. +// If no alignment is set Position.AlignTop is returned. +func (s Style) GetAlignVertical() Position { + v := s.getAsPosition(alignVerticalKey) + if v == Position(0) { + return Top + } + return v +} + // GetPadding returns the style's top, right, bottom, and left padding values, // in that order. 0 is returned for unset values. func (s Style) GetPadding() (top, right, bottom, left int) { diff --git a/set.go b/set.go index 28d1b1cd..e96399f5 100644 --- a/set.go +++ b/set.go @@ -104,9 +104,21 @@ func (s Style) Height(i int) Style { return s } -// Align sets a text alignment rule. +// Align sets a horizontal text alignment rule. func (s Style) Align(p Position) Style { - s.set(alignKey, p) + s.set(alignHorizontalKey, p) + return s +} + +// HorizontalAlign sets a horizontal text alignment rule. +func (s Style) AlignHorizontal(p Position) Style { + s.set(alignHorizontalKey, p) + return s +} + +// VerticalAlign sets a text alignment rule. +func (s Style) AlignVertical(p Position) Style { + s.set(alignVerticalKey, p) return s } diff --git a/style.go b/style.go index 576ace4a..fb89d06c 100644 --- a/style.go +++ b/style.go @@ -26,7 +26,8 @@ const ( backgroundKey widthKey heightKey - alignKey + alignHorizontalKey + alignVerticalKey // Padding. paddingTopKey @@ -169,9 +170,10 @@ func (s Style) Render(str string) string { fg = s.getAsColor(foregroundKey) bg = s.getAsColor(backgroundKey) - width = s.getAsInt(widthKey) - height = s.getAsInt(heightKey) - align = s.getAsPosition(alignKey) + width = s.getAsInt(widthKey) + height = s.getAsInt(heightKey) + horizontalAlign = s.getAsPosition(alignHorizontalKey) + verticalAlign = s.getAsPosition(alignVerticalKey) topPadding = s.getAsInt(paddingTopKey) rightPadding = s.getAsInt(paddingRightKey) @@ -327,10 +329,7 @@ func (s Style) Render(str string) string { // Height if height > 0 { - h := strings.Count(str, "\n") + 1 - if height > h { - str += strings.Repeat("\n", height-h) - } + str = alignTextVertical(str, verticalAlign, height, nil) } // Set alignment. This will also pad short lines with spaces so that all @@ -344,7 +343,7 @@ func (s Style) Render(str string) string { if colorWhitespace || styleWhitespace { st = &teWhitespace } - str = alignText(str, align, width, st) + str = alignTextHorizontal(str, horizontalAlign, width, st) } } diff --git a/unset.go b/unset.go index 0a03720d..25f3ac92 100644 --- a/unset.go +++ b/unset.go @@ -66,9 +66,22 @@ func (s Style) UnsetHeight() Style { return s } -// UnsetAlign removes the text alignment style rule, if set. +// UnsetAlign removes the horizontal and vertical text alignment style rule, if set. func (s Style) UnsetAlign() Style { - delete(s.rules, alignKey) + delete(s.rules, alignHorizontalKey) + delete(s.rules, alignVerticalKey) + return s +} + +// UnsetAlignHorizontal removes the horizontal text alignment style rule, if set. +func (s Style) UnsetAlignHorizontal() Style { + delete(s.rules, alignHorizontalKey) + return s +} + +// UnsetAlignHorizontal removes the vertical text alignment style rule, if set. +func (s Style) UnsetAlignVertical() Style { + delete(s.rules, alignVerticalKey) return s } From 0ce55509014b0cc476e197f22aa4d43add081a57 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Tue, 30 Aug 2022 17:14:53 -0400 Subject: [PATCH 007/126] feat: Align takes multiple arguments for setting horizontal + vertical alignment --- set.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/set.go b/set.go index e96399f5..02884543 100644 --- a/set.go +++ b/set.go @@ -104,9 +104,19 @@ func (s Style) Height(i int) Style { return s } -// Align sets a horizontal text alignment rule. -func (s Style) Align(p Position) Style { - s.set(alignHorizontalKey, p) +// Align is a shorthand method for setting horizontal and vertical alignment. +// +// With one argument, the position value is applied to the horizontal alignment. +// +// With two arguments, the value is applied to the vertical and horizontal +// alignments, in that order. +func (s Style) Align(p ...Position) Style { + if len(p) > 0 { + s.set(alignHorizontalKey, p[0]) + } + if len(p) > 1 { + s.set(alignVerticalKey, p[1]) + } return s } From 09292df479de8980c9c73f0d81fd69e89cab197d Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Sep 2022 03:58:37 +0200 Subject: [PATCH 008/126] test: fix argument order --- color_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/color_test.go b/color_test.go index 83531e0e..bbe07f14 100644 --- a/color_test.go +++ b/color_test.go @@ -81,7 +81,7 @@ func TestHexToColor(t *testing.T) { h := hexToColor(tc.input) o := uint(h.R)<<16 + uint(h.G)<<8 + uint(h.B) if o != tc.expected { - t.Errorf("expected %X, got %X (test #%d)", o, tc.expected, i+1) + t.Errorf("expected %X, got %X (test #%d)", tc.expected, o, i+1) } } } From cabfd38a134f490064e5be83ea8f00f9a7375b1b Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Sep 2022 03:56:27 +0200 Subject: [PATCH 009/126] test: add RGBA tests --- color_test.go | 144 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 2 deletions(-) diff --git a/color_test.go b/color_test.go index bbe07f14..cc0ab55c 100644 --- a/color_test.go +++ b/color_test.go @@ -7,8 +7,6 @@ import ( ) func TestSetColorProfile(t *testing.T) { - t.Parallel() - tt := []struct { profile termenv.Profile input string @@ -85,3 +83,145 @@ func TestHexToColor(t *testing.T) { } } } + +func TestRGBA(t *testing.T) { + tt := []struct { + profile termenv.Profile + darkBg bool + input TerminalColor + expected uint + }{ + // lipgloss.Color + { + termenv.TrueColor, + true, + Color("#FF0000"), + 0xFF0000, + }, + { + termenv.TrueColor, + true, + Color("9"), + 0xFF0000, + }, + { + termenv.TrueColor, + true, + Color("21"), + 0x0000FF, + }, + // lipgloss.AdaptiveColor + { + termenv.TrueColor, + true, + AdaptiveColor{Dark: "#FF0000", Light: "#0000FF"}, + 0xFF0000, + }, + { + termenv.TrueColor, + false, + AdaptiveColor{Dark: "#FF0000", Light: "#0000FF"}, + 0x0000FF, + }, + { + termenv.TrueColor, + true, + AdaptiveColor{Dark: "9", Light: "21"}, + 0xFF0000, + }, + { + termenv.TrueColor, + false, + AdaptiveColor{Dark: "9", Light: "21"}, + 0x0000FF, + }, + // lipgloss.CompleteColor + { + termenv.TrueColor, + true, + CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + 0xFF0000, + }, + { + termenv.ANSI256, + true, + CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + 0xFFFFFF, + }, + { + termenv.ANSI, + true, + CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + 0x0000FF, + }, + // lipgloss.CompleteAdaptiveColor + // dark + { + termenv.TrueColor, + true, + CompleteAdaptiveColor{ + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + Light: CompleteColor{TrueColor: "#0000FF", ANSI256: "231", ANSI: "12"}, + }, + 0xFF0000, + }, + { + termenv.ANSI256, + true, + CompleteAdaptiveColor{ + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "21", ANSI: "12"}, + }, + 0xFFFFFF, + }, + { + termenv.ANSI, + true, + CompleteAdaptiveColor{ + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "9"}, + }, + 0x0000FF, + }, + // light + { + termenv.TrueColor, + false, + CompleteAdaptiveColor{ + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + Light: CompleteColor{TrueColor: "#0000FF", ANSI256: "231", ANSI: "12"}, + }, + 0x0000FF, + }, + { + termenv.ANSI256, + false, + CompleteAdaptiveColor{ + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "21", ANSI: "12"}, + }, + 0x0000FF, + }, + { + termenv.ANSI, + false, + CompleteAdaptiveColor{ + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, + Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "9"}, + }, + 0xFF0000, + }, + } + + for i, tc := range tt { + SetColorProfile(tc.profile) + SetHasDarkBackground(tc.darkBg) + + r, g, b, _ := tc.input.RGBA() + o := uint(r/256)<<16 + uint(g/256)<<8 + uint(b/256) + + if o != tc.expected { + t.Errorf("expected %X, got %X (test #%d)", tc.expected, o, i+1) + } + } +} From 651d1265e74c1039a1adc7d1db576db73b64037f Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Sep 2022 04:40:12 +0200 Subject: [PATCH 010/126] fix: RGBA implementations for non-hex color values --- color.go | 55 ++++----------------------------------------------- color_test.go | 46 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 51 deletions(-) diff --git a/color.go b/color.go index 0e7de890..16409e16 100644 --- a/color.go +++ b/color.go @@ -1,7 +1,6 @@ package lipgloss import ( - "image/color" "sync" "github.com/muesli/termenv" @@ -144,7 +143,7 @@ func (c Color) color() termenv.Color { // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. func (c Color) RGBA() (r, g, b, a uint32) { - return hexToColor(c.value()).RGBA() + return termenv.ConvertToRGB(c.color()).RGBA() } // AdaptiveColor provides color options for light and dark backgrounds. The @@ -175,8 +174,7 @@ func (ac AdaptiveColor) color() termenv.Color { // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { - cf := hexToColor(ac.value()) - return cf.RGBA() + return termenv.ConvertToRGB(ac.color()).RGBA() } // CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color @@ -209,7 +207,7 @@ func (c CompleteColor) color() termenv.Color { // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF func (c CompleteColor) RGBA() (r, g, b, a uint32) { - return hexToColor(c.value()).RGBA() + return termenv.ConvertToRGB(c.color()).RGBA() } // CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color @@ -236,50 +234,5 @@ func (cac CompleteAdaptiveColor) color() termenv.Color { // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) { - return hexToColor(cac.value()).RGBA() -} - -// hexToColor translates a hex color string (#RRGGBB or #RGB) into a color.RGB, -// which satisfies the color.Color interface. If an invalid string is passed -// black with 100% opacity will be returned: or, in hex format, 0x000000FF. -func hexToColor(hex string) (c color.RGBA) { - c.A = 0xFF - - if hex == "" || hex[0] != '#' { - return c - } - - const ( - fullFormat = 7 // #RRGGBB - shortFormat = 4 // #RGB - ) - - switch len(hex) { - case fullFormat: - const offset = 4 - c.R = hexToByte(hex[1])<= '0' && b <= '9': - return b - '0' - case b >= 'a' && b <= 'f': - return b - 'a' + offset - case b >= 'A' && b <= 'F': - return b - 'A' + offset - } - // Invalid, but just return 0. - return 0 + return termenv.ConvertToRGB(cac.color()).RGBA() } diff --git a/color_test.go b/color_test.go index cc0ab55c..c52dbb23 100644 --- a/color_test.go +++ b/color_test.go @@ -1,6 +1,7 @@ package lipgloss import ( + "image/color" "testing" "github.com/muesli/termenv" @@ -225,3 +226,48 @@ func TestRGBA(t *testing.T) { } } } + +// hexToColor translates a hex color string (#RRGGBB or #RGB) into a color.RGB, +// which satisfies the color.Color interface. If an invalid string is passed +// black with 100% opacity will be returned: or, in hex format, 0x000000FF. +func hexToColor(hex string) (c color.RGBA) { + c.A = 0xFF + + if hex == "" || hex[0] != '#' { + return c + } + + const ( + fullFormat = 7 // #RRGGBB + shortFormat = 4 // #RGB + ) + + switch len(hex) { + case fullFormat: + const offset = 4 + c.R = hexToByte(hex[1])<= '0' && b <= '9': + return b - '0' + case b >= 'a' && b <= 'f': + return b - 'a' + offset + case b >= 'A' && b <= 'F': + return b - 'A' + offset + } + // Invalid, but just return 0. + return 0 +} From 0b889bb8b642bb612ac34111099d1f46b89cd4e0 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Sep 2022 04:59:08 +0200 Subject: [PATCH 011/126] docs: fix RGBA godocs --- color.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/color.go b/color.go index 16409e16..d70dfaf3 100644 --- a/color.go +++ b/color.go @@ -141,7 +141,7 @@ func (c Color) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. func (c Color) RGBA() (r, g, b, a uint32) { return termenv.ConvertToRGB(c.color()).RGBA() } @@ -172,7 +172,7 @@ func (ac AdaptiveColor) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFF. +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { return termenv.ConvertToRGB(ac.color()).RGBA() } @@ -205,7 +205,7 @@ func (c CompleteColor) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. func (c CompleteColor) RGBA() (r, g, b, a uint32) { return termenv.ConvertToRGB(c.color()).RGBA() } @@ -232,7 +232,7 @@ func (cac CompleteAdaptiveColor) color() termenv.Color { // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) { return termenv.ConvertToRGB(cac.color()).RGBA() } From 681d47362280cc8384ee6421111b77f89fe6dca7 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 7 Apr 2022 04:05:58 +0200 Subject: [PATCH 012/126] test: add Style tests --- go.mod | 1 + go.sum | 11 +++ style_test.go | 241 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 253 insertions(+) diff --git a/go.mod b/go.mod index 9e46b24a..6a2966b6 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/mattn/go-runewidth v0.0.13 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 + github.com/stretchr/testify v1.7.1 ) diff --git a/go.sum b/go.sum index ba0a481e..9e2e8e66 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -9,8 +11,17 @@ github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9Fn github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/style_test.go b/style_test.go index c1ee043d..825fa7aa 100644 --- a/style_test.go +++ b/style_test.go @@ -2,8 +2,249 @@ package lipgloss import ( "testing" + + "github.com/muesli/termenv" + "github.com/stretchr/testify/require" ) +func TestStyleRender(t *testing.T) { + t.Parallel() + + tt := []struct { + style Style + expected string + }{ + { + NewStyle().Foreground(Color("#5A56E0")), + "\x1b[38;2;89;86;224mhello\x1b[0m", + }, + { + NewStyle().Bold(true), + "\x1b[1mhello\x1b[0m", + }, + { + NewStyle().Italic(true), + "\x1b[3mhello\x1b[0m", + }, + { + NewStyle().Underline(true), + "\x1b[4;4mh\x1b[0m\x1b[4;4me\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4mo\x1b[0m", + }, + { + NewStyle().Blink(true), + "\x1b[5mhello\x1b[0m", + }, + { + NewStyle().Faint(true), + "\x1b[2mhello\x1b[0m", + }, + } + + SetColorProfile(termenv.TrueColor) + for i, tc := range tt { + res := tc.style.Render("hello") + if res != tc.expected { + t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", + i, tc.expected, formatEscapes(tc.expected), + res, formatEscapes(res)) + } + } +} + +func TestValueCopy(t *testing.T) { + t.Parallel() + + s := NewStyle(). + Bold(true) + + i := s + i.Bold(false) + + require.Equal(t, s.GetBold(), i.GetBold()) +} + +func TestStyleInherit(t *testing.T) { + t.Parallel() + + s := NewStyle(). + Bold(true). + Italic(true). + Underline(true). + Strikethrough(true). + Blink(true). + Faint(true). + Foreground(Color("#ffffff")). + Background(Color("#111111")). + Margin(1, 1, 1, 1). + Padding(1, 1, 1, 1) + + i := NewStyle().Inherit(s) + + require.Equal(t, s.GetBold(), i.GetBold()) + require.Equal(t, s.GetItalic(), i.GetItalic()) + require.Equal(t, s.GetUnderline(), i.GetUnderline()) + require.Equal(t, s.GetStrikethrough(), i.GetStrikethrough()) + require.Equal(t, s.GetBlink(), i.GetBlink()) + require.Equal(t, s.GetFaint(), i.GetFaint()) + require.Equal(t, s.GetForeground(), i.GetForeground()) + require.Equal(t, s.GetBackground(), i.GetBackground()) + + require.NotEqual(t, s.GetMarginLeft(), i.GetMarginLeft()) + require.NotEqual(t, s.GetMarginRight(), i.GetMarginRight()) + require.NotEqual(t, s.GetMarginTop(), i.GetMarginTop()) + require.NotEqual(t, s.GetMarginBottom(), i.GetMarginBottom()) + require.NotEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft()) + require.NotEqual(t, s.GetPaddingRight(), i.GetPaddingRight()) + require.NotEqual(t, s.GetPaddingTop(), i.GetPaddingTop()) + require.NotEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom()) +} + +func TestStyleCopy(t *testing.T) { + t.Parallel() + + s := NewStyle(). + Bold(true). + Italic(true). + Underline(true). + Strikethrough(true). + Blink(true). + Faint(true). + Foreground(Color("#ffffff")). + Background(Color("#111111")). + Margin(1, 1, 1, 1). + Padding(1, 1, 1, 1) + + i := s.Copy() + + require.Equal(t, s.GetBold(), i.GetBold()) + require.Equal(t, s.GetItalic(), i.GetItalic()) + require.Equal(t, s.GetUnderline(), i.GetUnderline()) + require.Equal(t, s.GetStrikethrough(), i.GetStrikethrough()) + require.Equal(t, s.GetBlink(), i.GetBlink()) + require.Equal(t, s.GetFaint(), i.GetFaint()) + require.Equal(t, s.GetForeground(), i.GetForeground()) + require.Equal(t, s.GetBackground(), i.GetBackground()) + + require.Equal(t, s.GetMarginLeft(), i.GetMarginLeft()) + require.Equal(t, s.GetMarginRight(), i.GetMarginRight()) + require.Equal(t, s.GetMarginTop(), i.GetMarginTop()) + require.Equal(t, s.GetMarginBottom(), i.GetMarginBottom()) + require.Equal(t, s.GetPaddingLeft(), i.GetPaddingLeft()) + require.Equal(t, s.GetPaddingRight(), i.GetPaddingRight()) + require.Equal(t, s.GetPaddingTop(), i.GetPaddingTop()) + require.Equal(t, s.GetPaddingBottom(), i.GetPaddingBottom()) +} + +func TestStyleUnset(t *testing.T) { + t.Parallel() + + s := NewStyle().Bold(true) + require.True(t, s.GetBold()) + s.UnsetBold() + require.False(t, s.GetBold()) + + s = NewStyle().Italic(true) + require.True(t, s.GetItalic()) + s.UnsetItalic() + require.False(t, s.GetItalic()) + + s = NewStyle().Underline(true) + require.True(t, s.GetUnderline()) + s.UnsetUnderline() + require.False(t, s.GetUnderline()) + + s = NewStyle().Strikethrough(true) + require.True(t, s.GetStrikethrough()) + s.UnsetStrikethrough() + require.False(t, s.GetStrikethrough()) + + s = NewStyle().Reverse(true) + require.True(t, s.GetReverse()) + s.UnsetReverse() + require.False(t, s.GetReverse()) + + s = NewStyle().Blink(true) + require.True(t, s.GetBlink()) + s.UnsetBlink() + require.False(t, s.GetBlink()) + + s = NewStyle().Faint(true) + require.True(t, s.GetFaint()) + s.UnsetFaint() + require.False(t, s.GetFaint()) + + s = NewStyle().Inline(true) + require.True(t, s.GetInline()) + s.UnsetInline() + require.False(t, s.GetInline()) + + // colors + col := Color("#ffffff") + s = NewStyle().Foreground(col) + require.Equal(t, col, s.GetForeground()) + s.UnsetForeground() + require.NotEqual(t, col, s.GetForeground()) + + s = NewStyle().Background(col) + require.Equal(t, col, s.GetBackground()) + s.UnsetBackground() + require.NotEqual(t, col, s.GetBackground()) + + // margins + s = NewStyle().Margin(1, 2, 3, 4) + require.Equal(t, 1, s.GetMarginTop()) + s.UnsetMarginTop() + require.Equal(t, 0, s.GetMarginTop()) + + require.Equal(t, 2, s.GetMarginRight()) + s.UnsetMarginRight() + require.Equal(t, 0, s.GetMarginRight()) + + require.Equal(t, 3, s.GetMarginBottom()) + s.UnsetMarginBottom() + require.Equal(t, 0, s.GetMarginBottom()) + + require.Equal(t, 4, s.GetMarginLeft()) + s.UnsetMarginLeft() + require.Equal(t, 0, s.GetMarginLeft()) + + // padding + s = NewStyle().Padding(1, 2, 3, 4) + require.Equal(t, 1, s.GetPaddingTop()) + s.UnsetPaddingTop() + require.Equal(t, 0, s.GetPaddingTop()) + + require.Equal(t, 2, s.GetPaddingRight()) + s.UnsetPaddingRight() + require.Equal(t, 0, s.GetPaddingRight()) + + require.Equal(t, 3, s.GetPaddingBottom()) + s.UnsetPaddingBottom() + require.Equal(t, 0, s.GetPaddingBottom()) + + require.Equal(t, 4, s.GetPaddingLeft()) + s.UnsetPaddingLeft() + require.Equal(t, 0, s.GetPaddingLeft()) + + // border + s = NewStyle().Border(normalBorder, true, true, true, true) + require.True(t, s.GetBorderTop()) + s.UnsetBorderTop() + require.False(t, s.GetBorderTop()) + + require.True(t, s.GetBorderRight()) + s.UnsetBorderRight() + require.False(t, s.GetBorderRight()) + + require.True(t, s.GetBorderBottom()) + s.UnsetBorderBottom() + require.False(t, s.GetBorderBottom()) + + require.True(t, s.GetBorderLeft()) + s.UnsetBorderLeft() + require.False(t, s.GetBorderLeft()) +} + func BenchmarkStyleRender(b *testing.B) { s := NewStyle(). Bold(true). From 065368aa1152edadf74e8e4608fbe979c1390cd1 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Wed, 1 Jun 2022 10:38:18 -0300 Subject: [PATCH 013/126] test: simplify & improve output Signed-off-by: Carlos A Becker --- color_test.go | 37 +++++++++++++++++++------------------ join_test.go | 15 +++++++++------ runes_test.go | 24 +++++++++++++++--------- 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/color_test.go b/color_test.go index c52dbb23..065b34f8 100644 --- a/color_test.go +++ b/color_test.go @@ -8,46 +8,47 @@ import ( ) func TestSetColorProfile(t *testing.T) { + input := "hello" + tt := []struct { + name string profile termenv.Profile - input string - style Style expected string }{ { + "ascii", termenv.Ascii, "hello", - NewStyle().Foreground(Color("#5A56E0")), - "hello", }, { + "ansi", termenv.ANSI, - "hello", - NewStyle().Foreground(Color("#5A56E0")), "\x1b[94mhello\x1b[0m", }, { + "ansi256", termenv.ANSI256, - "hello", - NewStyle().Foreground(Color("#5A56E0")), "\x1b[38;5;62mhello\x1b[0m", }, { + "truecolor", termenv.TrueColor, - "hello", - NewStyle().Foreground(Color("#5A56E0")), "\x1b[38;2;89;86;224mhello\x1b[0m", }, } - for i, tc := range tt { - SetColorProfile(tc.profile) - res := tc.style.Render(tc.input) - if res != tc.expected { - t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", - i, tc.expected, formatEscapes(tc.expected), - res, formatEscapes(res)) - } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + SetColorProfile(tc.profile) + style := NewStyle().Foreground(Color("#5A56E0")) + res := style.Render(input) + + if res != tc.expected { + t.Errorf("Expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", + tc.expected, formatEscapes(tc.expected), + res, formatEscapes(res)) + } + }) } } diff --git a/join_test.go b/join_test.go index 813280de..9dcf5137 100644 --- a/join_test.go +++ b/join_test.go @@ -4,18 +4,21 @@ import "testing" func TestJoinVertical(t *testing.T) { type test struct { + name string result string expected string } tests := []test{ - {JoinVertical(0, "A", "BBBB"), "A \nBBBB"}, - {JoinVertical(1, "A", "BBBB"), " A\nBBBB"}, - {JoinVertical(0.25, "A", "BBBB"), " A \nBBBB"}, + {"por0", JoinVertical(0, "A", "BBBB"), "A \nBBBB"}, + {"pos1", JoinVertical(1, "A", "BBBB"), " A\nBBBB"}, + {"pos0.25", JoinVertical(0.25, "A", "BBBB"), " A \nBBBB"}, } for _, test := range tests { - if test.result != test.expected { - t.Errorf("Got \n%s\n, expected \n%s\n", test.result, test.expected) - } + t.Run(test.name, func(t *testing.T) { + if test.result != test.expected { + t.Errorf("Got \n%s\n, expected \n%s\n", test.result, test.expected) + } + }) } } diff --git a/runes_test.go b/runes_test.go index 3e276587..44f3963e 100644 --- a/runes_test.go +++ b/runes_test.go @@ -6,37 +6,41 @@ import ( ) func TestStyleRunes(t *testing.T) { - t.Parallel() - matchedStyle := NewStyle().Reverse(true) unmatchedStyle := NewStyle() tt := []struct { + name string input string indices []int expected string }{ { + "hello 0", "hello", []int{0}, "\x1b[7mh\x1b[0mello", }, { + "你好 1", "你好", []int{1}, "你\x1b[7m好\x1b[0m", }, { + "hello 你好 6,7", "hello 你好", []int{6, 7}, "hello \x1b[7m你好\x1b[0m", }, { + "hello 1,3", "hello", []int{1, 3}, "h\x1b[7me\x1b[0ml\x1b[7ml\x1b[0mo", }, { + "你好 0,1", "你好", []int{0, 1}, "\x1b[7m你好\x1b[0m", @@ -47,13 +51,15 @@ func TestStyleRunes(t *testing.T) { return StyleRunes(str, indices, matchedStyle, unmatchedStyle) } - for i, tc := range tt { - res := fn(tc.input, tc.indices) - if fn(tc.input, tc.indices) != tc.expected { - t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual Output:\n\n`%s`\n`%s`\n\n", - i, tc.expected, formatEscapes(tc.expected), - res, formatEscapes(res)) - } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + res := fn(tc.input, tc.indices) + if res != tc.expected { + t.Errorf("Expected:\n\n`%s`\n`%s`\n\nActual Output:\n\n`%s`\n`%s`\n\n", + tc.expected, formatEscapes(tc.expected), + res, formatEscapes(res)) + } + }) } } From b4057ad28a1c1cddf10a24250b38611867ed4ff2 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 29 Jun 2022 10:55:26 -0400 Subject: [PATCH 014/126] docs: use new footer image --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 039e28c7..f9fb8e53 100644 --- a/README.md +++ b/README.md @@ -395,7 +395,7 @@ the stylesheet-based Markdown renderer. Part of [Charm](https://charm.sh). -The Charm logo +The Charm logo Charm热爱开源 • Charm loves open source From b10373fbc613c4b13a0970f84b481a7a06dbfba8 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 29 Jun 2022 10:57:03 -0400 Subject: [PATCH 015/126] docs: add feedback section to README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index f9fb8e53..21dfab23 100644 --- a/README.md +++ b/README.md @@ -386,6 +386,15 @@ the stylesheet-based Markdown renderer. [glamour]: https://github.com/charmbracelet/glamour +## Whatcha think? + +We’d love to hear your thoughts on this project. Feel free to drop us a note. + +* [Twitter](https://twitter.com/charmcli) +* [The Fediverse](https://mastodon.technology/@charm) +* [Slack](https://charm.sh/slack) + + ## License [MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE) From 30dc6d9f2beb38909449b520362c134223410c02 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 28 Sep 2022 22:15:34 -0400 Subject: [PATCH 016/126] docs(readme): ASCII is an acronym --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21dfab23..5b10bb75 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ lipgloss.Color("#04B575") // a green lipgloss.Color("#3C3C3C") // a dark gray ``` -...as well as a 1-bit Ascii profile, which is black and white only. +...as well as a 1-bit ASCII profile, which is black and white only. The terminal's color profile will be automatically detected, and colors outside the gamut of the current palette will be automatically coerced to their closest From 88320875343e12f890e4d6238c3fab81fdd89e12 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Sep 2022 07:56:01 +0200 Subject: [PATCH 017/126] ci: add coverage workflow --- .github/workflows/coverage.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..bcc6721f --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,28 @@ +name: coverage +on: [push, pull_request] + +jobs: + coverage: + strategy: + matrix: + go-version: [^1] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + env: + GO111MODULE: "on" + steps: + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go-version }} + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Coverage + env: + COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + go test -race -covermode atomic -coverprofile=profile.cov ./... + GO111MODULE=off go get github.com/mattn/goveralls + $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github From ce2c889fb70979b1fe5cc6d4c4361290e0da3407 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Sep 2022 07:53:58 +0200 Subject: [PATCH 018/126] chore: add dependabot config --- .github/dependabot.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b773d822 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,32 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" + time: "08:00" + labels: + - "dependencies" + commit-message: + prefix: "feat" + include: "scope" + - package-ecosystem: "gomod" + directory: "/example" + schedule: + interval: "daily" + time: "08:00" + labels: + - "dependencies" + commit-message: + prefix: "chore" + include: "scope" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + time: "08:00" + labels: + - "dependencies" + commit-message: + prefix: "chore" + include: "scope" From e087d81d085ed238858659641f78b3a633b94694 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 30 Sep 2022 08:28:56 +0200 Subject: [PATCH 019/126] ci: bump linter workflow --- .github/workflows/lint-soft.yml | 9 +++++++-- .github/workflows/lint.yml | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index b6c06e68..77348b95 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -13,9 +13,14 @@ jobs: name: lint-soft runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: ^1 + + - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: # Optional: golangci-lint command line arguments. args: --config .golangci-soft.yml --issues-exit-code=0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 74f4c5ce..1129ece6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,9 +13,14 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Install Go + uses: actions/setup-go@v3 + with: + go-version: ^1 + + - uses: actions/checkout@v3 - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + uses: golangci/golangci-lint-action@v3 with: # Optional: golangci-lint command line arguments. #args: From 5c31e2f6ab85bf0e477d43bfe932b9178bdf408b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Sep 2022 06:22:35 +0000 Subject: [PATCH 020/126] feat(deps): bump github.com/stretchr/testify from 1.7.1 to 1.8.0 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.1 to 1.8.0. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.7.1...v1.8.0) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 6a2966b6..7da82405 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,5 @@ require ( github.com/mattn/go-runewidth v0.0.13 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 - github.com/stretchr/testify v1.7.1 + github.com/stretchr/testify v1.8.0 ) diff --git a/go.sum b/go.sum index 9e2e8e66..5cfbbd9d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -17,11 +18,14 @@ github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From ae7c84f7b1584d71f16fb585f248dbc817a904b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Sep 2022 06:37:39 +0000 Subject: [PATCH 021/126] feat(deps): bump github.com/mattn/go-runewidth from 0.0.13 to 0.0.14 Bumps [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) from 0.0.13 to 0.0.14. - [Release notes](https://github.com/mattn/go-runewidth/releases) - [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.13...v0.0.14) --- updated-dependencies: - dependency-name: github.com/mattn/go-runewidth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 7da82405..620d0aed 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/charmbracelet/lipgloss go 1.15 require ( - github.com/mattn/go-runewidth v0.0.13 + github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 github.com/stretchr/testify v1.8.0 diff --git a/go.sum b/go.sum index 5cfbbd9d..57783e69 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,9 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= From e36ced828fc3d82c67c5a2d00efcb3a5b2214fcd Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 6 Oct 2022 11:06:50 +0200 Subject: [PATCH 022/126] chore: go mod tidy in example --- example/go.sum | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/example/go.sum b/example/go.sum index 9ef83e7d..a7665b07 100644 --- a/example/go.sum +++ b/example/go.sum @@ -1,19 +1,34 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 92c8fa0a572bf0d3993fa5fa358eb3677bf06e3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Oct 2022 08:49:22 +0000 Subject: [PATCH 023/126] chore(deps): bump actions/checkout from 2 to 3.1.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.1.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3.1.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b0c9f68c..45fb876d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3.1.0 - name: Download Go modules run: go mod download diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bcc6721f..65b2ccd9 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v3.1.0 - name: Coverage env: diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 77348b95..61acc014 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v3.1.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1129ece6..cd6755a9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v3.1.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: From 3dd9f0bfb881f63fe36c3a3645e981595b5dc0a4 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Sat, 15 Oct 2022 15:37:38 -0400 Subject: [PATCH 024/126] docs: minor fixes to comments --- get.go | 20 ++++++++++---------- runes.go | 2 +- set.go | 4 ++-- style.go | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/get.go b/get.go index f01b8f15..2613750f 100644 --- a/get.go +++ b/get.go @@ -6,42 +6,42 @@ import ( "github.com/muesli/reflow/ansi" ) -// GetBold returns the style's bold value It no value is set false is returned. +// GetBold returns the style's bold value. If no value is set false is returned. func (s Style) GetBold() bool { return s.getAsBool(boldKey, false) } -// GetItalic returns the style's italic value. It no value is set false is +// GetItalic returns the style's italic value. If no value is set false is // returned. func (s Style) GetItalic() bool { return s.getAsBool(italicKey, false) } -// GetUnderline returns the style's underline value. It no value is set false is +// GetUnderline returns the style's underline value. If no value is set false is // returned. func (s Style) GetUnderline() bool { return s.getAsBool(underlineKey, false) } -// GetStrikethrough returns the style's strikethrough value. It no value is set false +// GetStrikethrough returns the style's strikethrough value. If no value is set false // is returned. func (s Style) GetStrikethrough() bool { return s.getAsBool(strikethroughKey, false) } -// GetReverse returns the style's reverse value. It no value is set false is +// GetReverse returns the style's reverse value. If no value is set false is // returned. func (s Style) GetReverse() bool { return s.getAsBool(reverseKey, false) } -// GetBlink returns the style's blink value. It no value is set false is +// GetBlink returns the style's blink value. If no value is set false is // returned. func (s Style) GetBlink() bool { return s.getAsBool(blinkKey, false) } -// GetFaint returns the style's faint value. It no value is set false is +// GetFaint returns the style's faint value. If no value is set false is // returned. func (s Style) GetFaint() bool { return s.getAsBool(faintKey, false) @@ -72,7 +72,7 @@ func (s Style) GetHeight() int { } // GetAlign returns the style's implicit horizontal alignment setting. -// If no alignment is set Position.AlignLeft is returned. +// If no alignment is set Position.Left is returned. func (s Style) GetAlign() Position { v := s.getAsPosition(alignHorizontalKey) if v == Position(0) { @@ -82,7 +82,7 @@ func (s Style) GetAlign() Position { } // GetAlignHorizontal returns the style's implicit horizontal alignment setting. -// If no alignment is set Position.AlignLeft is returned. +// If no alignment is set Position.Left is returned. func (s Style) GetAlignHorizontal() Position { v := s.getAsPosition(alignHorizontalKey) if v == Position(0) { @@ -92,7 +92,7 @@ func (s Style) GetAlignHorizontal() Position { } // GetAlignVertical returns the style's implicit vertical alignment setting. -// If no alignment is set Position.AlignTop is returned. +// If no alignment is set Position.Top is returned. func (s Style) GetAlignVertical() Position { v := s.getAsPosition(alignVerticalKey) if v == Position(0) { diff --git a/runes.go b/runes.go index 723f6dbf..2c5265a3 100644 --- a/runes.go +++ b/runes.go @@ -4,7 +4,7 @@ import ( "strings" ) -// StyleRunes applys a given style to runes at the given indicesin the string. +// StyleRunes applys a given style to runes at the given indices in the string. // Note that you must provide styling options for both matched and unmatched // runes. Indices out of bounds will be ignored. func StyleRunes(str string, indices []int, matched, unmatched Style) string { diff --git a/set.go b/set.go index 02884543..4fa9c3b8 100644 --- a/set.go +++ b/set.go @@ -39,7 +39,7 @@ func (s Style) Italic(v bool) Style { // Underline sets an underline rule. By default, underlines will not be drawn on // whitespace like margins and padding. To change this behavior set -// renderUnderlinesOnSpaces. +// UnderlineSpaces. func (s Style) Underline(v bool) Style { s.set(underlineKey, v) return s @@ -47,7 +47,7 @@ func (s Style) Underline(v bool) Style { // Strikethrough sets a strikethrough rule. By default, strikes will not be // drawn on whitespace like margins and padding. To change this behavior set -// renderStrikethroughOnSpaces. +// StrikethroughSpaces. func (s Style) Strikethrough(v bool) Style { s.set(strikethroughKey, v) return s diff --git a/style.go b/style.go index fb89d06c..4c149326 100644 --- a/style.go +++ b/style.go @@ -432,7 +432,7 @@ func padLeft(str string, n int, style *termenv.Style) string { return b.String() } -// Apply right right padding. +// Apply right padding. func padRight(str string, n int, style *termenv.Style) string { if n == 0 || str == "" { return str From cdc8b16b07b1f65401227c0e9b92531d50b0be5c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Mon, 24 Oct 2022 08:57:38 +0200 Subject: [PATCH 025/126] docs: new readme footer --- README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5b10bb75..c4c282ef 100644 --- a/README.md +++ b/README.md @@ -386,20 +386,18 @@ the stylesheet-based Markdown renderer. [glamour]: https://github.com/charmbracelet/glamour -## Whatcha think? +## Feedback -We’d love to hear your thoughts on this project. Feel free to drop us a note. +We’d love to hear your thoughts on this project. Feel free to drop us a note! * [Twitter](https://twitter.com/charmcli) -* [The Fediverse](https://mastodon.technology/@charm) -* [Slack](https://charm.sh/slack) - +* [The Fediverse](https://mastodon.social/@charmcli) +* [Discord](https://charm.sh/chat) ## License [MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE) - *** Part of [Charm](https://charm.sh). From c85c4f44564ffaf393980d65e7ea16eea640a2b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 09:00:39 +0000 Subject: [PATCH 026/126] feat(deps): bump github.com/stretchr/testify from 1.8.0 to 1.8.1 Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 620d0aed..00a38771 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,5 @@ require ( github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 - github.com/stretchr/testify v1.8.0 + github.com/stretchr/testify v1.8.1 ) diff --git a/go.sum b/go.sum index 57783e69..b95d194f 100644 --- a/go.sum +++ b/go.sum @@ -20,9 +20,11 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= From 9ee8b6e19f5578a85faf965239f45fb4056fbe8d Mon Sep 17 00:00:00 2001 From: Victor/Alyx Bersy Date: Thu, 27 Oct 2022 21:02:57 +0200 Subject: [PATCH 027/126] feat: add `BlockBorder`, `OuterHalfBlockBorder`, and `InnerHalfBlockBorder` border styles (#120) --- borders.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ set.go | 5 +++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/borders.go b/borders.go index a3284ac0..23372808 100644 --- a/borders.go +++ b/borders.go @@ -84,6 +84,39 @@ var ( BottomRight: "╯", } + blockBorder = Border{ + Top: "█", + Bottom: "█", + Left: "█", + Right: "█", + TopLeft: "█", + TopRight: "█", + BottomLeft: "█", + BottomRight: "█", + } + + outerHalfBlockBorder = Border{ + Top: "▀", + Bottom: "▄", + Left: "▌", + Right: "▐", + TopLeft: "▛", + TopRight: "▜", + BottomLeft: "▙", + BottomRight: "▟", + } + + innerHalfBlockBorder = Border{ + Top: "▄", + Bottom: "▀", + Left: "▐", + Right: "▌", + TopLeft: "▗", + TopRight: "▖", + BottomLeft: "▝", + BottomRight: "▘", + } + thickBorder = Border{ Top: "━", Bottom: "━", @@ -129,6 +162,21 @@ func RoundedBorder() Border { return roundedBorder } +// BlockBorder returns a border that takes the whole block. +func BlockBorder() Border { + return blockBorder +} + +// OuterHalfBlockBorder returns a half-block border that sits outside the frame. +func OuterHalfBlockBorder() Border { + return outerHalfBlockBorder +} + +// InnerHalfBlockBorder returns a half-block border that sits inside the frame. +func InnerHalfBlockBorder() Border { + return innerHalfBlockBorder +} + // ThickBorder returns a border that's thicker than the one returned by // NormalBorder. func ThickBorder() Border { diff --git a/set.go b/set.go index 4fa9c3b8..21a9ce4f 100644 --- a/set.go +++ b/set.go @@ -300,8 +300,9 @@ func (s Style) Border(b Border, sides ...bool) Style { // the border style, the border will be enabled for all sides during rendering. // // You can define border characters as you'd like, though several default -// styles are included: NormalBorder(), RoundedBorder(), ThickBorder(), and -// DoubleBorder(). +// styles are included: NormalBorder(), RoundedBorder(), BlockBorder(), +// OuterHalfBlockBorder(), InnerHalfBlockBorder(), ThickBorder(), +// and DoubleBorder(). // // Example: // From 1c1240fd653db91c5a262fa19dca161c31964b02 Mon Sep 17 00:00:00 2001 From: nervo Date: Tue, 8 Nov 2022 10:55:11 +0100 Subject: [PATCH 028/126] docs: fix various typos (#156) --- README.md | 2 +- borders.go | 2 +- color.go | 6 +++--- get.go | 2 +- position.go | 4 ++-- runes.go | 2 +- set.go | 14 +++++++------- style.go | 2 +- unset.go | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index c4c282ef..468ecb61 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ var wildStyle = style.Copy().Blink(true) ``` `Copy()` performs a copy on the underlying data structure ensuring that you get -a true, dereferenced copy of a style. Without copying it's possible to mutate +a true, dereferenced copy of a style. Without copying, it's possible to mutate styles. diff --git a/borders.go b/borders.go index 23372808..b33b8e58 100644 --- a/borders.go +++ b/borders.go @@ -247,7 +247,7 @@ func (s Style) applyBorder(str string) string { border.Right = " " } - // If corners should be render but are set with the empty string, fill them + // If corners should be rendered but are set with the empty string, fill them // with a single space. if hasTop && hasLeft && border.TopLeft == "" { border.TopLeft = " " diff --git a/color.go b/color.go index d70dfaf3..e154539c 100644 --- a/color.go +++ b/color.go @@ -178,7 +178,7 @@ func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { } // CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color -// profiles. Automatic color degredation will not be performed. +// profiles. Automatic color degradation will not be performed. type CompleteColor struct { TrueColor string ANSI256 string @@ -210,9 +210,9 @@ func (c CompleteColor) RGBA() (r, g, b, a uint32) { return termenv.ConvertToRGB(c.color()).RGBA() } -// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color +// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color // profiles, with separate options for light and dark backgrounds. Automatic -// color degredation will not be performed. +// color degradation will not be performed. type CompleteAdaptiveColor struct { Light CompleteColor Dark CompleteColor diff --git a/get.go b/get.go index 2613750f..856d1f61 100644 --- a/get.go +++ b/get.go @@ -200,7 +200,7 @@ func (s Style) GetVerticalMargins() int { // GetBorder returns the style's border style (type Border) and value for the // top, right, bottom, and left in that order. If no value is set for the // border style, Border{} is returned. For all other unset values false is -// returend. +// returned. func (s Style) GetBorder() (b Border, top, right, bottom, left bool) { return s.getBorderStyle(), s.getAsBool(borderTopKey, false), diff --git a/position.go b/position.go index 2ecb8979..f419452b 100644 --- a/position.go +++ b/position.go @@ -39,7 +39,7 @@ func Place(width, height int, hPos, vPos Position, str string, opts ...Whitespac // PlaceHorizontal places a string or text block horizontally in an unstyled // block of a given width. If the given width is shorter than the max width of -// the string (measured by it's longest line) this will be a noöp. +// the string (measured by its longest line) this will be a noop. func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string { lines, contentWidth := getLines(str) gap := width - contentWidth @@ -89,7 +89,7 @@ func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOpti // PlaceVertical places a string or text block vertically in an unstyled block // of a given height. If the given height is shorter than the height of the -// string (measured by it's newlines) then this will be a noöp. +// string (measured by its newlines) then this will be a noop. func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string { contentHeight := strings.Count(str, "\n") + 1 gap := height - contentHeight diff --git a/runes.go b/runes.go index 2c5265a3..7a49e326 100644 --- a/runes.go +++ b/runes.go @@ -4,7 +4,7 @@ import ( "strings" ) -// StyleRunes applys a given style to runes at the given indices in the string. +// StyleRunes apply a given style to runes at the given indices in the string. // Note that you must provide styling options for both matched and unmatched // runes. Indices out of bounds will be ignored. func StyleRunes(str string, indices []int, matched, unmatched Style) string { diff --git a/set.go b/set.go index 21a9ce4f..38a0ed17 100644 --- a/set.go +++ b/set.go @@ -1,7 +1,7 @@ package lipgloss // This could (should) probably just be moved into NewStyle(). We've broken it -// out so we can call it in a lazy way. +// out, so we can call it in a lazy way. func (s *Style) init() { if s.rules == nil { s.rules = make(rules) @@ -16,7 +16,7 @@ func (s *Style) set(key propKey, value interface{}) { case int: // We don't allow negative integers on any of our values, so just keep // them at zero or above. We could use uints instead, but the - // conversions are a little tedious so we're sticking with ints for + // conversions are a little tedious, so we're sticking with ints for // sake of usability. s.rules[key] = max(0, v) default: @@ -120,13 +120,13 @@ func (s Style) Align(p ...Position) Style { return s } -// HorizontalAlign sets a horizontal text alignment rule. +// AlignHorizontal sets a horizontal text alignment rule. func (s Style) AlignHorizontal(p Position) Style { s.set(alignHorizontalKey, p) return s } -// VerticalAlign sets a text alignment rule. +// AlignVertical sets a text alignment rule. func (s Style) AlignVertical(p Position) Style { s.set(alignVerticalKey, p) return s @@ -251,7 +251,7 @@ func (s Style) MarginBackground(c TerminalColor) Style { return s } -// Border is shorthand for setting a the border style and which sides should +// Border is shorthand for setting the border style and which sides should // have a border at once. The variadic argument sides works as follows: // // With one value, the value is applied to all sides. @@ -505,7 +505,7 @@ func (s Style) MaxHeight(n int) Style { } // UnderlineSpaces determines whether to underline spaces between words. By -// default this is true. Spaces can also be underlined without underlining the +// default, this is true. Spaces can also be underlined without underlining the // text itself. func (s Style) UnderlineSpaces(v bool) Style { s.set(underlineSpacesKey, v) @@ -513,7 +513,7 @@ func (s Style) UnderlineSpaces(v bool) Style { } // StrikethroughSpaces determines whether to apply strikethroughs to spaces -// between words. By default this is true. Spaces can also be struck without +// between words. By default, this is true. Spaces can also be struck without // underlining the text itself. func (s Style) StrikethroughSpaces(v bool) Style { s.set(strikethroughSpacesKey, v) diff --git a/style.go b/style.go index 4c149326..a9fbb044 100644 --- a/style.go +++ b/style.go @@ -77,7 +77,7 @@ type rules map[propKey]interface{} // NewStyle returns a new, empty Style. While it's syntactic sugar for the // Style{} primitive, it's recommended to use this function for creating styles -// incase the underlying implementation changes. +// in case the underlying implementation changes. func NewStyle() Style { return Style{} } diff --git a/unset.go b/unset.go index 25f3ac92..a8367898 100644 --- a/unset.go +++ b/unset.go @@ -79,7 +79,7 @@ func (s Style) UnsetAlignHorizontal() Style { return s } -// UnsetAlignHorizontal removes the vertical text alignment style rule, if set. +// UnsetAlignVertical removes the vertical text alignment style rule, if set. func (s Style) UnsetAlignVertical() Style { delete(s.rules, alignVerticalKey) return s From 04a7a77281f2ae3a0c712facb09e5a7b68bd381c Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Fri, 18 Nov 2022 02:10:07 +0100 Subject: [PATCH 029/126] chore: disable dependabot timer --- .github/dependabot.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b773d822..a5d96639 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,6 @@ updates: directory: "/" schedule: interval: "daily" - time: "08:00" labels: - "dependencies" commit-message: @@ -14,7 +13,6 @@ updates: directory: "/example" schedule: interval: "daily" - time: "08:00" labels: - "dependencies" commit-message: @@ -24,7 +22,6 @@ updates: directory: "/" schedule: interval: "daily" - time: "08:00" labels: - "dependencies" commit-message: From b5904dc6ad265e83800e1248efb941c86bcc8716 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Jan 2023 23:09:55 +0000 Subject: [PATCH 030/126] chore(deps): bump actions/checkout from 3.1.0 to 3.3.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.1.0 to 3.3.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.1.0...v3.3.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45fb876d..5712dc3b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.1.0 + uses: actions/checkout@v3.3.0 - name: Download Go modules run: go mod download diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 65b2ccd9..b2266128 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.1.0 + uses: actions/checkout@v3.3.0 - name: Coverage env: diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 61acc014..b91419d7 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3.3.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cd6755a9..5b6cedc3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.1.0 + - uses: actions/checkout@v3.3.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: From 14eeaa6ffac0d2dec3f6b1806fbb1f2f5b347613 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 14 Oct 2022 14:59:10 -0300 Subject: [PATCH 031/126] fix: reduce dependencies Signed-off-by: Carlos A Becker --- go.mod | 1 - go.sum | 17 ----- style_test.go | 184 ++++++++++++++++++++++++++++---------------------- 3 files changed, 104 insertions(+), 98 deletions(-) diff --git a/go.mod b/go.mod index 00a38771..841b3579 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,4 @@ require ( github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 - github.com/stretchr/testify v1.8.1 ) diff --git a/go.sum b/go.sum index b95d194f..3411383b 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,3 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= @@ -13,22 +10,8 @@ github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9Fn github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/style_test.go b/style_test.go index 825fa7aa..1540519a 100644 --- a/style_test.go +++ b/style_test.go @@ -1,10 +1,10 @@ package lipgloss import ( + "reflect" "testing" "github.com/muesli/termenv" - "github.com/stretchr/testify/require" ) func TestStyleRender(t *testing.T) { @@ -60,7 +60,7 @@ func TestValueCopy(t *testing.T) { i := s i.Bold(false) - require.Equal(t, s.GetBold(), i.GetBold()) + requireEqual(t, s.GetBold(), i.GetBold()) } func TestStyleInherit(t *testing.T) { @@ -80,23 +80,23 @@ func TestStyleInherit(t *testing.T) { i := NewStyle().Inherit(s) - require.Equal(t, s.GetBold(), i.GetBold()) - require.Equal(t, s.GetItalic(), i.GetItalic()) - require.Equal(t, s.GetUnderline(), i.GetUnderline()) - require.Equal(t, s.GetStrikethrough(), i.GetStrikethrough()) - require.Equal(t, s.GetBlink(), i.GetBlink()) - require.Equal(t, s.GetFaint(), i.GetFaint()) - require.Equal(t, s.GetForeground(), i.GetForeground()) - require.Equal(t, s.GetBackground(), i.GetBackground()) - - require.NotEqual(t, s.GetMarginLeft(), i.GetMarginLeft()) - require.NotEqual(t, s.GetMarginRight(), i.GetMarginRight()) - require.NotEqual(t, s.GetMarginTop(), i.GetMarginTop()) - require.NotEqual(t, s.GetMarginBottom(), i.GetMarginBottom()) - require.NotEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft()) - require.NotEqual(t, s.GetPaddingRight(), i.GetPaddingRight()) - require.NotEqual(t, s.GetPaddingTop(), i.GetPaddingTop()) - require.NotEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom()) + requireEqual(t, s.GetBold(), i.GetBold()) + requireEqual(t, s.GetItalic(), i.GetItalic()) + requireEqual(t, s.GetUnderline(), i.GetUnderline()) + requireEqual(t, s.GetStrikethrough(), i.GetStrikethrough()) + requireEqual(t, s.GetBlink(), i.GetBlink()) + requireEqual(t, s.GetFaint(), i.GetFaint()) + requireEqual(t, s.GetForeground(), i.GetForeground()) + requireEqual(t, s.GetBackground(), i.GetBackground()) + + requireNotEqual(t, s.GetMarginLeft(), i.GetMarginLeft()) + requireNotEqual(t, s.GetMarginRight(), i.GetMarginRight()) + requireNotEqual(t, s.GetMarginTop(), i.GetMarginTop()) + requireNotEqual(t, s.GetMarginBottom(), i.GetMarginBottom()) + requireNotEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft()) + requireNotEqual(t, s.GetPaddingRight(), i.GetPaddingRight()) + requireNotEqual(t, s.GetPaddingTop(), i.GetPaddingTop()) + requireNotEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom()) } func TestStyleCopy(t *testing.T) { @@ -116,133 +116,133 @@ func TestStyleCopy(t *testing.T) { i := s.Copy() - require.Equal(t, s.GetBold(), i.GetBold()) - require.Equal(t, s.GetItalic(), i.GetItalic()) - require.Equal(t, s.GetUnderline(), i.GetUnderline()) - require.Equal(t, s.GetStrikethrough(), i.GetStrikethrough()) - require.Equal(t, s.GetBlink(), i.GetBlink()) - require.Equal(t, s.GetFaint(), i.GetFaint()) - require.Equal(t, s.GetForeground(), i.GetForeground()) - require.Equal(t, s.GetBackground(), i.GetBackground()) - - require.Equal(t, s.GetMarginLeft(), i.GetMarginLeft()) - require.Equal(t, s.GetMarginRight(), i.GetMarginRight()) - require.Equal(t, s.GetMarginTop(), i.GetMarginTop()) - require.Equal(t, s.GetMarginBottom(), i.GetMarginBottom()) - require.Equal(t, s.GetPaddingLeft(), i.GetPaddingLeft()) - require.Equal(t, s.GetPaddingRight(), i.GetPaddingRight()) - require.Equal(t, s.GetPaddingTop(), i.GetPaddingTop()) - require.Equal(t, s.GetPaddingBottom(), i.GetPaddingBottom()) + requireEqual(t, s.GetBold(), i.GetBold()) + requireEqual(t, s.GetItalic(), i.GetItalic()) + requireEqual(t, s.GetUnderline(), i.GetUnderline()) + requireEqual(t, s.GetStrikethrough(), i.GetStrikethrough()) + requireEqual(t, s.GetBlink(), i.GetBlink()) + requireEqual(t, s.GetFaint(), i.GetFaint()) + requireEqual(t, s.GetForeground(), i.GetForeground()) + requireEqual(t, s.GetBackground(), i.GetBackground()) + + requireEqual(t, s.GetMarginLeft(), i.GetMarginLeft()) + requireEqual(t, s.GetMarginRight(), i.GetMarginRight()) + requireEqual(t, s.GetMarginTop(), i.GetMarginTop()) + requireEqual(t, s.GetMarginBottom(), i.GetMarginBottom()) + requireEqual(t, s.GetPaddingLeft(), i.GetPaddingLeft()) + requireEqual(t, s.GetPaddingRight(), i.GetPaddingRight()) + requireEqual(t, s.GetPaddingTop(), i.GetPaddingTop()) + requireEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom()) } func TestStyleUnset(t *testing.T) { t.Parallel() s := NewStyle().Bold(true) - require.True(t, s.GetBold()) + requireTrue(t, s.GetBold()) s.UnsetBold() - require.False(t, s.GetBold()) + requireFalse(t, s.GetBold()) s = NewStyle().Italic(true) - require.True(t, s.GetItalic()) + requireTrue(t, s.GetItalic()) s.UnsetItalic() - require.False(t, s.GetItalic()) + requireFalse(t, s.GetItalic()) s = NewStyle().Underline(true) - require.True(t, s.GetUnderline()) + requireTrue(t, s.GetUnderline()) s.UnsetUnderline() - require.False(t, s.GetUnderline()) + requireFalse(t, s.GetUnderline()) s = NewStyle().Strikethrough(true) - require.True(t, s.GetStrikethrough()) + requireTrue(t, s.GetStrikethrough()) s.UnsetStrikethrough() - require.False(t, s.GetStrikethrough()) + requireFalse(t, s.GetStrikethrough()) s = NewStyle().Reverse(true) - require.True(t, s.GetReverse()) + requireTrue(t, s.GetReverse()) s.UnsetReverse() - require.False(t, s.GetReverse()) + requireFalse(t, s.GetReverse()) s = NewStyle().Blink(true) - require.True(t, s.GetBlink()) + requireTrue(t, s.GetBlink()) s.UnsetBlink() - require.False(t, s.GetBlink()) + requireFalse(t, s.GetBlink()) s = NewStyle().Faint(true) - require.True(t, s.GetFaint()) + requireTrue(t, s.GetFaint()) s.UnsetFaint() - require.False(t, s.GetFaint()) + requireFalse(t, s.GetFaint()) s = NewStyle().Inline(true) - require.True(t, s.GetInline()) + requireTrue(t, s.GetInline()) s.UnsetInline() - require.False(t, s.GetInline()) + requireFalse(t, s.GetInline()) // colors col := Color("#ffffff") s = NewStyle().Foreground(col) - require.Equal(t, col, s.GetForeground()) + requireEqual(t, col, s.GetForeground()) s.UnsetForeground() - require.NotEqual(t, col, s.GetForeground()) + requireNotEqual(t, col, s.GetForeground()) s = NewStyle().Background(col) - require.Equal(t, col, s.GetBackground()) + requireEqual(t, col, s.GetBackground()) s.UnsetBackground() - require.NotEqual(t, col, s.GetBackground()) + requireNotEqual(t, col, s.GetBackground()) // margins s = NewStyle().Margin(1, 2, 3, 4) - require.Equal(t, 1, s.GetMarginTop()) + requireEqual(t, 1, s.GetMarginTop()) s.UnsetMarginTop() - require.Equal(t, 0, s.GetMarginTop()) + requireEqual(t, 0, s.GetMarginTop()) - require.Equal(t, 2, s.GetMarginRight()) + requireEqual(t, 2, s.GetMarginRight()) s.UnsetMarginRight() - require.Equal(t, 0, s.GetMarginRight()) + requireEqual(t, 0, s.GetMarginRight()) - require.Equal(t, 3, s.GetMarginBottom()) + requireEqual(t, 3, s.GetMarginBottom()) s.UnsetMarginBottom() - require.Equal(t, 0, s.GetMarginBottom()) + requireEqual(t, 0, s.GetMarginBottom()) - require.Equal(t, 4, s.GetMarginLeft()) + requireEqual(t, 4, s.GetMarginLeft()) s.UnsetMarginLeft() - require.Equal(t, 0, s.GetMarginLeft()) + requireEqual(t, 0, s.GetMarginLeft()) // padding s = NewStyle().Padding(1, 2, 3, 4) - require.Equal(t, 1, s.GetPaddingTop()) + requireEqual(t, 1, s.GetPaddingTop()) s.UnsetPaddingTop() - require.Equal(t, 0, s.GetPaddingTop()) + requireEqual(t, 0, s.GetPaddingTop()) - require.Equal(t, 2, s.GetPaddingRight()) + requireEqual(t, 2, s.GetPaddingRight()) s.UnsetPaddingRight() - require.Equal(t, 0, s.GetPaddingRight()) + requireEqual(t, 0, s.GetPaddingRight()) - require.Equal(t, 3, s.GetPaddingBottom()) + requireEqual(t, 3, s.GetPaddingBottom()) s.UnsetPaddingBottom() - require.Equal(t, 0, s.GetPaddingBottom()) + requireEqual(t, 0, s.GetPaddingBottom()) - require.Equal(t, 4, s.GetPaddingLeft()) + requireEqual(t, 4, s.GetPaddingLeft()) s.UnsetPaddingLeft() - require.Equal(t, 0, s.GetPaddingLeft()) + requireEqual(t, 0, s.GetPaddingLeft()) // border s = NewStyle().Border(normalBorder, true, true, true, true) - require.True(t, s.GetBorderTop()) + requireTrue(t, s.GetBorderTop()) s.UnsetBorderTop() - require.False(t, s.GetBorderTop()) + requireFalse(t, s.GetBorderTop()) - require.True(t, s.GetBorderRight()) + requireTrue(t, s.GetBorderRight()) s.UnsetBorderRight() - require.False(t, s.GetBorderRight()) + requireFalse(t, s.GetBorderRight()) - require.True(t, s.GetBorderBottom()) + requireTrue(t, s.GetBorderBottom()) s.UnsetBorderBottom() - require.False(t, s.GetBorderBottom()) + requireFalse(t, s.GetBorderBottom()) - require.True(t, s.GetBorderLeft()) + requireTrue(t, s.GetBorderLeft()) s.UnsetBorderLeft() - require.False(t, s.GetBorderLeft()) + requireFalse(t, s.GetBorderLeft()) } func BenchmarkStyleRender(b *testing.B) { @@ -254,3 +254,27 @@ func BenchmarkStyleRender(b *testing.B) { s.Render("Hello world") } } + +func requireTrue(tb testing.TB, b bool) { + requireEqual(tb, true, b) +} + +func requireFalse(tb testing.TB, b bool) { + requireEqual(tb, false, b) +} + +func requireEqual(tb testing.TB, a, b interface{}) { + tb.Helper() + if !reflect.DeepEqual(a, b) { + tb.Errorf("%v != %v", a, b) + tb.FailNow() + } +} + +func requireNotEqual(tb testing.TB, a, b interface{}) { + tb.Helper() + if reflect.DeepEqual(a, b) { + tb.Errorf("%v == %v", a, b) + tb.FailNow() + } +} From f754c404f6dd63e783b8fde8f245306581335932 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 14 Oct 2022 14:59:10 -0300 Subject: [PATCH 032/126] fix: reduce dependencies Signed-off-by: Carlos A Becker --- color.go | 18 ++++++++++++++++-- example/go.sum | 15 ++++++++------- go.mod | 2 +- go.sum | 15 ++++++++------- style.go | 6 +++--- 5 files changed, 36 insertions(+), 20 deletions(-) diff --git a/color.go b/color.go index e154539c..ff0115c7 100644 --- a/color.go +++ b/color.go @@ -7,6 +7,7 @@ import ( ) var ( + output *termenv.Output colorProfile termenv.Profile getColorProfile sync.Once explicitColorProfile bool @@ -26,7 +27,11 @@ func ColorProfile() termenv.Profile { if !explicitColorProfile { getColorProfile.Do(func() { - colorProfile = termenv.EnvColorProfile() + if output != nil { + colorProfile = output.EnvColorProfile() + } else { + colorProfile = termenv.EnvColorProfile() + } }) } return colorProfile @@ -56,6 +61,11 @@ func SetColorProfile(p termenv.Profile) { explicitColorProfile = true } +// SetOutput sets the output to use for adaptive color detection. +func SetOutput(o *termenv.Output) { + output = o +} + // HasDarkBackground returns whether or not the terminal has a dark background. func HasDarkBackground() bool { colorProfileMtx.RLock() @@ -63,7 +73,11 @@ func HasDarkBackground() bool { if !explicitBackgroundColor { getBackgroundColor.Do(func() { - hasDarkBackground = termenv.HasDarkBackground() + if output != nil { + hasDarkBackground = output.HasDarkBackground() + } else { + hasDarkBackground = termenv.HasDarkBackground() + } }) } diff --git a/example/go.sum b/example/go.sum index a7665b07..66cf1ba0 100644 --- a/example/go.sum +++ b/example/go.sum @@ -1,18 +1,19 @@ +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -24,8 +25,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs= golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.mod b/go.mod index 841b3579..c9f6cb79 100644 --- a/go.mod +++ b/go.mod @@ -5,5 +5,5 @@ go 1.15 require ( github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 - github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 + github.com/muesli/termenv v0.14.0 ) diff --git a/go.sum b/go.sum index 3411383b..99871bac 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,18 @@ +github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0 h1:STjmj0uFfRryL9fzRA/OupNppeAID6QJYPMavTL7jtY= -github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/style.go b/style.go index a9fbb044..02dbd191 100644 --- a/style.go +++ b/style.go @@ -155,9 +155,9 @@ func (s Style) Inherit(i Style) Style { // Render applies the defined style formatting to a given string. func (s Style) Render(str string) string { var ( - te termenv.Style - teSpace termenv.Style - teWhitespace termenv.Style + te = ColorProfile().String() + teSpace = ColorProfile().String() + teWhitespace = ColorProfile().String() bold = s.getAsBool(boldKey, false) italic = s.getAsBool(italicKey, false) From 31ab45f81037efa9ba0c29abf3618d84767e7610 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 28 Jun 2022 13:28:35 -0400 Subject: [PATCH 033/126] feat: instantiate lipgloss renderers * Use lipgloss instances instead of a singleton global instance * Deprecate (s Style).Render & (s Style).String * Deprecate the Stringer interface * Update README and godocs * Update example --- README.md | 36 ++-- borders.go | 16 +- color.go | 139 +++------------- color_test.go | 5 +- example/main.go | 84 +++++----- go.mod | 2 +- go.sum | 6 +- position.go | 12 +- renderer.go | 426 ++++++++++++++++++++++++++++++++++++++++++++++++ style.go | 229 +------------------------- whitespace.go | 37 ++++- 11 files changed, 569 insertions(+), 423 deletions(-) create mode 100644 renderer.go diff --git a/README.md b/README.md index 468ecb61..6c437c4e 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ var style = lipgloss.NewStyle(). PaddingLeft(4). Width(22) -fmt.Println(style.Render("Hello, kitty.")) +fmt.Println(lipgloss.Render(style, "Hello, kitty.")) ``` @@ -151,11 +151,12 @@ var style = lipgloss.NewStyle(). Setting a minimum width and height is simple and straightforward. ```go -var str = lipgloss.NewStyle(). +var style = lipgloss.NewStyle(). Width(24). Height(32). - Foreground(lipgloss.Color("63")). - Render("What’s for lunch?") + Foreground(lipgloss.Color("63")) + +var str = lipgloss.Render(style, "What’s for lunch?") ``` @@ -263,29 +264,35 @@ and `MaxWidth`, and `MaxHeight` come in: ```go // Force rendering onto a single line, ignoring margins, padding, and borders. -someStyle.Inline(true).Render("yadda yadda") +lipgloss.Render(someStyle.Inline(true), "yadda yadda") // Also limit rendering to five cells -someStyle.Inline(true).MaxWidth(5).Render("yadda yadda") +lipgloss.Render(someStyle.Inline(true).MaxWidth(5), "yadda yadda") // Limit rendering to a 5x5 cell block -someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda") +lipgloss.Render(someStyle.MaxWidth(5).MaxHeight(5), "yadda yadda") ``` ## Rendering -Generally, you just call the `Render(string)` method on a `lipgloss.Style`: +Generally, you just pass a style and string to the default renderer: ```go -fmt.Println(lipgloss.NewStyle().Bold(true).Render("Hello, kitty.")) +style := lipgloss.NewStyle().Bold(true) +fmt.Println(lipgloss.Render(style, "Hello, kitty.")) ``` -But you could also use the Stringer interface: +But you can also use a custom renderer: ```go +// Render to stdout and force dark background mode. +var r = lipgloss.NewRenderer( + lipgloss.WithOutput(termenv.NewOutput(os.Stdout)), + lipgloss.WithDarkBackground(), +) var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true) -fmt.Printf("%s\n", style) +fmt.Println(r.Render(style)) ``` @@ -318,10 +325,11 @@ Sometimes you’ll want to know the width and height of text blocks when buildin your layouts. ```go -var block string = lipgloss.NewStyle(). +// Render a block of text. +var style = lipgloss.NewStyle(). Width(40). - Padding(2). - Render(someLongString) + Padding(2) +var block string = lipgloss.Render(style, someLongString) // Get the actual, physical dimensions of the text block. width := lipgloss.Width(block) diff --git a/borders.go b/borders.go index b33b8e58..f6dd008a 100644 --- a/borders.go +++ b/borders.go @@ -196,7 +196,7 @@ func HiddenBorder() Border { return hiddenBorder } -func (s Style) applyBorder(str string) string { +func (s Style) applyBorder(re *Renderer, str string) string { var ( topSet = s.isSet(borderTopKey) rightSet = s.isSet(borderRightKey) @@ -298,7 +298,7 @@ func (s Style) applyBorder(str string) string { // Render top if hasTop { top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) - top = styleBorder(top, topFG, topBG) + top = styleBorder(re, top, topFG, topBG) out.WriteString(top) out.WriteRune('\n') } @@ -317,7 +317,7 @@ func (s Style) applyBorder(str string) string { if leftIndex >= len(leftRunes) { leftIndex = 0 } - out.WriteString(styleBorder(r, leftFG, leftBG)) + out.WriteString(styleBorder(re, r, leftFG, leftBG)) } out.WriteString(l) if hasRight { @@ -326,7 +326,7 @@ func (s Style) applyBorder(str string) string { if rightIndex >= len(rightRunes) { rightIndex = 0 } - out.WriteString(styleBorder(r, rightFG, rightBG)) + out.WriteString(styleBorder(re, r, rightFG, rightBG)) } if i < len(lines)-1 { out.WriteRune('\n') @@ -336,7 +336,7 @@ func (s Style) applyBorder(str string) string { // Render bottom if hasBottom { bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width) - bottom = styleBorder(bottom, bottomFG, bottomBG) + bottom = styleBorder(re, bottom, bottomFG, bottomBG) out.WriteRune('\n') out.WriteString(bottom) } @@ -376,7 +376,7 @@ func renderHorizontalEdge(left, middle, right string, width int) string { } // Apply foreground and background styling to a border. -func styleBorder(border string, fg, bg TerminalColor) string { +func styleBorder(re *Renderer, border string, fg, bg TerminalColor) string { if fg == noColor && bg == noColor { return border } @@ -384,10 +384,10 @@ func styleBorder(border string, fg, bg TerminalColor) string { var style = termenv.Style{} if fg != noColor { - style = style.Foreground(ColorProfile().Color(fg.value())) + style = style.Foreground(re.color(fg)) } if bg != noColor { - style = style.Background(ColorProfile().Color(bg.value())) + style = style.Background(re.color(bg)) } return style.Styled(border) diff --git a/color.go b/color.go index ff0115c7..52ba7ecd 100644 --- a/color.go +++ b/color.go @@ -1,112 +1,13 @@ package lipgloss import ( - "sync" + "strconv" "github.com/muesli/termenv" ) -var ( - output *termenv.Output - colorProfile termenv.Profile - getColorProfile sync.Once - explicitColorProfile bool - - hasDarkBackground bool - getBackgroundColor sync.Once - explicitBackgroundColor bool - - colorProfileMtx sync.RWMutex -) - -// ColorProfile returns the detected termenv color profile. It will perform the -// actual check only once. -func ColorProfile() termenv.Profile { - colorProfileMtx.RLock() - defer colorProfileMtx.RUnlock() - - if !explicitColorProfile { - getColorProfile.Do(func() { - if output != nil { - colorProfile = output.EnvColorProfile() - } else { - colorProfile = termenv.EnvColorProfile() - } - }) - } - return colorProfile -} - -// SetColorProfile sets the color profile on a package-wide context. This -// function exists mostly for testing purposes so that you can assure you're -// testing against a specific profile. -// -// Outside of testing you likely won't want to use this function as -// ColorProfile() will detect and cache the terminal's color capabilities -// and choose the best available profile. -// -// Available color profiles are: -// -// termenv.Ascii (no color, 1-bit) -// termenv.ANSI (16 colors, 4-bit) -// termenv.ANSI256 (256 colors, 8-bit) -// termenv.TrueColor (16,777,216 colors, 24-bit) -// -// This function is thread-safe. -func SetColorProfile(p termenv.Profile) { - colorProfileMtx.Lock() - defer colorProfileMtx.Unlock() - - colorProfile = p - explicitColorProfile = true -} - -// SetOutput sets the output to use for adaptive color detection. -func SetOutput(o *termenv.Output) { - output = o -} - -// HasDarkBackground returns whether or not the terminal has a dark background. -func HasDarkBackground() bool { - colorProfileMtx.RLock() - defer colorProfileMtx.RUnlock() - - if !explicitBackgroundColor { - getBackgroundColor.Do(func() { - if output != nil { - hasDarkBackground = output.HasDarkBackground() - } else { - hasDarkBackground = termenv.HasDarkBackground() - } - }) - } - - return hasDarkBackground -} - -// SetHasDarkBackground sets the value of the background color detection on a -// package-wide context. This function exists mostly for testing purposes so -// that you can assure you're testing against a specific background color -// setting. -// -// Outside of testing you likely won't want to use this function as -// HasDarkBackground() will detect and cache the terminal's current background -// color setting. -// -// This function is thread-safe. -func SetHasDarkBackground(b bool) { - colorProfileMtx.Lock() - defer colorProfileMtx.Unlock() - - hasDarkBackground = b - explicitBackgroundColor = true -} - -// TerminalColor is a color intended to be rendered in the terminal. It -// satisfies the Go color.Color interface. +// TerminalColor is a color intended to be rendered in the terminal. type TerminalColor interface { - value() string - color() termenv.Color RGBA() (r, g, b, a uint32) } @@ -119,13 +20,7 @@ type TerminalColor interface { // var style = someStyle.Copy().Background(lipgloss.NoColor{}) type NoColor struct{} -func (n NoColor) value() string { - return "" -} - -func (n NoColor) color() termenv.Color { - return ColorProfile().Color("") -} +var noColor = NoColor{} // RGBA returns the RGBA value of this color. Because we have to return // something, despite this color being the absence of color, we're returning @@ -136,18 +31,12 @@ func (n NoColor) RGBA() (r, g, b, a uint32) { return 0x0, 0x0, 0x0, 0xFFFF } -var noColor = NoColor{} - // Color specifies a color by hex or ANSI value. For example: // // ansiColor := lipgloss.Color("21") // hexColor := lipgloss.Color("#0000ff") type Color string -func (c Color) value() string { - return string(c) -} - func (c Color) color() termenv.Color { return ColorProfile().Color(string(c)) } @@ -160,6 +49,26 @@ func (c Color) RGBA() (r, g, b, a uint32) { return termenv.ConvertToRGB(c.color()).RGBA() } +// ANSIColor is a color specified by an ANSI color value. It's merely syntactic +// sugar for the more general Color function. Invalid colors will render as +// black. +// +// Example usage: +// +// // These two statements are equivalent. +// colorA := lipgloss.ANSIColor(21) +// colorB := lipgloss.Color("21") +type ANSIColor uint + +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. Note that on error we return black with 100% opacity, or: +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +func (ac ANSIColor) RGBA() (r, g, b, a uint32) { + cf := Color(strconv.FormatUint(uint64(ac), 10)) + return cf.RGBA() +} + // AdaptiveColor provides color options for light and dark backgrounds. The // appropriate color will be returned at runtime based on the darkness of the // terminal background color. @@ -213,7 +122,7 @@ func (c CompleteColor) value() string { } func (c CompleteColor) color() termenv.Color { - return colorProfile.Color(c.value()) + return ColorProfile().Color(c.value()) } // RGBA returns the RGBA value of this color. This satisfies the Go Color diff --git a/color_test.go b/color_test.go index 065b34f8..748cf794 100644 --- a/color_test.go +++ b/color_test.go @@ -8,6 +8,7 @@ import ( ) func TestSetColorProfile(t *testing.T) { + r := renderer input := "hello" tt := []struct { @@ -39,9 +40,9 @@ func TestSetColorProfile(t *testing.T) { for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - SetColorProfile(tc.profile) + r.SetColorProfile(tc.profile) style := NewStyle().Foreground(Color("#5A56E0")) - res := style.Render(input) + res := Render(style, input) if res != tc.expected { t.Errorf("Expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", diff --git a/example/main.go b/example/main.go index 8ec832b8..49c01daa 100644 --- a/example/main.go +++ b/example/main.go @@ -29,13 +29,14 @@ var ( highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"} special = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"} - divider = lipgloss.NewStyle(). - SetString("•"). - Padding(0, 1). - Foreground(subtle). - String() + divider = lipgloss.Render( + lipgloss.NewStyle(). + Padding(0, 1). + Foreground(subtle), "•") - url = lipgloss.NewStyle().Foreground(special).Render + url = func(s string) string { + return lipgloss.Render(lipgloss.NewStyle().Foreground(special), s) + } // Tabs. @@ -122,14 +123,17 @@ var ( Height(8). Width(columnWidth + 1) - listHeader = lipgloss.NewStyle(). + listHeader = func(s string) string { + return lipgloss.Render(lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderBottom(true). BorderForeground(subtle). - MarginRight(2). - Render + MarginRight(2), s) + } - listItem = lipgloss.NewStyle().PaddingLeft(2).Render + listItem = func(s string) string { + return lipgloss.Render(lipgloss.NewStyle().PaddingLeft(2), s) + } checkMark = lipgloss.NewStyle().SetString("✓"). Foreground(special). @@ -137,10 +141,11 @@ var ( String() listDone = func(s string) string { - return checkMark + lipgloss.NewStyle(). - Strikethrough(true). - Foreground(lipgloss.AdaptiveColor{Light: "#969B86", Dark: "#696969"}). - Render(s) + return checkMark + lipgloss. + Render(lipgloss.NewStyle(). + Strikethrough(true). + Foreground(lipgloss.AdaptiveColor{Light: "#969B86", Dark: "#696969"}), + s) } // Paragraphs/History. @@ -192,13 +197,13 @@ func main() { { row := lipgloss.JoinHorizontal( lipgloss.Top, - activeTab.Render("Lip Gloss"), - tab.Render("Blush"), - tab.Render("Eye Shadow"), - tab.Render("Mascara"), - tab.Render("Foundation"), + lipgloss.Render(activeTab, "Lip Gloss"), + lipgloss.Render(tab, "Blush"), + lipgloss.Render(tab, "Eye Shadow"), + lipgloss.Render(tab, "Mascara"), + lipgloss.Render(tab, "Foundation"), ) - gap := tabGap.Render(strings.Repeat(" ", max(0, width-lipgloss.Width(row)-2))) + gap := lipgloss.Render(tabGap, strings.Repeat(" ", max(0, width-lipgloss.Width(row)-2))) row = lipgloss.JoinHorizontal(lipgloss.Bottom, row, gap) doc.WriteString(row + "\n\n") } @@ -220,8 +225,8 @@ func main() { } desc := lipgloss.JoinVertical(lipgloss.Left, - descStyle.Render("Style Definitions for Nice Terminal Layouts"), - infoStyle.Render("From Charm"+divider+url("https://github.com/charmbracelet/lipgloss")), + lipgloss.Render(descStyle, "Style Definitions for Nice Terminal Layouts"), + lipgloss.Render(infoStyle, "From Charm"+divider+url("https://github.com/charmbracelet/lipgloss")), ) row := lipgloss.JoinHorizontal(lipgloss.Top, title.String(), desc) @@ -230,16 +235,16 @@ func main() { // Dialog { - okButton := activeButtonStyle.Render("Yes") - cancelButton := buttonStyle.Render("Maybe") + okButton := lipgloss.Render(activeButtonStyle, "Yes") + cancelButton := lipgloss.Render(buttonStyle, "Maybe") - question := lipgloss.NewStyle().Width(50).Align(lipgloss.Center).Render("Are you sure you want to eat marmalade?") + question := lipgloss.Render(lipgloss.NewStyle().Width(50).Align(lipgloss.Center), "Are you sure you want to eat marmalade?") buttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton) ui := lipgloss.JoinVertical(lipgloss.Center, question, buttons) dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - dialogBoxStyle.Render(ui), + lipgloss.Render(dialogBoxStyle, ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), ) @@ -264,7 +269,7 @@ func main() { }() lists := lipgloss.JoinHorizontal(lipgloss.Top, - list.Render( + lipgloss.Render(list, lipgloss.JoinVertical(lipgloss.Left, listHeader("Citrus Fruits to Try"), listDone("Grapefruit"), @@ -274,7 +279,7 @@ func main() { listItem("Pomelo"), ), ), - list.Copy().Width(columnWidth).Render( + lipgloss.Render(list.Copy().Width(columnWidth), lipgloss.JoinVertical(lipgloss.Left, listHeader("Actual Lip Gloss Vendors"), listItem("Glossier"), @@ -298,9 +303,9 @@ func main() { doc.WriteString(lipgloss.JoinHorizontal( lipgloss.Top, - historyStyle.Copy().Align(lipgloss.Right).Render(historyA), - historyStyle.Copy().Align(lipgloss.Center).Render(historyB), - historyStyle.Copy().MarginRight(0).Render(historyC), + lipgloss.Render(historyStyle.Copy().Align(lipgloss.Right), historyA), + lipgloss.Render(historyStyle.Copy().Align(lipgloss.Center), historyB), + lipgloss.Render(historyStyle.Copy().MarginRight(0), historyC), )) doc.WriteString("\n\n") @@ -310,12 +315,13 @@ func main() { { w := lipgloss.Width - statusKey := statusStyle.Render("STATUS") - encoding := encodingStyle.Render("UTF-8") - fishCake := fishCakeStyle.Render("🍥 Fish Cake") - statusVal := statusText.Copy(). - Width(width - w(statusKey) - w(encoding) - w(fishCake)). - Render("Ravishing") + statusKey := lipgloss.Render(statusStyle, "STATUS") + encoding := lipgloss.Render(encodingStyle, "UTF-8") + fishCake := lipgloss.Render(fishCakeStyle, "🍥 Fish Cake") + statusVal := lipgloss.Render( + statusText.Copy(). + Width(width-w(statusKey)-w(encoding)-w(fishCake)), + "Ravishing") bar := lipgloss.JoinHorizontal(lipgloss.Top, statusKey, @@ -324,7 +330,7 @@ func main() { fishCake, ) - doc.WriteString(statusBarStyle.Width(width).Render(bar)) + doc.WriteString(lipgloss.Render(statusBarStyle.Width(width), bar)) } if physicalWidth > 0 { @@ -332,7 +338,7 @@ func main() { } // Okay, let's print it - fmt.Println(docStyle.Render(doc.String())) + fmt.Println(lipgloss.Render(docStyle, doc.String())) } func colorGrid(xSteps, ySteps int) [][]string { diff --git a/go.mod b/go.mod index c9f6cb79..9394ac3e 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,6 @@ go 1.15 require ( github.com/mattn/go-runewidth v0.0.14 - github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 + github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.14.0 ) diff --git a/go.sum b/go.sum index 99871bac..0db3c7c8 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,11 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= diff --git a/position.go b/position.go index f419452b..dd61ec81 100644 --- a/position.go +++ b/position.go @@ -48,11 +48,7 @@ func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOpti return str } - ws := &whitespace{} - for _, opt := range opts { - opt(ws) - } - + ws := NewWhitespace(opts...) var b strings.Builder for i, l := range lines { // Is this line shorter than the longest line? @@ -98,11 +94,7 @@ func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOptio return str } - ws := &whitespace{} - for _, opt := range opts { - opt(ws) - } - + ws := NewWhitespace(opts...) _, width := getLines(str) emptyLine := ws.render(width) b := strings.Builder{} diff --git a/renderer.go b/renderer.go new file mode 100644 index 00000000..dda39531 --- /dev/null +++ b/renderer.go @@ -0,0 +1,426 @@ +package lipgloss + +import ( + "fmt" + "strings" + "unicode" + + "github.com/muesli/reflow/truncate" + "github.com/muesli/reflow/wordwrap" + "github.com/muesli/reflow/wrap" + "github.com/muesli/termenv" +) + +var renderer = NewRenderer() + +// Renderer is a lipgloss terminal renderer. +type Renderer struct { + output *termenv.Output + hasDarkBackground bool +} + +// RendererOption is a function that can be used to configure a Renderer. +type RendererOption func(r *Renderer) + +// DefaultRenderer returns the default renderer. +func DefaultRenderer() *Renderer { + return renderer +} + +// NewRenderer creates a new Renderer. +func NewRenderer(options ...RendererOption) *Renderer { + r := &Renderer{} + for _, option := range options { + option(r) + } + if r.output == nil { + r.output = termenv.DefaultOutput() + } + if !r.hasDarkBackground { + r.hasDarkBackground = r.output.HasDarkBackground() + } + return r +} + +// WithOutput sets the termenv Output to use for rendering. +func WithOutput(output *termenv.Output) RendererOption { + return func(r *Renderer) { + r.output = output + } +} + +// WithDarkBackground forces the renderer to use a dark background. +func WithDarkBackground() RendererOption { + return func(r *Renderer) { + r.SetHasDarkBackground(true) + } +} + +// WithColorProfile sets the color profile on the renderer. This function is +// primarily intended for testing. For details, see the note on +// [Renderer.SetColorProfile]. +func WithColorProfile(p termenv.Profile) RendererOption { + return func(r *Renderer) { + r.SetColorProfile(p) + } +} + +// ColorProfile returns the detected termenv color profile. +func (r *Renderer) ColorProfile() termenv.Profile { + return r.output.Profile +} + +// ColorProfile returns the detected termenv color profile. +func ColorProfile() termenv.Profile { + return renderer.ColorProfile() +} + +// SetColorProfile sets the color profile on the renderer. This function exists +// mostly for testing purposes so that you can assure you're testing against +// a specific profile. +// +// Outside of testing you likely won't want to use this function as the color +// profile will detect and cache the terminal's color capabilities and choose +// the best available profile. +// +// Available color profiles are: +// +// termenv.Ascii (no color, 1-bit) +// termenv.ANSI (16 colors, 4-bit) +// termenv.ANSI256 (256 colors, 8-bit) +// termenv.TrueColor (16,777,216 colors, 24-bit) +// +// This function is thread-safe. +func (r *Renderer) SetColorProfile(p termenv.Profile) { + r.output.Profile = p +} + +// SetColorProfile sets the color profile on the default renderer. This +// function exists mostly for testing purposes so that you can assure you're +// testing against a specific profile. +// +// Outside of testing you likely won't want to use this function as the color +// profile will detect and cache the terminal's color capabilities and choose +// the best available profile. +// +// Available color profiles are: +// +// termenv.Ascii (no color, 1-bit) +// termenv.ANSI (16 colors, 4-bit) +// termenv.ANSI256 (256 colors, 8-bit) +// termenv.TrueColor (16,777,216 colors, 24-bit) +// +// This function is thread-safe. +func SetColorProfile(p termenv.Profile) { + renderer.SetColorProfile(p) +} + +// HasDarkBackground returns whether or not the terminal has a dark background. +func (r *Renderer) HasDarkBackground() bool { + return r.hasDarkBackground +} + +// HasDarkBackground returns whether or not the terminal has a dark background. +func HasDarkBackground() bool { + return renderer.HasDarkBackground() +} + +// SetHasDarkBackground sets the background color detection value on the +// renderer. This function exists mostly for testing purposes so that you can +// assure you're testing against a specific background color setting. +// +// Outside of testing you likely won't want to use this function as the +// backgrounds value will be automatically detected and cached against the +// terminal's current background color setting. +// +// This function is thread-safe. +func (r *Renderer) SetHasDarkBackground(b bool) { + r.hasDarkBackground = b +} + +// SetHasDarkBackground sets the background color detection value for the +// default renderer. This function exists mostly for testing purposes so that +// you can assure you're testing against a specific background color setting. +// +// Outside of testing you likely won't want to use this function as the +// backgrounds value will be automatically detected and cached against the +// terminal's current background color setting. +// +// This function is thread-safe. +func SetHasDarkBackground(b bool) { + renderer.SetHasDarkBackground(b) +} + +// Render formats a string according to the given style. +func (r *Renderer) Render(s Style, str string) string { + var ( + te = r.ColorProfile().String() + teSpace = r.ColorProfile().String() + teWhitespace = r.ColorProfile().String() + + bold = s.getAsBool(boldKey, false) + italic = s.getAsBool(italicKey, false) + underline = s.getAsBool(underlineKey, false) + strikethrough = s.getAsBool(strikethroughKey, false) + reverse = s.getAsBool(reverseKey, false) + blink = s.getAsBool(blinkKey, false) + faint = s.getAsBool(faintKey, false) + + fg = s.getAsColor(foregroundKey) + bg = s.getAsColor(backgroundKey) + + width = s.getAsInt(widthKey) + height = s.getAsInt(heightKey) + horizontalAlign = s.getAsPosition(alignHorizontalKey) + verticalAlign = s.getAsPosition(alignVerticalKey) + + topPadding = s.getAsInt(paddingTopKey) + rightPadding = s.getAsInt(paddingRightKey) + bottomPadding = s.getAsInt(paddingBottomKey) + leftPadding = s.getAsInt(paddingLeftKey) + + colorWhitespace = s.getAsBool(colorWhitespaceKey, true) + inline = s.getAsBool(inlineKey, false) + maxWidth = s.getAsInt(maxWidthKey) + maxHeight = s.getAsInt(maxHeightKey) + + underlineSpaces = underline && s.getAsBool(underlineSpacesKey, true) + strikethroughSpaces = strikethrough && s.getAsBool(strikethroughSpacesKey, true) + + // Do we need to style whitespace (padding and space outside + // paragraphs) separately? + styleWhitespace = reverse + + // Do we need to style spaces separately? + useSpaceStyler = underlineSpaces || strikethroughSpaces + ) + + if len(s.rules) == 0 { + return str + } + + // Enable support for ANSI on the legacy Windows cmd.exe console. This is a + // no-op on non-Windows systems and on Windows runs only once. + enableLegacyWindowsANSI() + + if bold { + te = te.Bold() + } + if italic { + te = te.Italic() + } + if underline { + te = te.Underline() + } + if reverse { + if reverse { + teWhitespace = teWhitespace.Reverse() + } + te = te.Reverse() + } + if blink { + te = te.Blink() + } + if faint { + te = te.Faint() + } + + if fg != noColor { + fgc := r.color(fg) + te = te.Foreground(fgc) + if styleWhitespace { + teWhitespace = teWhitespace.Foreground(fgc) + } + if useSpaceStyler { + teSpace = teSpace.Foreground(fgc) + } + } + + if bg != noColor { + bgc := r.color(bg) + te = te.Background(bgc) + if colorWhitespace { + teWhitespace = teWhitespace.Background(bgc) + } + if useSpaceStyler { + teSpace = teSpace.Background(bgc) + } + } + + if underline { + te = te.Underline() + } + if strikethrough { + te = te.CrossOut() + } + + if underlineSpaces { + teSpace = teSpace.Underline() + } + if strikethroughSpaces { + teSpace = teSpace.CrossOut() + } + + // Strip newlines in single line mode + if inline { + str = strings.ReplaceAll(str, "\n", "") + } + + // Word wrap + if !inline && width > 0 { + wrapAt := width - leftPadding - rightPadding + str = wordwrap.String(str, wrapAt) + str = wrap.String(str, wrapAt) // force-wrap long strings + } + + // Render core text + { + var b strings.Builder + + l := strings.Split(str, "\n") + for i := range l { + if useSpaceStyler { + // Look for spaces and apply a different styler + for _, r := range l[i] { + if unicode.IsSpace(r) { + b.WriteString(teSpace.Styled(string(r))) + continue + } + b.WriteString(te.Styled(string(r))) + } + } else { + b.WriteString(te.Styled(l[i])) + } + if i != len(l)-1 { + b.WriteRune('\n') + } + } + + str = b.String() + } + + // Padding + if !inline { + if leftPadding > 0 { + var st *termenv.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = padLeft(str, leftPadding, st) + } + + if rightPadding > 0 { + var st *termenv.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = padRight(str, rightPadding, st) + } + + if topPadding > 0 { + str = strings.Repeat("\n", topPadding) + str + } + + if bottomPadding > 0 { + str += strings.Repeat("\n", bottomPadding) + } + } + + // Height + if height > 0 { + str = alignTextVertical(str, verticalAlign, height, nil) + } + + // Set alignment. This will also pad short lines with spaces so that all + // lines are the same length, so we run it under a few different conditions + // beyond alignment. + { + numLines := strings.Count(str, "\n") + + if !(numLines == 0 && width == 0) { + var st *termenv.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = alignTextHorizontal(str, horizontalAlign, width, st) + } + } + + if !inline { + str = s.applyBorder(r, str) + str = s.applyMargins(r, str, inline) + } + + // Truncate according to MaxWidth + if maxWidth > 0 { + lines := strings.Split(str, "\n") + + for i := range lines { + lines[i] = truncate.String(lines[i], uint(maxWidth)) + } + + str = strings.Join(lines, "\n") + } + + // Truncate according to MaxHeight + if maxHeight > 0 { + lines := strings.Split(str, "\n") + str = strings.Join(lines[:min(maxHeight, len(lines))], "\n") + } + + return str +} + +// Render formats a string according to the given style using the default +// renderer. This is syntactic sugar for rendering with a DefaultRenderer. +func Render(s Style, str string) string { + return renderer.Render(s, str) +} + +func (r *Renderer) colorValue(c TerminalColor) string { + switch c := c.(type) { + case ANSIColor: + return fmt.Sprint(c) + case Color: + return string(c) + case AdaptiveColor: + if r.HasDarkBackground() { + return c.Dark + } + return c.Light + case CompleteColor: + switch r.ColorProfile() { + case termenv.TrueColor: + return c.TrueColor + case termenv.ANSI256: + return c.ANSI256 + case termenv.ANSI: + return c.ANSI + default: + return "" + } + case CompleteAdaptiveColor: + col := c.Light + if r.HasDarkBackground() { + col = c.Dark + } + switch r.ColorProfile() { + case termenv.TrueColor: + return col.TrueColor + case termenv.ANSI256: + return col.ANSI256 + case termenv.ANSI: + return col.ANSI + default: + return "" + } + + default: + return "" + } +} + +// color returns a termenv.color for the given TerminalColor. +func (r *Renderer) color(c TerminalColor) termenv.Color { + return r.ColorProfile().Color(r.colorValue(c)) +} diff --git a/style.go b/style.go index 02dbd191..95564ae7 100644 --- a/style.go +++ b/style.go @@ -2,11 +2,7 @@ package lipgloss import ( "strings" - "unicode" - "github.com/muesli/reflow/truncate" - "github.com/muesli/reflow/wordwrap" - "github.com/muesli/reflow/wrap" "github.com/muesli/termenv" ) @@ -106,6 +102,8 @@ func (s Style) Value() string { // String implements stringer for a Style, returning the rendered result based // on the rules in this style. An underlying string value must be set with // Style.SetString prior to using this method. +// +// Deprecated: Use Render(Style, string) instead. func (s Style) String() string { return s.Render(s.value) } @@ -153,226 +151,13 @@ func (s Style) Inherit(i Style) Style { } // Render applies the defined style formatting to a given string. +// +// Deprecated: Use Render(Style, string) instead. func (s Style) Render(str string) string { - var ( - te = ColorProfile().String() - teSpace = ColorProfile().String() - teWhitespace = ColorProfile().String() - - bold = s.getAsBool(boldKey, false) - italic = s.getAsBool(italicKey, false) - underline = s.getAsBool(underlineKey, false) - strikethrough = s.getAsBool(strikethroughKey, false) - reverse = s.getAsBool(reverseKey, false) - blink = s.getAsBool(blinkKey, false) - faint = s.getAsBool(faintKey, false) - - fg = s.getAsColor(foregroundKey) - bg = s.getAsColor(backgroundKey) - - width = s.getAsInt(widthKey) - height = s.getAsInt(heightKey) - horizontalAlign = s.getAsPosition(alignHorizontalKey) - verticalAlign = s.getAsPosition(alignVerticalKey) - - topPadding = s.getAsInt(paddingTopKey) - rightPadding = s.getAsInt(paddingRightKey) - bottomPadding = s.getAsInt(paddingBottomKey) - leftPadding = s.getAsInt(paddingLeftKey) - - colorWhitespace = s.getAsBool(colorWhitespaceKey, true) - inline = s.getAsBool(inlineKey, false) - maxWidth = s.getAsInt(maxWidthKey) - maxHeight = s.getAsInt(maxHeightKey) - - underlineSpaces = underline && s.getAsBool(underlineSpacesKey, true) - strikethroughSpaces = strikethrough && s.getAsBool(strikethroughSpacesKey, true) - - // Do we need to style whitespace (padding and space outside - // paragraphs) separately? - styleWhitespace = reverse - - // Do we need to style spaces separately? - useSpaceStyler = underlineSpaces || strikethroughSpaces - ) - - if len(s.rules) == 0 { - return str - } - - // Enable support for ANSI on the legacy Windows cmd.exe console. This is a - // no-op on non-Windows systems and on Windows runs only once. - enableLegacyWindowsANSI() - - if bold { - te = te.Bold() - } - if italic { - te = te.Italic() - } - if underline { - te = te.Underline() - } - if reverse { - if reverse { - teWhitespace = teWhitespace.Reverse() - } - te = te.Reverse() - } - if blink { - te = te.Blink() - } - if faint { - te = te.Faint() - } - - if fg != noColor { - fgc := fg.color() - te = te.Foreground(fgc) - if styleWhitespace { - teWhitespace = teWhitespace.Foreground(fgc) - } - if useSpaceStyler { - teSpace = teSpace.Foreground(fgc) - } - } - - if bg != noColor { - bgc := bg.color() - te = te.Background(bgc) - if colorWhitespace { - teWhitespace = teWhitespace.Background(bgc) - } - if useSpaceStyler { - teSpace = teSpace.Background(bgc) - } - } - - if underline { - te = te.Underline() - } - if strikethrough { - te = te.CrossOut() - } - - if underlineSpaces { - teSpace = teSpace.Underline() - } - if strikethroughSpaces { - teSpace = teSpace.CrossOut() - } - - // Strip newlines in single line mode - if inline { - str = strings.ReplaceAll(str, "\n", "") - } - - // Word wrap - if !inline && width > 0 { - wrapAt := width - leftPadding - rightPadding - str = wordwrap.String(str, wrapAt) - str = wrap.String(str, wrapAt) // force-wrap long strings - } - - // Render core text - { - var b strings.Builder - - l := strings.Split(str, "\n") - for i := range l { - if useSpaceStyler { - // Look for spaces and apply a different styler - for _, r := range l[i] { - if unicode.IsSpace(r) { - b.WriteString(teSpace.Styled(string(r))) - continue - } - b.WriteString(te.Styled(string(r))) - } - } else { - b.WriteString(te.Styled(l[i])) - } - if i != len(l)-1 { - b.WriteRune('\n') - } - } - - str = b.String() - } - - // Padding - if !inline { - if leftPadding > 0 { - var st *termenv.Style - if colorWhitespace || styleWhitespace { - st = &teWhitespace - } - str = padLeft(str, leftPadding, st) - } - - if rightPadding > 0 { - var st *termenv.Style - if colorWhitespace || styleWhitespace { - st = &teWhitespace - } - str = padRight(str, rightPadding, st) - } - - if topPadding > 0 { - str = strings.Repeat("\n", topPadding) + str - } - - if bottomPadding > 0 { - str += strings.Repeat("\n", bottomPadding) - } - } - - // Height - if height > 0 { - str = alignTextVertical(str, verticalAlign, height, nil) - } - - // Set alignment. This will also pad short lines with spaces so that all - // lines are the same length, so we run it under a few different conditions - // beyond alignment. - { - numLines := strings.Count(str, "\n") - - if !(numLines == 0 && width == 0) { - var st *termenv.Style - if colorWhitespace || styleWhitespace { - st = &teWhitespace - } - str = alignTextHorizontal(str, horizontalAlign, width, st) - } - } - - if !inline { - str = s.applyBorder(str) - str = s.applyMargins(str, inline) - } - - // Truncate according to MaxWidth - if maxWidth > 0 { - lines := strings.Split(str, "\n") - - for i := range lines { - lines[i] = truncate.String(lines[i], uint(maxWidth)) - } - - str = strings.Join(lines, "\n") - } - - // Truncate according to MaxHeight - if maxHeight > 0 { - lines := strings.Split(str, "\n") - str = strings.Join(lines[:min(maxHeight, len(lines))], "\n") - } - - return str + return renderer.Render(s, str) } -func (s Style) applyMargins(str string, inline bool) string { +func (s Style) applyMargins(re *Renderer, str string, inline bool) string { var ( topMargin = s.getAsInt(marginTopKey) rightMargin = s.getAsInt(marginRightKey) @@ -384,7 +169,7 @@ func (s Style) applyMargins(str string, inline bool) string { bgc := s.getAsColor(marginBackgroundKey) if bgc != noColor { - styler = styler.Background(bgc.color()) + styler = styler.Background(re.color(bgc)) } // Add left and right margin diff --git a/whitespace.go b/whitespace.go index 6c510a78..b63e45d0 100644 --- a/whitespace.go +++ b/whitespace.go @@ -7,14 +7,26 @@ import ( "github.com/muesli/termenv" ) -// whitespace is a whitespace renderer. -type whitespace struct { +// Whitespace is a whitespace renderer. +type Whitespace struct { + re *Renderer style termenv.Style chars string } +// NewWhitespace creates a new whitespace renderer. The order of the options +// matters, it you'r using WithWhitespaceRenderer, make sure it comes first as +// other options might depend on it. +func NewWhitespace(opts ...WhitespaceOption) *Whitespace { + w := &Whitespace{re: renderer} + for _, opt := range opts { + opt(w) + } + return w +} + // Render whitespaces. -func (w whitespace) render(width int) string { +func (w Whitespace) render(width int) string { if w.chars == "" { w.chars = " " } @@ -44,25 +56,32 @@ func (w whitespace) render(width int) string { } // WhitespaceOption sets a styling rule for rendering whitespace. -type WhitespaceOption func(*whitespace) +type WhitespaceOption func(*Whitespace) // WithWhitespaceForeground sets the color of the characters in the whitespace. func WithWhitespaceForeground(c TerminalColor) WhitespaceOption { - return func(w *whitespace) { - w.style = w.style.Foreground(c.color()) + return func(w *Whitespace) { + w.style = w.style.Foreground(w.re.color(c)) } } // WithWhitespaceBackground sets the background color of the whitespace. func WithWhitespaceBackground(c TerminalColor) WhitespaceOption { - return func(w *whitespace) { - w.style = w.style.Background(c.color()) + return func(w *Whitespace) { + w.style = w.style.Background(w.re.color(c)) } } // WithWhitespaceChars sets the characters to be rendered in the whitespace. func WithWhitespaceChars(s string) WhitespaceOption { - return func(w *whitespace) { + return func(w *Whitespace) { w.chars = s } } + +// WithWhitespaceRenderer sets the lipgloss renderer to be used for rendering. +func WithWhitespaceRenderer(r *Renderer) WhitespaceOption { + return func(w *Whitespace) { + w.re = r + } +} From b9c2626fe71863967282a96487018827117c305f Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 3 Oct 2022 13:11:44 -0400 Subject: [PATCH 034/126] feat(renderer): use style renderer lipgloss.Style now takes a renderer instance to be used to detect styles and colors based on its termenv.Output and terminal --- borders.go | 16 +-- color.go | 95 +++++--------- color_test.go | 29 +++-- example/go.sum | 6 +- example/main.go | 92 +++++++------ get.go | 4 +- renderer.go | 332 ++++------------------------------------------- renderer_test.go | 33 +++++ style.go | 260 ++++++++++++++++++++++++++++++++++--- style_test.go | 46 ++++++- whitespace.go | 18 +-- 11 files changed, 457 insertions(+), 474 deletions(-) create mode 100644 renderer_test.go diff --git a/borders.go b/borders.go index f6dd008a..18964221 100644 --- a/borders.go +++ b/borders.go @@ -196,7 +196,7 @@ func HiddenBorder() Border { return hiddenBorder } -func (s Style) applyBorder(re *Renderer, str string) string { +func (s Style) applyBorder(str string) string { var ( topSet = s.isSet(borderTopKey) rightSet = s.isSet(borderRightKey) @@ -298,7 +298,7 @@ func (s Style) applyBorder(re *Renderer, str string) string { // Render top if hasTop { top := renderHorizontalEdge(border.TopLeft, border.Top, border.TopRight, width) - top = styleBorder(re, top, topFG, topBG) + top = s.styleBorder(top, topFG, topBG) out.WriteString(top) out.WriteRune('\n') } @@ -317,7 +317,7 @@ func (s Style) applyBorder(re *Renderer, str string) string { if leftIndex >= len(leftRunes) { leftIndex = 0 } - out.WriteString(styleBorder(re, r, leftFG, leftBG)) + out.WriteString(s.styleBorder(r, leftFG, leftBG)) } out.WriteString(l) if hasRight { @@ -326,7 +326,7 @@ func (s Style) applyBorder(re *Renderer, str string) string { if rightIndex >= len(rightRunes) { rightIndex = 0 } - out.WriteString(styleBorder(re, r, rightFG, rightBG)) + out.WriteString(s.styleBorder(r, rightFG, rightBG)) } if i < len(lines)-1 { out.WriteRune('\n') @@ -336,7 +336,7 @@ func (s Style) applyBorder(re *Renderer, str string) string { // Render bottom if hasBottom { bottom := renderHorizontalEdge(border.BottomLeft, border.Bottom, border.BottomRight, width) - bottom = styleBorder(re, bottom, bottomFG, bottomBG) + bottom = s.styleBorder(bottom, bottomFG, bottomBG) out.WriteRune('\n') out.WriteString(bottom) } @@ -376,7 +376,7 @@ func renderHorizontalEdge(left, middle, right string, width int) string { } // Apply foreground and background styling to a border. -func styleBorder(re *Renderer, border string, fg, bg TerminalColor) string { +func (s Style) styleBorder(border string, fg, bg TerminalColor) string { if fg == noColor && bg == noColor { return border } @@ -384,10 +384,10 @@ func styleBorder(re *Renderer, border string, fg, bg TerminalColor) string { var style = termenv.Style{} if fg != noColor { - style = style.Foreground(re.color(fg)) + style = style.Foreground(fg.color(s.r)) } if bg != noColor { - style = style.Background(re.color(bg)) + style = style.Background(bg.color(s.r)) } return style.Styled(border) diff --git a/color.go b/color.go index 52ba7ecd..208d63b8 100644 --- a/color.go +++ b/color.go @@ -1,16 +1,18 @@ package lipgloss import ( - "strconv" + "fmt" "github.com/muesli/termenv" ) // TerminalColor is a color intended to be rendered in the terminal. type TerminalColor interface { - RGBA() (r, g, b, a uint32) + color(*Renderer) termenv.Color } +var noColor = NoColor{} + // NoColor is used to specify the absence of color styling. When this is active // foreground colors will be rendered with the terminal's default text color, // and background colors will not be drawn at all. @@ -20,15 +22,8 @@ type TerminalColor interface { // var style = someStyle.Copy().Background(lipgloss.NoColor{}) type NoColor struct{} -var noColor = NoColor{} - -// RGBA returns the RGBA value of this color. Because we have to return -// something, despite this color being the absence of color, we're returning -// black with 100% opacity. -// -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. -func (n NoColor) RGBA() (r, g, b, a uint32) { - return 0x0, 0x0, 0x0, 0xFFFF +func (NoColor) color(*Renderer) termenv.Color { + return termenv.NoColor{} } // Color specifies a color by hex or ANSI value. For example: @@ -37,16 +32,8 @@ func (n NoColor) RGBA() (r, g, b, a uint32) { // hexColor := lipgloss.Color("#0000ff") type Color string -func (c Color) color() termenv.Color { - return ColorProfile().Color(string(c)) -} - -// RGBA returns the RGBA value of this color. This satisfies the Go Color -// interface. Note that on error we return black with 100% opacity, or: -// -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. -func (c Color) RGBA() (r, g, b, a uint32) { - return termenv.ConvertToRGB(c.color()).RGBA() +func (c Color) color(r *Renderer) termenv.Color { + return r.ColorProfile().Color(string(c)) } // ANSIColor is a color specified by an ANSI color value. It's merely syntactic @@ -60,13 +47,8 @@ func (c Color) RGBA() (r, g, b, a uint32) { // colorB := lipgloss.Color("21") type ANSIColor uint -// RGBA returns the RGBA value of this color. This satisfies the Go Color -// interface. Note that on error we return black with 100% opacity, or: -// -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. -func (ac ANSIColor) RGBA() (r, g, b, a uint32) { - cf := Color(strconv.FormatUint(uint64(ac), 10)) - return cf.RGBA() +func (ac ANSIColor) color(r *Renderer) termenv.Color { + return Color(fmt.Sprintf("%d", ac)).color(r) } // AdaptiveColor provides color options for light and dark backgrounds. The @@ -81,23 +63,11 @@ type AdaptiveColor struct { Dark string } -func (ac AdaptiveColor) value() string { - if HasDarkBackground() { - return ac.Dark +func (ac AdaptiveColor) color(r *Renderer) termenv.Color { + if r.HasDarkBackground() { + return Color(ac.Dark).color(r) } - return ac.Light -} - -func (ac AdaptiveColor) color() termenv.Color { - return ColorProfile().Color(ac.value()) -} - -// RGBA returns the RGBA value of this color. This satisfies the Go Color -// interface. Note that on error we return black with 100% opacity, or: -// -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. -func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { - return termenv.ConvertToRGB(ac.color()).RGBA() + return Color(ac.Light).color(r) } // CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color @@ -108,19 +78,21 @@ type CompleteColor struct { ANSI string } -func (c CompleteColor) value() string { - switch ColorProfile() { +func (c CompleteColor) color(r *Renderer) termenv.Color { + p := r.ColorProfile() + switch p { case termenv.TrueColor: - return c.TrueColor + return p.Color(c.TrueColor) case termenv.ANSI256: - return c.ANSI256 + return p.Color(c.ANSI256) case termenv.ANSI: - return c.ANSI + return p.Color(c.ANSI) default: - return "" + return termenv.NoColor{} } } +<<<<<<< HEAD func (c CompleteColor) color() termenv.Color { return ColorProfile().Color(c.value()) } @@ -134,6 +106,9 @@ func (c CompleteColor) RGBA() (r, g, b, a uint32) { } // CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color +======= +// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color +>>>>>>> f22900b20f84 (feat(renderer): use style renderer) // profiles, with separate options for light and dark backgrounds. Automatic // color degradation will not be performed. type CompleteAdaptiveColor struct { @@ -141,21 +116,9 @@ type CompleteAdaptiveColor struct { Dark CompleteColor } -func (cac CompleteAdaptiveColor) value() string { - if HasDarkBackground() { - return cac.Dark.value() +func (cac CompleteAdaptiveColor) color(r *Renderer) termenv.Color { + if r.HasDarkBackground() { + return cac.Dark.color(r) } - return cac.Light.value() -} - -func (cac CompleteAdaptiveColor) color() termenv.Color { - return ColorProfile().Color(cac.value()) -} - -// RGBA returns the RGBA value of this color. This satisfies the Go Color -// interface. Note that on error we return black with 100% opacity, or: -// -// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. -func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) { - return termenv.ConvertToRGB(cac.color()).RGBA() + return cac.Light.color(r) } diff --git a/color_test.go b/color_test.go index 748cf794..e109d3b4 100644 --- a/color_test.go +++ b/color_test.go @@ -42,7 +42,7 @@ func TestSetColorProfile(t *testing.T) { t.Run(tc.name, func(t *testing.T) { r.SetColorProfile(tc.profile) style := NewStyle().Foreground(Color("#5A56E0")) - res := Render(style, input) + res := style.Render(input) if res != tc.expected { t.Errorf("Expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", @@ -117,25 +117,25 @@ func TestRGBA(t *testing.T) { { termenv.TrueColor, true, - AdaptiveColor{Dark: "#FF0000", Light: "#0000FF"}, + AdaptiveColor{Light: "#0000FF", Dark: "#FF0000"}, 0xFF0000, }, { termenv.TrueColor, false, - AdaptiveColor{Dark: "#FF0000", Light: "#0000FF"}, + AdaptiveColor{Light: "#0000FF", Dark: "#FF0000"}, 0x0000FF, }, { termenv.TrueColor, true, - AdaptiveColor{Dark: "9", Light: "21"}, + AdaptiveColor{Light: "21", Dark: "9"}, 0xFF0000, }, { termenv.TrueColor, false, - AdaptiveColor{Dark: "9", Light: "21"}, + AdaptiveColor{Light: "21", Dark: "9"}, 0x0000FF, }, // lipgloss.CompleteColor @@ -163,8 +163,8 @@ func TestRGBA(t *testing.T) { termenv.TrueColor, true, CompleteAdaptiveColor{ - Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, Light: CompleteColor{TrueColor: "#0000FF", ANSI256: "231", ANSI: "12"}, + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, }, 0xFF0000, }, @@ -172,8 +172,8 @@ func TestRGBA(t *testing.T) { termenv.ANSI256, true, CompleteAdaptiveColor{ - Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "21", ANSI: "12"}, + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, }, 0xFFFFFF, }, @@ -181,8 +181,8 @@ func TestRGBA(t *testing.T) { termenv.ANSI, true, CompleteAdaptiveColor{ - Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "9"}, + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, }, 0x0000FF, }, @@ -191,8 +191,8 @@ func TestRGBA(t *testing.T) { termenv.TrueColor, false, CompleteAdaptiveColor{ - Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, Light: CompleteColor{TrueColor: "#0000FF", ANSI256: "231", ANSI: "12"}, + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, }, 0x0000FF, }, @@ -200,8 +200,8 @@ func TestRGBA(t *testing.T) { termenv.ANSI256, false, CompleteAdaptiveColor{ - Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "21", ANSI: "12"}, + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, }, 0x0000FF, }, @@ -209,18 +209,19 @@ func TestRGBA(t *testing.T) { termenv.ANSI, false, CompleteAdaptiveColor{ - Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, Light: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "9"}, + Dark: CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, }, 0xFF0000, }, } + r := DefaultRenderer() for i, tc := range tt { - SetColorProfile(tc.profile) - SetHasDarkBackground(tc.darkBg) + r.SetColorProfile(tc.profile) + r.SetHasDarkBackground(tc.darkBg) - r, g, b, _ := tc.input.RGBA() + r, g, b, _ := termenv.ConvertToRGB(tc.input.color(r)).RGBA() o := uint(r/256)<<16 + uint(g/256)<<8 + uint(b/256) if o != tc.expected { diff --git a/example/go.sum b/example/go.sum index 66cf1ba0..366b13ed 100644 --- a/example/go.sum +++ b/example/go.sum @@ -7,11 +7,11 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69 github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk= -github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= diff --git a/example/main.go b/example/main.go index 49c01daa..c996dabf 100644 --- a/example/main.go +++ b/example/main.go @@ -29,13 +29,12 @@ var ( highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"} special = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"} - divider = lipgloss.Render( - lipgloss.NewStyle(). - Padding(0, 1). - Foreground(subtle), "•") + divider = lipgloss.NewStyle(). + Padding(0, 1). + Foreground(subtle).Render("•") url = func(s string) string { - return lipgloss.Render(lipgloss.NewStyle().Foreground(special), s) + return lipgloss.NewStyle().Foreground(special).Render(s) } // Tabs. @@ -124,15 +123,15 @@ var ( Width(columnWidth + 1) listHeader = func(s string) string { - return lipgloss.Render(lipgloss.NewStyle(). + return lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderBottom(true). BorderForeground(subtle). - MarginRight(2), s) + MarginRight(2).Render(s) } listItem = func(s string) string { - return lipgloss.Render(lipgloss.NewStyle().PaddingLeft(2), s) + return lipgloss.NewStyle().PaddingLeft(2).Render(s) } checkMark = lipgloss.NewStyle().SetString("✓"). @@ -141,11 +140,10 @@ var ( String() listDone = func(s string) string { - return checkMark + lipgloss. - Render(lipgloss.NewStyle(). - Strikethrough(true). - Foreground(lipgloss.AdaptiveColor{Light: "#969B86", Dark: "#696969"}), - s) + return checkMark + lipgloss.NewStyle(). + Strikethrough(true). + Foreground(lipgloss.AdaptiveColor{Light: "#969B86", Dark: "#696969"}). + Render(s) } // Paragraphs/History. @@ -197,13 +195,13 @@ func main() { { row := lipgloss.JoinHorizontal( lipgloss.Top, - lipgloss.Render(activeTab, "Lip Gloss"), - lipgloss.Render(tab, "Blush"), - lipgloss.Render(tab, "Eye Shadow"), - lipgloss.Render(tab, "Mascara"), - lipgloss.Render(tab, "Foundation"), + activeTab.Render("Lip Gloss"), + tab.Render("Blush"), + tab.Render("Eye Shadow"), + tab.Render("Mascara"), + tab.Render("Foundation"), ) - gap := lipgloss.Render(tabGap, strings.Repeat(" ", max(0, width-lipgloss.Width(row)-2))) + gap := tabGap.Render(strings.Repeat(" ", max(0, width-lipgloss.Width(row)-2))) row = lipgloss.JoinHorizontal(lipgloss.Bottom, row, gap) doc.WriteString(row + "\n\n") } @@ -225,8 +223,8 @@ func main() { } desc := lipgloss.JoinVertical(lipgloss.Left, - lipgloss.Render(descStyle, "Style Definitions for Nice Terminal Layouts"), - lipgloss.Render(infoStyle, "From Charm"+divider+url("https://github.com/charmbracelet/lipgloss")), + descStyle.Render("Style Definitions for Nice Terminal Layouts"), + infoStyle.Render("From Charm"+divider+url("https://github.com/charmbracelet/lipgloss")), ) row := lipgloss.JoinHorizontal(lipgloss.Top, title.String(), desc) @@ -235,16 +233,16 @@ func main() { // Dialog { - okButton := lipgloss.Render(activeButtonStyle, "Yes") - cancelButton := lipgloss.Render(buttonStyle, "Maybe") + okButton := activeButtonStyle.Render("Yes") + cancelButton := buttonStyle.Render("Maybe") - question := lipgloss.Render(lipgloss.NewStyle().Width(50).Align(lipgloss.Center), "Are you sure you want to eat marmalade?") + question := lipgloss.NewStyle().Width(50).Align(lipgloss.Center).Render("Are you sure you want to eat marmalade?") buttons := lipgloss.JoinHorizontal(lipgloss.Top, okButton, cancelButton) ui := lipgloss.JoinVertical(lipgloss.Center, question, buttons) dialog := lipgloss.Place(width, 9, lipgloss.Center, lipgloss.Center, - lipgloss.Render(dialogBoxStyle, ui), + dialogBoxStyle.Render(ui), lipgloss.WithWhitespaceChars("猫咪"), lipgloss.WithWhitespaceForeground(subtle), ) @@ -269,17 +267,16 @@ func main() { }() lists := lipgloss.JoinHorizontal(lipgloss.Top, - lipgloss.Render(list, - lipgloss.JoinVertical(lipgloss.Left, - listHeader("Citrus Fruits to Try"), - listDone("Grapefruit"), - listDone("Yuzu"), - listItem("Citron"), - listItem("Kumquat"), - listItem("Pomelo"), - ), + list.Render(lipgloss.JoinVertical(lipgloss.Left, + listHeader("Citrus Fruits to Try"), + listDone("Grapefruit"), + listDone("Yuzu"), + listItem("Citron"), + listItem("Kumquat"), + listItem("Pomelo"), ), - lipgloss.Render(list.Copy().Width(columnWidth), + ), + list.Copy().Width(columnWidth).Render( lipgloss.JoinVertical(lipgloss.Left, listHeader("Actual Lip Gloss Vendors"), listItem("Glossier"), @@ -303,9 +300,9 @@ func main() { doc.WriteString(lipgloss.JoinHorizontal( lipgloss.Top, - lipgloss.Render(historyStyle.Copy().Align(lipgloss.Right), historyA), - lipgloss.Render(historyStyle.Copy().Align(lipgloss.Center), historyB), - lipgloss.Render(historyStyle.Copy().MarginRight(0), historyC), + historyStyle.Copy().Align(lipgloss.Right).Render(historyA), + historyStyle.Copy().Align(lipgloss.Center).Render(historyB), + historyStyle.Copy().MarginRight(0).Render(historyC), )) doc.WriteString("\n\n") @@ -315,13 +312,12 @@ func main() { { w := lipgloss.Width - statusKey := lipgloss.Render(statusStyle, "STATUS") - encoding := lipgloss.Render(encodingStyle, "UTF-8") - fishCake := lipgloss.Render(fishCakeStyle, "🍥 Fish Cake") - statusVal := lipgloss.Render( - statusText.Copy(). - Width(width-w(statusKey)-w(encoding)-w(fishCake)), - "Ravishing") + statusKey := statusStyle.Render("STATUS") + encoding := encodingStyle.Render("UTF-8") + fishCake := fishCakeStyle.Render("🍥 Fish Cake") + statusVal := statusText.Copy(). + Width(width - w(statusKey) - w(encoding) - w(fishCake)). + Render("Ravishing") bar := lipgloss.JoinHorizontal(lipgloss.Top, statusKey, @@ -330,7 +326,7 @@ func main() { fishCake, ) - doc.WriteString(lipgloss.Render(statusBarStyle.Width(width), bar)) + doc.WriteString(statusBarStyle.Width(width).Render(bar)) } if physicalWidth > 0 { @@ -338,8 +334,8 @@ func main() { } // Okay, let's print it - fmt.Println(lipgloss.Render(docStyle, doc.String())) -} + fmt.Println(docStyle.Render(doc.String())) +} // func colorGrid(xSteps, ySteps int) [][]string { x0y0, _ := colorful.Hex("#F25D94") diff --git a/get.go b/get.go index 856d1f61..159792a6 100644 --- a/get.go +++ b/get.go @@ -415,12 +415,12 @@ func (s Style) getAsBool(k propKey, defaultVal bool) bool { func (s Style) getAsColor(k propKey) TerminalColor { v, ok := s.rules[k] if !ok { - return NoColor{} + return noColor } if c, ok := v.(TerminalColor); ok { return c } - return NoColor{} + return noColor } func (s Style) getAsInt(k propKey) int { diff --git a/renderer.go b/renderer.go index dda39531..04f3118e 100644 --- a/renderer.go +++ b/renderer.go @@ -1,13 +1,6 @@ package lipgloss import ( - "fmt" - "strings" - "unicode" - - "github.com/muesli/reflow/truncate" - "github.com/muesli/reflow/wordwrap" - "github.com/muesli/reflow/wrap" "github.com/muesli/termenv" ) @@ -16,7 +9,7 @@ var renderer = NewRenderer() // Renderer is a lipgloss terminal renderer. type Renderer struct { output *termenv.Output - hasDarkBackground bool + hasDarkBackground *bool } // RendererOption is a function that can be used to configure a Renderer. @@ -28,17 +21,13 @@ func DefaultRenderer() *Renderer { } // NewRenderer creates a new Renderer. -func NewRenderer(options ...RendererOption) *Renderer { - r := &Renderer{} +func NewRenderer(options ...func(r *Renderer)) *Renderer { + r := &Renderer{ + output: termenv.DefaultOutput(), + } for _, option := range options { option(r) } - if r.output == nil { - r.output = termenv.DefaultOutput() - } - if !r.hasDarkBackground { - r.hasDarkBackground = r.output.HasDarkBackground() - } return r } @@ -49,10 +38,10 @@ func WithOutput(output *termenv.Output) RendererOption { } } -// WithDarkBackground forces the renderer to use a dark background. -func WithDarkBackground() RendererOption { +// WithDarkBackground can force the renderer to use a light/dark background. +func WithDarkBackground(dark bool) RendererOption { return func(r *Renderer) { - r.SetHasDarkBackground(true) + r.SetHasDarkBackground(dark) } } @@ -115,27 +104,17 @@ func SetColorProfile(p termenv.Profile) { renderer.SetColorProfile(p) } -// HasDarkBackground returns whether or not the terminal has a dark background. -func (r *Renderer) HasDarkBackground() bool { - return r.hasDarkBackground -} - // HasDarkBackground returns whether or not the terminal has a dark background. func HasDarkBackground() bool { return renderer.HasDarkBackground() } -// SetHasDarkBackground sets the background color detection value on the -// renderer. This function exists mostly for testing purposes so that you can -// assure you're testing against a specific background color setting. -// -// Outside of testing you likely won't want to use this function as the -// backgrounds value will be automatically detected and cached against the -// terminal's current background color setting. -// -// This function is thread-safe. -func (r *Renderer) SetHasDarkBackground(b bool) { - r.hasDarkBackground = b +// HasDarkBackground returns whether or not the terminal has a dark background. +func (r *Renderer) HasDarkBackground() bool { + if r.hasDarkBackground != nil { + return *r.hasDarkBackground + } + return r.output.HasDarkBackground() } // SetHasDarkBackground sets the background color detection value for the @@ -151,276 +130,15 @@ func SetHasDarkBackground(b bool) { renderer.SetHasDarkBackground(b) } -// Render formats a string according to the given style. -func (r *Renderer) Render(s Style, str string) string { - var ( - te = r.ColorProfile().String() - teSpace = r.ColorProfile().String() - teWhitespace = r.ColorProfile().String() - - bold = s.getAsBool(boldKey, false) - italic = s.getAsBool(italicKey, false) - underline = s.getAsBool(underlineKey, false) - strikethrough = s.getAsBool(strikethroughKey, false) - reverse = s.getAsBool(reverseKey, false) - blink = s.getAsBool(blinkKey, false) - faint = s.getAsBool(faintKey, false) - - fg = s.getAsColor(foregroundKey) - bg = s.getAsColor(backgroundKey) - - width = s.getAsInt(widthKey) - height = s.getAsInt(heightKey) - horizontalAlign = s.getAsPosition(alignHorizontalKey) - verticalAlign = s.getAsPosition(alignVerticalKey) - - topPadding = s.getAsInt(paddingTopKey) - rightPadding = s.getAsInt(paddingRightKey) - bottomPadding = s.getAsInt(paddingBottomKey) - leftPadding = s.getAsInt(paddingLeftKey) - - colorWhitespace = s.getAsBool(colorWhitespaceKey, true) - inline = s.getAsBool(inlineKey, false) - maxWidth = s.getAsInt(maxWidthKey) - maxHeight = s.getAsInt(maxHeightKey) - - underlineSpaces = underline && s.getAsBool(underlineSpacesKey, true) - strikethroughSpaces = strikethrough && s.getAsBool(strikethroughSpacesKey, true) - - // Do we need to style whitespace (padding and space outside - // paragraphs) separately? - styleWhitespace = reverse - - // Do we need to style spaces separately? - useSpaceStyler = underlineSpaces || strikethroughSpaces - ) - - if len(s.rules) == 0 { - return str - } - - // Enable support for ANSI on the legacy Windows cmd.exe console. This is a - // no-op on non-Windows systems and on Windows runs only once. - enableLegacyWindowsANSI() - - if bold { - te = te.Bold() - } - if italic { - te = te.Italic() - } - if underline { - te = te.Underline() - } - if reverse { - if reverse { - teWhitespace = teWhitespace.Reverse() - } - te = te.Reverse() - } - if blink { - te = te.Blink() - } - if faint { - te = te.Faint() - } - - if fg != noColor { - fgc := r.color(fg) - te = te.Foreground(fgc) - if styleWhitespace { - teWhitespace = teWhitespace.Foreground(fgc) - } - if useSpaceStyler { - teSpace = teSpace.Foreground(fgc) - } - } - - if bg != noColor { - bgc := r.color(bg) - te = te.Background(bgc) - if colorWhitespace { - teWhitespace = teWhitespace.Background(bgc) - } - if useSpaceStyler { - teSpace = teSpace.Background(bgc) - } - } - - if underline { - te = te.Underline() - } - if strikethrough { - te = te.CrossOut() - } - - if underlineSpaces { - teSpace = teSpace.Underline() - } - if strikethroughSpaces { - teSpace = teSpace.CrossOut() - } - - // Strip newlines in single line mode - if inline { - str = strings.ReplaceAll(str, "\n", "") - } - - // Word wrap - if !inline && width > 0 { - wrapAt := width - leftPadding - rightPadding - str = wordwrap.String(str, wrapAt) - str = wrap.String(str, wrapAt) // force-wrap long strings - } - - // Render core text - { - var b strings.Builder - - l := strings.Split(str, "\n") - for i := range l { - if useSpaceStyler { - // Look for spaces and apply a different styler - for _, r := range l[i] { - if unicode.IsSpace(r) { - b.WriteString(teSpace.Styled(string(r))) - continue - } - b.WriteString(te.Styled(string(r))) - } - } else { - b.WriteString(te.Styled(l[i])) - } - if i != len(l)-1 { - b.WriteRune('\n') - } - } - - str = b.String() - } - - // Padding - if !inline { - if leftPadding > 0 { - var st *termenv.Style - if colorWhitespace || styleWhitespace { - st = &teWhitespace - } - str = padLeft(str, leftPadding, st) - } - - if rightPadding > 0 { - var st *termenv.Style - if colorWhitespace || styleWhitespace { - st = &teWhitespace - } - str = padRight(str, rightPadding, st) - } - - if topPadding > 0 { - str = strings.Repeat("\n", topPadding) + str - } - - if bottomPadding > 0 { - str += strings.Repeat("\n", bottomPadding) - } - } - - // Height - if height > 0 { - str = alignTextVertical(str, verticalAlign, height, nil) - } - - // Set alignment. This will also pad short lines with spaces so that all - // lines are the same length, so we run it under a few different conditions - // beyond alignment. - { - numLines := strings.Count(str, "\n") - - if !(numLines == 0 && width == 0) { - var st *termenv.Style - if colorWhitespace || styleWhitespace { - st = &teWhitespace - } - str = alignTextHorizontal(str, horizontalAlign, width, st) - } - } - - if !inline { - str = s.applyBorder(r, str) - str = s.applyMargins(r, str, inline) - } - - // Truncate according to MaxWidth - if maxWidth > 0 { - lines := strings.Split(str, "\n") - - for i := range lines { - lines[i] = truncate.String(lines[i], uint(maxWidth)) - } - - str = strings.Join(lines, "\n") - } - - // Truncate according to MaxHeight - if maxHeight > 0 { - lines := strings.Split(str, "\n") - str = strings.Join(lines[:min(maxHeight, len(lines))], "\n") - } - - return str -} - -// Render formats a string according to the given style using the default -// renderer. This is syntactic sugar for rendering with a DefaultRenderer. -func Render(s Style, str string) string { - return renderer.Render(s, str) -} - -func (r *Renderer) colorValue(c TerminalColor) string { - switch c := c.(type) { - case ANSIColor: - return fmt.Sprint(c) - case Color: - return string(c) - case AdaptiveColor: - if r.HasDarkBackground() { - return c.Dark - } - return c.Light - case CompleteColor: - switch r.ColorProfile() { - case termenv.TrueColor: - return c.TrueColor - case termenv.ANSI256: - return c.ANSI256 - case termenv.ANSI: - return c.ANSI - default: - return "" - } - case CompleteAdaptiveColor: - col := c.Light - if r.HasDarkBackground() { - col = c.Dark - } - switch r.ColorProfile() { - case termenv.TrueColor: - return col.TrueColor - case termenv.ANSI256: - return col.ANSI256 - case termenv.ANSI: - return col.ANSI - default: - return "" - } - - default: - return "" - } -} - -// color returns a termenv.color for the given TerminalColor. -func (r *Renderer) color(c TerminalColor) termenv.Color { - return r.ColorProfile().Color(r.colorValue(c)) +// SetHasDarkBackground sets the background color detection value on the +// renderer. This function exists mostly for testing purposes so that you can +// assure you're testing against a specific background color setting. +// +// Outside of testing you likely won't want to use this function as the +// backgrounds value will be automatically detected and cached against the +// terminal's current background color setting. +// +// This function is thread-safe. +func (r *Renderer) SetHasDarkBackground(b bool) { + r.hasDarkBackground = &b } diff --git a/renderer_test.go b/renderer_test.go new file mode 100644 index 00000000..fed38fce --- /dev/null +++ b/renderer_test.go @@ -0,0 +1,33 @@ +package lipgloss + +import ( + "os" + "testing" + + "github.com/muesli/termenv" +) + +func TestRendererHasDarkBackground(t *testing.T) { + r1 := NewRenderer(WithDarkBackground(false)) + if r1.HasDarkBackground() { + t.Error("Expected renderer to have light background") + } + r2 := NewRenderer(WithDarkBackground(true)) + if !r2.HasDarkBackground() { + t.Error("Expected renderer to have dark background") + } +} + +func TestRendererWithOutput(t *testing.T) { + f, err := os.Create(t.Name()) + if err != nil { + t.Fatal(err) + } + defer f.Close() + defer os.Remove(f.Name()) + output := termenv.NewOutput(f, termenv.WithProfile(termenv.TrueColor)) + r := NewRenderer(WithOutput(output)) + if r.output.Profile != termenv.TrueColor { + t.Error("Expected renderer to use true color") + } +} diff --git a/style.go b/style.go index 95564ae7..25da8936 100644 --- a/style.go +++ b/style.go @@ -2,7 +2,11 @@ package lipgloss import ( "strings" + "unicode" + "github.com/muesli/reflow/truncate" + "github.com/muesli/reflow/wordwrap" + "github.com/muesli/reflow/wrap" "github.com/muesli/termenv" ) @@ -71,15 +75,25 @@ const ( // A set of properties. type rules map[propKey]interface{} -// NewStyle returns a new, empty Style. While it's syntactic sugar for the +// NewStyle returns a new, empty Style. While it's syntactic sugar for the // Style{} primitive, it's recommended to use this function for creating styles -// in case the underlying implementation changes. -func NewStyle() Style { - return Style{} +// in case the underlying implementation changes. It takes an optional string +// value to be set as the underlying string value for this style. +func NewStyle(strs ...string) Style { + return renderer.NewStyle(strs...) +} + +// NewStyle returns a new, empty Style. While it's syntactic sugar for the +// Style{} primitive, it's recommended to use this function for creating styles +// in case the underlying implementation changes. It takes an optional string +// value to be set as the underlying string value for this style. +func (r *Renderer) NewStyle(strs ...string) Style { + return Style{r: r}.SetString(strs...) } // Style contains a set of rules that comprise a style as a whole. type Style struct { + r *Renderer rules map[propKey]interface{} value string } @@ -89,8 +103,8 @@ type Style struct { // a convenience for cases when having a stringer implementation is handy, such // as when using fmt.Sprintf. You can also simply define a style and render out // strings directly with Style.Render. -func (s Style) SetString(str string) Style { - s.value = str +func (s Style) SetString(strs ...string) Style { + s.value = strings.Join(strs, " ") return s } @@ -102,10 +116,8 @@ func (s Style) Value() string { // String implements stringer for a Style, returning the rendered result based // on the rules in this style. An underlying string value must be set with // Style.SetString prior to using this method. -// -// Deprecated: Use Render(Style, string) instead. func (s Style) String() string { - return s.Render(s.value) + return s.Render() } // Copy returns a copy of this style, including any underlying string values. @@ -115,6 +127,7 @@ func (s Style) Copy() Style { for k, v := range s.rules { o.rules[k] = v } + o.r = s.r o.value = s.value return o } @@ -151,13 +164,230 @@ func (s Style) Inherit(i Style) Style { } // Render applies the defined style formatting to a given string. -// -// Deprecated: Use Render(Style, string) instead. -func (s Style) Render(str string) string { - return renderer.Render(s, str) +func (s Style) Render(strs ...string) string { + if s.value != "" { + strs = append([]string{s.value}, strs...) + } + + var ( + str = strings.Join(strs, " ") + + te = s.r.ColorProfile().String() + teSpace = s.r.ColorProfile().String() + teWhitespace = s.r.ColorProfile().String() + + bold = s.getAsBool(boldKey, false) + italic = s.getAsBool(italicKey, false) + underline = s.getAsBool(underlineKey, false) + strikethrough = s.getAsBool(strikethroughKey, false) + reverse = s.getAsBool(reverseKey, false) + blink = s.getAsBool(blinkKey, false) + faint = s.getAsBool(faintKey, false) + + fg = s.getAsColor(foregroundKey) + bg = s.getAsColor(backgroundKey) + + width = s.getAsInt(widthKey) + height = s.getAsInt(heightKey) + horizontalAlign = s.getAsPosition(alignHorizontalKey) + verticalAlign = s.getAsPosition(alignVerticalKey) + + topPadding = s.getAsInt(paddingTopKey) + rightPadding = s.getAsInt(paddingRightKey) + bottomPadding = s.getAsInt(paddingBottomKey) + leftPadding = s.getAsInt(paddingLeftKey) + + colorWhitespace = s.getAsBool(colorWhitespaceKey, true) + inline = s.getAsBool(inlineKey, false) + maxWidth = s.getAsInt(maxWidthKey) + maxHeight = s.getAsInt(maxHeightKey) + + underlineSpaces = underline && s.getAsBool(underlineSpacesKey, true) + strikethroughSpaces = strikethrough && s.getAsBool(strikethroughSpacesKey, true) + + // Do we need to style whitespace (padding and space outside + // paragraphs) separately? + styleWhitespace = reverse + + // Do we need to style spaces separately? + useSpaceStyler = underlineSpaces || strikethroughSpaces + ) + + if len(s.rules) == 0 { + return str + } + + // Enable support for ANSI on the legacy Windows cmd.exe console. This is a + // no-op on non-Windows systems and on Windows runs only once. + enableLegacyWindowsANSI() + + if bold { + te = te.Bold() + } + if italic { + te = te.Italic() + } + if underline { + te = te.Underline() + } + if reverse { + if reverse { + teWhitespace = teWhitespace.Reverse() + } + te = te.Reverse() + } + if blink { + te = te.Blink() + } + if faint { + te = te.Faint() + } + + if fg != noColor { + te = te.Foreground(fg.color(s.r)) + if styleWhitespace { + teWhitespace = teWhitespace.Foreground(fg.color(s.r)) + } + if useSpaceStyler { + teSpace = teSpace.Foreground(fg.color(s.r)) + } + } + + if bg != noColor { + te = te.Background(bg.color(s.r)) + if colorWhitespace { + teWhitespace = teWhitespace.Background(bg.color(s.r)) + } + if useSpaceStyler { + teSpace = teSpace.Background(bg.color(s.r)) + } + } + + if underline { + te = te.Underline() + } + if strikethrough { + te = te.CrossOut() + } + + if underlineSpaces { + teSpace = teSpace.Underline() + } + if strikethroughSpaces { + teSpace = teSpace.CrossOut() + } + + // Strip newlines in single line mode + if inline { + str = strings.ReplaceAll(str, "\n", "") + } + + // Word wrap + if !inline && width > 0 { + wrapAt := width - leftPadding - rightPadding + str = wordwrap.String(str, wrapAt) + str = wrap.String(str, wrapAt) // force-wrap long strings + } + + // Render core text + { + var b strings.Builder + + l := strings.Split(str, "\n") + for i := range l { + if useSpaceStyler { + // Look for spaces and apply a different styler + for _, r := range l[i] { + if unicode.IsSpace(r) { + b.WriteString(teSpace.Styled(string(r))) + continue + } + b.WriteString(te.Styled(string(r))) + } + } else { + b.WriteString(te.Styled(l[i])) + } + if i != len(l)-1 { + b.WriteRune('\n') + } + } + + str = b.String() + } + + // Padding + if !inline { + if leftPadding > 0 { + var st *termenv.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = padLeft(str, leftPadding, st) + } + + if rightPadding > 0 { + var st *termenv.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = padRight(str, rightPadding, st) + } + + if topPadding > 0 { + str = strings.Repeat("\n", topPadding) + str + } + + if bottomPadding > 0 { + str += strings.Repeat("\n", bottomPadding) + } + } + + // Height + if height > 0 { + str = alignTextVertical(str, verticalAlign, height, nil) + } + + // Set alignment. This will also pad short lines with spaces so that all + // lines are the same length, so we run it under a few different conditions + // beyond alignment. + { + numLines := strings.Count(str, "\n") + + if !(numLines == 0 && width == 0) { + var st *termenv.Style + if colorWhitespace || styleWhitespace { + st = &teWhitespace + } + str = alignTextHorizontal(str, horizontalAlign, width, st) + } + } + + if !inline { + str = s.applyBorder(str) + str = s.applyMargins(str, inline) + } + + // Truncate according to MaxWidth + if maxWidth > 0 { + lines := strings.Split(str, "\n") + + for i := range lines { + lines[i] = truncate.String(lines[i], uint(maxWidth)) + } + + str = strings.Join(lines, "\n") + } + + // Truncate according to MaxHeight + if maxHeight > 0 { + lines := strings.Split(str, "\n") + str = strings.Join(lines[:min(maxHeight, len(lines))], "\n") + } + + return str } -func (s Style) applyMargins(re *Renderer, str string, inline bool) string { +func (s Style) applyMargins(str string, inline bool) string { var ( topMargin = s.getAsInt(marginTopKey) rightMargin = s.getAsInt(marginRightKey) @@ -169,7 +399,7 @@ func (s Style) applyMargins(re *Renderer, str string, inline bool) string { bgc := s.getAsColor(marginBackgroundKey) if bgc != noColor { - styler = styler.Background(re.color(bgc)) + styler = styler.Background(bgc.color(s.r)) } // Add left and right margin diff --git a/style_test.go b/style_test.go index 1540519a..2032c5ee 100644 --- a/style_test.go +++ b/style_test.go @@ -8,6 +8,7 @@ import ( ) func TestStyleRender(t *testing.T) { + renderer.SetColorProfile(termenv.TrueColor) t.Parallel() tt := []struct { @@ -40,9 +41,9 @@ func TestStyleRender(t *testing.T) { }, } - SetColorProfile(termenv.TrueColor) for i, tc := range tt { - res := tc.style.Render("hello") + s := tc.style.Copy().SetString("hello") + res := s.Render() if res != tc.expected { t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", i, tc.expected, formatEscapes(tc.expected), @@ -245,6 +246,47 @@ func TestStyleUnset(t *testing.T) { requireFalse(t, s.GetBorderLeft()) } +func TestStyleValue(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + style Style + expected string + }{ + { + name: "empty", + style: NewStyle(), + expected: "foo", + }, + { + name: "set string", + style: NewStyle().SetString("bar"), + expected: "bar foo", + }, + { + name: "set string with bold", + style: NewStyle().SetString("bar").Bold(true), + expected: "\x1b[1mbar foo\x1b[0m", + }, + { + name: "new style with string", + style: NewStyle("bar", "foobar"), + expected: "bar foobar foo", + }, + } + + for i, tc := range tt { + res := tc.style.Render("foo") + if res != tc.expected { + t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", + i, tc.expected, formatEscapes(tc.expected), + res, formatEscapes(res)) + } + } + +} + func BenchmarkStyleRender(b *testing.B) { s := NewStyle(). Bold(true). diff --git a/whitespace.go b/whitespace.go index b63e45d0..3a7128d0 100644 --- a/whitespace.go +++ b/whitespace.go @@ -58,17 +58,24 @@ func (w Whitespace) render(width int) string { // WhitespaceOption sets a styling rule for rendering whitespace. type WhitespaceOption func(*Whitespace) +// WithWhitespaceRenderer sets the lipgloss renderer to be used for rendering. +func WithWhitespaceRenderer(r *Renderer) WhitespaceOption { + return func(w *Whitespace) { + w.re = r + } +} + // WithWhitespaceForeground sets the color of the characters in the whitespace. func WithWhitespaceForeground(c TerminalColor) WhitespaceOption { return func(w *Whitespace) { - w.style = w.style.Foreground(w.re.color(c)) + w.style = w.style.Foreground(c.color(w.re)) } } // WithWhitespaceBackground sets the background color of the whitespace. func WithWhitespaceBackground(c TerminalColor) WhitespaceOption { return func(w *Whitespace) { - w.style = w.style.Background(w.re.color(c)) + w.style = w.style.Background(c.color(w.re)) } } @@ -78,10 +85,3 @@ func WithWhitespaceChars(s string) WhitespaceOption { w.chars = s } } - -// WithWhitespaceRenderer sets the lipgloss renderer to be used for rendering. -func WithWhitespaceRenderer(r *Renderer) WhitespaceOption { - return func(w *Whitespace) { - w.re = r - } -} From 6f6b47fbe3036a111218e662fe741f76c2950f1a Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 3 Oct 2022 13:45:30 -0400 Subject: [PATCH 035/126] docs: update readme * add new color types * more examples * custom renderer example --- README.md | 72 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 6c437c4e..6c201d27 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Users familiar with CSS will feel at home with Lip Gloss. import "github.com/charmbracelet/lipgloss" -var style = lipgloss.NewStyle(). +var style = lipgloss.NewStyle("Hello, kitty."). Bold(true). Foreground(lipgloss.Color("#FAFAFA")). Background(lipgloss.Color("#7D56F4")). @@ -27,10 +27,9 @@ var style = lipgloss.NewStyle(). PaddingLeft(4). Width(22) -fmt.Println(lipgloss.Render(style, "Hello, kitty.")) +fmt.Println(style) ``` - ## Colors Lip Gloss supports the following color profiles: @@ -77,6 +76,29 @@ lipgloss.AdaptiveColor{Light: "236", Dark: "248"} The terminal's background color will automatically be detected and the appropriate color will be chosen at runtime. +### Complete Colors + +CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color +profiles. + +```go +lipgloss.CompleteColor{True: "#0000FF", ANSI256: "86", ANSI: "5"} +``` + +Automatic color degradation will not be performed in this case and it will be +based on the color specified. + +### Complete Adaptive Colors + +You can use CompleteColor with AdaptiveColor to specify the exact values for +light and dark backgrounds without automatic color degradation. + +```go +lipgloss.CompleteAdaptiveColor{ + Light: CompleteColor{TrueColor: "#d7ffae", ANSI256: "193", ANSI: "11"}, + Dark: CompleteColor{TrueColor: "#d75fee", ANSI256: "163", ANSI: "5"}, +} +``` ## Inline Formatting @@ -264,37 +286,51 @@ and `MaxWidth`, and `MaxHeight` come in: ```go // Force rendering onto a single line, ignoring margins, padding, and borders. -lipgloss.Render(someStyle.Inline(true), "yadda yadda") +someStyle.Inline(true).Render("yadda yadda") // Also limit rendering to five cells -lipgloss.Render(someStyle.Inline(true).MaxWidth(5), "yadda yadda") +someStyle.Inline(true).MaxWidth(5).Render("yadda yadda") // Limit rendering to a 5x5 cell block -lipgloss.Render(someStyle.MaxWidth(5).MaxHeight(5), "yadda yadda") +someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda") ``` ## Rendering -Generally, you just pass a style and string to the default renderer: +Generally, you just call the `Render(string...)` method on a `lipgloss.Style`: ```go -style := lipgloss.NewStyle().Bold(true) -fmt.Println(lipgloss.Render(style, "Hello, kitty.")) +style := lipgloss.NewStyle("Hello,").Bold(true) +fmt.Println(style.Render("kitty.")) // Hello, kitty. +fmt.Println(style.Render("puppy.")) // Hello, puppy. ``` -But you can also use a custom renderer: +But you could also use the Stringer interface: + +```go +var style = lipgloss.NewStyle("你好,猫咪。").Bold(true) + +fmt.Println(style) +``` + +### Custom Renderers + +Use custom renderers to enforce rendering your styles in a specific way. You can +specify the color profile to use, True Color, ANSI 256, 8-bit ANSI, or good ol' +ASCII. You can also specify whether or not to assume dark background colors. ```go -// Render to stdout and force dark background mode. -var r = lipgloss.NewRenderer( - lipgloss.WithOutput(termenv.NewOutput(os.Stdout)), - lipgloss.WithDarkBackground(), +renderer := lipgloss.NewRenderer( + lipgloss.WithColorProfile(termenv.ANSI256), + lipgloss.WithDarkBackground(true), ) -var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true) -fmt.Println(r.Render(style)) +var style = renderer.NewStyle().Background(lipgloss.AdaptiveColor{Light: "63", Dark: "228"}) +fmt.Println(style.Render("Lip Gloss")) // This will always use the dark background color ``` +This is also useful when using lipgloss with an SSH server like [Wish][wish]. +See the [ssh example][ssh-example] for more details. ## Utilities @@ -329,7 +365,7 @@ your layouts. var style = lipgloss.NewStyle(). Width(40). Padding(2) -var block string = lipgloss.Render(style, someLongString) +var block string = style.Render(someLongString) // Get the actual, physical dimensions of the text block. width := lipgloss.Width(block) @@ -416,3 +452,5 @@ Charm热爱开源 • Charm loves open source [docs]: https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc +[wish]: https://github.com/charmbracelet/wish +[ssh-example]: examples/ssh From 3fe14d0547b32bf72f9f40dfe60878005c75063e Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 3 Oct 2022 14:59:14 -0400 Subject: [PATCH 036/126] chore: add ssh example --- .gitignore | 1 + {example => examples/layout}/go.mod | 0 {example => examples/layout}/go.sum | 0 {example => examples/layout}/main.go | 0 examples/ssh/go.mod | 15 ++ examples/ssh/go.sum | 133 ++++++++++++++++++ examples/ssh/main.go | 198 +++++++++++++++++++++++++++ 7 files changed, 347 insertions(+) create mode 100644 .gitignore rename {example => examples/layout}/go.mod (100%) rename {example => examples/layout}/go.sum (100%) rename {example => examples/layout}/main.go (100%) create mode 100644 examples/ssh/go.mod create mode 100644 examples/ssh/go.sum create mode 100644 examples/ssh/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a170af09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +ssh_example_ed25519* \ No newline at end of file diff --git a/example/go.mod b/examples/layout/go.mod similarity index 100% rename from example/go.mod rename to examples/layout/go.mod diff --git a/example/go.sum b/examples/layout/go.sum similarity index 100% rename from example/go.sum rename to examples/layout/go.sum diff --git a/example/main.go b/examples/layout/main.go similarity index 100% rename from example/main.go rename to examples/layout/main.go diff --git a/examples/ssh/go.mod b/examples/ssh/go.mod new file mode 100644 index 00000000..bb7e09e2 --- /dev/null +++ b/examples/ssh/go.mod @@ -0,0 +1,15 @@ +module ssh + +go 1.16 + +replace github.com/charmbracelet/lipgloss => ../.. + +require ( + github.com/charmbracelet/bubbletea v0.20.0 + github.com/charmbracelet/lipgloss v0.4.0 + github.com/charmbracelet/wish v0.5.0 + github.com/gliderlabs/ssh v0.3.4 + github.com/kr/pty v1.1.1 + github.com/lucasb-eyer/go-colorful v1.2.0 + github.com/muesli/termenv v0.13.0 +) diff --git a/examples/ssh/go.sum b/examples/ssh/go.sum new file mode 100644 index 00000000..bb95ba2e --- /dev/null +++ b/examples/ssh/go.sum @@ -0,0 +1,133 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= +github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= +github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= +github.com/charmbracelet/bubbletea v0.20.0 h1:/b8LEPgCbNr7WWZ2LuE/BV1/r4t5PyYJtDb+J3vpwxc= +github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM= +github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y= +github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= +github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg= +github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= +github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= +golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/ssh/main.go b/examples/ssh/main.go new file mode 100644 index 00000000..ef2ef0f9 --- /dev/null +++ b/examples/ssh/main.go @@ -0,0 +1,198 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/wish" + bm "github.com/charmbracelet/wish/bubbletea" + lm "github.com/charmbracelet/wish/logging" + "github.com/gliderlabs/ssh" + "github.com/kr/pty" + "github.com/lucasb-eyer/go-colorful" + "github.com/muesli/termenv" +) + +type sshOutput struct { + ssh.Session + tty *os.File +} + +func (s *sshOutput) Write(p []byte) (int, error) { + return s.Session.Write(p) +} + +func (s *sshOutput) Fd() uintptr { + return s.tty.Fd() +} + +type sshEnviron struct { + environ []string +} + +func (s *sshEnviron) Getenv(key string) string { + for _, v := range s.environ { + if strings.HasPrefix(v, key+"=") { + return v[len(key)+1:] + } + } + return "" +} + +func (s *sshEnviron) Environ() []string { + return s.environ +} + +func outputFromSession(s ssh.Session) *termenv.Output { + sshPty, _, _ := s.Pty() + _, tty, err := pty.Open() + if err != nil { + panic(err) + } + o := &sshOutput{ + Session: s, + tty: tty, + } + environ := s.Environ() + environ = append(environ, fmt.Sprintf("TERM=%s", sshPty.Term)) + e := &sshEnviron{ + environ: environ, + } + return termenv.NewOutput(o, termenv.WithEnvironment(e)) +} + +func main() { + addr := ":3456" + s, err := wish.NewServer( + wish.WithAddress(addr), + wish.WithHostKeyPath("ssh_example"), + wish.WithMiddleware( + bm.Middleware(func(s ssh.Session) (tea.Model, []tea.ProgramOption) { + output := outputFromSession(s) + renderer := lipgloss.NewRenderer(lipgloss.WithOutput(output)) + return model{ + detectedProfile: renderer.ColorProfile(), + renderer: renderer, + }, nil + }), + lm.Middleware(), + ), + ) + if err != nil { + log.Fatal(err) + } + log.Printf("Listening on %s", addr) + if err := s.ListenAndServe(); err != nil { + log.Fatal(err) + } +} + +type model struct { + detectedProfile termenv.Profile + renderer *lipgloss.Renderer +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "ctrl+c", "q": + return m, tea.Quit + } + } + return m, nil +} + +func (m model) View() string { + s := strings.Builder{} + m.renderer.SetColorProfile(m.detectedProfile) + fmt.Fprintf(&s, "Detected Color Profile: %s\n", colorProfile(m.renderer.ColorProfile())) + + // Color grid + fmt.Fprintf(&s, "%s\n", colors(m.renderer)) + + // Enforced color profile + m.renderer.SetColorProfile(termenv.TrueColor) + fmt.Fprintf(&s, "Enforce True Color\n") + + // Color grid + fmt.Fprintf(&s, "%s\n", colors(m.renderer)) + + // Detect dark background + fmt.Fprintf(&s, "Has Dark Background: %v\n", m.renderer.HasDarkBackground()) + + return s.String() +} + +func colors(r *lipgloss.Renderer) string { + colors := colorGrid(14, 8) + + b := strings.Builder{} + for _, x := range colors { + for _, y := range x { + s := r.NewStyle(" ").Background(lipgloss.Color(y)) + b.WriteString(s.String()) + } + b.WriteRune('\n') + } + + return b.String() +} + +func colorGrid(xSteps, ySteps int) [][]string { + x0y0, _ := colorful.Hex("#F25D94") + x1y0, _ := colorful.Hex("#EDFF82") + x0y1, _ := colorful.Hex("#643AFF") + x1y1, _ := colorful.Hex("#14F9D5") + + x0 := make([]colorful.Color, ySteps) + for i := range x0 { + x0[i] = x0y0.BlendLuv(x0y1, float64(i)/float64(ySteps)) + } + + x1 := make([]colorful.Color, ySteps) + for i := range x1 { + x1[i] = x1y0.BlendLuv(x1y1, float64(i)/float64(ySteps)) + } + + grid := make([][]string, ySteps) + for x := 0; x < ySteps; x++ { + y0 := x0[x] + grid[x] = make([]string, xSteps) + for y := 0; y < xSteps; y++ { + grid[x][y] = y0.BlendLuv(x1[x], float64(y)/float64(xSteps)).Hex() + } + } + + return grid +} + +func colorProfile(p termenv.Profile) string { + switch p { + case termenv.TrueColor: + return "True Color" + case termenv.ANSI256: + return "ANSI 256" + case termenv.ANSI: + return "ANSI" + case termenv.Ascii: + return "No Color" + default: + return "Unknown" + } +} + +func max(a, b int) int { + if a > b { + return a + } + return b +} From 8565b7428fb7dd2bba872ad1c3178375206ee1d2 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 4 Oct 2022 22:10:54 -0400 Subject: [PATCH 037/126] fix(whitespace): use position renderer --- position.go | 28 +++++++++++++++++++++++++--- whitespace.go | 27 ++++++++++----------------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/position.go b/position.go index dd61ec81..28f5ccbd 100644 --- a/position.go +++ b/position.go @@ -34,13 +34,26 @@ const ( // Place places a string or text block vertically in an unstyled box of a given // width or height. func Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string { - return PlaceVertical(height, vPos, PlaceHorizontal(width, hPos, str, opts...), opts...) + return renderer.Place(width, height, hPos, vPos, str, opts...) +} + +// Place places a string or text block vertically in an unstyled box of a given +// width or height. +func (r *Renderer) Place(width, height int, hPos, vPos Position, str string, opts ...WhitespaceOption) string { + return r.PlaceVertical(height, vPos, r.PlaceHorizontal(width, hPos, str, opts...), opts...) } // PlaceHorizontal places a string or text block horizontally in an unstyled // block of a given width. If the given width is shorter than the max width of // the string (measured by its longest line) this will be a noop. func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string { + return renderer.PlaceHorizontal(width, pos, str, opts...) +} + +// PlaceHorizontal places a string or text block horizontally in an unstyled +// block of a given width. If the given width is shorter than the max width of +// the string (measured by it's longest line) this will be a noöp. +func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string { lines, contentWidth := getLines(str) gap := width - contentWidth @@ -48,7 +61,8 @@ func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOpti return str } - ws := NewWhitespace(opts...) + ws := newWhitespace(r, opts...) + var b strings.Builder for i, l := range lines { // Is this line shorter than the longest line? @@ -87,6 +101,13 @@ func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOpti // of a given height. If the given height is shorter than the height of the // string (measured by its newlines) then this will be a noop. func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string { + return renderer.PlaceVertical(height, pos, str, opts...) +} + +// PlaceVertical places a string or text block vertically in an unstyled block +// of a given height. If the given height is shorter than the height of the +// string (measured by it's newlines) then this will be a noöp. +func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string { contentHeight := strings.Count(str, "\n") + 1 gap := height - contentHeight @@ -94,7 +115,8 @@ func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOptio return str } - ws := NewWhitespace(opts...) + ws := newWhitespace(r, opts...) + _, width := getLines(str) emptyLine := ws.render(width) b := strings.Builder{} diff --git a/whitespace.go b/whitespace.go index 3a7128d0..88b8f911 100644 --- a/whitespace.go +++ b/whitespace.go @@ -7,18 +7,18 @@ import ( "github.com/muesli/termenv" ) -// Whitespace is a whitespace renderer. -type Whitespace struct { +// whitespace is a whitespace renderer. +type whitespace struct { re *Renderer style termenv.Style chars string } -// NewWhitespace creates a new whitespace renderer. The order of the options +// newWhitespace creates a new whitespace renderer. The order of the options // matters, it you'r using WithWhitespaceRenderer, make sure it comes first as // other options might depend on it. -func NewWhitespace(opts ...WhitespaceOption) *Whitespace { - w := &Whitespace{re: renderer} +func newWhitespace(r *Renderer, opts ...WhitespaceOption) *whitespace { + w := &whitespace{re: r} for _, opt := range opts { opt(w) } @@ -26,7 +26,7 @@ func NewWhitespace(opts ...WhitespaceOption) *Whitespace { } // Render whitespaces. -func (w Whitespace) render(width int) string { +func (w whitespace) render(width int) string { if w.chars == "" { w.chars = " " } @@ -56,32 +56,25 @@ func (w Whitespace) render(width int) string { } // WhitespaceOption sets a styling rule for rendering whitespace. -type WhitespaceOption func(*Whitespace) - -// WithWhitespaceRenderer sets the lipgloss renderer to be used for rendering. -func WithWhitespaceRenderer(r *Renderer) WhitespaceOption { - return func(w *Whitespace) { - w.re = r - } -} +type WhitespaceOption func(*whitespace) // WithWhitespaceForeground sets the color of the characters in the whitespace. func WithWhitespaceForeground(c TerminalColor) WhitespaceOption { - return func(w *Whitespace) { + return func(w *whitespace) { w.style = w.style.Foreground(c.color(w.re)) } } // WithWhitespaceBackground sets the background color of the whitespace. func WithWhitespaceBackground(c TerminalColor) WhitespaceOption { - return func(w *Whitespace) { + return func(w *whitespace) { w.style = w.style.Background(c.color(w.re)) } } // WithWhitespaceChars sets the characters to be rendered in the whitespace. func WithWhitespaceChars(s string) WhitespaceOption { - return func(w *Whitespace) { + return func(w *whitespace) { w.chars = s } } From 0a35904daa43716243858d110e17b4180527cfad Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 4 Oct 2022 22:18:39 -0400 Subject: [PATCH 038/126] fix(whitespace): style color profile --- whitespace.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/whitespace.go b/whitespace.go index 88b8f911..b043e565 100644 --- a/whitespace.go +++ b/whitespace.go @@ -18,7 +18,10 @@ type whitespace struct { // matters, it you'r using WithWhitespaceRenderer, make sure it comes first as // other options might depend on it. func newWhitespace(r *Renderer, opts ...WhitespaceOption) *whitespace { - w := &whitespace{re: r} + w := &whitespace{ + re: r, + style: r.ColorProfile().String(), + } for _, opt := range opts { opt(w) } From 4aa1f3878506772c643a7be28ba287cb29c310cd Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 6 Oct 2022 12:25:51 -0400 Subject: [PATCH 039/126] chore: use unified examples go.mod --- examples/{ssh => }/go.mod | 5 +-- examples/{ssh => }/go.sum | 0 examples/layout/go.mod | 11 ------- examples/layout/go.sum | 35 --------------------- examples/layout/main.go | 38 +++++++++++------------ examples/ssh/main.go | 64 +++++++++++++++++++++++++++++---------- 6 files changed, 69 insertions(+), 84 deletions(-) rename examples/{ssh => }/go.mod (70%) rename examples/{ssh => }/go.sum (100%) delete mode 100644 examples/layout/go.mod delete mode 100644 examples/layout/go.sum diff --git a/examples/ssh/go.mod b/examples/go.mod similarity index 70% rename from examples/ssh/go.mod rename to examples/go.mod index bb7e09e2..5908799c 100644 --- a/examples/ssh/go.mod +++ b/examples/go.mod @@ -1,8 +1,8 @@ -module ssh +module examples go 1.16 -replace github.com/charmbracelet/lipgloss => ../.. +replace github.com/charmbracelet/lipgloss => ../ require ( github.com/charmbracelet/bubbletea v0.20.0 @@ -12,4 +12,5 @@ require ( github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/muesli/termenv v0.13.0 + golang.org/x/term v0.0.0-20210422114643-f5beecf764ed ) diff --git a/examples/ssh/go.sum b/examples/go.sum similarity index 100% rename from examples/ssh/go.sum rename to examples/go.sum diff --git a/examples/layout/go.mod b/examples/layout/go.mod deleted file mode 100644 index 3ff788fb..00000000 --- a/examples/layout/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module example - -go 1.16 - -require ( - github.com/charmbracelet/lipgloss v0.4.0 - github.com/lucasb-eyer/go-colorful v1.2.0 - golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 -) - -replace github.com/charmbracelet/lipgloss => ../ diff --git a/examples/layout/go.sum b/examples/layout/go.sum deleted file mode 100644 index 366b13ed..00000000 --- a/examples/layout/go.sum +++ /dev/null @@ -1,35 +0,0 @@ -github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= -github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= -github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20210317153231-de623e64d2a6 h1:EC6+IGYTjPpRfv9a2b/6Puw0W+hLtAhkV1tPsXhutqs= -golang.org/x/term v0.0.0-20210317153231-de623e64d2a6/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/layout/main.go b/examples/layout/main.go index c996dabf..8ec832b8 100644 --- a/examples/layout/main.go +++ b/examples/layout/main.go @@ -30,12 +30,12 @@ var ( special = lipgloss.AdaptiveColor{Light: "#43BF6D", Dark: "#73F59F"} divider = lipgloss.NewStyle(). + SetString("•"). Padding(0, 1). - Foreground(subtle).Render("•") + Foreground(subtle). + String() - url = func(s string) string { - return lipgloss.NewStyle().Foreground(special).Render(s) - } + url = lipgloss.NewStyle().Foreground(special).Render // Tabs. @@ -122,17 +122,14 @@ var ( Height(8). Width(columnWidth + 1) - listHeader = func(s string) string { - return lipgloss.NewStyle(). + listHeader = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderBottom(true). BorderForeground(subtle). - MarginRight(2).Render(s) - } + MarginRight(2). + Render - listItem = func(s string) string { - return lipgloss.NewStyle().PaddingLeft(2).Render(s) - } + listItem = lipgloss.NewStyle().PaddingLeft(2).Render checkMark = lipgloss.NewStyle().SetString("✓"). Foreground(special). @@ -267,14 +264,15 @@ func main() { }() lists := lipgloss.JoinHorizontal(lipgloss.Top, - list.Render(lipgloss.JoinVertical(lipgloss.Left, - listHeader("Citrus Fruits to Try"), - listDone("Grapefruit"), - listDone("Yuzu"), - listItem("Citron"), - listItem("Kumquat"), - listItem("Pomelo"), - ), + list.Render( + lipgloss.JoinVertical(lipgloss.Left, + listHeader("Citrus Fruits to Try"), + listDone("Grapefruit"), + listDone("Yuzu"), + listItem("Citron"), + listItem("Kumquat"), + listItem("Pomelo"), + ), ), list.Copy().Width(columnWidth).Render( lipgloss.JoinVertical(lipgloss.Left, @@ -335,7 +333,7 @@ func main() { // Okay, let's print it fmt.Println(docStyle.Render(doc.String())) -} // +} func colorGrid(xSteps, ySteps int) [][]string { x0y0, _ := colorful.Hex("#F25D94") diff --git a/examples/ssh/main.go b/examples/ssh/main.go index ef2ef0f9..ad00690a 100644 --- a/examples/ssh/main.go +++ b/examples/ssh/main.go @@ -9,7 +9,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/wish" - bm "github.com/charmbracelet/wish/bubbletea" lm "github.com/charmbracelet/wish/logging" "github.com/gliderlabs/ssh" "github.com/kr/pty" @@ -71,14 +70,54 @@ func main() { wish.WithAddress(addr), wish.WithHostKeyPath("ssh_example"), wish.WithMiddleware( - bm.Middleware(func(s ssh.Session) (tea.Model, []tea.ProgramOption) { - output := outputFromSession(s) - renderer := lipgloss.NewRenderer(lipgloss.WithOutput(output)) - return model{ - detectedProfile: renderer.ColorProfile(), - renderer: renderer, - }, nil - }), + func(sh ssh.Handler) ssh.Handler { + return func(s ssh.Session) { + output := outputFromSession(s) + pty, _, active := s.Pty() + if !active { + sh(s) + return + } + w, _ := pty.Window.Width, pty.Window.Height + + renderer := lipgloss.NewRenderer(lipgloss.WithOutput(output)) + str := strings.Builder{} + fmt.Fprintf(&str, "\n%s %s %s %s %s", + renderer.NewStyle("bold").Bold(true), + renderer.NewStyle("faint").Faint(true), + renderer.NewStyle("italic").Italic(true), + renderer.NewStyle("underline").Underline(true), + renderer.NewStyle("crossout").Strikethrough(true), + ) + + fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s", + renderer.NewStyle("red").Foreground(lipgloss.Color("#E88388")), + renderer.NewStyle("green").Foreground(lipgloss.Color("#A8CC8C")), + renderer.NewStyle("yellow").Foreground(lipgloss.Color("#DBAB79")), + renderer.NewStyle("blue").Foreground(lipgloss.Color("#71BEF2")), + renderer.NewStyle("magenta").Foreground(lipgloss.Color("#D290E4")), + renderer.NewStyle("cyan").Foreground(lipgloss.Color("#66C2CD")), + renderer.NewStyle("gray").Foreground(lipgloss.Color("#B9BFCA")), + ) + + fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s\n\n", + renderer.NewStyle("red").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#E88388")), + renderer.NewStyle("green").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#A8CC8C")), + renderer.NewStyle("yellow").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#DBAB79")), + renderer.NewStyle("blue").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#71BEF2")), + renderer.NewStyle("magenta").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#D290E4")), + renderer.NewStyle("cyan").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#66C2CD")), + renderer.NewStyle("gray").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#B9BFCA")), + ) + + fmt.Fprintf(&str, "%s %t\n", renderer.NewStyle("Has dark background?").Bold(true), renderer.HasDarkBackground()) + fmt.Fprintln(&str) + + wish.WriteString(s, renderer.Place(w, lipgloss.Height(str.String()), lipgloss.Center, lipgloss.Center, str.String())) + + sh(s) + } + }, lm.Middleware(), ), ) @@ -189,10 +228,3 @@ func colorProfile(p termenv.Profile) string { return "Unknown" } } - -func max(a, b int) int { - if a > b { - return a - } - return b -} From e5401eb8b85cc12b18b1479737f57b039de608e6 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 6 Oct 2022 12:37:18 -0400 Subject: [PATCH 040/126] feat(renderer): add WithTermenvOutput option --- examples/ssh/main.go | 2 +- renderer.go | 13 ++++++++++--- renderer_test.go | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/ssh/main.go b/examples/ssh/main.go index ad00690a..b5e543f1 100644 --- a/examples/ssh/main.go +++ b/examples/ssh/main.go @@ -80,7 +80,7 @@ func main() { } w, _ := pty.Window.Width, pty.Window.Height - renderer := lipgloss.NewRenderer(lipgloss.WithOutput(output)) + renderer := lipgloss.NewRenderer(lipgloss.WithTermenvOutput(output)) str := strings.Builder{} fmt.Fprintf(&str, "\n%s %s %s %s %s", renderer.NewStyle("bold").Bold(true), diff --git a/renderer.go b/renderer.go index 04f3118e..28ca477e 100644 --- a/renderer.go +++ b/renderer.go @@ -1,6 +1,8 @@ package lipgloss import ( + "io" + "github.com/muesli/termenv" ) @@ -21,7 +23,7 @@ func DefaultRenderer() *Renderer { } // NewRenderer creates a new Renderer. -func NewRenderer(options ...func(r *Renderer)) *Renderer { +func NewRenderer(options ...RendererOption) *Renderer { r := &Renderer{ output: termenv.DefaultOutput(), } @@ -31,8 +33,13 @@ func NewRenderer(options ...func(r *Renderer)) *Renderer { return r } -// WithOutput sets the termenv Output to use for rendering. -func WithOutput(output *termenv.Output) RendererOption { +// WithOutput sets the io.Writer to be used for rendering. +func WithOutput(w io.Writer) RendererOption { + return WithTermenvOutput(termenv.NewOutput(w)) +} + +// WithTermenvOutput sets the termenv Output to use for rendering. +func WithTermenvOutput(output *termenv.Output) RendererOption { return func(r *Renderer) { r.output = output } diff --git a/renderer_test.go b/renderer_test.go index fed38fce..2be6430e 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -26,7 +26,7 @@ func TestRendererWithOutput(t *testing.T) { defer f.Close() defer os.Remove(f.Name()) output := termenv.NewOutput(f, termenv.WithProfile(termenv.TrueColor)) - r := NewRenderer(WithOutput(output)) + r := NewRenderer(WithTermenvOutput(output)) if r.output.Profile != termenv.TrueColor { t.Error("Expected renderer to use true color") } From 53fe53727e873582405d0ca912d71fd16f20aee9 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 6 Oct 2022 12:52:12 -0400 Subject: [PATCH 041/126] feat: conform to the Go color interface implement RGBA with the default output --- color.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/color.go b/color.go index 208d63b8..ef7fd279 100644 --- a/color.go +++ b/color.go @@ -1,7 +1,7 @@ package lipgloss import ( - "fmt" + "strconv" "github.com/muesli/termenv" ) @@ -9,6 +9,7 @@ import ( // TerminalColor is a color intended to be rendered in the terminal. type TerminalColor interface { color(*Renderer) termenv.Color + RGBA() (r, g, b, a uint32) } var noColor = NoColor{} @@ -26,6 +27,17 @@ func (NoColor) color(*Renderer) termenv.Color { return termenv.NoColor{} } +// RGBA returns the RGBA value of this color. Because we have to return +// something, despite this color being the absence of color, we're returning +// black with 100% opacity. +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +// +// Deprecated. +func (n NoColor) RGBA() (r, g, b, a uint32) { + return 0x0, 0x0, 0x0, 0xFFFF +} + // Color specifies a color by hex or ANSI value. For example: // // ansiColor := lipgloss.Color("21") @@ -36,6 +48,16 @@ func (c Color) color(r *Renderer) termenv.Color { return r.ColorProfile().Color(string(c)) } +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. Note that on error we return black with 100% opacity, or: +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +// +// Deprecated. +func (c Color) RGBA() (r, g, b, a uint32) { + return termenv.ConvertToRGB(c.color(renderer)).RGBA() +} + // ANSIColor is a color specified by an ANSI color value. It's merely syntactic // sugar for the more general Color function. Invalid colors will render as // black. @@ -48,7 +70,18 @@ func (c Color) color(r *Renderer) termenv.Color { type ANSIColor uint func (ac ANSIColor) color(r *Renderer) termenv.Color { - return Color(fmt.Sprintf("%d", ac)).color(r) + return Color(strconv.FormatUint(uint64(ac), 10)).color(r) +} + +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. Note that on error we return black with 100% opacity, or: +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +// +// Deprecated. +func (ac ANSIColor) RGBA() (r, g, b, a uint32) { + cf := Color(strconv.FormatUint(uint64(ac), 10)) + return cf.RGBA() } // AdaptiveColor provides color options for light and dark backgrounds. The @@ -70,6 +103,16 @@ func (ac AdaptiveColor) color(r *Renderer) termenv.Color { return Color(ac.Light).color(r) } +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. Note that on error we return black with 100% opacity, or: +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +// +// Deprecated. +func (ac AdaptiveColor) RGBA() (r, g, b, a uint32) { + return termenv.ConvertToRGB(ac.color(renderer)).RGBA() +} + // CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color // profiles. Automatic color degradation will not be performed. type CompleteColor struct { @@ -92,23 +135,18 @@ func (c CompleteColor) color(r *Renderer) termenv.Color { } } -<<<<<<< HEAD -func (c CompleteColor) color() termenv.Color { - return ColorProfile().Color(c.value()) -} - // RGBA returns the RGBA value of this color. This satisfies the Go Color // interface. Note that on error we return black with 100% opacity, or: // // Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color +// +// Deprecated. func (c CompleteColor) RGBA() (r, g, b, a uint32) { - return termenv.ConvertToRGB(c.color()).RGBA() + return termenv.ConvertToRGB(c.color(renderer)).RGBA() } -// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color -======= // CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color ->>>>>>> f22900b20f84 (feat(renderer): use style renderer) // profiles, with separate options for light and dark backgrounds. Automatic // color degradation will not be performed. type CompleteAdaptiveColor struct { @@ -122,3 +160,13 @@ func (cac CompleteAdaptiveColor) color(r *Renderer) termenv.Color { } return cac.Light.color(r) } + +// RGBA returns the RGBA value of this color. This satisfies the Go Color +// interface. Note that on error we return black with 100% opacity, or: +// +// Red: 0x0, Green: 0x0, Blue: 0x0, Alpha: 0xFFFF. +// +// Deprecated. +func (cac CompleteAdaptiveColor) RGBA() (r, g, b, a uint32) { + return termenv.ConvertToRGB(cac.color(renderer)).RGBA() +} From 29a309cde1115cbcda1f8f4ff4ae8af3f2d1c858 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 13 Oct 2022 17:51:17 -0400 Subject: [PATCH 042/126] fix: renderer nil check --- color_test.go | 8 +++++++- style.go | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/color_test.go b/color_test.go index e109d3b4..0881076c 100644 --- a/color_test.go +++ b/color_test.go @@ -157,6 +157,12 @@ func TestRGBA(t *testing.T) { CompleteColor{TrueColor: "#FF0000", ANSI256: "231", ANSI: "12"}, 0x0000FF, }, + { + termenv.TrueColor, + true, + CompleteColor{TrueColor: "", ANSI256: "231", ANSI: "12"}, + 0x000000, + }, // lipgloss.CompleteAdaptiveColor // dark { @@ -221,7 +227,7 @@ func TestRGBA(t *testing.T) { r.SetColorProfile(tc.profile) r.SetHasDarkBackground(tc.darkBg) - r, g, b, _ := termenv.ConvertToRGB(tc.input.color(r)).RGBA() + r, g, b, _ := tc.input.RGBA() o := uint(r/256)<<16 + uint(g/256)<<8 + uint(b/256) if o != tc.expected { diff --git a/style.go b/style.go index 25da8936..fa008f00 100644 --- a/style.go +++ b/style.go @@ -165,6 +165,9 @@ func (s Style) Inherit(i Style) Style { // Render applies the defined style formatting to a given string. func (s Style) Render(strs ...string) string { + if s.r == nil { + s.r = DefaultRenderer() + } if s.value != "" { strs = append([]string{s.value}, strs...) } From abee24064a10c7d82e3e1da40aa7395beee9c9b8 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 18 Nov 2022 14:48:41 -0500 Subject: [PATCH 043/126] docs: update readme example --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c201d27..74ef30b6 100644 --- a/README.md +++ b/README.md @@ -173,12 +173,10 @@ var style = lipgloss.NewStyle(). Setting a minimum width and height is simple and straightforward. ```go -var style = lipgloss.NewStyle(). +var style = lipgloss.NewStyle("What’s for lunch?"). Width(24). Height(32). Foreground(lipgloss.Color("63")) - -var str = lipgloss.Render(style, "What’s for lunch?") ``` From 60d090659b41b09b008834a7aee7114ba9fdebe5 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 18 Nov 2022 15:53:06 -0500 Subject: [PATCH 044/126] feat(opts): use style options --- style.go | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/style.go b/style.go index fa008f00..395529a1 100644 --- a/style.go +++ b/style.go @@ -75,20 +75,34 @@ const ( // A set of properties. type rules map[propKey]interface{} +// StyleOption is a function that applies a style option to a Style. +type StyleOption func(*Style) + +// WithString sets the underlying string value for this style. +func WithString(strs ...string) StyleOption { + return func(s *Style) { + s.value = joinString(strs...) + } +} + // NewStyle returns a new, empty Style. While it's syntactic sugar for the // Style{} primitive, it's recommended to use this function for creating styles // in case the underlying implementation changes. It takes an optional string // value to be set as the underlying string value for this style. -func NewStyle(strs ...string) Style { - return renderer.NewStyle(strs...) +func NewStyle(opts ...StyleOption) Style { + return renderer.NewStyle(opts...) } // NewStyle returns a new, empty Style. While it's syntactic sugar for the // Style{} primitive, it's recommended to use this function for creating styles // in case the underlying implementation changes. It takes an optional string // value to be set as the underlying string value for this style. -func (r *Renderer) NewStyle(strs ...string) Style { - return Style{r: r}.SetString(strs...) +func (r *Renderer) NewStyle(opts ...StyleOption) Style { + s := Style{r: r} + for _, opt := range opts { + opt(&s) + } + return s } // Style contains a set of rules that comprise a style as a whole. @@ -98,13 +112,19 @@ type Style struct { value string } +// joinString joins a list of strings into a single string separated with a +// space. +func joinString(strs ...string) string { + return strings.Join(strs, " ") +} + // SetString sets the underlying string value for this style. To render once // the underlying string is set, use the Style.String. This method is // a convenience for cases when having a stringer implementation is handy, such // as when using fmt.Sprintf. You can also simply define a style and render out // strings directly with Style.Render. func (s Style) SetString(strs ...string) Style { - s.value = strings.Join(strs, " ") + s.value = joinString(strs...) return s } @@ -173,7 +193,7 @@ func (s Style) Render(strs ...string) string { } var ( - str = strings.Join(strs, " ") + str = joinString(strs...) te = s.r.ColorProfile().String() teSpace = s.r.ColorProfile().String() From cb0f2fd931c31a4d456d454e02080cbb636640cb Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 18 Nov 2022 15:57:57 -0500 Subject: [PATCH 045/126] chore(tests): add more style tests --- style_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/style_test.go b/style_test.go index 2032c5ee..d1d68539 100644 --- a/style_test.go +++ b/style_test.go @@ -9,6 +9,7 @@ import ( func TestStyleRender(t *testing.T) { renderer.SetColorProfile(termenv.TrueColor) + renderer.SetHasDarkBackground(true) t.Parallel() tt := []struct { @@ -19,6 +20,10 @@ func TestStyleRender(t *testing.T) { NewStyle().Foreground(Color("#5A56E0")), "\x1b[38;2;89;86;224mhello\x1b[0m", }, + { + NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), + "\x1b[38;2;89;86;224mhello\x1b[0m", + }, { NewStyle().Bold(true), "\x1b[1mhello\x1b[0m", @@ -52,6 +57,55 @@ func TestStyleRender(t *testing.T) { } } +func TestStyleCustomRender(t *testing.T) { + t.Parallel() + + r := NewRenderer(WithColorProfile(termenv.TrueColor), WithDarkBackground(false)) + tt := []struct { + style Style + expected string + }{ + { + r.NewStyle().Foreground(Color("#5A56E0")), + "\x1b[38;2;89;86;224mhello\x1b[0m", + }, + { + r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), + "\x1b[38;2;255;254;18mhello\x1b[0m", + }, + { + r.NewStyle().Bold(true), + "\x1b[1mhello\x1b[0m", + }, + { + r.NewStyle().Italic(true), + "\x1b[3mhello\x1b[0m", + }, + { + r.NewStyle().Underline(true), + "\x1b[4;4mh\x1b[0m\x1b[4;4me\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4mo\x1b[0m", + }, + { + r.NewStyle().Blink(true), + "\x1b[5mhello\x1b[0m", + }, + { + r.NewStyle().Faint(true), + "\x1b[2mhello\x1b[0m", + }, + } + + for i, tc := range tt { + s := tc.style.Copy().SetString("hello") + res := s.Render() + if res != tc.expected { + t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", + i, tc.expected, formatEscapes(tc.expected), + res, formatEscapes(res)) + } + } +} + func TestValueCopy(t *testing.T) { t.Parallel() @@ -271,7 +325,7 @@ func TestStyleValue(t *testing.T) { }, { name: "new style with string", - style: NewStyle("bar", "foobar"), + style: NewStyle(WithString("bar", "foobar")), expected: "bar foobar foo", }, } From 5e2b9055dcd0e14ee28aa1c2dc3e102d5f9f238c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 18 Nov 2022 16:02:02 -0500 Subject: [PATCH 046/126] docs: update readme --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 74ef30b6..e50e4ce2 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ Users familiar with CSS will feel at home with Lip Gloss. import "github.com/charmbracelet/lipgloss" -var style = lipgloss.NewStyle("Hello, kitty."). +var style = lipgloss.NewStyle(). + SetString("Hello, kitty."). Bold(true). Foreground(lipgloss.Color("#FAFAFA")). Background(lipgloss.Color("#7D56F4")). @@ -173,7 +174,8 @@ var style = lipgloss.NewStyle(). Setting a minimum width and height is simple and straightforward. ```go -var style = lipgloss.NewStyle("What’s for lunch?"). +var style = lipgloss.NewStyle(). + SetString("What’s for lunch?"). Width(24). Height(32). Foreground(lipgloss.Color("63")) @@ -298,7 +300,7 @@ someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda") Generally, you just call the `Render(string...)` method on a `lipgloss.Style`: ```go -style := lipgloss.NewStyle("Hello,").Bold(true) +style := lipgloss.NewStyle(lipgloss.WithString("Hello,")).Bold(true) fmt.Println(style.Render("kitty.")) // Hello, kitty. fmt.Println(style.Render("puppy.")) // Hello, puppy. ``` @@ -306,7 +308,7 @@ fmt.Println(style.Render("puppy.")) // Hello, puppy. But you could also use the Stringer interface: ```go -var style = lipgloss.NewStyle("你好,猫咪。").Bold(true) +var style = lipgloss.NewStyle(lipgloss.WithString("你好,猫咪。")).Bold(true) fmt.Println(style) ``` From 776c15f0da16d2b1058a079ec6a08a2e1170d721 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 18 Nov 2022 16:02:18 -0500 Subject: [PATCH 047/126] chore(examples): update ssh example chore(examples): go mod tidy --- examples/go.mod | 3 +- examples/go.sum | 23 ++----- examples/ssh/main.go | 144 +++++++------------------------------------ 3 files changed, 29 insertions(+), 141 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 5908799c..9d86b46e 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -5,12 +5,11 @@ go 1.16 replace github.com/charmbracelet/lipgloss => ../ require ( - github.com/charmbracelet/bubbletea v0.20.0 github.com/charmbracelet/lipgloss v0.4.0 github.com/charmbracelet/wish v0.5.0 github.com/gliderlabs/ssh v0.3.4 github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 - github.com/muesli/termenv v0.13.0 + github.com/muesli/termenv v0.14.0 golang.org/x/term v0.0.0-20210422114643-f5beecf764ed ) diff --git a/examples/go.sum b/examples/go.sum index bb95ba2e..801a2a10 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -6,22 +6,19 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg= -github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= +github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= -github.com/charmbracelet/bubbletea v0.20.0 h1:/b8LEPgCbNr7WWZ2LuE/BV1/r4t5PyYJtDb+J3vpwxc= github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM= github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y= github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg= github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk= -github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -52,25 +49,23 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0= -github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= +github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= @@ -79,13 +74,9 @@ github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNX github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= @@ -129,5 +120,3 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/ssh/main.go b/examples/ssh/main.go index b5e543f1..8a451078 100644 --- a/examples/ssh/main.go +++ b/examples/ssh/main.go @@ -6,13 +6,11 @@ import ( "os" "strings" - tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/wish" lm "github.com/charmbracelet/wish/logging" "github.com/gliderlabs/ssh" "github.com/kr/pty" - "github.com/lucasb-eyer/go-colorful" "github.com/muesli/termenv" ) @@ -80,37 +78,38 @@ func main() { } w, _ := pty.Window.Width, pty.Window.Height - renderer := lipgloss.NewRenderer(lipgloss.WithTermenvOutput(output)) + renderer := lipgloss.NewRenderer(lipgloss.WithTermenvOutput(output), + lipgloss.WithColorProfile(termenv.TrueColor)) str := strings.Builder{} fmt.Fprintf(&str, "\n%s %s %s %s %s", - renderer.NewStyle("bold").Bold(true), - renderer.NewStyle("faint").Faint(true), - renderer.NewStyle("italic").Italic(true), - renderer.NewStyle("underline").Underline(true), - renderer.NewStyle("crossout").Strikethrough(true), + renderer.NewStyle().SetString("bold").Bold(true), + renderer.NewStyle().SetString("faint").Faint(true), + renderer.NewStyle().SetString("italic").Italic(true), + renderer.NewStyle().SetString("underline").Underline(true), + renderer.NewStyle().SetString("crossout").Strikethrough(true), ) fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s", - renderer.NewStyle("red").Foreground(lipgloss.Color("#E88388")), - renderer.NewStyle("green").Foreground(lipgloss.Color("#A8CC8C")), - renderer.NewStyle("yellow").Foreground(lipgloss.Color("#DBAB79")), - renderer.NewStyle("blue").Foreground(lipgloss.Color("#71BEF2")), - renderer.NewStyle("magenta").Foreground(lipgloss.Color("#D290E4")), - renderer.NewStyle("cyan").Foreground(lipgloss.Color("#66C2CD")), - renderer.NewStyle("gray").Foreground(lipgloss.Color("#B9BFCA")), + renderer.NewStyle().SetString("red").Foreground(lipgloss.Color("#E88388")), + renderer.NewStyle().SetString("green").Foreground(lipgloss.Color("#A8CC8C")), + renderer.NewStyle().SetString("yellow").Foreground(lipgloss.Color("#DBAB79")), + renderer.NewStyle().SetString("blue").Foreground(lipgloss.Color("#71BEF2")), + renderer.NewStyle().SetString("magenta").Foreground(lipgloss.Color("#D290E4")), + renderer.NewStyle().SetString("cyan").Foreground(lipgloss.Color("#66C2CD")), + renderer.NewStyle().SetString("gray").Foreground(lipgloss.Color("#B9BFCA")), ) fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s\n\n", - renderer.NewStyle("red").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#E88388")), - renderer.NewStyle("green").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#A8CC8C")), - renderer.NewStyle("yellow").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#DBAB79")), - renderer.NewStyle("blue").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#71BEF2")), - renderer.NewStyle("magenta").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#D290E4")), - renderer.NewStyle("cyan").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#66C2CD")), - renderer.NewStyle("gray").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#B9BFCA")), + renderer.NewStyle().SetString("red").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#E88388")), + renderer.NewStyle().SetString("green").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#A8CC8C")), + renderer.NewStyle().SetString("yellow").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#DBAB79")), + renderer.NewStyle().SetString("blue").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#71BEF2")), + renderer.NewStyle().SetString("magenta").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#D290E4")), + renderer.NewStyle().SetString("cyan").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#66C2CD")), + renderer.NewStyle().SetString("gray").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#B9BFCA")), ) - fmt.Fprintf(&str, "%s %t\n", renderer.NewStyle("Has dark background?").Bold(true), renderer.HasDarkBackground()) + fmt.Fprintf(&str, "%s %t\n", renderer.NewStyle().SetString("Has dark background?").Bold(true), renderer.HasDarkBackground()) fmt.Fprintln(&str) wish.WriteString(s, renderer.Place(w, lipgloss.Height(str.String()), lipgloss.Center, lipgloss.Center, str.String())) @@ -129,102 +128,3 @@ func main() { log.Fatal(err) } } - -type model struct { - detectedProfile termenv.Profile - renderer *lipgloss.Renderer -} - -func (m model) Init() tea.Cmd { - return nil -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch msg.String() { - case "ctrl+c", "q": - return m, tea.Quit - } - } - return m, nil -} - -func (m model) View() string { - s := strings.Builder{} - m.renderer.SetColorProfile(m.detectedProfile) - fmt.Fprintf(&s, "Detected Color Profile: %s\n", colorProfile(m.renderer.ColorProfile())) - - // Color grid - fmt.Fprintf(&s, "%s\n", colors(m.renderer)) - - // Enforced color profile - m.renderer.SetColorProfile(termenv.TrueColor) - fmt.Fprintf(&s, "Enforce True Color\n") - - // Color grid - fmt.Fprintf(&s, "%s\n", colors(m.renderer)) - - // Detect dark background - fmt.Fprintf(&s, "Has Dark Background: %v\n", m.renderer.HasDarkBackground()) - - return s.String() -} - -func colors(r *lipgloss.Renderer) string { - colors := colorGrid(14, 8) - - b := strings.Builder{} - for _, x := range colors { - for _, y := range x { - s := r.NewStyle(" ").Background(lipgloss.Color(y)) - b.WriteString(s.String()) - } - b.WriteRune('\n') - } - - return b.String() -} - -func colorGrid(xSteps, ySteps int) [][]string { - x0y0, _ := colorful.Hex("#F25D94") - x1y0, _ := colorful.Hex("#EDFF82") - x0y1, _ := colorful.Hex("#643AFF") - x1y1, _ := colorful.Hex("#14F9D5") - - x0 := make([]colorful.Color, ySteps) - for i := range x0 { - x0[i] = x0y0.BlendLuv(x0y1, float64(i)/float64(ySteps)) - } - - x1 := make([]colorful.Color, ySteps) - for i := range x1 { - x1[i] = x1y0.BlendLuv(x1y1, float64(i)/float64(ySteps)) - } - - grid := make([][]string, ySteps) - for x := 0; x < ySteps; x++ { - y0 := x0[x] - grid[x] = make([]string, xSteps) - for y := 0; y < xSteps; y++ { - grid[x][y] = y0.BlendLuv(x1[x], float64(y)/float64(xSteps)).Hex() - } - } - - return grid -} - -func colorProfile(p termenv.Profile) string { - switch p { - case termenv.TrueColor: - return "True Color" - case termenv.ANSI256: - return "ANSI 256" - case termenv.ANSI: - return "ANSI" - case termenv.Ascii: - return "No Color" - default: - return "Unknown" - } -} From a74950e6da1649267bceda86bc4450fce6859141 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 22 Feb 2023 16:34:11 +0100 Subject: [PATCH 048/126] fix: don't concurrently change output profiles Multiple lipgloss renderer instances can end up using the same default termenv.Output. This leads to a race condition when manipulating the ColorProfile concurrently. We could mutex protect the standard output in termenv, but it feels like a weak promise, as the rest of the Output wouldn't (and probably shouldn't) get protected. Protecting it in lipgloss itself would require a global lock however. I can't come up with a proper use-case for this scenario, and therefore we shift this responsibility to the user. --- style_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/style_test.go b/style_test.go index d1d68539..d0a0ee0f 100644 --- a/style_test.go +++ b/style_test.go @@ -58,8 +58,6 @@ func TestStyleRender(t *testing.T) { } func TestStyleCustomRender(t *testing.T) { - t.Parallel() - r := NewRenderer(WithColorProfile(termenv.TrueColor), WithDarkBackground(false)) tt := []struct { style Style From 1eab58f1d8b3535b2332be800c905c484c3592dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Donk=C3=B3?= Date: Tue, 21 Feb 2023 08:41:56 +0100 Subject: [PATCH 049/126] docs: fix the description of `MaxHeight` --- set.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/set.go b/set.go index 38a0ed17..35dbcc83 100644 --- a/set.go +++ b/set.go @@ -492,8 +492,8 @@ func (s Style) MaxWidth(n int) Style { return o } -// MaxHeight applies a max width to a given style. This is useful in enforcing -// a certain width at render time, particularly with arbitrary strings and +// MaxHeight applies a max height to a given style. This is useful in enforcing +// a certain height at render time, particularly with arbitrary strings and // styles. // // Because this in intended to be used at the time of render, this method will From 4eea195dfa42809c0473678742da6d65d594c028 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 6 Mar 2023 16:54:13 -0500 Subject: [PATCH 050/126] feat(go): upgrade golang to 1.17 feat(examples): upgrade golang to 1.17 --- .github/workflows/build.yml | 2 +- examples/go.mod | 18 ++++++++++++++++-- examples/go.sum | 38 +++++++++++++++++++++++++++++++++---- go.mod | 10 +++++++++- go.sum | 6 ++++-- 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5712dc3b..56fe7616 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [~1.13, ^1] + go-version: [~1.17, ^1] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: diff --git a/examples/go.mod b/examples/go.mod index 9d86b46e..9dd8fbad 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,6 +1,6 @@ module examples -go 1.16 +go 1.17 replace github.com/charmbracelet/lipgloss => ../ @@ -11,5 +11,19 @@ require ( github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/muesli/termenv v0.14.0 - golang.org/x/term v0.0.0-20210422114643-f5beecf764ed + golang.org/x/term v0.6.0 +) + +require ( + github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/aymanbagabas/go-osc52 v1.2.2 // indirect + github.com/caarlos0/sshmarshal v0.1.0 // indirect + github.com/charmbracelet/keygen v0.3.0 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index 801a2a10..fa3fbadd 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -6,8 +6,9 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.2 h1:NT7wkhEhPTcKnBCdPi9djmyy9L3JOL4+3SsfJyqptCo= +github.com/aymanbagabas/go-osc52 v1.2.2/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= @@ -78,17 +79,31 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -101,15 +116,30 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.mod b/go.mod index 9394ac3e..7068e688 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,17 @@ module github.com/charmbracelet/lipgloss -go 1.15 +go 1.17 require ( github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.14.0 ) + +require ( + github.com/aymanbagabas/go-osc52 v1.2.2 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + golang.org/x/sys v0.6.0 // indirect +) diff --git a/go.sum b/go.sum index 0db3c7c8..e454af54 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ -github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E= github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52 v1.2.2 h1:NT7wkhEhPTcKnBCdPi9djmyy9L3JOL4+3SsfJyqptCo= +github.com/aymanbagabas/go-osc52 v1.2.2/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= @@ -14,5 +15,6 @@ github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From d09532a91a2324dc095c40541958829f7538b92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20G=C3=96R=C3=96G?= Date: Tue, 18 Oct 2022 21:56:45 +0200 Subject: [PATCH 051/126] chore: unify get border size function names --- get.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/get.go b/get.go index 159792a6..eb24a4ed 100644 --- a/get.go +++ b/get.go @@ -290,7 +290,16 @@ func (s Style) GetBorderLeftBackground() TerminalColor { // GetBorderTopWidth returns the width of the top border. If borders contain // runes of varying widths, the widest rune is returned. If no border exists on // the top edge, 0 is returned. +// +// Deprecated: This function simply calls Style.GetBorderTopSize. func (s Style) GetBorderTopWidth() int { + return s.GetBorderTopSize() +} + +// GetBorderTopSize returns the width of the top border. If borders contain +// runes of varying widths, the widest rune is returned. If no border exists on +// the top edge, 0 is returned. +func (s Style) GetBorderTopSize() int { if !s.getAsBool(borderTopKey, false) { return 0 } From c5382b35ef1c9ae24d02379e46388720a6cae40f Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 2 Mar 2023 16:53:19 -0500 Subject: [PATCH 052/126] ref(renderer): change renderer impl --- renderer.go | 47 +++++++++++++++++------------------------------ renderer_test.go | 10 ++++++---- set.go | 7 +++++++ style.go | 19 +++---------------- style_test.go | 20 ++++++++++++++++++-- 5 files changed, 51 insertions(+), 52 deletions(-) diff --git a/renderer.go b/renderer.go index 28ca477e..099e4b42 100644 --- a/renderer.go +++ b/renderer.go @@ -2,11 +2,12 @@ package lipgloss import ( "io" + "os" "github.com/muesli/termenv" ) -var renderer = NewRenderer() +var renderer = NewRenderer(os.Stdout) // Renderer is a lipgloss terminal renderer. type Renderer struct { @@ -22,43 +23,29 @@ func DefaultRenderer() *Renderer { return renderer } +// SetDefaultRenderer sets the default global renderer. +func SetDefaultRenderer(r *Renderer) { + renderer = r +} + // NewRenderer creates a new Renderer. -func NewRenderer(options ...RendererOption) *Renderer { +// +// w will be used to determine the terminal's color capabilities. +func NewRenderer(w io.Writer, opts ...termenv.OutputOption) *Renderer { r := &Renderer{ - output: termenv.DefaultOutput(), - } - for _, option := range options { - option(r) + output: termenv.NewOutput(w, opts...), } return r } -// WithOutput sets the io.Writer to be used for rendering. -func WithOutput(w io.Writer) RendererOption { - return WithTermenvOutput(termenv.NewOutput(w)) +// Output returns the termenv output. +func (r *Renderer) Output() *termenv.Output { + return r.output } -// WithTermenvOutput sets the termenv Output to use for rendering. -func WithTermenvOutput(output *termenv.Output) RendererOption { - return func(r *Renderer) { - r.output = output - } -} - -// WithDarkBackground can force the renderer to use a light/dark background. -func WithDarkBackground(dark bool) RendererOption { - return func(r *Renderer) { - r.SetHasDarkBackground(dark) - } -} - -// WithColorProfile sets the color profile on the renderer. This function is -// primarily intended for testing. For details, see the note on -// [Renderer.SetColorProfile]. -func WithColorProfile(p termenv.Profile) RendererOption { - return func(r *Renderer) { - r.SetColorProfile(p) - } +// SetOutput sets the termenv output. +func (r *Renderer) SetOutput(o *termenv.Output) { + r.output = o } // ColorProfile returns the detected termenv color profile. diff --git a/renderer_test.go b/renderer_test.go index 2be6430e..7f05acd7 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -8,11 +8,13 @@ import ( ) func TestRendererHasDarkBackground(t *testing.T) { - r1 := NewRenderer(WithDarkBackground(false)) + r1 := NewRenderer(os.Stdout) + r1.SetHasDarkBackground(false) if r1.HasDarkBackground() { t.Error("Expected renderer to have light background") } - r2 := NewRenderer(WithDarkBackground(true)) + r2 := NewRenderer(os.Stdout) + r2.SetHasDarkBackground(true) if !r2.HasDarkBackground() { t.Error("Expected renderer to have dark background") } @@ -25,8 +27,8 @@ func TestRendererWithOutput(t *testing.T) { } defer f.Close() defer os.Remove(f.Name()) - output := termenv.NewOutput(f, termenv.WithProfile(termenv.TrueColor)) - r := NewRenderer(WithTermenvOutput(output)) + r := NewRenderer(f) + r.SetColorProfile(termenv.TrueColor) if r.output.Profile != termenv.TrueColor { t.Error("Expected renderer to use true color") } diff --git a/set.go b/set.go index 35dbcc83..f8bf9a22 100644 --- a/set.go +++ b/set.go @@ -520,6 +520,13 @@ func (s Style) StrikethroughSpaces(v bool) Style { return s } +// Renderer sets the renderer for the style. This is useful for changing the +// renderer for a style that is being used in a different context. +func (s Style) Renderer(r *Renderer) Style { + s.r = r + return s +} + // whichSidesInt is a helper method for setting values on sides of a block based // on the number of arguments. It follows the CSS shorthand rules for blocks // like margin, padding. and borders. Here are how the rules work: diff --git a/style.go b/style.go index 395529a1..54f6d39b 100644 --- a/style.go +++ b/style.go @@ -75,33 +75,20 @@ const ( // A set of properties. type rules map[propKey]interface{} -// StyleOption is a function that applies a style option to a Style. -type StyleOption func(*Style) - -// WithString sets the underlying string value for this style. -func WithString(strs ...string) StyleOption { - return func(s *Style) { - s.value = joinString(strs...) - } -} - // NewStyle returns a new, empty Style. While it's syntactic sugar for the // Style{} primitive, it's recommended to use this function for creating styles // in case the underlying implementation changes. It takes an optional string // value to be set as the underlying string value for this style. -func NewStyle(opts ...StyleOption) Style { - return renderer.NewStyle(opts...) +func NewStyle() Style { + return renderer.NewStyle() } // NewStyle returns a new, empty Style. While it's syntactic sugar for the // Style{} primitive, it's recommended to use this function for creating styles // in case the underlying implementation changes. It takes an optional string // value to be set as the underlying string value for this style. -func (r *Renderer) NewStyle(opts ...StyleOption) Style { +func (r *Renderer) NewStyle() Style { s := Style{r: r} - for _, opt := range opts { - opt(&s) - } return s } diff --git a/style_test.go b/style_test.go index d0a0ee0f..5e1eb731 100644 --- a/style_test.go +++ b/style_test.go @@ -1,6 +1,7 @@ package lipgloss import ( + "io/ioutil" "reflect" "testing" @@ -58,7 +59,9 @@ func TestStyleRender(t *testing.T) { } func TestStyleCustomRender(t *testing.T) { - r := NewRenderer(WithColorProfile(termenv.TrueColor), WithDarkBackground(false)) + r := NewRenderer(ioutil.Discard) + r.SetHasDarkBackground(false) + r.SetColorProfile(termenv.TrueColor) tt := []struct { style Style expected string @@ -91,6 +94,10 @@ func TestStyleCustomRender(t *testing.T) { r.NewStyle().Faint(true), "\x1b[2mhello\x1b[0m", }, + { + NewStyle().Faint(true).Renderer(r), + "\x1b[2mhello\x1b[0m", + }, } for i, tc := range tt { @@ -104,6 +111,15 @@ func TestStyleCustomRender(t *testing.T) { } } +func TestStyleRenderer(t *testing.T) { + r := NewRenderer(ioutil.Discard) + s1 := NewStyle().Bold(true) + s2 := s1.Renderer(r) + if s1.r == s2.r { + t.Fatalf("expected different renderers") + } +} + func TestValueCopy(t *testing.T) { t.Parallel() @@ -323,7 +339,7 @@ func TestStyleValue(t *testing.T) { }, { name: "new style with string", - style: NewStyle(WithString("bar", "foobar")), + style: NewStyle().SetString("bar", "foobar"), expected: "bar foobar foo", }, } From b3440ac41fe6705b548067772dc7c9cd31d8b791 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 8 Mar 2023 12:14:19 -0500 Subject: [PATCH 053/126] feat(go): bump termenv to v0.15 --- examples/go.mod | 8 ++++---- examples/go.sum | 41 ++++++----------------------------------- go.mod | 4 ++-- go.sum | 9 ++++----- 4 files changed, 16 insertions(+), 46 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 9dd8fbad..d613d63b 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -10,13 +10,13 @@ require ( github.com/gliderlabs/ssh v0.3.4 github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 - github.com/muesli/termenv v0.14.0 - golang.org/x/term v0.6.0 + github.com/muesli/termenv v0.15.0 + golang.org/x/term v0.0.0-20210422114643-f5beecf764ed ) require ( github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect - github.com/aymanbagabas/go-osc52 v1.2.2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/charmbracelet/keygen v0.3.0 // indirect github.com/mattn/go-isatty v0.0.17 // indirect @@ -24,6 +24,6 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/crypto v0.7.0 // indirect + golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect golang.org/x/sys v0.6.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index fa3fbadd..0651f5b2 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -6,9 +6,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= -github.com/aymanbagabas/go-osc52 v1.2.2 h1:NT7wkhEhPTcKnBCdPi9djmyy9L3JOL4+3SsfJyqptCo= -github.com/aymanbagabas/go-osc52 v1.2.2/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= @@ -62,8 +61,8 @@ github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBc github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= -github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/muesli/termenv v0.15.0 h1:ZYfCF4CZGhAA4meilZ5pd7tfUX4QLH4zB7OBie4RMS8= +github.com/muesli/termenv v0.15.0/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -79,31 +78,17 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -116,30 +101,16 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.mod b/go.mod index 7068e688..bd53b2cc 100644 --- a/go.mod +++ b/go.mod @@ -5,11 +5,11 @@ go 1.17 require ( github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.14.0 + github.com/muesli/termenv v0.15.0 ) require ( - github.com/aymanbagabas/go-osc52 v1.2.2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/rivo/uniseg v0.2.0 // indirect diff --git a/go.sum b/go.sum index e454af54..f2a6dcb2 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,5 @@ -github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= -github.com/aymanbagabas/go-osc52 v1.2.2 h1:NT7wkhEhPTcKnBCdPi9djmyy9L3JOL4+3SsfJyqptCo= -github.com/aymanbagabas/go-osc52 v1.2.2/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= @@ -10,8 +9,8 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.14.0 h1:8x9NFfOe8lmIWK4pgy3IfVEy47f+ppe3tUqdPZG2Uy0= -github.com/muesli/termenv v0.14.0/go.mod h1:kG/pF1E7fh949Xhe156crRUrHNyK221IuGO7Ez60Uc8= +github.com/muesli/termenv v0.15.0 h1:ZYfCF4CZGhAA4meilZ5pd7tfUX4QLH4zB7OBie4RMS8= +github.com/muesli/termenv v0.15.0/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= From 19ca9a3f8c1edaff2cc9e4029ecef666367d2551 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 8 Mar 2023 14:51:26 -0500 Subject: [PATCH 054/126] docs: renderer documentation (#175) * docs(readme): add some context to the examples * docs(readme): revert to render-function-based initial example * docs(readme): drop extraneous stringer usage * docs(readme): copyedits * docs(renderer): minor documentation improvements * docs(readme): edit renderer section to focus on custom outputs * docs(readme): for now just use SetString to illustrate stringer * docs(readme): re-add Ayman's clever stringer example * docs(examples): tidy up wish example * docs(examples): improve wish example * docs(examples): session is an io.Writer Co-authored-by: Ayman Bagabas * docs(examples): add missing pty argument Co-authored-by: Ayman Bagabas * docs(example): remove extra space * fix(examples): use termenv output --------- Co-authored-by: Ayman Bagabas --- README.md | 36 ++++---- examples/layout/main.go | 2 + examples/ssh/main.go | 192 +++++++++++++++++++++++++++------------- renderer.go | 22 ++--- 4 files changed, 162 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index e50e4ce2..a071564e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,6 @@ Users familiar with CSS will feel at home with Lip Gloss. import "github.com/charmbracelet/lipgloss" var style = lipgloss.NewStyle(). - SetString("Hello, kitty."). Bold(true). Foreground(lipgloss.Color("#FAFAFA")). Background(lipgloss.Color("#7D56F4")). @@ -28,7 +27,7 @@ var style = lipgloss.NewStyle(). PaddingLeft(4). Width(22) -fmt.Println(style) +fmt.Println(style.Render("Hello, kitty")) ``` ## Colors @@ -300,7 +299,7 @@ someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda") Generally, you just call the `Render(string...)` method on a `lipgloss.Style`: ```go -style := lipgloss.NewStyle(lipgloss.WithString("Hello,")).Bold(true) +style := lipgloss.NewStyle().Bold(true).SetString("Hello,") fmt.Println(style.Render("kitty.")) // Hello, kitty. fmt.Println(style.Render("puppy.")) // Hello, puppy. ``` @@ -308,29 +307,32 @@ fmt.Println(style.Render("puppy.")) // Hello, puppy. But you could also use the Stringer interface: ```go -var style = lipgloss.NewStyle(lipgloss.WithString("你好,猫咪。")).Bold(true) - -fmt.Println(style) +var style = lipgloss.NewStyle().SetString("你好,猫咪。").Bold(true) +fmt.Println(style) // 你好,猫咪。 ``` ### Custom Renderers -Use custom renderers to enforce rendering your styles in a specific way. You can -specify the color profile to use, True Color, ANSI 256, 8-bit ANSI, or good ol' -ASCII. You can also specify whether or not to assume dark background colors. +Custom renderers allow you to render to a specific outputs. This is +particularly important when you want to render to different outputs and +correctly detect the color profile and dark background status for each, such as +in a server-client situation. ```go -renderer := lipgloss.NewRenderer( - lipgloss.WithColorProfile(termenv.ANSI256), - lipgloss.WithDarkBackground(true), -) +func myLittleHandler(sess ssh.Session) { + // Create a renderer for the client. + renderer := lipgloss.NewRenderer(sess) + + // Create a new style on the renderer. + style := renderer.NewStyle().Background(lipgloss.AdaptiveColor{Light: "63", Dark: "228"}) -var style = renderer.NewStyle().Background(lipgloss.AdaptiveColor{Light: "63", Dark: "228"}) -fmt.Println(style.Render("Lip Gloss")) // This will always use the dark background color + // Render. The color profile and dark background state will be correctly detected. + io.WriteString(sess, style.Render("Heyyyyyyy")) +} ``` -This is also useful when using lipgloss with an SSH server like [Wish][wish]. -See the [ssh example][ssh-example] for more details. +For an example on using a custom renderer over SSH with [Wish][wish] see the +[SSH example][ssh-example]. ## Utilities diff --git a/examples/layout/main.go b/examples/layout/main.go index 8ec832b8..e68cf772 100644 --- a/examples/layout/main.go +++ b/examples/layout/main.go @@ -1,5 +1,7 @@ package main +// This example demonstrates various Lip Gloss style and layout features. + import ( "fmt" "os" diff --git a/examples/ssh/main.go b/examples/ssh/main.go index 8a451078..cd23b052 100644 --- a/examples/ssh/main.go +++ b/examples/ssh/main.go @@ -1,5 +1,14 @@ package main +// This example demonstrates how to use a custom Lip Gloss renderer with Wish, +// a package for building custom SSH servers. +// +// The big advantage to using custom renderers here is that we can accurately +// detect the background color and color profile for each client and render +// against that accordingly. +// +// For details on wish see: https://github.com/charmbracelet/wish/ + import ( "fmt" "log" @@ -14,6 +23,41 @@ import ( "github.com/muesli/termenv" ) +// Available styles. +type styles struct { + bold lipgloss.Style + faint lipgloss.Style + italic lipgloss.Style + underline lipgloss.Style + strikethrough lipgloss.Style + red lipgloss.Style + green lipgloss.Style + yellow lipgloss.Style + blue lipgloss.Style + magenta lipgloss.Style + cyan lipgloss.Style + gray lipgloss.Style +} + +// Create new styles against a given renderer. +func makeStyles(r *lipgloss.Renderer) styles { + return styles{ + bold: r.NewStyle().SetString("bold").Bold(true), + faint: r.NewStyle().SetString("faint").Faint(true), + italic: r.NewStyle().SetString("italic").Italic(true), + underline: r.NewStyle().SetString("underline").Underline(true), + strikethrough: r.NewStyle().SetString("strikethrough").Strikethrough(true), + red: r.NewStyle().SetString("red").Foreground(lipgloss.Color("#E88388")), + green: r.NewStyle().SetString("green").Foreground(lipgloss.Color("#A8CC8C")), + yellow: r.NewStyle().SetString("yellow").Foreground(lipgloss.Color("#DBAB79")), + blue: r.NewStyle().SetString("blue").Foreground(lipgloss.Color("#71BEF2")), + magenta: r.NewStyle().SetString("magenta").Foreground(lipgloss.Color("#D290E4")), + cyan: r.NewStyle().SetString("cyan").Foreground(lipgloss.Color("#66C2CD")), + gray: r.NewStyle().SetString("gray").Foreground(lipgloss.Color("#B9BFCA")), + } +} + +// Bridge Wish and Termenv so we can query for a user's terminal capabilities. type sshOutput struct { ssh.Session tty *os.File @@ -23,6 +67,10 @@ func (s *sshOutput) Write(p []byte) (int, error) { return s.Session.Write(p) } +func (s *sshOutput) Read(p []byte) (int, error) { + return s.Session.Read(p) +} + func (s *sshOutput) Fd() uintptr { return s.tty.Fd() } @@ -44,86 +92,104 @@ func (s *sshEnviron) Environ() []string { return s.environ } -func outputFromSession(s ssh.Session) *termenv.Output { - sshPty, _, _ := s.Pty() +// Create a termenv.Output from the session. +func outputFromSession(sess ssh.Session) *termenv.Output { + sshPty, _, _ := sess.Pty() _, tty, err := pty.Open() if err != nil { - panic(err) + log.Fatal(err) } o := &sshOutput{ - Session: s, + Session: sess, tty: tty, } - environ := s.Environ() + environ := sess.Environ() environ = append(environ, fmt.Sprintf("TERM=%s", sshPty.Term)) - e := &sshEnviron{ - environ: environ, + e := &sshEnviron{environ: environ} + // We need to use unsafe mode here because the ssh session is not running + // locally and we already know that the session is a TTY. + return termenv.NewOutput(o, termenv.WithUnsafe(), termenv.WithEnvironment(e)) +} + +// Handle SSH requests. +func handler(next ssh.Handler) ssh.Handler { + return func(sess ssh.Session) { + // Get client's output. + clientOutput := outputFromSession(sess) + + pty, _, active := sess.Pty() + if !active { + next(sess) + return + } + width := pty.Window.Width + + // Initialize new renderer for the client. + renderer := lipgloss.NewRenderer(sess) + renderer.SetOutput(clientOutput) + + // Initialize new styles against the renderer. + styles := makeStyles(renderer) + + str := strings.Builder{} + + fmt.Fprintf(&str, "\n\n%s %s %s %s %s", + styles.bold, + styles.faint, + styles.italic, + styles.underline, + styles.strikethrough, + ) + + fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s", + styles.red, + styles.green, + styles.yellow, + styles.blue, + styles.magenta, + styles.cyan, + styles.gray, + ) + + fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s\n\n", + styles.red, + styles.green, + styles.yellow, + styles.blue, + styles.magenta, + styles.cyan, + styles.gray, + ) + + fmt.Fprintf(&str, "%s %t %s\n\n", styles.bold.Copy().UnsetString().Render("Has dark background?"), + renderer.HasDarkBackground(), + renderer.Output().BackgroundColor()) + + block := renderer.Place(width, + lipgloss.Height(str.String()), lipgloss.Center, lipgloss.Center, str.String(), + lipgloss.WithWhitespaceChars("/"), + lipgloss.WithWhitespaceForeground(lipgloss.AdaptiveColor{Light: "250", Dark: "236"}), + ) + + // Render to client. + wish.WriteString(sess, block) + + next(sess) } - return termenv.NewOutput(o, termenv.WithEnvironment(e)) } func main() { - addr := ":3456" + port := 3456 s, err := wish.NewServer( - wish.WithAddress(addr), + wish.WithAddress(fmt.Sprintf(":%d", port)), wish.WithHostKeyPath("ssh_example"), - wish.WithMiddleware( - func(sh ssh.Handler) ssh.Handler { - return func(s ssh.Session) { - output := outputFromSession(s) - pty, _, active := s.Pty() - if !active { - sh(s) - return - } - w, _ := pty.Window.Width, pty.Window.Height - - renderer := lipgloss.NewRenderer(lipgloss.WithTermenvOutput(output), - lipgloss.WithColorProfile(termenv.TrueColor)) - str := strings.Builder{} - fmt.Fprintf(&str, "\n%s %s %s %s %s", - renderer.NewStyle().SetString("bold").Bold(true), - renderer.NewStyle().SetString("faint").Faint(true), - renderer.NewStyle().SetString("italic").Italic(true), - renderer.NewStyle().SetString("underline").Underline(true), - renderer.NewStyle().SetString("crossout").Strikethrough(true), - ) - - fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s", - renderer.NewStyle().SetString("red").Foreground(lipgloss.Color("#E88388")), - renderer.NewStyle().SetString("green").Foreground(lipgloss.Color("#A8CC8C")), - renderer.NewStyle().SetString("yellow").Foreground(lipgloss.Color("#DBAB79")), - renderer.NewStyle().SetString("blue").Foreground(lipgloss.Color("#71BEF2")), - renderer.NewStyle().SetString("magenta").Foreground(lipgloss.Color("#D290E4")), - renderer.NewStyle().SetString("cyan").Foreground(lipgloss.Color("#66C2CD")), - renderer.NewStyle().SetString("gray").Foreground(lipgloss.Color("#B9BFCA")), - ) - - fmt.Fprintf(&str, "\n%s %s %s %s %s %s %s\n\n", - renderer.NewStyle().SetString("red").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#E88388")), - renderer.NewStyle().SetString("green").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#A8CC8C")), - renderer.NewStyle().SetString("yellow").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#DBAB79")), - renderer.NewStyle().SetString("blue").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#71BEF2")), - renderer.NewStyle().SetString("magenta").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#D290E4")), - renderer.NewStyle().SetString("cyan").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#66C2CD")), - renderer.NewStyle().SetString("gray").Foreground(lipgloss.Color("0")).Background(lipgloss.Color("#B9BFCA")), - ) - - fmt.Fprintf(&str, "%s %t\n", renderer.NewStyle().SetString("Has dark background?").Bold(true), renderer.HasDarkBackground()) - fmt.Fprintln(&str) - - wish.WriteString(s, renderer.Place(w, lipgloss.Height(str.String()), lipgloss.Center, lipgloss.Center, str.String())) - - sh(s) - } - }, - lm.Middleware(), - ), + wish.WithMiddleware(handler, lm.Middleware()), ) if err != nil { log.Fatal(err) } - log.Printf("Listening on %s", addr) + log.Printf("SSH server listening on port %d", port) + log.Printf("To connect from your local machine run: ssh localhost -p %d", port) if err := s.ListenAndServe(); err != nil { log.Fatal(err) } diff --git a/renderer.go b/renderer.go index 099e4b42..b5e115d1 100644 --- a/renderer.go +++ b/renderer.go @@ -15,7 +15,7 @@ type Renderer struct { hasDarkBackground *bool } -// RendererOption is a function that can be used to configure a Renderer. +// RendererOption is a function that can be used to configure a [Renderer]. type RendererOption func(r *Renderer) // DefaultRenderer returns the default renderer. @@ -68,10 +68,10 @@ func ColorProfile() termenv.Profile { // // Available color profiles are: // -// termenv.Ascii (no color, 1-bit) -// termenv.ANSI (16 colors, 4-bit) -// termenv.ANSI256 (256 colors, 8-bit) -// termenv.TrueColor (16,777,216 colors, 24-bit) +// termenv.Ascii // no color, 1-bit +// termenv.ANSI //16 colors, 4-bit +// termenv.ANSI256 // 256 colors, 8-bit +// termenv.TrueColor // 16,777,216 colors, 24-bit // // This function is thread-safe. func (r *Renderer) SetColorProfile(p termenv.Profile) { @@ -88,10 +88,10 @@ func (r *Renderer) SetColorProfile(p termenv.Profile) { // // Available color profiles are: // -// termenv.Ascii (no color, 1-bit) -// termenv.ANSI (16 colors, 4-bit) -// termenv.ANSI256 (256 colors, 8-bit) -// termenv.TrueColor (16,777,216 colors, 24-bit) +// termenv.Ascii // no color, 1-bit +// termenv.ANSI //16 colors, 4-bit +// termenv.ANSI256 // 256 colors, 8-bit +// termenv.TrueColor // 16,777,216 colors, 24-bit // // This function is thread-safe. func SetColorProfile(p termenv.Profile) { @@ -103,7 +103,9 @@ func HasDarkBackground() bool { return renderer.HasDarkBackground() } -// HasDarkBackground returns whether or not the terminal has a dark background. +// HasDarkBackground returns whether or not the renderer will render to a dark +// background. A dark background can either be auto-detected, or set explicitly +// on the renderer. func (r *Renderer) HasDarkBackground() bool { if r.hasDarkBackground != nil { return *r.hasDarkBackground From 7f47f0b5e0503783b2ec2e81305c334675e87a4c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 9 Mar 2023 13:23:40 -0500 Subject: [PATCH 055/126] fix(renderer): use termenv default renderer Creating a _new_ global termenv output clashes with the default global termenv output leading the terminal to block and freeze. Share the default termenv output with the global default lipgloss renderer. --- renderer.go | 7 +++++-- style.go | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/renderer.go b/renderer.go index b5e115d1..4bea8374 100644 --- a/renderer.go +++ b/renderer.go @@ -2,12 +2,15 @@ package lipgloss import ( "io" - "os" "github.com/muesli/termenv" ) -var renderer = NewRenderer(os.Stdout) +// We're manually creating the struct here to avoid initializing the output and +// query the terminal multiple times. +var renderer = &Renderer{ + output: termenv.DefaultOutput(), +} // Renderer is a lipgloss terminal renderer. type Renderer struct { diff --git a/style.go b/style.go index 54f6d39b..e94b8670 100644 --- a/style.go +++ b/style.go @@ -173,7 +173,7 @@ func (s Style) Inherit(i Style) Style { // Render applies the defined style formatting to a given string. func (s Style) Render(strs ...string) string { if s.r == nil { - s.r = DefaultRenderer() + s.r = renderer } if s.value != "" { strs = append([]string{s.value}, strs...) From c43b22e00f3438fc20e0718b682b3e98a9ae8919 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 9 Mar 2023 19:49:21 +0100 Subject: [PATCH 056/126] chore: bump termenv to v0.15.1 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bd53b2cc..a1376ba0 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.17 require ( github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.15.0 + github.com/muesli/termenv v0.15.1 ) require ( diff --git a/go.sum b/go.sum index f2a6dcb2..2c273fd1 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWV github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.0 h1:ZYfCF4CZGhAA4meilZ5pd7tfUX4QLH4zB7OBie4RMS8= -github.com/muesli/termenv v0.15.0/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= From 9afe8e2952f198e25722d06651eb218ad1599140 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Fri, 10 Mar 2023 10:28:23 -0300 Subject: [PATCH 057/126] fix: retract v0.7.0 (#181) Signed-off-by: Carlos A Becker --- go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/go.mod b/go.mod index a1376ba0..ced628d3 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,7 @@ module github.com/charmbracelet/lipgloss +retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. + go 1.17 require ( From 53cc780a5ac1728674689bacb8b0efbe5851f01b Mon Sep 17 00:00:00 2001 From: Glenn Gonda Date: Thu, 9 Mar 2023 23:24:10 -0800 Subject: [PATCH 058/126] chore: use io pkg for Discard --- style_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/style_test.go b/style_test.go index 5e1eb731..697e7d19 100644 --- a/style_test.go +++ b/style_test.go @@ -1,7 +1,7 @@ package lipgloss import ( - "io/ioutil" + "io" "reflect" "testing" @@ -59,7 +59,7 @@ func TestStyleRender(t *testing.T) { } func TestStyleCustomRender(t *testing.T) { - r := NewRenderer(ioutil.Discard) + r := NewRenderer(io.Discard) r.SetHasDarkBackground(false) r.SetColorProfile(termenv.TrueColor) tt := []struct { @@ -112,7 +112,7 @@ func TestStyleCustomRender(t *testing.T) { } func TestStyleRenderer(t *testing.T) { - r := NewRenderer(ioutil.Discard) + r := NewRenderer(io.Discard) s1 := NewStyle().Bold(true) s2 := s1.Renderer(r) if s1.r == s2.r { From 303724f7da66a59d25f9a2aff660667e55753dad Mon Sep 17 00:00:00 2001 From: Glenn Gonda Date: Sun, 12 Mar 2023 03:04:42 -0700 Subject: [PATCH 059/126] docs: fix typos and clean up comments (#182) --- color.go | 2 +- get.go | 18 +++++++++--------- join_test.go | 2 +- position.go | 4 ++-- set.go | 2 +- unset.go | 4 ++-- whitespace.go | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/color.go b/color.go index ef7fd279..adff1a6e 100644 --- a/color.go +++ b/color.go @@ -146,7 +146,7 @@ func (c CompleteColor) RGBA() (r, g, b, a uint32) { return termenv.ConvertToRGB(c.color(renderer)).RGBA() } -// CompleteColor specifies exact values for truecolor, ANSI256, and ANSI color +// CompleteAdaptiveColor specifies exact values for truecolor, ANSI256, and ANSI color // profiles, with separate options for light and dark backgrounds. Automatic // color degradation will not be performed. type CompleteAdaptiveColor struct { diff --git a/get.go b/get.go index eb24a4ed..be0055c3 100644 --- a/get.go +++ b/get.go @@ -53,7 +53,7 @@ func (s Style) GetForeground() TerminalColor { return s.getAsColor(foregroundKey) } -// GetBackground returns the style's back color. If no value is set +// GetBackground returns the style's background color. If no value is set // NoColor{} is returned. func (s Style) GetBackground() TerminalColor { return s.getAsColor(backgroundKey) @@ -191,7 +191,7 @@ func (s Style) GetHorizontalMargins() int { return s.getAsInt(marginLeftKey) + s.getAsInt(marginRightKey) } -// GetVerticalMargins returns the style's top and bottom padding. Unset values +// GetVerticalMargins returns the style's top and bottom margins. Unset values // are measured as 0. func (s Style) GetVerticalMargins() int { return s.getAsInt(marginTopKey) + s.getAsInt(marginBottomKey) @@ -257,7 +257,7 @@ func (s Style) GetBorderBottomForeground() TerminalColor { return s.getAsColor(borderBottomForegroundKey) } -// GetBorderLeftForeground returns the style's border bottom foreground +// GetBorderLeftForeground returns the style's border left foreground // color. If no value is set NoColor{} is returned. func (s Style) GetBorderLeftForeground() TerminalColor { return s.getAsColor(borderLeftForegroundKey) @@ -281,7 +281,7 @@ func (s Style) GetBorderBottomBackground() TerminalColor { return s.getAsColor(borderBottomBackgroundKey) } -// GetBorderLeftBackground returns the style's border bottom background +// GetBorderLeftBackground returns the style's border left background // color. If no value is set NoColor{} is returned. func (s Style) GetBorderLeftBackground() TerminalColor { return s.getAsColor(borderLeftBackgroundKey) @@ -344,9 +344,9 @@ func (s Style) GetHorizontalBorderSize() int { return b.GetLeftSize() + b.GetRightSize() } -// GetVerticalBorderSize returns the width of the horizontal borders. If +// GetVerticalBorderSize returns the width of the vertical borders. If // borders contain runes of varying widths, the widest rune is returned. If no -// border exists on the horizontal edges, 0 is returned. +// border exists on the vertical edges, 0 is returned. func (s Style) GetVerticalBorderSize() int { b := s.getBorderStyle() return b.GetTopSize() + b.GetBottomSize() @@ -364,7 +364,7 @@ func (s Style) GetMaxWidth() int { return s.getAsInt(maxWidthKey) } -// GetMaxHeight returns the style's max width setting. If no value is set 0 is +// GetMaxHeight returns the style's max height setting. If no value is set 0 is // returned. func (s Style) GetMaxHeight() int { return s.getAsInt(maxHeightKey) @@ -376,7 +376,7 @@ func (s Style) GetUnderlineSpaces() bool { return s.getAsBool(underlineSpacesKey, false) } -// GetStrikethroughSpaces returns whether or not the style is set to underline +// GetStrikethroughSpaces returns whether or not the style is set to strikethrough // spaces. If not value is set false is returned. func (s Style) GetStrikethroughSpaces() bool { return s.getAsBool(strikethroughSpacesKey, false) @@ -390,7 +390,7 @@ func (s Style) GetHorizontalFrameSize() int { return s.GetHorizontalMargins() + s.GetHorizontalPadding() + s.GetHorizontalBorderSize() } -// GetVerticalFrameSize returns the sum of the style's horizontal margins, padding +// GetVerticalFrameSize returns the sum of the style's vertical margins, padding // and border widths. // // Provisional: this method may be renamed. diff --git a/join_test.go b/join_test.go index 9dcf5137..cc82da30 100644 --- a/join_test.go +++ b/join_test.go @@ -9,7 +9,7 @@ func TestJoinVertical(t *testing.T) { expected string } tests := []test{ - {"por0", JoinVertical(0, "A", "BBBB"), "A \nBBBB"}, + {"pos0", JoinVertical(0, "A", "BBBB"), "A \nBBBB"}, {"pos1", JoinVertical(1, "A", "BBBB"), " A\nBBBB"}, {"pos0.25", JoinVertical(0.25, "A", "BBBB"), " A \nBBBB"}, } diff --git a/position.go b/position.go index 28f5ccbd..afefa8be 100644 --- a/position.go +++ b/position.go @@ -52,7 +52,7 @@ func PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOpti // PlaceHorizontal places a string or text block horizontally in an unstyled // block of a given width. If the given width is shorter than the max width of -// the string (measured by it's longest line) this will be a noöp. +// the string (measured by its longest line) this will be a noöp. func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ...WhitespaceOption) string { lines, contentWidth := getLines(str) gap := width - contentWidth @@ -106,7 +106,7 @@ func PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOptio // PlaceVertical places a string or text block vertically in an unstyled block // of a given height. If the given height is shorter than the height of the -// string (measured by it's newlines) then this will be a noöp. +// string (measured by its newlines) then this will be a noöp. func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...WhitespaceOption) string { contentHeight := strings.Count(str, "\n") + 1 gap := height - contentHeight diff --git a/set.go b/set.go index f8bf9a22..92e9f212 100644 --- a/set.go +++ b/set.go @@ -126,7 +126,7 @@ func (s Style) AlignHorizontal(p Position) Style { return s } -// AlignVertical sets a text alignment rule. +// AlignVertical sets a vertical text alignment rule. func (s Style) AlignVertical(p Position) Style { s.set(alignVerticalKey, p) return s diff --git a/unset.go b/unset.go index a8367898..4f8fe658 100644 --- a/unset.go +++ b/unset.go @@ -30,7 +30,7 @@ func (s Style) UnsetReverse() Style { return s } -// UnsetBlink removes the bold style rule, if set. +// UnsetBlink removes the blink style rule, if set. func (s Style) UnsetBlink() Style { delete(s.rules, blinkKey) return s @@ -112,7 +112,7 @@ func (s Style) UnsetPaddingTop() Style { return s } -// UnsetPaddingBottom removes the bottom style rule, if set. +// UnsetPaddingBottom removes the bottom padding style rule, if set. func (s Style) UnsetPaddingBottom() Style { delete(s.rules, paddingBottomKey) return s diff --git a/whitespace.go b/whitespace.go index b043e565..78815fed 100644 --- a/whitespace.go +++ b/whitespace.go @@ -15,7 +15,7 @@ type whitespace struct { } // newWhitespace creates a new whitespace renderer. The order of the options -// matters, it you'r using WithWhitespaceRenderer, make sure it comes first as +// matters, if you're using WithWhitespaceRenderer, make sure it comes first as // other options might depend on it. func newWhitespace(r *Renderer, opts ...WhitespaceOption) *whitespace { w := &whitespace{ From 403643d322f6b513e7cca624fae9aa49261582be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 00:05:08 +0000 Subject: [PATCH 060/126] chore(deps): bump actions/setup-go from 2 to 4 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 2 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v2...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56fe7616..fe654f55 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: GO111MODULE: "on" steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index b2266128..edf014db 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,7 +12,7 @@ jobs: GO111MODULE: "on" steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index b91419d7..f0b72e6c 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ^1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5b6cedc3..5ac17f9e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: ^1 From 06dd20ee5707e709919e06e986a2e922078afa5f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 00:05:03 +0000 Subject: [PATCH 061/126] chore(deps): bump actions/checkout from 3.3.0 to 3.4.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.3.0 to 3.4.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.3.0...v3.4.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fe654f55..44278d6d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.4.0 - name: Download Go modules run: go mod download diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index edf014db..50d2b0b2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.3.0 + uses: actions/checkout@v3.4.0 - name: Coverage env: diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index f0b72e6c..1b5f814b 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.4.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5ac17f9e..36b9b3be 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.3.0 + - uses: actions/checkout@v3.4.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: From da2d04b5347536769af11ef06fbb90b7024ab15f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 25 Mar 2023 00:02:19 +0000 Subject: [PATCH 062/126] chore(deps): bump actions/checkout from 3.4.0 to 3.5.0 Bumps [actions/checkout](https://github.com/actions/checkout) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 44278d6d..d07731b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.4.0 + uses: actions/checkout@v3.5.0 - name: Download Go modules run: go mod download diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 50d2b0b2..6182bd43 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.4.0 + uses: actions/checkout@v3.5.0 - name: Coverage env: diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 1b5f814b..07e59a5f 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.4.0 + - uses: actions/checkout@v3.5.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 36b9b3be..ba9f941c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.4.0 + - uses: actions/checkout@v3.5.0 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: From 4c3e61d4d182784833cc828845a3eded1bfe4b7c Mon Sep 17 00:00:00 2001 From: mieubrisse Date: Sat, 29 Apr 2023 10:51:31 -0300 Subject: [PATCH 063/126] Fix bugs with border size calculation --- .gitignore | 30 +++++++++++++++++++++++++++++- borders.go | 4 ++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a170af09..c6fe6f53 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,29 @@ -ssh_example_ed25519* \ No newline at end of file +ssh_example_ed25519* + +# IntelliJ +.idea +*.iws +*.iml +*.ipr + +# VS Code +*.vscode + +# Mac spotlight index files +.DS_Store + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Archive files +*.tgz +*.zip + + +# Node +node_modules +yarn-error.log diff --git a/borders.go b/borders.go index 18964221..2c493161 100644 --- a/borders.go +++ b/borders.go @@ -32,7 +32,7 @@ func (b Border) GetTopSize() int { // runes of varying widths, the widest rune is returned. If no border exists on // the right edge, 0 is returned. func (b Border) GetRightSize() int { - return getBorderEdgeWidth(b.TopRight, b.Top, b.BottomRight) + return getBorderEdgeWidth(b.TopRight, b.Right, b.BottomRight) } // GetBottomSize returns the width of the bottom border. If borders contain @@ -46,7 +46,7 @@ func (b Border) GetBottomSize() int { // of varying widths, the widest rune is returned. If no border exists on the // left edge, 0 is returned. func (b Border) GetLeftSize() int { - return getBorderEdgeWidth(b.TopLeft, b.Left, b.TopRight) + return getBorderEdgeWidth(b.TopLeft, b.Left, b.BottomLeft) } func getBorderEdgeWidth(borderParts ...string) (maxWidth int) { From 7fef6782b8408ffae3bd3dd333fc226590e457e5 Mon Sep 17 00:00:00 2001 From: mieubrisse Date: Sun, 30 Apr 2023 14:08:11 -0300 Subject: [PATCH 064/126] Undo additions to .gitignore --- .gitignore | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/.gitignore b/.gitignore index c6fe6f53..53e1c2b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1 @@ ssh_example_ed25519* - -# IntelliJ -.idea -*.iws -*.iml -*.ipr - -# VS Code -*.vscode - -# Mac spotlight index files -.DS_Store - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Archive files -*.tgz -*.zip - - -# Node -node_modules -yarn-error.log From 63540b90ab97e5121f8525e8f78ed293827dc18e Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 11 May 2023 12:39:34 +0000 Subject: [PATCH 065/126] docs: update license Signed-off-by: Carlos Alexandro Becker --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index ece3536f..6f5b1fa6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 Charmbracelet, Inc +Copyright (c) 2021-2023 Charmbracelet, Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 97bf676aa0a2e9191380320f81314dd32d0adb2d Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 12 May 2023 14:11:20 -0400 Subject: [PATCH 066/126] fix(ci): remove soft-serve workflow --- .github/workflows/soft-serve.yml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .github/workflows/soft-serve.yml diff --git a/.github/workflows/soft-serve.yml b/.github/workflows/soft-serve.yml deleted file mode 100644 index 8eb3221d..00000000 --- a/.github/workflows/soft-serve.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: soft-serve - -on: - push: - branches: - - master - -jobs: - soft-serve: - uses: charmbracelet/meta/.github/workflows/soft-serve.yml@main - secrets: - ssh-key: "${{ secrets.CHARM_SOFT_SERVE_KEY }}" From 9bc85709f2e262740d3c6d63a62368cfb2b6e293 Mon Sep 17 00:00:00 2001 From: bashbunni <15822994+bashbunni@users.noreply.github.com> Date: Tue, 23 May 2023 11:58:18 -0700 Subject: [PATCH 067/126] docs: add issue template (#193) * docs: add issue template * chore(template): add locale --- .github/ISSUE_TEMPLATE/bug_report.md | 38 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 5 +++ .github/ISSUE_TEMPLATE/feature_request.md | 20 ++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..c19be87a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**Setup** +Please complete the following information along with version numbers, if applicable. + - OS [e.g. Ubuntu, macOS] + - Shell [e.g. zsh, fish] + - Terminal Emulator [e.g. kitty, iterm] + - Terminal Multiplexer [e.g. tmux] + - Locale [e.g. en_US.UTF-8, zh_CN.UTF-8, etc.] + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Source Code** +Please include source code if needed to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +Add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..897a394e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: +- name: Discord + url: https://charm.sh/discord + about: Chat on our Discord. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..11fc491e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 859b49e84842d274841c4c1387d92be4776c7bd4 Mon Sep 17 00:00:00 2001 From: bashbunni <15822994+bashbunni@users.noreply.github.com> Date: Tue, 23 May 2023 11:58:52 -0700 Subject: [PATCH 068/126] docs: add FAQ (#189) * docs: add FAQ * chore(docs): update encoding/locale answer in FAQ * chore(docs): update color profile disclaimer * chore(docs): faq copyedits * chore(docs): additional faq copy edits * fix(docs): add missing closing code block in faq --------- Co-authored-by: Christian Rocha --- README.md | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/README.md b/README.md index a071564e..0fc51bdc 100644 --- a/README.md +++ b/README.md @@ -400,6 +400,45 @@ You can also style the whitespace. For details, see [the docs][docs]. *** +## FAQ + +
+ +Why are things misaligning? Why are borders at the wrong widths? + +

This is most likely due to your locale and encoding, particularly with +regard to Chinese, Japanese, and Korean (for example, zh_CN.UTF-8 +or ja_JP.UTF-8). The most direct way to fix this is to set +RUNEWIDTH_EASTASIAN=0 in your environment.

+ +

For details see https://github.com/charmbracelet/lipgloss/issues/40.

+
+ +
+ +Why isn't Lip Gloss displaying colors? + +

Lip Gloss automatically degrades colors to the best available option in the +given terminal, and if output's not a TTY it will remove color output entirely. +This is common when running tests, CI, or when piping output elsewhere.

+ +

If necessary, you can force a color profile in your tests with +SetColorProfile.

+ +```go +import ( + "github.com/charmbracelet/lipgloss" + "github.com/muesli/termenv" +) + +lipgloss.SetColorProfile(termenv.TrueColor) +``` + +*Note:* this option limits the flexibility of your application and can cause +ANSI escape codes to be output in cases where that might not be desired. Take +careful note of your use case and environment before choosing to force a color +profile. +
## What about [Bubble Tea][tea]? From 3556cdfa895d08eeaa5a92768b7acff39f8b7b7f Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Tue, 27 Jun 2023 12:07:45 -0300 Subject: [PATCH 069/126] feat(deps): update termenv (#202) Signed-off-by: Carlos Alexandro Becker --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index ced628d3..52ead2bd 100644 --- a/go.mod +++ b/go.mod @@ -7,13 +7,13 @@ go 1.17 require ( github.com/mattn/go-runewidth v0.0.14 github.com/muesli/reflow v0.3.0 - github.com/muesli/termenv v0.15.1 + github.com/muesli/termenv v0.15.2 ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/sys v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index 2c273fd1..6244eff3 100644 --- a/go.sum +++ b/go.sum @@ -2,18 +2,18 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiE github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= -github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 233079e2d9aa3bd42a98c5b3f33130c2b696c089 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Wed, 28 Jun 2023 16:50:39 +0200 Subject: [PATCH 070/126] fix: go mod tidy examples --- examples/go.mod | 6 +++--- examples/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index d613d63b..814544a7 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -10,7 +10,7 @@ require ( github.com/gliderlabs/ssh v0.3.4 github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 - github.com/muesli/termenv v0.15.0 + github.com/muesli/termenv v0.15.2 golang.org/x/term v0.0.0-20210422114643-f5beecf764ed ) @@ -19,11 +19,11 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/charmbracelet/keygen v0.3.0 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect - golang.org/x/sys v0.6.0 // indirect + golang.org/x/sys v0.7.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index 0651f5b2..cffc8669 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -49,8 +49,8 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= +github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= @@ -61,8 +61,8 @@ github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBc github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= -github.com/muesli/termenv v0.15.0 h1:ZYfCF4CZGhAA4meilZ5pd7tfUX4QLH4zB7OBie4RMS8= -github.com/muesli/termenv v0.15.0/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= +github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -101,9 +101,9 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From df8b3fa1f20885458615189fa0558c6226be8240 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 24 Jul 2023 11:22:48 -0400 Subject: [PATCH 071/126] feat: convert tabs to spaces with `Style.TabWidth(int)` (#204) * feat: convert tabs to spaces with Style.TabWidth(int) By default tabs will be converted to 4 spaces. To disable tab conversion set Style.TabWidth(NoTabConversion). --- get.go | 6 ++++++ set.go | 28 ++++++++++++++++++++++++++-- style.go | 23 ++++++++++++++++++++++- style_test.go | 20 +++++++++++++++++++- unset.go | 6 ++++++ 5 files changed, 79 insertions(+), 4 deletions(-) diff --git a/get.go b/get.go index be0055c3..d0f0826e 100644 --- a/get.go +++ b/get.go @@ -370,6 +370,12 @@ func (s Style) GetMaxHeight() int { return s.getAsInt(maxHeightKey) } +// GetTabWidth returns the style's tab width setting. If no value is set 4 is +// returned which is the implicit default. +func (s Style) GetTabWidth() int { + return s.getAsInt(tabWidthKey) +} + // GetUnderlineSpaces returns whether or not the style is set to underline // spaces. If not value is set false is returned. func (s Style) GetUnderlineSpaces() bool { diff --git a/set.go b/set.go index 92e9f212..ee660f2b 100644 --- a/set.go +++ b/set.go @@ -14,7 +14,14 @@ func (s *Style) set(key propKey, value interface{}) { switch v := value.(type) { case int: - // We don't allow negative integers on any of our values, so just keep + // TabWidth is the only property that may have a negative value (and + // that negative value can be no less than -1). + if key == tabWidthKey { + s.rules[key] = v + break + } + + // We don't allow negative integers on any of our other values, so just keep // them at zero or above. We could use uints instead, but the // conversions are a little tedious, so we're sticking with ints for // sake of usability. @@ -497,13 +504,30 @@ func (s Style) MaxWidth(n int) Style { // styles. // // Because this in intended to be used at the time of render, this method will -// not mutate the style and instead return a copy. +// not mutate the style and instead returns a copy. func (s Style) MaxHeight(n int) Style { o := s.Copy() o.set(maxHeightKey, n) return o } +// NoTabConversion can be passed to [Style.TabWidth] to disable the replacement +// of tabs with spaces at render time. +const NoTabConversion = -1 + +// TabWidth sets the number of spaces that a tab (/t) should be rendered as. +// When set to 0, tabs will be removed. To disable the replacement of tabs with +// spaces entirely, set this to [NoTabConversion]. +// +// By default, tabs will be replaced with 4 spaces. +func (s Style) TabWidth(n int) Style { + if n <= -1 { + n = -1 + } + s.set(tabWidthKey, n) + return s +} + // UnderlineSpaces determines whether to underline spaces between words. By // default, this is true. Spaces can also be underlined without underlining the // text itself. diff --git a/style.go b/style.go index e94b8670..9993d9c7 100644 --- a/style.go +++ b/style.go @@ -10,6 +10,8 @@ import ( "github.com/muesli/termenv" ) +const tabWidthDefault = 4 + // Property for a key. type propKey int @@ -68,6 +70,7 @@ const ( inlineKey maxWidthKey maxHeightKey + tabWidthKey underlineSpacesKey strikethroughSpacesKey ) @@ -224,7 +227,7 @@ func (s Style) Render(strs ...string) string { ) if len(s.rules) == 0 { - return str + return s.maybeConvertTabs(str) } // Enable support for ANSI on the legacy Windows cmd.exe console. This is a @@ -287,6 +290,9 @@ func (s Style) Render(strs ...string) string { teSpace = teSpace.CrossOut() } + // Potentially convert tabs to spaces + str = s.maybeConvertTabs(str) + // Strip newlines in single line mode if inline { str = strings.ReplaceAll(str, "\n", "") @@ -397,6 +403,21 @@ func (s Style) Render(strs ...string) string { return str } +func (s Style) maybeConvertTabs(str string) string { + tw := tabWidthDefault + if s.isSet(tabWidthKey) { + tw = s.getAsInt(tabWidthKey) + } + switch tw { + case -1: + return str + case 0: + return strings.ReplaceAll(str, "\t", "") + default: + return strings.ReplaceAll(str, "\t", strings.Repeat(" ", tw)) + } +} + func (s Style) applyMargins(str string, inline bool) string { var ( topMargin = s.getAsInt(marginTopKey) diff --git a/style_test.go b/style_test.go index 697e7d19..fd9c8aac 100644 --- a/style_test.go +++ b/style_test.go @@ -181,7 +181,8 @@ func TestStyleCopy(t *testing.T) { Foreground(Color("#ffffff")). Background(Color("#111111")). Margin(1, 1, 1, 1). - Padding(1, 1, 1, 1) + Padding(1, 1, 1, 1). + TabWidth(2) i := s.Copy() @@ -202,6 +203,7 @@ func TestStyleCopy(t *testing.T) { requireEqual(t, s.GetPaddingRight(), i.GetPaddingRight()) requireEqual(t, s.GetPaddingTop(), i.GetPaddingTop()) requireEqual(t, s.GetPaddingBottom(), i.GetPaddingBottom()) + requireEqual(t, s.GetTabWidth(), i.GetTabWidth()) } func TestStyleUnset(t *testing.T) { @@ -312,6 +314,12 @@ func TestStyleUnset(t *testing.T) { requireTrue(t, s.GetBorderLeft()) s.UnsetBorderLeft() requireFalse(t, s.GetBorderLeft()) + + // tab width + s = NewStyle().TabWidth(2) + requireEqual(t, s.GetTabWidth(), 2) + s.UnsetTabWidth() + requireNotEqual(t, s.GetTabWidth(), 4) } func TestStyleValue(t *testing.T) { @@ -352,7 +360,17 @@ func TestStyleValue(t *testing.T) { res, formatEscapes(res)) } } +} +func TestTabConversion(t *testing.T) { + s := NewStyle() + requireEqual(t, "[ ]", s.Render("[\t]")) + s = NewStyle().TabWidth(2) + requireEqual(t, "[ ]", s.Render("[\t]")) + s = NewStyle().TabWidth(0) + requireEqual(t, "[]", s.Render("[\t]")) + s = NewStyle().TabWidth(-1) + requireEqual(t, "[\t]", s.Render("[\t]")) } func BenchmarkStyleRender(b *testing.B) { diff --git a/unset.go b/unset.go index 4f8fe658..f889f9e2 100644 --- a/unset.go +++ b/unset.go @@ -287,6 +287,12 @@ func (s Style) UnsetMaxHeight() Style { return s } +// UnsetMaxHeight removes the max height style rule, if set. +func (s Style) UnsetTabWidth() Style { + delete(s.rules, tabWidthKey) + return s +} + // UnsetUnderlineSpaces removes the value set by UnderlineSpaces. func (s Style) UnsetUnderlineSpaces() Style { delete(s.rules, underlineSpacesKey) From 01248034d06f4f1d35d31b10e77f1604b2f7a6e2 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 24 Jul 2023 11:50:04 -0400 Subject: [PATCH 072/126] chore(lint): add various nolint directives (#206) Generally run-of-the-mill directives to disable magic number and exhaustive switches. Reasonings should be self explanatory while reading the code and comments, where applicable. --- align.go | 7 ++++--- color.go | 4 ++-- join.go | 4 ++-- position.go | 4 ++-- set.go | 18 +++++++++--------- style.go | 2 +- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/align.go b/align.go index c3997038..6c0fb2dc 100644 --- a/align.go +++ b/align.go @@ -21,7 +21,7 @@ func alignTextHorizontal(str string, pos Position, width int, style *termenv.Sty shortAmount += max(0, width-(shortAmount+lineWidth)) // difference from the total width, if set if shortAmount > 0 { - switch pos { + switch pos { //nolint:exhaustive case Right: s := strings.Repeat(" ", shortAmount) if style != nil { @@ -29,8 +29,9 @@ func alignTextHorizontal(str string, pos Position, width int, style *termenv.Sty } l = s + l case Center: - left := shortAmount / 2 - right := left + shortAmount%2 // note that we put the remainder on the right + // Note: remainder goes on the right. + left := shortAmount / 2 //nolint:gomnd + right := left + shortAmount%2 //nolint:gomnd leftSpaces := strings.Repeat(" ", left) rightSpaces := strings.Repeat(" ", right) diff --git a/color.go b/color.go index adff1a6e..43f5b434 100644 --- a/color.go +++ b/color.go @@ -35,7 +35,7 @@ func (NoColor) color(*Renderer) termenv.Color { // // Deprecated. func (n NoColor) RGBA() (r, g, b, a uint32) { - return 0x0, 0x0, 0x0, 0xFFFF + return 0x0, 0x0, 0x0, 0xFFFF //nolint:gomnd } // Color specifies a color by hex or ANSI value. For example: @@ -123,7 +123,7 @@ type CompleteColor struct { func (c CompleteColor) color(r *Renderer) termenv.Color { p := r.ColorProfile() - switch p { + switch p { //nolint:exhaustive case termenv.TrueColor: return p.Color(c.TrueColor) case termenv.ANSI256: diff --git a/join.go b/join.go index cc16600a..f265976e 100644 --- a/join.go +++ b/join.go @@ -60,7 +60,7 @@ func JoinHorizontal(pos Position, strs ...string) string { extraLines := make([]string, maxHeight-len(blocks[i])) - switch pos { + switch pos { //nolint:exhaustive case Top: blocks[i] = append(blocks[i], extraLines...) @@ -139,7 +139,7 @@ func JoinVertical(pos Position, strs ...string) string { for j, line := range block { w := maxWidth - ansi.PrintableRuneWidth(line) - switch pos { + switch pos { //nolint:exhaustive case Left: b.WriteString(line) b.WriteString(strings.Repeat(" ", w)) diff --git a/position.go b/position.go index afefa8be..7d229e03 100644 --- a/position.go +++ b/position.go @@ -68,7 +68,7 @@ func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ... // Is this line shorter than the longest line? short := max(0, contentWidth-ansi.PrintableRuneWidth(l)) - switch pos { + switch pos { //nolint:exhaustive case Left: b.WriteString(l) b.WriteString(ws.render(gap + short)) @@ -121,7 +121,7 @@ func (r *Renderer) PlaceVertical(height int, pos Position, str string, opts ...W emptyLine := ws.render(width) b := strings.Builder{} - switch pos { + switch pos { //nolint:exhaustive case Top: b.WriteString(str) b.WriteRune('\n') diff --git a/set.go b/set.go index ee660f2b..432eac5d 100644 --- a/set.go +++ b/set.go @@ -569,19 +569,19 @@ func whichSidesInt(i ...int) (top, right, bottom, left int, ok bool) { left = i[0] right = i[0] ok = true - case 2: + case 2: //nolint:gomnd top = i[0] bottom = i[0] left = i[1] right = i[1] ok = true - case 3: + case 3: //nolint:gomnd top = i[0] left = i[1] right = i[1] bottom = i[2] ok = true - case 4: + case 4: //nolint:gomnd top = i[0] right = i[1] bottom = i[2] @@ -602,19 +602,19 @@ func whichSidesBool(i ...bool) (top, right, bottom, left bool, ok bool) { left = i[0] right = i[0] ok = true - case 2: + case 2: //nolint:gomnd top = i[0] bottom = i[0] left = i[1] right = i[1] ok = true - case 3: + case 3: //nolint:gomnd top = i[0] left = i[1] right = i[1] bottom = i[2] ok = true - case 4: + case 4: //nolint:gomnd top = i[0] right = i[1] bottom = i[2] @@ -635,19 +635,19 @@ func whichSidesColor(i ...TerminalColor) (top, right, bottom, left TerminalColor left = i[0] right = i[0] ok = true - case 2: + case 2: //nolint:gomnd top = i[0] bottom = i[0] left = i[1] right = i[1] ok = true - case 3: + case 3: //nolint:gomnd top = i[0] left = i[1] right = i[1] bottom = i[2] ok = true - case 4: + case 4: //nolint:gomnd top = i[0] right = i[1] bottom = i[2] diff --git a/style.go b/style.go index 9993d9c7..608c5970 100644 --- a/style.go +++ b/style.go @@ -151,7 +151,7 @@ func (s Style) Inherit(i Style) Style { s.init() for k, v := range i.rules { - switch k { + switch k { //nolint:exhaustive case marginTopKey, marginRightKey, marginBottomKey, marginLeftKey: // Margins are not inherited continue From b3bce2366a3170367c690001d920dad8c9f603bf Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 24 Jul 2023 12:42:34 -0400 Subject: [PATCH 073/126] chore(docs): add section about tab conversion (#207) --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 0fc51bdc..2ebabc76 100644 --- a/README.md +++ b/README.md @@ -294,6 +294,20 @@ someStyle.Inline(true).MaxWidth(5).Render("yadda yadda") someStyle.MaxWidth(5).MaxHeight(5).Render("yadda yadda") ``` +## Tabs + +The tab character (`\t`) is rendered differently in different terminals (often +as 8 spaces, sometimes 4). Because of this inconsistency, Lip Gloss converts +tabs to 4 spaces at render time. This behavior can be changed on a per-style +basis, however: + +```go +style := lipgloss.NewStyle() // tabs will render as 4 spaces, the default +style = style.TabWidth(2) // render tabs as 2 spaces +style = style.TabWidth(0) // remove tabs entirely +style = style.TabWidth(lipgloss.NoTabConversion) // leave tabs intact +``` + ## Rendering Generally, you just call the `Render(string...)` method on a `lipgloss.Style`: From ac8231edce572cc7aec5bc8347d67803f7aaa787 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 1 Aug 2023 05:23:15 -0700 Subject: [PATCH 074/126] fix: renderer race condition (#210) Guard accessing the underlying Termenv output behind a mutex. Multiple goroutines can set/get the dark background color causing a race condition. Needs: https://github.com/muesli/termenv/pull/146 --- renderer.go | 12 ++++++++++++ renderer_test.go | 20 +++++++++++++++++++- style.go | 7 ++++--- style_test.go | 19 ++++++++++--------- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/renderer.go b/renderer.go index 4bea8374..8e551f93 100644 --- a/renderer.go +++ b/renderer.go @@ -2,6 +2,7 @@ package lipgloss import ( "io" + "sync" "github.com/muesli/termenv" ) @@ -16,6 +17,7 @@ var renderer = &Renderer{ type Renderer struct { output *termenv.Output hasDarkBackground *bool + mtx sync.RWMutex } // RendererOption is a function that can be used to configure a [Renderer]. @@ -43,11 +45,15 @@ func NewRenderer(w io.Writer, opts ...termenv.OutputOption) *Renderer { // Output returns the termenv output. func (r *Renderer) Output() *termenv.Output { + r.mtx.RLock() + defer r.mtx.RUnlock() return r.output } // SetOutput sets the termenv output. func (r *Renderer) SetOutput(o *termenv.Output) { + r.mtx.Lock() + defer r.mtx.Unlock() r.output = o } @@ -78,6 +84,8 @@ func ColorProfile() termenv.Profile { // // This function is thread-safe. func (r *Renderer) SetColorProfile(p termenv.Profile) { + r.mtx.Lock() + defer r.mtx.Unlock() r.output.Profile = p } @@ -110,6 +118,8 @@ func HasDarkBackground() bool { // background. A dark background can either be auto-detected, or set explicitly // on the renderer. func (r *Renderer) HasDarkBackground() bool { + r.mtx.RLock() + defer r.mtx.RUnlock() if r.hasDarkBackground != nil { return *r.hasDarkBackground } @@ -139,5 +149,7 @@ func SetHasDarkBackground(b bool) { // // This function is thread-safe. func (r *Renderer) SetHasDarkBackground(b bool) { + r.mtx.Lock() + defer r.mtx.Unlock() r.hasDarkBackground = &b } diff --git a/renderer_test.go b/renderer_test.go index 7f05acd7..6c0b145f 100644 --- a/renderer_test.go +++ b/renderer_test.go @@ -1,6 +1,7 @@ package lipgloss import ( + "io" "os" "testing" @@ -29,7 +30,24 @@ func TestRendererWithOutput(t *testing.T) { defer os.Remove(f.Name()) r := NewRenderer(f) r.SetColorProfile(termenv.TrueColor) - if r.output.Profile != termenv.TrueColor { + if r.ColorProfile() != termenv.TrueColor { t.Error("Expected renderer to use true color") } } + +func TestRace(t *testing.T) { + r := NewRenderer(io.Discard) + o := r.Output() + + for i := 0; i < 100; i++ { + t.Run("SetColorProfile", func(t *testing.T) { + t.Parallel() + r.SetHasDarkBackground(false) + r.HasDarkBackground() + r.SetOutput(o) + r.SetColorProfile(termenv.ANSI256) + r.SetHasDarkBackground(true) + r.Output() + }) + } +} diff --git a/style.go b/style.go index 608c5970..ee50bcf7 100644 --- a/style.go +++ b/style.go @@ -185,9 +185,10 @@ func (s Style) Render(strs ...string) string { var ( str = joinString(strs...) - te = s.r.ColorProfile().String() - teSpace = s.r.ColorProfile().String() - teWhitespace = s.r.ColorProfile().String() + p = s.r.ColorProfile() + te = p.String() + teSpace = p.String() + teWhitespace = p.String() bold = s.getAsBool(boldKey, false) italic = s.getAsBool(italicKey, false) diff --git a/style_test.go b/style_test.go index fd9c8aac..9cb2a9a2 100644 --- a/style_test.go +++ b/style_test.go @@ -9,8 +9,9 @@ import ( ) func TestStyleRender(t *testing.T) { - renderer.SetColorProfile(termenv.TrueColor) - renderer.SetHasDarkBackground(true) + r := NewRenderer(io.Discard) + r.SetColorProfile(termenv.TrueColor) + r.SetHasDarkBackground(true) t.Parallel() tt := []struct { @@ -18,31 +19,31 @@ func TestStyleRender(t *testing.T) { expected string }{ { - NewStyle().Foreground(Color("#5A56E0")), + r.NewStyle().Foreground(Color("#5A56E0")), "\x1b[38;2;89;86;224mhello\x1b[0m", }, { - NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), + r.NewStyle().Foreground(AdaptiveColor{Light: "#fffe12", Dark: "#5A56E0"}), "\x1b[38;2;89;86;224mhello\x1b[0m", }, { - NewStyle().Bold(true), + r.NewStyle().Bold(true), "\x1b[1mhello\x1b[0m", }, { - NewStyle().Italic(true), + r.NewStyle().Italic(true), "\x1b[3mhello\x1b[0m", }, { - NewStyle().Underline(true), + r.NewStyle().Underline(true), "\x1b[4;4mh\x1b[0m\x1b[4;4me\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4ml\x1b[0m\x1b[4;4mo\x1b[0m", }, { - NewStyle().Blink(true), + r.NewStyle().Blink(true), "\x1b[5mhello\x1b[0m", }, { - NewStyle().Faint(true), + r.NewStyle().Faint(true), "\x1b[2mhello\x1b[0m", }, } From 160ffe826ed8e61f632ca4fe3b5acab844b658bf Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 4 Aug 2023 09:58:59 -0700 Subject: [PATCH 075/126] fix: cache color profile and background (#212) * fix: renderer race condition Guard accessing the underlying Termenv output behind a mutex. Multiple goroutines can set/get the dark background color causing a race condition. * fix: cache color profile and background * docs: add comment --- renderer.go | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/renderer.go b/renderer.go index 8e551f93..85ffd254 100644 --- a/renderer.go +++ b/renderer.go @@ -16,8 +16,16 @@ var renderer = &Renderer{ // Renderer is a lipgloss terminal renderer. type Renderer struct { output *termenv.Output - hasDarkBackground *bool - mtx sync.RWMutex + colorProfile termenv.Profile + hasDarkBackground bool + + getColorProfile sync.Once + explicitColorProfile bool + + getBackgroundColor sync.Once + explicitBackgroundColor bool + + mtx sync.RWMutex } // RendererOption is a function that can be used to configure a [Renderer]. @@ -59,7 +67,18 @@ func (r *Renderer) SetOutput(o *termenv.Output) { // ColorProfile returns the detected termenv color profile. func (r *Renderer) ColorProfile() termenv.Profile { - return r.output.Profile + r.mtx.RLock() + defer r.mtx.RUnlock() + + if !r.explicitColorProfile { + r.getColorProfile.Do(func() { + // NOTE: we don't need to lock here because sync.Once provides its + // own locking mechanism. + r.colorProfile = r.output.EnvColorProfile() + }) + } + + return r.colorProfile } // ColorProfile returns the detected termenv color profile. @@ -86,7 +105,9 @@ func ColorProfile() termenv.Profile { func (r *Renderer) SetColorProfile(p termenv.Profile) { r.mtx.Lock() defer r.mtx.Unlock() - r.output.Profile = p + + r.colorProfile = p + r.explicitColorProfile = true } // SetColorProfile sets the color profile on the default renderer. This @@ -120,10 +141,16 @@ func HasDarkBackground() bool { func (r *Renderer) HasDarkBackground() bool { r.mtx.RLock() defer r.mtx.RUnlock() - if r.hasDarkBackground != nil { - return *r.hasDarkBackground + + if !r.explicitBackgroundColor { + r.getBackgroundColor.Do(func() { + // NOTE: we don't need to lock here because sync.Once provides its + // own locking mechanism. + r.hasDarkBackground = r.output.HasDarkBackground() + }) } - return r.output.HasDarkBackground() + + return r.hasDarkBackground } // SetHasDarkBackground sets the background color detection value for the @@ -151,5 +178,7 @@ func SetHasDarkBackground(b bool) { func (r *Renderer) SetHasDarkBackground(b bool) { r.mtx.Lock() defer r.mtx.Unlock() - r.hasDarkBackground = &b + + r.hasDarkBackground = b + r.explicitBackgroundColor = true } From 7611cdfffeb9b248c875ecb223cc1a23ff7adfc2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Aug 2023 12:59:17 -0400 Subject: [PATCH 076/126] chore(deps): bump golang.org/x/crypto in /examples (#192) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20220307211146-efcb8507fb70 to 0.1.0. - [Release notes](https://github.com/golang/crypto/releases) - [Commits](https://github.com/golang/crypto/commits/v0.1.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/go.mod | 4 ++-- examples/go.sum | 26 ++++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 814544a7..40626251 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -11,7 +11,7 @@ require ( github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/muesli/termenv v0.15.2 - golang.org/x/term v0.0.0-20210422114643-f5beecf764ed + golang.org/x/term v0.1.0 ) require ( @@ -24,6 +24,6 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/sys v0.7.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index cffc8669..b704e63e 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -78,17 +78,28 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70 h1:syTAU9FwmvzEoIYMqcPHOcVm4H3U5u90WsvuYgwpETU= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -101,16 +112,27 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210422114643-f5beecf764ed h1:Ei4bQjjpYUsS4efOUz+5Nz++IVkHk87n2zBA0NxBWc0= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 07d22725d9ac870b409b09327c52ea5f49a23fe1 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 4 Aug 2023 13:49:32 -0400 Subject: [PATCH 077/126] chore(ci): bump workflow actions --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d07731b2..bb91d2f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.5.0 + uses: actions/checkout@v3 - name: Download Go modules run: go mod download diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 6182bd43..845096e4 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3.5.0 + uses: actions/checkout@v3 - name: Coverage env: diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 07e59a5f..62305289 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.5.0 + - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ba9f941c..2f26b2ac 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3.5.0 + - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: From 18166eaa8adf46a03c4b732ab1dec81fad1c691b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 11:57:55 +0000 Subject: [PATCH 078/126] chore(deps): bump actions/checkout from 3 to 4 (#222) --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bb91d2f0..81c9e743 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download Go modules run: go mod download diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 845096e4..f6985a68 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Coverage env: diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 62305289..5ce0d37f 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2f26b2ac..16f38b8a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -18,7 +18,7 @@ jobs: with: go-version: ^1 - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: From 2d2a577e2d32c9b23e64be859b8af98599bba243 Mon Sep 17 00:00:00 2001 From: nervo Date: Thu, 5 Oct 2023 17:45:52 +0200 Subject: [PATCH 079/126] fix(border): GetBorderRightSize (#224) --- get.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get.go b/get.go index d0f0826e..783fa834 100644 --- a/get.go +++ b/get.go @@ -333,7 +333,7 @@ func (s Style) GetBorderRightSize() int { if !s.getAsBool(borderRightKey, false) { return 0 } - return s.getBorderStyle().GetBottomSize() + return s.getBorderStyle().GetRightSize() } // GetHorizontalBorderSize returns the width of the horizontal borders. If From b0eb95dbc18f712ad939ab7193cb69951a95361f Mon Sep 17 00:00:00 2001 From: nervo Date: Thu, 5 Oct 2023 17:48:38 +0200 Subject: [PATCH 080/126] fix(border): both GetHorizontalBorderSize and GetVerticalBorderSize (#225) --- get.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/get.go b/get.go index 783fa834..d2623c4e 100644 --- a/get.go +++ b/get.go @@ -340,16 +340,14 @@ func (s Style) GetBorderRightSize() int { // borders contain runes of varying widths, the widest rune is returned. If no // border exists on the horizontal edges, 0 is returned. func (s Style) GetHorizontalBorderSize() int { - b := s.getBorderStyle() - return b.GetLeftSize() + b.GetRightSize() + return s.GetBorderLeftSize() + s.GetBorderRightSize() } // GetVerticalBorderSize returns the width of the vertical borders. If // borders contain runes of varying widths, the widest rune is returned. If no // border exists on the vertical edges, 0 is returned. func (s Style) GetVerticalBorderSize() int { - b := s.getBorderStyle() - return b.GetTopSize() + b.GetBottomSize() + return s.GetBorderTopSize() + s.GetBorderBottomSize() } // GetInline returns the style's inline setting. If no value is set false is From b766f24d454396ae75c1b0e86310dffadf4ceb77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Oct 2023 12:01:04 -0400 Subject: [PATCH 081/126] feat(deps): bump github.com/mattn/go-runewidth from 0.0.14 to 0.0.15 (#215) Bumps [github.com/mattn/go-runewidth](https://github.com/mattn/go-runewidth) from 0.0.14 to 0.0.15. - [Commits](https://github.com/mattn/go-runewidth/compare/v0.0.14...v0.0.15) --- updated-dependencies: - dependency-name: github.com/mattn/go-runewidth dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 52ead2bd..423301da 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. go 1.17 require ( - github.com/mattn/go-runewidth v0.0.14 + github.com/mattn/go-runewidth v0.0.15 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 ) diff --git a/go.sum b/go.sum index 6244eff3..596350b3 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,9 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= From 93cd5e0d35ae7fed6884560097b304cec79f0fc1 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 5 Oct 2023 09:07:18 -0700 Subject: [PATCH 082/126] feat: bump minimum go version to 1.18 (#216) * feat: bump minimum go version to 1.18 * chore: bump go version in build.yml --------- Co-authored-by: Maas Lalani --- .github/workflows/build.yml | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81c9e743..8ac0cd2a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [~1.17, ^1] + go-version: [~1.18, ^1] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: diff --git a/go.mod b/go.mod index 423301da..094f648d 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/charmbracelet/lipgloss retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. -go 1.17 +go 1.18 require ( github.com/mattn/go-runewidth v0.0.15 From 408dcf3b9ec535cddf7d343bf5b1a3dd0eba4a00 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Thu, 5 Oct 2023 12:26:16 -0400 Subject: [PATCH 083/126] feat: add `Middle` borders (#230) --- borders.go | 126 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 48 deletions(-) diff --git a/borders.go b/borders.go index 2c493161..f244b53a 100644 --- a/borders.go +++ b/borders.go @@ -11,14 +11,19 @@ import ( // Border contains a series of values which comprise the various parts of a // border. type Border struct { - Top string - Bottom string - Left string - Right string - TopLeft string - TopRight string - BottomRight string - BottomLeft string + Top string + Bottom string + Left string + Right string + TopLeft string + TopRight string + BottomLeft string + BottomRight string + MiddleLeft string + MiddleRight string + Middle string + MiddleTop string + MiddleBottom string } // GetTopSize returns the width of the top border. If borders contain runes of @@ -63,25 +68,35 @@ var ( noBorder = Border{} normalBorder = Border{ - Top: "─", - Bottom: "─", - Left: "│", - Right: "│", - TopLeft: "┌", - TopRight: "┐", - BottomLeft: "└", - BottomRight: "┘", + Top: "─", + Bottom: "─", + Left: "│", + Right: "│", + TopLeft: "┌", + TopRight: "┐", + BottomLeft: "└", + BottomRight: "┘", + MiddleLeft: "├", + MiddleRight: "┤", + Middle: "┼", + MiddleTop: "┬", + MiddleBottom: "┴", } roundedBorder = Border{ - Top: "─", - Bottom: "─", - Left: "│", - Right: "│", - TopLeft: "╭", - TopRight: "╮", - BottomLeft: "╰", - BottomRight: "╯", + Top: "─", + Bottom: "─", + Left: "│", + Right: "│", + TopLeft: "╭", + TopRight: "╮", + BottomLeft: "╰", + BottomRight: "╯", + MiddleLeft: "├", + MiddleRight: "┤", + Middle: "┼", + MiddleTop: "┬", + MiddleBottom: "┴", } blockBorder = Border{ @@ -118,36 +133,51 @@ var ( } thickBorder = Border{ - Top: "━", - Bottom: "━", - Left: "┃", - Right: "┃", - TopLeft: "┏", - TopRight: "┓", - BottomLeft: "┗", - BottomRight: "┛", + Top: "━", + Bottom: "━", + Left: "┃", + Right: "┃", + TopLeft: "┏", + TopRight: "┓", + BottomLeft: "┗", + BottomRight: "┛", + MiddleLeft: "┣", + MiddleRight: "┫", + Middle: "╋", + MiddleTop: "┳", + MiddleBottom: "┻", } doubleBorder = Border{ - Top: "═", - Bottom: "═", - Left: "║", - Right: "║", - TopLeft: "╔", - TopRight: "╗", - BottomLeft: "╚", - BottomRight: "╝", + Top: "═", + Bottom: "═", + Left: "║", + Right: "║", + TopLeft: "╔", + TopRight: "╗", + BottomLeft: "╚", + BottomRight: "╝", + MiddleLeft: "╠", + MiddleRight: "╣", + Middle: "╬", + MiddleTop: "╦", + MiddleBottom: "╩", } hiddenBorder = Border{ - Top: " ", - Bottom: " ", - Left: " ", - Right: " ", - TopLeft: " ", - TopRight: " ", - BottomLeft: " ", - BottomRight: " ", + Top: " ", + Bottom: " ", + Left: " ", + Right: " ", + TopLeft: " ", + TopRight: " ", + BottomLeft: " ", + BottomRight: " ", + MiddleLeft: " ", + MiddleRight: " ", + Middle: " ", + MiddleTop: " ", + MiddleBottom: " ", } ) From 4476263d0598a0799b48f75d1bfb394b4dce79f4 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Tue, 10 Oct 2023 10:27:52 -0400 Subject: [PATCH 084/126] Feature: Tables (#218) * feat: tables * fix(table): examples * docs(table): `any` -> `string` * chore: go mod tidy * docs(table): update image * fix(table): lint * fix: remove binary * chore: color adjustments to pokemon example (#231) * fix(table): support rendering empty data sets * chore(table): simplify table's data interface * fix(table): correct GoDoc + add doc comments to Data methods --------- Co-authored-by: Christian Rocha Co-authored-by: Christian Muehlhaeuser --- README.md | 53 +- examples/go.mod | 7 +- examples/go.sum | 110 +--- examples/table/chess/main.go | 40 ++ examples/table/demo.tape | 29 + examples/table/languages/main.go | 73 +++ examples/table/mindy/main.go | 65 +++ examples/table/pokemon/main.go | 113 ++++ go.mod | 3 +- go.sum | 7 +- table/rows.go | 113 ++++ table/table.go | 522 ++++++++++++++++++ table/table_test.go | 898 +++++++++++++++++++++++++++++++ table/util.go | 62 +++ unset.go | 2 +- 15 files changed, 1984 insertions(+), 113 deletions(-) create mode 100644 examples/table/chess/main.go create mode 100644 examples/table/demo.tape create mode 100644 examples/table/languages/main.go create mode 100644 examples/table/mindy/main.go create mode 100644 examples/table/pokemon/main.go create mode 100644 table/rows.go create mode 100644 table/table.go create mode 100644 table/table_test.go create mode 100644 table/util.go diff --git a/README.md b/README.md index 2ebabc76..f3aa745a 100644 --- a/README.md +++ b/README.md @@ -391,7 +391,6 @@ height := lipgloss.Height(block) w, h := lipgloss.Size(block) ``` - ### Placing Text in Whitespace Sometimes you’ll simply want to place a block of text in whitespace. @@ -411,6 +410,58 @@ block := lipgloss.Place(30, 80, lipgloss.Right, lipgloss.Bottom, fancyStyledPara You can also style the whitespace. For details, see [the docs][docs]. +### Rendering Tables + +Lip Gloss ships with a table rendering sub-package. + +```go +import "github.com/charmbracelet/lipgloss/table" +``` + +Define some rows of data. + +```go +rows := [][]string{ + {"Chinese", "您好", "你好"}, + {"Japanese", "こんにちは", "やあ"}, + {"Arabic", "أهلين", "أهلا"}, + {"Russian", "Здравствуйте", "Привет"}, + {"Spanish", "Hola", "¿Qué tal?"}, +} +``` + +Use the table package to style and render the table. + +```go +t := table.New(). + Border(lipgloss.NormalBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))). + StyleFunc(func(row, col int) lipgloss.Style { + switch { + case row == 0: + return HeaderStyle + case row%2 == 0: + return EvenRowStyle + default: + return OddRowStyle + } + }). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + +// You can also add tables row-by-row +t.Row("English", "You look absolutely fabulous.", "How's it going?") +``` + +Print the table. + +```go +fmt.Println(t) +``` + +![Table Example](https://github.com/charmbracelet/lipgloss/assets/42545625/6e4b70c4-f494-45da-a467-bdd27df30d5d) + +For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc). *** diff --git a/examples/go.mod b/examples/go.mod index 40626251..34b54802 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,6 +1,6 @@ module examples -go 1.17 +go 1.18 replace github.com/charmbracelet/lipgloss => ../ @@ -20,10 +20,11 @@ require ( github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/charmbracelet/keygen v0.3.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect - github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sys v0.12.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index b704e63e..ff1b55fe 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,145 +1,47 @@ -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= -github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM= github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y= github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg= github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= -github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= -github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= -github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/table/chess/main.go b/examples/table/chess/main.go new file mode 100644 index 00000000..a69a9bc4 --- /dev/null +++ b/examples/table/chess/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" +) + +func main() { + re := lipgloss.NewRenderer(os.Stdout) + labelStyle := re.NewStyle().Foreground(lipgloss.Color("241")) + + board := [][]string{ + {"♜", "♞", "♝", "♛", "♚", "♝", "♞", "♜"}, + {"♟", "♟", "♟", "♟", "♟", "♟", "♟", "♟"}, + {" ", " ", " ", " ", " ", " ", " ", " "}, + {" ", " ", " ", " ", " ", " ", " ", " "}, + {" ", " ", " ", " ", " ", " ", " ", " "}, + {" ", " ", " ", " ", " ", " ", " ", " "}, + {"♙", "♙", "♙", "♙", "♙", "♙", "♙", "♙"}, + {"♖", "♘", "♗", "♕", "♔", "♗", "♘", "♖"}, + } + + t := table.New(). + Border(lipgloss.NormalBorder()). + BorderRow(true). + BorderColumn(true). + Rows(board...). + StyleFunc(func(row, col int) lipgloss.Style { + return lipgloss.NewStyle().Padding(0, 1) + }) + + ranks := labelStyle.Render(strings.Join([]string{" A", "B", "C", "D", "E", "F", "G", "H "}, " ")) + files := labelStyle.Render(strings.Join([]string{" 1", "2", "3", "4", "5", "6", "7", "8 "}, "\n\n ")) + + fmt.Println(lipgloss.JoinVertical(lipgloss.Right, lipgloss.JoinHorizontal(lipgloss.Center, files, t.Render()), ranks) + "\n") +} diff --git a/examples/table/demo.tape b/examples/table/demo.tape new file mode 100644 index 00000000..281690f1 --- /dev/null +++ b/examples/table/demo.tape @@ -0,0 +1,29 @@ +Output table.gif + +Set Height 900 +Set Width 1600 +Set Padding 80 +Set FontSize 42 + +Hide +Type "go build -o table" +Enter +Ctrl+L +Show + +Sleep 0.5s +Type "clear && ./table" +Sleep 0.5s +Enter +Sleep 1s + +Screenshot "table.png" + +Sleep 1s + +Hide +Type "rm table" +Enter +Show + +Sleep 1s diff --git a/examples/table/languages/main.go b/examples/table/languages/main.go new file mode 100644 index 00000000..7e2f07f7 --- /dev/null +++ b/examples/table/languages/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "os" + + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" +) + +const ( + purple = lipgloss.Color("99") + gray = lipgloss.Color("245") + lightGray = lipgloss.Color("241") +) + +func main() { + re := lipgloss.NewRenderer(os.Stdout) + + var ( + // HeaderStyle is the lipgloss style used for the table headers. + HeaderStyle = re.NewStyle().Foreground(purple).Bold(true).Align(lipgloss.Center) + // CellStyle is the base lipgloss style used for the table rows. + CellStyle = re.NewStyle().Padding(0, 1).Width(14) + // OddRowStyle is the lipgloss style used for odd-numbered table rows. + OddRowStyle = CellStyle.Copy().Foreground(gray) + // EvenRowStyle is the lipgloss style used for even-numbered table rows. + EvenRowStyle = CellStyle.Copy().Foreground(lightGray) + // BorderStyle is the lipgloss style used for the table border. + BorderStyle = lipgloss.NewStyle().Foreground(purple) + ) + + rows := [][]string{ + {"Chinese", "您好", "你好"}, + {"Japanese", "こんにちは", "やあ"}, + {"Arabic", "أهلين", "أهلا"}, + {"Russian", "Здравствуйте", "Привет"}, + {"Spanish", "Hola", "¿Qué tal?"}, + {"English", "You look absolutely fabulous.", "How's it going?"}, + } + + t := table.New(). + Border(lipgloss.ThickBorder()). + BorderStyle(BorderStyle). + StyleFunc(func(row, col int) lipgloss.Style { + var style lipgloss.Style + + switch { + case row == 0: + return HeaderStyle + case row%2 == 0: + style = EvenRowStyle + default: + style = OddRowStyle + } + + // Make the second column a little wider. + if col == 1 { + style = style.Copy().Width(22) + } + + // Arabic is a right-to-left language, so right align the text. + if rows[row-1][0] == "Arabic" && col != 0 { + style = style.Copy().Align(lipgloss.Right) + } + + return style + }). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + fmt.Println(t) +} diff --git a/examples/table/mindy/main.go b/examples/table/mindy/main.go new file mode 100644 index 00000000..f58fff38 --- /dev/null +++ b/examples/table/mindy/main.go @@ -0,0 +1,65 @@ +package main + +import ( + "fmt" + "os" + + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" +) + +func main() { + re := lipgloss.NewRenderer(os.Stdout) + labelStyle := re.NewStyle().Width(3).Align(lipgloss.Right) + swatchStyle := re.NewStyle().Width(6) + + data := [][]string{} + for i := 0; i < 13; i += 8 { + data = append(data, makeRow(i, i+5)) + } + data = append(data, makeEmptyRow()) + for i := 6; i < 15; i += 8 { + data = append(data, makeRow(i, i+1)) + } + data = append(data, makeEmptyRow()) + for i := 16; i < 231; i += 6 { + data = append(data, makeRow(i, i+5)) + } + data = append(data, makeEmptyRow()) + for i := 232; i < 256; i += 6 { + data = append(data, makeRow(i, i+5)) + } + + t := table.New(). + Border(lipgloss.HiddenBorder()). + Rows(data...). + StyleFunc(func(row, col int) lipgloss.Style { + color := lipgloss.Color(fmt.Sprint(data[row-1][col-col%2])) + switch { + case col%2 == 0: + return labelStyle.Foreground(color) + default: + return swatchStyle.Background(color) + } + }) + + fmt.Println(t) +} + +const rowLength = 12 + +func makeRow(start, end int) []string { + var row []string + for i := start; i <= end; i++ { + row = append(row, fmt.Sprint(i)) + row = append(row, "") + } + for i := len(row); i < rowLength; i++ { + row = append(row, "") + } + return row +} + +func makeEmptyRow() []string { + return makeRow(0, -1) +} diff --git a/examples/table/pokemon/main.go b/examples/table/pokemon/main.go new file mode 100644 index 00000000..45c5c1d1 --- /dev/null +++ b/examples/table/pokemon/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" +) + +func main() { + re := lipgloss.NewRenderer(os.Stdout) + baseStyle := re.NewStyle().Padding(0, 1) + headerStyle := baseStyle.Copy().Foreground(lipgloss.Color("252")).Bold(true) + selectedStyle := baseStyle.Copy().Foreground(lipgloss.Color("#01BE85")).Background(lipgloss.Color("#00432F")) + typeColors := map[string]lipgloss.Color{ + "Bug": lipgloss.Color("#D7FF87"), + "Electric": lipgloss.Color("#FDFF90"), + "Fire": lipgloss.Color("#FF7698"), + "Flying": lipgloss.Color("#FF87D7"), + "Grass": lipgloss.Color("#75FBAB"), + "Ground": lipgloss.Color("#FF875F"), + "Normal": lipgloss.Color("#929292"), + "Poison": lipgloss.Color("#7D5AFC"), + "Water": lipgloss.Color("#00E2C7"), + } + dimTypeColors := map[string]lipgloss.Color{ + "Bug": lipgloss.Color("#97AD64"), + "Electric": lipgloss.Color("#FCFF5F"), + "Fire": lipgloss.Color("#BA5F75"), + "Flying": lipgloss.Color("#C97AB2"), + "Grass": lipgloss.Color("#59B980"), + "Ground": lipgloss.Color("#C77252"), + "Normal": lipgloss.Color("#727272"), + "Poison": lipgloss.Color("#634BD0"), + "Water": lipgloss.Color("#439F8E"), + } + + headers := []any{"#", "Name", "Type 1", "Type 2", "Japanese", "Official Rom."} + data := [][]string{ + {"1", "Bulbasaur", "Grass", "Poison", "フシギダネ", "Bulbasaur"}, + {"2", "Ivysaur", "Grass", "Poison", "フシギソウ", "Ivysaur"}, + {"3", "Venusaur", "Grass", "Poison", "フシギバナ", "Venusaur"}, + {"4", "Charmander", "Fire", "", "ヒトカゲ", "Hitokage"}, + {"5", "Charmeleon", "Fire", "", "リザード", "Lizardo"}, + {"6", "Charizard", "Fire", "Flying", "リザードン", "Lizardon"}, + {"7", "Squirtle", "Water", "", "ゼニガメ", "Zenigame"}, + {"8", "Wartortle", "Water", "", "カメール", "Kameil"}, + {"9", "Blastoise", "Water", "", "カメックス", "Kamex"}, + {"10", "Caterpie", "Bug", "", "キャタピー", "Caterpie"}, + {"11", "Metapod", "Bug", "", "トランセル", "Trancell"}, + {"12", "Butterfree", "Bug", "Flying", "バタフリー", "Butterfree"}, + {"13", "Weedle", "Bug", "Poison", "ビードル", "Beedle"}, + {"14", "Kakuna", "Bug", "Poison", "コクーン", "Cocoon"}, + {"15", "Beedrill", "Bug", "Poison", "スピアー", "Spear"}, + {"16", "Pidgey", "Normal", "Flying", "ポッポ", "Poppo"}, + {"17", "Pidgeotto", "Normal", "Flying", "ピジョン", "Pigeon"}, + {"18", "Pidgeot", "Normal", "Flying", "ピジョット", "Pigeot"}, + {"19", "Rattata", "Normal", "", "コラッタ", "Koratta"}, + {"20", "Raticate", "Normal", "", "ラッタ", "Ratta"}, + {"21", "Spearow", "Normal", "Flying", "オニスズメ", "Onisuzume"}, + {"22", "Fearow", "Normal", "Flying", "オニドリル", "Onidrill"}, + {"23", "Ekans", "Poison", "", "アーボ", "Arbo"}, + {"24", "Arbok", "Poison", "", "アーボック", "Arbok"}, + {"25", "Pikachu", "Electric", "", "ピカチュウ", "Pikachu"}, + {"26", "Raichu", "Electric", "", "ライチュウ", "Raichu"}, + {"27", "Sandshrew", "Ground", "", "サンド", "Sand"}, + {"28", "Sandslash", "Ground", "", "サンドパン", "Sandpan"}, + } + + CapitalizeHeaders := func(data []any) []any { + for i := range data { + data[i] = strings.ToUpper(data[i].(string)) + } + return data + } + + t := table.New(). + Border(lipgloss.NormalBorder()). + BorderStyle(re.NewStyle().Foreground(lipgloss.Color("238"))). + Headers(CapitalizeHeaders(headers)...). + Width(80). + Rows(data...). + StyleFunc(func(row, col int) lipgloss.Style { + if row == 0 { + return headerStyle + } + + if data[row-1][1] == "Pikachu" { + return selectedStyle + } + + even := row%2 == 0 + + switch col { + case 2, 3: // Type 1 + 2 + c := typeColors + if even { + c = dimTypeColors + } + + color := c[fmt.Sprint(data[row-1][col])] + return baseStyle.Copy().Foreground(color) + } + + if even { + return baseStyle.Copy().Foreground(lipgloss.Color("245")) + } + return baseStyle.Copy().Foreground(lipgloss.Color("252")) + }) + fmt.Println(t) +} diff --git a/go.mod b/go.mod index 094f648d..dafbf171 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 ) require ( @@ -15,5 +16,5 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect github.com/rivo/uniseg v0.2.0 // indirect - golang.org/x/sys v0.7.0 // indirect + golang.org/x/sys v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 596350b3..2eb3b422 100644 --- a/go.sum +++ b/go.sum @@ -5,7 +5,6 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= @@ -15,6 +14,8 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/table/rows.go b/table/rows.go new file mode 100644 index 00000000..afebef11 --- /dev/null +++ b/table/rows.go @@ -0,0 +1,113 @@ +package table + +// Data is the interface that wraps the basic methods of a table model. +type Data interface { + // At returns the contents of the cell at the given index. + At(row, cell int) string + + // Rows returns the number of rows in the table. + Rows() int + + // Columns returns the number of columns in the table. + Columns() int +} + +// StringData is a string-based implementation of the Data interface. +type StringData struct { + rows [][]string + columns int +} + +// NewStringData creates a new StringData with the given number of columns. +func NewStringData(rows ...[]string) *StringData { + m := StringData{columns: 0} + + for _, row := range rows { + m.columns = max(m.columns, len(row)) + m.rows = append(m.rows, row) + } + + return &m +} + +// Append appends the given row to the table. +func (m *StringData) Append(row []string) { + m.columns = max(m.columns, len(row)) + m.rows = append(m.rows, row) +} + +// At returns the contents of the cell at the given index. +func (m *StringData) At(row, cell int) string { + if row >= len(m.rows) || cell >= len(m.rows[row]) { + return "" + } + + return m.rows[row][cell] +} + +// Columns returns the number of columns in the table. +func (m *StringData) Columns() int { + return m.columns +} + +// Item appends the given row to the table. +func (m *StringData) Item(rows ...string) *StringData { + m.columns = max(m.columns, len(rows)) + m.rows = append(m.rows, rows) + return m +} + +// Rows returns the number of rows in the table. +func (m *StringData) Rows() int { + return len(m.rows) +} + +// Filter applies a filter on some data. +type Filter struct { + data Data + filter func(row int) bool +} + +// NewFilter initializes a new Filter. +func NewFilter(data Data) *Filter { + return &Filter{data: data} +} + +// Filter applies the given filter function to the data. +func (m *Filter) Filter(f func(row int) bool) *Filter { + m.filter = f + return m +} + +// Row returns the row at the given index. +func (m *Filter) At(row, cell int) string { + j := 0 + for i := 0; i < m.data.Rows(); i++ { + if m.filter(i) { + if j == row { + return m.data.At(i, cell) + } + + j++ + } + } + + return "" +} + +// Columns returns the number of columns in the table. +func (m *Filter) Columns() int { + return m.data.Columns() +} + +// Rows returns the number of rows in the table. +func (m *Filter) Rows() int { + j := 0 + for i := 0; i < m.data.Rows(); i++ { + if m.filter(i) { + j++ + } + } + + return j +} diff --git a/table/table.go b/table/table.go new file mode 100644 index 00000000..2fdd1e15 --- /dev/null +++ b/table/table.go @@ -0,0 +1,522 @@ +package table + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/lipgloss" + "github.com/mattn/go-runewidth" +) + +// StyleFunc is the style function that determines the style of a Cell. +// +// It takes the row and column of the cell as an input and determines the +// lipgloss Style to use for that cell position. +// +// Example: +// +// t := table.New(). +// Headers("Name", "Age"). +// Row("Kini", 4). +// Row("Eli", 1). +// Row("Iris", 102). +// StyleFunc(func(row, col int) lipgloss.Style { +// switch { +// case row == 0: +// return HeaderStyle +// case row%2 == 0: +// return EvenRowStyle +// default: +// return OddRowStyle +// } +// }) +type StyleFunc func(row, col int) lipgloss.Style + +// DefaultStyles is a TableStyleFunc that returns a new Style with no attributes. +func DefaultStyles(_, _ int) lipgloss.Style { + return lipgloss.NewStyle() +} + +// Table is a type for rendering tables. +type Table struct { + styleFunc StyleFunc + border lipgloss.Border + + borderTop bool + borderBottom bool + borderLeft bool + borderRight bool + borderHeader bool + borderColumn bool + borderRow bool + + borderStyle lipgloss.Style + headers []any + data Data + + width int + height int + offset int + + // widths tracks the width of each column. + widths []int + + // heights tracks the height of each row. + heights []int +} + +// New returns a new Table that can be modified through different +// attributes. +// +// By default, a table has no border, no styling, and no rows. +func New() *Table { + return &Table{ + styleFunc: DefaultStyles, + border: lipgloss.RoundedBorder(), + borderBottom: true, + borderColumn: true, + borderHeader: true, + borderLeft: true, + borderRight: true, + borderTop: true, + data: NewStringData(), + } +} + +// ClearRows clears the table rows. +func (t *Table) ClearRows() *Table { + t.data = nil + return t +} + +// StyleFunc sets the style for a cell based on it's position (row, column). +func (t *Table) StyleFunc(style StyleFunc) *Table { + t.styleFunc = style + return t +} + +// style returns the style for a cell based on it's position (row, column). +func (t *Table) style(row, col int) lipgloss.Style { + if t.styleFunc == nil { + return lipgloss.NewStyle() + } + return t.styleFunc(row, col) +} + +// Data sets the table data. +func (t *Table) Data(data Data) *Table { + t.data = data + return t +} + +// Rows appends rows to the table data. +func (t *Table) Rows(rows ...[]string) *Table { + for _, row := range rows { + switch t.data.(type) { + case *StringData: + t.data.(*StringData).Append(row) + } + } + return t +} + +// Row appends a row to the table data. +func (t *Table) Row(row ...string) *Table { + switch t.data.(type) { + case *StringData: + t.data.(*StringData).Append(row) + } + return t +} + +// Headers sets the table headers. +func (t *Table) Headers(headers ...any) *Table { + t.headers = headers + return t +} + +// Border sets the table border. +func (t *Table) Border(border lipgloss.Border) *Table { + t.border = border + return t +} + +// BorderTop sets the top border. +func (t *Table) BorderTop(v bool) *Table { + t.borderTop = v + return t +} + +// BorderBottom sets the bottom border. +func (t *Table) BorderBottom(v bool) *Table { + t.borderBottom = v + return t +} + +// BorderLeft sets the left border. +func (t *Table) BorderLeft(v bool) *Table { + t.borderLeft = v + return t +} + +// BorderRight sets the right border. +func (t *Table) BorderRight(v bool) *Table { + t.borderRight = v + return t +} + +// BorderHeader sets the header separator border. +func (t *Table) BorderHeader(v bool) *Table { + t.borderHeader = v + return t +} + +// BorderColumn sets the column border separator. +func (t *Table) BorderColumn(v bool) *Table { + t.borderColumn = v + return t +} + +// BorderRow sets the row border separator. +func (t *Table) BorderRow(v bool) *Table { + t.borderRow = v + return t +} + +// BorderStyle sets the style for the table border. +func (t *Table) BorderStyle(style lipgloss.Style) *Table { + t.borderStyle = style + return t +} + +// Width sets the table width, this auto-sizes the columns to fit the width by +// either expanding or contracting the widths of each column as a best effort +// approach. +func (t *Table) Width(w int) *Table { + t.width = w + return t +} + +// Height sets the table height. +func (t *Table) Height(h int) *Table { + t.height = h + return t +} + +// Offset sets the table rendering offset. +func (t *Table) Offset(o int) *Table { + t.offset = o + return t +} + +// String returns the table as a string. +func (t *Table) String() string { + hasHeaders := t.headers != nil && len(t.headers) > 0 + hasRows := t.data != nil && t.data.Rows() > 0 + + if !hasHeaders && !hasRows { + return "" + } + + var s strings.Builder + + // Add empty cells to the headers, until it's the same length as the longest + // row (only if there are at headers in the first place). + if hasHeaders { + for i := len(t.headers); i < t.data.Columns(); i++ { + t.headers = append(t.headers, "") + } + } + + // Initialize the widths. + t.widths = make([]int, max(len(t.headers), t.data.Columns())) + t.heights = make([]int, btoi(hasHeaders)+t.data.Rows()) + + // The style function may affect width of the table. It's possible to set + // the StyleFunc after the headers and rows. Update the widths for a final + // time. + for i, cell := range t.headers { + t.widths[i] = max(t.widths[i], lipgloss.Width(t.style(0, i).Render(fmt.Sprint(cell)))) + t.heights[0] = max(t.heights[0], lipgloss.Height(t.style(0, i).Render(fmt.Sprint(cell)))) + } + + for r := 0; r < t.data.Rows(); r++ { + for i := 0; i < t.data.Columns(); i++ { + cell := t.data.At(r, i) + + rendered := t.style(r+1, i).Render(cell) + t.heights[r+btoi(hasHeaders)] = max(t.heights[r+btoi(hasHeaders)], lipgloss.Height(rendered)) + t.widths[i] = max(t.widths[i], lipgloss.Width(rendered)) + } + } + + // Table Resizing Logic. + // + // Given a user defined table width, we must ensure the table is exactly that + // width. This must account for all borders, column, separators, and column + // data. + // + // In the case where the table is narrower than the specified table width, + // we simply expand the columns evenly to fit the width. + // For example, a table with 3 columns takes up 50 characters total, and the + // width specified is 80, we expand each column by 10 characters, adding 30 + // to the total width. + // + // In the case where the table is wider than the specified table width, we + // _could_ simply shrink the columns evenly but this would result in data + // being truncated (perhaps unnecessarily). The naive approach could result + // in very poor cropping of the table data. So, instead of shrinking columns + // evenly, we calculate the median non-whitespace length of each column, and + // shrink the columns based on the largest median. + // + // For example, + // ┌──────┬───────────────┬──────────┐ + // │ Name │ Age of Person │ Location │ + // ├──────┼───────────────┼──────────┤ + // │ Kini │ 40 │ New York │ + // │ Eli │ 30 │ London │ + // │ Iris │ 20 │ Paris │ + // └──────┴───────────────┴──────────┘ + // + // Median non-whitespace length vs column width of each column: + // + // Name: 4 / 5 + // Age of Person: 2 / 15 + // Location: 6 / 10 + // + // The biggest difference is 15 - 2, so we can shrink the 2nd column by 13. + + width := t.computeWidth() + + if width < t.width && t.width > 0 { + // Table is too narrow, expand the columns evenly until it reaches the + // desired width. + var i int + for width < t.width { + t.widths[i]++ + width++ + i = (i + 1) % len(t.widths) + } + } else if width > t.width && t.width > 0 { + // Table is too wide, calculate the median non-whitespace length of each + // column, and shrink the columns based on the largest difference. + columnMedians := make([]int, len(t.widths)) + for c := range t.widths { + trimmedWidth := make([]int, t.data.Rows()) + for r := 0; r < t.data.Rows(); r++ { + renderedCell := t.style(r+btoi(hasHeaders), c).Render(t.data.At(r, c)) + nonWhitespaceChars := lipgloss.Width(strings.TrimRight(renderedCell, " ")) + trimmedWidth[r] = nonWhitespaceChars + 1 + } + + columnMedians[c] = median(trimmedWidth) + } + + // Find the biggest differences between the median and the column width. + // Shrink the columns based on the largest difference. + differences := make([]int, len(t.widths)) + for i := range t.widths { + differences[i] = t.widths[i] - columnMedians[i] + } + + for width > t.width { + index, _ := largest(differences) + if differences[index] < 1 { + break + } + + shrink := min(differences[index], width-t.width) + t.widths[index] -= shrink + width -= shrink + differences[index] = 0 + } + + // Table is still too wide, begin shrinking the columns based on the + // largest column. + for width > t.width { + index, _ := largest(t.widths) + if t.widths[index] < 1 { + break + } + t.widths[index]-- + width-- + } + } + + if t.borderTop { + s.WriteString(t.constructTopBorder()) + s.WriteString("\n") + } + + if hasHeaders { + s.WriteString(t.constructHeaders()) + s.WriteString("\n") + } + + for r := t.offset; r < t.data.Rows(); r++ { + s.WriteString(t.constructRow(r)) + } + + if t.borderBottom { + s.WriteString(t.constructBottomBorder()) + } + + return lipgloss.NewStyle(). + MaxHeight(t.computeHeight()). + MaxWidth(t.width).Render(s.String()) +} + +// computeWidth computes the width of the table in it's current configuration. +func (t *Table) computeWidth() int { + width := sum(t.widths) + btoi(t.borderLeft) + btoi(t.borderRight) + if t.borderColumn { + width += len(t.widths) - 1 + } + return width +} + +// computeHeight computes the height of the table in it's current configuration. +func (t *Table) computeHeight() int { + hasHeaders := t.headers != nil && len(t.headers) > 0 + return sum(t.heights) - 1 + btoi(hasHeaders) + + btoi(t.borderTop) + btoi(t.borderBottom) + + btoi(t.borderHeader) + t.data.Rows()*btoi(t.borderRow) +} + +// Render returns the table as a string. +func (t *Table) Render() string { + return t.String() +} + +// constructTopBorder constructs the top border for the table given it's current +// border configuration and data. +func (t *Table) constructTopBorder() string { + var s strings.Builder + if t.borderLeft { + s.WriteString(t.borderStyle.Render(t.border.TopLeft)) + } + for i := 0; i < len(t.widths); i++ { + s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i]))) + if i < len(t.widths)-1 && t.borderColumn { + s.WriteString(t.borderStyle.Render(t.border.MiddleTop)) + } + } + if t.borderRight { + s.WriteString(t.borderStyle.Render(t.border.TopRight)) + } + return s.String() +} + +// constructBottomBorder constructs the bottom border for the table given it's current +// border configuration and data. +func (t *Table) constructBottomBorder() string { + var s strings.Builder + if t.borderLeft { + s.WriteString(t.borderStyle.Render(t.border.BottomLeft)) + } + for i := 0; i < len(t.widths); i++ { + s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i]))) + if i < len(t.widths)-1 && t.borderColumn { + s.WriteString(t.borderStyle.Render(t.border.MiddleBottom)) + } + } + if t.borderRight { + s.WriteString(t.borderStyle.Render(t.border.BottomRight)) + } + return s.String() +} + +// constructHeaders constructs the headers for the table given it's current +// header configuration and data. +func (t *Table) constructHeaders() string { + var s strings.Builder + if t.borderLeft { + s.WriteString(t.borderStyle.Render(t.border.Left)) + } + for i, header := range t.headers { + s.WriteString(t.style(0, i). + MaxHeight(1). + Width(t.widths[i]). + MaxWidth(t.widths[i]). + Render(runewidth.Truncate(fmt.Sprint(header), t.widths[i], "…"))) + if i < len(t.headers)-1 && t.borderColumn { + s.WriteString(t.borderStyle.Render(t.border.Left)) + } + } + if t.borderHeader { + if t.borderRight { + s.WriteString(t.borderStyle.Render(t.border.Right)) + } + s.WriteString("\n") + if t.borderLeft { + s.WriteString(t.borderStyle.Render(t.border.MiddleLeft)) + } + for i := 0; i < len(t.headers); i++ { + s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Top, t.widths[i]))) + if i < len(t.headers)-1 && t.borderColumn { + s.WriteString(t.borderStyle.Render(t.border.Middle)) + } + } + if t.borderRight { + s.WriteString(t.borderStyle.Render(t.border.MiddleRight)) + } + } + if t.borderRight && !t.borderHeader { + s.WriteString(t.borderStyle.Render(t.border.Right)) + } + return s.String() +} + +// constructRow constructs the row for the table given an index and row data +// based on the current configuration. +func (t *Table) constructRow(index int) string { + var s strings.Builder + + hasHeaders := t.headers != nil && len(t.headers) > 0 + height := t.heights[index+btoi(hasHeaders)] + + var cells []string + left := strings.Repeat(t.borderStyle.Render(t.border.Left)+"\n", height) + if t.borderLeft { + cells = append(cells, left) + } + + for c := 0; c < t.data.Columns(); c++ { + cell := t.data.At(index, c) + + cells = append(cells, t.style(index+1, c). + Height(height). + MaxHeight(height). + Width(t.widths[c]). + MaxWidth(t.widths[c]). + Render(runewidth.Truncate(cell, t.widths[c]*height, "…"))) + + if c < t.data.Columns()-1 && t.borderColumn { + cells = append(cells, left) + } + } + + if t.borderRight { + right := strings.Repeat(t.borderStyle.Render(t.border.Right)+"\n", height) + cells = append(cells, right) + } + + for i, cell := range cells { + cells[i] = strings.TrimRight(cell, "\n") + } + + s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, cells...) + "\n") + + if t.borderRow && index < t.data.Rows()-1 { + s.WriteString(t.borderStyle.Render(t.border.MiddleLeft)) + for i := 0; i < len(t.widths); i++ { + s.WriteString(t.borderStyle.Render(strings.Repeat(t.border.Bottom, t.widths[i]))) + if i < len(t.widths)-1 && t.borderColumn { + s.WriteString(t.borderStyle.Render(t.border.Middle)) + } + } + s.WriteString(t.borderStyle.Render(t.border.MiddleRight) + "\n") + } + + return s.String() +} diff --git a/table/table_test.go b/table/table_test.go new file mode 100644 index 00000000..4a86f1be --- /dev/null +++ b/table/table_test.go @@ -0,0 +1,898 @@ +package table + +import ( + "strings" + "testing" + + "github.com/charmbracelet/lipgloss" +) + +var TableStyle = func(row, col int) lipgloss.Style { + switch { + case row == 0: + return lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Center) + case row%2 == 0: + return lipgloss.NewStyle().Padding(0, 1) + default: + return lipgloss.NewStyle().Padding(0, 1) + } +} + +func TestTable(t *testing.T) { + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Row("Chinese", "Nǐn hǎo", "Nǐ hǎo"). + Row("French", "Bonjour", "Salut"). + Row("Japanese", "こんにちは", "やあ"). + Row("Russian", "Zdravstvuyte", "Privet"). + Row("Spanish", "Hola", "¿Qué tal?") + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼──────────────┼───────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ French │ Bonjour │ Salut │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableEmpty(t *testing.T) { + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL") + + expected := strings.TrimSpace(` +┌──────────┬────────┬──────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼────────┼──────────┤ +└──────────┴────────┴──────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableOffset(t *testing.T) { + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Row("Chinese", "Nǐn hǎo", "Nǐ hǎo"). + Row("French", "Bonjour", "Salut"). + Row("Japanese", "こんにちは", "やあ"). + Row("Russian", "Zdravstvuyte", "Privet"). + Row("Spanish", "Hola", "¿Qué tal?"). + Offset(1) + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼──────────────┼───────────┤ +│ French │ Bonjour │ Salut │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableBorder(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.DoubleBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +╔══════════╦══════════════╦═══════════╗ +║ LANGUAGE ║ FORMAL ║ INFORMAL ║ +╠══════════╬══════════════╬═══════════╣ +║ Chinese ║ Nǐn hǎo ║ Nǐ hǎo ║ +║ French ║ Bonjour ║ Salut ║ +║ Japanese ║ こんにちは ║ やあ ║ +║ Russian ║ Zdravstvuyte ║ Privet ║ +║ Spanish ║ Hola ║ ¿Qué tal? ║ +╚══════════╩══════════════╩═══════════╝ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableSetRows(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼──────────────┼───────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ French │ Bonjour │ Salut │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestMoreCellsThanHeaders(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ LANGUAGE │ FORMAL │ │ +├──────────┼──────────────┼───────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ French │ Bonjour │ Salut │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestMoreCellsThanHeadersExtra(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet", "Privet", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┬────────┬────────┐ +│ LANGUAGE │ FORMAL │ │ │ │ +├──────────┼──────────────┼───────────┼────────┼────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ │ │ +│ French │ Bonjour │ Salut │ Salut │ │ +│ Japanese │ こんにちは │ やあ │ │ │ +│ Russian │ Zdravstvuyte │ Privet │ Privet │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ │ │ +└──────────┴──────────────┴───────────┴────────┴────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableNoHeaders(t *testing.T) { + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Row("Chinese", "Nǐn hǎo", "Nǐ hǎo"). + Row("French", "Bonjour", "Salut"). + Row("Japanese", "こんにちは", "やあ"). + Row("Russian", "Zdravstvuyte", "Privet"). + Row("Spanish", "Hola", "¿Qué tal?") + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ French │ Bonjour │ Salut │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableNoColumnSeparators(t *testing.T) { + table := New(). + Border(lipgloss.NormalBorder()). + BorderColumn(false). + StyleFunc(TableStyle). + Row("Chinese", "Nǐn hǎo", "Nǐ hǎo"). + Row("French", "Bonjour", "Salut"). + Row("Japanese", "こんにちは", "やあ"). + Row("Russian", "Zdravstvuyte", "Privet"). + Row("Spanish", "Hola", "¿Qué tal?") + + expected := strings.TrimSpace(` +┌───────────────────────────────────┐ +│ Chinese Nǐn hǎo Nǐ hǎo │ +│ French Bonjour Salut │ +│ Japanese こんにちは やあ │ +│ Russian Zdravstvuyte Privet │ +│ Spanish Hola ¿Qué tal? │ +└───────────────────────────────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableNoColumnSeparatorsWithHeaders(t *testing.T) { + table := New(). + Border(lipgloss.NormalBorder()). + BorderColumn(false). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Row("Chinese", "Nǐn hǎo", "Nǐ hǎo"). + Row("French", "Bonjour", "Salut"). + Row("Japanese", "こんにちは", "やあ"). + Row("Russian", "Zdravstvuyte", "Privet"). + Row("Spanish", "Hola", "¿Qué tal?") + + expected := strings.TrimSpace(` +┌───────────────────────────────────┐ +│ LANGUAGE FORMAL INFORMAL │ +├───────────────────────────────────┤ +│ Chinese Nǐn hǎo Nǐ hǎo │ +│ French Bonjour Salut │ +│ Japanese こんにちは やあ │ +│ Russian Zdravstvuyte Privet │ +│ Spanish Hola ¿Qué tal? │ +└───────────────────────────────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestBorderColumnsWithExtraRows(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet", "Privet", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + BorderColumn(false). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌───────────────────────────────────────────────────┐ +│ LANGUAGE FORMAL │ +├───────────────────────────────────────────────────┤ +│ Chinese Nǐn hǎo Nǐ hǎo │ +│ French Bonjour Salut Salut │ +│ Japanese こんにちは やあ │ +│ Russian Zdravstvuyte Privet Privet Privet │ +│ Spanish Hola ¿Qué tal? │ +└───────────────────────────────────────────────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestNew(t *testing.T) { + table := New() + expected := "" + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableUnsetBorders(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...). + BorderTop(false). + BorderBottom(false). + BorderLeft(false). + BorderRight(false) + + expected := strings.TrimPrefix(` + LANGUAGE │ FORMAL │ INFORMAL +──────────┼──────────────┼─────────── + Chinese │ Nǐn hǎo │ Nǐ hǎo + French │ Bonjour │ Salut + Japanese │ こんにちは │ やあ + Russian │ Zdravstvuyte │ Privet + Spanish │ Hola │ ¿Qué tal? `, "\n") + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", debug(expected), debug(table.String())) + } +} + +func TestTableUnsetHeaderSeparator(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...). + BorderHeader(false). + BorderTop(false). + BorderBottom(false). + BorderLeft(false). + BorderRight(false) + + expected := strings.TrimPrefix(` + LANGUAGE │ FORMAL │ INFORMAL + Chinese │ Nǐn hǎo │ Nǐ hǎo + French │ Bonjour │ Salut + Japanese │ こんにちは │ やあ + Russian │ Zdravstvuyte │ Privet + Spanish │ Hola │ ¿Qué tal? `, "\n") + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", debug(expected), debug(table.String())) + } +} + +func TestTableUnsetHeaderSeparatorWithBorder(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...). + BorderHeader(false) + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ French │ Bonjour │ Salut │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableRowSeparators(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + BorderRow(true). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼──────────────┼───────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +├──────────┼──────────────┼───────────┤ +│ French │ Bonjour │ Salut │ +├──────────┼──────────────┼───────────┤ +│ Japanese │ こんにちは │ やあ │ +├──────────┼──────────────┼───────────┤ +│ Russian │ Zdravstvuyte │ Privet │ +├──────────┼──────────────┼───────────┤ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableHeights(t *testing.T) { + styleFunc := func(row, col int) lipgloss.Style { + if row == 0 { + return lipgloss.NewStyle().Padding(0, 1) + } + if col == 0 { + return lipgloss.NewStyle().Width(18).Padding(1) + } + return lipgloss.NewStyle().Width(25).Padding(1, 2) + } + + rows := [][]string{ + {"Chutar o balde", `Literally translates to "kick the bucket." It's used when someone gives up or loses patience.`}, + {"Engolir sapos", `Literally means "to swallow frogs." It's used to describe someone who has to tolerate or endure unpleasant situations.`}, + {"Arroz de festa", `Literally means "party rice." It´s used to refer to someone who shows up everywhere.`}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(styleFunc). + Headers("EXPRESSION", "MEANING"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌──────────────────┬─────────────────────────┐ +│ EXPRESSION │ MEANING │ +├──────────────────┼─────────────────────────┤ +│ │ │ +│ Chutar o balde │ Literally translates │ +│ │ to "kick the bucket." │ +│ │ It's used when │ +│ │ someone gives up or │ +│ │ loses patience. │ +│ │ │ +│ │ │ +│ Engolir sapos │ Literally means "to │ +│ │ swallow frogs." It's │ +│ │ used to describe │ +│ │ someone who has to │ +│ │ tolerate or endure │ +│ │ unpleasant │ +│ │ situations. │ +│ │ │ +│ │ │ +│ Arroz de festa │ Literally means │ +│ │ "party rice." It´s │ +│ │ used to refer to │ +│ │ someone who shows up │ +│ │ everywhere. │ +│ │ │ +└──────────────────┴─────────────────────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableMultiLineRowSeparator(t *testing.T) { + styleFunc := func(row, col int) lipgloss.Style { + if row == 0 { + return lipgloss.NewStyle().Padding(0, 1) + } + if col == 0 { + return lipgloss.NewStyle().Width(18).Padding(1) + } + return lipgloss.NewStyle().Width(25).Padding(1, 2) + } + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(styleFunc). + Headers("EXPRESSION", "MEANING"). + BorderRow(true). + Row("Chutar o balde", `Literally translates to "kick the bucket." It's used when someone gives up or loses patience.`). + Row("Engolir sapos", `Literally means "to swallow frogs." It's used to describe someone who has to tolerate or endure unpleasant situations.`). + Row("Arroz de festa", `Literally means "party rice." It´s used to refer to someone who shows up everywhere.`) + + expected := strings.TrimSpace(` +┌──────────────────┬─────────────────────────┐ +│ EXPRESSION │ MEANING │ +├──────────────────┼─────────────────────────┤ +│ │ │ +│ Chutar o balde │ Literally translates │ +│ │ to "kick the bucket." │ +│ │ It's used when │ +│ │ someone gives up or │ +│ │ loses patience. │ +│ │ │ +├──────────────────┼─────────────────────────┤ +│ │ │ +│ Engolir sapos │ Literally means "to │ +│ │ swallow frogs." It's │ +│ │ used to describe │ +│ │ someone who has to │ +│ │ tolerate or endure │ +│ │ unpleasant │ +│ │ situations. │ +│ │ │ +├──────────────────┼─────────────────────────┤ +│ │ │ +│ Arroz de festa │ Literally means │ +│ │ "party rice." It´s │ +│ │ used to refer to │ +│ │ someone who shows up │ +│ │ everywhere. │ +│ │ │ +└──────────────────┴─────────────────────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableWidthExpand(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Width(80). + StyleFunc(TableStyle). + Border(lipgloss.NormalBorder()). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌────────────────────────┬────────────────────────────┬────────────────────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├────────────────────────┼────────────────────────────┼────────────────────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ French │ Bonjour │ Salut │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└────────────────────────┴────────────────────────────┴────────────────────────┘ +`) + + if lipgloss.Width(table.String()) != 80 { + t.Fatalf("expected table width to be 80, got %d", lipgloss.Width(table.String())) + } + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableWidthShrink(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Width(30). + StyleFunc(TableStyle). + Border(lipgloss.NormalBorder()). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌─────────┬─────────┬────────┐ +│ LANGUAG │ FORMAL │ INFORM │ +├─────────┼─────────┼────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ French │ Bonjour │ Salut │ +│ Japanes │ こんに │ やあ │ +│ Russian │ Zdravst │ Privet │ +│ Spanish │ Hola │ ¿Qué │ +└─────────┴─────────┴────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableWidthSmartCrop(t *testing.T) { + rows := [][]string{ + {"Kini", "40", "New York"}, + {"Eli", "30", "London"}, + {"Iris", "20", "Paris"}, + } + + table := New(). + Width(25). + StyleFunc(TableStyle). + Border(lipgloss.NormalBorder()). + Headers("Name", "Age of Person", "Location"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌──────┬─────┬──────────┐ +│ Name │ Age │ Location │ +├──────┼─────┼──────────┤ +│ Kini │ 40 │ New York │ +│ Eli │ 30 │ London │ +│ Iris │ 20 │ Paris │ +└──────┴─────┴──────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableWidthSmartCropExtensive(t *testing.T) { + rows := [][]string{ + {"Chinese", "您好", "你好"}, + {"Japanese", "こんにちは", "やあ"}, + {"Arabic", "أهلين", "أهلا"}, + {"Russian", "Здравствуйте", "Привет"}, + {"Spanish", "Hola", "¿Qué tal?"}, + {"English", "You look absolutely fabulous.", "How's it going?"}, + } + + table := New(). + Width(18). + StyleFunc(TableStyle). + Border(lipgloss.ThickBorder()). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┏━━━━┳━━━━━┳━━━━━┓ +┃ LA ┃ FOR ┃ INF ┃ +┣━━━━╋━━━━━╋━━━━━┫ +┃ Ch ┃ 您 ┃ 你 ┃ +┃ Ja ┃ こ ┃ や ┃ +┃ Ar ┃ أهل ┃ أهل ┃ +┃ Ru ┃ Здр ┃ При ┃ +┃ Sp ┃ Hol ┃ ¿Qu ┃ +┃ En ┃ You ┃ How ┃ +┗━━━━┻━━━━━┻━━━━━┛ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableWidthSmartCropTiny(t *testing.T) { + rows := [][]string{ + {"Chinese", "您好", "你好"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Здравствуйте", "Привет"}, + {"Spanish", "Hola", "¿Qué tal?"}, + {"English", "You look absolutely fabulous.", "How's it going?"}, + } + + table := New(). + Width(1). + StyleFunc(TableStyle). + Border(lipgloss.NormalBorder()). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +┌ +│ +├ +│ +│ +│ +│ +│ +└ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableWidths(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Width(30). + StyleFunc(TableStyle). + BorderLeft(false). + BorderRight(false). + Border(lipgloss.NormalBorder()). + BorderColumn(false). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +────────────────────────────── + LANGUAGE FORMAL INFORMAL +────────────────────────────── + Chinese Nǐn hǎo Nǐ hǎo + French Bonjour Salut + Japanese こんに やあ + Russian Zdravst Privet + Spanish Hola ¿Qué tal? +────────────────────────────── +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestTableWidthShrinkNoBorders(t *testing.T) { + rows := [][]string{ + {"Chinese", "Nǐn hǎo", "Nǐ hǎo"}, + {"French", "Bonjour", "Salut"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Zdravstvuyte", "Privet"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Width(30). + StyleFunc(TableStyle). + BorderLeft(false). + BorderRight(false). + Border(lipgloss.NormalBorder()). + BorderColumn(false). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + expected := strings.TrimSpace(` +────────────────────────────── + LANGUAGE FORMAL INFORMAL +────────────────────────────── + Chinese Nǐn hǎo Nǐ hǎo + French Bonjour Salut + Japanese こんに やあ + Russian Zdravst Privet + Spanish Hola ¿Qué tal? +────────────────────────────── +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestFilter(t *testing.T) { + data := NewStringData(). + Item("Chinese", "Nǐn hǎo", "Nǐ hǎo"). + Item("French", "Bonjour", "Salut"). + Item("Japanese", "こんにちは", "やあ"). + Item("Russian", "Zdravstvuyte", "Privet"). + Item("Spanish", "Hola", "¿Qué tal?") + + filter := NewFilter(data).Filter(func(row int) bool { + return data.At(row, 0) != "French" + }) + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Data(filter) + + expected := strings.TrimSpace(` +┌──────────┬──────────────┬───────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼──────────────┼───────────┤ +│ Chinese │ Nǐn hǎo │ Nǐ hǎo │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Zdravstvuyte │ Privet │ +│ Spanish │ Hola │ ¿Qué tal? │ +└──────────┴──────────────┴───────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func TestFilterInverse(t *testing.T) { + data := NewStringData(). + Item("Chinese", "Nǐn hǎo", "Nǐ hǎo"). + Item("French", "Bonjour", "Salut"). + Item("Japanese", "こんにちは", "やあ"). + Item("Russian", "Zdravstvuyte", "Privet"). + Item("Spanish", "Hola", "¿Qué tal?") + + filter := NewFilter(data).Filter(func(row int) bool { + return data.At(row, 0) == "French" + }) + + table := New(). + Border(lipgloss.NormalBorder()). + StyleFunc(TableStyle). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Data(filter) + + expected := strings.TrimSpace(` +┌──────────┬─────────┬──────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼─────────┼──────────┤ +│ French │ Bonjour │ Salut │ +└──────────┴─────────┴──────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + +func debug(s string) string { + return strings.ReplaceAll(s, " ", ".") +} diff --git a/table/util.go b/table/util.go new file mode 100644 index 00000000..51285338 --- /dev/null +++ b/table/util.go @@ -0,0 +1,62 @@ +package table + +import "golang.org/x/exp/slices" + +// btoi converts a boolean to an integer, 1 if true, 0 if false. +func btoi(b bool) int { + if b { + return 1 + } + return 0 +} + +// max returns the greater of two integers. +func max(a, b int) int { + if a > b { + return a + } + return b +} + +// min returns the greater of two integers. +func min(a, b int) int { + if a < b { + return a + } + return b +} + +// sum returns the sum of all integers in a slice. +func sum(n []int) int { + var sum int + for _, i := range n { + sum += i + } + return sum +} + +// median returns the median of a slice of integers. +func median(n []int) int { + slices.Sort(n) + + if len(n) <= 0 { + return 0 + } + if len(n)%2 == 0 { + h := len(n) / 2 //nolint:gomnd + return (n[h-1] + n[h]) / 2 //nolint:gomnd + } + return n[len(n)/2] +} + +// largest returns the largest element and it's index from a slice of integers. +func largest(n []int) (int, int) { //nolint:unparam + var largest, index int + for i, e := range n { + if n[i] > n[index] { + largest = e + index = i + } + } + return index, largest +} diff --git a/unset.go b/unset.go index f889f9e2..770734c8 100644 --- a/unset.go +++ b/unset.go @@ -287,7 +287,7 @@ func (s Style) UnsetMaxHeight() Style { return s } -// UnsetMaxHeight removes the max height style rule, if set. +// UnsetTabWidth removes the tab width style rule, if set. func (s Style) UnsetTabWidth() Style { delete(s.rules, tabWidthKey) return s From 42db8736172b8e35766ae630f04d0be3b16951aa Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Tue, 10 Oct 2023 11:32:38 -0400 Subject: [PATCH 085/126] test(table): ensure README example works --- examples/table/languages/main.go | 5 ++-- table/table_test.go | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/examples/table/languages/main.go b/examples/table/languages/main.go index 7e2f07f7..601cdc84 100644 --- a/examples/table/languages/main.go +++ b/examples/table/languages/main.go @@ -36,7 +36,6 @@ func main() { {"Arabic", "أهلين", "أهلا"}, {"Russian", "Здравствуйте", "Привет"}, {"Spanish", "Hola", "¿Qué tal?"}, - {"English", "You look absolutely fabulous.", "How's it going?"}, } t := table.New(). @@ -60,7 +59,7 @@ func main() { } // Arabic is a right-to-left language, so right align the text. - if rows[row-1][0] == "Arabic" && col != 0 { + if row < len(rows) && rows[row-1][0] == "Arabic" && col != 0 { style = style.Copy().Align(lipgloss.Right) } @@ -69,5 +68,7 @@ func main() { Headers("LANGUAGE", "FORMAL", "INFORMAL"). Rows(rows...) + t.Row("English", "You look absolutely fabulous.", "How's it going?") + fmt.Println(t) } diff --git a/table/table_test.go b/table/table_test.go index 4a86f1be..4e5ccad0 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -46,6 +46,54 @@ func TestTable(t *testing.T) { } } +func TestTableExample(t *testing.T) { + HeaderStyle := lipgloss.NewStyle().Padding(0, 1).Align(lipgloss.Center) + EvenRowStyle := lipgloss.NewStyle().Padding(0, 1) + OddRowStyle := lipgloss.NewStyle().Padding(0, 1) + + rows := [][]string{ + {"Chinese", "您好", "你好"}, + {"Japanese", "こんにちは", "やあ"}, + {"Russian", "Здравствуйте", "Привет"}, + {"Spanish", "Hola", "¿Qué tal?"}, + } + + table := New(). + Border(lipgloss.NormalBorder()). + BorderStyle(lipgloss.NewStyle().Foreground(lipgloss.Color("99"))). + StyleFunc(func(row, col int) lipgloss.Style { + switch { + case row == 0: + return HeaderStyle + case row%2 == 0: + return EvenRowStyle + default: + return OddRowStyle + } + }). + Headers("LANGUAGE", "FORMAL", "INFORMAL"). + Rows(rows...) + + // You can also add tables row-by-row + table.Row("English", "You look absolutely fabulous.", "How's it going?") + + expected := strings.TrimSpace(` +┌──────────┬───────────────────────────────┬─────────────────┐ +│ LANGUAGE │ FORMAL │ INFORMAL │ +├──────────┼───────────────────────────────┼─────────────────┤ +│ Chinese │ 您好 │ 你好 │ +│ Japanese │ こんにちは │ やあ │ +│ Russian │ Здравствуйте │ Привет │ +│ Spanish │ Hola │ ¿Qué tal? │ +│ English │ You look absolutely fabulous. │ How's it going? │ +└──────────┴───────────────────────────────┴─────────────────┘ +`) + + if table.String() != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, table.String()) + } +} + func TestTableEmpty(t *testing.T) { table := New(). Border(lipgloss.NormalBorder()). From f093bc15098a6f7ea879f4345445684fdb458601 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Thu, 12 Oct 2023 00:53:13 -0400 Subject: [PATCH 086/126] Make headers `[]string` (#234) * fix: make headers `[]string` * downgrade Lip Gloss to 1.17 --- .github/workflows/build.yml | 2 +- examples/go.mod | 3 +- examples/go.sum | 104 ++++++++++++++++++++++++++++++++- examples/table/pokemon/main.go | 6 +- go.mod | 3 +- go.sum | 4 +- table/table.go | 11 ++-- table/util.go | 6 +- 8 files changed, 119 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ac0cd2a..81c9e743 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [~1.18, ^1] + go-version: [~1.17, ^1] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: diff --git a/examples/go.mod b/examples/go.mod index 34b54802..c87579b3 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -1,6 +1,6 @@ module examples -go 1.18 +go 1.17 replace github.com/charmbracelet/lipgloss => ../ @@ -25,6 +25,5 @@ require ( github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/sys v0.12.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index ff1b55fe..3d63a536 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,47 +1,147 @@ +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/caarlos0/sshmarshal v0.0.0-20220308164159-9ddb9f83c6b3/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= github.com/caarlos0/sshmarshal v0.1.0 h1:zTCZrDORFfWh526Tsb7vCm3+Yg/SfW/Ub8aQDeosk0I= github.com/caarlos0/sshmarshal v0.1.0/go.mod h1:7Pd/0mmq9x/JCzKauogNjSQEhivBclCQHfr9dlpDIyA= +github.com/charmbracelet/bubbletea v0.20.0/go.mod h1:zpkze1Rioo4rJELjRyGlm9T2YNou1Fm4LIJQSa5QMEM= github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBcsan2Y= github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg= github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/table/pokemon/main.go b/examples/table/pokemon/main.go index 45c5c1d1..628d90d6 100644 --- a/examples/table/pokemon/main.go +++ b/examples/table/pokemon/main.go @@ -37,7 +37,7 @@ func main() { "Water": lipgloss.Color("#439F8E"), } - headers := []any{"#", "Name", "Type 1", "Type 2", "Japanese", "Official Rom."} + headers := []string{"#", "Name", "Type 1", "Type 2", "Japanese", "Official Rom."} data := [][]string{ {"1", "Bulbasaur", "Grass", "Poison", "フシギダネ", "Bulbasaur"}, {"2", "Ivysaur", "Grass", "Poison", "フシギソウ", "Ivysaur"}, @@ -69,9 +69,9 @@ func main() { {"28", "Sandslash", "Ground", "", "サンドパン", "Sandpan"}, } - CapitalizeHeaders := func(data []any) []any { + CapitalizeHeaders := func(data []string) []string { for i := range data { - data[i] = strings.ToUpper(data[i].(string)) + data[i] = strings.ToUpper(data[i]) } return data } diff --git a/go.mod b/go.mod index dafbf171..0123aabb 100644 --- a/go.mod +++ b/go.mod @@ -2,13 +2,12 @@ module github.com/charmbracelet/lipgloss retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. -go 1.18 +go 1.17 require ( github.com/mattn/go-runewidth v0.0.15 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 ) require ( diff --git a/go.sum b/go.sum index 2eb3b422..cba58dc9 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,7 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= @@ -14,8 +15,7 @@ github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1n github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/table/table.go b/table/table.go index 2fdd1e15..953721a0 100644 --- a/table/table.go +++ b/table/table.go @@ -1,7 +1,6 @@ package table import ( - "fmt" "strings" "github.com/charmbracelet/lipgloss" @@ -51,7 +50,7 @@ type Table struct { borderRow bool borderStyle lipgloss.Style - headers []any + headers []string data Data width int @@ -130,7 +129,7 @@ func (t *Table) Row(row ...string) *Table { } // Headers sets the table headers. -func (t *Table) Headers(headers ...any) *Table { +func (t *Table) Headers(headers ...string) *Table { t.headers = headers return t } @@ -236,8 +235,8 @@ func (t *Table) String() string { // the StyleFunc after the headers and rows. Update the widths for a final // time. for i, cell := range t.headers { - t.widths[i] = max(t.widths[i], lipgloss.Width(t.style(0, i).Render(fmt.Sprint(cell)))) - t.heights[0] = max(t.heights[0], lipgloss.Height(t.style(0, i).Render(fmt.Sprint(cell)))) + t.widths[i] = max(t.widths[i], lipgloss.Width(t.style(0, i).Render(cell))) + t.heights[0] = max(t.heights[0], lipgloss.Height(t.style(0, i).Render(cell))) } for r := 0; r < t.data.Rows(); r++ { @@ -438,7 +437,7 @@ func (t *Table) constructHeaders() string { MaxHeight(1). Width(t.widths[i]). MaxWidth(t.widths[i]). - Render(runewidth.Truncate(fmt.Sprint(header), t.widths[i], "…"))) + Render(runewidth.Truncate(header, t.widths[i], "…"))) if i < len(t.headers)-1 && t.borderColumn { s.WriteString(t.borderStyle.Render(t.border.Left)) } diff --git a/table/util.go b/table/util.go index 51285338..cd7c2b30 100644 --- a/table/util.go +++ b/table/util.go @@ -1,6 +1,8 @@ package table -import "golang.org/x/exp/slices" +import ( + "sort" +) // btoi converts a boolean to an integer, 1 if true, 0 if false. func btoi(b bool) int { @@ -37,7 +39,7 @@ func sum(n []int) int { // median returns the median of a slice of integers. func median(n []int) int { - slices.Sort(n) + sort.Ints(n) if len(n) <= 0 { return 0 From b0605d31b2ce814e8cbc168be01df08bafc4f29d Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Thu, 12 Oct 2023 07:30:51 -0400 Subject: [PATCH 087/126] docs: also link to table examples in the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f3aa745a..40777a4f 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,7 @@ fmt.Println(t) ![Table Example](https://github.com/charmbracelet/lipgloss/assets/42545625/6e4b70c4-f494-45da-a467-bdd27df30d5d) -For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc). +For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc) and [examples](https://github.com/charmbracelet/lipgloss/tree/master/examples/table). *** From 49671292f7b87676e1854232eac64b3b454434a2 Mon Sep 17 00:00:00 2001 From: Reid Mason Date: Sat, 21 Oct 2023 00:52:10 +0100 Subject: [PATCH 088/126] fix: Corrected border shorthand functions explanation (#237) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 40777a4f..21bc40c2 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ pattern to the margin and padding shorthand functions. lipgloss.NewStyle(). Border(lipgloss.ThickBorder(), true, false) -// Add a thick border to the right and bottom sides. Rules are set clockwise +// Add a double border to the top and left sides. Rules are set clockwise // from top. lipgloss.NewStyle(). Border(lipgloss.DoubleBorder(), true, false, false, true) From 59fcf6fa80bd800114775ed055fda65006e6c319 Mon Sep 17 00:00:00 2001 From: Julien Ammous Date: Wed, 22 Nov 2023 21:16:16 +0100 Subject: [PATCH 089/126] fix(align): fixed help comment (#239) --- set.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/set.go b/set.go index 432eac5d..5483f2db 100644 --- a/set.go +++ b/set.go @@ -115,7 +115,7 @@ func (s Style) Height(i int) Style { // // With one argument, the position value is applied to the horizontal alignment. // -// With two arguments, the value is applied to the vertical and horizontal +// With two arguments, the value is applied to the horizontal and vertical // alignments, in that order. func (s Style) Align(p ...Position) Style { if len(p) > 0 { From d354842a40db7ef94c68b35b3570f419d0b85e4a Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 27 Nov 2023 14:33:38 -0500 Subject: [PATCH 090/126] feat: Style.Transform for altering strings at render time (#232) * feat: Style.Transform for altering strings at render time * feat: add `UnsetTransform` --------- Co-authored-by: Maas Lalani --- get.go | 17 +++++++++++++++++ set.go | 12 ++++++++++++ style.go | 8 ++++++++ style_test.go | 37 +++++++++++++++++++++++++++++++++++++ unset.go | 6 ++++++ 5 files changed, 80 insertions(+) diff --git a/get.go b/get.go index d2623c4e..9be3d64d 100644 --- a/get.go +++ b/get.go @@ -408,6 +408,12 @@ func (s Style) GetFrameSize() (x, y int) { return s.GetHorizontalFrameSize(), s.GetVerticalFrameSize() } +// GetTransform returns the transform set on the style. If no transform is set +// nil is returned. +func (s Style) GetTransform() func(string) string { + return s.getAsTransform(transformKey) +} + // Returns whether or not the given property is set. func (s Style) isSet(k propKey) bool { _, exists := s.rules[k] @@ -469,6 +475,17 @@ func (s Style) getBorderStyle() Border { return noBorder } +func (s Style) getAsTransform(k propKey) func(string) string { + v, ok := s.rules[k] + if !ok { + return nil + } + if fn, ok := v.(func(string) string); ok { + return fn + } + return nil +} + // Split a string into lines, additionally returning the size of the widest // line. func getLines(s string) (lines []string, widest int) { diff --git a/set.go b/set.go index 5483f2db..5846b598 100644 --- a/set.go +++ b/set.go @@ -544,6 +544,18 @@ func (s Style) StrikethroughSpaces(v bool) Style { return s } +// Transform applies a given function to a string at render time, allowing for +// the string being rendered to be manipuated. +// +// Example: +// +// s := NewStyle().Transform(strings.ToUpper) +// fmt.Println(s.Render("raow!") // "RAOW!" +func (s Style) Transform(fn func(string) string) Style { + s.set(transformKey, fn) + return s +} + // Renderer sets the renderer for the style. This is useful for changing the // renderer for a style that is being used in a different context. func (s Style) Renderer(r *Renderer) Style { diff --git a/style.go b/style.go index ee50bcf7..72be9ef3 100644 --- a/style.go +++ b/style.go @@ -73,6 +73,8 @@ const ( tabWidthKey underlineSpacesKey strikethroughSpacesKey + + transformKey ) // A set of properties. @@ -225,6 +227,8 @@ func (s Style) Render(strs ...string) string { // Do we need to style spaces separately? useSpaceStyler = underlineSpaces || strikethroughSpaces + + transform = s.getAsTransform(transformKey) ) if len(s.rules) == 0 { @@ -401,6 +405,10 @@ func (s Style) Render(strs ...string) string { str = strings.Join(lines[:min(maxHeight, len(lines))], "\n") } + if transform != nil { + return transform(str) + } + return str } diff --git a/style_test.go b/style_test.go index 9cb2a9a2..a5148db0 100644 --- a/style_test.go +++ b/style_test.go @@ -3,6 +3,7 @@ package lipgloss import ( "io" "reflect" + "strings" "testing" "github.com/muesli/termenv" @@ -374,6 +375,42 @@ func TestTabConversion(t *testing.T) { requireEqual(t, "[\t]", s.Render("[\t]")) } +func TestStringTransform(t *testing.T) { + for i, tc := range []struct { + input string + fn func(string) string + expected string + }{ + { + "raow", + strings.ToUpper, + "RAOW", + }, + { + "The quick brown 狐 jumped over the lazy 犬", + func(s string) string { + n := 0 + rune := make([]rune, len(s)) + for _, r := range s { + rune[n] = r + n++ + } + rune = rune[0:n] + for i := 0; i < n/2; i++ { + rune[i], rune[n-1-i] = rune[n-1-i], rune[i] + } + return string(rune) + }, + "犬 yzal eht revo depmuj 狐 nworb kciuq ehT", + }, + } { + res := NewStyle().Transform(tc.fn).Render(tc.input) + if res != tc.expected { + t.Errorf("Test #%d:\nExpected: %q\nGot: %q", i+1, tc.expected, res) + } + } +} + func BenchmarkStyleRender(b *testing.B) { s := NewStyle(). Bold(true). diff --git a/unset.go b/unset.go index 770734c8..5387bcf7 100644 --- a/unset.go +++ b/unset.go @@ -305,6 +305,12 @@ func (s Style) UnsetStrikethroughSpaces() Style { return s } +// UnsetTransform removes the value set by Transform. +func (s Style) UnsetTransform() Style { + delete(s.rules, transformKey) + return s +} + // UnsetString sets the underlying string value to the empty string. func (s Style) UnsetString() Style { s.value = "" From fb3000d55f35dc7e748eef3fee5b9cd9a289d178 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Dec 2023 08:55:19 -0300 Subject: [PATCH 091/126] chore(deps): bump actions/setup-go from 4 to 5 (#244) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- .github/workflows/coverage.yml | 2 +- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 81c9e743..2b48198d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: GO111MODULE: "on" steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f6985a68..067d42c7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,7 +12,7 @@ jobs: GO111MODULE: "on" steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 5ce0d37f..4f3fbc0c 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ^1 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 16f38b8a..10df8cc9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ^1 From 04def9df3e907a8ca95a0b037a2a04fc680e1d31 Mon Sep 17 00:00:00 2001 From: Michael Lorant Date: Wed, 31 Jan 2024 09:38:39 +1100 Subject: [PATCH 092/126] fix: the empty string can have padding on the right (#253) An empty string could not have padding applied on the right side. Issue: #252 Signed-off-by: Michael Lorant --- style.go | 2 +- style_test.go | 31 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/style.go b/style.go index 72be9ef3..ad96f79c 100644 --- a/style.go +++ b/style.go @@ -489,7 +489,7 @@ func padLeft(str string, n int, style *termenv.Style) string { // Apply right padding. func padRight(str string, n int, style *termenv.Style) string { - if n == 0 || str == "" { + if n == 0 { return str } diff --git a/style_test.go b/style_test.go index a5148db0..a8c799fb 100644 --- a/style_test.go +++ b/style_test.go @@ -329,33 +329,62 @@ func TestStyleValue(t *testing.T) { tt := []struct { name string + text string style Style expected string }{ { name: "empty", + text: "foo", style: NewStyle(), expected: "foo", }, { name: "set string", + text: "foo", style: NewStyle().SetString("bar"), expected: "bar foo", }, { name: "set string with bold", + text: "foo", style: NewStyle().SetString("bar").Bold(true), expected: "\x1b[1mbar foo\x1b[0m", }, { name: "new style with string", + text: "foo", style: NewStyle().SetString("bar", "foobar"), expected: "bar foobar foo", }, + { + name: "margin right", + text: "foo", + style: NewStyle().MarginRight(1), + expected: "foo ", + }, + { + name: "margin left", + text: "foo", + style: NewStyle().MarginLeft(1), + expected: " foo", + }, + { + name: "empty text margin right", + text: "", + style: NewStyle().MarginRight(1), + expected: " ", + }, + { + name: "empty text margin left", + text: "", + style: NewStyle().MarginLeft(1), + expected: " ", + }, } for i, tc := range tt { - res := tc.style.Render("foo") + res := tc.style.Render(tc.text) if res != tc.expected { t.Errorf("Test %d, expected:\n\n`%s`\n`%s`\n\nActual output:\n\n`%s`\n`%s`\n\n", i, tc.expected, formatEscapes(tc.expected), From 59874c2afabe9d8b65123ef2279ce4e61c113181 Mon Sep 17 00:00:00 2001 From: Michael Lorant Date: Wed, 31 Jan 2024 09:44:00 +1100 Subject: [PATCH 093/126] chore: apply gofumpt to all files (#255) Apply gofumpt to all files to correct minor formatting issues. Signed-off-by: Michael Lorant --- align.go | 2 +- borders.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/align.go b/align.go index 6c0fb2dc..7ee549fc 100644 --- a/align.go +++ b/align.go @@ -69,7 +69,7 @@ func alignTextVertical(str string, pos Position, height int, _ *termenv.Style) s case Top: return str + strings.Repeat("\n", height-strHeight) case Center: - var topPadding, bottomPadding = (height - strHeight) / 2, (height - strHeight) / 2 + topPadding, bottomPadding := (height-strHeight)/2, (height-strHeight)/2 //nolint:gomnd if strHeight+topPadding+bottomPadding > height { topPadding-- } else if strHeight+topPadding+bottomPadding < height { diff --git a/borders.go b/borders.go index f244b53a..d84bfec4 100644 --- a/borders.go +++ b/borders.go @@ -411,7 +411,7 @@ func (s Style) styleBorder(border string, fg, bg TerminalColor) string { return border } - var style = termenv.Style{} + style := termenv.Style{} if fg != noColor { style = style.Foreground(fg.color(s.r)) From 92946d34c2342f6e061d72cf6bec57526c9da3d2 Mon Sep 17 00:00:00 2001 From: Michael Lorant Date: Wed, 31 Jan 2024 14:18:35 +1100 Subject: [PATCH 094/126] chore: refactor padding functions (#254) The `padLeft` and `padRight` functions were nearly identical and could benefit from being refactored to share common code. A new function called `pad` was added that applies padding based on the sign of the amount of padding needed. A positive value applies padding to the right side while a negative value applies padding to the left side. Issue: #252 Signed-off-by: Michael Lorant --- style.go | 52 ++++++++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/style.go b/style.go index ad96f79c..58dcc0fa 100644 --- a/style.go +++ b/style.go @@ -464,36 +464,23 @@ func (s Style) applyMargins(str string, inline bool) string { // Apply left padding. func padLeft(str string, n int, style *termenv.Style) string { - if n == 0 { - return str - } - - sp := strings.Repeat(" ", n) - if style != nil { - sp = style.Styled(sp) - } - - b := strings.Builder{} - l := strings.Split(str, "\n") - - for i := range l { - b.WriteString(sp) - b.WriteString(l[i]) - if i != len(l)-1 { - b.WriteRune('\n') - } - } - - return b.String() + return pad(str, -n, style) } // Apply right padding. func padRight(str string, n int, style *termenv.Style) string { + return pad(str, n, style) +} + +// pad adds padding to either the left or right side of a string. +// Positive values add to the right side while negative values +// add to the left side. +func pad(str string, n int, style *termenv.Style) string { if n == 0 { return str } - sp := strings.Repeat(" ", n) + sp := strings.Repeat(" ", abs(n)) if style != nil { sp = style.Styled(sp) } @@ -502,8 +489,17 @@ func padRight(str string, n int, style *termenv.Style) string { l := strings.Split(str, "\n") for i := range l { - b.WriteString(l[i]) - b.WriteString(sp) + switch { + // pad right + case n > 0: + b.WriteString(l[i]) + b.WriteString(sp) + // pad left + default: + b.WriteString(sp) + b.WriteString(l[i]) + } + if i != len(l)-1 { b.WriteRune('\n') } @@ -525,3 +521,11 @@ func min(a, b int) int { } return b } + +func abs(a int) int { + if a < 0 { + return -a + } + + return a +} From de4601232b791a1397d6c645904b001a311a5693 Mon Sep 17 00:00:00 2001 From: Michael Lorant Date: Sat, 3 Feb 2024 11:04:43 +1100 Subject: [PATCH 095/126] Fix truncate of table cells containing ANSI (#256) --- go.mod | 1 + go.sum | 2 ++ table/table.go | 6 +++--- table/table_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0123aabb..8e7cf2ed 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. go 1.17 require ( + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/mattn/go-runewidth v0.0.15 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 diff --git a/go.sum b/go.sum index cba58dc9..4a449c22 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= diff --git a/table/table.go b/table/table.go index 953721a0..68e41adb 100644 --- a/table/table.go +++ b/table/table.go @@ -4,7 +4,7 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - "github.com/mattn/go-runewidth" + "github.com/muesli/reflow/truncate" ) // StyleFunc is the style function that determines the style of a Cell. @@ -437,7 +437,7 @@ func (t *Table) constructHeaders() string { MaxHeight(1). Width(t.widths[i]). MaxWidth(t.widths[i]). - Render(runewidth.Truncate(header, t.widths[i], "…"))) + Render(truncate.StringWithTail(header, uint(t.widths[i]), "…"))) if i < len(t.headers)-1 && t.borderColumn { s.WriteString(t.borderStyle.Render(t.border.Left)) } @@ -488,7 +488,7 @@ func (t *Table) constructRow(index int) string { MaxHeight(height). Width(t.widths[c]). MaxWidth(t.widths[c]). - Render(runewidth.Truncate(cell, t.widths[c]*height, "…"))) + Render(truncate.StringWithTail(cell, uint(t.widths[c]*height), "…"))) if c < t.data.Columns()-1 && t.borderColumn { cells = append(cells, left) diff --git a/table/table_test.go b/table/table_test.go index 4e5ccad0..31f30104 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -3,7 +3,9 @@ package table import ( "strings" "testing" + "unicode" + "github.com/acarl005/stripansi" "github.com/charmbracelet/lipgloss" ) @@ -941,6 +943,52 @@ func TestFilterInverse(t *testing.T) { } } +func TestTableANSI(t *testing.T) { + const code = "\x1b[31mC\x1b[0m\x1b[32mo\x1b[0m\x1b[34md\x1b[0m\x1b[33me\x1b[0m" + + rows := [][]string{ + {"Apple", "Red", "\x1b[31m31\x1b[0m"}, + {"Lime", "Green", "\x1b[32m32\x1b[0m"}, + {"Banana", "Yellow", "\x1b[33m33\x1b[0m"}, + {"Blueberry", "Blue", "\x1b[34m34\x1b[0m"}, + } + + table := New(). + Width(29). + StyleFunc(TableStyle). + Border(lipgloss.NormalBorder()). + Headers("Fruit", "Color", code). + Rows(rows...) + + expected := strings.TrimSpace(` +┌───────────┬────────┬──────┐ +│ Fruit │ Color │ Code │ +├───────────┼────────┼──────┤ +│ Apple │ Red │ 31 │ +│ Lime │ Green │ 32 │ +│ Banana │ Yellow │ 33 │ +│ Blueberry │ Blue │ 34 │ +└───────────┴────────┴──────┘ +`) + + if stripString(table.String()) != expected { + t.Fatalf("expected:\n\n%s\n\ngot:\n\n%s", expected, stripString(table.String())) + } +} + func debug(s string) string { return strings.ReplaceAll(s, " ", ".") } + +func stripString(str string) string { + s := stripansi.Strip(str) + ss := strings.Split(s, "\n") + + var lines []string + for _, l := range ss { + trim := strings.TrimRightFunc(l, unicode.IsSpace) + lines = append(lines, trim) + } + + return strings.Join(lines, "\n") +} From 2745d8a3d83029e649b21bf6bf26298b3c51ce3a Mon Sep 17 00:00:00 2001 From: Michael Lorant Date: Wed, 14 Feb 2024 02:03:24 +1100 Subject: [PATCH 096/126] Improve maximum width of characters in a string (#257) The function `maxRuneWidth` was using a flawed approach by only evaluating strings based on each rune. The `uniseg` package provides a more accurate method based on grapheme clusters. This change switches over to the improved implementation which should achieve better results when determining the maximum width of all grapheme clusters in a string. Signed-off-by: Michael Lorant --- .github/workflows/build.yml | 2 +- borders.go | 13 +++++++++---- go.mod | 4 ++-- go.sum | 5 ++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2b48198d..f804b0cd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ jobs: test: strategy: matrix: - go-version: [~1.17, ^1] + go-version: [~1.18, ^1] os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} env: diff --git a/borders.go b/borders.go index d84bfec4..6ef90d56 100644 --- a/borders.go +++ b/borders.go @@ -3,9 +3,9 @@ package lipgloss import ( "strings" - "github.com/mattn/go-runewidth" "github.com/muesli/reflow/ansi" "github.com/muesli/termenv" + "github.com/rivo/uniseg" ) // Border contains a series of values which comprise the various parts of a @@ -423,13 +423,18 @@ func (s Style) styleBorder(border string, fg, bg TerminalColor) string { return style.Styled(border) } -func maxRuneWidth(str string) (width int) { - for _, r := range str { - w := runewidth.RuneWidth(r) +func maxRuneWidth(str string) int { + var width int + + state := -1 + for len(str) > 0 { + var w int + _, str, w, state = uniseg.FirstGraphemeClusterInString(str, state) if w > width { width = w } } + return width } diff --git a/go.mod b/go.mod index 8e7cf2ed..98f4be8a 100644 --- a/go.mod +++ b/go.mod @@ -2,19 +2,19 @@ module github.com/charmbracelet/lipgloss retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. -go 1.17 +go 1.18 require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/mattn/go-runewidth v0.0.15 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 + github.com/rivo/uniseg v0.4.6 ) require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect - github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/sys v0.12.0 // indirect ) diff --git a/go.sum b/go.sum index 4a449c22..bab2c1cf 100644 --- a/go.sum +++ b/go.sum @@ -7,7 +7,6 @@ github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= @@ -15,9 +14,9 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= +github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From bb7ffe226d40ba75dda2bf62dd5577af3c635b66 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Tue, 13 Feb 2024 10:26:46 -0500 Subject: [PATCH 097/126] fix(ci): update coverage workflow --- .github/workflows/coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 067d42c7..d197e453 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -24,5 +24,5 @@ jobs: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | go test -race -covermode atomic -coverprofile=profile.cov ./... - GO111MODULE=off go get github.com/mattn/goveralls - $(go env GOPATH)/bin/goveralls -coverprofile=profile.cov -service=github + go install github.com/mattn/goveralls@latest + goveralls -coverprofile=profile.cov -service=github From 13584f26deeb5f6188fa1e80e43aa2ca04f297cb Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Tue, 13 Feb 2024 10:09:58 -0500 Subject: [PATCH 098/126] chore: go mod tidy --- examples/go.mod | 2 +- examples/go.sum | 5 ++++- go.mod | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index c87579b3..f2c72a3b 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/rivo/uniseg v0.2.0 // indirect + github.com/rivo/uniseg v0.4.6 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/sys v0.12.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index 3d63a536..73db3cf8 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,6 +1,8 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -69,8 +71,9 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= +github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/go.mod b/go.mod index 98f4be8a..3174e9a9 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ go 1.18 require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d - github.com/mattn/go-runewidth v0.0.15 github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 github.com/rivo/uniseg v0.4.6 @@ -16,5 +15,6 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect golang.org/x/sys v0.12.0 // indirect ) From 207eb25c9f720e48e2d46b5c2f213d07b2006052 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Wed, 28 Feb 2024 21:02:24 -0500 Subject: [PATCH 099/126] Create CODEOWNERS --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..d834ea6f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @meowgorithm @muesli From 8464a7c90b02905ea471be3bc20951dce981b3ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 14:46:51 -0500 Subject: [PATCH 100/126] chore(deps): bump golangci/golangci-lint-action from 3 to 4 (#259) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 3 to 4. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v3...v4) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 4f3fbc0c..a1eafa04 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: # Optional: golangci-lint command line arguments. args: --config .golangci-soft.yml --issues-exit-code=0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 10df8cc9..881d0d30 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4 with: # Optional: golangci-lint command line arguments. #args: From 652c37dd07489c7c484711c8e21f4b221f57d056 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Mar 2024 14:48:57 -0500 Subject: [PATCH 101/126] feat(deps): bump github.com/rivo/uniseg from 0.4.6 to 0.4.7 (#262) Bumps [github.com/rivo/uniseg](https://github.com/rivo/uniseg) from 0.4.6 to 0.4.7. - [Release notes](https://github.com/rivo/uniseg/releases) - [Commits](https://github.com/rivo/uniseg/compare/v0.4.6...v0.4.7) --- updated-dependencies: - dependency-name: github.com/rivo/uniseg dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 3174e9a9..7dcd467b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 - github.com/rivo/uniseg v0.4.6 + github.com/rivo/uniseg v0.4.7 ) require ( diff --git a/go.sum b/go.sum index bab2c1cf..02fd2915 100644 --- a/go.sum +++ b/go.sum @@ -15,8 +15,8 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= From 439c06fae64d2f53261b692fcfcbe464d8e18d89 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Tue, 5 Mar 2024 10:35:45 -0500 Subject: [PATCH 102/126] docs(table): ANSI-aware cell example --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- examples/table/ansi/main.go | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 examples/table/ansi/main.go diff --git a/examples/go.mod b/examples/go.mod index f2c72a3b..b93aef15 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -23,7 +23,7 @@ require ( github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/rivo/uniseg v0.4.6 // indirect + github.com/rivo/uniseg v0.4.7 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/sys v0.12.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index 73db3cf8..d16930bd 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -72,8 +72,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= diff --git a/examples/table/ansi/main.go b/examples/table/ansi/main.go new file mode 100644 index 00000000..66d457d5 --- /dev/null +++ b/examples/table/ansi/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" +) + +func main() { + s := lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Render + + t := table.New() + t.Row("Bubble Tea", s("Milky")) + t.Row("Milk Tea", s("Also milky")) + t.Row("Actual milk", s("Milky as well")) + fmt.Println(t.Render()) +} From 552cd7298f5f53b8c93b43835deec0b1b65b6195 Mon Sep 17 00:00:00 2001 From: Ben Iofel Date: Thu, 14 Mar 2024 11:00:06 -0400 Subject: [PATCH 103/126] Fix JoinHorizontal docs (#247) --- join.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/join.go b/join.go index f265976e..fee4a919 100644 --- a/join.go +++ b/join.go @@ -12,7 +12,7 @@ import ( // the position, with 0 being all the way at the top and 1 being all the way // at the bottom. // -// If you just want to align to the left, right or center you may as well just +// If you just want to align to the top, center or bottom you may as well just // use the helper constants Top, Center, and Bottom. // // Example: From 96795629c12a5c224d491ed9a71219f7cbda248a Mon Sep 17 00:00:00 2001 From: Unseen Daan Date: Thu, 14 Mar 2024 16:12:23 +0100 Subject: [PATCH 104/126] fix: always render horizontal border edge when enabled (#211) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a style has a border set and an empty string is rendered, it should still render the border. When the left border is set, width is incremented by 1, so in most cases the width will not be 0. When we render an empty string, with a double border and no left border we expect the following: ╗ ║ ╝ But if we don't render a horizontal edge when the string width is less than 1 we see this: ║ The string width can't be lower than 0, so we can safely remove the (width < 1) check to create the expected behavior. Co-authored-by: Daan Schoone --- borders.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/borders.go b/borders.go index 6ef90d56..7a9a0ffb 100644 --- a/borders.go +++ b/borders.go @@ -376,10 +376,6 @@ func (s Style) applyBorder(str string) string { // Render the horizontal (top or bottom) portion of a border. func renderHorizontalEdge(left, middle, right string, width int) string { - if width < 1 { - return "" - } - if middle == "" { middle = " " } From d760238dd2e9cefae6e49ce1f10dad5c8f2a9241 Mon Sep 17 00:00:00 2001 From: Maas Lalani Date: Fri, 15 Mar 2024 10:56:32 -0400 Subject: [PATCH 105/126] fix(style): possible nil panic (#245) --- style.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/style.go b/style.go index 58dcc0fa..6efbc3b5 100644 --- a/style.go +++ b/style.go @@ -402,7 +402,10 @@ func (s Style) Render(strs ...string) string { // Truncate according to MaxHeight if maxHeight > 0 { lines := strings.Split(str, "\n") - str = strings.Join(lines[:min(maxHeight, len(lines))], "\n") + height := min(maxHeight, len(lines)) + if len(lines) > 0 { + str = strings.Join(lines[:height], "\n") + } } if transform != nil { From 734aea5ee00f495fdcfa9d93e5bcbb999b9782aa Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 18 Mar 2024 22:09:05 -0400 Subject: [PATCH 106/126] chore(README): various header improvements * Update header image colors * Link header to high-res mascot * Add phorm badge --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 21bc40c2..22defac7 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,12 @@ Lip Gloss =========

- Lip Gloss Title Treatment
+ Lip Gloss title treatment
Latest Release GoDoc Build Status + phorm.ai +

Style definitions for nice terminal layouts. Built with TUIs in mind. From b71dd126641028b1d5de397303b1fb3ddebe42b3 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Fri, 22 Mar 2024 16:12:37 -0400 Subject: [PATCH 107/126] fix: transform shouldn't operate on ANSI sequences --- style.go | 8 ++++---- style_test.go | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/style.go b/style.go index 6efbc3b5..ad6b52a3 100644 --- a/style.go +++ b/style.go @@ -231,6 +231,10 @@ func (s Style) Render(strs ...string) string { transform = s.getAsTransform(transformKey) ) + if transform != nil { + str = transform(str) + } + if len(s.rules) == 0 { return s.maybeConvertTabs(str) } @@ -408,10 +412,6 @@ func (s Style) Render(strs ...string) string { } } - if transform != nil { - return transform(str) - } - return str } diff --git a/style_test.go b/style_test.go index a8c799fb..84620e02 100644 --- a/style_test.go +++ b/style_test.go @@ -410,11 +410,19 @@ func TestStringTransform(t *testing.T) { fn func(string) string expected string }{ + // No-op. + { + "hello", + func(s string) string { return s }, + "hello", + }, + // Uppercase. { "raow", strings.ToUpper, "RAOW", }, + // English and Chinese. { "The quick brown 狐 jumped over the lazy 犬", func(s string) string { @@ -433,9 +441,10 @@ func TestStringTransform(t *testing.T) { "犬 yzal eht revo depmuj 狐 nworb kciuq ehT", }, } { - res := NewStyle().Transform(tc.fn).Render(tc.input) - if res != tc.expected { - t.Errorf("Test #%d:\nExpected: %q\nGot: %q", i+1, tc.expected, res) + res := NewStyle().Bold(true).Transform(tc.fn).Render(tc.input) + expected := "\x1b[1m" + tc.expected + "\x1b[0m" + if res != expected { + t.Errorf("Test #%d:\nExpected: %q\nGot: %q", i+1, expected, res) } } } From f16ea2bdcb882155b70b05ca92fed6024e712eac Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 14 Mar 2024 22:19:53 -0400 Subject: [PATCH 108/126] feat(table): replace stripansi with ansi.Strip Use term/ansi to strip out ANSI sequences --- go.mod | 4 ++-- go.sum | 12 ++++++++---- table/table_test.go | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 7dcd467b..3b6dbedb 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. go 1.18 require ( - github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d + github.com/charmbracelet/x/exp/term v0.0.0-20240315000626-435a7f02d93f github.com/muesli/reflow v0.3.0 github.com/muesli/termenv v0.15.2 github.com/rivo/uniseg v0.4.7 @@ -16,5 +16,5 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index 02fd2915..a79fd2be 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,8 @@ -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/charmbracelet/x/exp/term v0.0.0-20240315000626-435a7f02d93f h1:e1L6gJufLxUL0V8AnquQT4VoS4j8tGc3fu56A5fGs1Q= +github.com/charmbracelet/x/exp/term v0.0.0-20240315000626-435a7f02d93f/go.mod h1:madZtB2OVDOG+ZnLruGITVZceYy047W+BLQ1MNQzbWg= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= @@ -13,10 +14,13 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/table/table_test.go b/table/table_test.go index 31f30104..47efdfbc 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -5,8 +5,8 @@ import ( "testing" "unicode" - "github.com/acarl005/stripansi" "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/x/exp/term/ansi" ) var TableStyle = func(row, col int) lipgloss.Style { @@ -981,7 +981,7 @@ func debug(s string) string { } func stripString(str string) string { - s := stripansi.Strip(str) + s := ansi.Strip(str) ss := strings.Split(s, "\n") var lines []string From 387f2d73e8b61dd1970d79a334497b875a3319e8 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 28 Mar 2024 16:41:30 -0400 Subject: [PATCH 109/126] refactor: replace props map with struct fields Use an int to store property existence and style struct fields to store the actual values for each non-bool property. Fixes: https://github.com/charmbracelet/lipgloss/pull/139 Fixes: https://github.com/charmbracelet/lipgloss/pull/141 --- get.go | 104 +++++++++++++++++++++---------- set.go | 169 +++++++++++++++++++++++++++++++++++++++++++------- style.go | 102 +++++++++++++++++++++++------- style_test.go | 48 +++++++------- unset.go | 127 +++++++++++++++++++------------------ 5 files changed, 390 insertions(+), 160 deletions(-) diff --git a/get.go b/get.go index 9be3d64d..a3b096d3 100644 --- a/get.go +++ b/get.go @@ -416,74 +416,114 @@ func (s Style) GetTransform() func(string) string { // Returns whether or not the given property is set. func (s Style) isSet(k propKey) bool { - _, exists := s.rules[k] - return exists + return s.props.has(k) } func (s Style) getAsBool(k propKey, defaultVal bool) bool { - v, ok := s.rules[k] - if !ok { + if !s.isSet(k) { return defaultVal } - if b, ok := v.(bool); ok { - return b - } - return defaultVal + return s.attrs&int(k) != 0 } func (s Style) getAsColor(k propKey) TerminalColor { - v, ok := s.rules[k] - if !ok { + if !s.isSet(k) { return noColor } - if c, ok := v.(TerminalColor); ok { + + var c TerminalColor + switch k { + case foregroundKey: + c = s.fgColor + case backgroundKey: + c = s.bgColor + case marginBackgroundKey: + c = s.marginBgColor + case borderTopForegroundKey: + c = s.borderTopFgColor + case borderRightForegroundKey: + c = s.borderRightFgColor + case borderBottomForegroundKey: + c = s.borderBottomFgColor + case borderLeftForegroundKey: + c = s.borderLeftFgColor + case borderTopBackgroundKey: + c = s.borderTopBgColor + case borderRightBackgroundKey: + c = s.borderRightBgColor + case borderBottomBackgroundKey: + c = s.borderBottomBgColor + case borderLeftBackgroundKey: + c = s.borderLeftBgColor + } + + if c != nil { return c } + return noColor } func (s Style) getAsInt(k propKey) int { - v, ok := s.rules[k] - if !ok { + if !s.isSet(k) { return 0 } - if i, ok := v.(int); ok { - return i + switch k { + case widthKey: + return s.width + case heightKey: + return s.height + case paddingTopKey: + return s.paddingTop + case paddingRightKey: + return s.paddingRight + case paddingBottomKey: + return s.paddingBottom + case paddingLeftKey: + return s.paddingLeft + case marginTopKey: + return s.marginTop + case marginRightKey: + return s.marginRight + case marginBottomKey: + return s.marginBottom + case marginLeftKey: + return s.marginLeft + case maxWidthKey: + return s.maxWidth + case maxHeightKey: + return s.maxHeight + case tabWidthKey: + return s.tabWidth } return 0 } func (s Style) getAsPosition(k propKey) Position { - v, ok := s.rules[k] - if !ok { + if !s.isSet(k) { return Position(0) } - if p, ok := v.(Position); ok { - return p + switch k { + case alignHorizontalKey: + return s.alignHorizontal + case alignVerticalKey: + return s.alignVertical } return Position(0) } func (s Style) getBorderStyle() Border { - v, ok := s.rules[borderStyleKey] - if !ok { + if !s.isSet(borderStyleKey) { return noBorder } - if b, ok := v.(Border); ok { - return b - } - return noBorder + return s.borderStyle } -func (s Style) getAsTransform(k propKey) func(string) string { - v, ok := s.rules[k] - if !ok { +func (s Style) getAsTransform(propKey) func(string) string { + if !s.isSet(transformKey) { return nil } - if fn, ok := v.(func(string) string); ok { - return fn - } - return nil + return s.transform } // Split a string into lines, additionally returning the size of the widest diff --git a/set.go b/set.go index 5846b598..d4f4bff9 100644 --- a/set.go +++ b/set.go @@ -1,34 +1,161 @@ package lipgloss -// This could (should) probably just be moved into NewStyle(). We've broken it -// out, so we can call it in a lazy way. -func (s *Style) init() { - if s.rules == nil { - s.rules = make(rules) - } -} - // Set a value on the underlying rules map. func (s *Style) set(key propKey, value interface{}) { - s.init() - - switch v := value.(type) { - case int: + // We don't allow negative integers on any of our other values, so just keep + // them at zero or above. We could use uints instead, but the + // conversions are a little tedious, so we're sticking with ints for + // sake of usability. + switch key { + case foregroundKey: + s.fgColor = colorOrNil(value) + case backgroundKey: + s.bgColor = colorOrNil(value) + case widthKey: + s.width = max(0, value.(int)) + case heightKey: + s.height = max(0, value.(int)) + case alignHorizontalKey: + s.alignHorizontal = value.(Position) + case alignVerticalKey: + s.alignVertical = value.(Position) + case paddingTopKey: + s.paddingTop = max(0, value.(int)) + case paddingRightKey: + s.paddingRight = max(0, value.(int)) + case paddingBottomKey: + s.paddingBottom = max(0, value.(int)) + case paddingLeftKey: + s.paddingLeft = max(0, value.(int)) + case marginTopKey: + s.marginTop = max(0, value.(int)) + case marginRightKey: + s.marginRight = max(0, value.(int)) + case marginBottomKey: + s.marginBottom = max(0, value.(int)) + case marginLeftKey: + s.marginLeft = max(0, value.(int)) + case marginBackgroundKey: + s.marginBgColor = colorOrNil(value) + case borderStyleKey: + s.borderStyle = value.(Border) + case borderTopForegroundKey: + s.borderTopFgColor = colorOrNil(value) + case borderRightForegroundKey: + s.borderRightFgColor = colorOrNil(value) + case borderBottomForegroundKey: + s.borderBottomFgColor = colorOrNil(value) + case borderLeftForegroundKey: + s.borderLeftFgColor = colorOrNil(value) + case borderTopBackgroundKey: + s.borderTopBgColor = colorOrNil(value) + case borderRightBackgroundKey: + s.borderRightBgColor = colorOrNil(value) + case borderBottomBackgroundKey: + s.borderBottomBgColor = colorOrNil(value) + case borderLeftBackgroundKey: + s.borderLeftBgColor = colorOrNil(value) + case maxWidthKey: + s.maxWidth = max(0, value.(int)) + case maxHeightKey: + s.maxHeight = max(0, value.(int)) + case tabWidthKey: // TabWidth is the only property that may have a negative value (and // that negative value can be no less than -1). - if key == tabWidthKey { - s.rules[key] = v - break + s.tabWidth = value.(int) + case transformKey: + s.transform = value.(func(string) string) + default: + if v, ok := value.(bool); ok { + if v { + s.attrs |= int(key) + } else { + s.attrs &^= int(key) + } + } else if attrs, ok := value.(int); ok { + // bool attrs + if attrs&int(key) != 0 { + s.attrs |= int(key) + } else { + s.attrs &^= int(key) + } } + } - // We don't allow negative integers on any of our other values, so just keep - // them at zero or above. We could use uints instead, but the - // conversions are a little tedious, so we're sticking with ints for - // sake of usability. - s.rules[key] = max(0, v) + // Set the prop on + s.props = s.props.set(key) +} + +// setFrom sets the property from another style. +func (s *Style) setFrom(key propKey, i Style) { + switch key { + case foregroundKey: + s.set(foregroundKey, i.fgColor) + case backgroundKey: + s.set(backgroundKey, i.bgColor) + case widthKey: + s.set(widthKey, i.width) + case heightKey: + s.set(heightKey, i.height) + case alignHorizontalKey: + s.set(alignHorizontalKey, i.alignHorizontal) + case alignVerticalKey: + s.set(alignVerticalKey, i.alignVertical) + case paddingTopKey: + s.set(paddingTopKey, i.paddingTop) + case paddingRightKey: + s.set(paddingRightKey, i.paddingRight) + case paddingBottomKey: + s.set(paddingBottomKey, i.paddingBottom) + case paddingLeftKey: + s.set(paddingLeftKey, i.paddingLeft) + case marginTopKey: + s.set(marginTopKey, i.marginTop) + case marginRightKey: + s.set(marginRightKey, i.marginRight) + case marginBottomKey: + s.set(marginBottomKey, i.marginBottom) + case marginLeftKey: + s.set(marginLeftKey, i.marginLeft) + case marginBackgroundKey: + s.set(marginBackgroundKey, i.marginBgColor) + case borderStyleKey: + s.set(borderStyleKey, i.borderStyle) + case borderTopForegroundKey: + s.set(borderTopForegroundKey, i.borderTopFgColor) + case borderRightForegroundKey: + s.set(borderRightForegroundKey, i.borderRightFgColor) + case borderBottomForegroundKey: + s.set(borderBottomForegroundKey, i.borderBottomFgColor) + case borderLeftForegroundKey: + s.set(borderLeftForegroundKey, i.borderLeftFgColor) + case borderTopBackgroundKey: + s.set(borderTopBackgroundKey, i.borderTopBgColor) + case borderRightBackgroundKey: + s.set(borderRightBackgroundKey, i.borderRightBgColor) + case borderBottomBackgroundKey: + s.set(borderBottomBackgroundKey, i.borderBottomBgColor) + case borderLeftBackgroundKey: + s.set(borderLeftBackgroundKey, i.borderLeftBgColor) + case maxWidthKey: + s.set(maxWidthKey, i.maxWidth) + case maxHeightKey: + s.set(maxHeightKey, i.maxHeight) + case tabWidthKey: + s.set(tabWidthKey, i.tabWidth) + case transformKey: + s.set(transformKey, i.transform) default: - s.rules[key] = v + // Set attributes for set bool properties + s.set(key, i.attrs) + } +} + +func colorOrNil(c interface{}) TerminalColor { + if c, ok := c.(TerminalColor); ok { + return c } + return nil } // Bold sets a bold formatting rule. diff --git a/style.go b/style.go index ad6b52a3..5d2adfb5 100644 --- a/style.go +++ b/style.go @@ -17,13 +17,20 @@ type propKey int // Available properties. const ( - boldKey propKey = iota + // bool props come first + boldKey propKey = 1 << iota italicKey underlineKey strikethroughKey reverseKey blinkKey faintKey + underlineSpacesKey + strikethroughSpacesKey + colorWhitespaceKey + + // non-bool props + foregroundKey backgroundKey widthKey @@ -37,8 +44,6 @@ const ( paddingBottomKey paddingLeftKey - colorWhitespaceKey - // Margins. marginTopKey marginRightKey @@ -71,14 +76,27 @@ const ( maxWidthKey maxHeightKey tabWidthKey - underlineSpacesKey - strikethroughSpacesKey transformKey ) -// A set of properties. -type rules map[propKey]interface{} +// props is a set of properties. +type props int + +// set sets a property. +func (p props) set(k propKey) props { + return p | props(k) +} + +// unset unsets a property. +func (p props) unset(k propKey) props { + return p &^ props(k) +} + +// has checks if a property is set. +func (p props) has(k propKey) bool { + return p&props(k) != 0 +} // NewStyle returns a new, empty Style. While it's syntactic sugar for the // Style{} primitive, it's recommended to use this function for creating styles @@ -100,8 +118,48 @@ func (r *Renderer) NewStyle() Style { // Style contains a set of rules that comprise a style as a whole. type Style struct { r *Renderer - rules map[propKey]interface{} + props props value string + + // we store bool props values here + attrs int + + // props that have values + fgColor TerminalColor + bgColor TerminalColor + + width int + height int + + alignHorizontal Position + alignVertical Position + + paddingTop int + paddingRight int + paddingBottom int + paddingLeft int + + marginTop int + marginRight int + marginBottom int + marginLeft int + marginBgColor TerminalColor + + borderStyle Border + borderTopFgColor TerminalColor + borderRightFgColor TerminalColor + borderBottomFgColor TerminalColor + borderLeftFgColor TerminalColor + borderTopBgColor TerminalColor + borderRightBgColor TerminalColor + borderBottomBgColor TerminalColor + borderLeftBgColor TerminalColor + + maxWidth int + maxHeight int + tabWidth int + + transform func(string) string } // joinString joins a list of strings into a single string separated with a @@ -133,15 +191,10 @@ func (s Style) String() string { } // Copy returns a copy of this style, including any underlying string values. +// +// Deprecated: Copy is deprecated and will be removed in a future release. func (s Style) Copy() Style { - o := NewStyle() - o.init() - for k, v := range s.rules { - o.rules[k] = v - } - o.r = s.r - o.value = s.value - return o + return s } // Inherit overlays the style in the argument onto this style by copying each explicitly @@ -150,9 +203,11 @@ func (s Style) Copy() Style { // // Margins, padding, and underlying string values are not inherited. func (s Style) Inherit(i Style) Style { - s.init() + for k := boldKey; k <= transformKey; k <<= 1 { + if !i.isSet(k) { + continue + } - for k, v := range i.rules { switch k { //nolint:exhaustive case marginTopKey, marginRightKey, marginBottomKey, marginLeftKey: // Margins are not inherited @@ -163,14 +218,15 @@ func (s Style) Inherit(i Style) Style { case backgroundKey: // The margins also inherit the background color if !s.isSet(marginBackgroundKey) && !i.isSet(marginBackgroundKey) { - s.rules[marginBackgroundKey] = v + s.set(marginBackgroundKey, i.bgColor) } } - if _, exists := s.rules[k]; exists { + if s.isSet(k) { continue } - s.rules[k] = v + + s.setFrom(k, i) } return s } @@ -235,7 +291,7 @@ func (s Style) Render(strs ...string) string { str = transform(str) } - if len(s.rules) == 0 { + if s.props == 0 { return s.maybeConvertTabs(str) } @@ -511,7 +567,7 @@ func pad(str string, n int, style *termenv.Style) string { return b.String() } -func max(a, b int) int { +func max(a, b int) int { // nolint:unparam if a > b { return a } diff --git a/style_test.go b/style_test.go index 84620e02..f22b3590 100644 --- a/style_test.go +++ b/style_test.go @@ -213,114 +213,114 @@ func TestStyleUnset(t *testing.T) { s := NewStyle().Bold(true) requireTrue(t, s.GetBold()) - s.UnsetBold() + s = s.UnsetBold() requireFalse(t, s.GetBold()) s = NewStyle().Italic(true) requireTrue(t, s.GetItalic()) - s.UnsetItalic() + s = s.UnsetItalic() requireFalse(t, s.GetItalic()) s = NewStyle().Underline(true) requireTrue(t, s.GetUnderline()) - s.UnsetUnderline() + s = s.UnsetUnderline() requireFalse(t, s.GetUnderline()) s = NewStyle().Strikethrough(true) requireTrue(t, s.GetStrikethrough()) - s.UnsetStrikethrough() + s = s.UnsetStrikethrough() requireFalse(t, s.GetStrikethrough()) s = NewStyle().Reverse(true) requireTrue(t, s.GetReverse()) - s.UnsetReverse() + s = s.UnsetReverse() requireFalse(t, s.GetReverse()) s = NewStyle().Blink(true) requireTrue(t, s.GetBlink()) - s.UnsetBlink() + s = s.UnsetBlink() requireFalse(t, s.GetBlink()) s = NewStyle().Faint(true) requireTrue(t, s.GetFaint()) - s.UnsetFaint() + s = s.UnsetFaint() requireFalse(t, s.GetFaint()) s = NewStyle().Inline(true) requireTrue(t, s.GetInline()) - s.UnsetInline() + s = s.UnsetInline() requireFalse(t, s.GetInline()) // colors col := Color("#ffffff") s = NewStyle().Foreground(col) requireEqual(t, col, s.GetForeground()) - s.UnsetForeground() + s = s.UnsetForeground() requireNotEqual(t, col, s.GetForeground()) s = NewStyle().Background(col) requireEqual(t, col, s.GetBackground()) - s.UnsetBackground() + s = s.UnsetBackground() requireNotEqual(t, col, s.GetBackground()) // margins s = NewStyle().Margin(1, 2, 3, 4) requireEqual(t, 1, s.GetMarginTop()) - s.UnsetMarginTop() + s = s.UnsetMarginTop() requireEqual(t, 0, s.GetMarginTop()) requireEqual(t, 2, s.GetMarginRight()) - s.UnsetMarginRight() + s = s.UnsetMarginRight() requireEqual(t, 0, s.GetMarginRight()) requireEqual(t, 3, s.GetMarginBottom()) - s.UnsetMarginBottom() + s = s.UnsetMarginBottom() requireEqual(t, 0, s.GetMarginBottom()) requireEqual(t, 4, s.GetMarginLeft()) - s.UnsetMarginLeft() + s = s.UnsetMarginLeft() requireEqual(t, 0, s.GetMarginLeft()) // padding s = NewStyle().Padding(1, 2, 3, 4) requireEqual(t, 1, s.GetPaddingTop()) - s.UnsetPaddingTop() + s = s.UnsetPaddingTop() requireEqual(t, 0, s.GetPaddingTop()) requireEqual(t, 2, s.GetPaddingRight()) - s.UnsetPaddingRight() + s = s.UnsetPaddingRight() requireEqual(t, 0, s.GetPaddingRight()) requireEqual(t, 3, s.GetPaddingBottom()) - s.UnsetPaddingBottom() + s = s.UnsetPaddingBottom() requireEqual(t, 0, s.GetPaddingBottom()) requireEqual(t, 4, s.GetPaddingLeft()) - s.UnsetPaddingLeft() + s = s.UnsetPaddingLeft() requireEqual(t, 0, s.GetPaddingLeft()) // border s = NewStyle().Border(normalBorder, true, true, true, true) requireTrue(t, s.GetBorderTop()) - s.UnsetBorderTop() + s = s.UnsetBorderTop() requireFalse(t, s.GetBorderTop()) requireTrue(t, s.GetBorderRight()) - s.UnsetBorderRight() + s = s.UnsetBorderRight() requireFalse(t, s.GetBorderRight()) requireTrue(t, s.GetBorderBottom()) - s.UnsetBorderBottom() + s = s.UnsetBorderBottom() requireFalse(t, s.GetBorderBottom()) requireTrue(t, s.GetBorderLeft()) - s.UnsetBorderLeft() + s = s.UnsetBorderLeft() requireFalse(t, s.GetBorderLeft()) // tab width s = NewStyle().TabWidth(2) requireEqual(t, s.GetTabWidth(), 2) - s.UnsetTabWidth() + s = s.UnsetTabWidth() requireNotEqual(t, s.GetTabWidth(), 4) } @@ -460,10 +460,12 @@ func BenchmarkStyleRender(b *testing.B) { } func requireTrue(tb testing.TB, b bool) { + tb.Helper() requireEqual(tb, true, b) } func requireFalse(tb testing.TB, b bool) { + tb.Helper() requireEqual(tb, false, b) } diff --git a/unset.go b/unset.go index 5387bcf7..19d93370 100644 --- a/unset.go +++ b/unset.go @@ -1,159 +1,164 @@ package lipgloss +// unset unsets a property from a style. +func (s *Style) unset(key propKey) { + s.props = s.props.unset(key) +} + // UnsetBold removes the bold style rule, if set. func (s Style) UnsetBold() Style { - delete(s.rules, boldKey) + s.unset(boldKey) return s } // UnsetItalic removes the italic style rule, if set. func (s Style) UnsetItalic() Style { - delete(s.rules, italicKey) + s.unset(italicKey) return s } // UnsetUnderline removes the underline style rule, if set. func (s Style) UnsetUnderline() Style { - delete(s.rules, underlineKey) + s.unset(underlineKey) return s } // UnsetStrikethrough removes the strikethrough style rule, if set. func (s Style) UnsetStrikethrough() Style { - delete(s.rules, strikethroughKey) + s.unset(strikethroughKey) return s } // UnsetReverse removes the reverse style rule, if set. func (s Style) UnsetReverse() Style { - delete(s.rules, reverseKey) + s.unset(reverseKey) return s } // UnsetBlink removes the blink style rule, if set. func (s Style) UnsetBlink() Style { - delete(s.rules, blinkKey) + s.unset(blinkKey) return s } // UnsetFaint removes the faint style rule, if set. func (s Style) UnsetFaint() Style { - delete(s.rules, faintKey) + s.unset(faintKey) return s } // UnsetForeground removes the foreground style rule, if set. func (s Style) UnsetForeground() Style { - delete(s.rules, foregroundKey) + s.unset(foregroundKey) return s } // UnsetBackground removes the background style rule, if set. func (s Style) UnsetBackground() Style { - delete(s.rules, backgroundKey) + s.unset(backgroundKey) return s } // UnsetWidth removes the width style rule, if set. func (s Style) UnsetWidth() Style { - delete(s.rules, widthKey) + s.unset(widthKey) return s } // UnsetHeight removes the height style rule, if set. func (s Style) UnsetHeight() Style { - delete(s.rules, heightKey) + s.unset(heightKey) return s } // UnsetAlign removes the horizontal and vertical text alignment style rule, if set. func (s Style) UnsetAlign() Style { - delete(s.rules, alignHorizontalKey) - delete(s.rules, alignVerticalKey) + s.unset(alignHorizontalKey) + s.unset(alignVerticalKey) return s } // UnsetAlignHorizontal removes the horizontal text alignment style rule, if set. func (s Style) UnsetAlignHorizontal() Style { - delete(s.rules, alignHorizontalKey) + s.unset(alignHorizontalKey) return s } // UnsetAlignVertical removes the vertical text alignment style rule, if set. func (s Style) UnsetAlignVertical() Style { - delete(s.rules, alignVerticalKey) + s.unset(alignVerticalKey) return s } // UnsetPadding removes all padding style rules. func (s Style) UnsetPadding() Style { - delete(s.rules, paddingLeftKey) - delete(s.rules, paddingRightKey) - delete(s.rules, paddingTopKey) - delete(s.rules, paddingBottomKey) + s.unset(paddingLeftKey) + s.unset(paddingRightKey) + s.unset(paddingTopKey) + s.unset(paddingBottomKey) return s } // UnsetPaddingLeft removes the left padding style rule, if set. func (s Style) UnsetPaddingLeft() Style { - delete(s.rules, paddingLeftKey) + s.unset(paddingLeftKey) return s } // UnsetPaddingRight removes the right padding style rule, if set. func (s Style) UnsetPaddingRight() Style { - delete(s.rules, paddingRightKey) + s.unset(paddingRightKey) return s } // UnsetPaddingTop removes the top padding style rule, if set. func (s Style) UnsetPaddingTop() Style { - delete(s.rules, paddingTopKey) + s.unset(paddingTopKey) return s } // UnsetPaddingBottom removes the bottom padding style rule, if set. func (s Style) UnsetPaddingBottom() Style { - delete(s.rules, paddingBottomKey) + s.unset(paddingBottomKey) return s } // UnsetColorWhitespace removes the rule for coloring padding, if set. func (s Style) UnsetColorWhitespace() Style { - delete(s.rules, colorWhitespaceKey) + s.unset(colorWhitespaceKey) return s } // UnsetMargins removes all margin style rules. func (s Style) UnsetMargins() Style { - delete(s.rules, marginLeftKey) - delete(s.rules, marginRightKey) - delete(s.rules, marginTopKey) - delete(s.rules, marginBottomKey) + s.unset(marginLeftKey) + s.unset(marginRightKey) + s.unset(marginTopKey) + s.unset(marginBottomKey) return s } // UnsetMarginLeft removes the left margin style rule, if set. func (s Style) UnsetMarginLeft() Style { - delete(s.rules, marginLeftKey) + s.unset(marginLeftKey) return s } // UnsetMarginRight removes the right margin style rule, if set. func (s Style) UnsetMarginRight() Style { - delete(s.rules, marginRightKey) + s.unset(marginRightKey) return s } // UnsetMarginTop removes the top margin style rule, if set. func (s Style) UnsetMarginTop() Style { - delete(s.rules, marginTopKey) + s.unset(marginTopKey) return s } // UnsetMarginBottom removes the bottom margin style rule, if set. func (s Style) UnsetMarginBottom() Style { - delete(s.rules, marginBottomKey) + s.unset(marginBottomKey) return s } @@ -161,153 +166,153 @@ func (s Style) UnsetMarginBottom() Style { // margin's background color can be set from the background color of another // style during inheritance. func (s Style) UnsetMarginBackground() Style { - delete(s.rules, marginBackgroundKey) + s.unset(marginBackgroundKey) return s } // UnsetBorderStyle removes the border style rule, if set. func (s Style) UnsetBorderStyle() Style { - delete(s.rules, borderStyleKey) + s.unset(borderStyleKey) return s } // UnsetBorderTop removes the border top style rule, if set. func (s Style) UnsetBorderTop() Style { - delete(s.rules, borderTopKey) + s.unset(borderTopKey) return s } // UnsetBorderRight removes the border right style rule, if set. func (s Style) UnsetBorderRight() Style { - delete(s.rules, borderRightKey) + s.unset(borderRightKey) return s } // UnsetBorderBottom removes the border bottom style rule, if set. func (s Style) UnsetBorderBottom() Style { - delete(s.rules, borderBottomKey) + s.unset(borderBottomKey) return s } // UnsetBorderLeft removes the border left style rule, if set. func (s Style) UnsetBorderLeft() Style { - delete(s.rules, borderLeftKey) + s.unset(borderLeftKey) return s } // UnsetBorderForeground removes all border foreground color styles, if set. func (s Style) UnsetBorderForeground() Style { - delete(s.rules, borderTopForegroundKey) - delete(s.rules, borderRightForegroundKey) - delete(s.rules, borderBottomForegroundKey) - delete(s.rules, borderLeftForegroundKey) + s.unset(borderTopForegroundKey) + s.unset(borderRightForegroundKey) + s.unset(borderBottomForegroundKey) + s.unset(borderLeftForegroundKey) return s } // UnsetBorderTopForeground removes the top border foreground color rule, // if set. func (s Style) UnsetBorderTopForeground() Style { - delete(s.rules, borderTopForegroundKey) + s.unset(borderTopForegroundKey) return s } // UnsetBorderRightForeground removes the right border foreground color rule, // if set. func (s Style) UnsetBorderRightForeground() Style { - delete(s.rules, borderRightForegroundKey) + s.unset(borderRightForegroundKey) return s } // UnsetBorderBottomForeground removes the bottom border foreground color // rule, if set. func (s Style) UnsetBorderBottomForeground() Style { - delete(s.rules, borderBottomForegroundKey) + s.unset(borderBottomForegroundKey) return s } // UnsetBorderLeftForeground removes the left border foreground color rule, // if set. func (s Style) UnsetBorderLeftForeground() Style { - delete(s.rules, borderLeftForegroundKey) + s.unset(borderLeftForegroundKey) return s } // UnsetBorderBackground removes all border background color styles, if // set. func (s Style) UnsetBorderBackground() Style { - delete(s.rules, borderTopBackgroundKey) - delete(s.rules, borderRightBackgroundKey) - delete(s.rules, borderBottomBackgroundKey) - delete(s.rules, borderLeftBackgroundKey) + s.unset(borderTopBackgroundKey) + s.unset(borderRightBackgroundKey) + s.unset(borderBottomBackgroundKey) + s.unset(borderLeftBackgroundKey) return s } // UnsetBorderTopBackgroundColor removes the top border background color rule, // if set. func (s Style) UnsetBorderTopBackgroundColor() Style { - delete(s.rules, borderTopBackgroundKey) + s.unset(borderTopBackgroundKey) return s } // UnsetBorderRightBackground removes the right border background color // rule, if set. func (s Style) UnsetBorderRightBackground() Style { - delete(s.rules, borderRightBackgroundKey) + s.unset(borderRightBackgroundKey) return s } // UnsetBorderBottomBackground removes the bottom border background color // rule, if set. func (s Style) UnsetBorderBottomBackground() Style { - delete(s.rules, borderBottomBackgroundKey) + s.unset(borderBottomBackgroundKey) return s } // UnsetBorderLeftBackground removes the left border color rule, if set. func (s Style) UnsetBorderLeftBackground() Style { - delete(s.rules, borderLeftBackgroundKey) + s.unset(borderLeftBackgroundKey) return s } // UnsetInline removes the inline style rule, if set. func (s Style) UnsetInline() Style { - delete(s.rules, inlineKey) + s.unset(inlineKey) return s } // UnsetMaxWidth removes the max width style rule, if set. func (s Style) UnsetMaxWidth() Style { - delete(s.rules, maxWidthKey) + s.unset(maxWidthKey) return s } // UnsetMaxHeight removes the max height style rule, if set. func (s Style) UnsetMaxHeight() Style { - delete(s.rules, maxHeightKey) + s.unset(maxHeightKey) return s } // UnsetTabWidth removes the tab width style rule, if set. func (s Style) UnsetTabWidth() Style { - delete(s.rules, tabWidthKey) + s.unset(tabWidthKey) return s } // UnsetUnderlineSpaces removes the value set by UnderlineSpaces. func (s Style) UnsetUnderlineSpaces() Style { - delete(s.rules, underlineSpacesKey) + s.unset(underlineSpacesKey) return s } // UnsetStrikethroughSpaces removes the value set by StrikethroughSpaces. func (s Style) UnsetStrikethroughSpaces() Style { - delete(s.rules, strikethroughSpacesKey) + s.unset(strikethroughSpacesKey) return s } // UnsetTransform removes the value set by Transform. func (s Style) UnsetTransform() Style { - delete(s.rules, transformKey) + s.unset(transformKey) return s } From f6f93348b0ab3501567f8bb3917130c1203e59d6 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 29 Mar 2024 15:05:18 -0400 Subject: [PATCH 110/126] feat: switch to term/ansi for text manipulation (#268) * feat: switch to term/ansi for text manipulation Use ANSI aware, wide characters support, uniseg backed term/ansi package to calculate string widths, truncate, and wrap strings. Related: https://github.com/muesli/reflow/pull/71 Fixes: https://github.com/charmbracelet/lipgloss/issues/258 Fixes: https://github.com/charmbracelet/lipgloss/issues/220 * Update get.go --- align.go | 4 ++-- borders.go | 8 ++++---- examples/go.mod | 4 ++-- examples/go.sum | 17 ++++++++++++----- get.go | 4 ++-- go.mod | 3 +-- go.sum | 12 ++---------- join.go | 6 +++--- position.go | 4 ++-- size.go | 4 ++-- style.go | 10 ++++------ table/table.go | 6 +++--- whitespace.go | 6 +++--- 13 files changed, 42 insertions(+), 46 deletions(-) diff --git a/align.go b/align.go index 7ee549fc..4aec3717 100644 --- a/align.go +++ b/align.go @@ -3,7 +3,7 @@ package lipgloss import ( "strings" - "github.com/muesli/reflow/ansi" + "github.com/charmbracelet/x/exp/term/ansi" "github.com/muesli/termenv" ) @@ -15,7 +15,7 @@ func alignTextHorizontal(str string, pos Position, width int, style *termenv.Sty var b strings.Builder for i, l := range lines { - lineWidth := ansi.PrintableRuneWidth(l) + lineWidth := ansi.StringWidth(l) shortAmount := widestLine - lineWidth // difference from the widest line shortAmount += max(0, width-(shortAmount+lineWidth)) // difference from the total width, if set diff --git a/borders.go b/borders.go index 7a9a0ffb..38f875f1 100644 --- a/borders.go +++ b/borders.go @@ -3,7 +3,7 @@ package lipgloss import ( "strings" - "github.com/muesli/reflow/ansi" + "github.com/charmbracelet/x/exp/term/ansi" "github.com/muesli/termenv" "github.com/rivo/uniseg" ) @@ -380,8 +380,8 @@ func renderHorizontalEdge(left, middle, right string, width int) string { middle = " " } - leftWidth := ansi.PrintableRuneWidth(left) - rightWidth := ansi.PrintableRuneWidth(right) + leftWidth := ansi.StringWidth(left) + rightWidth := ansi.StringWidth(right) runes := []rune(middle) j := 0 @@ -394,7 +394,7 @@ func renderHorizontalEdge(left, middle, right string, width int) string { if j >= len(runes) { j = 0 } - i += ansi.PrintableRuneWidth(string(runes[j])) + i += ansi.StringWidth(string(runes[j])) } out.WriteString(right) diff --git a/examples/go.mod b/examples/go.mod index b93aef15..1eb982f9 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -7,6 +7,7 @@ replace github.com/charmbracelet/lipgloss => ../ require ( github.com/charmbracelet/lipgloss v0.4.0 github.com/charmbracelet/wish v0.5.0 + github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd github.com/gliderlabs/ssh v0.3.4 github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 @@ -22,8 +23,7 @@ require ( github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.18.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index d16930bd..0e38eb5a 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -1,8 +1,6 @@ github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= -github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -18,11 +16,15 @@ github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBc github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg= github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk= +github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= +github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd h1:HqBjkSFXXfW4IgX3TMKipWoPEN08T3Pi4SA/3DLss/U= +github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= @@ -34,6 +36,7 @@ github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6 github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= @@ -61,7 +64,7 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= @@ -82,6 +85,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -92,6 +96,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -116,13 +121,15 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/get.go b/get.go index 9be3d64d..d6c83a97 100644 --- a/get.go +++ b/get.go @@ -3,7 +3,7 @@ package lipgloss import ( "strings" - "github.com/muesli/reflow/ansi" + "github.com/charmbracelet/x/exp/term/ansi" ) // GetBold returns the style's bold value. If no value is set false is returned. @@ -492,7 +492,7 @@ func getLines(s string) (lines []string, widest int) { lines = strings.Split(s, "\n") for _, l := range lines { - w := ansi.PrintableRuneWidth(l) + w := ansi.StringWidth(l) if widest < w { widest = w } diff --git a/go.mod b/go.mod index 3b6dbedb..5b4599ef 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,7 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. go 1.18 require ( - github.com/charmbracelet/x/exp/term v0.0.0-20240315000626-435a7f02d93f - github.com/muesli/reflow v0.3.0 + github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd github.com/muesli/termenv v0.15.2 github.com/rivo/uniseg v0.4.7 ) diff --git a/go.sum b/go.sum index a79fd2be..a313ebc7 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,18 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/x/exp/term v0.0.0-20240315000626-435a7f02d93f h1:e1L6gJufLxUL0V8AnquQT4VoS4j8tGc3fu56A5fGs1Q= -github.com/charmbracelet/x/exp/term v0.0.0-20240315000626-435a7f02d93f/go.mod h1:madZtB2OVDOG+ZnLruGITVZceYy047W+BLQ1MNQzbWg= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd h1:HqBjkSFXXfW4IgX3TMKipWoPEN08T3Pi4SA/3DLss/U= +github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/join.go b/join.go index fee4a919..8e3115b0 100644 --- a/join.go +++ b/join.go @@ -4,7 +4,7 @@ import ( "math" "strings" - "github.com/muesli/reflow/ansi" + "github.com/charmbracelet/x/exp/term/ansi" ) // JoinHorizontal is a utility function for horizontally joining two @@ -85,7 +85,7 @@ func JoinHorizontal(pos Position, strs ...string) string { b.WriteString(block[i]) // Also make lines the same length - b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.PrintableRuneWidth(block[i]))) + b.WriteString(strings.Repeat(" ", maxWidths[j]-ansi.StringWidth(block[i]))) } if i < len(blocks[0])-1 { b.WriteRune('\n') @@ -137,7 +137,7 @@ func JoinVertical(pos Position, strs ...string) string { var b strings.Builder for i, block := range blocks { for j, line := range block { - w := maxWidth - ansi.PrintableRuneWidth(line) + w := maxWidth - ansi.StringWidth(line) switch pos { //nolint:exhaustive case Left: diff --git a/position.go b/position.go index 7d229e03..f57b7bb9 100644 --- a/position.go +++ b/position.go @@ -4,7 +4,7 @@ import ( "math" "strings" - "github.com/muesli/reflow/ansi" + "github.com/charmbracelet/x/exp/term/ansi" ) // Position represents a position along a horizontal or vertical axis. It's in @@ -66,7 +66,7 @@ func (r *Renderer) PlaceHorizontal(width int, pos Position, str string, opts ... var b strings.Builder for i, l := range lines { // Is this line shorter than the longest line? - short := max(0, contentWidth-ansi.PrintableRuneWidth(l)) + short := max(0, contentWidth-ansi.StringWidth(l)) switch pos { //nolint:exhaustive case Left: diff --git a/size.go b/size.go index 439a5cb8..0be4d13a 100644 --- a/size.go +++ b/size.go @@ -3,7 +3,7 @@ package lipgloss import ( "strings" - "github.com/muesli/reflow/ansi" + "github.com/charmbracelet/x/exp/term/ansi" ) // Width returns the cell width of characters in the string. ANSI sequences are @@ -14,7 +14,7 @@ import ( // will give you accurate results. func Width(str string) (width int) { for _, l := range strings.Split(str, "\n") { - w := ansi.PrintableRuneWidth(l) + w := ansi.StringWidth(l) if w > width { width = w } diff --git a/style.go b/style.go index ad6b52a3..43d1144a 100644 --- a/style.go +++ b/style.go @@ -4,9 +4,7 @@ import ( "strings" "unicode" - "github.com/muesli/reflow/truncate" - "github.com/muesli/reflow/wordwrap" - "github.com/muesli/reflow/wrap" + "github.com/charmbracelet/x/exp/term/ansi" "github.com/muesli/termenv" ) @@ -310,8 +308,8 @@ func (s Style) Render(strs ...string) string { // Word wrap if !inline && width > 0 { wrapAt := width - leftPadding - rightPadding - str = wordwrap.String(str, wrapAt) - str = wrap.String(str, wrapAt) // force-wrap long strings + str = ansi.Wordwrap(str, wrapAt, "") + str = ansi.Wrap(str, wrapAt, false) // force-wrap long strings } // Render core text @@ -397,7 +395,7 @@ func (s Style) Render(strs ...string) string { lines := strings.Split(str, "\n") for i := range lines { - lines[i] = truncate.String(lines[i], uint(maxWidth)) + lines[i] = ansi.Truncate(lines[i], maxWidth, "") } str = strings.Join(lines, "\n") diff --git a/table/table.go b/table/table.go index 68e41adb..75d1b566 100644 --- a/table/table.go +++ b/table/table.go @@ -4,7 +4,7 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - "github.com/muesli/reflow/truncate" + "github.com/charmbracelet/x/exp/term/ansi" ) // StyleFunc is the style function that determines the style of a Cell. @@ -437,7 +437,7 @@ func (t *Table) constructHeaders() string { MaxHeight(1). Width(t.widths[i]). MaxWidth(t.widths[i]). - Render(truncate.StringWithTail(header, uint(t.widths[i]), "…"))) + Render(ansi.Truncate(header, t.widths[i], "…"))) if i < len(t.headers)-1 && t.borderColumn { s.WriteString(t.borderStyle.Render(t.border.Left)) } @@ -488,7 +488,7 @@ func (t *Table) constructRow(index int) string { MaxHeight(height). Width(t.widths[c]). MaxWidth(t.widths[c]). - Render(truncate.StringWithTail(cell, uint(t.widths[c]*height), "…"))) + Render(ansi.Truncate(cell, t.widths[c]*height, "…"))) if c < t.data.Columns()-1 && t.borderColumn { cells = append(cells, left) diff --git a/whitespace.go b/whitespace.go index 78815fed..19656871 100644 --- a/whitespace.go +++ b/whitespace.go @@ -3,7 +3,7 @@ package lipgloss import ( "strings" - "github.com/muesli/reflow/ansi" + "github.com/charmbracelet/x/exp/term/ansi" "github.com/muesli/termenv" ) @@ -45,12 +45,12 @@ func (w whitespace) render(width int) string { if j >= len(r) { j = 0 } - i += ansi.PrintableRuneWidth(string(r[j])) + i += ansi.StringWidth(string(r[j])) } // Fill any extra gaps white spaces. This might be necessary if any runes // are more than one cell wide, which could leave a one-rune gap. - short := width - ansi.PrintableRuneWidth(b.String()) + short := width - ansi.StringWidth(b.String()) if short > 0 { b.WriteString(strings.Repeat(" ", short)) } From 2434fdaf076087c1f68282a3983e1d84ae0f25a7 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 29 Mar 2024 15:14:02 -0400 Subject: [PATCH 111/126] fix: combining both conditional and unconditional wrapping (#275) * feat: switch to term/ansi for text manipulation Use ANSI aware, wide characters support, uniseg backed term/ansi package to calculate string widths, truncate, and wrap strings. Related: https://github.com/muesli/reflow/pull/71 Fixes: https://github.com/charmbracelet/lipgloss/issues/258 Fixes: https://github.com/charmbracelet/lipgloss/issues/220 * fix: combining both conditional and unconditional wrapping Uses `ansi.SmartWrap` https://github.com/charmbracelet/x/pull/57 Fixes: https://github.com/muesli/reflow/issues/43 * chore: update deps * Update get.go --- go.mod | 2 +- go.sum | 4 ++-- style.go | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 5b4599ef..75132bc3 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. go 1.18 require ( - github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd + github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad github.com/muesli/termenv v0.15.2 github.com/rivo/uniseg v0.4.7 ) diff --git a/go.sum b/go.sum index a313ebc7..ad7dda8f 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd h1:HqBjkSFXXfW4IgX3TMKipWoPEN08T3Pi4SA/3DLss/U= -github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE= +github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad h1:ixybSpyIZys0qK4JF0asuuxdr9fMXHrOQa7/G9eO+nc= +github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= diff --git a/style.go b/style.go index 43d1144a..98540ad7 100644 --- a/style.go +++ b/style.go @@ -308,8 +308,7 @@ func (s Style) Render(strs ...string) string { // Word wrap if !inline && width > 0 { wrapAt := width - leftPadding - rightPadding - str = ansi.Wordwrap(str, wrapAt, "") - str = ansi.Wrap(str, wrapAt, false) // force-wrap long strings + str = ansi.Wrap(str, wrapAt, "") } // Render core text From 83959f5d942376688e1a06201f3f523343bfc34c Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Fri, 29 Mar 2024 15:16:13 -0400 Subject: [PATCH 112/126] chore: go mod tidy --- examples/go.mod | 2 +- examples/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 1eb982f9..5cad0943 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -7,7 +7,7 @@ replace github.com/charmbracelet/lipgloss => ../ require ( github.com/charmbracelet/lipgloss v0.4.0 github.com/charmbracelet/wish v0.5.0 - github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd + github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad github.com/gliderlabs/ssh v0.3.4 github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 diff --git a/examples/go.sum b/examples/go.sum index 0e38eb5a..f740d16d 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -17,8 +17,8 @@ github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBER github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg= github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk= github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0= -github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd h1:HqBjkSFXXfW4IgX3TMKipWoPEN08T3Pi4SA/3DLss/U= -github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE= +github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad h1:ixybSpyIZys0qK4JF0asuuxdr9fMXHrOQa7/G9eO+nc= +github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 29fafad5be923707318d4557fb39e80ffc0fa6ae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Mar 2024 15:44:34 -0400 Subject: [PATCH 113/126] chore(deps): bump golang.org/x/crypto from 0.1.0 to 0.17.0 in /examples (#277) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.1.0 to 0.17.0. - [Commits](https://github.com/golang/crypto/compare/v0.1.0...v0.17.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/go.mod | 6 +++--- examples/go.sum | 24 +++++++++++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index 5cad0943..1b8bb8ce 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -7,12 +7,11 @@ replace github.com/charmbracelet/lipgloss => ../ require ( github.com/charmbracelet/lipgloss v0.4.0 github.com/charmbracelet/wish v0.5.0 - github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad github.com/gliderlabs/ssh v0.3.4 github.com/kr/pty v1.1.1 github.com/lucasb-eyer/go-colorful v1.2.0 github.com/muesli/termenv v0.15.2 - golang.org/x/term v0.1.0 + golang.org/x/term v0.15.0 ) require ( @@ -20,10 +19,11 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/charmbracelet/keygen v0.3.0 // indirect + github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad // indirect github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - golang.org/x/crypto v0.1.0 // indirect + golang.org/x/crypto v0.17.0 // indirect golang.org/x/sys v0.18.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index f740d16d..0f53cf9e 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -94,19 +94,22 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220307211146-efcb8507fb70/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -125,25 +128,32 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From d0be07ea6b9c058cce9b951351b97948b2e3851e Mon Sep 17 00:00:00 2001 From: Michael Lorant Date: Sun, 14 Apr 2024 03:28:30 +1000 Subject: [PATCH 114/126] chore(deps): Upgrade dependencies (#279) Upgrade dependencies to latest stable versions. As part of this dependency upgrade, the `term` package is being updated to include an important fix for wrapping strings containing ANSI codes. See charmbracelet/x/issues/58 for more details. Signed-off-by: Michael Lorant --- go.mod | 6 +++--- go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index 75132bc3..da46fbf1 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze. go 1.18 require ( - github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad + github.com/charmbracelet/x/exp/term v0.0.0-20240408110044-525ba71bb562 github.com/muesli/termenv v0.15.2 github.com/rivo/uniseg v0.4.7 ) @@ -13,7 +13,7 @@ require ( require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.18 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/sys v0.19.0 // indirect ) diff --git a/go.sum b/go.sum index ad7dda8f..46dfded1 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,11 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad h1:ixybSpyIZys0qK4JF0asuuxdr9fMXHrOQa7/G9eO+nc= -github.com/charmbracelet/x/exp/term v0.0.0-20240329185201-62a6965a9fad/go.mod h1:6GZ13FjIP6eOCqWU4lqgveGnYxQo9c3qBzHPeFu4HBE= +github.com/charmbracelet/x/exp/term v0.0.0-20240408110044-525ba71bb562 h1:jCSNgVpyc16IspmSdrUTio2lY33YojCN4tKOyQxWIg4= +github.com/charmbracelet/x/exp/term v0.0.0-20240408110044-525ba71bb562/go.mod h1:yQqGHmheaQfkqiJWjklPHVAq1dKbk8uGbcoS/lcKCJ0= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= -github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= @@ -14,5 +14,5 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= From 1d4cfcb75bb76ed84db9018f834ea5b2deac4f65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 13:51:19 -0400 Subject: [PATCH 115/126] chore(deps): bump golangci/golangci-lint-action from 4 to 5 (#283) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 4 to 5. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v4...v5) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index a1eafa04..9522e9d7 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v5 with: # Optional: golangci-lint command line arguments. args: --config .golangci-soft.yml --issues-exit-code=0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 881d0d30..dddcf841 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v4 + uses: golangci/golangci-lint-action@v5 with: # Optional: golangci-lint command line arguments. #args: From 0d3715fd494f60371f0e1ef4415fbd00c8476735 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 1 May 2024 14:15:02 -0400 Subject: [PATCH 116/126] chore: update examples to remove deprecated Copy() --- examples/go.mod | 6 +++++- examples/go.sum | 11 +++++++++++ examples/layout/main.go | 22 +++++++++++----------- examples/ssh/main.go | 2 +- examples/table/languages/main.go | 8 ++++---- examples/table/pokemon/main.go | 10 +++++----- 6 files changed, 37 insertions(+), 22 deletions(-) diff --git a/examples/go.mod b/examples/go.mod index b93aef15..bf6ad38d 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -19,11 +19,15 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/caarlos0/sshmarshal v0.1.0 // indirect github.com/charmbracelet/keygen v0.3.0 // indirect + github.com/charmbracelet/x/exp/term v0.0.0-20240425164147-ba2a9512b05f // indirect + github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/mattn/go-isatty v0.0.18 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.19.0 // indirect ) diff --git a/examples/go.sum b/examples/go.sum index d16930bd..3e74f033 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -18,11 +18,15 @@ github.com/charmbracelet/keygen v0.3.0 h1:mXpsQcH7DDlST5TddmXNXjS0L7ECk4/kLQYyBc github.com/charmbracelet/keygen v0.3.0/go.mod h1:1ukgO8806O25lUZ5s0IrNur+RlwTBERlezdgW71F5rM= github.com/charmbracelet/wish v0.5.0 h1:FkkdNBFqrLABR1ciNrAL2KCxoyWfKhXnIGZw6GfAtPg= github.com/charmbracelet/wish v0.5.0/go.mod h1:5GAn5SrDSZ7cgKjnC+3kDmiIo7I6k4/AYiRzC4+tpCk= +github.com/charmbracelet/x/exp/term v0.0.0-20240425164147-ba2a9512b05f h1:1BXkZqDueTOBECyDoFGRi0xMYgjJ6vvoPIkWyKOwzTc= +github.com/charmbracelet/x/exp/term v0.0.0-20240425164147-ba2a9512b05f/go.mod h1:yQqGHmheaQfkqiJWjklPHVAq1dKbk8uGbcoS/lcKCJ0= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= +github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= @@ -61,6 +65,8 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= @@ -82,6 +88,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -116,6 +124,7 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -123,6 +132,8 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210422114643-f5beecf764ed/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= diff --git a/examples/layout/main.go b/examples/layout/main.go index e68cf772..e3281c6b 100644 --- a/examples/layout/main.go +++ b/examples/layout/main.go @@ -68,9 +68,9 @@ var ( BorderForeground(highlight). Padding(0, 1) - activeTab = tab.Copy().Border(activeTabBorder, true) + activeTab = tab.Border(activeTabBorder, true) - tabGap = tab.Copy(). + tabGap = tab. BorderTop(false). BorderLeft(false). BorderRight(false) @@ -109,7 +109,7 @@ var ( Padding(0, 3). MarginTop(1) - activeButtonStyle = buttonStyle.Copy(). + activeButtonStyle = buttonStyle. Foreground(lipgloss.Color("#FFF7DB")). Background(lipgloss.Color("#F25D94")). MarginRight(2). @@ -173,13 +173,13 @@ var ( Padding(0, 1). MarginRight(1) - encodingStyle = statusNugget.Copy(). + encodingStyle = statusNugget. Background(lipgloss.Color("#A550DF")). Align(lipgloss.Right) statusText = lipgloss.NewStyle().Inherit(statusBarStyle) - fishCakeStyle = statusNugget.Copy().Background(lipgloss.Color("#6124DF")) + fishCakeStyle = statusNugget.Background(lipgloss.Color("#6124DF")) // Page. @@ -215,7 +215,7 @@ func main() { for i, v := range colors { const offset = 2 c := lipgloss.Color(v[0]) - fmt.Fprint(&title, titleStyle.Copy().MarginLeft(i*offset).Background(c)) + fmt.Fprint(&title, titleStyle.MarginLeft(i*offset).Background(c)) if i < len(colors)-1 { title.WriteRune('\n') } @@ -276,7 +276,7 @@ func main() { listItem("Pomelo"), ), ), - list.Copy().Width(columnWidth).Render( + list.Width(columnWidth).Render( lipgloss.JoinVertical(lipgloss.Left, listHeader("Actual Lip Gloss Vendors"), listItem("Glossier"), @@ -300,9 +300,9 @@ func main() { doc.WriteString(lipgloss.JoinHorizontal( lipgloss.Top, - historyStyle.Copy().Align(lipgloss.Right).Render(historyA), - historyStyle.Copy().Align(lipgloss.Center).Render(historyB), - historyStyle.Copy().MarginRight(0).Render(historyC), + historyStyle.Align(lipgloss.Right).Render(historyA), + historyStyle.Align(lipgloss.Center).Render(historyB), + historyStyle.MarginRight(0).Render(historyC), )) doc.WriteString("\n\n") @@ -315,7 +315,7 @@ func main() { statusKey := statusStyle.Render("STATUS") encoding := encodingStyle.Render("UTF-8") fishCake := fishCakeStyle.Render("🍥 Fish Cake") - statusVal := statusText.Copy(). + statusVal := statusText. Width(width - w(statusKey) - w(encoding) - w(fishCake)). Render("Ravishing") diff --git a/examples/ssh/main.go b/examples/ssh/main.go index cd23b052..0871ebb5 100644 --- a/examples/ssh/main.go +++ b/examples/ssh/main.go @@ -161,7 +161,7 @@ func handler(next ssh.Handler) ssh.Handler { styles.gray, ) - fmt.Fprintf(&str, "%s %t %s\n\n", styles.bold.Copy().UnsetString().Render("Has dark background?"), + fmt.Fprintf(&str, "%s %t %s\n\n", styles.bold.UnsetString().Render("Has dark background?"), renderer.HasDarkBackground(), renderer.Output().BackgroundColor()) diff --git a/examples/table/languages/main.go b/examples/table/languages/main.go index 601cdc84..b0e94d82 100644 --- a/examples/table/languages/main.go +++ b/examples/table/languages/main.go @@ -23,9 +23,9 @@ func main() { // CellStyle is the base lipgloss style used for the table rows. CellStyle = re.NewStyle().Padding(0, 1).Width(14) // OddRowStyle is the lipgloss style used for odd-numbered table rows. - OddRowStyle = CellStyle.Copy().Foreground(gray) + OddRowStyle = CellStyle.Foreground(gray) // EvenRowStyle is the lipgloss style used for even-numbered table rows. - EvenRowStyle = CellStyle.Copy().Foreground(lightGray) + EvenRowStyle = CellStyle.Foreground(lightGray) // BorderStyle is the lipgloss style used for the table border. BorderStyle = lipgloss.NewStyle().Foreground(purple) ) @@ -55,12 +55,12 @@ func main() { // Make the second column a little wider. if col == 1 { - style = style.Copy().Width(22) + style = style.Width(22) } // Arabic is a right-to-left language, so right align the text. if row < len(rows) && rows[row-1][0] == "Arabic" && col != 0 { - style = style.Copy().Align(lipgloss.Right) + style = style.Align(lipgloss.Right) } return style diff --git a/examples/table/pokemon/main.go b/examples/table/pokemon/main.go index 628d90d6..5c25eee9 100644 --- a/examples/table/pokemon/main.go +++ b/examples/table/pokemon/main.go @@ -12,8 +12,8 @@ import ( func main() { re := lipgloss.NewRenderer(os.Stdout) baseStyle := re.NewStyle().Padding(0, 1) - headerStyle := baseStyle.Copy().Foreground(lipgloss.Color("252")).Bold(true) - selectedStyle := baseStyle.Copy().Foreground(lipgloss.Color("#01BE85")).Background(lipgloss.Color("#00432F")) + headerStyle := baseStyle.Foreground(lipgloss.Color("252")).Bold(true) + selectedStyle := baseStyle.Foreground(lipgloss.Color("#01BE85")).Background(lipgloss.Color("#00432F")) typeColors := map[string]lipgloss.Color{ "Bug": lipgloss.Color("#D7FF87"), "Electric": lipgloss.Color("#FDFF90"), @@ -101,13 +101,13 @@ func main() { } color := c[fmt.Sprint(data[row-1][col])] - return baseStyle.Copy().Foreground(color) + return baseStyle.Foreground(color) } if even { - return baseStyle.Copy().Foreground(lipgloss.Color("245")) + return baseStyle.Foreground(lipgloss.Color("245")) } - return baseStyle.Copy().Foreground(lipgloss.Color("252")) + return baseStyle.Foreground(lipgloss.Color("252")) }) fmt.Println(t) } From 8e3f4122dcb04b0401be50bc326fccc2e4b68a02 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 1 May 2024 14:22:21 -0400 Subject: [PATCH 117/126] docs: update Copy() notes --- README.md | 41 +++++++++++++---------------------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 22defac7..6758809d 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -Lip Gloss -========= +# Lip Gloss

Lip Gloss title treatment
@@ -66,7 +65,6 @@ The terminal's color profile will be automatically detected, and colors outside the gamut of the current palette will be automatically coerced to their closest available value. - ### Adaptive Colors You can also specify color options for light and dark backgrounds: @@ -117,7 +115,6 @@ var style = lipgloss.NewStyle(). Reverse(true) ``` - ## Block-Level Formatting Lip Gloss also supports rules for block-level formatting: @@ -156,7 +153,6 @@ lipgloss.NewStyle().Padding(1, 4, 2) lipgloss.NewStyle().Margin(2, 4, 3, 1) ``` - ## Aligning Text You can align paragraphs of text to the left, right, or center. @@ -169,7 +165,6 @@ var style = lipgloss.NewStyle(). Align(lipgloss.Center) // just kidding, align it in the center ``` - ## Width and Height Setting a minimum width and height is simple and straightforward. @@ -182,7 +177,6 @@ var style = lipgloss.NewStyle(). Foreground(lipgloss.Color("63")) ``` - ## Borders Adding borders is easy: @@ -230,21 +224,19 @@ lipgloss.NewStyle(). For more on borders see [the docs][docs]. - ## Copying Styles -Just use `Copy()`: +Just use assignment ```go var style = lipgloss.NewStyle().Foreground(lipgloss.Color("219")) -var wildStyle = style.Copy().Blink(true) +var wildStyle = style.Blink(true) ``` -`Copy()` performs a copy on the underlying data structure ensuring that you get -a true, dereferenced copy of a style. Without copying, it's possible to mutate -styles. - +Since `Style` data structure contains only primitive types, assigning a style +to another effectively creates a new copy of the style without mutating the +original. ## Inheritance @@ -263,7 +255,6 @@ var styleB = lipgloss.NewStyle(). Inherit(styleA) ``` - ## Unsetting Rules All rules can be unset: @@ -278,7 +269,6 @@ var style = lipgloss.NewStyle(). When a rule is unset, it won't be inherited or copied. - ## Enforcing Rules Sometimes, such as when developing a component, you want to make sure style @@ -355,7 +345,6 @@ For an example on using a custom renderer over SSH with [Wish][wish] see the In addition to pure styling, Lip Gloss also ships with some utilities to help assemble your layouts. - ### Joining Paragraphs Horizontally and vertically joining paragraphs is a cinch. @@ -372,7 +361,6 @@ lipgloss.JoinVertical(lipgloss.Center, paragraphA, paragraphB) lipgloss.JoinHorizontal(0.2, paragraphA, paragraphB, paragraphC) ``` - ### Measuring Width and Height Sometimes you’ll want to know the width and height of text blocks when building @@ -465,7 +453,7 @@ fmt.Println(t) For more on tables see [the docs](https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc) and [examples](https://github.com/charmbracelet/lipgloss/tree/master/examples/table). -*** +--- ## FAQ @@ -501,10 +489,11 @@ import ( lipgloss.SetColorProfile(termenv.TrueColor) ``` -*Note:* this option limits the flexibility of your application and can cause +_Note:_ this option limits the flexibility of your application and can cause ANSI escape codes to be output in cases where that might not be desired. Take careful note of your use case and environment before choosing to force a color profile. + ## What about [Bubble Tea][tea]? @@ -518,7 +507,6 @@ In simple terms, you can use Lip Gloss to help build your Bubble Tea views. [tea]: https://github.com/charmbracelet/tea - ## Under the Hood Lip Gloss is built on the excellent [Termenv][termenv] and [Reflow][reflow] @@ -528,7 +516,6 @@ For many use cases Termenv and Reflow will be sufficient for your needs. [termenv]: https://github.com/muesli/termenv [reflow]: https://github.com/muesli/reflow - ## Rendering Markdown For a more document-centric rendering solution with support for things like @@ -537,20 +524,19 @@ the stylesheet-based Markdown renderer. [glamour]: https://github.com/charmbracelet/glamour - ## Feedback We’d love to hear your thoughts on this project. Feel free to drop us a note! -* [Twitter](https://twitter.com/charmcli) -* [The Fediverse](https://mastodon.social/@charmcli) -* [Discord](https://charm.sh/chat) +- [Twitter](https://twitter.com/charmcli) +- [The Fediverse](https://mastodon.social/@charmcli) +- [Discord](https://charm.sh/chat) ## License [MIT](https://github.com/charmbracelet/lipgloss/raw/master/LICENSE) -*** +--- Part of [Charm](https://charm.sh). @@ -558,7 +544,6 @@ Part of [Charm](https://charm.sh). Charm热爱开源 • Charm loves open source - [docs]: https://pkg.go.dev/github.com/charmbracelet/lipgloss?tab=doc [wish]: https://github.com/charmbracelet/wish [ssh-example]: examples/ssh From efbfa4d2db0eb45a401b29ef43a1ed0a49673946 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 1 May 2024 14:26:50 -0400 Subject: [PATCH 118/126] chore: update README.md Co-authored-by: Christian Rocha --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6758809d..f9e01b41 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ var style = lipgloss.NewStyle().Foreground(lipgloss.Color("219")) var wildStyle = style.Blink(true) ``` -Since `Style` data structure contains only primitive types, assigning a style +Since `Style` data structures contains only primitive types, assigning a style to another effectively creates a new copy of the style without mutating the original. From 9f111af5aa9868b4cc56303b4094243d500f7dd0 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 1 May 2024 15:59:11 -0400 Subject: [PATCH 119/126] chore(lint): add nolint directives --- get.go | 6 +++--- set.go | 6 +++--- style.go | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/get.go b/get.go index 50eba512..5783f9ad 100644 --- a/get.go +++ b/get.go @@ -432,7 +432,7 @@ func (s Style) getAsColor(k propKey) TerminalColor { } var c TerminalColor - switch k { + switch k { //nolint:exhaustive case foregroundKey: c = s.fgColor case backgroundKey: @@ -468,7 +468,7 @@ func (s Style) getAsInt(k propKey) int { if !s.isSet(k) { return 0 } - switch k { + switch k { //nolint:exhaustive case widthKey: return s.width case heightKey: @@ -503,7 +503,7 @@ func (s Style) getAsPosition(k propKey) Position { if !s.isSet(k) { return Position(0) } - switch k { + switch k { //nolint:exhaustive case alignHorizontalKey: return s.alignHorizontal case alignVerticalKey: diff --git a/set.go b/set.go index d4f4bff9..6f8b0825 100644 --- a/set.go +++ b/set.go @@ -6,7 +6,7 @@ func (s *Style) set(key propKey, value interface{}) { // them at zero or above. We could use uints instead, but the // conversions are a little tedious, so we're sticking with ints for // sake of usability. - switch key { + switch key { //nolint:exhaustive case foregroundKey: s.fgColor = colorOrNil(value) case backgroundKey: @@ -66,7 +66,7 @@ func (s *Style) set(key propKey, value interface{}) { case transformKey: s.transform = value.(func(string) string) default: - if v, ok := value.(bool); ok { + if v, ok := value.(bool); ok { //nolint:nestif if v { s.attrs |= int(key) } else { @@ -88,7 +88,7 @@ func (s *Style) set(key propKey, value interface{}) { // setFrom sets the property from another style. func (s *Style) setFrom(key propKey, i Style) { - switch key { + switch key { //nolint:exhaustive case foregroundKey: s.set(foregroundKey, i.fgColor) case backgroundKey: diff --git a/style.go b/style.go index 9e752729..b9c44fc5 100644 --- a/style.go +++ b/style.go @@ -394,7 +394,7 @@ func (s Style) Render(strs ...string) string { } // Padding - if !inline { + if !inline { //nolint:nestif if leftPadding > 0 { var st *termenv.Style if colorWhitespace || styleWhitespace { @@ -564,7 +564,7 @@ func pad(str string, n int, style *termenv.Style) string { return b.String() } -func max(a, b int) int { // nolint:unparam +func max(a, b int) int { //nolint:unparam if a > b { return a } From e35d216358713610a0f82a57c51ca769ece98fed Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 1 May 2024 15:59:30 -0400 Subject: [PATCH 120/126] chore(lint): end comments with periods --- style.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/style.go b/style.go index b9c44fc5..522352c2 100644 --- a/style.go +++ b/style.go @@ -15,7 +15,7 @@ type propKey int // Available properties. const ( - // bool props come first + // Boolean props come first. boldKey propKey = 1 << iota italicKey underlineKey @@ -27,8 +27,7 @@ const ( strikethroughSpacesKey colorWhitespaceKey - // non-bool props - + // Non-boolean props. foregroundKey backgroundKey widthKey From c986440c2acbd01abe20b9cc6409f54bc31570a4 Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Wed, 1 May 2024 15:59:54 -0400 Subject: [PATCH 121/126] chore(lint): remove deprecated ifshort linting option --- .golangci-soft.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.golangci-soft.yml b/.golangci-soft.yml index ef456e06..1b6824bb 100644 --- a/.golangci-soft.yml +++ b/.golangci-soft.yml @@ -23,7 +23,6 @@ linters: - gomnd - gomoddirectives - goprintffuncname - - ifshort # - lll - misspell - nakedret From 517b1a163eac429712956ba831e97c89b2616455 Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Mon, 6 May 2024 09:16:14 -0400 Subject: [PATCH 122/126] fix: remove unused type --- renderer.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/renderer.go b/renderer.go index 85ffd254..233aa7c0 100644 --- a/renderer.go +++ b/renderer.go @@ -28,9 +28,6 @@ type Renderer struct { mtx sync.RWMutex } -// RendererOption is a function that can be used to configure a [Renderer]. -type RendererOption func(r *Renderer) - // DefaultRenderer returns the default renderer. func DefaultRenderer() *Renderer { return renderer From 3ee5dcab73cbfd5edd62af2d4f63b4644b942b4c Mon Sep 17 00:00:00 2001 From: Christian Rocha Date: Mon, 6 May 2024 15:59:26 -0400 Subject: [PATCH 123/126] chore(docs): doc updates with regard to style.Copy() deprecation --- README.md | 10 ++++++---- style.go | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f9e01b41..1376c7c6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,6 @@ GoDoc Build Status phorm.ai -

Style definitions for nice terminal layouts. Built with TUIs in mind. @@ -226,12 +225,15 @@ For more on borders see [the docs][docs]. ## Copying Styles -Just use assignment +Just use assignment: ```go -var style = lipgloss.NewStyle().Foreground(lipgloss.Color("219")) +style := lipgloss.NewStyle().Foreground(lipgloss.Color("219")) + +copiedStyle := style // this is a true copy + +wildStyle := style.Blink(true) // this is also true copy, with blink added -var wildStyle = style.Blink(true) ``` Since `Style` data structures contains only primitive types, assigning a style diff --git a/style.go b/style.go index 522352c2..198751ce 100644 --- a/style.go +++ b/style.go @@ -189,7 +189,7 @@ func (s Style) String() string { // Copy returns a copy of this style, including any underlying string values. // -// Deprecated: Copy is deprecated and will be removed in a future release. +// Deprecated: to copy just use assignment (i.e. a := b). All methods also return a new style. func (s Style) Copy() Style { return s } From 2fe044adcb06371220c9abff05acbf714f715dc1 Mon Sep 17 00:00:00 2001 From: Hugo Leonardo Costa e Silva Date: Thu, 9 May 2024 13:07:41 -0300 Subject: [PATCH 124/126] fix: Change the propkeys from int to int64 When I create binaries for each OS using goreleaser, the propKeys generates an int overflow since the iota duplicates for each propKey This commit changes the int type to works well with binaries generated for arm and i386 archs. --- style.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/style.go b/style.go index 198751ce..c1e05c04 100644 --- a/style.go +++ b/style.go @@ -11,7 +11,7 @@ import ( const tabWidthDefault = 4 // Property for a key. -type propKey int +type propKey int64 // Available properties. const ( @@ -78,7 +78,7 @@ const ( ) // props is a set of properties. -type props int +type props int64 // set sets a property. func (p props) set(k propKey) props { From e3596ae70d0e46c30edea00f26892a71bba5b4aa Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Thu, 9 May 2024 12:20:09 -0400 Subject: [PATCH 125/126] chore(ci): test for different GOOS & GOARCH --- .github/workflows/build.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f804b0cd..21321398 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,3 +26,22 @@ jobs: - name: Test run: go test ./... + + test-goos: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + cache: true + # https://go.dev/doc/install/source#environment + - run: GOOS=darwin GOARCH=amd64 go test -c -v ./... + - run: GOOS=darwin GOARCH=arm64 go test -c -v ./... + - run: GOOS=linux GOARCH=386 go test -c -v ./... + - run: GOOS=linux GOARCH=amd64 go test -c -v ./... + - run: GOOS=linux GOARCH=arm go test -c -v ./... + - run: GOOS=linux GOARCH=arm64 go test -c -v ./... + - run: GOOS=windows GOARCH=amd64 go test -c -v ./... + - run: GOOS=windows GOARCH=386 go test -c -v ./... + - run: GOOS=windows GOARCH=arm go test -c -v ./... + - run: GOOS=windows GOARCH=arm64 go test -c -v ./... From ce5323ee411230bef6ac0f3f2344dd0b19c9d3b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 11:09:26 -0300 Subject: [PATCH 126/126] chore(deps): bump golangci/golangci-lint-action from 5 to 6 (#290) Bumps [golangci/golangci-lint-action](https://github.com/golangci/golangci-lint-action) from 5 to 6. - [Release notes](https://github.com/golangci/golangci-lint-action/releases) - [Commits](https://github.com/golangci/golangci-lint-action/compare/v5...v6) --- updated-dependencies: - dependency-name: golangci/golangci-lint-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/lint-soft.yml | 2 +- .github/workflows/lint.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-soft.yml b/.github/workflows/lint-soft.yml index 9522e9d7..87d1e1f9 100644 --- a/.github/workflows/lint-soft.yml +++ b/.github/workflows/lint-soft.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: # Optional: golangci-lint command line arguments. args: --config .golangci-soft.yml --issues-exit-code=0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index dddcf841..f617a5a2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@v4 - name: golangci-lint - uses: golangci/golangci-lint-action@v5 + uses: golangci/golangci-lint-action@v6 with: # Optional: golangci-lint command line arguments. #args: