diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 38025667..4bb9c829 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,6 +13,10 @@ updates: commit-message: prefix: "chore" include: "scope" + groups: + all: + patterns: + - "*" - package-ecosystem: "github-actions" directory: "/" @@ -26,6 +30,10 @@ updates: commit-message: prefix: "chore" include: "scope" + groups: + all: + patterns: + - "*" - package-ecosystem: "docker" directory: "/" @@ -37,8 +45,12 @@ updates: labels: - "dependencies" commit-message: - prefix: "feat" + prefix: "chore" include: "scope" + groups: + all: + patterns: + - "*" - package-ecosystem: "gomod" directory: "/example" @@ -52,3 +64,7 @@ updates: commit-message: prefix: "chore" include: "scope" + groups: + all: + patterns: + - "*" diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ab232146..83eeaf6e 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,12 +12,12 @@ jobs: GO111MODULE: "on" steps: - name: Install Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version: ${{ matrix.go-version }} - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - run: | git config --global url."https://${{ secrets.PERSONAL_ACCESS_TOKEN }}@github.com/charmbracelet".insteadOf "https://github.com/charmbracelet" diff --git a/.golangci.yml b/.golangci.yml index 7c0a115e..929cb0ac 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -7,7 +7,6 @@ linters: - exhaustive - goconst - godot - - godox - gomoddirectives - goprintffuncname - gosec @@ -27,13 +26,13 @@ linters: - whitespace - wrapcheck exclusions: + rules: + - text: '(slog|log)\.\w+' + linters: + - noctx generated: lax presets: - common-false-positives - paths: - - third_party$ - - builtin$ - - examples$ issues: max-issues-per-linter: 0 max-same-issues: 0 @@ -43,7 +42,3 @@ formatters: - goimports exclusions: generated: lax - paths: - - third_party$ - - builtin$ - - examples$ diff --git a/LICENSE b/LICENSE index 6f5b1fa6..3658b33e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2023 Charmbracelet, Inc +Copyright (c) 2021-2025 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 diff --git a/README.md b/README.md index cee2371c..741fe69f 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,20 @@ # Lip Gloss
Style definitions for nice terminal layouts. Built with TUIs in mind. - + Lip Gloss takes an expressive, declarative approach to terminal rendering. Users familiar with CSS will feel at home with Lip Gloss. @@ -806,7 +810,7 @@ We’d love to hear your thoughts on this project. Feel free to drop us a note! Part of [Charm](https://charm.sh). -
+
Charm热爱开源 • Charm loves open source
diff --git a/borders.go b/borders.go
index b36f8743..a8d3e904 100644
--- a/borders.go
+++ b/borders.go
@@ -2,6 +2,7 @@ package lipgloss
import (
"strings"
+ "unicode/utf8"
"github.com/charmbracelet/x/ansi"
"github.com/muesli/termenv"
@@ -485,6 +486,6 @@ func getFirstRuneAsString(str string) string {
if str == "" {
return str
}
- r := []rune(str)
- return string(r[0])
+ _, size := utf8.DecodeRuneInString(str)
+ return str[:size]
}
diff --git a/borders_test.go b/borders_test.go
index 44b95d0c..c2fb5c14 100644
--- a/borders_test.go
+++ b/borders_test.go
@@ -1,6 +1,8 @@
package lipgloss
-import "testing"
+import (
+ "testing"
+)
func TestStyle_GetBorderSizes(t *testing.T) {
tests := []struct {
@@ -94,3 +96,80 @@ func TestStyle_GetBorderSizes(t *testing.T) {
})
}
}
+
+// Old implementation using rune slice conversion
+func getFirstRuneAsStringOld(str string) string {
+ if str == "" {
+ return str
+ }
+ r := []rune(str)
+ return string(r[0])
+}
+
+func TestGetFirstRuneAsString(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ want string
+ }{
+ {"Empty", "", ""},
+ {"SingleASCII", "A", "A"},
+ {"SingleUnicode", "世", "世"},
+ {"ASCIIString", "Hello", "H"},
+ {"UnicodeString", "你好世界", "你"},
+ {"MixedASCIIFirst", "Hello世界", "H"},
+ {"MixedUnicodeFirst", "世界Hello", "世"},
+ {"Emoji", "😀Happy", "😀"},
+ {"MultiByteFirst", "ñoño", "ñ"},
+ {"LongString", "The quick brown fox jumps over the lazy dog", "T"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got := getFirstRuneAsString(tt.input)
+ if got != tt.want {
+ t.Errorf("getFirstRuneAsString(%q) = %q, want %q", tt.input, got, tt.want)
+ }
+
+ // Verify new implementation matches old implementation
+ old := getFirstRuneAsStringOld(tt.input)
+ if got != old {
+ t.Errorf("getFirstRuneAsString(%q) = %q, but old implementation returns %q", tt.input, got, old)
+ }
+ })
+ }
+}
+
+func BenchmarkGetFirstRuneAsString(b *testing.B) {
+ testCases := []struct {
+ name string
+ str string
+ }{
+ {"ASCII", "Hello, World!"},
+ {"Unicode", "你好世界"},
+ {"Single", "A"},
+ {"Empty", ""},
+ }
+
+ b.Run("Old", func(b *testing.B) {
+ for _, tc := range testCases {
+ b.Run(tc.name, func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ _ = getFirstRuneAsStringOld(tc.str)
+ }
+ })
+ }
+ })
+
+ b.Run("New", func(b *testing.B) {
+ for _, tc := range testCases {
+ b.Run(tc.name, func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ _ = getFirstRuneAsString(tc.str)
+ }
+ })
+ }
+ })
+}
diff --git a/examples/go.mod b/examples/go.mod
index 4f8dcb6e..eea207a8 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -13,34 +13,38 @@ require (
github.com/creack/pty v1.1.21
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/muesli/gamut v0.3.1
- github.com/muesli/termenv v0.15.2
- golang.org/x/term v0.27.0
+ github.com/muesli/termenv v0.16.0
+ golang.org/x/term v0.29.0
)
require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/bubbletea v0.25.0 // indirect
+ github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/keygen v0.5.0 // indirect
github.com/charmbracelet/log v0.4.0 // indirect
- github.com/charmbracelet/x/ansi v0.6.0 // indirect
+ github.com/charmbracelet/x/ansi v0.8.0 // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 // indirect
github.com/charmbracelet/x/exp/term v0.0.0-20240328150354-ab9afc214dfd // indirect
+ github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
- github.com/mattn/go-runewidth v0.0.15 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/clusters v0.0.0-20200529215643-2700303c1762 // indirect
github.com/muesli/kmeans v0.3.1 // 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
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
- golang.org/x/crypto v0.31.0 // indirect
+ golang.org/x/crypto v0.35.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
- golang.org/x/sync v0.10.0 // indirect
- golang.org/x/sys v0.28.0 // indirect
- golang.org/x/text v0.21.0 // indirect
+ golang.org/x/sync v0.11.0 // indirect
+ golang.org/x/sys v0.30.0 // indirect
+ golang.org/x/text v0.22.0 // indirect
)
diff --git a/examples/go.sum b/examples/go.sum
index 5959d361..e90ef4a7 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -5,6 +5,8 @@ github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ
github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
+github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/keygen v0.5.0 h1:XY0fsoYiCSM9axkrU+2ziE6u6YjJulo/b9Dghnw6MZc=
github.com/charmbracelet/keygen v0.5.0/go.mod h1:DfvCgLHxZ9rJxdK0DGw3C/LkV4SgdGbnliHcObV3L+8=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
@@ -13,13 +15,17 @@ github.com/charmbracelet/ssh v0.0.0-20240401141849-854cddfa2917 h1:NZKjJ7d/pzk/A
github.com/charmbracelet/ssh v0.0.0-20240401141849-854cddfa2917/go.mod h1:8/Ve8iGRRIGFM1kepYfRF2pEOF5Y3TEZYoJaA54228U=
github.com/charmbracelet/wish v1.4.0 h1:pL1uVP/YuYgJheHEj98teZ/n6pMYnmlZq/fcHvomrfc=
github.com/charmbracelet/wish v1.4.0/go.mod h1:ew4/MjJVfW/akEO9KmrQHQv1F7bQRGscRMrA+KtovTk=
-github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA=
-github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
+github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
+github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
+github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
+github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 h1:3RXpZWGWTOeVXCTv0Dnzxdv/MhNUkBfEcbaTY0zrTQI=
github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
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/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
+github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
@@ -35,8 +41,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
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/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -50,8 +56,8 @@ github.com/muesli/kmeans v0.3.1 h1:KshLQ8wAETfLWOJKMuDCVYHnafddSa1kwGh/IypGIzY=
github.com/muesli/kmeans v0.3.1/go.mod h1:8/OvJW7cHc1BpRf8URb43m+vR105DDe+Kj1WcFXYDqc=
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/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
+github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
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=
@@ -59,22 +65,24 @@ 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=
github.com/wcharczuk/go-chart/v2 v2.1.0/go.mod h1:yx7MvAVNcP/kN9lKXM/NTce4au4DFN99j6i1OwDclNA=
+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/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs=
+golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
+golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
-golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
-golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
+golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
+golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
+golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
+golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/go.mod b/go.mod
index 09792957..2f60edad 100644
--- a/go.mod
+++ b/go.mod
@@ -4,13 +4,13 @@ retract v0.7.0 // v0.7.0 introduces a bug that causes some apps to freeze.
retract v0.11.1 // v0.11.1 uses a broken version of x/ansi StringWidth that causes some lines to wrap incorrectly.
-go 1.18
+go 1.24.0
require (
- github.com/aymanbagabas/go-udiff v0.2.0
- github.com/charmbracelet/x/ansi v0.8.0
+ github.com/aymanbagabas/go-udiff v0.3.1
+ github.com/charmbracelet/x/ansi v0.10.2
github.com/charmbracelet/x/cellbuf v0.0.13
- github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a
+ github.com/charmbracelet/x/exp/golden v0.0.0-20250609102027-b60490452b30
github.com/muesli/termenv v0.16.0
github.com/rivo/uniseg v0.4.7
)
@@ -19,9 +19,9 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
- github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
+ github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/mattn/go-runewidth v0.0.17 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.30.0 // indirect
)
diff --git a/go.sum b/go.sum
index 3858e705..cf41b749 100644
--- a/go.sum
+++ b/go.sum
@@ -1,23 +1,23 @@
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/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
-github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
+github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
+github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
-github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
-github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
+github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
+github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
-github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
-github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
+github.com/charmbracelet/x/exp/golden v0.0.0-20250609102027-b60490452b30 h1:lF42GCGfbMxx4SOYkjChVoUDexdM/hQ4DWnAHcJ/6K0=
+github.com/charmbracelet/x/exp/golden v0.0.0-20250609102027-b60490452b30/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
-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/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
+github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
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.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
-github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-runewidth v0.0.17 h1:78v8ZlW0bP43XfmAfPsdXcoNCelfMHsDmd/pkENfrjQ=
+github.com/mattn/go-runewidth v0.0.17/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
@@ -26,6 +26,7 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
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=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
+golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
diff --git a/style.go b/style.go
index 59fa3ab2..7e962a4f 100644
--- a/style.go
+++ b/style.go
@@ -565,20 +565,6 @@ func pad(str string, n int, style *termenv.Style) string {
return b.String()
}
-func max(a, b int) int { //nolint:unparam,predeclared
- if a > b {
- return a
- }
- return b
-}
-
-func min(a, b int) int { //nolint:predeclared
- if a < b {
- return a
- }
- return b
-}
-
func abs(a int) int {
if a < 0 {
return -a
diff --git a/table/resizing.go b/table/resizing.go
index 731e571c..30b0a010 100644
--- a/table/resizing.go
+++ b/table/resizing.go
@@ -212,7 +212,7 @@ func (r *resizer) expandTableWidth() (colWidths, rowHeights []int) {
colWidths[shorterColumnIndex]++
}
- rowHeights = r.expandRowHeigths(colWidths)
+ rowHeights = r.expandRowHeights(colWidths)
return
}
@@ -288,11 +288,11 @@ func (r *resizer) shrinkTableWidth() (colWidths, rowHeights []int) {
shrinkToMedian()
shrinkBiggestColumns(false)
- return colWidths, r.expandRowHeigths(colWidths)
+ return colWidths, r.expandRowHeights(colWidths)
}
-// expandRowHeigths expands the row heights.
-func (r *resizer) expandRowHeigths(colWidths []int) (rowHeights []int) {
+// expandRowHeights expands the row heights.
+func (r *resizer) expandRowHeights(colWidths []int) (rowHeights []int) {
rowHeights = r.defaultRowHeights()
if !r.wrap {
return rowHeights
diff --git a/table/table.go b/table/table.go
index b17c223f..ad17fcfb 100644
--- a/table/table.go
+++ b/table/table.go
@@ -347,16 +347,25 @@ func (t *Table) constructBottomBorder() string {
// constructHeaders constructs the headers for the table given it's current
// header configuration and data.
func (t *Table) constructHeaders() string {
+ height := t.heights[HeaderRow+1]
+
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(HeaderRow, i).
- MaxHeight(1).
- Width(t.widths[i]).
+ cellStyle := t.style(HeaderRow, i)
+
+ if !t.wrap {
+ header = t.truncateCell(header, HeaderRow, i)
+ }
+
+ s.WriteString(cellStyle.
+ Height(height - cellStyle.GetVerticalMargins()).
+ MaxHeight(height).
+ Width(t.widths[i] - cellStyle.GetHorizontalMargins()).
MaxWidth(t.widths[i]).
- Render(t.truncateCell(header, -1, i)))
+ Render(t.truncateCell(header, HeaderRow, i)))
if i < len(t.headers)-1 && t.borderColumn {
s.WriteString(t.borderStyle.Render(t.border.Left))
}
@@ -469,7 +478,7 @@ func (t *Table) constructRow(index int, isOverflow bool) string {
s.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, cells...) + "\n")
- if t.borderRow && index < t.data.Rows()-1 {
+ if t.borderRow && index < t.data.Rows()-1 && !isOverflow {
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])))
diff --git a/table/table_test.go b/table/table_test.go
index 75cc86fe..3957c5d1 100644
--- a/table/table_test.go
+++ b/table/table_test.go
@@ -92,6 +92,23 @@ func TestTableNoStyleFunc(t *testing.T) {
golden.RequireEqual(t, []byte(table.String()))
}
+func TestTableMarginAndRightAlignment(t *testing.T) {
+ table := New().
+ Border(lipgloss.NormalBorder()).
+ StyleFunc(func(row, col int) lipgloss.Style {
+ return lipgloss.NewStyle().Margin(0, 1).Align(lipgloss.Right)
+ }).
+ Headers("LANGUAGE", "FORMAL", "INFORMAL").
+ Row("Arabic", "أهلين", "أهلا").
+ Row("Chinese", "Nǐn hǎo", "Nǐ hǎo").
+ Row("French", "Bonjour", "Salut").
+ Row("Japanese", "こんにちは", "やあ").
+ Row("Russian", "Zdravstvuyte", "Privet").
+ Row("Spanish", "Hola", "¿Qué tal?")
+
+ golden.RequireEqual(t, []byte(table.String()))
+}
+
func TestTableOffset(t *testing.T) {
table := New().
Border(lipgloss.NormalBorder()).
@@ -319,14 +336,28 @@ func TestTableRowSeparators(t *testing.T) {
{"Spanish", "Hola", "¿Qué tal?"},
}
- table := New().
- Border(lipgloss.NormalBorder()).
- StyleFunc(TableStyle).
- BorderRow(true).
- Headers("LANGUAGE", "FORMAL", "INFORMAL").
- Rows(rows...)
+ t.Run("no overflow", func(t *testing.T) {
+ table := New().
+ Border(lipgloss.NormalBorder()).
+ StyleFunc(TableStyle).
+ BorderRow(true).
+ Headers("LANGUAGE", "FORMAL", "INFORMAL").
+ Rows(rows...)
- golden.RequireEqual(t, []byte(table.String()))
+ golden.RequireEqual(t, []byte(table.String()))
+ })
+
+ t.Run("with overflow", func(t *testing.T) {
+ table := New().
+ Border(lipgloss.NormalBorder()).
+ StyleFunc(TableStyle).
+ BorderRow(true).
+ Headers("LANGUAGE", "FORMAL", "INFORMAL").
+ Rows(rows...).
+ Height(8)
+
+ golden.RequireEqual(t, []byte(table.String()))
+ })
}
func TestTableHeights(t *testing.T) {
diff --git a/table/testdata/TestTableMarginAndRightAlignment.golden b/table/testdata/TestTableMarginAndRightAlignment.golden
new file mode 100644
index 00000000..f00102da
--- /dev/null
+++ b/table/testdata/TestTableMarginAndRightAlignment.golden
@@ -0,0 +1,10 @@
+┌──────────┬──────────────┬───────────┐
+│ LANGUAGE │ FORMAL │ INFORMAL │
+├──────────┼──────────────┼───────────┤
+│ Arabic │ أهلين │ أهلا │
+│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
+│ French │ Bonjour │ Salut │
+│ Japanese │ こんにちは │ やあ │
+│ Russian │ Zdravstvuyte │ Privet │
+│ Spanish │ Hola │ ¿Qué tal? │
+└──────────┴──────────────┴───────────┘
\ No newline at end of file
diff --git a/table/testdata/TestTableRowSeparators.golden b/table/testdata/TestTableRowSeparators/no_overflow.golden
similarity index 100%
rename from table/testdata/TestTableRowSeparators.golden
rename to table/testdata/TestTableRowSeparators/no_overflow.golden
diff --git a/table/testdata/TestTableRowSeparators/with_overflow.golden b/table/testdata/TestTableRowSeparators/with_overflow.golden
new file mode 100644
index 00000000..e0c519c9
--- /dev/null
+++ b/table/testdata/TestTableRowSeparators/with_overflow.golden
@@ -0,0 +1,11 @@
+┌──────────┬──────────────┬───────────┐
+│ LANGUAGE │ FORMAL │ INFORMAL │
+├──────────┼──────────────┼───────────┤
+│ Chinese │ Nǐn hǎo │ Nǐ hǎo │
+├──────────┼──────────────┼───────────┤
+│ French │ Bonjour │ Salut │
+├──────────┼──────────────┼───────────┤
+│ Japanese │ こんにちは │ やあ │
+├──────────┼──────────────┼───────────┤
+│ … │ … │ … │
+└──────────┴──────────────┴───────────┘
\ No newline at end of file
diff --git a/table/util.go b/table/util.go
index 74bcdffe..d139ca28 100644
--- a/table/util.go
+++ b/table/util.go
@@ -12,22 +12,6 @@ func btoi(b bool) int {
return 0
}
-// max returns the greater of two integers.
-func max(a, b int) int { //nolint:predeclared
- if a > b {
- return a
- }
- return b
-}
-
-// min returns the smaller of two integers.
-func min(a, b int) int { //nolint:predeclared
- 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
diff --git a/tree/renderer.go b/tree/renderer.go
index fea96fae..d6d4593f 100644
--- a/tree/renderer.go
+++ b/tree/renderer.go
@@ -138,10 +138,3 @@ func (r *renderer) render(node Node, root bool, prefix string) string {
}
return strings.Join(strs, "\n")
}
-
-func max(a, b int) int {
- if a > b {
- return a
- }
- return b
-}