From 19717dd3ae10bea90d6f0c6e0df835cb5fec7e2e Mon Sep 17 00:00:00 2001 From: Zachary Yedidia Date: Sun, 29 May 2016 17:58:06 -0400 Subject: [PATCH] Check if the file being edited has been modified by an external program --- cmd/micro/buffer.go | 57 +++++++++++++++++++++++++++++++++++++++--- cmd/micro/cursor.go | 13 ++-------- cmd/micro/messenger.go | 2 ++ cmd/micro/util.go | 12 +++++++++ cmd/micro/view.go | 6 ++++- 5 files changed, 75 insertions(+), 15 deletions(-) diff --git a/cmd/micro/buffer.go b/cmd/micro/buffer.go index 6a0aaec6..a968aaed 100644 --- a/cmd/micro/buffer.go +++ b/cmd/micro/buffer.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/vinzmay/go-rope" ) @@ -29,6 +30,9 @@ type Buffer struct { IsModified bool + // Stores the last modification time of the file the buffer is pointing to + ModTime time.Time + // Provide efficient and easy access to text and lines so the rope String does not // need to be constantly recalculated // These variables are updated in the update() function @@ -45,6 +49,7 @@ type Buffer struct { type SerializedBuffer struct { EventHandler *EventHandler Cursor Cursor + ModTime time.Time } // NewBuffer creates a new buffer from `txt` with path and name `path` @@ -58,6 +63,8 @@ func NewBuffer(txt, path string) *Buffer { b.Path = path b.Name = path + b.ModTime, _ = GetModTime(b.Path) + b.EventHandler = NewEventHandler(b) b.Update() @@ -88,12 +95,15 @@ func NewBuffer(txt, path string) *Buffer { if settings["savecursor"].(bool) { b.Cursor = buffer.Cursor b.Cursor.buf = b - b.Cursor.Clamp() + b.Cursor.Relocate() } if settings["saveundo"].(bool) { - b.EventHandler = buffer.EventHandler - b.EventHandler.buf = b + // We should only use last time's eventhandler if the file wasn't by someone else in the meantime + if b.ModTime == buffer.ModTime { + b.EventHandler = buffer.EventHandler + b.EventHandler.buf = b + } } } file.Close() @@ -115,6 +125,43 @@ func (b *Buffer) String() string { return "" } +// CheckModTime makes sure that the file this buffer points to hasn't been updated +// by an external program since it was last read +// If it has, we ask the user if they would like to reload the file +func (b *Buffer) CheckModTime() { + modTime, ok := GetModTime(b.Path) + if ok { + if modTime != b.ModTime { + choice, canceled := messenger.YesNoPrompt("The file has changed since it was last read. Reload file? (y,n)") + messenger.Reset() + messenger.Clear() + if !choice || canceled { + // Don't load new changes -- do nothing + b.ModTime, _ = GetModTime(b.Path) + } else { + // Load new changes + data, err := ioutil.ReadFile(b.Path) + txt := string(data) + + if err != nil { + messenger.Error(err.Error()) + return + } + if txt == "" { + b.r = new(rope.Rope) + } else { + b.r = rope.New(txt) + } + + b.ModTime, _ = GetModTime(b.Path) + b.Cursor.Relocate() + b.IsModified = false + b.Update() + } + } + } +} + // Update fetches the string from the rope and updates the `text` and `lines` in the buffer func (b *Buffer) Update() { b.Lines = strings.Split(b.String(), "\n") @@ -137,6 +184,7 @@ func (b *Buffer) Serialize() error { err = enc.Encode(SerializedBuffer{ b.EventHandler, b.Cursor, + b.ModTime, }) // err = enc.Encode(b.Cursor) } @@ -149,10 +197,13 @@ func (b *Buffer) Serialize() error { // SaveAs saves the buffer to a specified path (filename), creating the file if it does not exist func (b *Buffer) SaveAs(filename string) error { b.UpdateRules() + b.Name = filename + b.Path = filename data := []byte(b.String()) err := ioutil.WriteFile(filename, data, 0644) if err == nil { b.IsModified = false + b.ModTime, _ = GetModTime(filename) return b.Serialize() } return err diff --git a/cmd/micro/cursor.go b/cmd/micro/cursor.go index d83598b3..d3a6176c 100644 --- a/cmd/micro/cursor.go +++ b/cmd/micro/cursor.go @@ -63,17 +63,7 @@ type Cursor struct { OrigSelection [2]int } -// Clamp makes sure that the cursor is in the bounds of the buffer -// It cannot be less than 0 or greater than the buffer length -func (c *Cursor) Clamp() { - loc := c.Loc() - if loc < 0 { - c.SetLoc(0) - } else if loc > c.buf.Len() { - c.SetLoc(c.buf.Len()) - } -} - +// Goto puts the cursor at the given cursor's location and gives the current cursor its selection too func (c *Cursor) Goto(b Cursor) { c.X, c.Y, c.LastVisualX = b.X, b.Y, b.LastVisualX c.OrigSelection, c.CurSelection = b.OrigSelection, b.CurSelection @@ -331,6 +321,7 @@ func (c *Cursor) Right() { if c.Loc() == c.buf.Len() { return } + // TermMessage(Count(c.buf.Lines[c.Y])) if c.X < Count(c.buf.Lines[c.Y]) { c.X++ } else { diff --git a/cmd/micro/messenger.go b/cmd/micro/messenger.go index 24964ea0..b75a779f 100644 --- a/cmd/micro/messenger.go +++ b/cmd/micro/messenger.go @@ -98,9 +98,11 @@ func (m *Messenger) Error(msg ...interface{}) { func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) { m.Message(prompt) + _, h := screen.Size() for { m.Clear() m.Display() + screen.ShowCursor(Count(m.message), h-1) screen.Show() event := screen.PollEvent() diff --git a/cmd/micro/util.go b/cmd/micro/util.go index 63b0a040..02dacf38 100644 --- a/cmd/micro/util.go +++ b/cmd/micro/util.go @@ -1,9 +1,11 @@ package main import ( + "os" "path/filepath" "strconv" "strings" + "time" "unicode/utf8" ) @@ -126,6 +128,16 @@ func EscapePath(path string) string { return strings.Replace(path, "/", "%", -1) } +// GetModTime returns the last modification time for a given file +// It also returns a boolean if there was a problem accessing the file +func GetModTime(path string) (time.Time, bool) { + info, err := os.Stat(path) + if err != nil { + return time.Now(), false + } + return info.ModTime(), true +} + func runePos(p int, str string) int { return utf8.RuneCountInString(str[:p]) } diff --git a/cmd/micro/view.go b/cmd/micro/view.go index 70fa5aa9..c576cca8 100644 --- a/cmd/micro/view.go +++ b/cmd/micro/view.go @@ -201,8 +201,10 @@ func (v *View) ReOpen() { } buf := NewBuffer(string(file), filename) v.Buf = buf - v.matches = Match(v) v.Cursor.Relocate() + buf.Cursor.Goto(*v.Cursor) + v.Cursor = &buf.Cursor + v.matches = Match(v) v.Relocate() } } @@ -272,6 +274,8 @@ func (v *View) HandleEvent(event tcell.Event) { // By default it's true because most events should cause a relocate relocate := true + v.Buf.CheckModTime() + switch e := event.(type) { case *tcell.EventResize: // Window resized