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{}{
196 "keepautoindent": false,
198 "colorcolumn": float64(0),
199 "colorscheme": "default",
203 "fileformat": "unix",
209 "rmtrailingws": false,
213 "scrollspeed": float64(2),
214 "scrollmargin": float64(3),
221 "tabmovement": false,
222 "tabsize": float64(4),
223 "tabstospaces": false,
225 "pluginchannels": []string{
226 "https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
228 "pluginrepos": []string{},
233 // DefaultLocalSettings returns the default local settings
234 // Note that filetype is a local only option
235 func DefaultLocalSettings() map[string]interface{} {
236 return map[string]interface{}{
238 "keepautoindent": false,
240 "colorcolumn": float64(0),
244 "fileformat": "unix",
245 "filetype": "Unknown",
248 "rmtrailingws": false,
252 "scrollspeed": float64(2),
253 "scrollmargin": float64(3),
259 "tabmovement": false,
260 "tabsize": float64(4),
261 "tabstospaces": false,
266 // SetOption attempts to set the given option to the value
267 // By default it will set the option as global, but if the option
268 // is local only it will set the local version
269 // Use setlocal to force an option to be set locally
270 func SetOption(option, value string) error {
271 if _, ok := globalSettings[option]; !ok {
272 if _, ok := CurView().Buf.Settings[option]; !ok {
273 return errors.New("Invalid option")
275 SetLocalOption(option, value, CurView())
279 var nativeValue interface{}
281 kind := reflect.TypeOf(globalSettings[option]).Kind()
282 if kind == reflect.Bool {
283 b, err := ParseBool(value)
285 return errors.New("Invalid value")
288 } else if kind == reflect.String {
290 } else if kind == reflect.Float64 {
291 i, err := strconv.Atoi(value)
293 return errors.New("Invalid value")
295 nativeValue = float64(i)
297 return errors.New("Option has unsupported value type")
300 if err := optionIsValid(option, nativeValue); err != nil {
304 globalSettings[option] = nativeValue
306 if option == "colorscheme" {
309 for _, tab := range tabs {
310 for _, view := range tab.views {
311 view.Buf.UpdateRules()
316 if option == "infobar" || option == "keymenu" {
317 for _, tab := range tabs {
322 if option == "mouse" {
323 if !nativeValue.(bool) {
324 screen.DisableMouse()
330 if _, ok := CurView().Buf.Settings[option]; ok {
331 for _, tab := range tabs {
332 for _, view := range tab.views {
333 SetLocalOption(option, value, view)
341 // SetLocalOption sets the local version of this option
342 func SetLocalOption(option, value string, view *View) error {
344 if _, ok := buf.Settings[option]; !ok {
345 return errors.New("Invalid option")
348 var nativeValue interface{}
350 kind := reflect.TypeOf(buf.Settings[option]).Kind()
351 if kind == reflect.Bool {
352 b, err := ParseBool(value)
354 return errors.New("Invalid value")
357 } else if kind == reflect.String {
359 } else if kind == reflect.Float64 {
360 i, err := strconv.Atoi(value)
362 return errors.New("Invalid value")
364 nativeValue = float64(i)
366 return errors.New("Option has unsupported value type")
369 if err := optionIsValid(option, nativeValue); err != nil {
373 if option == "fastdirty" {
374 // If it is being turned off, we have to hash every open buffer
376 for _, tab := range tabs {
377 for _, v := range tab.views {
378 if !nativeValue.(bool) {
379 if v.Buf.origHash == empty {
380 data, err := ioutil.ReadFile(v.Buf.AbsPath)
384 v.Buf.origHash = md5.Sum(data)
387 v.Buf.IsModified = v.Buf.Modified()
393 buf.Settings[option] = nativeValue
395 if option == "statusline" {
396 view.ToggleStatusLine()
399 if option == "filetype" {
405 if option == "fileformat" {
406 buf.IsModified = true
409 if option == "syntax" {
410 if !nativeValue.(bool) {
413 buf.highlighter.HighlightStates(buf)
420 // SetOptionAndSettings sets the given option and saves the option setting to the settings config file
421 func SetOptionAndSettings(option, value string) {
422 filename := configDir + "/settings.json"
424 err := SetOption(option, value)
427 messenger.Error(err.Error())
431 err = WriteSettings(filename)
433 messenger.Error("Error writing to settings.json: " + err.Error())
438 func optionIsValid(option string, value interface{}) error {
439 if validator, ok := optionValidators[option]; ok {
440 return validator(option, value)
448 func validatePositiveValue(option string, value interface{}) error {
449 tabsize, ok := value.(float64)
452 return errors.New("Expected numeric type for " + option)
456 return errors.New(option + " must be greater than 0")
462 func validateNonNegativeValue(option string, value interface{}) error {
463 nativeValue, ok := value.(float64)
466 return errors.New("Expected numeric type for " + option)
470 return errors.New(option + " must be non-negative")
476 func validateColorscheme(option string, value interface{}) error {
477 colorscheme, ok := value.(string)
480 return errors.New("Expected string type for colorscheme")
483 if !ColorschemeExists(colorscheme) {
484 return errors.New(colorscheme + " is not a valid colorscheme")
490 func validateLineEnding(option string, value interface{}) error {
491 endingType, ok := value.(string)
494 return errors.New("Expected string type for file format")
497 if endingType != "unix" && endingType != "dos" {
498 return errors.New("File format must be either 'unix' or 'dos'")