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{}
32 parsedSettings = make(map[string]interface{})
35 // Options with validators
36 var optionValidators = map[string]optionValidator{
37 "autosave": validateNonNegativeValue,
38 "tabsize": validatePositiveValue,
39 "scrollmargin": validateNonNegativeValue,
40 "scrollspeed": validateNonNegativeValue,
41 "colorscheme": validateColorscheme,
42 "colorcolumn": validateNonNegativeValue,
43 "fileformat": validateLineEnding,
44 "encoding": validateEncoding,
47 func ReadSettings() error {
48 filename := ConfigDir + "/settings.json"
49 if _, e := os.Stat(filename); e == nil {
50 input, err := ioutil.ReadFile(filename)
52 return errors.New("Error reading settings.json file: " + err.Error())
54 if !strings.HasPrefix(string(input), "null") {
55 // Unmarshal the input into the parsed map
56 err = json5.Unmarshal(input, &parsedSettings)
58 return errors.New("Error reading settings.json: " + err.Error())
65 // InitGlobalSettings initializes the options map and sets all options to their default values
66 // Must be called after ReadSettings
67 func InitGlobalSettings() {
68 GlobalSettings = DefaultGlobalSettings()
70 for k, v := range parsedSettings {
71 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
77 // InitLocalSettings scans the json in settings.json and sets the options locally based
78 // on whether the filetype or path matches ft or glob local settings
79 // Must be called after ReadSettings
80 func InitLocalSettings(settings map[string]interface{}, path string) error {
82 for k, v := range parsedSettings {
83 if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
84 if strings.HasPrefix(k, "ft:") {
85 if settings["filetype"].(string) == k[3:] {
86 for k1, v1 := range v.(map[string]interface{}) {
91 g, err := glob.Compile(k)
93 parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
97 if g.MatchString(path) {
98 for k1, v1 := range v.(map[string]interface{}) {
108 // WriteSettings writes the settings to the specified filename as JSON
109 func WriteSettings(filename string) error {
111 if _, e := os.Stat(ConfigDir); e == nil {
112 for k, v := range GlobalSettings {
113 parsedSettings[k] = v
116 txt, _ := json.MarshalIndent(parsedSettings, "", " ")
117 err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
122 // RegisterCommonOption creates a new option. This is meant to be called by plugins to add options.
123 func RegisterCommonOption(name string, defaultvalue interface{}) error {
124 if v, ok := GlobalSettings[name]; !ok {
125 defaultCommonSettings[name] = defaultvalue
126 GlobalSettings[name] = defaultvalue
127 err := WriteSettings(ConfigDir + "/settings.json")
129 return errors.New("Error writing settings.json file: " + err.Error())
132 defaultCommonSettings[name] = v
137 func RegisterGlobalOption(name string, defaultvalue interface{}) error {
138 if v, ok := GlobalSettings[name]; !ok {
139 defaultGlobalSettings[name] = defaultvalue
140 GlobalSettings[name] = defaultvalue
141 err := WriteSettings(ConfigDir + "/settings.json")
143 return errors.New("Error writing settings.json file: " + err.Error())
146 defaultGlobalSettings[name] = v
151 // GetGlobalOption returns the global value of the given option
152 func GetGlobalOption(name string) interface{} {
153 return GlobalSettings[name]
156 var defaultCommonSettings = map[string]interface{}{
160 "colorcolumn": float64(0),
165 "fileformat": "unix",
166 "filetype": "unknown",
169 "keepautoindent": false,
173 "rmtrailingws": false,
178 "scrollmargin": float64(3),
179 "scrollspeed": float64(2),
184 "statusformatl": "$(filename) $(modified)($(line),$(col)) | ft:$(opt:filetype) | $(opt:fileformat) | $(opt:encoding)",
185 "statusformatr": "$(bind:ToggleKeyMenu): bindings, $(bind:ToggleHelp): help",
188 "tabmovement": false,
189 "tabsize": float64(4),
190 "tabstospaces": false,
194 func GetInfoBarOffset() int {
196 if GetGlobalOption("infobar").(bool) {
199 if GetGlobalOption("keymenu").(bool) {
205 // DefaultCommonSettings returns the default global settings for micro
206 // Note that colorscheme is a global only option
207 func DefaultCommonSettings() map[string]interface{} {
208 commonsettings := make(map[string]interface{})
209 for k, v := range defaultCommonSettings {
210 commonsettings[k] = v
212 return commonsettings
215 var defaultGlobalSettings = map[string]interface{}{
216 "autosave": float64(0),
217 "colorscheme": "default",
226 // DefaultGlobalSettings returns the default global settings for micro
227 // Note that colorscheme is a global only option
228 func DefaultGlobalSettings() map[string]interface{} {
229 globalsettings := make(map[string]interface{})
230 for k, v := range defaultCommonSettings {
231 globalsettings[k] = v
233 for k, v := range defaultGlobalSettings {
234 globalsettings[k] = v
236 return globalsettings
239 // DefaultAllSettings returns a map of all settings and their
240 // default values (both common and global settings)
241 func DefaultAllSettings() map[string]interface{} {
242 allsettings := make(map[string]interface{})
243 for k, v := range defaultCommonSettings {
246 for k, v := range defaultGlobalSettings {
252 func GetNativeValue(option string, realValue interface{}, value string) (interface{}, error) {
253 var native interface{}
254 kind := reflect.TypeOf(realValue).Kind()
255 if kind == reflect.Bool {
256 b, err := util.ParseBool(value)
258 return nil, ErrInvalidValue
261 } else if kind == reflect.String {
263 } else if kind == reflect.Float64 {
264 i, err := strconv.Atoi(value)
266 return nil, ErrInvalidValue
270 return nil, ErrInvalidValue
273 if err := OptionIsValid(option, native); err != nil {
279 // OptionIsValid checks if a value is valid for a certain option
280 func OptionIsValid(option string, value interface{}) error {
281 if validator, ok := optionValidators[option]; ok {
282 return validator(option, value)
290 func validatePositiveValue(option string, value interface{}) error {
291 tabsize, ok := value.(float64)
294 return errors.New("Expected numeric type for " + option)
298 return errors.New(option + " must be greater than 0")
304 func validateNonNegativeValue(option string, value interface{}) error {
305 nativeValue, ok := value.(float64)
308 return errors.New("Expected numeric type for " + option)
312 return errors.New(option + " must be non-negative")
318 func validateColorscheme(option string, value interface{}) error {
319 colorscheme, ok := value.(string)
322 return errors.New("Expected string type for colorscheme")
325 if !ColorschemeExists(colorscheme) {
326 return errors.New(colorscheme + " is not a valid colorscheme")
332 func validateLineEnding(option string, value interface{}) error {
333 endingType, ok := value.(string)
336 return errors.New("Expected string type for file format")
339 if endingType != "unix" && endingType != "dos" {
340 return errors.New("File format must be either 'unix' or 'dos'")
346 func validateEncoding(option string, value interface{}) error {
347 _, err := htmlindex.Get(value.(string))