11 "github.com/flynn/json5"
12 "github.com/zyedidia/glob"
15 type optionValidator func(string, interface{}) error
17 // The options that the user can set
18 var globalSettings map[string]interface{}
20 // This is the raw parsed json
21 var parsedSettings map[string]interface{}
23 // Options with validators
24 var optionValidators = map[string]optionValidator{
25 "tabsize": validatePositiveValue,
26 "scrollmargin": validateNonNegativeValue,
27 "scrollspeed": validateNonNegativeValue,
28 "colorscheme": validateColorscheme,
29 "colorcolumn": validateNonNegativeValue,
30 "fileformat": validateLineEnding,
33 func ReadSettings() error {
34 filename := configDir + "/settings.json"
35 if _, e := os.Stat(filename); e == nil {
36 input, err := ioutil.ReadFile(filename)
38 return errors.New("Error reading settings.json file: " + err.Error())
40 if !strings.HasPrefix(string(input), "null") {
41 // Unmarshal the input into the parsed map
42 err = json5.Unmarshal(input, &parsedSettings)
44 return errors.New("Error reading settings.json: " + err.Error())
51 // InitGlobalSettings initializes the options map and sets all options to their default values
52 // Must be called after ReadSettings
53 func InitGlobalSettings() {
54 globalSettings = DefaultGlobalSettings()
56 for k, v := range parsedSettings {
57 if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
63 // InitLocalSettings scans the json in settings.json and sets the options locally based
64 // on whether the buffer matches the glob
65 // Must be called after ReadSettings
66 func InitLocalSettings(buf *Buffer) error {
68 for k, v := range parsedSettings {
69 if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
70 if strings.HasPrefix(k, "ft:") {
71 if buf.Settings["filetype"].(string) == k[3:] {
72 for k1, v1 := range v.(map[string]interface{}) {
77 g, err := glob.Compile(k)
79 parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
83 if g.MatchString(buf.Path) {
84 for k1, v1 := range v.(map[string]interface{}) {
94 // WriteSettings writes the settings to the specified filename as JSON
95 func WriteSettings(filename string) error {
97 if _, e := os.Stat(configDir); e == nil {
98 for k, v := range globalSettings {
102 txt, _ := json.MarshalIndent(parsedSettings, "", " ")
103 err = ioutil.WriteFile(filename, append(txt, '\n'), 0644)
108 // AddOption creates a new option. This is meant to be called by plugins to add options.
109 func AddOption(name string, value interface{}) error {
110 globalSettings[name] = value
111 err := WriteSettings(configDir + "/settings.json")
113 return errors.New("Error writing settings.json file: " + err.Error())
118 // GetGlobalOption returns the global value of the given option
119 func GetGlobalOption(name string) interface{} {
120 return globalSettings[name]
123 // GetLocalOption returns the local value of the given option
124 func GetLocalOption(name string, buf *Buffer) interface{} {
125 return buf.Settings[name]
128 // TODO: get option for current buffer
129 // GetOption returns the value of the given option
130 // If there is a local version of the option, it returns that
131 // otherwise it will return the global version
132 // func GetOption(name string) interface{} {
133 // if GetLocalOption(name, CurView().Buf) != nil {
134 // return GetLocalOption(name, CurView().Buf)
136 // return GetGlobalOption(name)
139 func DefaultCommonSettings() map[string]interface{} {
140 return map[string]interface{}{
144 "colorcolumn": float64(0),
148 "fileformat": "unix",
152 "keepautoindent": false,
154 "matchbraceleft": false,
155 "rmtrailingws": false,
160 "scrollmargin": float64(3),
161 "scrollspeed": float64(2),
168 "tabmovement": false,
169 "tabsize": float64(4),
170 "tabstospaces": false,
175 // DefaultGlobalSettings returns the default global settings for micro
176 // Note that colorscheme is a global only option
177 func DefaultGlobalSettings() map[string]interface{} {
178 common := DefaultCommonSettings()
179 common["colorscheme"] = "default"
180 common["infobar"] = true
181 common["keymenu"] = false
182 common["mouse"] = true
183 common["pluginchannels"] = []string{"https://raw.githubusercontent.com/micro-editor/plugin-channel/master/channel.json"}
184 common["pluginrepos"] = []string{}
185 common["savehistory"] = true
186 common["sucmd"] = "sudo"
187 common["termtitle"] = false
191 // DefaultLocalSettings returns the default local settings
192 // Note that filetype is a local only option
193 func DefaultLocalSettings() map[string]interface{} {
194 common := DefaultCommonSettings()
195 common["filetype"] = "Unknown"
199 // TODO: everything else
201 // SetOption attempts to set the given option to the value
202 // By default it will set the option as global, but if the option
203 // is local only it will set the local version
204 // Use setlocal to force an option to be set locally
205 // func SetOption(option, value string) error {
206 // if _, ok := globalSettings[option]; !ok {
207 // if _, ok := CurView().Buf.Settings[option]; !ok {
208 // return errors.New("Invalid option")
210 // SetLocalOption(option, value, CurView())
214 // var nativeValue interface{}
216 // kind := reflect.TypeOf(globalSettings[option]).Kind()
217 // if kind == reflect.Bool {
218 // b, err := ParseBool(value)
220 // return errors.New("Invalid value")
223 // } else if kind == reflect.String {
224 // nativeValue = value
225 // } else if kind == reflect.Float64 {
226 // i, err := strconv.Atoi(value)
228 // return errors.New("Invalid value")
230 // nativeValue = float64(i)
232 // return errors.New("Option has unsupported value type")
235 // if err := optionIsValid(option, nativeValue); err != nil {
239 // globalSettings[option] = nativeValue
241 // if option == "colorscheme" {
242 // // LoadSyntaxFiles()
244 // for _, tab := range tabs {
245 // for _, view := range tab.Views {
246 // view.Buf.UpdateRules()
251 // if option == "infobar" || option == "keymenu" {
252 // for _, tab := range tabs {
257 // if option == "mouse" {
258 // if !nativeValue.(bool) {
259 // screen.DisableMouse()
261 // screen.EnableMouse()
265 // if len(tabs) != 0 {
266 // if _, ok := CurView().Buf.Settings[option]; ok {
267 // for _, tab := range tabs {
268 // for _, view := range tab.Views {
269 // SetLocalOption(option, value, view)
278 // // SetLocalOption sets the local version of this option
279 // func SetLocalOption(option, value string, view *View) error {
281 // if _, ok := buf.Settings[option]; !ok {
282 // return errors.New("Invalid option")
285 // var nativeValue interface{}
287 // kind := reflect.TypeOf(buf.Settings[option]).Kind()
288 // if kind == reflect.Bool {
289 // b, err := ParseBool(value)
291 // return errors.New("Invalid value")
294 // } else if kind == reflect.String {
295 // nativeValue = value
296 // } else if kind == reflect.Float64 {
297 // i, err := strconv.Atoi(value)
299 // return errors.New("Invalid value")
301 // nativeValue = float64(i)
303 // return errors.New("Option has unsupported value type")
306 // if err := optionIsValid(option, nativeValue); err != nil {
310 // if option == "fastdirty" {
311 // // If it is being turned off, we have to hash every open buffer
312 // var empty [md5.Size]byte
313 // var wg sync.WaitGroup
315 // for _, tab := range tabs {
316 // for _, v := range tab.Views {
317 // if !nativeValue.(bool) {
318 // if v.Buf.origHash == empty {
321 // go func(b *Buffer) { // calculate md5 hash of the file
324 // if file, e := os.Open(b.AbsPath); e == nil {
325 // defer file.Close()
329 // if _, e = io.Copy(h, file); e == nil {
330 // h.Sum(b.origHash[:0])
336 // v.Buf.IsModified = v.Buf.Modified()
344 // buf.Settings[option] = nativeValue
346 // if option == "statusline" {
347 // view.ToggleStatusLine()
350 // if option == "filetype" {
351 // // LoadSyntaxFiles()
356 // if option == "fileformat" {
357 // buf.IsModified = true
360 // if option == "syntax" {
361 // if !nativeValue.(bool) {
362 // buf.ClearMatches()
364 // if buf.highlighter != nil {
365 // buf.highlighter.HighlightStates(buf)
373 // // SetOptionAndSettings sets the given option and saves the option setting to the settings config file
374 // func SetOptionAndSettings(option, value string) {
375 // filename := configDir + "/settings.json"
377 // err := SetOption(option, value)
380 // messenger.Error(err.Error())
384 // err = WriteSettings(filename)
386 // messenger.Error("Error writing to settings.json: " + err.Error())
391 func optionIsValid(option string, value interface{}) error {
392 if validator, ok := optionValidators[option]; ok {
393 return validator(option, value)
401 func validatePositiveValue(option string, value interface{}) error {
402 tabsize, ok := value.(float64)
405 return errors.New("Expected numeric type for " + option)
409 return errors.New(option + " must be greater than 0")
415 func validateNonNegativeValue(option string, value interface{}) error {
416 nativeValue, ok := value.(float64)
419 return errors.New("Expected numeric type for " + option)
423 return errors.New(option + " must be non-negative")
429 func validateColorscheme(option string, value interface{}) error {
430 colorscheme, ok := value.(string)
433 return errors.New("Expected string type for colorscheme")
436 if !ColorschemeExists(colorscheme) {
437 return errors.New(colorscheme + " is not a valid colorscheme")
443 func validateLineEnding(option string, value interface{}) error {
444 endingType, ok := value.(string)
447 return errors.New("Expected string type for file format")
450 if endingType != "unix" && endingType != "dos" {
451 return errors.New("File format must be either 'unix' or 'dos'")