package main
import (
+ "crypto/md5"
+ "encoding/json"
"errors"
"io/ioutil"
"os"
"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{}
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
// 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"
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
+ }
}
}
}
// 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{})
err = json5.Unmarshal(input, &parsed)
if err != nil {
TermMessage("Error reading settings.json:", err.Error())
+ invalidSettings = true
}
for k, v := range parsed {
}
}
- txt, _ := json5.MarshalIndent(parsed, "", " ")
+ txt, _ := json.MarshalIndent(parsed, "", " ")
err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
}
return err
// 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,
}
}
// 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,
}
}
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)
+ }
}
}
}
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)
+ }
}
}
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
+}