]> git.lizzy.rs Git - micro.git/blob - internal/config/colorscheme.go
Fix regression: non-working direct colors in syntax files (#2252)
[micro.git] / internal / config / colorscheme.go
1 package config
2
3 import (
4         "errors"
5         "regexp"
6         "strconv"
7         "strings"
8
9         "github.com/zyedidia/tcell/v2"
10 )
11
12 // DefStyle is Micro's default style
13 var DefStyle tcell.Style = tcell.StyleDefault
14
15 // Colorscheme is the current colorscheme
16 var Colorscheme map[string]tcell.Style
17
18 // GetColor takes in a syntax group and returns the colorscheme's style for that group
19 func GetColor(color string) tcell.Style {
20         st := DefStyle
21         if color == "" {
22                 return st
23         }
24         groups := strings.Split(color, ".")
25         if len(groups) > 1 {
26                 curGroup := ""
27                 for i, g := range groups {
28                         if i != 0 {
29                                 curGroup += "."
30                         }
31                         curGroup += g
32                         if style, ok := Colorscheme[curGroup]; ok {
33                                 st = style
34                         }
35                 }
36         } else if style, ok := Colorscheme[color]; ok {
37                 st = style
38         } else {
39                 st = StringToStyle(color)
40         }
41
42         return st
43 }
44
45 // ColorschemeExists checks if a given colorscheme exists
46 func ColorschemeExists(colorschemeName string) bool {
47         return FindRuntimeFile(RTColorscheme, colorschemeName) != nil
48 }
49
50 // InitColorscheme picks and initializes the colorscheme when micro starts
51 func InitColorscheme() error {
52         Colorscheme = make(map[string]tcell.Style)
53         DefStyle = tcell.StyleDefault
54
55         return LoadDefaultColorscheme()
56 }
57
58 // LoadDefaultColorscheme loads the default colorscheme from $(ConfigDir)/colorschemes
59 func LoadDefaultColorscheme() error {
60         return LoadColorscheme(GlobalSettings["colorscheme"].(string))
61 }
62
63 // LoadColorscheme loads the given colorscheme from a directory
64 func LoadColorscheme(colorschemeName string) error {
65         file := FindRuntimeFile(RTColorscheme, colorschemeName)
66         if file == nil {
67                 return errors.New(colorschemeName + " is not a valid colorscheme")
68         }
69         if data, err := file.Data(); err != nil {
70                 return errors.New("Error loading colorscheme: " + err.Error())
71         } else {
72                 Colorscheme, err = ParseColorscheme(string(data))
73                 if err != nil {
74                         return err
75                 }
76         }
77         return nil
78 }
79
80 // ParseColorscheme parses the text definition for a colorscheme and returns the corresponding object
81 // Colorschemes are made up of color-link statements linking a color group to a list of colors
82 // For example, color-link keyword (blue,red) makes all keywords have a blue foreground and
83 // red background
84 func ParseColorscheme(text string) (map[string]tcell.Style, error) {
85         var err error
86         parser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
87
88         lines := strings.Split(text, "\n")
89
90         c := make(map[string]tcell.Style)
91
92         for _, line := range lines {
93                 if strings.TrimSpace(line) == "" ||
94                         strings.TrimSpace(line)[0] == '#' {
95                         // Ignore this line
96                         continue
97                 }
98
99                 matches := parser.FindSubmatch([]byte(line))
100                 if len(matches) == 3 {
101                         link := string(matches[1])
102                         colors := string(matches[2])
103
104                         style := StringToStyle(colors)
105                         c[link] = style
106
107                         if link == "default" {
108                                 DefStyle = style
109                         }
110                 } else {
111                         err = errors.New("Color-link statement is not valid: " + line)
112                 }
113         }
114
115         return c, err
116 }
117
118 // StringToStyle returns a style from a string
119 // The strings must be in the format "extra foregroundcolor,backgroundcolor"
120 // The 'extra' can be bold, reverse, italic or underline
121 func StringToStyle(str string) tcell.Style {
122         var fg, bg string
123         spaceSplit := strings.Split(str, " ")
124         split := strings.Split(spaceSplit[len(spaceSplit)-1], ",")
125         if len(split) > 1 {
126                 fg, bg = split[0], split[1]
127         } else {
128                 fg = split[0]
129         }
130         fg = strings.TrimSpace(fg)
131         bg = strings.TrimSpace(bg)
132
133         var fgColor, bgColor tcell.Color
134         var ok bool
135         if fg == "" || fg == "default" {
136                 fgColor, _, _ = DefStyle.Decompose()
137         } else {
138                 fgColor, ok = StringToColor(fg)
139                 if !ok {
140                         fgColor, _, _ = DefStyle.Decompose()
141                 }
142         }
143         if bg == "" || bg == "default" {
144                 _, bgColor, _ = DefStyle.Decompose()
145         } else {
146                 bgColor, ok = StringToColor(bg)
147                 if !ok {
148                         _, bgColor, _ = DefStyle.Decompose()
149                 }
150         }
151
152         style := DefStyle.Foreground(fgColor).Background(bgColor)
153         if strings.Contains(str, "bold") {
154                 style = style.Bold(true)
155         }
156         if strings.Contains(str, "italic") {
157                 style = style.Italic(true)
158         }
159         if strings.Contains(str, "reverse") {
160                 style = style.Reverse(true)
161         }
162         if strings.Contains(str, "underline") {
163                 style = style.Underline(true)
164         }
165         return style
166 }
167
168 // StringToColor returns a tcell color from a string representation of a color
169 // We accept either bright... or light... to mean the brighter version of a color
170 func StringToColor(str string) (tcell.Color, bool) {
171         switch str {
172         case "black":
173                 return tcell.ColorBlack, true
174         case "red":
175                 return tcell.ColorMaroon, true
176         case "green":
177                 return tcell.ColorGreen, true
178         case "yellow":
179                 return tcell.ColorOlive, true
180         case "blue":
181                 return tcell.ColorNavy, true
182         case "magenta":
183                 return tcell.ColorPurple, true
184         case "cyan":
185                 return tcell.ColorTeal, true
186         case "white":
187                 return tcell.ColorSilver, true
188         case "brightblack", "lightblack":
189                 return tcell.ColorGray, true
190         case "brightred", "lightred":
191                 return tcell.ColorRed, true
192         case "brightgreen", "lightgreen":
193                 return tcell.ColorLime, true
194         case "brightyellow", "lightyellow":
195                 return tcell.ColorYellow, true
196         case "brightblue", "lightblue":
197                 return tcell.ColorBlue, true
198         case "brightmagenta", "lightmagenta":
199                 return tcell.ColorFuchsia, true
200         case "brightcyan", "lightcyan":
201                 return tcell.ColorAqua, true
202         case "brightwhite", "lightwhite":
203                 return tcell.ColorWhite, true
204         case "default":
205                 return tcell.ColorDefault, true
206         default:
207                 // Check if this is a 256 color
208                 if num, err := strconv.Atoi(str); err == nil {
209                         return GetColor256(num), true
210                 }
211                 // Check if this is a truecolor hex value
212                 if len(str) == 7 && str[0] == '#' {
213                         return tcell.GetColor(str), true
214                 }
215                 return tcell.ColorDefault, false
216         }
217 }
218
219 // GetColor256 returns the tcell color for a number between 0 and 255
220 func GetColor256(color int) tcell.Color {
221         if color == 0 {
222                 return tcell.ColorDefault
223         }
224         return tcell.PaletteColor(color)
225 }