mirror of
https://github.com/anotherhadi/ilovetui.git
synced 2026-08-22 12:35:48 +02:00
Change style package, new components, ...
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# style
|
||||
|
||||
Shared Base16 theming for bubbletea/lipgloss TUIs. Loaded automatically on import from
|
||||
`~/.config/ilovetui/config.yaml` (embedded default as fallback), exposed as the package-level
|
||||
`style.S`.
|
||||
|
||||
- `S.NerdFonts` and `S.BorderType` come from the same config as the colors. This package has no
|
||||
icon registry: components that want icons read `NerdFonts` and pick their own glyphs.
|
||||
- `RenderWithTitle` renders a bordered box with a title embedded in the top border, following
|
||||
`S.BorderType`.
|
||||
- Never imports `charm.land/bubbles/v2/*`. Anything that needs to know about a specific component
|
||||
belongs in `bubbles/` instead.
|
||||
|
||||
## Config
|
||||
|
||||
Copy [`default.yaml`](default.yaml) to `~/.config/ilovetui/config.yaml` and edit it.
|
||||
|
||||
See `examples/style`.
|
||||
@@ -0,0 +1,57 @@
|
||||
package style
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
var borderTypes = map[string]lipgloss.Border{
|
||||
"rounded": lipgloss.RoundedBorder(),
|
||||
"normal": lipgloss.NormalBorder(),
|
||||
"thick": lipgloss.ThickBorder(),
|
||||
"double": lipgloss.DoubleBorder(),
|
||||
"hidden": lipgloss.HiddenBorder(),
|
||||
"ascii": lipgloss.ASCIIBorder(),
|
||||
}
|
||||
|
||||
func resolveBorderType(name string) lipgloss.Border {
|
||||
if b, ok := borderTypes[strings.ToLower(strings.TrimSpace(name))]; ok {
|
||||
return b
|
||||
}
|
||||
return lipgloss.RoundedBorder()
|
||||
}
|
||||
|
||||
func ContentHeight(totalH int) int {
|
||||
h := totalH - 2
|
||||
if h < 0 {
|
||||
return 0
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
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])
|
||||
titleW := lipgloss.Width(title)
|
||||
|
||||
b, _, _, _, _ := border.GetBorder()
|
||||
topLeft, top, topRight := b.TopLeft, b.Top, b.TopRight
|
||||
|
||||
fillW := boxWidth - titleW - lipgloss.Width(topLeft) - lipgloss.Width(topRight) - 2
|
||||
if fillW < 0 {
|
||||
fillW = 0
|
||||
}
|
||||
bc := lipgloss.NewStyle().Foreground(border.GetBorderTopForeground())
|
||||
topLine := bc.Render(topLeft+" ") + bc.Render(title) + bc.Render(" "+strings.Repeat(top, fillW)+topRight)
|
||||
|
||||
return lipgloss.JoinVertical(lipgloss.Left, topLine, box)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package style
|
||||
|
||||
type colorsYAML struct {
|
||||
Base00 string `yaml:"base00"`
|
||||
Base01 string `yaml:"base01"`
|
||||
Base02 string `yaml:"base02"`
|
||||
Base03 string `yaml:"base03"`
|
||||
Base04 string `yaml:"base04"`
|
||||
Base05 string `yaml:"base05"`
|
||||
Base06 string `yaml:"base06"`
|
||||
Base07 string `yaml:"base07"`
|
||||
Base08 string `yaml:"base08"`
|
||||
Base09 string `yaml:"base09"`
|
||||
Base0A string `yaml:"base0a"`
|
||||
Base0B string `yaml:"base0b"`
|
||||
Base0C string `yaml:"base0c"`
|
||||
Base0D string `yaml:"base0d"`
|
||||
Base0E string `yaml:"base0e"`
|
||||
Base0F string `yaml:"base0f"`
|
||||
}
|
||||
|
||||
type configYAML struct {
|
||||
Colors colorsYAML `yaml:"colors"`
|
||||
NerdFonts bool `yaml:"nerd_fonts"`
|
||||
Border string `yaml:"border"`
|
||||
}
|
||||
|
||||
func pickString(base, user string) string {
|
||||
if user != "" {
|
||||
return user
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func mergeConfig(base, user configYAML) configYAML {
|
||||
return configYAML{
|
||||
Colors: mergeColors(base.Colors, user.Colors),
|
||||
|
||||
NerdFonts: base.NerdFonts || user.NerdFonts,
|
||||
Border: pickString(base.Border, user.Border),
|
||||
}
|
||||
}
|
||||
|
||||
func mergeColors(base, user colorsYAML) colorsYAML {
|
||||
return colorsYAML{
|
||||
Base00: pickString(base.Base00, user.Base00),
|
||||
Base01: pickString(base.Base01, user.Base01),
|
||||
Base02: pickString(base.Base02, user.Base02),
|
||||
Base03: pickString(base.Base03, user.Base03),
|
||||
Base04: pickString(base.Base04, user.Base04),
|
||||
Base05: pickString(base.Base05, user.Base05),
|
||||
Base06: pickString(base.Base06, user.Base06),
|
||||
Base07: pickString(base.Base07, user.Base07),
|
||||
Base08: pickString(base.Base08, user.Base08),
|
||||
Base09: pickString(base.Base09, user.Base09),
|
||||
Base0A: pickString(base.Base0A, user.Base0A),
|
||||
Base0B: pickString(base.Base0B, user.Base0B),
|
||||
Base0C: pickString(base.Base0C, user.Base0C),
|
||||
Base0D: pickString(base.Base0D, user.Base0D),
|
||||
Base0E: pickString(base.Base0E, user.Base0E),
|
||||
Base0F: pickString(base.Base0F, user.Base0F),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
# ilovetui default config
|
||||
# Copy to ~/.config/ilovetui/config.yaml and edit to customize.
|
||||
|
||||
# Whether components may use Nerd Font glyphs (requires a patched font).
|
||||
# Leave false for maximum terminal/font compatibility.
|
||||
nerd_fonts: false
|
||||
|
||||
# Border style used by panels across all ilovetui-based TUIs.
|
||||
# One of: rounded, normal, thick, double, hidden, ascii.
|
||||
border: rounded
|
||||
|
||||
colors:
|
||||
base00: "#110F12" # Background
|
||||
base01: "#1C1920" # Lighter Background / Status Bars
|
||||
base02: "#1D1A26" # Selection Background
|
||||
base03: "#514D63" # Comments / Invisibles
|
||||
base04: "#8E8AA0" # Dark Foreground / Status Bars
|
||||
base05: "#C2BED6" # Default Foreground
|
||||
base06: "#D8D5EA" # Light Foreground
|
||||
base07: "#EAE7F7" # Light Background
|
||||
base08: "#E07080" # Variables / Errors / Diff Deleted
|
||||
base09: "#D49070" # Integers / Constants / Booleans
|
||||
base0a: "#C4B060" # Classes / Warnings / Search Background
|
||||
base0b: "#80B880" # Strings / Success / Diff Inserted
|
||||
base0c: "#70B8C0" # Support / Regex / Escape Characters
|
||||
base0d: "#9E97F8" # Functions / Methods / Headings / Accent
|
||||
base0e: "#C090E8" # Keywords / Storage / Diff Changed
|
||||
base0f: "#D080A0" # Embedded / Misc
|
||||
@@ -0,0 +1,241 @@
|
||||
package style
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
|
||||
"charm.land/glamour/v2/ansi"
|
||||
)
|
||||
|
||||
func hexColor(c color.Color) *string {
|
||||
r, g, b, _ := c.RGBA()
|
||||
s := fmt.Sprintf("#%02X%02X%02X", r>>8, g>>8, b>>8)
|
||||
return &s
|
||||
}
|
||||
|
||||
func GlamourStyleConfig() ansi.StyleConfig {
|
||||
str := func(s string) *string { return &s }
|
||||
boolPtr := func(b bool) *bool { return &b }
|
||||
uintPtr := func(u uint) *uint { return &u }
|
||||
hex := hexColor
|
||||
|
||||
return ansi.StyleConfig{
|
||||
Document: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
BlockPrefix: "\n",
|
||||
BlockSuffix: "\n",
|
||||
Color: hex(S.Base05),
|
||||
},
|
||||
Margin: uintPtr(2),
|
||||
},
|
||||
BlockQuote: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Color: hex(S.Base03),
|
||||
Italic: boolPtr(true),
|
||||
},
|
||||
Indent: uintPtr(1),
|
||||
IndentToken: str("│ "),
|
||||
},
|
||||
List: ansi.StyleList{
|
||||
LevelIndent: 2,
|
||||
},
|
||||
Heading: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
BlockSuffix: "\n",
|
||||
Color: hex(S.Base0D),
|
||||
Bold: boolPtr(true),
|
||||
},
|
||||
},
|
||||
H1: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Prefix: " ",
|
||||
Suffix: " ",
|
||||
Color: hex(S.Base07),
|
||||
BackgroundColor: hex(S.Base0D),
|
||||
Bold: boolPtr(true),
|
||||
},
|
||||
},
|
||||
H2: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Prefix: "## ",
|
||||
Color: hex(S.Base0D),
|
||||
Bold: boolPtr(true),
|
||||
},
|
||||
},
|
||||
H3: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Prefix: "### ",
|
||||
Color: hex(S.Base0C),
|
||||
},
|
||||
},
|
||||
H4: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Prefix: "#### ",
|
||||
Color: hex(S.Base0B),
|
||||
},
|
||||
},
|
||||
H5: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Prefix: "##### ",
|
||||
Color: hex(S.Base09),
|
||||
},
|
||||
},
|
||||
H6: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Prefix: "###### ",
|
||||
Color: hex(S.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(S.Base03),
|
||||
Format: "\n--------\n",
|
||||
},
|
||||
Item: ansi.StylePrimitive{
|
||||
BlockPrefix: "• ",
|
||||
},
|
||||
Enumeration: ansi.StylePrimitive{
|
||||
BlockPrefix: ". ",
|
||||
},
|
||||
Task: ansi.StyleTask{
|
||||
Ticked: "[✓] ",
|
||||
Unticked: "[ ] ",
|
||||
},
|
||||
Link: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0C),
|
||||
Underline: boolPtr(true),
|
||||
},
|
||||
LinkText: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0D),
|
||||
Bold: boolPtr(true),
|
||||
},
|
||||
Image: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0C),
|
||||
Underline: boolPtr(true),
|
||||
},
|
||||
ImageText: ansi.StylePrimitive{
|
||||
Color: hex(S.Base04),
|
||||
Format: "Image: {{.text}} ->",
|
||||
},
|
||||
Code: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Prefix: " ",
|
||||
Suffix: " ",
|
||||
Color: hex(S.Base0B),
|
||||
BackgroundColor: hex(S.Base01),
|
||||
},
|
||||
},
|
||||
CodeBlock: ansi.StyleCodeBlock{
|
||||
StyleBlock: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{
|
||||
Color: hex(S.Base04),
|
||||
},
|
||||
Margin: uintPtr(2),
|
||||
},
|
||||
Chroma: &ansi.Chroma{
|
||||
Text: ansi.StylePrimitive{
|
||||
Color: hex(S.Base05),
|
||||
},
|
||||
Error: ansi.StylePrimitive{
|
||||
Color: hex(S.Base07),
|
||||
BackgroundColor: hex(S.Base08),
|
||||
},
|
||||
Comment: ansi.StylePrimitive{
|
||||
Color: hex(S.Base03),
|
||||
Italic: boolPtr(true),
|
||||
},
|
||||
CommentPreproc: ansi.StylePrimitive{
|
||||
Color: hex(S.Base09),
|
||||
},
|
||||
Keyword: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0E),
|
||||
},
|
||||
KeywordReserved: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0E),
|
||||
},
|
||||
KeywordNamespace: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0D),
|
||||
},
|
||||
KeywordType: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0A),
|
||||
},
|
||||
Operator: ansi.StylePrimitive{
|
||||
Color: hex(S.Base05),
|
||||
},
|
||||
Punctuation: ansi.StylePrimitive{
|
||||
Color: hex(S.Base05),
|
||||
},
|
||||
Name: ansi.StylePrimitive{
|
||||
Color: hex(S.Base05),
|
||||
},
|
||||
NameBuiltin: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0D),
|
||||
},
|
||||
NameTag: ansi.StylePrimitive{
|
||||
Color: hex(S.Base08),
|
||||
},
|
||||
NameAttribute: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0A),
|
||||
},
|
||||
NameClass: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0A),
|
||||
Bold: boolPtr(true),
|
||||
Underline: boolPtr(true),
|
||||
},
|
||||
NameConstant: ansi.StylePrimitive{
|
||||
Color: hex(S.Base09),
|
||||
},
|
||||
NameDecorator: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0C),
|
||||
},
|
||||
NameFunction: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0D),
|
||||
},
|
||||
LiteralNumber: ansi.StylePrimitive{
|
||||
Color: hex(S.Base09),
|
||||
},
|
||||
LiteralString: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0B),
|
||||
},
|
||||
LiteralStringEscape: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0C),
|
||||
},
|
||||
GenericDeleted: ansi.StylePrimitive{
|
||||
Color: hex(S.Base08),
|
||||
},
|
||||
GenericEmph: ansi.StylePrimitive{
|
||||
Italic: boolPtr(true),
|
||||
},
|
||||
GenericInserted: ansi.StylePrimitive{
|
||||
Color: hex(S.Base0B),
|
||||
},
|
||||
GenericStrong: ansi.StylePrimitive{
|
||||
Bold: boolPtr(true),
|
||||
},
|
||||
GenericSubheading: ansi.StylePrimitive{
|
||||
Color: hex(S.Base04),
|
||||
},
|
||||
Background: ansi.StylePrimitive{
|
||||
BackgroundColor: hex(S.Base01),
|
||||
},
|
||||
},
|
||||
},
|
||||
Table: ansi.StyleTable{
|
||||
StyleBlock: ansi.StyleBlock{
|
||||
StylePrimitive: ansi.StylePrimitive{},
|
||||
},
|
||||
},
|
||||
DefinitionDescription: ansi.StylePrimitive{
|
||||
BlockPrefix: "\n> ",
|
||||
},
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
package style
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
type Styles struct {
|
||||
Base00 color.Color
|
||||
Base01 color.Color
|
||||
Base02 color.Color
|
||||
Base03 color.Color
|
||||
Base04 color.Color
|
||||
Base05 color.Color
|
||||
Base06 color.Color
|
||||
Base07 color.Color
|
||||
Base08 color.Color
|
||||
Base09 color.Color
|
||||
Base0A color.Color
|
||||
Base0B color.Color
|
||||
Base0C color.Color
|
||||
Base0D color.Color
|
||||
Base0E color.Color
|
||||
Base0F color.Color
|
||||
|
||||
Background color.Color
|
||||
SubtleBg color.Color
|
||||
Selection color.Color
|
||||
Subtle color.Color
|
||||
Muted color.Color
|
||||
Text color.Color
|
||||
Primary color.Color
|
||||
Success color.Color
|
||||
Warning color.Color
|
||||
Error color.Color
|
||||
|
||||
Bold lipgloss.Style
|
||||
Faint lipgloss.Style
|
||||
|
||||
NerdFonts bool
|
||||
BorderType lipgloss.Border
|
||||
|
||||
Panel lipgloss.Style
|
||||
PanelFocused lipgloss.Style
|
||||
|
||||
PagerDotActive string
|
||||
PagerDotInactive string
|
||||
}
|
||||
|
||||
func newStyles(c colorsYAML, nerdFonts bool, borderName string) Styles {
|
||||
lc := func(s string) color.Color {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" && s[0] != '#' {
|
||||
s = "#" + s
|
||||
}
|
||||
return lipgloss.Color(s)
|
||||
}
|
||||
|
||||
b00 := lc(c.Base00)
|
||||
b01 := lc(c.Base01)
|
||||
b02 := lc(c.Base02)
|
||||
b03 := lc(c.Base03)
|
||||
b04 := lc(c.Base04)
|
||||
b05 := lc(c.Base05)
|
||||
b06 := lc(c.Base06)
|
||||
b07 := lc(c.Base07)
|
||||
b08 := lc(c.Base08)
|
||||
b09 := lc(c.Base09)
|
||||
b0A := lc(c.Base0A)
|
||||
b0B := lc(c.Base0B)
|
||||
b0C := lc(c.Base0C)
|
||||
b0D := lc(c.Base0D)
|
||||
b0E := lc(c.Base0E)
|
||||
b0F := lc(c.Base0F)
|
||||
|
||||
borderType := resolveBorderType(borderName)
|
||||
|
||||
return Styles{
|
||||
Base00: b00, Base01: b01, Base02: b02, Base03: b03,
|
||||
Base04: b04, Base05: b05, Base06: b06, Base07: b07,
|
||||
Base08: b08, Base09: b09, Base0A: b0A, Base0B: b0B,
|
||||
Base0C: b0C, Base0D: b0D, Base0E: b0E, Base0F: b0F,
|
||||
|
||||
Background: b00,
|
||||
SubtleBg: b01,
|
||||
Selection: b02,
|
||||
Subtle: b03,
|
||||
Muted: b04,
|
||||
Text: b05,
|
||||
Primary: b0D,
|
||||
Success: b0B,
|
||||
Warning: b09,
|
||||
Error: b08,
|
||||
|
||||
Bold: lipgloss.NewStyle().Bold(true),
|
||||
Faint: lipgloss.NewStyle().Foreground(b03).Faint(true),
|
||||
|
||||
NerdFonts: nerdFonts,
|
||||
BorderType: borderType,
|
||||
|
||||
Panel: lipgloss.NewStyle().
|
||||
Border(borderType).
|
||||
BorderForeground(b03),
|
||||
|
||||
PanelFocused: lipgloss.NewStyle().
|
||||
Border(borderType).
|
||||
BorderForeground(b0D),
|
||||
|
||||
PagerDotActive: lipgloss.NewStyle().Foreground(b0D).SetString("•").String(),
|
||||
PagerDotInactive: lipgloss.NewStyle().Foreground(b03).SetString("•").String(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package style
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed default.yaml
|
||||
var DefaultConfig []byte
|
||||
|
||||
var S Styles
|
||||
|
||||
func init() {
|
||||
path := DefaultConfigPath()
|
||||
if data, err := os.ReadFile(path); err == nil {
|
||||
if s, err := stylesFromBytes(data); err == nil {
|
||||
S = s
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
s, _ := stylesFromBytes(DefaultConfig)
|
||||
S = s
|
||||
}
|
||||
|
||||
func Init() error {
|
||||
path := DefaultConfigPath()
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
s, e := stylesFromBytes(DefaultConfig)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
S = s
|
||||
return nil
|
||||
}
|
||||
return InitFromBytes(data)
|
||||
}
|
||||
|
||||
func InitFrom(path string) error {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("style: read config: %w", err)
|
||||
}
|
||||
return InitFromBytes(data)
|
||||
}
|
||||
|
||||
func InitFromBytes(data []byte) error {
|
||||
s, err := stylesFromBytes(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
S = s
|
||||
return nil
|
||||
}
|
||||
|
||||
func DefaultConfigPath() string {
|
||||
return filepath.Join(configDir(), "ilovetui", "config.yaml")
|
||||
}
|
||||
|
||||
func stylesFromBytes(data []byte) (Styles, error) {
|
||||
var base configYAML
|
||||
if err := yaml.Unmarshal(DefaultConfig, &base); err != nil {
|
||||
return Styles{}, fmt.Errorf("style: parse default config: %w", err)
|
||||
}
|
||||
var user configYAML
|
||||
if err := yaml.Unmarshal(data, &user); err != nil {
|
||||
return Styles{}, fmt.Errorf("style: parse config: %w", err)
|
||||
}
|
||||
merged := mergeConfig(base, user)
|
||||
return newStyles(merged.Colors, merged.NerdFonts, merged.Border), nil
|
||||
}
|
||||
|
||||
func configDir() string {
|
||||
if dir := os.Getenv("XDG_CONFIG_HOME"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
home, _ := os.UserHomeDir()
|
||||
return filepath.Join(home, ".config")
|
||||
}
|
||||
Reference in New Issue
Block a user