2019-01-14 02:06:58 +00:00
|
|
|
package buffer
|
|
|
|
|
|
|
|
|
|
import (
|
2024-07-21 19:20:29 +00:00
|
|
|
"crypto/md5"
|
2024-07-23 19:06:21 +00:00
|
|
|
"reflect"
|
2024-07-21 19:20:29 +00:00
|
|
|
|
2020-05-04 14:16:15 +00:00
|
|
|
"github.com/zyedidia/micro/v2/internal/config"
|
2023-10-12 23:56:16 +00:00
|
|
|
ulua "github.com/zyedidia/micro/v2/internal/lua"
|
2020-05-04 14:16:15 +00:00
|
|
|
"github.com/zyedidia/micro/v2/internal/screen"
|
2025-02-06 18:54:47 +00:00
|
|
|
"golang.org/x/text/encoding/htmlindex"
|
|
|
|
|
"golang.org/x/text/encoding/unicode"
|
2023-10-12 23:56:16 +00:00
|
|
|
luar "layeh.com/gopher-luar"
|
2019-01-14 02:06:58 +00:00
|
|
|
)
|
|
|
|
|
|
2024-08-12 18:26:51 +00:00
|
|
|
func (b *Buffer) ReloadSettings(reloadFiletype bool) {
|
|
|
|
|
settings := config.ParsedSettings()
|
2025-02-14 19:57:11 +00:00
|
|
|
config.UpdatePathGlobLocals(settings, b.AbsPath)
|
2024-08-12 18:26:51 +00:00
|
|
|
|
2025-02-12 20:43:59 +00:00
|
|
|
oldFiletype := b.Settings["filetype"].(string)
|
|
|
|
|
|
2025-02-12 20:25:10 +00:00
|
|
|
_, local := b.LocalSettings["filetype"]
|
|
|
|
|
_, volatile := config.VolatileSettings["filetype"]
|
|
|
|
|
if reloadFiletype && !local && !volatile {
|
2024-08-12 18:26:51 +00:00
|
|
|
// 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()
|
|
|
|
|
|
2025-02-12 20:43:59 +00:00
|
|
|
curFiletype := b.Settings["filetype"].(string)
|
|
|
|
|
if oldFiletype != curFiletype {
|
|
|
|
|
b.doCallbacks("filetype", oldFiletype, curFiletype)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config.UpdateFileTypeLocals(settings, curFiletype)
|
|
|
|
|
|
2024-08-12 18:26:51 +00:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-26 19:06:06 +00:00
|
|
|
func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
|
2023-10-12 23:56:16 +00:00
|
|
|
oldValue := b.Settings[option]
|
|
|
|
|
if reflect.DeepEqual(oldValue, nativeValue) {
|
2024-07-26 19:06:06 +00:00
|
|
|
return
|
2024-07-23 19:06:21 +00:00
|
|
|
}
|
|
|
|
|
|
2019-01-14 02:06:58 +00:00
|
|
|
b.Settings[option] = nativeValue
|
|
|
|
|
|
|
|
|
|
if option == "fastdirty" {
|
|
|
|
|
if !nativeValue.(bool) {
|
calcHash: Remove checking file size
Let calcHash() unconditionally hash whatever buffer it is asked to hash,
and let its callers explicitly check if the buffer is too large before
calling calcHash(). This makes things simpler and less error-prone
(no extra source of truth about whether the file is too large, we don't
need to remember to check if calcHash() fails, we can be sure calcHash()
will actually update the provided hash), and actually faster (since just
calculating the buffer size, i.e. adding line lengths, is faster than
md5 calculation).
In particular, this fixes the following bugs:
1. Since ReOpen() doesn't check calcHash() return value, if the reloaded
file is too large while the old version of the file is not,
calcHash() returns ErrFileTooLarge and doesn't update origHash, so
so Modified() returns true since the reloaded file's md5 sum doesn't
match the old origHash, so micro wrongly reports the newly reloaded
file as modified.
2. Since Modified() doesn't check calcHash() return value, Modified()
may return false positives or false negatives if the buffer has
*just* become too large so calcHash() returns ErrFileTooLarge and
doesn't update `buff`.
2024-08-18 13:10:07 +00:00
|
|
|
if b.Size() > LargeFileThreshold {
|
|
|
|
|
b.Settings["fastdirty"] = true
|
2024-07-21 19:20:29 +00:00
|
|
|
} else {
|
calcHash: Remove checking file size
Let calcHash() unconditionally hash whatever buffer it is asked to hash,
and let its callers explicitly check if the buffer is too large before
calling calcHash(). This makes things simpler and less error-prone
(no extra source of truth about whether the file is too large, we don't
need to remember to check if calcHash() fails, we can be sure calcHash()
will actually update the provided hash), and actually faster (since just
calculating the buffer size, i.e. adding line lengths, is faster than
md5 calculation).
In particular, this fixes the following bugs:
1. Since ReOpen() doesn't check calcHash() return value, if the reloaded
file is too large while the old version of the file is not,
calcHash() returns ErrFileTooLarge and doesn't update origHash, so
so Modified() returns true since the reloaded file's md5 sum doesn't
match the old origHash, so micro wrongly reports the newly reloaded
file as modified.
2. Since Modified() doesn't check calcHash() return value, Modified()
may return false positives or false negatives if the buffer has
*just* become too large so calcHash() returns ErrFileTooLarge and
doesn't update `buff`.
2024-08-18 13:10:07 +00:00
|
|
|
if !b.isModified {
|
|
|
|
|
calcHash(b, &b.origHash)
|
|
|
|
|
} else {
|
2024-08-18 13:19:19 +00:00
|
|
|
// prevent using an old stale origHash value
|
calcHash: Remove checking file size
Let calcHash() unconditionally hash whatever buffer it is asked to hash,
and let its callers explicitly check if the buffer is too large before
calling calcHash(). This makes things simpler and less error-prone
(no extra source of truth about whether the file is too large, we don't
need to remember to check if calcHash() fails, we can be sure calcHash()
will actually update the provided hash), and actually faster (since just
calculating the buffer size, i.e. adding line lengths, is faster than
md5 calculation).
In particular, this fixes the following bugs:
1. Since ReOpen() doesn't check calcHash() return value, if the reloaded
file is too large while the old version of the file is not,
calcHash() returns ErrFileTooLarge and doesn't update origHash, so
so Modified() returns true since the reloaded file's md5 sum doesn't
match the old origHash, so micro wrongly reports the newly reloaded
file as modified.
2. Since Modified() doesn't check calcHash() return value, Modified()
may return false positives or false negatives if the buffer has
*just* become too large so calcHash() returns ErrFileTooLarge and
doesn't update `buff`.
2024-08-18 13:10:07 +00:00
|
|
|
b.origHash = [md5.Size]byte{}
|
2020-07-05 00:09:44 +00:00
|
|
|
}
|
2019-01-14 02:06:58 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if option == "statusline" {
|
|
|
|
|
screen.Redraw()
|
|
|
|
|
} else if option == "filetype" {
|
2024-08-12 18:26:51 +00:00
|
|
|
b.ReloadSettings(false)
|
2019-01-14 02:06:58 +00:00
|
|
|
} else if option == "fileformat" {
|
2019-01-15 04:24:49 +00:00
|
|
|
switch b.Settings["fileformat"].(string) {
|
|
|
|
|
case "unix":
|
|
|
|
|
b.Endings = FFUnix
|
|
|
|
|
case "dos":
|
|
|
|
|
b.Endings = FFDos
|
|
|
|
|
}
|
2019-01-14 21:52:25 +00:00
|
|
|
b.isModified = true
|
2019-01-14 02:06:58 +00:00
|
|
|
} else if option == "syntax" {
|
|
|
|
|
if !nativeValue.(bool) {
|
|
|
|
|
b.ClearMatches()
|
|
|
|
|
} else {
|
|
|
|
|
b.UpdateRules()
|
|
|
|
|
}
|
2019-01-24 00:06:20 +00:00
|
|
|
} else if option == "encoding" {
|
2025-02-06 18:54:47 +00:00
|
|
|
enc, err := htmlindex.Get(b.Settings["encoding"].(string))
|
|
|
|
|
if err != nil {
|
|
|
|
|
enc = unicode.UTF8
|
|
|
|
|
b.Settings["encoding"] = "utf-8"
|
|
|
|
|
}
|
|
|
|
|
b.encoding = enc
|
2019-01-24 00:06:20 +00:00
|
|
|
b.isModified = true
|
2020-02-09 00:37:37 +00:00
|
|
|
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
|
2019-06-17 21:45:38 +00:00
|
|
|
b.Type.Readonly = nativeValue.(bool)
|
2021-09-28 20:39:03 +00:00
|
|
|
} else if option == "hlsearch" {
|
|
|
|
|
for _, buf := range OpenBuffers {
|
|
|
|
|
if b.SharedBuffer == buf.SharedBuffer {
|
|
|
|
|
buf.HighlightSearch = nativeValue.(bool)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-06 00:38:33 +00:00
|
|
|
} else {
|
|
|
|
|
for _, pl := range config.Plugins {
|
|
|
|
|
if option == pl.Name {
|
|
|
|
|
if nativeValue.(bool) {
|
|
|
|
|
if !pl.Loaded {
|
|
|
|
|
pl.Load()
|
|
|
|
|
}
|
|
|
|
|
_, err := pl.Call("init")
|
|
|
|
|
if err != nil && err != config.ErrNoSuchFunction {
|
|
|
|
|
screen.TermMessage(err)
|
|
|
|
|
}
|
|
|
|
|
} else if !nativeValue.(bool) && pl.Loaded {
|
|
|
|
|
_, err := pl.Call("deinit")
|
|
|
|
|
if err != nil && err != config.ErrNoSuchFunction {
|
|
|
|
|
screen.TermMessage(err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-22 19:47:30 +00:00
|
|
|
}
|
2019-01-14 02:06:58 +00:00
|
|
|
|
2025-02-12 20:43:59 +00:00
|
|
|
b.doCallbacks(option, oldValue, nativeValue)
|
2024-07-26 19:06:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
2021-03-03 19:51:06 +00:00
|
|
|
|
2019-01-14 02:06:58 +00:00
|
|
|
return nil
|
|
|
|
|
}
|
2019-06-16 21:35:00 +00:00
|
|
|
|
|
|
|
|
// SetOption sets a given option to a value just for this buffer
|
|
|
|
|
func (b *Buffer) SetOption(option, value string) error {
|
|
|
|
|
if _, ok := b.Settings[option]; !ok {
|
|
|
|
|
return config.ErrInvalidOption
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nativeValue, err := config.GetNativeValue(option, b.Settings[option], value)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return b.SetOptionNative(option, nativeValue)
|
|
|
|
|
}
|
2025-02-12 20:43:59 +00:00
|
|
|
|
|
|
|
|
func (b *Buffer) doCallbacks(option string, oldValue interface{}, newValue interface{}) {
|
|
|
|
|
if b.OptionCallback != nil {
|
|
|
|
|
b.OptionCallback(option, newValue)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := config.RunPluginFn("onBufferOptionChanged",
|
|
|
|
|
luar.New(ulua.L, b), luar.New(ulua.L, option),
|
|
|
|
|
luar.New(ulua.L, oldValue), luar.New(ulua.L, newValue)); err != nil {
|
|
|
|
|
screen.TermMessage(err)
|
|
|
|
|
}
|
|
|
|
|
}
|