12 "github.com/flynn/json5"
13 "github.com/zyedidia/glob"
14 "github.com/zyedidia/micro/internal/util"
15 "golang.org/x/text/encoding/htmlindex"
18 type optionValidator func(string, interface{}) error
21 ErrInvalidOption = errors.New("Invalid option")
22 ErrInvalidValue = errors.New("Invalid value")
24 // The options that the user can set
25 GlobalSettings map[string]interface{}
27 // This is the raw parsed json
28 parsedSettings map[string]interface{}
31 // Options with validators
32 var optionValidators = map[string]optionValidator{
33 "tabsize": validatePositiveValue,
34 "scrollmargin": validateNonNegativeValue,
35 "scrollspeed": validateNonNegativeValue,
36 "colorscheme": validateColorscheme,
37 "colorcolumn": validateNonNegativeValue,
38 "fileformat": validateLineEnding,
39 "encoding": validateEncoding,
42 func ReadSettings() error {
43 filename := ConfigDir + "/settings.json"
44 if _, e := os.Stat(filename); e == nil {
45 input, err := ioutil.ReadFile(filename)
47 return errors.New("Error reading settings.json file: " + err.Error())
49 if !strings.HasPrefix(string(input), "null") {
50 // Unmarshal the input into the parsed map
51 err = json5.Unmarshal(input, &parsedSettings)
53 return errors.New("Error reading settings.json: " + err.Error())
60 // InitGlobalSettings initializes the options map and sets all options to their default values
61 // Must be called after ReadSettings
62 func InitGlobalSettings() {
63 GlobalSettings = DefaultGlobalSettings()
65 for k, v := range parsedSettings {
66 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
72 // InitLocalSettings scans the json in settings.json and sets the options locally based
73 // on whether the filetype or path matches ft or glob local settings
74 // Must be called after ReadSettings
75 func InitLocalSettings(settings map[string]interface{}, path string) error {
77 for k, v := range parsedSettings {
78 if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
79 if strings.HasPrefix(k, "ft:") {
80 if settings["filetype"].(string) == k[3:] {
81 for k1, v1 := range v.(map[string]interface{}) {
86 g, err := glob.Compile(k)
88 parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
92 if g.MatchString(path) {
93 for k1, v1 := range v.(map[string]interface{}) {
103 // WriteSettings writes the settings to the specified filename as JSON
104 func WriteSettings(filename string) error {
106 if _, e := os.Stat(ConfigDir); e == nil {
107 for k, v := range GlobalSettings {
108 parsedSettings[k] = v
111 txt, _ := json.MarshalIndent(parsedSettings, "", " ")
112 err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
117 // RegisterCommonOption creates a new option. This is meant to be called by plugins to add options.
118 func RegisterCommonOption(name string, defaultvalue interface{}) error {
119 if v, ok := GlobalSettings[name]; !ok {
120 defaultCommonSettings[name] = defaultvalue
121 GlobalSettings[name] = defaultvalue
122 err := WriteSettings(ConfigDir + "/settings.json")
124 return errors.New("Error writing settings.json file: " + err.Error())
127 defaultCommonSettings[name] = v
132 func RegisterLocalOption(name string, defaultvalue interface{}) {
133 defaultLocalSettings[name] = defaultvalue
136 func RegisterGlobalOption(name string, defaultvalue interface{}) error {
137 if v, ok := GlobalSettings[name]; !ok {
138 defaultGlobalSettings[name] = defaultvalue
139 GlobalSettings[name] = defaultvalue
140 err := WriteSettings(ConfigDir + "/settings.json")
142 return errors.New("Error writing settings.json file: " + err.Error())
145 defaultGlobalSettings[name] = v
150 // GetGlobalOption returns the global value of the given option
151 func GetGlobalOption(name string) interface{} {
152 return GlobalSettings[name]
155 var defaultCommonSettings = map[string]interface{}{
159 "colorcolumn": float64(0),
164 "fileformat": "unix",
167 "keepautoindent": false,
169 "matchbraceleft": false,
170 "rmtrailingws": false,
175 "scrollmargin": float64(3),
176 "scrollspeed": float64(2),
181 "statusformatl": "$(filename) $(modified)($(line),$(col)) $(opt:filetype) $(opt:fileformat) $(opt:encoding)",
182 "statusformatr": "$(bind:ToggleKeyMenu): show bindings, $(bind:ToggleHelp): toggle help",
185 "tabmovement": false,
186 "tabsize": float64(4),
187 "tabstospaces": false,
191 func GetInfoBarOffset() int {
193 if GetGlobalOption("infobar").(bool) {
196 if GetGlobalOption("keymenu").(bool) {
202 var defaultGlobalSettings = map[string]interface{}{
203 "colorscheme": "default",
212 // DefaultGlobalSettings returns the default global settings for micro
213 // Note that colorscheme is a global only option
214 func DefaultGlobalSettings() map[string]interface{} {
215 globalsettings := make(map[string]interface{})
216 for k, v := range defaultCommonSettings {
217 globalsettings[k] = v
219 for k, v := range defaultGlobalSettings {
220 globalsettings[k] = v
222 return globalsettings
225 // LocalSettings is a list of the local only settings
226 var LocalSettings = []string{"filetype", "readonly"}
228 var defaultLocalSettings = map[string]interface{}{
229 "filetype": "unknown",
233 // DefaultLocalSettings returns the default local settings
234 // Note that filetype is a local only option
235 func DefaultLocalSettings() map[string]interface{} {
236 localsettings := make(map[string]interface{})
237 for k, v := range defaultCommonSettings {
240 for k, v := range defaultLocalSettings {
246 // DefaultAllSettings returns a map of all settings and their
247 // default values (both local and global settings)
248 func DefaultAllSettings() map[string]interface{} {
249 allsettings := make(map[string]interface{})
250 for k, v := range defaultCommonSettings {
253 for k, v := range defaultGlobalSettings {
256 for k, v := range defaultLocalSettings {
262 func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
263 var native interface{}
264 kind := reflect.TypeOf(realValue).Kind()
265 if kind == reflect.Bool {
266 b, err := util.ParseBool(value)
268 return nil, ErrInvalidValue
271 } else if kind == reflect.String {
273 } else if kind == reflect.Float64 {
274 i, err := strconv.Atoi(value)
276 return nil, ErrInvalidValue
280 return nil, ErrInvalidValue
283 if err := OptionIsValid(option, native); err != nil {
289 // OptionIsValid checks if a value is valid for a certain option
290 func OptionIsValid(option string, value interface{}) error {
291 if validator, ok := optionValidators[option]; ok {
292 return validator(option, value)
300 func validatePositiveValue(option string, value interface{}) error {
301 tabsize, ok := value.(float64)
304 return errors.New("Expected numeric type for " + option)
308 return errors.New(option + " must be greater than 0")
314 func validateNonNegativeValue(option string, value interface{}) error {
315 nativeValue, ok := value.(float64)
318 return errors.New("Expected numeric type for " + option)
322 return errors.New(option + " must be non-negative")
328 func validateColorscheme(option string, value interface{}) error {
329 colorscheme, ok := value.(string)
332 return errors.New("Expected string type for colorscheme")
335 if !ColorschemeExists(colorscheme) {
336 return errors.New(colorscheme + " is not a valid colorscheme")
342 func validateLineEnding(option string, value interface{}) error {
343 endingType, ok := value.(string)
346 return errors.New("Expected string type for file format")
349 if endingType != "unix" && endingType != "dos" {
350 return errors.New("File format must be either 'unix' or 'dos'")
356 func validateEncoding(option string, value interface{}) error {
357 _, err := htmlindex.Get(value.(string))