From e73eb8bc94580b306d978ece2188549e52238f48 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 8 Apr 2020 18:31:56 +1000 Subject: [PATCH] WIP --- main.go | 2 +- pkg/app/app.go | 105 +++++++++++----------------- pkg/gui/file_watching.go | 146 --------------------------------------- pkg/gui/files_panel.go | 4 -- pkg/gui/gui.go | 10 +-- 5 files changed, 42 insertions(+), 225 deletions(-) delete mode 100644 pkg/gui/file_watching.go diff --git a/main.go b/main.go index 3efc287..af868a5 100644 --- a/main.go +++ b/main.go @@ -64,7 +64,7 @@ func main() { log.Fatal(err.Error()) } - app, err := app.NewApp(appConfig, filterPath) + app, err := app.NewApp(appConfig) if err == nil { err = app.Run() diff --git a/pkg/app/app.go b/pkg/app/app.go index 02d8f8c..56133fe 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1,8 +1,7 @@ package app import ( - "bufio" - "fmt" + "errors" "io" "io/ioutil" "os" @@ -22,14 +21,13 @@ import ( type App struct { closers []io.Closer - Config config.AppConfigurer - Log *logrus.Entry - OSCommand *commands.OSCommand - GitCommand *commands.GitCommand - Gui *gui.Gui - Tr *i18n.Localizer - Updater *updates.Updater // may only need this on the Gui - ClientContext string + Config config.AppConfigurer + Log *logrus.Entry + OSCommand *commands.OSCommand + GitCommand *commands.GitCommand + Gui *gui.Gui + Tr *i18n.Localizer + Updater *updates.Updater // may only need this on the Gui } type errorMapping struct { @@ -91,7 +89,7 @@ func newLogger(config config.AppConfigurer) *logrus.Entry { } // NewApp bootstrap a new application -func NewApp(config config.AppConfigurer, filterPath string) (*App, error) { +func NewApp(config config.AppConfigurer) (*App, error) { app := &App{ closers: []io.Closer{}, Config: config, @@ -100,12 +98,6 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) { app.Log = newLogger(config) app.Tr = i18n.NewLocalizer(app.Log) - // if we are being called in 'demon' mode, we can just return here - app.ClientContext = os.Getenv("LAZYGIT_CLIENT_COMMAND") - if app.ClientContext != "" { - return app, nil - } - app.OSCommand = commands.NewOSCommand(app.Log, config) app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr) @@ -113,7 +105,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) { return app, err } - if err := app.setupRepo(); err != nil { + if err := app.setupPackage(); err != nil { return app, err } @@ -121,63 +113,51 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) { if err != nil { return app, err } - app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater, filterPath) + app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater) if err != nil { return app, err } return app, nil } -func (app *App) setupRepo() error { - // if we are not in a git repo, we ask if we want to `git init` - if err := app.OSCommand.RunCommand("git status"); err != nil { - if !strings.Contains(err.Error(), "Not a git repository") { - return err - } - fmt.Print(app.Tr.SLocalize("CreateRepo")) - response, _ := bufio.NewReader(os.Stdin).ReadString('\n') - if strings.Trim(response, " \n") != "y" { - os.Exit(1) - } - if err := app.OSCommand.RunCommand("git init"); err != nil { - return err - } +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false } - return nil + return !info.IsDir() } -func (app *App) Run() error { - if app.ClientContext == "INTERACTIVE_REBASE" { - return app.Rebase() - } - - if app.ClientContext == "EXIT_IMMEDIATELY" { - os.Exit(0) +func (app *App) setupPackage() error { + // ensure we have a package.json file here or in an ancestor directory + // if there's not, pick the first one from state or return an error + dir, err := os.Getwd() + if err != nil { + return err } + for { + if fileExists("package.json") { + return nil + } - err := app.Gui.RunWithSubprocesses() - return err -} - -// Rebase contains logic for when we've been run in demon mode, meaning we've -// given lazynpm as a command for git to call e.g. to edit a file -func (app *App) Rebase() error { - app.Log.Info("Lazynpm invoked as interactive rebase demon") - app.Log.Info("args: ", os.Args) - - if strings.HasSuffix(os.Args[1], "git-rebase-todo") { - if err := ioutil.WriteFile(os.Args[1], []byte(os.Getenv("LAZYGIT_REBASE_TODO")), 0644); err != nil { + if err := os.Chdir(".."); err != nil { return err } - } else if strings.HasSuffix(os.Args[1], ".git/COMMIT_EDITMSG") { - // if we are rebasing and squashing, we'll see a COMMIT_EDITMSG - // but in this case we don't need to edit it, so we'll just return - } else { - app.Log.Info("Lazynpm demon did not match on any use cases") + newDir, err := os.Getwd() + if err != nil { + return err + } + if newDir == dir { + return errors.New("must start lazynpm in an npm package") + } + dir = newDir } +} - return nil +func (app *App) Run() error { + err := app.Gui.RunWithSubprocesses() + return err } // Close closes any resources @@ -195,12 +175,7 @@ func (app *App) Close() error { func (app *App) KnownError(err error) (string, bool) { errorMessage := err.Error() - mappings := []errorMapping{ - { - originalError: "fatal: not a git repository (or any of the parent directories): .git", - newError: app.Tr.SLocalize("notARepository"), - }, - } + mappings := []errorMapping{} for _, mapping := range mappings { if strings.Contains(errorMessage, mapping.originalError) { diff --git a/pkg/gui/file_watching.go b/pkg/gui/file_watching.go deleted file mode 100644 index 7bd7753..0000000 --- a/pkg/gui/file_watching.go +++ /dev/null @@ -1,146 +0,0 @@ -package gui - -import ( - "os" - "path/filepath" - - "github.com/fsnotify/fsnotify" - "github.com/jesseduffield/lazynpm/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 = 50 - -type fileWatcher struct { - Watcher *fsnotify.Watcher - WatchedFilenames []string - Log *logrus.Entry - Disabled bool -} - -func NewFileWatcher(log *logrus.Entry) *fileWatcher { - // TODO: get this going again, and ensure we don't see any crashes from it - return &fileWatcher{ - Disabled: true, - } - - watcher, err := fsnotify.NewWatcher() - - if err != nil { - log.Error(err) - return &fileWatcher{ - Disabled: true, - } - } - - 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 { - if w.Disabled { - return nil - } - - if len(files) == 0 { - return nil - } - - // watch the files for changes - dirName, err := os.Getwd() - if err != nil { - return err - } - - for _, file := range files[0:min(MAX_WATCHED_FILES, len(files))] { - if file.Deleted { - continue - } - filename := filepath.Join(dirName, file.Name) - if w.watchingFilename(filename) { - continue - } - if len(w.WatchedFilenames) > MAX_WATCHED_FILES { - w.popOldestFilename() - } - - w.watchFilename(filename) - } - - return nil -} - -func min(a int, b int) int { - if a < b { - return a - } - return b -} - -// 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() { - gui.fileWatcher = NewFileWatcher(gui.Log) - if gui.fileWatcher.Disabled { - return - } - go func() { - for { - select { - // watch for 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 - } - // only refresh if we're not already - if !gui.State.IsRefreshingFiles { - gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []int{FILES}}) - } - - // watch for errors - case err := <-gui.fileWatcher.Watcher.Errors: - if err != nil { - gui.Log.Warn(err) - } - } - } - }() -} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 0c4dae7..be23297 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -374,10 +374,6 @@ func (gui *Gui) refreshStateFiles() error { files := gui.GitCommand.GetStatusFiles() gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files) - if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil { - return err - } - gui.refreshSelectedLine(&gui.State.Panels.Files.SelectedLine, len(gui.State.Files)) return nil } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 79e2c7d..e39bfc1 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -89,7 +89,6 @@ type Gui struct { statusManager *statusManager credentials credentials waitForIntro sync.WaitGroup - fileWatcher *fileWatcher viewBufferManagerMap map[string]*tasks.ViewBufferManager stopChan chan struct{} } @@ -273,7 +272,7 @@ func (gui *Gui) resetState() { // for now the split view will always be on // NewGui builds a new gui handler -func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater, filterPath string) (*Gui, error) { +func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) { gui := &Gui{ Log: log, GitCommand: gitCommand, @@ -286,9 +285,6 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma } gui.resetState() - gui.State.FilterPath = filterPath - - gui.watchFilesForChanges() gui.GenerateSentinelErrors() @@ -371,10 +367,6 @@ func (gui *Gui) RunWithSubprocesses() error { } gui.viewBufferManagerMap = map[string]*tasks.ViewBufferManager{} - if !gui.fileWatcher.Disabled { - gui.fileWatcher.Watcher.Close() - } - close(gui.stopChan) if err == gocui.ErrQuit {