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 ./... 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: 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 diff --git a/README.md b/README.md index 36e985f9..b92dba25 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. @@ -224,17 +223,20 @@ 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")) +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.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 -styles. +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. ## Inheritance @@ -652,6 +654,7 @@ Print the list: src="https://github.com/charmbracelet/lipgloss/assets/245435/44e37a5b-5124-4f49-a332-1756a355002e" />

+ --- ## FAQ @@ -700,6 +703,7 @@ import ( ) lipgloss.SetColorProfile(termenv.TrueColor) + ``` _Note_: this option limits the flexibility of your application and can cause @@ -761,3 +765,4 @@ 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 +``` diff --git a/examples/go.mod b/examples/go.mod index f79a55b1..6cc5e655 100644 --- a/examples/go.mod +++ b/examples/go.mod @@ -19,7 +19,7 @@ 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-20240408110044-525ba71bb562 // indirect + github.com/charmbracelet/x/exp/term v0.0.0-20240425164147-ba2a9512b05f // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect diff --git a/examples/go.sum b/examples/go.sum index 0af75f5c..14577f35 100644 --- a/examples/go.sum +++ b/examples/go.sum @@ -9,8 +9,8 @@ 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-20240408110044-525ba71bb562 h1:jCSNgVpyc16IspmSdrUTio2lY33YojCN4tKOyQxWIg4= -github.com/charmbracelet/x/exp/term v0.0.0-20240408110044-525ba71bb562/go.mod h1:yQqGHmheaQfkqiJWjklPHVAq1dKbk8uGbcoS/lcKCJ0= +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/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= 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) } diff --git a/get.go b/get.go index d6c83a97..5783f9ad 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 { //nolint:exhaustive + 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 { //nolint:exhaustive + 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 { //nolint:exhaustive + 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/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 diff --git a/set.go b/set.go index 5846b598..6f8b0825 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 { //nolint:exhaustive + 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 { //nolint:nestif + 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 { //nolint:exhaustive + 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 f736079c..c1e05c04 100644 --- a/style.go +++ b/style.go @@ -11,17 +11,23 @@ import ( const tabWidthDefault = 4 // Property for a key. -type propKey int +type propKey int64 // Available properties. const ( - boldKey propKey = iota + // Boolean props come first. + boldKey propKey = 1 << iota italicKey underlineKey strikethroughKey reverseKey blinkKey faintKey + underlineSpacesKey + strikethroughSpacesKey + colorWhitespaceKey + + // Non-boolean props. foregroundKey backgroundKey widthKey @@ -35,8 +41,6 @@ const ( paddingBottomKey paddingLeftKey - colorWhitespaceKey - // Margins. marginTopKey marginRightKey @@ -69,14 +73,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 int64 + +// 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 @@ -98,8 +115,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 @@ -131,15 +188,10 @@ func (s Style) String() string { } // Copy returns a copy of this style, including any underlying string values. +// +// Deprecated: to copy just use assignment (i.e. a := b). All methods also return a new style. 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 @@ -148,9 +200,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 @@ -161,14 +215,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 } @@ -233,7 +288,7 @@ func (s Style) Render(strs ...string) string { str = transform(str) } - if len(s.rules) == 0 { + if s.props == 0 { return s.maybeConvertTabs(str) } @@ -338,7 +393,7 @@ func (s Style) Render(strs ...string) string { } // Padding - if !inline { //nolint: nestif + if !inline { //nolint:nestif if leftPadding > 0 { var st *termenv.Style if colorWhitespace || styleWhitespace { @@ -508,7 +563,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 }