]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/settings.go
Add hidehelp option
[micro.git] / cmd / micro / settings.go
index 9f1d7915065ccf4ba50707028e9191b663111e65..b1b1c277727a72322055b04f520e423b86324251 100644 (file)
@@ -1,6 +1,8 @@
 package main
 
 import (
+       "crypto/md5"
+       "encoding/json"
        "errors"
        "io/ioutil"
        "os"
@@ -8,15 +10,30 @@ import (
        "strconv"
        "strings"
 
-       "github.com/yosuke-furukawa/json5/encoding/json5"
+       "github.com/flynn/json5"
        "github.com/zyedidia/glob"
 )
 
+type optionValidator func(string, interface{}) error
+
 // The options that the user can set
 var globalSettings map[string]interface{}
 
+var invalidSettings bool
+
+// Options with validators
+var optionValidators = map[string]optionValidator{
+       "tabsize":      validatePositiveValue,
+       "scrollmargin": validateNonNegativeValue,
+       "scrollspeed":  validateNonNegativeValue,
+       "colorscheme":  validateColorscheme,
+       "colorcolumn":  validateNonNegativeValue,
+       "fileformat":   validateLineEnding,
+}
+
 // InitGlobalSettings initializes the options map and sets all options to their default values
 func InitGlobalSettings() {
+       invalidSettings = false
        defaults := DefaultGlobalSettings()
        var parsed map[string]interface{}
 
@@ -27,12 +44,14 @@ func InitGlobalSettings() {
                if !strings.HasPrefix(string(input), "null") {
                        if err != nil {
                                TermMessage("Error reading settings.json file: " + err.Error())
+                               invalidSettings = true
                                return
                        }
 
                        err = json5.Unmarshal(input, &parsed)
                        if err != nil {
                                TermMessage("Error reading settings.json:", err.Error())
+                               invalidSettings = true
                        }
                } else {
                        writeSettings = true
@@ -60,6 +79,7 @@ func InitGlobalSettings() {
 // InitLocalSettings scans the json in settings.json and sets the options locally based
 // on whether the buffer matches the glob
 func InitLocalSettings(buf *Buffer) {
+       invalidSettings = false
        var parsed map[string]interface{}
 
        filename := configDir + "/settings.json"
@@ -67,26 +87,36 @@ func InitLocalSettings(buf *Buffer) {
                input, err := ioutil.ReadFile(filename)
                if err != nil {
                        TermMessage("Error reading settings.json file: " + err.Error())
+                       invalidSettings = true
                        return
                }
 
                err = json5.Unmarshal(input, &parsed)
                if err != nil {
                        TermMessage("Error reading settings.json:", err.Error())
+                       invalidSettings = true
                }
        }
 
        for k, v := range parsed {
                if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
-                       g, err := glob.Compile(k)
-                       if err != nil {
-                               TermMessage("Error with glob setting ", k, ": ", err)
-                               continue
-                       }
+                       if strings.HasPrefix(k, "ft:") {
+                               if buf.Settings["filetype"].(string) == k[3:] {
+                                       for k1, v1 := range v.(map[string]interface{}) {
+                                               buf.Settings[k1] = v1
+                                       }
+                               }
+                       } else {
+                               g, err := glob.Compile(k)
+                               if err != nil {
+                                       TermMessage("Error with glob setting ", k, ": ", err)
+                                       continue
+                               }
 
-                       if g.MatchString(buf.Path) {
-                               for k1, v1 := range v.(map[string]interface{}) {
-                                       buf.Settings[k1] = v1
+                               if g.MatchString(buf.Path) {
+                                       for k1, v1 := range v.(map[string]interface{}) {
+                                               buf.Settings[k1] = v1
+                                       }
                                }
                        }
                }
@@ -95,6 +125,11 @@ func InitLocalSettings(buf *Buffer) {
 
 // WriteSettings writes the settings to the specified filename as JSON
 func WriteSettings(filename string) error {
+       if invalidSettings {
+               // Do not write the settings if there was an error when reading them
+               return nil
+       }
+
        var err error
        if _, e := os.Stat(configDir); e == nil {
                parsed := make(map[string]interface{})
@@ -113,6 +148,7 @@ func WriteSettings(filename string) error {
                                err = json5.Unmarshal(input, &parsed)
                                if err != nil {
                                        TermMessage("Error reading settings.json:", err.Error())
+                                       invalidSettings = true
                                }
 
                                for k, v := range parsed {
@@ -125,7 +161,7 @@ func WriteSettings(filename string) error {
                        }
                }
 
-               txt, _ := json5.MarshalIndent(parsed, "", "    ")
+               txt, _ := json.MarshalIndent(parsed, "", "    ")
                err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
        }
        return err
@@ -164,21 +200,45 @@ func GetOption(name string) interface{} {
 // Note that colorscheme is a global only option
 func DefaultGlobalSettings() map[string]interface{} {
        return map[string]interface{}{
-               "autoindent":   true,
-               "colorscheme":  "zenburn",
-               "cursorline":   true,
-               "ignorecase":   false,
-               "indentchar":   " ",
-               "infobar":      true,
-               "ruler":        true,
-               "savecursor":   false,
-               "saveundo":     false,
-               "scrollspeed":  float64(2),
-               "scrollmargin": float64(3),
-               "statusline":   true,
-               "syntax":       true,
-               "tabsize":      float64(4),
-               "tabstospaces": false,
+               "autoindent":     true,
+               "autosave":       false,
+               "basename":       false,
+               "colorcolumn":    float64(0),
+               "colorscheme":    "default",
+               "cursorline":     true,
+               "eofnewline":     false,
+               "fastdirty":      true,
+               "fileformat":     "unix",
+               "hidehelp":       false,
+               "ignorecase":     false,
+               "indentchar":     " ",
+               "infobar":        true,
+               "keepautoindent": false,
+               "keymenu":        false,
+               "matchbrace":     false,
+               "matchbraceleft": false,
+               "mouse":          true,
+               "pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
+               "pluginrepos":    []string{},
+               "rmtrailingws":   false,
+               "ruler":          true,
+               "savecursor":     false,
+               "savehistory":    true,
+               "saveundo":       false,
+               "scrollbar":      false,
+               "scrollmargin":   float64(3),
+               "scrollspeed":    float64(2),
+               "softwrap":       false,
+               "splitbottom":    true,
+               "splitright":     true,
+               "statusline":     true,
+               "sucmd":          "sudo",
+               "syntax":         true,
+               "tabmovement":    false,
+               "tabsize":        float64(4),
+               "tabstospaces":   false,
+               "termtitle":      false,
+               "useprimary":     true,
        }
 }
 
@@ -186,20 +246,37 @@ func DefaultGlobalSettings() map[string]interface{} {
 // Note that filetype is a local only option
 func DefaultLocalSettings() map[string]interface{} {
        return map[string]interface{}{
-               "autoindent":   true,
-               "cursorline":   true,
-               "filetype":     "Unknown",
-               "ignorecase":   false,
-               "indentchar":   " ",
-               "ruler":        true,
-               "savecursor":   false,
-               "saveundo":     false,
-               "scrollspeed":  float64(2),
-               "scrollmargin": float64(3),
-               "statusline":   true,
-               "syntax":       true,
-               "tabsize":      float64(4),
-               "tabstospaces": false,
+               "autoindent":     true,
+               "autosave":       false,
+               "basename":       false,
+               "colorcolumn":    float64(0),
+               "cursorline":     true,
+               "eofnewline":     false,
+               "fastdirty":      true,
+               "fileformat":     "unix",
+               "filetype":       "Unknown",
+               "hidehelp":       false,
+               "ignorecase":     false,
+               "indentchar":     " ",
+               "keepautoindent": false,
+               "matchbrace":     false,
+               "matchbraceleft": false,
+               "rmtrailingws":   false,
+               "ruler":          true,
+               "savecursor":     false,
+               "saveundo":       false,
+               "scrollbar":      false,
+               "scrollmargin":   float64(3),
+               "scrollspeed":    float64(2),
+               "softwrap":       false,
+               "splitbottom":    true,
+               "splitright":     true,
+               "statusline":     true,
+               "syntax":         true,
+               "tabmovement":    false,
+               "tabsize":        float64(4),
+               "tabstospaces":   false,
+               "useprimary":     true,
        }
 }
 
@@ -216,45 +293,63 @@ func SetOption(option, value string) error {
                return nil
        }
 
+       var nativeValue interface{}
+
        kind := reflect.TypeOf(globalSettings[option]).Kind()
        if kind == reflect.Bool {
                b, err := ParseBool(value)
                if err != nil {
                        return errors.New("Invalid value")
                }
-               globalSettings[option] = b
+               nativeValue = b
        } else if kind == reflect.String {
-               globalSettings[option] = value
+               nativeValue = value
        } else if kind == reflect.Float64 {
                i, err := strconv.Atoi(value)
                if err != nil {
                        return errors.New("Invalid value")
                }
-               globalSettings[option] = float64(i)
+               nativeValue = float64(i)
+       } else {
+               return errors.New("Option has unsupported value type")
+       }
+
+       if err := optionIsValid(option, nativeValue); err != nil {
+               return err
        }
 
+       globalSettings[option] = nativeValue
+
        if option == "colorscheme" {
-               LoadSyntaxFiles()
+               // LoadSyntaxFiles()
+               InitColorscheme()
                for _, tab := range tabs {
-                       for _, view := range tab.views {
+                       for _, view := range tab.Views {
                                view.Buf.UpdateRules()
-                               if view.Buf.Settings["syntax"].(bool) {
-                                       view.matches = Match(view)
-                               }
                        }
                }
        }
 
-       if option == "infobar" {
+       if option == "infobar" || option == "keymenu" {
                for _, tab := range tabs {
                        tab.Resize()
                }
        }
 
-       if _, ok := CurView().Buf.Settings[option]; ok {
-               for _, tab := range tabs {
-                       for _, view := range tab.views {
-                               SetLocalOption(option, value, view)
+       if option == "mouse" {
+               if !nativeValue.(bool) {
+                       screen.DisableMouse()
+               } else {
+                       screen.EnableMouse()
+               }
+       }
+
+       if len(tabs) != 0 {
+               if _, ok := CurView().Buf.Settings[option]; ok {
+                       for _, tab := range tabs {
+                               for _, view := range tab.Views {
+                                       SetLocalOption(option, value, view)
+                               }
                        }
                }
        }
@@ -269,35 +364,74 @@ func SetLocalOption(option, value string, view *View) error {
                return errors.New("Invalid option")
        }
 
+       var nativeValue interface{}
+
        kind := reflect.TypeOf(buf.Settings[option]).Kind()
        if kind == reflect.Bool {
                b, err := ParseBool(value)
                if err != nil {
                        return errors.New("Invalid value")
                }
-               buf.Settings[option] = b
+               nativeValue = b
        } else if kind == reflect.String {
-               buf.Settings[option] = value
+               nativeValue = value
        } else if kind == reflect.Float64 {
                i, err := strconv.Atoi(value)
                if err != nil {
                        return errors.New("Invalid value")
                }
-               buf.Settings[option] = float64(i)
+               nativeValue = float64(i)
+       } else {
+               return errors.New("Option has unsupported value type")
+       }
+
+       if err := optionIsValid(option, nativeValue); err != nil {
+               return err
+       }
+
+       if option == "fastdirty" {
+               // If it is being turned off, we have to hash every open buffer
+               var empty [16]byte
+               for _, tab := range tabs {
+                       for _, v := range tab.Views {
+                               if !nativeValue.(bool) {
+                                       if v.Buf.origHash == empty {
+                                               data, err := ioutil.ReadFile(v.Buf.AbsPath)
+                                               if err != nil {
+                                                       data = []byte{}
+                                               }
+                                               v.Buf.origHash = md5.Sum(data)
+                                       }
+                               } else {
+                                       v.Buf.IsModified = v.Buf.Modified()
+                               }
+                       }
+               }
        }
 
+       buf.Settings[option] = nativeValue
+
        if option == "statusline" {
                view.ToggleStatusLine()
-               if buf.Settings["syntax"].(bool) {
-                       view.matches = Match(view)
-               }
        }
 
        if option == "filetype" {
-               LoadSyntaxFiles()
+               // LoadSyntaxFiles()
+               InitColorscheme()
                buf.UpdateRules()
-               if buf.Settings["syntax"].(bool) {
-                       view.matches = Match(view)
+       }
+
+       if option == "fileformat" {
+               buf.IsModified = true
+       }
+
+       if option == "syntax" {
+               if !nativeValue.(bool) {
+                       buf.ClearMatches()
+               } else {
+                       if buf.highlighter != nil {
+                               buf.highlighter.HighlightStates(buf)
+                       }
                }
        }
 
@@ -321,3 +455,69 @@ func SetOptionAndSettings(option, value string) {
                return
        }
 }
+
+func optionIsValid(option string, value interface{}) error {
+       if validator, ok := optionValidators[option]; ok {
+               return validator(option, value)
+       }
+
+       return nil
+}
+
+// Option validators
+
+func validatePositiveValue(option string, value interface{}) error {
+       tabsize, ok := value.(float64)
+
+       if !ok {
+               return errors.New("Expected numeric type for " + option)
+       }
+
+       if tabsize < 1 {
+               return errors.New(option + " must be greater than 0")
+       }
+
+       return nil
+}
+
+func validateNonNegativeValue(option string, value interface{}) error {
+       nativeValue, ok := value.(float64)
+
+       if !ok {
+               return errors.New("Expected numeric type for " + option)
+       }
+
+       if nativeValue < 0 {
+               return errors.New(option + " must be non-negative")
+       }
+
+       return nil
+}
+
+func validateColorscheme(option string, value interface{}) error {
+       colorscheme, ok := value.(string)
+
+       if !ok {
+               return errors.New("Expected string type for colorscheme")
+       }
+
+       if !ColorschemeExists(colorscheme) {
+               return errors.New(colorscheme + " is not a valid colorscheme")
+       }
+
+       return nil
+}
+
+func validateLineEnding(option string, value interface{}) error {
+       endingType, ok := value.(string)
+
+       if !ok {
+               return errors.New("Expected string type for file format")
+       }
+
+       if endingType != "unix" && endingType != "dos" {
+               return errors.New("File format must be either 'unix' or 'dos'")
+       }
+
+       return nil
+}