diff --git a/cmd/spilltea/main.go b/cmd/spilltea/main.go index 251b16c..bafb6ff 100644 --- a/cmd/spilltea/main.go +++ b/cmd/spilltea/main.go @@ -44,7 +44,8 @@ func main() { ) flag.CommandLine.SetOutput(os.Stdout) flag.Usage = func() { - fmt.Println("Usage: spilltea [flags]\n") + fmt.Println("Usage: spilltea [flags]") + fmt.Println("") flag.PrintDefaults() } flag.Parse() @@ -121,7 +122,7 @@ func main() { config.Global.App.UpstreamProxy = *flagUpstreamProxy } - style.Init(config.Global) + style.Init() icons.Init(config.Global) keys.Init(config.Global) diff --git a/go.mod b/go.mod index 4617709..f4e6033 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,10 @@ require ( charm.land/bubbletea/v2 v2.0.6 charm.land/glamour/v2 v2.0.0 charm.land/lipgloss/v2 v2.0.3 + github.com/andybalholm/brotli v1.0.4 + github.com/anotherhadi/ilovetui v0.1.4 github.com/charmbracelet/x/ansi v0.11.7 + github.com/klauspost/compress v1.17.8 github.com/lqqyt2423/go-mitmproxy v1.8.11 github.com/sirupsen/logrus v1.9.4 github.com/spf13/pflag v1.0.10 @@ -20,7 +23,6 @@ require ( require ( github.com/alecthomas/chroma/v2 v2.24.1 // indirect - github.com/andybalholm/brotli v1.0.4 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect @@ -39,7 +41,6 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-runewidth v0.0.23 // indirect diff --git a/go.sum b/go.sum index fdc819e..c691d5b 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/anotherhadi/ilovetui v0.1.4 h1:2sLAm3rJxg7/NrrTyH+Tk+kbIq+SvkBvrI7OvffvDcw= +github.com/anotherhadi/ilovetui v0.1.4/go.mod h1:ZjSCOM/wev1wAz235/Z/54h/6mRu6WkNhRaXxUhe2hw= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o= diff --git a/internal/config/colors.go b/internal/config/colors.go deleted file mode 100644 index bd1cdac..0000000 --- a/internal/config/colors.go +++ /dev/null @@ -1,20 +0,0 @@ -package config - -type Colors struct { - Base00 string `mapstructure:"base00"` - Base01 string `mapstructure:"base01"` - Base02 string `mapstructure:"base02"` - Base03 string `mapstructure:"base03"` - Base04 string `mapstructure:"base04"` - Base05 string `mapstructure:"base05"` - Base06 string `mapstructure:"base06"` - Base07 string `mapstructure:"base07"` - Base08 string `mapstructure:"base08"` - Base09 string `mapstructure:"base09"` - Base0A string `mapstructure:"base0a"` - Base0B string `mapstructure:"base0b"` - Base0C string `mapstructure:"base0c"` - Base0D string `mapstructure:"base0d"` - Base0E string `mapstructure:"base0e"` - Base0F string `mapstructure:"base0f"` -} diff --git a/internal/config/config.go b/internal/config/config.go index 1c74d8f..7066ada 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -31,7 +31,6 @@ type Config struct { } `mapstructure:"app"` TUI struct { - Colors Colors `mapstructure:"colors"` UseNerdfontIcons bool `mapstructure:"use_nerdfont_icons"` DefaultSidebarState string `mapstructure:"default_sidebar_state"` PrettyPrintBody bool `mapstructure:"pretty_print_body"` diff --git a/internal/config/default_config.yaml b/internal/config/default_config.yaml index 36ae8d4..0f570ce 100644 --- a/internal/config/default_config.yaml +++ b/internal/config/default_config.yaml @@ -27,23 +27,6 @@ tui: use_nerdfont_icons: false default_sidebar_state: "expanded" # hidden, collapsed, expanded pretty_print_body: true # auto-indent JSON and HTML response bodies - colors: - base00: "110F12" # Default Background - base01: "1C1920" # Lighter Background (status bars, line numbers) - base02: "1D1A26" # Selection Background - base03: "514D63" # Comments, Invisibles, faint text - base04: "8E8AA0" # Dark Foreground (status bars) - base05: "C2BED6" # Default Foreground, Caret, Delimiters - base06: "D8D5EA" # Light Foreground (rarely used) - base07: "EAE7F7" # Light Background (rarely used) - base08: "E07080" # Red: errors, diff deleted - base09: "D49070" # Orange: integers, constants, warnings - base0a: "C4B060" # Yellow: classes, search highlight - base0b: "80B880" # Green: strings, diff inserted, success - base0c: "70B8C0" # Cyan: support, regex, escape chars - base0d: "9E97F8" # Blue/Accent: functions, headings, primary - base0e: "C090E8" # Purple: keywords, storage - base0f: "D080A0" # Pink: deprecated, embedded language tags keybindings: global: diff --git a/internal/style/border.go b/internal/style/border.go deleted file mode 100644 index 6abeb96..0000000 --- a/internal/style/border.go +++ /dev/null @@ -1,43 +0,0 @@ -package style - -import ( - "strings" - - "charm.land/lipgloss/v2" -) - -// PanelContentH returns the usable inner content height for a panel rendered by -// RenderWithTitle. It subtracts the two border lines (top + bottom) from the -// total panel height. -func PanelContentH(totalH int) int { - h := totalH - 2 - if h < 0 { - return 0 - } - return h -} - -// RenderWithTitle renders a lipgloss bordered box with a title embedded in the -// top border, matching the border's own foreground color. height is the total -// desired output height (including both border lines). -func RenderWithTitle(border lipgloss.Style, title, content string, width, height int) string { - boxH := height - 1 - if contentH := boxH - 1; contentH > 0 { - lines := strings.Split(content, "\n") - if len(lines) > contentH { - content = strings.Join(lines[:contentH], "\n") - } - } - box := border.BorderTop(false).Width(width).Height(boxH).Render(content) - - boxWidth := lipgloss.Width(strings.SplitN(box, "\n", 2)[0]) - label := " " + title + " " - fillW := boxWidth - lipgloss.Width(label) - 2 - if fillW < 0 { - fillW = 0 - } - bc := lipgloss.NewStyle().Foreground(border.GetBorderTopForeground()) - topLine := bc.Render("╭ ") + bc.Render(title) + bc.Render(" "+strings.Repeat("─", fillW)+"╮") - - return lipgloss.JoinVertical(lipgloss.Left, topLine, box) -} diff --git a/internal/style/components.go b/internal/style/components.go index 4508833..2f10345 100644 --- a/internal/style/components.go +++ b/internal/style/components.go @@ -7,6 +7,7 @@ import ( "charm.land/bubbles/v2/textarea" "charm.land/bubbles/v2/viewport" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" ) func NewViewport() viewport.Model { @@ -24,7 +25,7 @@ func ViewportView(vp *viewport.Model) string { if len(lines) == 0 { return v } - arrow := lipgloss.NewStyle().Foreground(S.Subtle).Render("↓") + arrow := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).Render("↓") arrowW := lipgloss.Width(arrow) inner := vp.Width() - 2*arrowW if inner < 0 { @@ -51,16 +52,16 @@ func NewTextarea(showLineNumbers bool) textarea.Model { ts := ta.Styles() ts.Focused.Base = lipgloss.NewStyle() ts.Blurred.Base = lipgloss.NewStyle() - ts.Focused.Text = lipgloss.NewStyle().Foreground(S.Text) - ts.Focused.CursorLine = lipgloss.NewStyle().Background(S.Selection).Foreground(S.Text) - ts.Focused.CursorLineNumber = lipgloss.NewStyle().Background(S.Selection).Foreground(S.Primary).Bold(true) - ts.Focused.LineNumber = lipgloss.NewStyle().Foreground(S.Subtle) - ts.Focused.Placeholder = lipgloss.NewStyle().Foreground(S.Subtle) - ts.Focused.EndOfBuffer = lipgloss.NewStyle().Foreground(S.SubtleBg) - ts.Blurred.Text = lipgloss.NewStyle().Foreground(S.MutedFg) - ts.Blurred.LineNumber = lipgloss.NewStyle().Foreground(S.SubtleBg) - ts.Blurred.Placeholder = lipgloss.NewStyle().Foreground(S.Subtle) - ts.Blurred.EndOfBuffer = lipgloss.NewStyle().Foreground(S.SubtleBg) + ts.Focused.Text = lipgloss.NewStyle().Foreground(ilovetui.S.Text) + ts.Focused.CursorLine = lipgloss.NewStyle().Background(ilovetui.S.Selection).Foreground(ilovetui.S.Text) + ts.Focused.CursorLineNumber = lipgloss.NewStyle().Background(ilovetui.S.Selection).Foreground(ilovetui.S.Primary).Bold(true) + ts.Focused.LineNumber = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) + ts.Focused.Placeholder = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) + ts.Focused.EndOfBuffer = lipgloss.NewStyle().Foreground(ilovetui.S.SubtleBg) + ts.Blurred.Text = lipgloss.NewStyle().Foreground(ilovetui.S.Muted) + ts.Blurred.LineNumber = lipgloss.NewStyle().Foreground(ilovetui.S.SubtleBg) + ts.Blurred.Placeholder = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) + ts.Blurred.EndOfBuffer = lipgloss.NewStyle().Foreground(ilovetui.S.SubtleBg) ta.SetStyles(ts) return ta } @@ -69,15 +70,15 @@ func SeverityStyle(sev string) lipgloss.Style { base := lipgloss.NewStyle().Bold(true) switch sev { case "critical": - return base.Foreground(S.Error) + return base.Foreground(ilovetui.S.Error) case "high": - return base.Foreground(S.Warning) + return base.Foreground(ilovetui.S.Warning) case "medium": - return base.Foreground(S.Primary) + return base.Foreground(ilovetui.S.Primary) case "low": - return base.Foreground(S.Success) + return base.Foreground(ilovetui.S.Success) default: - return base.Foreground(S.Subtle) + return base.Foreground(ilovetui.S.Subtle) } } @@ -85,13 +86,13 @@ func StatusStyle(code, width int) lipgloss.Style { base := lipgloss.NewStyle().Bold(true).Width(width) switch { case code >= 500: - return base.Foreground(S.Error) + return base.Foreground(ilovetui.S.Error) case code >= 400: - return base.Foreground(S.Warning) + return base.Foreground(ilovetui.S.Warning) case code >= 300: - return base.Foreground(S.Primary) + return base.Foreground(ilovetui.S.Primary) default: - return base.Foreground(S.Success) + return base.Foreground(ilovetui.S.Success) } } diff --git a/internal/style/glamour.go b/internal/style/glamour.go deleted file mode 100644 index 0c07dcb..0000000 --- a/internal/style/glamour.go +++ /dev/null @@ -1,236 +0,0 @@ -package style - -import ( - "github.com/anotherhadi/spilltea/internal/config" - - "charm.land/glamour/v2/ansi" -) - -func GlamourStyleConfig(cfg *config.Config) ansi.StyleConfig { - c := cfg.TUI.Colors - - str := func(s string) *string { return &s } - hex := func(base string) *string { return str("#" + base) } - boolPtr := func(b bool) *bool { return &b } - uintPtr := func(u uint) *uint { return &u } - - return ansi.StyleConfig{ - Document: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - BlockPrefix: "\n", - BlockSuffix: "\n", - Color: hex(c.Base05), - }, - Margin: uintPtr(2), - }, - BlockQuote: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: hex(c.Base03), - Italic: boolPtr(true), - }, - Indent: uintPtr(1), - IndentToken: str("│ "), - }, - List: ansi.StyleList{ - LevelIndent: 2, - }, - Heading: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - BlockSuffix: "\n", - Color: hex(c.Base0D), - Bold: boolPtr(true), - }, - }, - H1: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: " ", - Suffix: " ", - Color: hex(c.Base07), - BackgroundColor: hex(c.Base0D), - Bold: boolPtr(true), - }, - }, - H2: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "## ", - Color: hex(c.Base0D), - Bold: boolPtr(true), - }, - }, - H3: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "### ", - Color: hex(c.Base0C), - }, - }, - H4: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "#### ", - Color: hex(c.Base0B), - }, - }, - H5: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "##### ", - Color: hex(c.Base09), - }, - }, - H6: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: "###### ", - Color: hex(c.Base08), - Bold: boolPtr(false), - }, - }, - Strikethrough: ansi.StylePrimitive{ - CrossedOut: boolPtr(true), - }, - Emph: ansi.StylePrimitive{ - Italic: boolPtr(true), - }, - Strong: ansi.StylePrimitive{ - Bold: boolPtr(true), - }, - HorizontalRule: ansi.StylePrimitive{ - Color: hex(c.Base03), - Format: "\n--------\n", - }, - Item: ansi.StylePrimitive{ - BlockPrefix: "• ", - }, - Enumeration: ansi.StylePrimitive{ - BlockPrefix: ". ", - }, - Task: ansi.StyleTask{ - Ticked: "[✓] ", - Unticked: "[ ] ", - }, - Link: ansi.StylePrimitive{ - Color: hex(c.Base0C), - Underline: boolPtr(true), - }, - LinkText: ansi.StylePrimitive{ - Color: hex(c.Base0D), - Bold: boolPtr(true), - }, - Image: ansi.StylePrimitive{ - Color: hex(c.Base0C), - Underline: boolPtr(true), - }, - ImageText: ansi.StylePrimitive{ - Color: hex(c.Base04), - Format: "Image: {{.text}} ->", - }, - Code: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Prefix: " ", - Suffix: " ", - Color: hex(c.Base0B), - BackgroundColor: hex(c.Base01), - }, - }, - CodeBlock: ansi.StyleCodeBlock{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{ - Color: hex(c.Base04), - }, - Margin: uintPtr(2), - }, - Chroma: &ansi.Chroma{ - Text: ansi.StylePrimitive{ - Color: hex(c.Base05), - }, - Error: ansi.StylePrimitive{ - Color: hex(c.Base07), - BackgroundColor: hex(c.Base08), - }, - Comment: ansi.StylePrimitive{ - Color: hex(c.Base03), - Italic: boolPtr(true), - }, - CommentPreproc: ansi.StylePrimitive{ - Color: hex(c.Base09), - }, - Keyword: ansi.StylePrimitive{ - Color: hex(c.Base0E), - }, - KeywordReserved: ansi.StylePrimitive{ - Color: hex(c.Base0E), - }, - KeywordNamespace: ansi.StylePrimitive{ - Color: hex(c.Base0D), - }, - KeywordType: ansi.StylePrimitive{ - Color: hex(c.Base0A), - }, - Operator: ansi.StylePrimitive{ - Color: hex(c.Base05), - }, - Punctuation: ansi.StylePrimitive{ - Color: hex(c.Base05), - }, - Name: ansi.StylePrimitive{ - Color: hex(c.Base05), - }, - NameBuiltin: ansi.StylePrimitive{ - Color: hex(c.Base0D), - }, - NameTag: ansi.StylePrimitive{ - Color: hex(c.Base08), - }, - NameAttribute: ansi.StylePrimitive{ - Color: hex(c.Base0A), - }, - NameClass: ansi.StylePrimitive{ - Color: hex(c.Base0A), - Bold: boolPtr(true), - Underline: boolPtr(true), - }, - NameConstant: ansi.StylePrimitive{ - Color: hex(c.Base09), - }, - NameDecorator: ansi.StylePrimitive{ - Color: hex(c.Base0C), - }, - NameFunction: ansi.StylePrimitive{ - Color: hex(c.Base0D), - }, - LiteralNumber: ansi.StylePrimitive{ - Color: hex(c.Base09), - }, - LiteralString: ansi.StylePrimitive{ - Color: hex(c.Base0B), - }, - LiteralStringEscape: ansi.StylePrimitive{ - Color: hex(c.Base0C), - }, - GenericDeleted: ansi.StylePrimitive{ - Color: hex(c.Base08), - }, - GenericEmph: ansi.StylePrimitive{ - Italic: boolPtr(true), - }, - GenericInserted: ansi.StylePrimitive{ - Color: hex(c.Base0B), - }, - GenericStrong: ansi.StylePrimitive{ - Bold: boolPtr(true), - }, - GenericSubheading: ansi.StylePrimitive{ - Color: hex(c.Base04), - }, - Background: ansi.StylePrimitive{ - BackgroundColor: hex(c.Base01), - }, - }, - }, - Table: ansi.StyleTable{ - StyleBlock: ansi.StyleBlock{ - StylePrimitive: ansi.StylePrimitive{}, - }, - }, - DefinitionDescription: ansi.StylePrimitive{ - BlockPrefix: "\n> ", - }, - } -} diff --git a/internal/style/highlight.go b/internal/style/highlight.go index d746291..6dee535 100644 --- a/internal/style/highlight.go +++ b/internal/style/highlight.go @@ -3,12 +3,13 @@ package style import ( "bytes" "encoding/json" - "image/color" "strings" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/config" "golang.org/x/net/html" + "image/color" ) func Paint(c color.Color, s string) string { @@ -75,8 +76,8 @@ func highlightHeaders(raw string) string { } else if trimmed == "" { out.WriteString(line) } else if idx := strings.Index(trimmed, ": "); idx != -1 { - out.WriteString(Paint(S.Subtle, trimmed[:idx+2])) - out.WriteString(Paint(S.Text, trimmed[idx+2:])) + out.WriteString(Paint(ilovetui.S.Subtle, trimmed[:idx+2])) + out.WriteString(Paint(ilovetui.S.Text, trimmed[idx+2:])) } else { out.WriteString(line) } @@ -95,16 +96,16 @@ func highlightStatusLine(line string) string { switch parts[0] { case "GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS", "CONNECT", "TRACE": result := S.Method(parts[0]).Width(0).Render(parts[0]) + " " - result += Paint(S.Primary, parts[1]) + result += Paint(ilovetui.S.Primary, parts[1]) if len(parts) == 3 { - result += " " + Paint(S.Subtle, parts[2]) + result += " " + Paint(ilovetui.S.Subtle, parts[2]) } return result } - result := Paint(S.Subtle, parts[0]) + " " - result += Paint(S.Warning, parts[1]) + result := Paint(ilovetui.S.Subtle, parts[0]) + " " + result += Paint(ilovetui.S.Warning, parts[1]) if len(parts) == 3 { - result += " " + Paint(S.MutedFg, parts[2]) + result += " " + Paint(ilovetui.S.Muted, parts[2]) } return result } @@ -134,9 +135,9 @@ func highlightJSON(s string) string { k++ } if k < n && s[k] == ':' { - out.WriteString(Paint(S.Primary, str)) + out.WriteString(Paint(ilovetui.S.Primary, str)) } else { - out.WriteString(Paint(S.Success, str)) + out.WriteString(Paint(ilovetui.S.Success, str)) } i = j case (ch >= '0' && ch <= '9') || (ch == '-' && i+1 < n && s[i+1] >= '0' && s[i+1] <= '9'): @@ -147,19 +148,19 @@ func highlightJSON(s string) string { for j < n && ((s[j] >= '0' && s[j] <= '9') || s[j] == '.' || s[j] == 'e' || s[j] == 'E' || s[j] == '+' || s[j] == '-') { j++ } - out.WriteString(Paint(S.Warning, s[i:j])) + out.WriteString(Paint(ilovetui.S.Warning, s[i:j])) i = j case i+4 <= n && s[i:i+4] == "true": - out.WriteString(Paint(S.Error, "true")) + out.WriteString(Paint(ilovetui.S.Error, "true")) i += 4 case i+5 <= n && s[i:i+5] == "false": - out.WriteString(Paint(S.Error, "false")) + out.WriteString(Paint(ilovetui.S.Error, "false")) i += 5 case i+4 <= n && s[i:i+4] == "null": - out.WriteString(Paint(S.Error, "null")) + out.WriteString(Paint(ilovetui.S.Error, "null")) i += 4 case ch == '{' || ch == '}' || ch == '[' || ch == ']' || ch == ':' || ch == ',': - out.WriteString(Paint(S.Subtle, string(ch))) + out.WriteString(Paint(ilovetui.S.Subtle, string(ch))) i++ default: out.WriteByte(ch) @@ -265,11 +266,11 @@ func highlightHTML(s string) string { if i+4 <= n && s[i:i+4] == "") if end == -1 { - out.WriteString(Paint(S.Subtle, s[i:])) + out.WriteString(Paint(ilovetui.S.Subtle, s[i:])) break } end = i + end + 3 - out.WriteString(Paint(S.Subtle, s[i:end])) + out.WriteString(Paint(ilovetui.S.Subtle, s[i:end])) i = end continue } @@ -278,10 +279,10 @@ func highlightHTML(s string) string { i++ continue } - out.WriteString(Paint(S.Subtle, "<")) + out.WriteString(Paint(ilovetui.S.Subtle, "<")) i++ if i < n && (s[i] == '/' || s[i] == '!') { - out.WriteString(Paint(S.Subtle, string(s[i]))) + out.WriteString(Paint(ilovetui.S.Subtle, string(s[i]))) i++ } j := i @@ -289,7 +290,7 @@ func highlightHTML(s string) string { j++ } if j > i { - out.WriteString(Paint(S.Primary, s[i:j])) + out.WriteString(Paint(ilovetui.S.Primary, s[i:j])) i = j } for i < n && s[i] != '>' { @@ -299,10 +300,10 @@ func highlightHTML(s string) string { out.WriteByte(ch) i++ case ch == '/': - out.WriteString(Paint(S.Subtle, "/")) + out.WriteString(Paint(ilovetui.S.Subtle, "/")) i++ case ch == '=': - out.WriteString(Paint(S.Subtle, "=")) + out.WriteString(Paint(ilovetui.S.Subtle, "=")) i++ case ch == '"' || ch == '\'': q := ch @@ -313,19 +314,19 @@ func highlightHTML(s string) string { if j < n { j++ } - out.WriteString(Paint(S.Success, s[i:j])) + out.WriteString(Paint(ilovetui.S.Success, s[i:j])) i = j default: j = i for j < n && s[j] != '=' && s[j] != ' ' && s[j] != '>' && s[j] != '/' && s[j] != '\t' && s[j] != '\n' { j++ } - out.WriteString(Paint(S.Warning, s[i:j])) + out.WriteString(Paint(ilovetui.S.Warning, s[i:j])) i = j } } if i < n && s[i] == '>' { - out.WriteString(Paint(S.Subtle, ">")) + out.WriteString(Paint(ilovetui.S.Subtle, ">")) i++ } } diff --git a/internal/style/style.go b/internal/style/style.go index 2672e52..3006d46 100644 --- a/internal/style/style.go +++ b/internal/style/style.go @@ -1,29 +1,12 @@ package style import ( - "image/color" - "charm.land/bubbles/v2/help" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/config" + ilovetui "github.com/anotherhadi/ilovetui" ) type Styles struct { - Primary color.Color - Success color.Color - Error color.Color - Warning color.Color - SubtleBg color.Color - Selection color.Color - Text color.Color - MutedFg color.Color - Subtle color.Color - - Bold lipgloss.Style - Faint lipgloss.Style - - Panel lipgloss.Style - PanelFocused lipgloss.Style PanelEditing lipgloss.Style PagerDotActive string @@ -38,67 +21,33 @@ type Styles struct { var S *Styles -func Init(cfg *config.Config) { - c := cfg.TUI.Colors - - subtleBg := lipgloss.Color("#" + c.Base01) // Lighter Background (status bars) - selection := lipgloss.Color("#" + c.Base02) // Selection Background - subtle := lipgloss.Color("#" + c.Base03) // Faint text, borders - mutedFg := lipgloss.Color("#" + c.Base04) // Muted foreground - text := lipgloss.Color("#" + c.Base05) // Default Foreground - errCol := lipgloss.Color("#" + c.Base08) // Red: errors - warning := lipgloss.Color("#" + c.Base09) // Orange: warnings - success := lipgloss.Color("#" + c.Base0B) // Green: success - primary := lipgloss.Color("#" + c.Base0D) // Accent: primary - purple := lipgloss.Color("#" + c.Base0E) // Purple: editing - +func Init() { methodBase := lipgloss.NewStyle().Bold(true).Width(7) S = &Styles{ - Primary: primary, - Success: success, - Error: errCol, - Warning: warning, - SubtleBg: subtleBg, - Selection: selection, - MutedFg: mutedFg, - Text: text, - Subtle: subtle, - - Bold: lipgloss.NewStyle().Bold(true), - Faint: lipgloss.NewStyle().Foreground(subtle).Faint(true), - - Panel: lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(subtle), - - PanelFocused: lipgloss.NewStyle(). - Border(lipgloss.RoundedBorder()). - BorderForeground(primary), - PanelEditing: lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(purple), + BorderForeground(ilovetui.S.Base0E), - PagerDotActive: lipgloss.NewStyle().Foreground(primary).SetString("•").String(), - PagerDotInactive: lipgloss.NewStyle().Foreground(subtle).SetString("•").String(), + PagerDotActive: lipgloss.NewStyle().Foreground(ilovetui.S.Primary).SetString("•").String(), + PagerDotInactive: lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).SetString("•").String(), - methodGet: methodBase.Foreground(success), - methodPost: methodBase.Foreground(warning), - methodPutPatch: methodBase.Foreground(primary), - methodDelete: methodBase.Foreground(errCol), - methodDefault: methodBase.Foreground(text), + methodGet: methodBase.Foreground(ilovetui.S.Success), + methodPost: methodBase.Foreground(ilovetui.S.Warning), + methodPutPatch: methodBase.Foreground(ilovetui.S.Primary), + methodDelete: methodBase.Foreground(ilovetui.S.Error), + methodDefault: methodBase.Foreground(ilovetui.S.Text), } } func NewHelp() help.Model { h := help.New() - h.Styles.ShortKey = lipgloss.NewStyle().Foreground(S.Primary) - h.Styles.ShortDesc = lipgloss.NewStyle().Foreground(S.MutedFg) - h.Styles.ShortSeparator = lipgloss.NewStyle().Foreground(S.Subtle) - h.Styles.FullKey = lipgloss.NewStyle().Foreground(S.Primary) - h.Styles.FullDesc = lipgloss.NewStyle().Foreground(S.MutedFg) - h.Styles.FullSeparator = lipgloss.NewStyle().Foreground(S.Subtle) - h.Styles.Ellipsis = lipgloss.NewStyle().Foreground(S.Subtle) + h.Styles.ShortKey = lipgloss.NewStyle().Foreground(ilovetui.S.Primary) + h.Styles.ShortDesc = lipgloss.NewStyle().Foreground(ilovetui.S.Muted) + h.Styles.ShortSeparator = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) + h.Styles.FullKey = lipgloss.NewStyle().Foreground(ilovetui.S.Primary) + h.Styles.FullDesc = lipgloss.NewStyle().Foreground(ilovetui.S.Muted) + h.Styles.FullSeparator = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) + h.Styles.Ellipsis = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) return h } diff --git a/internal/ui/app/sidebar.go b/internal/ui/app/sidebar.go index 139ce72..0dff550 100644 --- a/internal/ui/app/sidebar.go +++ b/internal/ui/app/sidebar.go @@ -5,7 +5,7 @@ import ( "strings" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" ) type sidebarState string @@ -42,7 +42,6 @@ func (m *Model) renderSidebar() string { if m.sidebarState == sidebarHidden { return "" } - s := style.S // content width inside bordered panel inner := m.getSidebarWidth() - 2 @@ -50,18 +49,18 @@ func (m *Model) renderSidebar() string { if m.sidebarState == sidebarCollapsed { titleText = "SPLT" } - title := lipgloss.NewStyle().Width(inner).Bold(true).Foreground(s.Primary).Padding(0, 1).Render(titleText) + title := lipgloss.NewStyle().Width(inner).Bold(true).Foreground(ilovetui.S.Primary).Padding(0, 1).Render(titleText) divider := strings.Repeat("─", inner) - badgeSelected := lipgloss.NewStyle().Foreground(s.Primary).Bold(true) - badgeNormal := lipgloss.NewStyle().Foreground(s.Subtle) - textSelected := lipgloss.NewStyle().Foreground(s.Primary) - textNormal := lipgloss.NewStyle().Foreground(s.Text) + badgeSelected := lipgloss.NewStyle().Foreground(ilovetui.S.Primary).Bold(true) + badgeNormal := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) + textSelected := lipgloss.NewStyle().Foreground(ilovetui.S.Primary) + textNormal := lipgloss.NewStyle().Foreground(ilovetui.S.Text) lineStyle := lipgloss.NewStyle().Width(inner).Padding(0, 1) var items strings.Builder - badgeUnread := lipgloss.NewStyle().Foreground(s.Warning).Bold(true) + badgeUnread := lipgloss.NewStyle().Foreground(ilovetui.S.Warning).Bold(true) for i, entry := range sidebarEntries { selected := entry.id == m.page @@ -95,13 +94,13 @@ func (m *Model) renderSidebar() string { } parts := []string{ title, - lipgloss.NewStyle().Width(inner).Foreground(s.Subtle).Padding(0, 1).Render(name), + lipgloss.NewStyle().Width(inner).Foreground(ilovetui.S.Subtle).Padding(0, 1).Render(name), } parts = append(parts, - lipgloss.NewStyle().Foreground(s.Subtle).Render(divider), + lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).Render(divider), items.String(), ) body := lipgloss.JoinVertical(lipgloss.Left, parts...) - return s.Panel.Width(m.getSidebarWidth()).Height(m.height).Render(body) + return ilovetui.S.Panel.Width(m.getSidebarWidth()).Height(m.height).Render(body) } diff --git a/internal/ui/app/view.go b/internal/ui/app/view.go index 48a44cd..e4b4003 100644 --- a/internal/ui/app/view.go +++ b/internal/ui/app/view.go @@ -3,7 +3,7 @@ package app import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" ) func (m Model) View() tea.View { @@ -52,5 +52,5 @@ func (m *Model) renderActivePage() string { return e.render(m) } } - return style.S.Faint.Render("Work in progress") + return ilovetui.S.Faint.Render("Work in progress") } diff --git a/internal/ui/components/copy/model.go b/internal/ui/components/copy/model.go index af5d61e..9e3e4b0 100644 --- a/internal/ui/components/copy/model.go +++ b/internal/ui/components/copy/model.go @@ -6,7 +6,7 @@ import ( "charm.land/bubbles/v2/list" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" ) const ( @@ -47,20 +47,18 @@ type Model struct { } func New() Model { - s := style.S - delegate := list.NewDefaultDelegate() delegate.SetSpacing(0) - delegate.Styles.NormalTitle = lipgloss.NewStyle().Foreground(s.Text).PaddingLeft(2) - delegate.Styles.NormalDesc = lipgloss.NewStyle().Foreground(s.Subtle).PaddingLeft(2) + delegate.Styles.NormalTitle = lipgloss.NewStyle().Foreground(ilovetui.S.Text).PaddingLeft(2) + delegate.Styles.NormalDesc = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).PaddingLeft(2) delegate.Styles.SelectedTitle = lipgloss.NewStyle(). Border(lipgloss.NormalBorder(), false, false, false, true). - BorderForeground(s.Primary). - Foreground(s.Primary).Bold(true).PaddingLeft(1) + BorderForeground(ilovetui.S.Primary). + Foreground(ilovetui.S.Primary).Bold(true).PaddingLeft(1) delegate.Styles.SelectedDesc = lipgloss.NewStyle(). Border(lipgloss.NormalBorder(), false, false, false, true). - BorderForeground(s.Primary). - Foreground(s.MutedFg).PaddingLeft(1) + BorderForeground(ilovetui.S.Primary). + Foreground(ilovetui.S.Muted).PaddingLeft(1) l := list.New(allItems, delegate, popupW, 8) l.SetShowTitle(false) @@ -125,7 +123,7 @@ func (m Model) popupHeight() int { } func (m Model) listHeight() int { - return style.PanelContentH(m.popupHeight()) - 1 + return ilovetui.ContentHeight(m.popupHeight()) - 1 } func (m Model) extract(id string) string { diff --git a/internal/ui/components/copy/view.go b/internal/ui/components/copy/view.go index f96b1eb..537efb9 100644 --- a/internal/ui/components/copy/view.go +++ b/internal/ui/components/copy/view.go @@ -2,14 +2,12 @@ package copy import ( "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" copyasUI "github.com/anotherhadi/spilltea/internal/ui/components/copyas" ) func (m *Model) View(background string) string { - s := style.S - - hint := lipgloss.NewStyle().Foreground(s.Subtle). + hint := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle). Render(" enter: copy • /: filter • esc: cancel") inner := lipgloss.JoinVertical(lipgloss.Left, @@ -19,10 +17,10 @@ func (m *Model) View(background string) string { border := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(s.Primary) + BorderForeground(ilovetui.S.Primary) popupH := m.popupHeight() - popup := style.RenderWithTitle(border, "Copy", inner, m.popupInnerWidth()+2, popupH) + popup := ilovetui.RenderWithTitle(border, "Copy", inner, m.popupInnerWidth()+2, popupH) return copyasUI.OverlayCenter(background, popup, m.width, m.height) } diff --git a/internal/ui/components/copyas/model.go b/internal/ui/components/copyas/model.go index 8e0f0f3..bbc86cd 100644 --- a/internal/ui/components/copyas/model.go +++ b/internal/ui/components/copyas/model.go @@ -4,7 +4,7 @@ import ( "charm.land/bubbles/v2/list" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" ) const ( @@ -48,20 +48,18 @@ type Model struct { } func New() Model { - s := style.S - delegate := list.NewDefaultDelegate() delegate.SetSpacing(0) - delegate.Styles.NormalTitle = lipgloss.NewStyle().Foreground(s.Text).PaddingLeft(2) - delegate.Styles.NormalDesc = lipgloss.NewStyle().Foreground(s.Subtle).PaddingLeft(2) + delegate.Styles.NormalTitle = lipgloss.NewStyle().Foreground(ilovetui.S.Text).PaddingLeft(2) + delegate.Styles.NormalDesc = lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).PaddingLeft(2) delegate.Styles.SelectedTitle = lipgloss.NewStyle(). Border(lipgloss.NormalBorder(), false, false, false, true). - BorderForeground(s.Primary). - Foreground(s.Primary).Bold(true).PaddingLeft(1) + BorderForeground(ilovetui.S.Primary). + Foreground(ilovetui.S.Primary).Bold(true).PaddingLeft(1) delegate.Styles.SelectedDesc = lipgloss.NewStyle(). Border(lipgloss.NormalBorder(), false, false, false, true). - BorderForeground(s.Primary). - Foreground(s.MutedFg).PaddingLeft(1) + BorderForeground(ilovetui.S.Primary). + Foreground(ilovetui.S.Muted).PaddingLeft(1) l := list.New(allFormats, delegate, popupW, 8) l.SetShowTitle(false) @@ -116,5 +114,5 @@ func (m Model) popupHeight() int { // listHeight = panel content area - hint line (1) func (m Model) listHeight() int { - return style.PanelContentH(m.popupHeight()) - 1 + return ilovetui.ContentHeight(m.popupHeight()) - 1 } diff --git a/internal/ui/components/copyas/view.go b/internal/ui/components/copyas/view.go index bd211e3..773f400 100644 --- a/internal/ui/components/copyas/view.go +++ b/internal/ui/components/copyas/view.go @@ -4,14 +4,12 @@ import ( "strings" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/charmbracelet/x/ansi" ) func (m *Model) View(background string) string { - s := style.S - - hint := lipgloss.NewStyle().Foreground(s.Subtle). + hint := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle). Render(" enter: copy • /: filter • esc: cancel") inner := lipgloss.JoinVertical(lipgloss.Left, @@ -21,17 +19,15 @@ func (m *Model) View(background string) string { border := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(s.Primary) + BorderForeground(ilovetui.S.Primary) popupH := m.popupHeight() - popup := style.RenderWithTitle(border, "Copy as", inner, m.popupInnerWidth()+2, popupH) + popup := ilovetui.RenderWithTitle(border, "Copy as", inner, m.popupInnerWidth()+2, popupH) return OverlayCenter(background, popup, m.width, m.height) } func OverlayCenter(bg, popup string, w, h int) string { - s := style.S - stripped := ansi.Strip(bg) rawLines := strings.Split(stripped, "\n") bgRunes := make([][]rune, h) @@ -67,7 +63,7 @@ func OverlayCenter(bg, popup string, w, h int) string { startX = 0 } - dim := lipgloss.NewStyle().Foreground(s.Subtle).Faint(true) + dim := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).Faint(true) result := make([]string, h) for y := 0; y < h; y++ { diff --git a/internal/ui/components/notifications/model.go b/internal/ui/components/notifications/model.go index cf73da8..f00b565 100644 --- a/internal/ui/components/notifications/model.go +++ b/internal/ui/components/notifications/model.go @@ -7,7 +7,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/charmbracelet/x/ansi" ) @@ -76,7 +76,6 @@ func (m Model) View(background string) string { return background } - s := style.S const popupW = 34 var popups []string @@ -89,17 +88,17 @@ func (m Model) View(background string) string { var accent color.Color switch n.kind { case KindSuccess: - accent = s.Success + accent = ilovetui.S.Success case KindWarning: - accent = s.Warning + accent = ilovetui.S.Warning case KindError: - accent = s.Error + accent = ilovetui.S.Error default: - accent = s.Primary + accent = ilovetui.S.Primary } titleStr := lipgloss.NewStyle().Foreground(accent).Bold(true).Render(n.title) - bodyStr := lipgloss.NewStyle().Foreground(s.Text).Width(popupW).Render(n.body) + bodyStr := lipgloss.NewStyle().Foreground(ilovetui.S.Text).Width(popupW).Render(n.body) inner := lipgloss.JoinVertical(lipgloss.Left, titleStr, bodyStr) box := lipgloss.NewStyle(). diff --git a/internal/ui/diff/model.go b/internal/ui/diff/model.go index a684fa2..0e50ce6 100644 --- a/internal/ui/diff/model.go +++ b/internal/ui/diff/model.go @@ -8,6 +8,7 @@ import ( "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" "github.com/anotherhadi/spilltea/internal/util" @@ -85,10 +86,9 @@ func wordDiff(leftLine, rightLine string) (leftRendered, rightRendered string) { segs[lo], segs[hi] = segs[hi], segs[lo] } - s := style.S - boldErr := lipgloss.NewStyle().Foreground(s.Error).Bold(true) - boldOk := lipgloss.NewStyle().Foreground(s.Success).Bold(true) - dim := lipgloss.NewStyle().Foreground(s.Subtle) + boldErr := lipgloss.NewStyle().Foreground(ilovetui.S.Error).Bold(true) + boldOk := lipgloss.NewStyle().Foreground(ilovetui.S.Success).Bold(true) + dim := lipgloss.NewStyle().Foreground(ilovetui.S.Subtle) var lb, rb strings.Builder for _, seg := range segs { @@ -255,7 +255,7 @@ func (m *Model) recalcSizes() { rightInner = 0 } - viewportH := style.PanelContentH(panelH) + viewportH := ilovetui.ContentHeight(panelH) m.leftViewport.SetWidth(leftInner) m.leftViewport.SetHeight(viewportH) @@ -296,13 +296,11 @@ func hlLines(raw string) []string { } func (m *Model) refreshViewports() { - s := style.S - if m.left.raw == "" { placeholder := lipgloss.Place( m.leftViewport.Width(), m.leftViewport.Height(), lipgloss.Center, lipgloss.Center, - s.Faint.Render(util.CenterLines("<(^_^)>", "send two entries here to compare")), + ilovetui.S.Faint.Render(util.CenterLines("<(^_^)>", "send two entries here to compare")), ) m.leftViewport.SetContent(placeholder) m.rightViewport.SetContent("") @@ -314,7 +312,7 @@ func (m *Model) refreshViewports() { placeholder := lipgloss.Place( m.rightViewport.Width(), m.rightViewport.Height(), lipgloss.Center, lipgloss.Center, - s.Faint.Render(util.CenterLines("(・3・)", "waiting for second entry…")), + ilovetui.S.Faint.Render(util.CenterLines("(・3・)", "waiting for second entry…")), ) m.rightViewport.SetContent(placeholder) return diff --git a/internal/ui/diff/view.go b/internal/ui/diff/view.go index 745a672..02c47fd 100644 --- a/internal/ui/diff/view.go +++ b/internal/ui/diff/view.go @@ -5,6 +5,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/icons" "github.com/anotherhadi/spilltea/internal/style" "github.com/charmbracelet/x/ansi" @@ -26,8 +27,6 @@ func (m Model) View() tea.View { } func (m *Model) renderPanels(panelH int) string { - s := style.S - leftW := m.width / 2 rightW := m.width - leftW @@ -46,20 +45,20 @@ func (m *Model) renderPanels(panelH int) string { rightTitle = ansi.Truncate(rightTitle, maxW, "…") } - leftBorder := s.Panel - rightBorder := s.Panel + leftBorder := ilovetui.S.Panel + rightBorder := ilovetui.S.Panel switch m.focus { case bothSlots: - leftBorder = s.PanelFocused - rightBorder = s.PanelFocused + leftBorder = ilovetui.S.PanelFocused + rightBorder = ilovetui.S.PanelFocused case leftSlot: - leftBorder = s.PanelFocused + leftBorder = ilovetui.S.PanelFocused case rightSlot: - rightBorder = s.PanelFocused + rightBorder = ilovetui.S.PanelFocused } - left := style.RenderWithTitle(leftBorder, leftTitle, style.ViewportView(&m.leftViewport), leftW, panelH) - right := style.RenderWithTitle(rightBorder, rightTitle, style.ViewportView(&m.rightViewport), rightW, panelH) + left := ilovetui.RenderWithTitle(leftBorder, leftTitle, style.ViewportView(&m.leftViewport), leftW, panelH) + right := ilovetui.RenderWithTitle(rightBorder, rightTitle, style.ViewportView(&m.rightViewport), rightW, panelH) return lipgloss.JoinHorizontal(lipgloss.Top, left, right) } @@ -69,12 +68,11 @@ func (m *Model) renderStatusBar() string { } func renderLeftLines(lines []diffLine) string { - s := style.S var sb strings.Builder for _, l := range lines { switch l.kind { case lineRemoved: - sb.WriteString(style.Paint(s.Error, "- ") + l.text + "\n") + sb.WriteString(style.Paint(ilovetui.S.Error, "- ") + l.text + "\n") case lineAdded: sb.WriteString("\n") default: @@ -85,12 +83,11 @@ func renderLeftLines(lines []diffLine) string { } func renderRightLines(lines []diffLine) string { - s := style.S var sb strings.Builder for _, l := range lines { switch l.kind { case lineAdded: - sb.WriteString(style.Paint(s.Success, "+ ") + l.text + "\n") + sb.WriteString(style.Paint(ilovetui.S.Success, "+ ") + l.text + "\n") case lineRemoved: sb.WriteString("\n") default: diff --git a/internal/ui/docs/model.go b/internal/ui/docs/model.go index 85e0cfc..359bc03 100644 --- a/internal/ui/docs/model.go +++ b/internal/ui/docs/model.go @@ -14,6 +14,7 @@ import ( "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" ) @@ -58,7 +59,7 @@ func New() Model { ti := textinput.New() ti.Prompt = "/" s := ti.Styles() - s.Focused.Prompt = lipgloss.NewStyle().Foreground(style.S.Primary) + s.Focused.Prompt = lipgloss.NewStyle().Foreground(ilovetui.S.Primary) ti.SetStyles(s) return Model{ @@ -182,8 +183,8 @@ func injectHighlightsInLine(ansiLine string, intervals [][]int, currentIdx int) return ansiLine } - normalOpen, normalClose := lipglossAnsiCodes(lipgloss.NewStyle().Background(style.S.SubtleBg)) - currentOpen, currentClose := lipglossAnsiCodes(lipgloss.NewStyle().Background(style.S.Primary).Foreground(style.S.Text)) + normalOpen, normalClose := lipglossAnsiCodes(lipgloss.NewStyle().Background(ilovetui.S.SubtleBg)) + currentOpen, currentClose := lipglossAnsiCodes(lipgloss.NewStyle().Background(ilovetui.S.Primary).Foreground(ilovetui.S.Text)) type injection struct { visPos int diff --git a/internal/ui/docs/view.go b/internal/ui/docs/view.go index 6b8871e..8a52d92 100644 --- a/internal/ui/docs/view.go +++ b/internal/ui/docs/view.go @@ -9,15 +9,15 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/glamour/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/config" - "github.com/anotherhadi/spilltea/internal/style" "github.com/charmbracelet/x/ansi" ) func windowStyle() lipgloss.Style { return lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(style.S.Subtle). + BorderForeground(ilovetui.S.Subtle). Padding(0, 0) } @@ -31,7 +31,7 @@ func (e Model) View() tea.View { countText = fmt.Sprintf("%d/%d", e.matchIndex+1, len(e.matches)) } count := lipgloss.NewStyle().Padding(0, 1). - Foreground(style.S.MutedFg). + Foreground(ilovetui.S.Muted). Render(countText) statusBar = lipgloss.JoinHorizontal(lipgloss.Top, statusBar, count) } @@ -61,7 +61,7 @@ func (m *Model) renderMarkdown() { width := m.viewport.Width() - 2 renderer, _ := glamour.NewTermRenderer( - glamour.WithStyles(style.GlamourStyleConfig(cfg)), + glamour.WithStyles(ilovetui.GlamourStyleConfig()), glamour.WithWordWrap(width), ) diff --git a/internal/ui/findings/model.go b/internal/ui/findings/model.go index 2141d50..6aa0a0d 100644 --- a/internal/ui/findings/model.go +++ b/internal/ui/findings/model.go @@ -11,7 +11,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/glamour/v2" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/config" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/db" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" @@ -78,7 +78,7 @@ func (m *Model) recalcSizes() { listH, bodyH := style.SplitH(m.height, m.renderStatusBar(), 0.35) - listVH := style.PanelContentH(listH) - 1 // -1 for the pager dots row + listVH := ilovetui.ContentHeight(listH) - 1 // -1 for the pager dots row if listVH < 0 { listVH = 0 } @@ -89,7 +89,7 @@ func (m *Model) recalcSizes() { m.pager.PerPage = 1 } - bodyVH := style.PanelContentH(bodyH) + bodyVH := ilovetui.ContentHeight(bodyH) m.bodyViewport.SetWidth(inner) m.bodyViewport.SetHeight(bodyVH) @@ -148,7 +148,7 @@ func (m *Model) refreshBodyScroll(reset bool) { func (m *Model) renderMarkdownCached(src string, width int) string { if src == "" { - return style.S.Faint.Render(util.CenterLines("(ㆆ _ ㆆ)", "no description")) + return ilovetui.S.Faint.Render(util.CenterLines("(ㆆ _ ㆆ)", "no description")) } tmpl, err := template.New("").Parse(src) if err != nil { @@ -164,7 +164,7 @@ func (m *Model) renderMarkdownCached(src string, width int) string { // Rebuild renderer if width changed or not yet built. if m.renderer == nil || m.rendererWidth != width { r, err := glamour.NewTermRenderer( - glamour.WithStyles(style.GlamourStyleConfig(config.Global)), + glamour.WithStyles(ilovetui.GlamourStyleConfig()), glamour.WithWordWrap(width), ) if err == nil { diff --git a/internal/ui/findings/view.go b/internal/ui/findings/view.go index 75585e7..34d77a8 100644 --- a/internal/ui/findings/view.go +++ b/internal/ui/findings/view.go @@ -6,6 +6,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/icons" "github.com/anotherhadi/spilltea/internal/style" "github.com/anotherhadi/spilltea/internal/util" @@ -27,34 +28,31 @@ func (m Model) View() tea.View { } func (m *Model) renderListPanel(w, h int) string { - s := style.S var dots string if len(m.findings) > 0 { - dots = s.Faint.Render(m.pager.View()) + dots = ilovetui.S.Faint.Render(m.pager.View()) } inner := lipgloss.JoinVertical(lipgloss.Left, m.listViewport.View(), lipgloss.PlaceHorizontal(m.listViewport.Width(), lipgloss.Center, dots), ) - return style.RenderWithTitle(s.PanelFocused, icons.I.Findings+"Findings", inner, w, h) + return ilovetui.RenderWithTitle(ilovetui.S.PanelFocused, icons.I.Findings+"Findings", inner, w, h) } func (m *Model) renderBodyPanel(h int) string { - s := style.S title := "Description" if len(m.findings) > 0 { title = m.findings[m.cursor].Title } - return style.RenderWithTitle(s.Panel, title, style.ViewportView(&m.bodyViewport), m.width, h) + return ilovetui.RenderWithTitle(ilovetui.S.Panel, title, style.ViewportView(&m.bodyViewport), m.width, h) } func (m *Model) renderList() string { - s := style.S if len(m.findings) == 0 { return lipgloss.Place( m.listViewport.Width(), m.listViewport.Height(), lipgloss.Center, lipgloss.Center, - s.Faint.Render(util.CenterLines("(҂◡_◡) ᕤ", "no findings")), + ilovetui.S.Faint.Render(util.CenterLines("(҂◡_◡) ᕤ", "no findings")), ) } @@ -76,18 +74,18 @@ func (m *Model) renderList() string { titleW = 0 } - pluginStr := s.Faint.Width(8).Render(util.Truncate(f.PluginName, 8)) + pluginStr := ilovetui.S.Faint.Width(8).Render(util.Truncate(f.PluginName, 8)) var line string if selected { - bg := lipgloss.NewStyle().Background(s.Selection) + bg := lipgloss.NewStyle().Background(ilovetui.S.Selection) line = lipgloss.JoinHorizontal(lipgloss.Top, - bg.Bold(true).Foreground(s.Primary).Width(2).Render(">"), - sevStyle.Background(s.Selection).Width(8).Render(f.Severity), + bg.Bold(true).Foreground(ilovetui.S.Primary).Width(2).Render(">"), + sevStyle.Background(ilovetui.S.Selection).Width(8).Render(f.Severity), bg.Width(1).Render(""), - bg.Foreground(s.Subtle).Width(8).Render(util.Truncate(f.PluginName, 8)), + bg.Foreground(ilovetui.S.Subtle).Width(8).Render(util.Truncate(f.PluginName, 8)), bg.Width(1).Render(""), - bg.Foreground(s.Subtle).Width(10).Render(ts), + bg.Foreground(ilovetui.S.Subtle).Width(10).Render(ts), bg.Width(1).Render(""), bg.Bold(true).Width(titleW).Render(f.Title), ) @@ -98,9 +96,9 @@ func (m *Model) renderList() string { " ", pluginStr, " ", - s.Faint.Width(10).Render(ts), + ilovetui.S.Faint.Width(10).Render(ts), " ", - s.Bold.Render(f.Title), + ilovetui.S.Bold.Render(f.Title), ) } sb.WriteString(fmt.Sprintf("%s\n", line)) diff --git a/internal/ui/history/model.go b/internal/ui/history/model.go index d32088d..cf94971 100644 --- a/internal/ui/history/model.go +++ b/internal/ui/history/model.go @@ -7,6 +7,7 @@ import ( "charm.land/bubbles/v2/textinput" "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/db" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" @@ -131,7 +132,7 @@ func (m *Model) recalcSizes() { inner = 0 } - listVH := style.PanelContentH(listH) - 1 // -1 for the pager dots row + listVH := ilovetui.ContentHeight(listH) - 1 // -1 for the pager dots row if listVH < 0 { listVH = 0 } @@ -142,7 +143,7 @@ func (m *Model) recalcSizes() { m.pager.PerPage = 1 } - bodyVH := style.PanelContentH(bodyH) + bodyVH := ilovetui.ContentHeight(bodyH) m.bodyViewport.SetWidth(inner) m.bodyViewport.SetHeight(bodyVH) diff --git a/internal/ui/history/update.go b/internal/ui/history/update.go index dea6f71..964919f 100644 --- a/internal/ui/history/update.go +++ b/internal/ui/history/update.go @@ -7,6 +7,7 @@ import ( "charm.land/bubbles/v2/key" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/db" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" @@ -345,7 +346,7 @@ func (m *Model) refreshBody() { } if raw == "" { w, h := m.bodyViewport.Width(), m.bodyViewport.Height() - m.bodyViewport.SetContent(lipgloss.Place(w, h, lipgloss.Center, lipgloss.Center, style.S.Faint.Render(util.CenterLines("(˘・_・˘)", "no response stored")))) + m.bodyViewport.SetContent(lipgloss.Place(w, h, lipgloss.Center, lipgloss.Center, ilovetui.S.Faint.Render(util.CenterLines("(˘・_・˘)", "no response stored")))) return } m.bodyViewport.SetContent(style.HighlightHTTP(raw)) diff --git a/internal/ui/history/view.go b/internal/ui/history/view.go index 6fc8753..287c352 100644 --- a/internal/ui/history/view.go +++ b/internal/ui/history/view.go @@ -6,6 +6,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/icons" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" @@ -28,60 +29,56 @@ func (m Model) View() tea.View { } func (m *Model) renderListPanel(w, h int) string { - s := style.S var dots string if len(m.entries) > 0 { - dots = s.Faint.Render(m.pager.View()) + dots = ilovetui.S.Faint.Render(m.pager.View()) } inner := lipgloss.JoinVertical(lipgloss.Left, m.listViewport.View(), lipgloss.PlaceHorizontal(m.listViewport.Width(), lipgloss.Center, dots), ) - return style.RenderWithTitle(s.PanelFocused, icons.I.History+"History", inner, w, h) + return ilovetui.RenderWithTitle(ilovetui.S.PanelFocused, icons.I.History+"History", inner, w, h) } func (m *Model) renderBodyPanel(h int) string { - s := style.S title := icons.I.Request + "Request" if m.focusedPanel == panelResponse { title = icons.I.Response + "Response" } - return style.RenderWithTitle(s.Panel, title, style.ViewportView(&m.bodyViewport), m.width, h) + return ilovetui.RenderWithTitle(ilovetui.S.Panel, title, style.ViewportView(&m.bodyViewport), m.width, h) } func (m *Model) renderStatusBar() string { - s := style.S pad := lipgloss.NewStyle().Padding(0, 1) escKey := keys.Keys.Global.Escape.Help().Key switch m.searchKind { case searchKindFulltext: filterKey := keys.Keys.History.Filter.Help().Key if m.searchAccepted { - accent := lipgloss.NewStyle().Foreground(s.Primary) - filterLine := pad.Render(accent.Render(filterKey) + " " + s.Bold.Render(m.searchInput.Value()) + s.Faint.Render(" "+escKey+" to clear")) + accent := lipgloss.NewStyle().Foreground(ilovetui.S.Primary) + filterLine := pad.Render(accent.Render(filterKey) + " " + ilovetui.S.Bold.Render(m.searchInput.Value()) + ilovetui.S.Faint.Render(" "+escKey+" to clear")) return lipgloss.JoinVertical(lipgloss.Left, filterLine, pad.Render(m.help.View(historyKeyMap{width: m.width}))) } - return pad.Render(s.Faint.Render(filterKey) + " " + m.searchInput.View()) + return pad.Render(ilovetui.S.Faint.Render(filterKey) + " " + m.searchInput.View()) case searchKindSQL: sqlKey := keys.Keys.History.SqlQuery.Help().Key if m.searchAccepted { - accent := lipgloss.NewStyle().Foreground(s.Primary) - filterLine := pad.Render(accent.Render(sqlKey) + " " + s.Bold.Render(m.searchInput.Value()) + s.Faint.Render(" "+escKey+" to clear")) + accent := lipgloss.NewStyle().Foreground(ilovetui.S.Primary) + filterLine := pad.Render(accent.Render(sqlKey) + " " + ilovetui.S.Bold.Render(m.searchInput.Value()) + ilovetui.S.Faint.Render(" "+escKey+" to clear")) return lipgloss.JoinVertical(lipgloss.Left, filterLine, pad.Render(m.help.View(historyKeyMap{width: m.width}))) } - return pad.Render(s.Faint.Render(sqlKey) + " " + m.searchInput.View()) + return pad.Render(ilovetui.S.Faint.Render(sqlKey) + " " + m.searchInput.View()) default: return pad.Render(m.help.View(historyKeyMap{width: m.width})) } } func (m *Model) renderList() string { - s := style.S if m.searchErr != "" { return lipgloss.Place( m.listViewport.Width(), m.listViewport.Height(), lipgloss.Center, lipgloss.Center, - lipgloss.NewStyle().Foreground(s.Error).Render(m.searchErr), + lipgloss.NewStyle().Foreground(ilovetui.S.Error).Render(m.searchErr), ) } if len(m.entries) == 0 { @@ -92,7 +89,7 @@ func (m *Model) renderList() string { return lipgloss.Place( m.listViewport.Width(), m.listViewport.Height(), lipgloss.Center, lipgloss.Center, - s.Faint.Render(msg), + ilovetui.S.Faint.Render(msg), ) } @@ -103,7 +100,7 @@ func (m *Model) renderList() string { globalIdx := start + i selected := globalIdx == m.cursor - selBg := s.Selection + selBg := ilovetui.S.Selection w := m.listViewport.Width() statusStr := fmt.Sprintf("%3d", e.StatusCode) @@ -115,7 +112,7 @@ func (m *Model) renderList() string { ts := e.Timestamp.Format("15:04:05") statusSt := style.StatusStyle(e.StatusCode, 3) - flagSt := lipgloss.NewStyle().Foreground(s.Primary) + flagSt := lipgloss.NewStyle().Foreground(ilovetui.S.Primary) var line string if selected { @@ -128,13 +125,13 @@ func (m *Model) renderList() string { } } line = lipgloss.JoinHorizontal(lipgloss.Top, - bg.Bold(true).Foreground(s.Primary).Width(2).Render(">"), - bg.Foreground(s.Primary).Width(2).Render(flagStr), - s.Method(e.Method).Background(selBg).Render(e.Method), + bg.Bold(true).Foreground(ilovetui.S.Primary).Width(2).Render(">"), + bg.Foreground(ilovetui.S.Primary).Width(2).Render(flagStr), + style.S.Method(e.Method).Background(selBg).Render(e.Method), bg.Width(1).Render(""), statusSt.Background(selBg).Render(statusStr), bg.Width(1).Render(""), - bg.Foreground(s.Subtle).Width(10).Render(ts), + bg.Foreground(ilovetui.S.Subtle).Width(10).Render(ts), bg.Width(1).Render(""), bg.Bold(true).Width(hostPathW).Render(e.Host+e.Path), ) @@ -149,14 +146,14 @@ func (m *Model) renderList() string { line = lipgloss.JoinHorizontal(lipgloss.Top, " ", flagSt.Width(2).Render(flagStr), - s.Method(e.Method).Render(e.Method), + style.S.Method(e.Method).Render(e.Method), " ", statusSt.Render(statusStr), " ", - s.Faint.Width(10).Render(ts), + ilovetui.S.Faint.Width(10).Render(ts), " ", - s.Bold.Render(e.Host), - s.Faint.Render(e.Path), + ilovetui.S.Bold.Render(e.Host), + ilovetui.S.Faint.Render(e.Path), ) } sb.WriteString(line + "\n") diff --git a/internal/ui/home/model.go b/internal/ui/home/model.go index 8b237a1..aa649d4 100644 --- a/internal/ui/home/model.go +++ b/internal/ui/home/model.go @@ -14,10 +14,10 @@ import ( "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/db" "github.com/anotherhadi/spilltea/internal/icons" "github.com/anotherhadi/spilltea/internal/keys" - "github.com/anotherhadi/spilltea/internal/style" "github.com/anotherhadi/spilltea/internal/ui/components/teapot" ) @@ -88,19 +88,18 @@ type homeDelegate struct { } func newHomeDelegate() homeDelegate { - s := style.S leftBorder := lipgloss.Border{Left: "│"} return homeDelegate{ - normalTitle: lipgloss.NewStyle().Foreground(s.Text).PaddingLeft(4), - normalDesc: lipgloss.NewStyle().Foreground(s.Subtle).Faint(true).PaddingLeft(4), + normalTitle: lipgloss.NewStyle().Foreground(ilovetui.S.Text).PaddingLeft(4), + normalDesc: lipgloss.NewStyle().Foreground(ilovetui.S.Subtle).Faint(true).PaddingLeft(4), selectedTitle: lipgloss.NewStyle(). Border(leftBorder, false, false, false, true). - BorderForeground(s.Primary). - Foreground(s.Primary).Bold(true).PaddingLeft(3), + BorderForeground(ilovetui.S.Primary). + Foreground(ilovetui.S.Primary).Bold(true).PaddingLeft(3), selectedDesc: lipgloss.NewStyle(). Border(leftBorder, false, false, false, true). - BorderForeground(s.Primary). - Foreground(s.MutedFg).PaddingLeft(3), + BorderForeground(ilovetui.S.Primary). + Foreground(ilovetui.S.Muted).PaddingLeft(3), filterMatch: lipgloss.NewStyle().Underline(true), } } @@ -304,14 +303,13 @@ func buildItems(projects []Project) []list.Item { } func (m Model) renderHelpLine() string { - s := style.S k := keys.Keys.Home fs := m.list.FilterState() - kStyle := lipgloss.NewStyle().Foreground(s.MutedFg).Inline(true) - dStyle := s.Faint.Inline(true) + kStyle := lipgloss.NewStyle().Foreground(ilovetui.S.Muted).Inline(true) + dStyle := ilovetui.S.Faint.Inline(true) - sep := s.Faint.Inline(true).Render(" • ") + sep := ilovetui.S.Faint.Inline(true).Render(" • ") item := func(keyStr, desc string) string { return kStyle.Render(keyStr) + " " + dStyle.Render(desc) } diff --git a/internal/ui/home/view.go b/internal/ui/home/view.go index 12f4227..56002e1 100644 --- a/internal/ui/home/view.go +++ b/internal/ui/home/view.go @@ -5,29 +5,28 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/style" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/ui/components/teapot" ) const inputPanelMaxW = 44 func (m Model) View() tea.View { - s := style.S iw := m.innerW() var sb strings.Builder sb.WriteString("\n") if m.height > teapotMinH { frames := teapot.TeapotFrames() - frame := lipgloss.NewStyle().Foreground(s.Primary).Render(frames[m.teapotFrame]) + frame := lipgloss.NewStyle().Foreground(ilovetui.S.Primary).Render(frames[m.teapotFrame]) sb.WriteString(center(iw, frame)) sb.WriteString("\n\n") } else { sb.WriteString("\n") } - sb.WriteString(center(iw, lipgloss.NewStyle().Bold(true).Foreground(s.Primary).Render("SPILLTEA"))) + sb.WriteString(center(iw, lipgloss.NewStyle().Bold(true).Foreground(ilovetui.S.Primary).Render("SPILLTEA"))) sb.WriteString("\n") - sb.WriteString(center(iw, s.Faint.Render("choose a project to get started"))) + sb.WriteString(center(iw, ilovetui.S.Faint.Render("choose a project to get started"))) sb.WriteString("\n\n") if m.mode == modeNaming { @@ -50,7 +49,6 @@ func (m Model) View() tea.View { } func (m Model) renderNamingPanel() string { - s := style.S iw := m.innerW() panelW := inputPanelMaxW @@ -63,15 +61,15 @@ func (m Model) renderNamingPanel() string { innerW := inputPanelInnerW(iw) inputLine := lipgloss.NewStyle().Width(innerW).Render(m.nameInput.View()) - label := lipgloss.NewStyle().Foreground(s.MutedFg).Render("Project name") + label := lipgloss.NewStyle().Foreground(ilovetui.S.Muted).Render("Project name") panel := lipgloss.NewStyle(). Border(lipgloss.RoundedBorder()). - BorderForeground(s.Primary). + BorderForeground(ilovetui.S.Primary). Padding(1, 2). Width(panelW). Render(label + "\n" + inputLine) - hint := s.Faint.Render("[enter] confirm [esc] cancel") + hint := ilovetui.S.Faint.Render("[enter] confirm [esc] cancel") var sb strings.Builder sb.WriteString(center(iw, panel)) diff --git a/internal/ui/intercept/helpers.go b/internal/ui/intercept/helpers.go index 108f3d0..c2884e8 100644 --- a/internal/ui/intercept/helpers.go +++ b/internal/ui/intercept/helpers.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/intercept" "github.com/anotherhadi/spilltea/internal/style" "github.com/anotherhadi/spilltea/internal/util" @@ -163,14 +164,14 @@ func (m *Model) recalcSizes() { if bodyInner < 0 { bodyInner = 0 } - bodyVH := style.PanelContentH(bodyH) + bodyVH := ilovetui.ContentHeight(bodyH) m.textarea.SetWidth(bodyInner) m.textarea.SetHeight(bodyVH) m.bodyViewport.SetWidth(bodyInner) m.bodyViewport.SetHeight(bodyVH) - listVH := style.PanelContentH(listH) - 1 // -1 for the pager dots row + listVH := ilovetui.ContentHeight(listH) - 1 // -1 for the pager dots row if listVH < 0 { listVH = 0 } diff --git a/internal/ui/intercept/view.go b/internal/ui/intercept/view.go index 7e9f4d3..651b8ce 100644 --- a/internal/ui/intercept/view.go +++ b/internal/ui/intercept/view.go @@ -6,6 +6,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/icons" "github.com/anotherhadi/spilltea/internal/style" "github.com/anotherhadi/spilltea/internal/util" @@ -38,17 +39,15 @@ func (m Model) View() tea.View { } func (m *Model) renderListPanel(w, h int) string { - s := style.S - focused := !m.editing && (!m.captureResponse || m.focusedPanel == panelRequests) - border := s.Panel + border := ilovetui.S.Panel if focused { - border = s.PanelFocused + border = ilovetui.S.PanelFocused } var dots string if len(m.queue) > 0 { - dots = s.Faint.Render(m.pager.View()) + dots = ilovetui.S.Faint.Render(m.pager.View()) } inner := lipgloss.JoinVertical(lipgloss.Left, m.listViewport.View(), @@ -57,32 +56,28 @@ func (m *Model) renderListPanel(w, h int) string { title := icons.I.Request + "Requests" if !m.interceptEnabled { - title += " " + lipgloss.NewStyle().Foreground(style.S.Error).Render("[intercept off]") + title += " " + lipgloss.NewStyle().Foreground(ilovetui.S.Error).Render("[intercept off]") } - return style.RenderWithTitle(border, title, inner, w, h) + return ilovetui.RenderWithTitle(border, title, inner, w, h) } func (m *Model) renderResponseListPanel(w, h int) string { - s := style.S - focused := !m.editing && m.focusedPanel == panelResponses - border := s.Panel + border := ilovetui.S.Panel if focused { - border = s.PanelFocused + border = ilovetui.S.PanelFocused } - dots := s.Faint.Render(m.responsePager.View()) + dots := ilovetui.S.Faint.Render(m.responsePager.View()) inner := lipgloss.JoinVertical(lipgloss.Left, m.responseViewport.View(), lipgloss.PlaceHorizontal(m.responseViewport.Width(), lipgloss.Center, dots), ) - return style.RenderWithTitle(border, icons.I.Response+"Responses", inner, w, h) + return ilovetui.RenderWithTitle(border, icons.I.Response+"Responses", inner, w, h) } func (m *Model) renderBodyPanel(h int) string { - s := style.S - var body string if m.editing { body = m.textarea.View() @@ -90,13 +85,13 @@ func (m *Model) renderBodyPanel(h int) string { body = style.ViewportView(&m.bodyViewport) } - border := s.Panel + border := ilovetui.S.Panel if m.editing { - border = s.PanelFocused + border = ilovetui.S.PanelFocused } title := icons.I.Detail + "Details" - return style.RenderWithTitle(border, title, body, m.width, h) + return ilovetui.RenderWithTitle(border, title, body, m.width, h) } func (m *Model) renderStatusBar() string { @@ -105,10 +100,9 @@ func (m *Model) renderStatusBar() string { func (m *Model) renderList() string { if len(m.queue) == 0 { - return lipgloss.Place(m.listViewport.Width(), m.listViewport.Height(), lipgloss.Center, lipgloss.Center, style.S.Faint.Render(util.CenterLines("(。◕‿‿◕。)", "waiting for a request"))) + return lipgloss.Place(m.listViewport.Width(), m.listViewport.Height(), lipgloss.Center, lipgloss.Center, ilovetui.S.Faint.Render(util.CenterLines("(。◕‿‿◕。)", "waiting for a request"))) } - s := style.S start, end := util.PageBounds(m.pager, len(m.queue)) var sb strings.Builder @@ -121,7 +115,7 @@ func (m *Model) renderList() string { } selected := globalIdx == m.cursor - selBg := s.Selection + selBg := ilovetui.S.Selection w := m.listViewport.Width() const fixedW = 2 + 7 + 2 @@ -134,18 +128,18 @@ func (m *Model) renderList() string { if selected { bg := lipgloss.NewStyle().Background(selBg) line = lipgloss.JoinHorizontal(lipgloss.Top, - bg.Bold(true).Foreground(s.Primary).Width(2).Render(">"), - s.Method(r.Method).Background(selBg).Render(r.Method), + bg.Bold(true).Foreground(ilovetui.S.Primary).Width(2).Render(">"), + style.S.Method(r.Method).Background(selBg).Render(r.Method), bg.Width(2).Render(""), bg.Bold(true).Width(hostPathW).Render(r.URL.Host+path), ) } else { line = lipgloss.JoinHorizontal(lipgloss.Top, " ", - s.Method(r.Method).Render(r.Method), - s.Faint.Render(" "), - s.Bold.Render(r.URL.Host), - s.Faint.Render(path), + style.S.Method(r.Method).Render(r.Method), + ilovetui.S.Faint.Render(" "), + ilovetui.S.Bold.Render(r.URL.Host), + ilovetui.S.Faint.Render(path), ) } sb.WriteString(line + "\n") @@ -155,10 +149,9 @@ func (m *Model) renderList() string { func (m *Model) renderResponseList() string { if len(m.responseQueue) == 0 { - return lipgloss.Place(m.responseViewport.Width(), m.responseViewport.Height(), lipgloss.Center, lipgloss.Center, style.S.Faint.Render(util.CenterLines("(҂◡_◡)", "no response yet"))) + return lipgloss.Place(m.responseViewport.Width(), m.responseViewport.Height(), lipgloss.Center, lipgloss.Center, ilovetui.S.Faint.Render(util.CenterLines("(҂◡_◡)", "no response yet"))) } - s := style.S start, end := util.PageBounds(m.responsePager, len(m.responseQueue)) var sb strings.Builder @@ -177,7 +170,7 @@ func (m *Model) renderResponseList() string { statusStr := fmt.Sprintf("%d", code) selected := globalIdx == m.responseCursor - selBg := s.Selection + selBg := ilovetui.S.Selection statusSt := style.StatusStyle(code, 7) @@ -192,7 +185,7 @@ func (m *Model) renderResponseList() string { if selected { bg := lipgloss.NewStyle().Background(selBg) line = lipgloss.JoinHorizontal(lipgloss.Top, - bg.Bold(true).Foreground(s.Primary).Width(2).Render(">"), + bg.Bold(true).Foreground(ilovetui.S.Primary).Width(2).Render(">"), statusSt.Background(selBg).Render(statusStr), bg.Width(2).Render(""), bg.Bold(true).Width(hostPathW).Render(f.Request.URL.Host+path), @@ -201,9 +194,9 @@ func (m *Model) renderResponseList() string { line = lipgloss.JoinHorizontal(lipgloss.Top, " ", statusSt.Render(statusStr), - s.Faint.Render(" "), - s.Bold.Render(f.Request.URL.Host), - s.Faint.Render(path), + ilovetui.S.Faint.Render(" "), + ilovetui.S.Bold.Render(f.Request.URL.Host), + ilovetui.S.Faint.Render(path), ) } sb.WriteString(line + "\n") diff --git a/internal/ui/plugins/model.go b/internal/ui/plugins/model.go index 0d99337..3639b6a 100644 --- a/internal/ui/plugins/model.go +++ b/internal/ui/plugins/model.go @@ -10,6 +10,7 @@ import ( "charm.land/bubbles/v2/textinput" "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/plugins" "github.com/anotherhadi/spilltea/internal/style" @@ -77,7 +78,7 @@ func (m *Model) recalcSizes() { inner = 0 } - listVH := style.PanelContentH(listH) - 1 // -1 for the pager dots row + listVH := ilovetui.ContentHeight(listH) - 1 // -1 for the pager dots row if listVH < 0 { listVH = 0 } @@ -90,7 +91,7 @@ func (m *Model) recalcSizes() { m.filterInput.SetWidth(inner - 2) - detailContentH := style.PanelContentH(detailH) + detailContentH := ilovetui.ContentHeight(detailH) const headerH = 2 const configFixedH = 2 // blank line + label line textareaH := max(3, detailContentH/3) diff --git a/internal/ui/plugins/view.go b/internal/ui/plugins/view.go index 785e122..9464f9b 100644 --- a/internal/ui/plugins/view.go +++ b/internal/ui/plugins/view.go @@ -7,7 +7,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/glamour/v2" "charm.land/lipgloss/v2" - "github.com/anotherhadi/spilltea/internal/config" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/icons" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" @@ -16,7 +16,7 @@ import ( func (m Model) View() tea.View { if m.width == 0 || m.manager == nil { - return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, style.S.Faint.Render(util.CenterLines("(._.)~*.'", "no plugins loaded")))) + return tea.NewView(lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, ilovetui.S.Faint.Render(util.CenterLines("(._.)~*.'", "no plugins loaded")))) } listH, detailH := style.SplitH(m.height, m.renderStatusBar(), 0.4) @@ -30,36 +30,34 @@ func (m Model) View() tea.View { } func (m *Model) renderListPanel(w, h int) string { - s := style.S - panelStyle := s.PanelFocused + panelStyle := ilovetui.S.PanelFocused if m.editing { - panelStyle = s.Panel + panelStyle = ilovetui.S.Panel } var dots string if len(m.filtered) > 0 { - dots = s.Faint.Render(m.pager.View()) + dots = ilovetui.S.Faint.Render(m.pager.View()) } inner := lipgloss.JoinVertical(lipgloss.Left, m.listViewport.View(), lipgloss.PlaceHorizontal(m.listViewport.Width(), lipgloss.Center, dots), ) - return style.RenderWithTitle(panelStyle, icons.I.Plugin+"Plugins", inner, w, h) + return ilovetui.RenderWithTitle(panelStyle, icons.I.Plugin+"Plugins", inner, w, h) } func (m *Model) renderDetailPanel(h int) string { - s := style.S - panelStyle := s.Panel + panelStyle := ilovetui.S.Panel if m.editing { - panelStyle = s.PanelFocused + panelStyle = ilovetui.S.PanelFocused } info, ok := m.selected() if !ok { - return style.RenderWithTitle(panelStyle, icons.I.Detail+"Detail", "", m.width, h) + return ilovetui.RenderWithTitle(panelStyle, icons.I.Detail+"Detail", "", m.width, h) } - statusSt := lipgloss.NewStyle().Foreground(s.Error) + statusSt := lipgloss.NewStyle().Foreground(ilovetui.S.Error) if info.Enabled { - statusSt = lipgloss.NewStyle().Foreground(s.Success) + statusSt = lipgloss.NewStyle().Foreground(ilovetui.S.Success) } status := "disabled" if info.Enabled { @@ -69,8 +67,8 @@ func (m *Model) renderDetailPanel(h int) string { pad := lipgloss.NewStyle().Padding(0, 1) header := pad.Render( - s.Bold.Render(info.Name) + " " + statusSt.Render(status) + "\n" + - s.Faint.Render(filepath.Base(info.FilePath)), + ilovetui.S.Bold.Render(info.Name) + " " + statusSt.Render(status) + "\n" + + ilovetui.S.Faint.Render(filepath.Base(info.FilePath)), ) parts := []string{header, style.ViewportView(&m.detailViewport)} @@ -79,16 +77,16 @@ func (m *Model) renderDetailPanel(h int) string { var configLabel string if m.editing { escKey := keys.Keys.Global.Escape.Help().Key - configLabel = pad.Render(s.Faint.Render("editing config (" + escKey + " to save):")) + configLabel = pad.Render(ilovetui.S.Faint.Render("editing config (" + escKey + " to save):")) } else { editKey := keys.Keys.Plugins.EditConfig.Help().Key - configLabel = pad.Render(s.Faint.Render("config (" + editKey + " to edit):")) + configLabel = pad.Render(ilovetui.S.Faint.Render("config (" + editKey + " to edit):")) } parts = append(parts, "", configLabel, pad.Render(m.textarea.View())) } inner := lipgloss.JoinVertical(lipgloss.Left, parts...) - return style.RenderWithTitle(panelStyle, icons.I.Detail+"Detail", inner, m.width, h) + return ilovetui.RenderWithTitle(panelStyle, icons.I.Detail+"Detail", inner, m.width, h) } func renderPluginDescription(desc string, width int) string { @@ -100,7 +98,7 @@ func renderPluginDescription(desc string, width int) string { desc = strings.Join(lines, "\n") r, err := glamour.NewTermRenderer( - glamour.WithStyles(style.GlamourStyleConfig(config.Global)), + glamour.WithStyles(ilovetui.GlamourStyleConfig()), glamour.WithWordWrap(width), ) if err != nil { @@ -114,23 +112,21 @@ func renderPluginDescription(desc string, width int) string { } func (m *Model) renderStatusBar() string { - s := style.S pad := lipgloss.NewStyle().Padding(0, 1) filterKey := keys.Keys.Plugins.Filter.Help().Key if m.filtering { - return pad.Render(s.Faint.Render(filterKey) + " " + m.filterInput.View()) + return pad.Render(ilovetui.S.Faint.Render(filterKey) + " " + m.filterInput.View()) } if m.filter != "" { escKey := keys.Keys.Global.Escape.Help().Key - accent := lipgloss.NewStyle().Foreground(s.Primary) - filterLine := pad.Render(accent.Render(filterKey) + " " + s.Bold.Render(m.filter) + s.Faint.Render(" "+escKey+" to clear")) + accent := lipgloss.NewStyle().Foreground(ilovetui.S.Primary) + filterLine := pad.Render(accent.Render(filterKey) + " " + ilovetui.S.Bold.Render(m.filter) + ilovetui.S.Faint.Render(" "+escKey+" to clear")) return lipgloss.JoinVertical(lipgloss.Left, filterLine, pad.Render(m.help.View(pluginsKeyMap{editing: m.editing, hasConfig: m.hasConfig(), width: m.width}))) } return pad.Render(m.help.View(pluginsKeyMap{editing: m.editing, hasConfig: m.hasConfig(), width: m.width})) } func (m *Model) renderList() string { - s := style.S if len(m.filtered) == 0 { msg := util.CenterLines("(ง •̀_•́)ง", "no plugins", "", "spilltea --add-default-plugins") if m.filter != "" { @@ -139,7 +135,7 @@ func (m *Model) renderList() string { return lipgloss.Place( m.listViewport.Width(), m.listViewport.Height(), lipgloss.Center, lipgloss.Center, - s.Faint.Render(msg), + ilovetui.S.Faint.Render(msg), ) } @@ -150,10 +146,10 @@ func (m *Model) renderList() string { globalIdx := start + i selected := globalIdx == m.cursor - enabledSt := lipgloss.NewStyle().Foreground(s.Error) + enabledSt := lipgloss.NewStyle().Foreground(ilovetui.S.Error) enabledStr := "off" if p.Enabled { - enabledSt = lipgloss.NewStyle().Foreground(s.Success) + enabledSt = lipgloss.NewStyle().Foreground(ilovetui.S.Success) enabledStr = "on " } @@ -166,10 +162,10 @@ func (m *Model) renderList() string { var line string if selected { - bg := lipgloss.NewStyle().Background(s.Selection) + bg := lipgloss.NewStyle().Background(ilovetui.S.Selection) line = lipgloss.JoinHorizontal(lipgloss.Top, - bg.Bold(true).Foreground(s.Primary).Width(2).Render(">"), - enabledSt.Background(s.Selection).Width(3).Render(enabledStr), + bg.Bold(true).Foreground(ilovetui.S.Primary).Width(2).Render(">"), + enabledSt.Background(ilovetui.S.Selection).Width(3).Render(enabledStr), bg.Width(1).Render(""), bg.Bold(true).Width(nameW).Render(p.Name), ) @@ -178,7 +174,7 @@ func (m *Model) renderList() string { " ", enabledSt.Width(3).Render(enabledStr), " ", - s.Bold.Render(p.Name), + ilovetui.S.Bold.Render(p.Name), ) } sb.WriteString(line + "\n") diff --git a/internal/ui/replay/model.go b/internal/ui/replay/model.go index d74ad94..0d99dd6 100644 --- a/internal/ui/replay/model.go +++ b/internal/ui/replay/model.go @@ -9,6 +9,7 @@ import ( "charm.land/bubbles/v2/textarea" "charm.land/bubbles/v2/viewport" tea "charm.land/bubbletea/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/db" "github.com/anotherhadi/spilltea/internal/keys" "github.com/anotherhadi/spilltea/internal/style" @@ -155,7 +156,7 @@ func (m *Model) recalcSizes() { if listInner < 0 { listInner = 0 } - listVH := style.PanelContentH(listH) - 1 // -1 for the pager dots row + listVH := ilovetui.ContentHeight(listH) - 1 // -1 for the pager dots row if listVH < 0 { listVH = 0 } @@ -175,7 +176,7 @@ func (m *Model) recalcSizes() { if rightInner < 0 { rightInner = 0 } - bodyVH := style.PanelContentH(bodyH) + bodyVH := ilovetui.ContentHeight(bodyH) m.requestViewport.SetWidth(leftInner) m.requestViewport.SetHeight(bodyVH) diff --git a/internal/ui/replay/update.go b/internal/ui/replay/update.go index 27f785f..03cc8d6 100644 --- a/internal/ui/replay/update.go +++ b/internal/ui/replay/update.go @@ -16,6 +16,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" "github.com/andybalholm/brotli" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/config" "github.com/anotherhadi/spilltea/internal/db" "github.com/anotherhadi/spilltea/internal/keys" @@ -350,11 +351,11 @@ func (m *Model) refreshBody() { m.requestViewport.SetXOffset(0) if e.Sending { - m.responseViewport.SetContent(lipgloss.Place(m.responseViewport.Width(), m.responseViewport.Height(), lipgloss.Center, lipgloss.Center, style.S.Faint.Render(util.CenterLines("(ノ◕ヮ◕)ノ*:・゚", "sending...")))) + m.responseViewport.SetContent(lipgloss.Place(m.responseViewport.Width(), m.responseViewport.Height(), lipgloss.Center, lipgloss.Center, ilovetui.S.Faint.Render(util.CenterLines("(ノ◕ヮ◕)ノ*:・゚", "sending...")))) } else if e.ResponseRaw != "" { m.responseViewport.SetContent(style.HighlightHTTP(e.ResponseRaw)) } else { - m.responseViewport.SetContent(lipgloss.Place(m.responseViewport.Width(), m.responseViewport.Height(), lipgloss.Center, lipgloss.Center, style.S.Faint.Render(util.CenterLines("( •_•)>⌐■", "press send to fire")))) + m.responseViewport.SetContent(lipgloss.Place(m.responseViewport.Width(), m.responseViewport.Height(), lipgloss.Center, lipgloss.Center, ilovetui.S.Faint.Render(util.CenterLines("( •_•)>⌐■", "press send to fire")))) } m.responseViewport.SetYOffset(0) m.responseViewport.SetXOffset(0) diff --git a/internal/ui/replay/view.go b/internal/ui/replay/view.go index 418856b..0bbab36 100644 --- a/internal/ui/replay/view.go +++ b/internal/ui/replay/view.go @@ -6,6 +6,7 @@ import ( tea "charm.land/bubbletea/v2" "charm.land/lipgloss/v2" + ilovetui "github.com/anotherhadi/ilovetui" "github.com/anotherhadi/spilltea/internal/icons" "github.com/anotherhadi/spilltea/internal/style" "github.com/anotherhadi/spilltea/internal/util" @@ -33,45 +34,42 @@ func (m Model) View() tea.View { } func (m *Model) renderListPanel(w, h int) string { - s := style.S - panelStyle := s.Panel + panelStyle := ilovetui.S.Panel if !m.editing && m.focusedPanel == panelList { - panelStyle = s.PanelFocused + panelStyle = ilovetui.S.PanelFocused } var dots string if len(m.entries) > 0 { - dots = s.Faint.Render(m.pager.View()) + dots = ilovetui.S.Faint.Render(m.pager.View()) } inner := lipgloss.JoinVertical(lipgloss.Left, m.listViewport.View(), lipgloss.PlaceHorizontal(m.listViewport.Width(), lipgloss.Center, dots), ) - return style.RenderWithTitle(panelStyle, icons.I.Replay+"Replay", inner, w, h) + return ilovetui.RenderWithTitle(panelStyle, icons.I.Replay+"Replay", inner, w, h) } func (m *Model) renderRequestPanel(w, h int) string { - s := style.S var body string - border := s.Panel + border := ilovetui.S.Panel if m.editing { body = m.textarea.View() - border = s.PanelFocused + border = ilovetui.S.PanelFocused } else { body = style.ViewportView(&m.requestViewport) if m.focusedPanel == panelRequest { - border = s.PanelFocused + border = ilovetui.S.PanelFocused } } - return style.RenderWithTitle(border, icons.I.Request+"Request", body, w, h) + return ilovetui.RenderWithTitle(border, icons.I.Request+"Request", body, w, h) } func (m *Model) renderResponsePanel(w, h int) string { - s := style.S - border := s.Panel + border := ilovetui.S.Panel if !m.editing && m.focusedPanel == panelResponse { - border = s.PanelFocused + border = ilovetui.S.PanelFocused } - return style.RenderWithTitle(border, icons.I.Response+"Response", style.ViewportView(&m.responseViewport), w, h) + return ilovetui.RenderWithTitle(border, icons.I.Response+"Response", style.ViewportView(&m.responseViewport), w, h) } func (m *Model) renderStatusBar() string { @@ -83,18 +81,17 @@ func (m *Model) renderList() string { return lipgloss.Place( m.listViewport.Width(), m.listViewport.Height(), lipgloss.Center, lipgloss.Center, - style.S.Faint.Render(util.CenterLines("(╥﹏╥)", "send a request from History or Intercept")), + ilovetui.S.Faint.Render(util.CenterLines("(╥﹏╥)", "send a request from History or Intercept")), ) } - s := style.S start, end := util.PageBounds(m.pager, len(m.entries)) var sb strings.Builder for i, e := range m.entries[start:end] { globalIdx := start + i selected := globalIdx == m.cursor - selBg := s.Selection + selBg := ilovetui.S.Selection w := m.listViewport.Width() const fixedW = 2 + 7 + 1 + 3 + 1 @@ -109,8 +106,8 @@ func (m *Model) renderList() string { if selected { bg := lipgloss.NewStyle().Background(selBg) line = lipgloss.JoinHorizontal(lipgloss.Top, - bg.Bold(true).Foreground(s.Primary).Width(2).Render(">"), - s.Method(e.Method).Background(selBg).Render(e.Method), + bg.Bold(true).Foreground(ilovetui.S.Primary).Width(2).Render(">"), + style.S.Method(e.Method).Background(selBg).Render(e.Method), bg.Width(1).Render(""), statusSt.Background(selBg).Render(statusStr), bg.Width(1).Render(""), @@ -119,12 +116,12 @@ func (m *Model) renderList() string { } else { line = lipgloss.JoinHorizontal(lipgloss.Top, " ", - s.Method(e.Method).Render(e.Method), + style.S.Method(e.Method).Render(e.Method), " ", statusSt.Render(statusStr), " ", - s.Bold.Render(e.Host), - s.Faint.Render(e.Path), + ilovetui.S.Bold.Render(e.Host), + ilovetui.S.Faint.Render(e.Path), ) } sb.WriteString(line + "\n") @@ -136,11 +133,11 @@ func entryStatus(e Entry) (string, lipgloss.Style) { base := lipgloss.NewStyle().Bold(true).Width(3) switch { case e.Sending: - return "···", base.Foreground(style.S.Subtle) + return "···", base.Foreground(ilovetui.S.Subtle) case e.Err != nil: - return "ERR", base.Foreground(style.S.Error) + return "ERR", base.Foreground(ilovetui.S.Error) case e.StatusCode == 0: - return "---", base.Foreground(style.S.Subtle) + return "---", base.Foreground(ilovetui.S.Subtle) } return fmt.Sprintf("%3d", e.StatusCode), style.StatusStyle(e.StatusCode, 3) } diff --git a/nix/gomod2nix.toml b/nix/gomod2nix.toml index 078ffad..2a53dc9 100644 --- a/nix/gomod2nix.toml +++ b/nix/gomod2nix.toml @@ -25,6 +25,10 @@ schema = 3 version = 'v1.0.4' hash = 'sha256-gAnPRdGP4yna4hiRIEDyBtDOVJqd7RU27wlPu96Rdf8=' + [mod.'github.com/anotherhadi/ilovetui'] + version = 'v0.1.4' + hash = 'sha256-WOwCI4gqNnQvnQG2Miadx+St02IiImAX3B7ne7DdblA=' + [mod.'github.com/atotto/clipboard'] version = 'v0.1.4' hash = 'sha256-ZZ7U5X0gWOu8zcjZcWbcpzGOGdycwq0TjTFh/eZHjXk='