13 "github.com/flynn/json5"
14 "github.com/zyedidia/glob"
17 type optionValidator func(string, interface{}) error
19 // The options that the user can set
20 var globalSettings map[string]interface{}
22 var invalidSettings bool
24 // Options with validators
25 var optionValidators = map[string]optionValidator{
26 "tabsize": validatePositiveValue,
27 "scrollmargin": validateNonNegativeValue,
28 "scrollspeed": validateNonNegativeValue,
29 "colorscheme": validateColorscheme,
30 "colorcolumn": validateNonNegativeValue,
31 "fileformat": validateLineEnding,
34 // InitGlobalSettings initializes the options map and sets all options to their default values
35 func InitGlobalSettings() {
36 invalidSettings = false
37 defaults := DefaultGlobalSettings()
38 var parsed map[string]interface{}
40 filename := configDir + "/settings.json"
41 writeSettings := false
42 if _, e := os.Stat(filename); e == nil {
43 input, err := ioutil.ReadFile(filename)
44 if !strings.HasPrefix(string(input), "null") {
46 TermMessage("Error reading settings.json file: " + err.Error())
47 invalidSettings = true
51 err = json5.Unmarshal(input, &parsed)
53 TermMessage("Error reading settings.json:", err.Error())
54 invalidSettings = true
61 globalSettings = make(map[string]interface{})
62 for k, v := range defaults {
65 for k, v := range parsed {
66 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
71 if _, err := os.Stat(filename); os.IsNotExist(err) || writeSettings {
72 err := WriteSettings(filename)
74 TermMessage("Error writing settings.json file: " + err.Error())
79 // InitLocalSettings scans the json in settings.json and sets the options locally based
80 // on whether the buffer matches the glob
81 func InitLocalSettings(buf *Buffer) {
82 invalidSettings = false
83 var parsed map[string]interface{}
85 filename := configDir + "/settings.json"
86 if _, e := os.Stat(filename); e == nil {
87 input, err := ioutil.ReadFile(filename)
89 TermMessage("Error reading settings.json file: " + err.Error())
90 invalidSettings = true
94 err = json5.Unmarshal(input, &parsed)
96 TermMessage("Error reading settings.json:", err.Error())
97 invalidSettings = true
101 for k, v := range parsed {
102 if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
103 g, err := glob.Compile(k)
105 TermMessage("Error with glob setting ", k, ": ", err)
109 if g.MatchString(buf.Path) {
110 for k1, v1 := range v.(map[string]interface{}) {
111 buf.Settings[k1] = v1
118 // WriteSettings writes the settings to the specified filename as JSON
119 func WriteSettings(filename string) error {
121 // Do not write the settings if there was an error when reading them
126 if _, e := os.Stat(configDir); e == nil {
127 parsed := make(map[string]interface{})
129 filename := configDir + "/settings.json"
130 for k, v := range globalSettings {
133 if _, e := os.Stat(filename); e == nil {
134 input, err := ioutil.ReadFile(filename)
135 if string(input) != "null" {
140 err = json5.Unmarshal(input, &parsed)
142 TermMessage("Error reading settings.json:", err.Error())
143 invalidSettings = true
146 for k, v := range parsed {
147 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
148 if _, ok := globalSettings[k]; ok {
149 parsed[k] = globalSettings[k]
156 txt, _ := json.MarshalIndent(parsed, "", " ")
157 err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
162 // AddOption creates a new option. This is meant to be called by plugins to add options.
163 func AddOption(name string, value interface{}) {
164 globalSettings[name] = value
165 err := WriteSettings(configDir + "/settings.json")
167 TermMessage("Error writing settings.json file: " + err.Error())
171 // GetGlobalOption returns the global value of the given option
172 func GetGlobalOption(name string) interface{} {
173 return globalSettings[name]
176 // GetLocalOption returns the local value of the given option
177 func GetLocalOption(name string, buf *Buffer) interface{} {
178 return buf.Settings[name]
181 // GetOption returns the value of the given option
182 // If there is a local version of the option, it returns that
183 // otherwise it will return the global version
184 func GetOption(name string) interface{} {
185 if GetLocalOption(name, CurView().Buf) != nil {
186 return GetLocalOption(name, CurView().Buf)
188 return GetGlobalOption(name)
191 // DefaultGlobalSettings returns the default global settings for micro
192 // Note that colorscheme is a global only option
193 func DefaultGlobalSettings() map[string]interface{} {
194 return map[string]interface{}{
197 "colorcolumn": float64(0),
198 "colorscheme": "default",
202 "fileformat": "unix",
206 "keepautoindent": false,
209 "pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
210 "pluginrepos": []string{},
211 "rmtrailingws": false,
216 "scrollmargin": float64(3),
217 "scrollspeed": float64(2),
224 "tabmovement": false,
225 "tabsize": float64(4),
226 "tabstospaces": false,
232 // DefaultLocalSettings returns the default local settings
233 // Note that filetype is a local only option
234 func DefaultLocalSettings() map[string]interface{} {
235 return map[string]interface{}{
238 "colorcolumn": float64(0),
242 "fileformat": "unix",
243 "filetype": "Unknown",
246 "keepautoindent": false,
247 "rmtrailingws": false,
251 "scrollmargin": float64(3),
252 "scrollspeed": float64(2),
258 "tabmovement": false,
259 "tabsize": float64(4),
260 "tabstospaces": false,
265 // SetOption attempts to set the given option to the value
266 // By default it will set the option as global, but if the option
267 // is local only it will set the local version
268 // Use setlocal to force an option to be set locally
269 func SetOption(option, value string) error {
270 if _, ok := globalSettings[option]; !ok {
271 if _, ok := CurView().Buf.Settings[option]; !ok {
272 return errors.New("Invalid option")
274 SetLocalOption(option, value, CurView())
278 var nativeValue interface{}
280 kind := reflect.TypeOf(globalSettings[option]).Kind()
281 if kind == reflect.Bool {
282 b, err := ParseBool(value)
284 return errors.New("Invalid value")
287 } else if kind == reflect.String {
289 } else if kind == reflect.Float64 {
290 i, err := strconv.Atoi(value)
292 return errors.New("Invalid value")
294 nativeValue = float64(i)
296 return errors.New("Option has unsupported value type")
299 if err := optionIsValid(option, nativeValue); err != nil {
303 globalSettings[option] = nativeValue
305 if option == "colorscheme" {
308 for _, tab := range tabs {
309 for _, view := range tab.views {
310 view.Buf.UpdateRules()
315 if option == "infobar" || option == "keymenu" {
316 for _, tab := range tabs {
321 if option == "mouse" {
322 if !nativeValue.(bool) {
323 screen.DisableMouse()
329 if _, ok := CurView().Buf.Settings[option]; ok {
330 for _, tab := range tabs {
331 for _, view := range tab.views {
332 SetLocalOption(option, value, view)
340 // SetLocalOption sets the local version of this option
341 func SetLocalOption(option, value string, view *View) error {
343 if _, ok := buf.Settings[option]; !ok {
344 return errors.New("Invalid option")
347 var nativeValue interface{}
349 kind := reflect.TypeOf(buf.Settings[option]).Kind()
350 if kind == reflect.Bool {
351 b, err := ParseBool(value)
353 return errors.New("Invalid value")
356 } else if kind == reflect.String {
358 } else if kind == reflect.Float64 {
359 i, err := strconv.Atoi(value)
361 return errors.New("Invalid value")
363 nativeValue = float64(i)
365 return errors.New("Option has unsupported value type")
368 if err := optionIsValid(option, nativeValue); err != nil {
372 if option == "fastdirty" {
373 // If it is being turned off, we have to hash every open buffer
375 for _, tab := range tabs {
376 for _, v := range tab.views {
377 if !nativeValue.(bool) {
378 if v.Buf.origHash == empty {
379 data, err := ioutil.ReadFile(v.Buf.AbsPath)
383 v.Buf.origHash = md5.Sum(data)
386 v.Buf.IsModified = v.Buf.Modified()
392 buf.Settings[option] = nativeValue
394 if option == "statusline" {
395 view.ToggleStatusLine()
398 if option == "filetype" {
404 if option == "fileformat" {
405 buf.IsModified = true
408 if option == "syntax" {
409 if !nativeValue.(bool) {
412 buf.highlighter.HighlightStates(buf)
419 // SetOptionAndSettings sets the given option and saves the option setting to the settings config file
420 func SetOptionAndSettings(option, value string) {
421 filename := configDir + "/settings.json"
423 err := SetOption(option, value)
426 messenger.Error(err.Error())
430 err = WriteSettings(filename)
432 messenger.Error("Error writing to settings.json: " + err.Error())
437 func optionIsValid(option string, value interface{}) error {
438 if validator, ok := optionValidators[option]; ok {
439 return validator(option, value)
447 func validatePositiveValue(option string, value interface{}) error {
448 tabsize, ok := value.(float64)
451 return errors.New("Expected numeric type for " + option)
455 return errors.New(option + " must be greater than 0")
461 func validateNonNegativeValue(option string, value interface{}) error {
462 nativeValue, ok := value.(float64)
465 return errors.New("Expected numeric type for " + option)
469 return errors.New(option + " must be non-negative")
475 func validateColorscheme(option string, value interface{}) error {
476 colorscheme, ok := value.(string)
479 return errors.New("Expected string type for colorscheme")
482 if !ColorschemeExists(colorscheme) {
483 return errors.New(colorscheme + " is not a valid colorscheme")
489 func validateLineEnding(option string, value interface{}) error {
490 endingType, ok := value.(string)
493 return errors.New("Expected string type for file format")
496 if endingType != "unix" && endingType != "dos" {
497 return errors.New("File format must be either 'unix' or 'dos'")