13 "github.com/zyedidia/glob"
14 "github.com/zyedidia/json5"
15 "github.com/zyedidia/micro/v2/internal/util"
16 "golang.org/x/text/encoding/htmlindex"
19 type optionValidator func(string, interface{}) error
22 ErrInvalidOption = errors.New("Invalid option")
23 ErrInvalidValue = errors.New("Invalid value")
25 // The options that the user can set
26 GlobalSettings map[string]interface{}
28 // This is the raw parsed json
29 parsedSettings map[string]interface{}
33 parsedSettings = make(map[string]interface{})
36 // Options with validators
37 var optionValidators = map[string]optionValidator{
38 "autosave": validateNonNegativeValue,
39 "tabsize": validatePositiveValue,
40 "scrollmargin": validateNonNegativeValue,
41 "scrollspeed": validateNonNegativeValue,
42 "colorscheme": validateColorscheme,
43 "colorcolumn": validateNonNegativeValue,
44 "fileformat": validateLineEnding,
45 "encoding": validateEncoding,
48 func ReadSettings() error {
49 filename := filepath.Join(ConfigDir, "settings.json")
50 if _, e := os.Stat(filename); e == nil {
51 input, err := ioutil.ReadFile(filename)
53 return errors.New("Error reading settings.json file: " + err.Error())
55 if !strings.HasPrefix(string(input), "null") {
56 // Unmarshal the input into the parsed map
57 err = json5.Unmarshal(input, &parsedSettings)
59 return errors.New("Error reading settings.json: " + err.Error())
62 // check if autosave is a boolean and convert it to float if so
63 if v, ok := parsedSettings["autosave"]; ok {
67 parsedSettings["autosave"] = 8.0
69 parsedSettings["autosave"] = 0.0
78 // InitGlobalSettings initializes the options map and sets all options to their default values
79 // Must be called after ReadSettings
80 func InitGlobalSettings() {
81 GlobalSettings = DefaultGlobalSettings()
83 for k, v := range parsedSettings {
84 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
90 // InitLocalSettings scans the json in settings.json and sets the options locally based
91 // on whether the filetype or path matches ft or glob local settings
92 // Must be called after ReadSettings
93 func InitLocalSettings(settings map[string]interface{}, path string) error {
95 for k, v := range parsedSettings {
96 if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
97 if strings.HasPrefix(k, "ft:") {
98 if settings["filetype"].(string) == k[3:] {
99 for k1, v1 := range v.(map[string]interface{}) {
104 g, err := glob.Compile(k)
106 parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
110 if g.MatchString(path) {
111 for k1, v1 := range v.(map[string]interface{}) {
121 // WriteSettings writes the settings to the specified filename as JSON
122 func WriteSettings(filename string) error {
124 if _, e := os.Stat(ConfigDir); e == nil {
125 for k, v := range GlobalSettings {
126 parsedSettings[k] = v
129 txt, _ := json.MarshalIndent(parsedSettings, "", " ")
130 err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
135 func OverwriteSettings(filename string) error {
137 if _, e := os.Stat(ConfigDir); e == nil {
138 txt, _ := json.MarshalIndent(GlobalSettings, "", " ")
139 err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
144 // RegisterCommonOptionPlug creates a new option (called pl.name). This is meant to be called by plugins to add options.
145 func RegisterCommonOptionPlug(pl string, name string, defaultvalue interface{}) error {
146 name = pl + "." + name
147 if v, ok := GlobalSettings[name]; !ok {
148 defaultCommonSettings[name] = defaultvalue
149 GlobalSettings[name] = defaultvalue
150 err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
152 return errors.New("Error writing settings.json file: " + err.Error())
155 defaultCommonSettings[name] = v
160 // RegisterGlobalOptionPlug creates a new global-only option (named pl.name)
161 func RegisterGlobalOptionPlug(pl string, name string, defaultvalue interface{}) error {
162 return RegisterGlobalOption(pl+"."+name, defaultvalue)
165 // RegisterGlobalOption creates a new global-only option
166 func RegisterGlobalOption(name string, defaultvalue interface{}) error {
167 if v, ok := GlobalSettings[name]; !ok {
168 DefaultGlobalOnlySettings[name] = defaultvalue
169 GlobalSettings[name] = defaultvalue
170 err := WriteSettings(filepath.Join(ConfigDir, "settings.json"))
172 return errors.New("Error writing settings.json file: " + err.Error())
175 DefaultGlobalOnlySettings[name] = v
180 // GetGlobalOption returns the global value of the given option
181 func GetGlobalOption(name string) interface{} {
182 return GlobalSettings[name]
185 var defaultCommonSettings = map[string]interface{}{
190 "colorcolumn": float64(0),
196 "fileformat": "unix",
197 "filetype": "unknown",
200 "keepautoindent": false,
204 "rmtrailingws": false,
209 "scrollmargin": float64(3),
210 "scrollspeed": float64(2),
215 "statusformatl": "$(filename) $(modified)($(line),$(col)) $(status.paste)| ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
216 "statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
219 "tabmovement": false,
220 "tabsize": float64(4),
221 "tabstospaces": false,
225 func GetInfoBarOffset() int {
227 if GetGlobalOption("infobar").(bool) {
230 if GetGlobalOption("keymenu").(bool) {
236 // DefaultCommonSettings returns the default global settings for micro
237 // Note that colorscheme is a global only option
238 func DefaultCommonSettings() map[string]interface{} {
239 commonsettings := make(map[string]interface{})
240 for k, v := range defaultCommonSettings {
241 commonsettings[k] = v
243 return commonsettings
246 // a list of settings that should only be globally modified and their
248 var DefaultGlobalOnlySettings = map[string]interface{}{
249 "autosave": float64(0),
250 "colorscheme": "default",
257 "pluginchannels": []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"},
258 "pluginrepos": []string{},
262 // a list of settings that should never be globally modified
263 var LocalSettings = []string{
268 // DefaultGlobalSettings returns the default global settings for micro
269 // Note that colorscheme is a global only option
270 func DefaultGlobalSettings() map[string]interface{} {
271 globalsettings := make(map[string]interface{})
272 for k, v := range defaultCommonSettings {
273 globalsettings[k] = v
275 for k, v := range DefaultGlobalOnlySettings {
276 globalsettings[k] = v
278 return globalsettings
281 // DefaultAllSettings returns a map of all settings and their
282 // default values (both common and global settings)
283 func DefaultAllSettings() map[string]interface{} {
284 allsettings := make(map[string]interface{})
285 for k, v := range defaultCommonSettings {
288 for k, v := range DefaultGlobalOnlySettings {
294 // GetNativeValue parses and validates a value for a given option
295 func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
296 var native interface{}
297 kind := reflect.TypeOf(realValue).Kind()
298 if kind == reflect.Bool {
299 b, err := util.ParseBool(value)
301 return nil, ErrInvalidValue
304 } else if kind == reflect.String {
306 } else if kind == reflect.Float64 {
307 i, err := strconv.Atoi(value)
309 return nil, ErrInvalidValue
313 return nil, ErrInvalidValue
316 if err := OptionIsValid(option, native); err != nil {
322 // OptionIsValid checks if a value is valid for a certain option
323 func OptionIsValid(option string, value interface{}) error {
324 if validator, ok := optionValidators[option]; ok {
325 return validator(option, value)
333 func validatePositiveValue(option string, value interface{}) error {
334 tabsize, ok := value.(float64)
337 return errors.New("Expected numeric type for " + option)
341 return errors.New(option + " must be greater than 0")
347 func validateNonNegativeValue(option string, value interface{}) error {
348 nativeValue, ok := value.(float64)
351 return errors.New("Expected numeric type for " + option)
355 return errors.New(option + " must be non-negative")
361 func validateColorscheme(option string, value interface{}) error {
362 colorscheme, ok := value.(string)
365 return errors.New("Expected string type for colorscheme")
368 if !ColorschemeExists(colorscheme) {
369 return errors.New(colorscheme + " is not a valid colorscheme")
375 func validateLineEnding(option string, value interface{}) error {
376 endingType, ok := value.(string)
379 return errors.New("Expected string type for file format")
382 if endingType != "unix" && endingType != "dos" {
383 return errors.New("File format must be either 'unix' or 'dos'")
389 func validateEncoding(option string, value interface{}) error {
390 _, err := htmlindex.Get(value.(string))