11 "github.com/zyedidia/glob"
12 "github.com/zyedidia/json5/encoding/json5"
15 type optionValidator func(string, interface{}) error
17 // The options that the user can set
18 var globalSettings map[string]interface{}
20 // Options with validators
21 var optionValidators = map[string]optionValidator{
22 "tabsize": validatePositiveValue,
23 "scrollmargin": validateNonNegativeValue,
24 "scrollspeed": validateNonNegativeValue,
25 "colorscheme": validateColorscheme,
26 "colorcolumn": validateNonNegativeValue,
29 // InitGlobalSettings initializes the options map and sets all options to their default values
30 func InitGlobalSettings() {
31 defaults := DefaultGlobalSettings()
32 var parsed map[string]interface{}
34 filename := configDir + "/settings.json"
35 writeSettings := false
36 if _, e := os.Stat(filename); e == nil {
37 input, err := ioutil.ReadFile(filename)
38 if !strings.HasPrefix(string(input), "null") {
40 TermMessage("Error reading settings.json file: " + err.Error())
44 err = json5.Unmarshal(input, &parsed)
46 TermMessage("Error reading settings.json:", err.Error())
53 globalSettings = make(map[string]interface{})
54 for k, v := range defaults {
57 for k, v := range parsed {
58 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
63 if _, err := os.Stat(filename); os.IsNotExist(err) || writeSettings {
64 err := WriteSettings(filename)
66 TermMessage("Error writing settings.json file: " + err.Error())
71 // InitLocalSettings scans the json in settings.json and sets the options locally based
72 // on whether the buffer matches the glob
73 func InitLocalSettings(buf *Buffer) {
74 var parsed map[string]interface{}
76 filename := configDir + "/settings.json"
77 if _, e := os.Stat(filename); e == nil {
78 input, err := ioutil.ReadFile(filename)
80 TermMessage("Error reading settings.json file: " + err.Error())
84 err = json5.Unmarshal(input, &parsed)
86 TermMessage("Error reading settings.json:", err.Error())
90 for k, v := range parsed {
91 if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
92 g, err := glob.Compile(k)
94 TermMessage("Error with glob setting ", k, ": ", err)
98 if g.MatchString(buf.Path) {
99 for k1, v1 := range v.(map[string]interface{}) {
100 buf.Settings[k1] = v1
107 // WriteSettings writes the settings to the specified filename as JSON
108 func WriteSettings(filename string) error {
110 if _, e := os.Stat(configDir); e == nil {
111 parsed := make(map[string]interface{})
113 filename := configDir + "/settings.json"
114 for k, v := range globalSettings {
117 if _, e := os.Stat(filename); e == nil {
118 input, err := ioutil.ReadFile(filename)
119 if string(input) != "null" {
124 err = json5.Unmarshal(input, &parsed)
126 TermMessage("Error reading settings.json:", err.Error())
129 for k, v := range parsed {
130 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
131 if _, ok := globalSettings[k]; ok {
132 parsed[k] = globalSettings[k]
139 txt, _ := json5.MarshalIndent(parsed, "", " ")
140 err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
145 // AddOption creates a new option. This is meant to be called by plugins to add options.
146 func AddOption(name string, value interface{}) {
147 globalSettings[name] = value
148 err := WriteSettings(configDir + "/settings.json")
150 TermMessage("Error writing settings.json file: " + err.Error())
154 // GetGlobalOption returns the global value of the given option
155 func GetGlobalOption(name string) interface{} {
156 return globalSettings[name]
159 // GetLocalOption returns the local value of the given option
160 func GetLocalOption(name string, buf *Buffer) interface{} {
161 return buf.Settings[name]
164 // GetOption returns the value of the given option
165 // If there is a local version of the option, it returns that
166 // otherwise it will return the global version
167 func GetOption(name string) interface{} {
168 if GetLocalOption(name, CurView().Buf) != nil {
169 return GetLocalOption(name, CurView().Buf)
171 return GetGlobalOption(name)
174 // DefaultGlobalSettings returns the default global settings for micro
175 // Note that colorscheme is a global only option
176 func DefaultGlobalSettings() map[string]interface{} {
177 return map[string]interface{}{
180 "colorcolumn": float64(0),
181 "colorscheme": "default",
189 "scrollspeed": float64(2),
190 "scrollmargin": float64(3),
193 "tabsize": float64(4),
194 "tabstospaces": false,
195 "pluginchannels": []string{
196 "https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
198 "pluginrepos": []string{},
202 // DefaultLocalSettings returns the default local settings
203 // Note that filetype is a local only option
204 func DefaultLocalSettings() map[string]interface{} {
205 return map[string]interface{}{
208 "colorcolumn": float64(0),
210 "filetype": "Unknown",
216 "scrollspeed": float64(2),
217 "scrollmargin": float64(3),
220 "tabsize": float64(4),
221 "tabstospaces": false,
225 // SetOption attempts to set the given option to the value
226 // By default it will set the option as global, but if the option
227 // is local only it will set the local version
228 // Use setlocal to force an option to be set locally
229 func SetOption(option, value string) error {
230 if _, ok := globalSettings[option]; !ok {
231 if _, ok := CurView().Buf.Settings[option]; !ok {
232 return errors.New("Invalid option")
234 SetLocalOption(option, value, CurView())
238 var nativeValue interface{}
240 kind := reflect.TypeOf(globalSettings[option]).Kind()
241 if kind == reflect.Bool {
242 b, err := ParseBool(value)
244 return errors.New("Invalid value")
247 } else if kind == reflect.String {
249 } else if kind == reflect.Float64 {
250 i, err := strconv.Atoi(value)
252 return errors.New("Invalid value")
254 nativeValue = float64(i)
256 return errors.New("Option has unsupported value type")
259 if err := optionIsValid(option, nativeValue); err != nil {
263 globalSettings[option] = nativeValue
265 if option == "colorscheme" {
267 for _, tab := range tabs {
268 for _, view := range tab.views {
269 view.Buf.UpdateRules()
270 if view.Buf.Settings["syntax"].(bool) {
271 view.matches = Match(view)
277 if option == "infobar" {
278 for _, tab := range tabs {
283 if _, ok := CurView().Buf.Settings[option]; ok {
284 for _, tab := range tabs {
285 for _, view := range tab.views {
286 SetLocalOption(option, value, view)
294 // SetLocalOption sets the local version of this option
295 func SetLocalOption(option, value string, view *View) error {
297 if _, ok := buf.Settings[option]; !ok {
298 return errors.New("Invalid option")
301 var nativeValue interface{}
303 kind := reflect.TypeOf(buf.Settings[option]).Kind()
304 if kind == reflect.Bool {
305 b, err := ParseBool(value)
307 return errors.New("Invalid value")
310 } else if kind == reflect.String {
312 } else if kind == reflect.Float64 {
313 i, err := strconv.Atoi(value)
315 return errors.New("Invalid value")
317 nativeValue = float64(i)
319 return errors.New("Option has unsupported value type")
322 if err := optionIsValid(option, nativeValue); err != nil {
326 buf.Settings[option] = nativeValue
328 if option == "statusline" {
329 view.ToggleStatusLine()
330 if buf.Settings["syntax"].(bool) {
331 view.matches = Match(view)
335 if option == "filetype" {
338 if buf.Settings["syntax"].(bool) {
339 view.matches = Match(view)
346 // SetOptionAndSettings sets the given option and saves the option setting to the settings config file
347 func SetOptionAndSettings(option, value string) {
348 filename := configDir + "/settings.json"
350 err := SetOption(option, value)
353 messenger.Error(err.Error())
357 err = WriteSettings(filename)
359 messenger.Error("Error writing to settings.json: " + err.Error())
364 func optionIsValid(option string, value interface{}) error {
365 if validator, ok := optionValidators[option]; ok {
366 return validator(option, value)
374 func validatePositiveValue(option string, value interface{}) error {
375 tabsize, ok := value.(float64)
378 return errors.New("Expected numeric type for " + option)
382 return errors.New(option + " must be greater than 0")
388 func validateNonNegativeValue(option string, value interface{}) error {
389 nativeValue, ok := value.(float64)
392 return errors.New("Expected numeric type for " + option)
396 return errors.New(option + " must be non-negative")
402 func validateColorscheme(option string, value interface{}) error {
403 colorscheme, ok := value.(string)
406 return errors.New("Expected string type for colorscheme")
409 if !ColorschemeExists(colorscheme) {
410 return errors.New(colorscheme + " is not a valid colorscheme")