149 lines
3.5 KiB
Go
149 lines
3.5 KiB
Go
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
|
|
}
|