diff --git a/gui/buffer.go b/gui/buffer.go index 66b7d28..48c43c0 100644 --- a/gui/buffer.go +++ b/gui/buffer.go @@ -17,6 +17,7 @@ import ( "github.com/felixangell/phi/cfg" "github.com/felixangell/phi/lex" "github.com/felixangell/strife" + "github.com/sqweek/dialog" "github.com/veandco/go-sdl2/sdl" ) @@ -184,6 +185,44 @@ func (s *selection) renderAt(ctx *strife.Renderer, xOff int, yOff int) { } } +func (b *Buffer) reload() { + // if the file doesn't exist, try to create it before reading it + if _, err := os.Stat(b.filePath); os.IsNotExist(err) { + // this shouldn't really happen, for some + // reason the file no longer exists? + log.Println("File does not exist when reloading?! " + b.filePath) + return + } + + // if the file has modifications made to it + // ask if the user wants to reload the file or not + // otherwise re-load it anyway. + if b.modified { + ok := dialog.Message("This file has been modified, would you like to reload?").YesNo() + if !ok { + return + } + } + + contents, err := ioutil.ReadFile(b.filePath) + if err != nil { + panic(err) + } + + b.contents = []*rope.Rope{} + + lines := strings.Split(string(contents), "\n") + for _, line := range lines { + b.appendLine(line) + } + + // TODO perhaps when we reload the current line might not exist or something + // try and set the cursor to what it was before but maybe make sure its not out + // of bounds, etc. + + b.modified = false +} + func (b *Buffer) OpenFile(filePath string) { b.filePath = filePath @@ -212,6 +251,9 @@ func (b *Buffer) OpenFile(filePath string) { panic(err) } + // add the file to the watcher. + b.parent.registerFile(filePath, b) + lines := strings.Split(string(contents), "\n") for _, line := range lines { b.appendLine(line) diff --git a/gui/view.go b/gui/view.go index f10eaf2..158f36f 100644 --- a/gui/view.go +++ b/gui/view.go @@ -1,15 +1,35 @@ package gui import ( + "fmt" "log" "runtime" "unicode" "github.com/felixangell/phi/cfg" "github.com/felixangell/strife" + "github.com/fsnotify/fsnotify" "github.com/veandco/go-sdl2/sdl" ) +type bufferEvent interface { + Process(view *View) + String() string +} + +type ReloadBufferEvent struct { + buff *Buffer +} + +func (r *ReloadBufferEvent) Process(view *View) { + log.Println("reloading buffer", r.buff.filePath) + r.buff.reload() +} + +func (r *ReloadBufferEvent) String() string { + return "reload-buffer-event" +} + // View is an array of buffers basically. type View struct { BaseComponent @@ -18,12 +38,18 @@ type View struct { buffers []*BufferPane focusedBuff int commandPalette *CommandPalette + + watcher *fsnotify.Watcher + bufferMap map[string]*Buffer + bufferEvents chan bufferEvent } func NewView(width, height int, conf *cfg.TomlConfig) *View { view := &View{ - conf: conf, - buffers: []*BufferPane{}, + conf: conf, + buffers: []*BufferPane{}, + bufferMap: map[string]*Buffer{}, + bufferEvents: make(chan bufferEvent), } view.Translate(width, height) @@ -32,9 +58,65 @@ func NewView(width, height int, conf *cfg.TomlConfig) *View { view.commandPalette = NewCommandPalette(*conf, view) view.UnfocusBuffers() + var err error + view.watcher, err = fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + // ? + } + + // goroutine to handle all of the fsnotify events + // converts them into events phi can handle cleanly. + go func() { + for { + select { + case event := <-view.watcher.Events: + log.Println("evt: ", event) + if event.Op&fsnotify.Write == fsnotify.Write { + + // modified so we specify a reload event + buff, ok := view.bufferMap[event.Name] + if !ok { + panic(fmt.Sprintf("no such buffer for file '%s'", event.Name)) + break + } + + view.bufferEvents <- &ReloadBufferEvent{buff} + log.Println("modified file:", event.Name) + } + case err := <-view.watcher.Errors: + log.Println("error:", err) + } + } + }() + + // handles all of the phi events + go func() { + for { + event := <-view.bufferEvents + event.Process(view) + } + }() + return view } +func (n *View) registerFile(path string, buff *Buffer) { + log.Println("Registering file ", path) + + err := n.watcher.Add(path) + if err != nil { + log.Println(fmt.Sprintf("Failed to register file '%s'", path), "to buffer ", buff.index) + return + } + + n.bufferMap[path] = buff +} + +func (n *View) Close() { + n.watcher.Close() +} + func (n *View) hidePalette() { p := n.commandPalette p.clearInput()