]> git.lizzy.rs Git - micro.git/blobdiff - cmd/micro/settings.go
Start refactor
[micro.git] / cmd / micro / settings.go
index 2266099b8b6ee3ca28970f8ab2320e2c4500f703..e4ac634462e28c62231572d5d3e118b77699e11d 100644 (file)
@@ -1,15 +1,15 @@
 package main
 
 import (
+       "encoding/json"
        "errors"
        "io/ioutil"
        "os"
        "reflect"
-       "strconv"
        "strings"
 
+       "github.com/flynn/json5"
        "github.com/zyedidia/glob"
-       "github.com/zyedidia/json5/encoding/json5"
 )
 
 type optionValidator func(string, interface{}) error
@@ -17,6 +17,9 @@ type optionValidator func(string, interface{}) error
 // The options that the user can set
 var globalSettings map[string]interface{}
 
+// This is the raw parsed json
+var parsedSettings map[string]interface{}
+
 // Options with validators
 var optionValidators = map[string]optionValidator{
        "tabsize":      validatePositiveValue,
@@ -24,131 +27,92 @@ var optionValidators = map[string]optionValidator{
        "scrollspeed":  validateNonNegativeValue,
        "colorscheme":  validateColorscheme,
        "colorcolumn":  validateNonNegativeValue,
+       "fileformat":   validateLineEnding,
 }
 
-// InitGlobalSettings initializes the options map and sets all options to their default values
-func InitGlobalSettings() {
-       defaults := DefaultGlobalSettings()
-       var parsed map[string]interface{}
-
+func ReadSettings() error {
        filename := configDir + "/settings.json"
-       writeSettings := false
        if _, e := os.Stat(filename); e == nil {
                input, err := ioutil.ReadFile(filename)
+               if err != nil {
+                       return errors.New("Error reading settings.json file: " + err.Error())
+               }
                if !strings.HasPrefix(string(input), "null") {
+                       // Unmarshal the input into the parsed map
+                       err = json5.Unmarshal(input, &parsedSettings)
                        if err != nil {
-                               TermMessage("Error reading settings.json file: " + err.Error())
-                               return
-                       }
-
-                       err = json5.Unmarshal(input, &parsed)
-                       if err != nil {
-                               TermMessage("Error reading settings.json:", err.Error())
+                               return errors.New("Error reading settings.json: " + err.Error())
                        }
-               } else {
-                       writeSettings = true
                }
        }
+       return nil
+}
 
-       globalSettings = make(map[string]interface{})
-       for k, v := range defaults {
-               globalSettings[k] = v
-       }
-       for k, v := range parsed {
+// InitGlobalSettings initializes the options map and sets all options to their default values
+// Must be called after ReadSettings
+func InitGlobalSettings() {
+       globalSettings = DefaultGlobalSettings()
+
+       for k, v := range parsedSettings {
                if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
                        globalSettings[k] = v
                }
        }
-
-       if _, err := os.Stat(filename); os.IsNotExist(err) || writeSettings {
-               err := WriteSettings(filename)
-               if err != nil {
-                       TermMessage("Error writing settings.json file: " + err.Error())
-               }
-       }
 }
 
 // InitLocalSettings scans the json in settings.json and sets the options locally based
 // on whether the buffer matches the glob
-func InitLocalSettings(buf *Buffer) {
-       var parsed map[string]interface{}
-
-       filename := configDir + "/settings.json"
-       if _, e := os.Stat(filename); e == nil {
-               input, err := ioutil.ReadFile(filename)
-               if err != nil {
-                       TermMessage("Error reading settings.json file: " + err.Error())
-                       return
-               }
-
-               err = json5.Unmarshal(input, &parsed)
-               if err != nil {
-                       TermMessage("Error reading settings.json:", err.Error())
-               }
-       }
-
-       for k, v := range parsed {
+// Must be called after ReadSettings
+func InitLocalSettings(buf *Buffer) error {
+       var parseError error
+       for k, v := range parsedSettings {
                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 {
+                                       parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
+                                       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
+                                       }
                                }
                        }
                }
        }
+       return parseError
 }
 
 // WriteSettings writes the settings to the specified filename as JSON
 func WriteSettings(filename string) error {
        var err error
        if _, e := os.Stat(configDir); e == nil {
-               parsed := make(map[string]interface{})
-
-               filename := configDir + "/settings.json"
                for k, v := range globalSettings {
-                       parsed[k] = v
-               }
-               if _, e := os.Stat(filename); e == nil {
-                       input, err := ioutil.ReadFile(filename)
-                       if string(input) != "null" {
-                               if err != nil {
-                                       return err
-                               }
-
-                               err = json5.Unmarshal(input, &parsed)
-                               if err != nil {
-                                       TermMessage("Error reading settings.json:", err.Error())
-                               }
-
-                               for k, v := range parsed {
-                                       if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
-                                               if _, ok := globalSettings[k]; ok {
-                                                       parsed[k] = globalSettings[k]
-                                               }
-                                       }
-                               }
-                       }
+                       parsedSettings[k] = v
                }
 
-               txt, _ := json5.MarshalIndent(parsed, "", "    ")
+               txt, _ := json.MarshalIndent(parsedSettings, "", "    ")
                err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
        }
        return err
 }
 
 // AddOption creates a new option. This is meant to be called by plugins to add options.
-func AddOption(name string, value interface{}) {
+func AddOption(name string, value interface{}) error {
        globalSettings[name] = value
        err := WriteSettings(configDir + "/settings.json")
        if err != nil {
-               TermMessage("Error writing settings.json file: " + err.Error())
+               return errors.New("Error writing settings.json file: " + err.Error())
        }
+       return nil
 }
 
 // GetGlobalOption returns the global value of the given option
@@ -161,209 +125,268 @@ func GetLocalOption(name string, buf *Buffer) interface{} {
        return buf.Settings[name]
 }
 
+// TODO: get option for current buffer
 // GetOption returns the value of the given option
 // If there is a local version of the option, it returns that
 // otherwise it will return the global version
-func GetOption(name string) interface{} {
-       if GetLocalOption(name, CurView().Buf) != nil {
-               return GetLocalOption(name, CurView().Buf)
+// func GetOption(name string) interface{} {
+//     if GetLocalOption(name, CurView().Buf) != nil {
+//             return GetLocalOption(name, CurView().Buf)
+//     }
+//     return GetGlobalOption(name)
+// }
+
+func DefaultCommonSettings() map[string]interface{} {
+       return map[string]interface{}{
+               "autoindent":     true,
+               "autosave":       false,
+               "basename":       false,
+               "colorcolumn":    float64(0),
+               "cursorline":     true,
+               "eofnewline":     false,
+               "fastdirty":      true,
+               "fileformat":     "unix",
+               "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,
+               "smartpaste":     true,
+               "splitbottom":    true,
+               "splitright":     true,
+               "statusline":     true,
+               "syntax":         true,
+               "tabmovement":    false,
+               "tabsize":        float64(4),
+               "tabstospaces":   false,
+               "useprimary":     true,
        }
-       return GetGlobalOption(name)
 }
 
 // DefaultGlobalSettings returns the default global settings for micro
 // Note that colorscheme is a global only option
 func DefaultGlobalSettings() map[string]interface{} {
-       return map[string]interface{}{
-               "autoindent":   true,
-               "autosave":     false,
-               "colorcolumn":  float64(0),
-               "colorscheme":  "default",
-               "cursorline":   true,
-               "eofnewline":   false,
-               "ignorecase":   false,
-               "indentchar":   " ",
-               "infobar":      true,
-               "ruler":        true,
-               "savecursor":   false,
-               "saveundo":     false,
-               "scrollspeed":  float64(2),
-               "scrollmargin": float64(3),
-               "softwrap":     false,
-               "statusline":   true,
-               "syntax":       true,
-               "tabsize":      float64(4),
-               "tabstospaces": false,
-               "pluginchannels": []string{
-                       "https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json",
-               },
-               "pluginrepos": []string{},
-       }
+       common := DefaultCommonSettings()
+       common["colorscheme"] = "default"
+       common["infobar"] = true
+       common["keymenu"] = false
+       common["mouse"] = true
+       common["pluginchannels"] = []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"}
+       common["pluginrepos"] = []string{}
+       common["savehistory"] = true
+       common["sucmd"] = "sudo"
+       common["termtitle"] = false
+       return common
 }
 
 // DefaultLocalSettings returns the default local settings
 // Note that filetype is a local only option
 func DefaultLocalSettings() map[string]interface{} {
-       return map[string]interface{}{
-               "autoindent":   true,
-               "autosave":     false,
-               "colorcolumn":  float64(0),
-               "cursorline":   true,
-               "eofnewline":   false,
-               "filetype":     "Unknown",
-               "ignorecase":   false,
-               "indentchar":   " ",
-               "ruler":        true,
-               "savecursor":   false,
-               "saveundo":     false,
-               "scrollspeed":  float64(2),
-               "scrollmargin": float64(3),
-               "softwrap":     false,
-               "statusline":   true,
-               "syntax":       true,
-               "tabsize":      float64(4),
-               "tabstospaces": false,
-       }
+       common := DefaultCommonSettings()
+       common["filetype"] = "Unknown"
+       return common
 }
 
+// TODO: everything else
+
 // SetOption attempts to set the given option to the value
 // By default it will set the option as global, but if the option
 // is local only it will set the local version
 // Use setlocal to force an option to be set locally
-func SetOption(option, value string) error {
-       if _, ok := globalSettings[option]; !ok {
-               if _, ok := CurView().Buf.Settings[option]; !ok {
-                       return errors.New("Invalid option")
-               }
-               SetLocalOption(option, value, CurView())
-               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")
-               }
-               nativeValue = b
-       } else if kind == reflect.String {
-               nativeValue = value
-       } else if kind == reflect.Float64 {
-               i, err := strconv.Atoi(value)
-               if err != nil {
-                       return errors.New("Invalid value")
-               }
-               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()
-               for _, tab := range tabs {
-                       for _, view := range tab.views {
-                               view.Buf.UpdateRules()
-                               if view.Buf.Settings["syntax"].(bool) {
-                                       view.matches = Match(view)
-                               }
-                       }
-               }
-       }
-
-       if option == "infobar" {
-               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)
-                       }
-               }
-       }
-
-       return nil
-}
-
-// SetLocalOption sets the local version of this option
-func SetLocalOption(option, value string, view *View) error {
-       buf := view.Buf
-       if _, ok := buf.Settings[option]; !ok {
-               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")
-               }
-               nativeValue = b
-       } else if kind == reflect.String {
-               nativeValue = value
-       } else if kind == reflect.Float64 {
-               i, err := strconv.Atoi(value)
-               if err != nil {
-                       return errors.New("Invalid value")
-               }
-               nativeValue = float64(i)
-       } else {
-               return errors.New("Option has unsupported value type")
-       }
-
-       if err := optionIsValid(option, nativeValue); err != nil {
-               return err
-       }
-
-       buf.Settings[option] = nativeValue
-
-       if option == "statusline" {
-               view.ToggleStatusLine()
-               if buf.Settings["syntax"].(bool) {
-                       view.matches = Match(view)
-               }
-       }
-
-       if option == "filetype" {
-               LoadSyntaxFiles()
-               buf.UpdateRules()
-               if buf.Settings["syntax"].(bool) {
-                       view.matches = Match(view)
-               }
-       }
-
-       return nil
-}
-
-// SetOptionAndSettings sets the given option and saves the option setting to the settings config file
-func SetOptionAndSettings(option, value string) {
-       filename := configDir + "/settings.json"
-
-       err := SetOption(option, value)
-
-       if err != nil {
-               messenger.Error(err.Error())
-               return
-       }
-
-       err = WriteSettings(filename)
-       if err != nil {
-               messenger.Error("Error writing to settings.json: " + err.Error())
-               return
-       }
-}
+// func SetOption(option, value string) error {
+//     if _, ok := globalSettings[option]; !ok {
+//             if _, ok := CurView().Buf.Settings[option]; !ok {
+//                     return errors.New("Invalid option")
+//             }
+//             SetLocalOption(option, value, CurView())
+//             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")
+//             }
+//             nativeValue = b
+//     } else if kind == reflect.String {
+//             nativeValue = value
+//     } else if kind == reflect.Float64 {
+//             i, err := strconv.Atoi(value)
+//             if err != nil {
+//                     return errors.New("Invalid value")
+//             }
+//             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()
+//             InitColorscheme()
+//             for _, tab := range tabs {
+//                     for _, view := range tab.Views {
+//                             view.Buf.UpdateRules()
+//                     }
+//             }
+//     }
+//
+//     if option == "infobar" || option == "keymenu" {
+//             for _, tab := range tabs {
+//                     tab.Resize()
+//             }
+//     }
+//
+//     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)
+//                             }
+//                     }
+//             }
+//     }
+//
+//     return nil
+// }
+//
+// // SetLocalOption sets the local version of this option
+// func SetLocalOption(option, value string, view *View) error {
+//     buf := view.Buf
+//     if _, ok := buf.Settings[option]; !ok {
+//             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")
+//             }
+//             nativeValue = b
+//     } else if kind == reflect.String {
+//             nativeValue = value
+//     } else if kind == reflect.Float64 {
+//             i, err := strconv.Atoi(value)
+//             if err != nil {
+//                     return errors.New("Invalid value")
+//             }
+//             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 [md5.Size]byte
+//             var wg sync.WaitGroup
+//
+//             for _, tab := range tabs {
+//                     for _, v := range tab.Views {
+//                             if !nativeValue.(bool) {
+//                                     if v.Buf.origHash == empty {
+//                                             wg.Add(1)
+//
+//                                             go func(b *Buffer) { // calculate md5 hash of the file
+//                                                     defer wg.Done()
+//
+//                                                     if file, e := os.Open(b.AbsPath); e == nil {
+//                                                             defer file.Close()
+//
+//                                                             h := md5.New()
+//
+//                                                             if _, e = io.Copy(h, file); e == nil {
+//                                                                     h.Sum(b.origHash[:0])
+//                                                             }
+//                                                     }
+//                                             }(v.Buf)
+//                                     }
+//                             } else {
+//                                     v.Buf.IsModified = v.Buf.Modified()
+//                             }
+//                     }
+//             }
+//
+//             wg.Wait()
+//     }
+//
+//     buf.Settings[option] = nativeValue
+//
+//     if option == "statusline" {
+//             view.ToggleStatusLine()
+//     }
+//
+//     if option == "filetype" {
+//             // LoadSyntaxFiles()
+//             InitColorscheme()
+//             buf.UpdateRules()
+//     }
+//
+//     if option == "fileformat" {
+//             buf.IsModified = true
+//     }
+//
+//     if option == "syntax" {
+//             if !nativeValue.(bool) {
+//                     buf.ClearMatches()
+//             } else {
+//                     if buf.highlighter != nil {
+//                             buf.highlighter.HighlightStates(buf)
+//                     }
+//             }
+//     }
+//
+//     return nil
+// }
+//
+// // SetOptionAndSettings sets the given option and saves the option setting to the settings config file
+// func SetOptionAndSettings(option, value string) {
+//     filename := configDir + "/settings.json"
+//
+//     err := SetOption(option, value)
+//
+//     if err != nil {
+//             messenger.Error(err.Error())
+//             return
+//     }
+//
+//     err = WriteSettings(filename)
+//     if err != nil {
+//             messenger.Error("Error writing to settings.json: " + err.Error())
+//             return
+//     }
+// }
 
 func optionIsValid(option string, value interface{}) error {
        if validator, ok := optionValidators[option]; ok {
@@ -416,3 +439,17 @@ func validateColorscheme(option string, value interface{}) error {
 
        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
+}