Files
zyedidia.micro/cmd/micro/config/settings.go
2018-08-27 15:53:10 -04:00

456 lines
11 KiB
Go

package config
import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"reflect"
"strings"
"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{}
// This is the raw parsed json
var parsedSettings map[string]interface{}
// Options with validators
var optionValidators = map[string]optionValidator{
"tabsize": validatePositiveValue,
"scrollmargin": validateNonNegativeValue,
"scrollspeed": validateNonNegativeValue,
"colorscheme": validateColorscheme,
"colorcolumn": validateNonNegativeValue,
"fileformat": validateLineEnding,
}
func ReadSettings() error {
filename := ConfigDir + "/settings.json"
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 {
return errors.New("Error reading settings.json: " + err.Error())
}
}
}
return nil
}
// 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
}
}
}
// InitLocalSettings scans the json in settings.json and sets the options locally based
// on whether the filetype or path matches ft or glob local settings
// Must be called after ReadSettings
func InitLocalSettings(settings map[string]interface{}, path string) error {
var parseError error
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
if settings["filetype"].(string) == k[3:] {
for k1, v1 := range v.(map[string]interface{}) {
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(path) {
for k1, v1 := range v.(map[string]interface{}) {
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 {
for k, v := range GlobalSettings {
parsedSettings[k] = v
}
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{}) error {
GlobalSettings[name] = value
err := WriteSettings(ConfigDir + "/settings.json")
if err != nil {
return errors.New("Error writing settings.json file: " + err.Error())
}
return nil
}
// GetGlobalOption returns the global value of the given option
func GetGlobalOption(name string) interface{} {
return GlobalSettings[name]
}
// GetLocalOption returns the local value of the given option
// 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)
// }
// 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,
}
}
// DefaultGlobalSettings returns the default global settings for micro
// Note that colorscheme is a global only option
func DefaultGlobalSettings() map[string]interface{} {
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{} {
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()
// 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 {
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
}