summaryrefslogtreecommitdiff
path: root/vendor/github.com/charmbracelet/x/cellbuf/wrap.go
diff options
context:
space:
mode:
authormo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
committermo khan <mo@mokhan.ca>2025-07-22 17:35:49 -0600
commit20ef0d92694465ac86b550df139e8366a0a2b4fa (patch)
tree3f14589e1ce6eb9306a3af31c3a1f9e1af5ed637 /vendor/github.com/charmbracelet/x/cellbuf/wrap.go
parent44e0d272c040cdc53a98b9f1dc58ae7da67752e6 (diff)
feat: connect to spicedb
Diffstat (limited to 'vendor/github.com/charmbracelet/x/cellbuf/wrap.go')
-rw-r--r--vendor/github.com/charmbracelet/x/cellbuf/wrap.go178
1 files changed, 178 insertions, 0 deletions
diff --git a/vendor/github.com/charmbracelet/x/cellbuf/wrap.go b/vendor/github.com/charmbracelet/x/cellbuf/wrap.go
new file mode 100644
index 0000000..59a2a33
--- /dev/null
+++ b/vendor/github.com/charmbracelet/x/cellbuf/wrap.go
@@ -0,0 +1,178 @@
+package cellbuf
+
+import (
+ "bytes"
+ "unicode"
+ "unicode/utf8"
+
+ "github.com/charmbracelet/x/ansi"
+)
+
+// Wrap returns a string that is wrapped to the specified limit applying any
+// ANSI escape sequences in the string. It tries to wrap the string at word
+// boundaries, but will break words if necessary.
+//
+// The breakpoints string is a list of characters that are considered
+// breakpoints for word wrapping. A hyphen (-) is always considered a
+// breakpoint.
+//
+// Note: breakpoints must be a string of 1-cell wide rune characters.
+func Wrap(s string, limit int, breakpoints string) string {
+ if len(s) == 0 {
+ return ""
+ }
+
+ if limit < 1 {
+ return s
+ }
+
+ p := ansi.GetParser()
+ defer ansi.PutParser(p)
+
+ var (
+ buf bytes.Buffer
+ word bytes.Buffer
+ space bytes.Buffer
+ style, curStyle Style
+ link, curLink Link
+ curWidth int
+ wordLen int
+ )
+
+ addSpace := func() {
+ curWidth += space.Len()
+ buf.Write(space.Bytes())
+ space.Reset()
+ }
+
+ addWord := func() {
+ if word.Len() == 0 {
+ return
+ }
+
+ curLink = link
+ curStyle = style
+
+ addSpace()
+ curWidth += wordLen
+ buf.Write(word.Bytes())
+ word.Reset()
+ wordLen = 0
+ }
+
+ addNewline := func() {
+ if !curStyle.Empty() {
+ buf.WriteString(ansi.ResetStyle)
+ }
+ if !curLink.Empty() {
+ buf.WriteString(ansi.ResetHyperlink())
+ }
+ buf.WriteByte('\n')
+ if !curLink.Empty() {
+ buf.WriteString(ansi.SetHyperlink(curLink.URL, curLink.Params))
+ }
+ if !curStyle.Empty() {
+ buf.WriteString(curStyle.Sequence())
+ }
+ curWidth = 0
+ space.Reset()
+ }
+
+ var state byte
+ for len(s) > 0 {
+ seq, width, n, newState := ansi.DecodeSequence(s, state, p)
+ switch width {
+ case 0:
+ if ansi.Equal(seq, "\t") {
+ addWord()
+ space.WriteString(seq)
+ break
+ } else if ansi.Equal(seq, "\n") {
+ if wordLen == 0 {
+ if curWidth+space.Len() > limit {
+ curWidth = 0
+ } else {
+ // preserve whitespaces
+ buf.Write(space.Bytes())
+ }
+ space.Reset()
+ }
+
+ addWord()
+ addNewline()
+ break
+ } else if ansi.HasCsiPrefix(seq) && p.Command() == 'm' {
+ // SGR style sequence [ansi.SGR]
+ ReadStyle(p.Params(), &style)
+ } else if ansi.HasOscPrefix(seq) && p.Command() == 8 {
+ // Hyperlink sequence [ansi.SetHyperlink]
+ ReadLink(p.Data(), &link)
+ }
+
+ word.WriteString(seq)
+ default:
+ if len(seq) == 1 {
+ // ASCII
+ r, _ := utf8.DecodeRuneInString(seq)
+ if unicode.IsSpace(r) {
+ addWord()
+ space.WriteRune(r)
+ break
+ } else if r == '-' || runeContainsAny(r, breakpoints) {
+ addSpace()
+ if curWidth+wordLen+width <= limit {
+ addWord()
+ buf.WriteString(seq)
+ curWidth += width
+ break
+ }
+ }
+ }
+
+ if wordLen+width > limit {
+ // Hardwrap the word if it's too long
+ addWord()
+ }
+
+ word.WriteString(seq)
+ wordLen += width
+
+ if curWidth+wordLen+space.Len() > limit {
+ addNewline()
+ }
+ }
+
+ s = s[n:]
+ state = newState
+ }
+
+ if wordLen == 0 {
+ if curWidth+space.Len() > limit {
+ curWidth = 0
+ } else {
+ // preserve whitespaces
+ buf.Write(space.Bytes())
+ }
+ space.Reset()
+ }
+
+ addWord()
+
+ if !curLink.Empty() {
+ buf.WriteString(ansi.ResetHyperlink())
+ }
+ if !curStyle.Empty() {
+ buf.WriteString(ansi.ResetStyle)
+ }
+
+ return buf.String()
+}
+
+func runeContainsAny[T string | []rune](r rune, s T) bool {
+ for _, c := range []rune(s) {
+ if c == r {
+ return true
+ }
+ }
+ return false
+}