From c877b2a5cb34670b43456a93af6d7a226d30968e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 27 Feb 2023 08:00:13 +0530 Subject: [PATCH] Code to dump basic colors from a theme as escape codes --- gen-go-code.py | 15 ++++- tools/themes/collection.go | 102 ++++++++++++++++++++++++++++---- tools/themes/collection_test.go | 4 +- tools/utils/style/wrapper.go | 10 ++++ 4 files changed, 118 insertions(+), 13 deletions(-) diff --git a/gen-go-code.py b/gen-go-code.py index 1c1dda004..4c5097b20 100755 --- a/gen-go-code.py +++ b/gen-go-code.py @@ -427,11 +427,24 @@ def generate_spinners() -> str: def generate_color_names() -> str: + selfg = "" if Options.selection_foreground is None else Options.selection_foreground.as_sharp + selbg = "" if Options.selection_background is None else Options.selection_background.as_sharp + cursor = "" if Options.cursor is None else Options.cursor.as_sharp return 'package style\n\nvar ColorNames = map[string]RGBA{' + '\n'.join( f'\t"{name}": RGBA{{ Red:{val.red}, Green:{val.green}, Blue:{val.blue} }},' for name, val in color_names.items() ) + '\n}' + '\n\nvar ColorTable = [256]uint32{' + ', '.join( - f'{x}' for x in Options.color_table) + '}\n' + f'{x}' for x in Options.color_table) + '}\n' + f''' +var DefaultColors = struct {{ +Foreground, Background, Cursor, SelectionFg, SelectionBg string +}}{{ +Foreground: "{Options.foreground.as_sharp}", +Background: "{Options.background.as_sharp}", +Cursor: "{cursor}", +SelectionFg: "{selfg}", +SelectionBg: "{selbg}", +}} +''' def load_ref_map() -> Dict[str, Dict[str, str]]: diff --git a/tools/themes/collection.go b/tools/themes/collection.go index 4dcd4eb0b..2d8945a2a 100644 --- a/tools/themes/collection.go +++ b/tools/themes/collection.go @@ -4,6 +4,7 @@ package themes import ( "archive/zip" + "bufio" "encoding/json" "errors" "fmt" @@ -17,6 +18,7 @@ import ( "path" "path/filepath" "regexp" + "strconv" "strings" "time" @@ -140,12 +142,12 @@ type ThemeMetadata struct { Author string `json:"author"` } -func parse_theme_metadata(path string) (*ThemeMetadata, string, error) { +func parse_theme_metadata(path string) (*ThemeMetadata, map[string]string, error) { var in_metadata, in_blurb, finished_metadata bool ans := ThemeMetadata{} - settings := utils.NewSet[string]() + settings := map[string]string{} read_is_dark := func(key, val string) (err error) { - settings.Add(key) + settings[key] = val if key == "background" { val = strings.TrimSpace(val) if val != "" { @@ -197,25 +199,25 @@ func parse_theme_metadata(path string) (*ThemeMetadata, string, error) { } return } - source := "" - cp := config.ConfigParser{LineHandler: read_is_dark, CommentsHandler: read_metadata, SourceHandler: func(code, path string) { source = code }} + cp := config.ConfigParser{LineHandler: read_is_dark, CommentsHandler: read_metadata} err := cp.ParseFiles(path) if err != nil { - return nil, "", err + return nil, nil, err } - ans.Num_settings = settings.Len() - return &ans, source, nil + ans.Num_settings = len(settings) + return &ans, settings, nil } type Theme struct { metadata *ThemeMetadata code string + settings map[string]string zip_reader *zip.File is_user_defined bool } -func (self *Theme) Code() (string, error) { +func (self *Theme) load_code() (string, error) { if self.zip_reader != nil { f, err := self.zip_reader.Open() self.zip_reader = nil @@ -232,6 +234,86 @@ func (self *Theme) Code() (string, error) { return self.code, nil } +func (self *Theme) Settings() (map[string]string, error) { + if self.zip_reader != nil { + code, err := self.load_code() + if err != nil { + return nil, err + } + self.settings = make(map[string]string, 64) + scanner := bufio.NewScanner(strings.NewReader(code)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line != "" && line[0] != '#' { + key, val, found := strings.Cut(line, " ") + if found { + self.settings[key] = val + } + } + } + } + return self.settings, nil +} + +func (self *Theme) AsEscapeCodes() (string, error) { + settings, err := self.Settings() + if err != nil { + return "", err + } + w := strings.Builder{} + w.Grow(4096) + + w.WriteString("\033]4") + set_color := func(i int, sharp string) { + w.WriteByte(';') + w.WriteString(strconv.Itoa(i)) + w.WriteByte(';') + w.WriteString(sharp) + } + + set_default_color := func(name, defval string, num int) { + w.WriteString("\033]") + defer func() { w.WriteString("\033\\") }() + val, found := settings[name] + if !found { + val = defval + } + if val != "" { + rgba, err := style.ParseColor(val) + if err == nil { + w.WriteString(strconv.Itoa(num)) + w.WriteByte(';') + w.WriteString(rgba.AsRGBSharp()) + return + } + } + w.WriteByte('1') + w.WriteString(strconv.Itoa(num)) + } + set_default_color("foreground", style.DefaultColors.Foreground, 10) + set_default_color("background", style.DefaultColors.Background, 11) + set_default_color("cursor", style.DefaultColors.Cursor, 12) + set_default_color("selection_background", style.DefaultColors.SelectionBg, 17) + set_default_color("selection_foreground", style.DefaultColors.SelectionFg, 19) + + for i := 0; i < 256; i++ { + key := "color" + strconv.Itoa(i) + val := settings[key] + if val != "" { + rgba, err := style.ParseColor(val) + if err == nil { + set_color(i, rgba.AsRGBSharp()) + continue + } + } + rgba := style.RGBA{} + rgba.FromRGB(style.ColorTable[i]) + set_color(i, rgba.AsRGBSharp()) + } + w.WriteString("\033\\") + return w.String(), nil +} + type Themes struct { name_map map[string]*Theme index_map []string @@ -265,7 +347,7 @@ func (self *Themes) add_from_dir(dirpath string) error { if m.Name == "" { m.Name = theme_name_from_file_name(e.Name()) } - t := Theme{metadata: m, is_user_defined: true, code: conf} + t := Theme{metadata: m, is_user_defined: true, settings: conf} self.name_map[m.Name] = &t } } diff --git a/tools/themes/collection_test.go b/tools/themes/collection_test.go index db9f8e1de..c8b21c30b 100644 --- a/tools/themes/collection_test.go +++ b/tools/themes/collection_test.go @@ -142,13 +142,13 @@ func TestThemeCollections(t *testing.T) { t.Fatal(err) } defer closer.Close() - if code, err := coll.ThemeByName("Empty").Code(); code != "empty" { + if code, err := coll.ThemeByName("Empty").load_code(); code != "empty" { if err != nil { t.Fatal(err) } t.Fatal("failed to load code for empty theme") } - if code, err := coll.ThemeByName("Alabaster Dark").Code(); code != "alabaster" { + if code, err := coll.ThemeByName("Alabaster Dark").load_code(); code != "alabaster" { if err != nil { t.Fatal(err) } diff --git a/tools/utils/style/wrapper.go b/tools/utils/style/wrapper.go index fd94c8a7a..e8cdf969f 100644 --- a/tools/utils/style/wrapper.go +++ b/tools/utils/style/wrapper.go @@ -57,6 +57,10 @@ type RGBA struct { Red, Green, Blue, Inverse_alpha uint8 } +func (self RGBA) AsRGBSharp() string { + return fmt.Sprintf("#%02x%02x%02x", self.Red, self.Green, self.Blue) +} + func (self *RGBA) parse_rgb_strings(r string, g string, b string) bool { var rv, gv, bv uint64 var err error @@ -77,6 +81,12 @@ func (self *RGBA) AsRGB() uint32 { return uint32(self.Blue) | (uint32(self.Green) << 8) | (uint32(self.Red) << 16) } +func (self *RGBA) FromRGB(col uint32) { + self.Red = uint8((col >> 16) & 0xff) + self.Green = uint8((col >> 8) & 0xff) + self.Blue = uint8((col) & 0xff) +} + type color_type struct { is_numbered bool val RGBA