Merge pull request #3343 from JoeKar/fix/volatile-after-reinit

Rework `filetype` change, `reload` command and `autosave`
This commit is contained in:
Jöran Karl
2024-08-19 19:59:08 +02:00
committed by GitHub
7 changed files with 243 additions and 128 deletions

View File

@@ -273,6 +273,10 @@ func main() {
screen.TermMessage(err)
continue
}
if err = config.OptionIsValid(k, nativeValue); err != nil {
screen.TermMessage(err)
continue
}
config.GlobalSettings[k] = nativeValue
config.VolatileSettings[k] = true
}
@@ -352,9 +356,9 @@ func main() {
log.Println(clipErr, " or change 'clipboard' option")
}
config.StartAutoSave()
if a := config.GetGlobalOption("autosave").(float64); a > 0 {
config.SetAutoTime(int(a))
config.StartAutoSave()
config.SetAutoTime(a)
}
screen.Events = make(chan tcell.Event)

View File

@@ -7,6 +7,7 @@ import (
"os"
"os/exec"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
@@ -357,10 +358,24 @@ func reloadRuntime(reloadPlugins bool) {
err := config.ReadSettings()
if err != nil {
screen.TermMessage(err)
}
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
} else {
parsedSettings := config.ParsedSettings()
defaultSettings := config.DefaultAllSettings()
for k := range defaultSettings {
if _, ok := config.VolatileSettings[k]; ok {
// reload should not override volatile settings
continue
}
if _, ok := parsedSettings[k]; ok {
err = doSetGlobalOptionNative(k, parsedSettings[k])
} else {
err = doSetGlobalOptionNative(k, defaultSettings[k])
}
if err != nil {
screen.TermMessage(err)
}
}
}
if reloadPlugins {
@@ -393,7 +408,7 @@ func reloadRuntime(reloadPlugins bool) {
screen.TermMessage(err)
}
for _, b := range buffer.OpenBuffers {
b.UpdateRules()
b.ReloadSettings(true)
}
}
@@ -512,16 +527,11 @@ func (h *BufPane) NewTabCmd(args []string) {
}
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
// check for local option first...
for _, s := range config.LocalSettings {
if s == option {
MainTab().CurPane().Buf.SetOptionNative(option, nativeValue)
return nil
}
func doSetGlobalOptionNative(option string, nativeValue interface{}) error {
if reflect.DeepEqual(config.GlobalSettings[option], nativeValue) {
return nil
}
// ...if it's not local continue with the globals
config.GlobalSettings[option] = nativeValue
config.ModifiedSettings[option] = true
delete(config.VolatileSettings, option)
@@ -542,8 +552,7 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
}
} else if option == "autosave" {
if nativeValue.(float64) > 0 {
config.SetAutoTime(int(nativeValue.(float64)))
config.StartAutoSave()
config.SetAutoTime(nativeValue.(float64))
} else {
config.SetAutoTime(0)
}
@@ -574,8 +583,30 @@ func SetGlobalOptionNative(option string, nativeValue interface{}) error {
}
}
return nil
}
func SetGlobalOptionNative(option string, nativeValue interface{}) error {
if err := config.OptionIsValid(option, nativeValue); err != nil {
return err
}
// check for local option first...
for _, s := range config.LocalSettings {
if s == option {
return MainTab().CurPane().Buf.SetOptionNative(option, nativeValue)
}
}
// ...if it's not local continue with the globals...
if err := doSetGlobalOptionNative(option, nativeValue); err != nil {
return err
}
// ...at last check the buffer locals
for _, b := range buffer.OpenBuffers {
b.SetOptionNative(option, nativeValue)
b.DoSetOptionNative(option, nativeValue)
delete(b.LocalSettings, option)
}
return config.WriteSettings(filepath.Join(config.ConfigDir, "settings.json"))
@@ -602,16 +633,10 @@ func (h *BufPane) ResetCmd(args []string) {
}
option := args[0]
defaults := config.DefaultAllSettings()
defaultGlobals := config.DefaultGlobalSettings()
defaultLocals := config.DefaultCommonSettings()
if _, ok := defaultGlobals[option]; ok {
SetGlobalOptionNative(option, defaultGlobals[option])
return
}
if _, ok := defaultLocals[option]; ok {
h.Buf.SetOptionNative(option, defaultLocals[option])
if _, ok := defaults[option]; ok {
SetGlobalOptionNative(option, defaults[option])
return
}
InfoBar.Error(config.ErrInvalidOption)

View File

@@ -86,6 +86,8 @@ type SharedBuffer struct {
// Settings customized by the user
Settings map[string]interface{}
// LocalSettings customized by the user for this buffer only
LocalSettings map[string]bool
Suggestions []string
Completions []string
@@ -326,6 +328,7 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
// assigning the filetype.
settings := config.DefaultCommonSettings()
b.Settings = config.DefaultCommonSettings()
b.LocalSettings = make(map[string]bool)
for k, v := range config.GlobalSettings {
if _, ok := config.DefaultGlobalOnlySettings[k]; !ok {
// make sure setting is not global-only
@@ -364,6 +367,9 @@ func NewBuffer(r io.Reader, size int64, path string, startcursor Loc, btype BufT
case "dos":
ff = FFDos
}
} else {
// in case of autodetection treat as locally set
b.LocalSettings["fileformat"] = true
}
b.LineArray = NewLineArray(uint64(size), ff, reader)

View File

@@ -2,12 +2,54 @@ package buffer
import (
"crypto/md5"
"reflect"
"github.com/zyedidia/micro/v2/internal/config"
"github.com/zyedidia/micro/v2/internal/screen"
)
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
func (b *Buffer) ReloadSettings(reloadFiletype bool) {
settings := config.ParsedSettings()
if _, ok := b.LocalSettings["filetype"]; !ok && reloadFiletype {
// need to update filetype before updating other settings based on it
b.Settings["filetype"] = "unknown"
if v, ok := settings["filetype"]; ok {
b.Settings["filetype"] = v
}
}
// update syntax rules, which will also update filetype if needed
b.UpdateRules()
settings["filetype"] = b.Settings["filetype"]
config.InitLocalSettings(settings, b.Path)
for k, v := range config.DefaultCommonSettings() {
if k == "filetype" {
// prevent recursion
continue
}
if _, ok := config.VolatileSettings[k]; ok {
// reload should not override volatile settings
continue
}
if _, ok := b.LocalSettings[k]; ok {
// reload should not override local settings
continue
}
if _, ok := settings[k]; ok {
b.DoSetOptionNative(k, settings[k])
} else {
b.DoSetOptionNative(k, v)
}
}
}
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
if reflect.DeepEqual(b.Settings[option], nativeValue) {
return
}
b.Settings[option] = nativeValue
if option == "fastdirty" {
@@ -26,17 +68,7 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
} else if option == "statusline" {
screen.Redraw()
} else if option == "filetype" {
config.InitRuntimeFiles(true)
err := config.ReadSettings()
if err != nil {
screen.TermMessage(err)
}
err = config.InitGlobalSettings()
if err != nil {
screen.TermMessage(err)
}
config.InitLocalSettings(b.Settings, b.Path)
b.UpdateRules()
b.ReloadSettings(false)
} else if option == "fileformat" {
switch b.Settings["fileformat"].(string) {
case "unix":
@@ -85,6 +117,15 @@ func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
if b.OptionCallback != nil {
b.OptionCallback(option, nativeValue)
}
}
func (b *Buffer) SetOptionNative(option string, nativeValue interface{}) error {
if err := config.OptionIsValid(option, nativeValue); err != nil {
return err
}
b.DoSetOptionNative(option, nativeValue)
b.LocalSettings[option] = true
return nil
}

View File

@@ -1,44 +1,49 @@
package config
import (
"sync"
"time"
)
var Autosave chan bool
var autotime int
// lock for autosave
var autolock sync.Mutex
var autotime chan float64
func init() {
Autosave = make(chan bool)
autotime = make(chan float64)
}
func SetAutoTime(a int) {
autolock.Lock()
autotime = a
autolock.Unlock()
}
func GetAutoTime() int {
autolock.Lock()
a := autotime
autolock.Unlock()
return a
func SetAutoTime(a float64) {
autotime <- a
}
func StartAutoSave() {
go func() {
var a float64
var t *time.Timer
var elapsed <-chan time.Time
for {
autolock.Lock()
a := autotime
autolock.Unlock()
if a < 1 {
break
select {
case a = <-autotime:
if t != nil {
t.Stop()
for len(elapsed) > 0 {
<-elapsed
}
}
if a > 0 {
if t != nil {
t.Reset(time.Duration(a * float64(time.Second)))
} else {
t = time.NewTimer(time.Duration(a * float64(time.Second)))
elapsed = t.C
}
}
case <-elapsed:
if a > 0 {
t.Reset(time.Duration(a * float64(time.Second)))
Autosave <- true
}
}
time.Sleep(time.Duration(a) * time.Second)
Autosave <- true
}
}()
}

View File

@@ -154,10 +154,67 @@ var (
func init() {
ModifiedSettings = make(map[string]bool)
VolatileSettings = make(map[string]bool)
parsedSettings = make(map[string]interface{})
}
func validateParsedSettings() error {
var err error
defaults := DefaultAllSettings()
for k, v := range parsedSettings {
if strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if strings.HasPrefix(k, "ft:") {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := defaults[k1]; ok {
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
err = e
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1]
continue
}
}
}
} else {
if _, e := glob.Compile(k); e != nil {
err = errors.New("Error with glob setting " + k + ": " + e.Error())
delete(parsedSettings, k)
continue
}
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := defaults[k1]; ok {
if e := verifySetting(k1, v1, defaults[k1]); e != nil {
err = e
parsedSettings[k].(map[string]interface{})[k1] = defaults[k1]
continue
}
}
}
}
continue
}
if k == "autosave" {
// if autosave is a boolean convert it to float
s, ok := v.(bool)
if ok {
if s {
parsedSettings["autosave"] = 8.0
} else {
parsedSettings["autosave"] = 0.0
}
}
continue
}
if _, ok := defaults[k]; ok {
if e := verifySetting(k, v, defaults[k]); e != nil {
err = e
parsedSettings[k] = defaults[k]
continue
}
}
}
return err
}
func ReadSettings() error {
parsedSettings = make(map[string]interface{})
filename := filepath.Join(ConfigDir, "settings.json")
if _, e := os.Stat(filename); e == nil {
input, err := ioutil.ReadFile(filename)
@@ -172,46 +229,54 @@ func ReadSettings() error {
settingsParseError = true
return errors.New("Error reading settings.json: " + err.Error())
}
// check if autosave is a boolean and convert it to float if so
if v, ok := parsedSettings["autosave"]; ok {
s, ok := v.(bool)
if ok {
if s {
parsedSettings["autosave"] = 8.0
} else {
parsedSettings["autosave"] = 0.0
}
}
err = validateParsedSettings()
if err != nil {
return err
}
}
}
return nil
}
func verifySetting(option string, value reflect.Type, def reflect.Type) bool {
func ParsedSettings() map[string]interface{} {
s := make(map[string]interface{})
for k, v := range parsedSettings {
s[k] = v
}
return s
}
func verifySetting(option string, value interface{}, def interface{}) error {
var interfaceArr []interface{}
valType := reflect.TypeOf(value)
defType := reflect.TypeOf(def)
assignable := false
switch option {
case "pluginrepos", "pluginchannels":
return value.AssignableTo(reflect.TypeOf(interfaceArr))
assignable = valType.AssignableTo(reflect.TypeOf(interfaceArr))
default:
return def.AssignableTo(value)
assignable = defType.AssignableTo(valType)
}
if !assignable {
return fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", option, valType, def, defType)
}
if err := OptionIsValid(option, value); err != nil {
return err
}
return nil
}
// InitGlobalSettings initializes the options map and sets all options to their default values
// Must be called after ReadSettings
func InitGlobalSettings() error {
var err error
GlobalSettings = DefaultGlobalSettings()
GlobalSettings = DefaultAllSettings()
for k, v := range parsedSettings {
if !strings.HasPrefix(reflect.TypeOf(v).String(), "map") {
if _, ok := GlobalSettings[k]; ok && !verifySetting(k, reflect.TypeOf(v), reflect.TypeOf(GlobalSettings[k])) {
err = fmt.Errorf("Global Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v), GlobalSettings[k], reflect.TypeOf(GlobalSettings[k]))
continue
}
GlobalSettings[k] = v
}
}
@@ -221,40 +286,25 @@ func InitGlobalSettings() error {
// 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
func InitLocalSettings(settings map[string]interface{}, path string) {
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{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
continue
}
settings[k1] = v1
}
}
} else {
g, err := glob.Compile(k)
if err != nil {
parseError = errors.New("Error with glob setting " + k + ": " + err.Error())
continue
}
g, _ := glob.Compile(k)
if g.MatchString(path) {
for k1, v1 := range v.(map[string]interface{}) {
if _, ok := settings[k1]; ok && !verifySetting(k1, reflect.TypeOf(v1), reflect.TypeOf(settings[k1])) {
parseError = fmt.Errorf("Error: setting '%s' has incorrect type (%s), using default value: %v (%s)", k, reflect.TypeOf(v1), settings[k1], reflect.TypeOf(settings[k1]))
continue
}
settings[k1] = v1
}
}
}
}
}
return parseError
}
// WriteSettings writes the settings to the specified filename as JSON
@@ -269,7 +319,7 @@ func WriteSettings(filename string) error {
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultGlobalSettings()
defaults := DefaultAllSettings()
// remove any options froms parsedSettings that have since been marked as default
for k, v := range parsedSettings {
@@ -304,7 +354,7 @@ func OverwriteSettings(filename string) error {
var err error
if _, e := os.Stat(ConfigDir); e == nil {
defaults := DefaultGlobalSettings()
defaults := DefaultAllSettings()
for k, v := range GlobalSettings {
if def, ok := defaults[k]; !ok || !reflect.DeepEqual(v, def) {
if _, wr := ModifiedSettings[k]; wr {
@@ -370,8 +420,8 @@ func GetInfoBarOffset() int {
return offset
}
// DefaultCommonSettings returns the default global settings for micro
// Note that colorscheme is a global only option
// DefaultCommonSettings returns a map of all common buffer settings
// and their default values
func DefaultCommonSettings() map[string]interface{} {
commonsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings {
@@ -380,21 +430,8 @@ func DefaultCommonSettings() map[string]interface{} {
return commonsettings
}
// DefaultGlobalSettings returns the default global settings for micro
// Note that colorscheme is a global only option
func DefaultGlobalSettings() map[string]interface{} {
globalsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings {
globalsettings[k] = v
}
for k, v := range DefaultGlobalOnlySettings {
globalsettings[k] = v
}
return globalsettings
}
// DefaultAllSettings returns a map of all settings and their
// default values (both common and global settings)
// DefaultAllSettings returns a map of all common buffer & global-only settings
// and their default values
func DefaultAllSettings() map[string]interface{} {
allsettings := make(map[string]interface{})
for k, v := range defaultCommonSettings {
@@ -419,18 +456,15 @@ func GetNativeValue(option string, realValue interface{}, value string) (interfa
} else if kind == reflect.String {
native = value
} else if kind == reflect.Float64 {
i, err := strconv.Atoi(value)
f, err := strconv.ParseFloat(value, 64)
if err != nil {
return nil, ErrInvalidValue
}
native = float64(i)
native = f
} else {
return nil, ErrInvalidValue
}
if err := OptionIsValid(option, native); err != nil {
return nil, err
}
return native, nil
}
@@ -446,13 +480,13 @@ func OptionIsValid(option string, value interface{}) error {
// Option validators
func validatePositiveValue(option string, value interface{}) error {
tabsize, ok := value.(float64)
nativeValue, ok := value.(float64)
if !ok {
return errors.New("Expected numeric type for " + option)
}
if tabsize < 1 {
if nativeValue < 1 {
return errors.New(option + " must be greater than 0")
}

View File

@@ -66,9 +66,9 @@ local last_ft
function updateCommentType(buf)
if buf.Settings["commenttype"] == nil or (last_ft ~= buf.Settings["filetype"] and last_ft ~= nil) then
if ft[buf.Settings["filetype"]] ~= nil then
buf.Settings["commenttype"] = ft[buf.Settings["filetype"]]
buf:SetOptionNative("commenttype", ft[buf.Settings["filetype"]])
else
buf.Settings["commenttype"] = "# %s"
buf:SetOptionNative("commenttype", "# %s")
end
last_ft = buf.Settings["filetype"]