From ef57bdcf9ba93e774b85efd3c92acd204cbd6aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Fri, 6 Feb 2026 00:06:59 +0100 Subject: [PATCH 1/3] buffer: Don't cancel the backup in case the buffer is shared Otherwise it will be removed async, which shouldn't happen in case there is still one buffer open with the same modified file. --- internal/buffer/buffer.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 2735ca467c..0418ab8929 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -527,7 +527,9 @@ func (b *Buffer) Fini() { if !b.Modified() { b.Serialize() } - b.CancelBackup() + if !b.Shared() { + b.CancelBackup() + } if b.Type == BTStdout { fmt.Fprint(util.Stdout, string(b.Bytes())) From e323cde09c551a7755db50369323675c00da8a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Tue, 10 Feb 2026 19:17:43 +0100 Subject: [PATCH 2/3] buffer: Don't `Serialize` in case the buffer is shared Otherwise we unnecessarily serialize the shared buffer every time when closing a bufpane with this buffer, so every such serialize overwrites the previous one, thus only the last serialize (when closing the last instance of the buffer, i.e. when actually closing the file, i.e. when the buffer is not shared anymore) will be used anyway. --- internal/buffer/buffer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 0418ab8929..9ddd8bd906 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -524,10 +524,10 @@ func (b *Buffer) Close() { // Fini should be called when a buffer is closed and performs // some cleanup func (b *Buffer) Fini() { - if !b.Modified() { - b.Serialize() - } if !b.Shared() { + if !b.Modified() { + b.Serialize() + } b.CancelBackup() } From 31be8d2b9a5ce7d015688b41be0766f4644dd536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6ran=20Karl?= <3951388+JoeKar@users.noreply.github.com> Date: Wed, 11 Feb 2026 06:52:51 +0100 Subject: [PATCH 3/3] buffer: Resolve symlinks in file paths Otherwise we can't identify if we have the same file open multiple times via different symlinks. The test must be adapted to resolve symlinks in `findBuffer()`. --- cmd/micro/micro_test.go | 4 +++- internal/buffer/buffer.go | 9 +++++---- internal/buffer/save.go | 5 +---- internal/util/util.go | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/cmd/micro/micro_test.go b/cmd/micro/micro_test.go index 09cad432cb..185b296e5b 100644 --- a/cmd/micro/micro_test.go +++ b/cmd/micro/micro_test.go @@ -11,6 +11,7 @@ import ( "github.com/micro-editor/micro/v2/internal/buffer" "github.com/micro-editor/micro/v2/internal/config" "github.com/micro-editor/micro/v2/internal/screen" + "github.com/micro-editor/micro/v2/internal/util" "github.com/micro-editor/tcell/v2" "github.com/stretchr/testify/assert" ) @@ -157,8 +158,9 @@ func openFile(file string) { func findBuffer(file string) *buffer.Buffer { var buf *buffer.Buffer + file = util.ResolvePath(file) for _, b := range buffer.OpenBuffers { - if b.Path == file { + if b.AbsPath == file { buf = b } } diff --git a/internal/buffer/buffer.go b/internal/buffer/buffer.go index 9ddd8bd906..d1f8db4b2b 100644 --- a/internal/buffer/buffer.go +++ b/internal/buffer/buffer.go @@ -355,9 +355,9 @@ func NewBufferFromString(text, path string, btype BufType) *Buffer { // Places the cursor at startcursor. If startcursor is -1, -1 places the // cursor at an autodetected location (based on savecursor or :LINE:COL) func NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) *Buffer { - absPath, err := filepath.Abs(path) - if err != nil { - absPath = path + absPath := path + if btype == BTDefault && path != "" { + absPath = util.ResolvePath(path) } b := new(Buffer) @@ -391,6 +391,7 @@ func NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) } config.UpdatePathGlobLocals(b.Settings, absPath) + var err error b.encoding, err = htmlindex.Get(b.Settings["encoding"].(string)) if err != nil { b.encoding = unicode.UTF8 @@ -489,7 +490,7 @@ func NewBuffer(r io.Reader, size int64, path string, btype BufType, cmd Command) } } - err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b)) + err := config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b)) if err != nil { screen.TermMessage(err) } diff --git a/internal/buffer/save.go b/internal/buffer/save.go index 44e8f4a3ed..373cb8a1b1 100644 --- a/internal/buffer/save.go +++ b/internal/buffer/save.go @@ -285,10 +285,7 @@ func (b *Buffer) saveToFile(filename string, withSudo bool, autoSave bool) error return errors.New("Error: " + filename + " is not a regular file and cannot be saved") } - absFilename, err := filepath.Abs(filename) - if err != nil { - return err - } + absFilename := util.ResolvePath(filename) // Get the leading path to the file | "." is returned if there's no leading path provided if dirname := filepath.Dir(absFilename); dirname != "." { diff --git a/internal/util/util.go b/internal/util/util.go index cad6374349..9447bb3384 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -489,6 +489,38 @@ func DetermineEscapePath(dir string, path string) (string, string) { return url, "" } +// ResolvePath provides the absolute file path for the given relative file path +// as well as resolves symlinks. If it fails to get the absolute path or to +// resolve symlinks, it returns unresolved path in place of resolved one. +func ResolvePath(path string) string { + absPath, err := filepath.Abs(path) + if err != nil { + absPath = path + } + + var remainder []string + for { + resolvedPath, err := filepath.EvalSymlinks(absPath) + if err == nil { + absPath = resolvedPath + break + } else if errors.Is(err, fs.ErrNotExist) { + remainder = append([]string{filepath.Base(absPath)}, remainder...) + absPath = filepath.Dir(absPath) + continue + } + break + } + + if len(remainder) > 0 { + remainder = append([]string{absPath}, remainder...) + absPath = filepath.Join(remainder...) + absPath = filepath.Clean(absPath) + } + + return absPath +} + // GetLeadingWhitespace returns the leading whitespace of the given byte array func GetLeadingWhitespace(b []byte) []byte { ws := []byte{}