package theme import ( "embed" "encoding/json" "fmt" "io/fs" "path/filepath" "sort" "strings" "github.com/charmbracelet/lipgloss" ) //go:embed themes/*.json var embeddedThemes embed.FS // LoadEmbeddedThemesJSON reads all embedded theme JSON files and unmarshals // them into ThemeJSON objects keyed by theme name. func LoadEmbeddedThemesJSON() (map[string]ThemeJSON, error) { paths, err := fs.Glob(embeddedThemes, "themes/*.json") if err != nil { return nil, err } sort.Strings(paths) out := make(map[string]ThemeJSON, len(paths)) for _, path := range paths { b, readErr := embeddedThemes.ReadFile(path) if readErr != nil { return nil, fmt.Errorf("read embedded theme %q: %w", path, readErr) } var th ThemeJSON if unmarshalErr := json.Unmarshal(b, &th); unmarshalErr != nil { return nil, fmt.Errorf("decode embedded theme %q: %w", path, unmarshalErr) } if strings.TrimSpace(th.Name) == "" { th.Name = strings.TrimSuffix(filepath.Base(path), ".json") } out[th.Name] = th } return out, nil } func MapEmbeddedThemeToEditorTheme(em map[string]ThemeJSON) map[string]EditorTheme { out := make(map[string]EditorTheme, len(em)) for name, in := range em { line := styleFromJSON(in.Line) lineBg := colorString(in.Line.BG) syntaxExact := make(map[string]lipgloss.Style, len(in.Syntax.Exact)) for capture, col := range in.Syntax.Exact { c := normalizeCaptureKey(capture) if c == "" { continue } syntaxExact[c] = syntaxColorStyle(col, lineBg) } syntaxGroup := make(map[string]lipgloss.Style, len(in.Syntax.Group)) for group, col := range in.Syntax.Group { g := normalizeCaptureKey(group) if g == "" { continue } syntaxGroup[g] = syntaxColorStyle(col, lineBg) } key := strings.TrimSpace(name) if key == "" { key = strings.TrimSpace(in.Name) } if key == "" { continue } out[key] = EditorTheme{ Cursors: CursorTheme{ Normal: styleFromJSON(in.Cursors.Normal), Insert: styleFromJSON(in.Cursors.Insert), Command: styleFromJSON(in.Cursors.Command), Replace: styleFromJSON(in.Cursors.Replace), }, Gutter: GutterTheme{ Default: styleFromJSON(in.Gutter.Default), CurrentLine: styleFromJSON(in.Gutter.CurrentLine), }, VisualHightlight: styleFromJSON(in.VisualHighlight), StatusBar: StatusBarTheme{ Default: styleFromJSON(in.StatusBar.Default), }, CommandLine: CommandLineTheme{ Error: styleFromJSON(in.CommandLine.Error), OutputBorder: styleFromJSON(in.CommandLine.OutputBorder), ContinueMessage: styleFromJSON(in.CommandLine.ContinueMessage), }, Line: line, Background: styleFromJSON(in.Background), Syntax: SyntaxTheme{ Exact: syntaxExact, Group: syntaxGroup, }, } } return out } func styleFromJSON(in ColorStyleJSON) lipgloss.Style { out := lipgloss.NewStyle() if fg := colorString(in.FG); fg != "" { out = out.Foreground(lipgloss.Color(fg)) } if bg := colorString(in.BG); bg != "" { out = out.Background(lipgloss.Color(bg)) } return out } func colorString(c string) string { return strings.TrimSpace(c) } func normalizeCaptureKey(k string) string { k = strings.TrimSpace(strings.ToLower(k)) k = strings.TrimPrefix(k, "@") return k } func syntaxColorStyle(fg, bg string) lipgloss.Style { out := lipgloss.NewStyle() if f := colorString(fg); f != "" { out = out.Foreground(lipgloss.Color(f)) } if b := colorString(bg); b != "" { out = out.Background(lipgloss.Color(b)) } return out }