diff --git a/pkg/gui/file_watching.go b/pkg/gui/file_watching.go index 90fb32639..9ad7f68c4 100644 --- a/pkg/gui/file_watching.go +++ b/pkg/gui/file_watching.go @@ -4,24 +4,103 @@ import ( "os" "path/filepath" + "github.com/davecgh/go-spew/spew" "github.com/fsnotify/fsnotify" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/sirupsen/logrus" ) +// macs for some bizarre reason cap the number of watchable files to 256. +// there's no obvious platform agonstic way to check the situation of the user's +// computer so we're just arbitrarily capping at 200. This isn't so bad because +// file watching is only really an added bonus for faster refreshing. +const MAX_WATCHED_FILES = 200 + +type fileWatcher struct { + Watcher *fsnotify.Watcher + WatchedFilenames []string + Log *logrus.Entry +} + +func NewFileWatcher(log *logrus.Entry) *fileWatcher { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Error(err) + return nil + } + + return &fileWatcher{ + Watcher: watcher, + Log: log, + WatchedFilenames: make([]string, 0, MAX_WATCHED_FILES), + } +} + +func (w *fileWatcher) watchingFilename(filename string) bool { + for _, watchedFilename := range w.WatchedFilenames { + if watchedFilename == filename { + return true + } + } + return false +} + +func (w *fileWatcher) popOldestFilename() { + // shift the last off the array to make way for this one + oldestFilename := w.WatchedFilenames[0] + w.WatchedFilenames = w.WatchedFilenames[1:] + if err := w.Watcher.Remove(oldestFilename); err != nil { + // swallowing errors here because it doesn't really matter if we can't unwatch a file + w.Log.Warn(err) + } +} + +func (w *fileWatcher) watchFilename(filename string) { + w.Log.Warn(filename) + if err := w.Watcher.Add(filename); err != nil { + // swallowing errors here because it doesn't really matter if we can't watch a file + w.Log.Warn(err) + } + + // assume we're watching it now to be safe + w.WatchedFilenames = append(w.WatchedFilenames, filename) +} + +func (w *fileWatcher) addFilesToFileWatcher(files []*commands.File) error { + // watch the files for changes + dirName, err := os.Getwd() + if err != nil { + return err + } + + for _, file := range files { + filename := filepath.Join(dirName, file.Name) + if w.watchingFilename(filename) { + continue + } + if len(w.WatchedFilenames) > MAX_WATCHED_FILES { + w.popOldestFilename() + } + + w.watchFilename(filename) + w.Log.Warn(spew.Sdump(w.WatchedFilenames)) + } + + return nil +} + // NOTE: given that we often edit files ourselves, this may make us end up refreshing files too often // TODO: consider watching the whole directory recursively (could be more expensive) func (gui *Gui) watchFilesForChanges() { - var err error - gui.fileWatcher, err = fsnotify.NewWatcher() - if err != nil { - gui.Log.Error(err) + gui.fileWatcher = NewFileWatcher(gui.Log) + if gui.fileWatcher == nil { return } go func() { for { select { // watch for events - case event := <-gui.fileWatcher.Events: + case event := <-gui.fileWatcher.Watcher.Events: if event.Op == fsnotify.Chmod { // for some reason we pick up chmod events when they don't actually happen continue @@ -37,7 +116,7 @@ func (gui *Gui) watchFilesForChanges() { } // watch for errors - case err := <-gui.fileWatcher.Errors: + case err := <-gui.fileWatcher.Watcher.Errors: if err != nil { gui.Log.Warn(err) } @@ -45,20 +124,3 @@ func (gui *Gui) watchFilesForChanges() { } }() } - -func (gui *Gui) addFilesToFileWatcher(files []*commands.File) error { - // watch the files for changes - dirName, err := os.Getwd() - if err != nil { - return err - } - - for _, file := range files { - if err := gui.fileWatcher.Add(filepath.Join(dirName, file.Name)); err != nil { - // swallowing errors here because it doesn't really matter if we can't watch a file - gui.Log.Warn(err) - } - } - - return nil -} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index aa2e8592e..82a81654c 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -356,7 +356,7 @@ func (gui *Gui) refreshStateFiles() error { files := gui.GitCommand.GetStatusFiles() gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files) - if err := gui.addFilesToFileWatcher(files); err != nil { + if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil { return err } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index fc24bbd6b..530c00383 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -15,7 +15,6 @@ import ( "strings" "time" - "github.com/fsnotify/fsnotify" "github.com/go-errors/errors" // "strings" @@ -82,7 +81,7 @@ type Gui struct { statusManager *statusManager credentials credentials waitForIntro sync.WaitGroup - fileWatcher *fsnotify.Watcher + fileWatcher *fileWatcher } // for now the staging panel state, unlike the other panel states, is going to be @@ -840,7 +839,7 @@ func (gui *Gui) RunWithSubprocesses() error { } } - gui.fileWatcher.Close() + gui.fileWatcher.Watcher.Close() break } else if err == gui.Errors.ErrSwitchRepo {