Make isModified reflect actual modified/unmodified state of buffer

Instead of calculating the hash of the buffer every time Modified() is
called, do that every time b.isModified is updated (i.e. every time the
buffer is modified) and set b.isModified value accordingly.

This change means that the hash will be recalculated every time the user
types or deletes a character. But that is what already happens anyway,
since inserting or deleting characters triggers redrawing the display,
in particular redrawing the status line, which triggers Modified() in
order to show the up-to-date modified/unmodified status in the status
line. And with this change, we will be able to check this status
more than once during a single "handle event & redraw" cycle, while
still recalculating the hash only once.
This commit is contained in:
Dmytro Maluka
2025-07-27 00:24:02 +02:00
parent 4ade5cdf24
commit f938f62e31
4 changed files with 24 additions and 23 deletions

View File

@@ -125,7 +125,7 @@ func (b *Buffer) ApplyBackup(fsize int64) (bool, bool) {
if choice%3 == 0 {
// recover
b.LineArray = NewLineArray(uint64(fsize), FFAuto, backup)
b.isModified = true
b.setModified()
return true, true
} else if choice%3 == 1 {
// delete

View File

@@ -126,20 +126,36 @@ type SharedBuffer struct {
}
func (b *SharedBuffer) insert(pos Loc, value []byte) {
b.isModified = true
b.HasSuggestions = false
b.LineArray.insert(pos, value)
b.setModified()
inslines := bytes.Count(value, []byte{'\n'})
b.MarkModified(pos.Y, pos.Y+inslines)
}
func (b *SharedBuffer) remove(start, end Loc) []byte {
b.isModified = true
b.HasSuggestions = false
defer b.setModified()
defer b.MarkModified(start.Y, end.Y)
return b.LineArray.remove(start, end)
}
func (b *SharedBuffer) setModified() {
if b.Type.Scratch {
return
}
if b.Settings["fastdirty"].(bool) {
b.isModified = true
} else {
var buff [md5.Size]byte
b.calcHash(&buff)
b.isModified = buff != b.origHash
}
}
// calcHash calculates md5 hash of all lines in the buffer
func (b *SharedBuffer) calcHash(out *[md5.Size]byte) {
h := md5.New()
@@ -653,18 +669,7 @@ func (b *Buffer) Shared() bool {
// Modified returns if this buffer has been modified since
// being opened
func (b *Buffer) Modified() bool {
if b.Type.Scratch {
return false
}
if b.Settings["fastdirty"].(bool) {
return b.isModified
}
var buff [md5.Size]byte
b.calcHash(&buff)
return buff != b.origHash
return b.isModified
}
// Size returns the number of bytes in the current buffer
@@ -1233,7 +1238,6 @@ func (b *Buffer) FindMatchingBrace(start Loc) (Loc, bool, bool) {
func (b *Buffer) Retab() {
toSpaces := b.Settings["tabstospaces"].(bool)
tabsize := util.IntOpt(b.Settings["tabsize"])
dirty := false
for i := 0; i < b.LinesNum(); i++ {
l := b.LineBytes(i)
@@ -1254,10 +1258,9 @@ func (b *Buffer) Retab() {
b.Unlock()
b.MarkModified(i, i)
dirty = true
}
b.isModified = dirty
b.setModified()
}
// ParseCursorLocation turns a cursor location like 10:5 (LINE:COL)

View File

@@ -206,9 +206,7 @@ func (b *Buffer) Save() error {
// AutoSave saves the buffer to its default path
func (b *Buffer) AutoSave() error {
// Doing full b.Modified() check every time would be costly, due to the hash
// calculation. So use just isModified even if fastdirty is not set.
if !b.isModified {
if !b.Modified() {
return nil
}
return b.saveToFile(b.Path, false, true)

View File

@@ -91,7 +91,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
case "dos":
b.Endings = FFDos
}
b.isModified = true
b.setModified()
} else if option == "syntax" {
if !nativeValue.(bool) {
b.ClearMatches()
@@ -105,7 +105,7 @@ func (b *Buffer) DoSetOptionNative(option string, nativeValue interface{}) {
b.Settings["encoding"] = "utf-8"
}
b.encoding = enc
b.isModified = true
b.setModified()
} else if option == "readonly" && b.Type.Kind == BTDefault.Kind {
b.Type.Readonly = nativeValue.(bool)
} else if option == "hlsearch" {