From 51547e38227b2443de955f4d17d46429039cf9f1 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 30 Jan 2022 09:53:28 +1100 Subject: move all refresh code into the one file --- pkg/gui/basic_context.go | 74 ------ pkg/gui/branches_panel.go | 30 --- pkg/gui/commits_panel.go | 92 ------- pkg/gui/context/base_context.go | 67 +++-- pkg/gui/context/view_trait.go | 2 +- pkg/gui/files_panel.go | 165 ------------ pkg/gui/merge_panel.go | 21 -- pkg/gui/reflog_panel.go | 46 ---- pkg/gui/refresh.go | 577 ++++++++++++++++++++++++++++++++++++++++ pkg/gui/remotes_panel.go | 24 -- pkg/gui/simple_context.go | 74 ++++++ pkg/gui/stash_panel.go | 7 - pkg/gui/status_panel.go | 28 -- pkg/gui/submodules_panel.go | 11 - pkg/gui/tags_panel.go | 11 - pkg/gui/view_helpers.go | 135 ---------- 16 files changed, 682 insertions(+), 682 deletions(-) delete mode 100644 pkg/gui/basic_context.go create mode 100644 pkg/gui/refresh.go create mode 100644 pkg/gui/simple_context.go (limited to 'pkg/gui') diff --git a/pkg/gui/basic_context.go b/pkg/gui/basic_context.go deleted file mode 100644 index e9a3ca933..000000000 --- a/pkg/gui/basic_context.go +++ /dev/null @@ -1,74 +0,0 @@ -package gui - -import ( - "github.com/jesseduffield/lazygit/pkg/gui/context" - "github.com/jesseduffield/lazygit/pkg/gui/types" -) - -type SimpleContext struct { - OnFocus func(opts ...types.OnFocusOpts) error - OnFocusLost func() error - OnRender func() error - // this is for pushing some content to the main view - OnRenderToMain func(opts ...types.OnFocusOpts) error - - *context.BaseContext -} - -type NewSimpleContextOpts struct { - OnFocus func(opts ...types.OnFocusOpts) error - OnFocusLost func() error - OnRender func() error - // this is for pushing some content to the main view - OnRenderToMain func(opts ...types.OnFocusOpts) error -} - -func NewSimpleContext(baseContext *context.BaseContext, opts NewSimpleContextOpts) *SimpleContext { - return &SimpleContext{ - OnFocus: opts.OnFocus, - OnFocusLost: opts.OnFocusLost, - OnRender: opts.OnRender, - OnRenderToMain: opts.OnRenderToMain, - BaseContext: baseContext, - } -} - -var _ types.Context = &SimpleContext{} - -func (self *SimpleContext) HandleRender() error { - if self.OnRender != nil { - return self.OnRender() - } - return nil -} - -func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error { - if self.OnFocus != nil { - if err := self.OnFocus(opts...); err != nil { - return err - } - } - - if self.OnRenderToMain != nil { - if err := self.OnRenderToMain(opts...); err != nil { - return err - } - } - - return nil -} - -func (self *SimpleContext) HandleFocusLost() error { - if self.OnFocusLost != nil { - return self.OnFocusLost() - } - return nil -} - -func (self *SimpleContext) HandleRenderToMain() error { - if self.OnRenderToMain != nil { - return self.OnRenderToMain() - } - - return nil -} diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 1efde5c28..59160bc0f 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -47,36 +47,6 @@ func (gui *Gui) branchesRenderToMain() error { }) } -// gui.refreshStatus is called at the end of this because that's when we can -// be sure there is a state.Branches array to pick the current branch from -func (gui *Gui) refreshBranches() { - reflogCommits := gui.State.FilteredReflogCommits - if gui.State.Modes.Filtering.Active() { - // in filter mode we filter our reflog commits to just those containing the path - // however we need all the reflog entries to populate the recencies of our branches - // which allows us to order them correctly. So if we're filtering we'll just - // manually load all the reflog commits here - var err error - reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "") - if err != nil { - gui.c.Log.Error(err) - } - } - - branches, err := gui.git.Loaders.Branches.Load(reflogCommits) - if err != nil { - _ = gui.c.Error(err) - } - - gui.State.Branches = branches - - if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Branches); err != nil { - gui.c.Log.Error(err) - } - - gui.refreshStatus() -} - // specific functions func (gui *Gui) handleBranchPress() error { diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 26b3fac09..b2233f377 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -1,11 +1,7 @@ package gui import ( - "sync" - - "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -58,81 +54,6 @@ func (gui *Gui) branchCommitsRenderToMain() error { }) } -// during startup, the bottleneck is fetching the reflog entries. We need these -// on startup to sort the branches by recency. So we have two phases: INITIAL, and COMPLETE. -// In the initial phase we don't get any reflog commits, but we asynchronously get them -// and refresh the branches after that -func (gui *Gui) refreshReflogCommitsConsideringStartup() { - switch gui.State.StartupStage { - case INITIAL: - go utils.Safe(func() { - _ = gui.refreshReflogCommits() - gui.refreshBranches() - gui.State.StartupStage = COMPLETE - }) - - case COMPLETE: - _ = gui.refreshReflogCommits() - } -} - -// whenever we change commits, we should update branches because the upstream/downstream -// counts can change. Whenever we change branches we should probably also change commits -// e.g. in the case of switching branches. -func (gui *Gui) refreshCommits() { - wg := sync.WaitGroup{} - wg.Add(2) - - go utils.Safe(func() { - gui.refreshReflogCommitsConsideringStartup() - - gui.refreshBranches() - wg.Done() - }) - - go utils.Safe(func() { - _ = gui.refreshCommitsWithLimit() - ctx, ok := gui.State.Contexts.CommitFiles.GetParentContext() - if ok && ctx.GetKey() == context.BRANCH_COMMITS_CONTEXT_KEY { - // This makes sense when we've e.g. just amended a commit, meaning we get a new commit SHA at the same position. - // However if we've just added a brand new commit, it pushes the list down by one and so we would end up - // showing the contents of a different commit than the one we initially entered. - // Ideally we would know when to refresh the commit files context and when not to, - // or perhaps we could just pop that context off the stack whenever cycling windows. - // For now the awkwardness remains. - commit := gui.getSelectedLocalCommit() - if commit != nil { - gui.State.Panels.CommitFiles.refName = commit.RefName() - _ = gui.refreshCommitFilesView() - } - } - wg.Done() - }) - - wg.Wait() -} - -func (gui *Gui) refreshCommitsWithLimit() error { - gui.Mutexes.BranchCommitsMutex.Lock() - defer gui.Mutexes.BranchCommitsMutex.Unlock() - - commits, err := gui.git.Loaders.Commits.GetCommits( - loaders.GetCommitsOptions{ - Limit: gui.State.Panels.Commits.LimitCommits, - FilterPath: gui.State.Modes.Filtering.GetPath(), - IncludeRebaseCommits: true, - RefName: gui.refForLog(), - All: gui.ShowWholeGitGraph, - }, - ) - if err != nil { - return err - } - gui.State.Commits = commits - - return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) -} - func (gui *Gui) refForLog() string { bisectInfo := gui.git.Bisect.GetInfo() gui.State.BisectInfo = bisectInfo @@ -148,16 +69,3 @@ func (gui *Gui) refForLog() string { return bisectInfo.GetStartSha() } - -func (gui *Gui) refreshRebaseCommits() error { - gui.Mutexes.BranchCommitsMutex.Lock() - defer gui.Mutexes.BranchCommitsMutex.Unlock() - - updatedCommits, err := gui.git.Loaders.Commits.MergeRebasingCommits(gui.State.Commits) - if err != nil { - return err - } - gui.State.Commits = updatedCommits - - return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) -} diff --git a/pkg/gui/context/base_context.go b/pkg/gui/context/base_context.go index e95b298a4..f6007fe5f 100644 --- a/pkg/gui/context/base_context.go +++ b/pkg/gui/context/base_context.go @@ -3,35 +3,48 @@ package context import "github.com/jesseduffield/lazygit/pkg/gui/types" type BaseContext struct { - Kind types.ContextKind - Key types.ContextKey + kind types.ContextKind + key types.ContextKey ViewName string - WindowName string - OnGetOptionsMap func() map[string]string + windowName string + onGetOptionsMap func() map[string]string *ParentContextMgr } +type NewBaseContextOpts struct { + Kind types.ContextKind + Key types.ContextKey + ViewName string + WindowName string + + OnGetOptionsMap func() map[string]string +} + +func NewBaseContext(opts NewBaseContextOpts) *BaseContext { + return &BaseContext{ + kind: opts.Kind, + key: opts.Key, + ViewName: opts.ViewName, + windowName: opts.WindowName, + onGetOptionsMap: opts.OnGetOptionsMap, + ParentContextMgr: &ParentContextMgr{}, + } +} + func (self *BaseContext) GetOptionsMap() map[string]string { - if self.OnGetOptionsMap != nil { - return self.OnGetOptionsMap() + if self.onGetOptionsMap != nil { + return self.onGetOptionsMap() } return nil } func (self *BaseContext) SetWindowName(windowName string) { - self.WindowName = windowName + self.windowName = windowName } func (self *BaseContext) GetWindowName() string { - windowName := self.WindowName - - if windowName != "" { - return windowName - } - - // TODO: actually set this for everything so we don't default to the view name - return self.ViewName + return self.windowName } func (self *BaseContext) GetViewName() string { @@ -39,29 +52,9 @@ func (self *BaseContext) GetViewName() string { } func (self *BaseContext) GetKind() types.ContextKind { - return self.Kind + return self.kind } func (self *BaseContext) GetKey() types.ContextKey { - return self.Key -} - -type NewBaseContextOpts struct { - Kind types.ContextKind - Key types.ContextKey - ViewName string - WindowName string - - OnGetOptionsMap func() map[string]string -} - -func NewBaseContext(opts NewBaseContextOpts) *BaseContext { - return &BaseContext{ - Kind: opts.Kind, - Key: opts.Key, - ViewName: opts.ViewName, - WindowName: opts.WindowName, - OnGetOptionsMap: opts.OnGetOptionsMap, - ParentContextMgr: &ParentContextMgr{}, - } + return self.key } diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go index 4c02a4990..1409ed561 100644 --- a/pkg/gui/context/view_trait.go +++ b/pkg/gui/context/view_trait.go @@ -36,7 +36,7 @@ func (self *ViewTrait) SetFooter(value string) { } func (self *ViewTrait) SetOriginX(value int) { - self.getView().SetOriginX(value) + _ = self.getView().SetOriginX(value) } // tells us the bounds of line indexes shown in the view currently diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 04d05df6f..db4bd3a54 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -2,12 +2,8 @@ package gui import ( "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/types/enums" - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -87,167 +83,6 @@ func (gui *Gui) filesRenderToMain() error { return gui.refreshMainViews(refreshOpts) } -func (gui *Gui) refreshFilesAndSubmodules() error { - gui.Mutexes.RefreshingFilesMutex.Lock() - gui.State.IsRefreshingFiles = true - defer func() { - gui.State.IsRefreshingFiles = false - gui.Mutexes.RefreshingFilesMutex.Unlock() - }() - - prevSelectedPath := gui.getSelectedPath() - - if err := gui.refreshStateSubmoduleConfigs(); err != nil { - return err - } - - if err := gui.refreshMergeState(); err != nil { - return err - } - - if err := gui.refreshStateFiles(); err != nil { - return err - } - - gui.OnUIThread(func() error { - if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil { - gui.c.Log.Error(err) - } - - if types.ContextKey(gui.Views.Files.Context) == context.FILES_CONTEXT_KEY { - // doing this a little custom (as opposed to using gui.c.PostRefreshUpdate) because we handle selecting the file explicitly below - if err := gui.State.Contexts.Files.HandleRender(); err != nil { - return err - } - } - - if gui.currentContext().GetKey() == context.FILES_CONTEXT_KEY { - currentSelectedPath := gui.getSelectedPath() - alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath - if !alreadySelected { - gui.takeOverMergeConflictScrolling() - } - - gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) - return gui.filesRenderToMain() - } - - return nil - }) - - return nil -} - -func (gui *Gui) refreshStateFiles() error { - state := gui.State - - // keep track of where the cursor is currently and the current file names - // when we refresh, go looking for a matching name - // move the cursor to there. - - selectedNode := gui.getSelectedFileNode() - - prevNodes := gui.State.FileTreeViewModel.GetAllItems() - prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx - - // If git thinks any of our files have inline merge conflicts, but they actually don't, - // we stage them. - // Note that if files with merge conflicts have both arisen and have been resolved - // between refreshes, we won't stage them here. This is super unlikely though, - // and this approach spares us from having to call `git status` twice in a row. - // Although this also means that at startup we won't be staging anything until - // we call git status again. - pathsToStage := []string{} - prevConflictFileCount := 0 - for _, file := range state.FileTreeViewModel.GetAllFiles() { - if file.HasMergeConflicts { - prevConflictFileCount++ - } - if file.HasInlineMergeConflicts { - hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name) - if err != nil { - gui.Log.Error(err) - } else if !hasConflicts { - pathsToStage = append(pathsToStage, file.Name) - } - } - } - - if len(pathsToStage) > 0 { - gui.c.LogAction(gui.Tr.Actions.StageResolvedFiles) - if err := gui.git.WorkingTree.StageFiles(pathsToStage); err != nil { - return gui.c.Error(err) - } - } - - files := gui.git.Loaders.Files. - GetStatusFiles(loaders.GetStatusFileOptions{}) - - conflictFileCount := 0 - for _, file := range files { - if file.HasMergeConflicts { - conflictFileCount++ - } - } - - if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 { - gui.OnUIThread(func() error { return gui.promptToContinueRebase() }) - } - - // for when you stage the old file of a rename and the new file is in a collapsed dir - state.FileTreeViewModel.RWMutex.Lock() - for _, file := range files { - if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path { - state.FileTreeViewModel.ExpandToPath(file.Name) - } - } - - // only taking over the filter if it hasn't already been set by the user. - // Though this does make it impossible for the user to actually say they want to display all if - // conflicts are currently being shown. Hmm. Worth it I reckon. If we need to add some - // extra state here to see if the user's set the filter themselves we can do that, but - // I'd prefer to maintain as little state as possible. - if conflictFileCount > 0 { - if state.FileTreeViewModel.GetFilter() == filetree.DisplayAll { - state.FileTreeViewModel.SetFilter(filetree.DisplayConflicted) - } - } else if state.FileTreeViewModel.GetFilter() == filetree.DisplayConflicted { - state.FileTreeViewModel.SetFilter(filetree.DisplayAll) - } - - state.FileTreeViewModel.SetFiles(files) - state.FileTreeViewModel.RWMutex.Unlock() - - if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil { - return err - } - - if selectedNode != nil { - newIdx := gui.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], state.FileTreeViewModel.GetAllItems()) - if newIdx != -1 && newIdx != prevSelectedLineIdx { - newNode := state.FileTreeViewModel.GetItemAtIndex(newIdx) - // when not in tree mode, we show merge conflict files at the top, so you - // can work through them one by one without having to sift through a large - // set of files. If you have just fixed the merge conflicts of a file, we - // actually don't want to jump to that file's new position, because that - // file will now be ages away amidst the other files without merge - // conflicts: the user in this case would rather work on the next file - // with merge conflicts, which will have moved up to fill the gap left by - // the last file, meaning the cursor doesn't need to move at all. - leaveCursor := !state.FileTreeViewModel.InTreeMode() && newNode != nil && - selectedNode.File != nil && selectedNode.File.HasMergeConflicts && - newNode.File != nil && !newNode.File.HasMergeConflicts - - if !leaveCursor { - state.Panels.Files.SelectedLineIdx = newIdx - } - } - } - - gui.refreshSelectedLine(state.Panels.Files, state.FileTreeViewModel.GetItemsLength()) - return nil -} - // promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress func (gui *Gui) promptToContinueRebase() error { gui.takeOverMergeConflictScrolling() diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 865eb4ffd..54783e986 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -8,7 +8,6 @@ import ( "math" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -284,26 +283,6 @@ func (gui *Gui) setConflictsAndRenderWithLock(path string, hasFocus bool) (bool, return gui.setConflictsAndRender(path, hasFocus) } -func (gui *Gui) refreshMergeState() error { - gui.State.Panels.Merging.Lock() - defer gui.State.Panels.Merging.Unlock() - - if gui.currentContext().GetKey() != context.MAIN_MERGING_CONTEXT_KEY { - return nil - } - - hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true) - if err != nil { - return gui.c.Error(err) - } - - if !hasConflicts { - return gui.escapeMerge() - } - - return nil -} - func (gui *Gui) switchToMerge(path string) error { gui.takeOverMergeConflictScrolling() diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index 74254fa7b..7f46a2748 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -37,52 +37,6 @@ func (gui *Gui) reflogCommitsRenderToMain() error { }) } -// the reflogs panel is the only panel where we cache data, in that we only -// load entries that have been created since we last ran the call. This means -// we need to be more careful with how we use this, and to ensure we're emptying -// the reflogs array when changing contexts. -// This method also manages two things: ReflogCommits and FilteredReflogCommits. -// FilteredReflogCommits are rendered in the reflogs panel, and ReflogCommits -// are used by the branches panel to obtain recency values for sorting. -func (gui *Gui) refreshReflogCommits() error { - // pulling state into its own variable incase it gets swapped out for another state - // and we get an out of bounds exception - state := gui.State - var lastReflogCommit *models.Commit - if len(state.ReflogCommits) > 0 { - lastReflogCommit = state.ReflogCommits[0] - } - - refresh := func(stateCommits *[]*models.Commit, filterPath string) error { - commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommits. - GetReflogCommits(lastReflogCommit, filterPath) - if err != nil { - return gui.c.Error(err) - } - - if onlyObtainedNewReflogCommits { - *stateCommits = append(commits, *stateCommits...) - } else { - *stateCommits = commits - } - return nil - } - - if err := refresh(&state.ReflogCommits, ""); err != nil { - return err - } - - if gui.State.Modes.Filtering.Active() { - if err := refresh(&state.FilteredReflogCommits, state.Modes.Filtering.GetPath()); err != nil { - return err - } - } else { - state.FilteredReflogCommits = state.ReflogCommits - } - - return gui.c.PostRefreshUpdate(gui.State.Contexts.ReflogCommits) -} - func (gui *Gui) CheckoutReflogCommit() error { commit := gui.getSelectedReflogCommit() if commit == nil { diff --git a/pkg/gui/refresh.go b/pkg/gui/refresh.go new file mode 100644 index 000000000..bf22b5ff6 --- /dev/null +++ b/pkg/gui/refresh.go @@ -0,0 +1,577 @@ +package gui + +import ( + "fmt" + "strings" + "sync" + + "github.com/jesseduffield/lazygit/pkg/commands/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" + "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/filetree" + "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" + "github.com/jesseduffield/lazygit/pkg/gui/style" + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func getScopeNames(scopes []types.RefreshableView) []string { + scopeNameMap := map[types.RefreshableView]string{ + types.COMMITS: "commits", + types.BRANCHES: "branches", + types.FILES: "files", + types.SUBMODULES: "submodules", + types.STASH: "stash", + types.REFLOG: "reflog", + types.TAGS: "tags", + types.REMOTES: "remotes", + types.STATUS: "status", + types.BISECT_INFO: "bisect", + } + + scopeNames := make([]string, len(scopes)) + for i, scope := range scopes { + scopeNames[i] = scopeNameMap[scope] + } + + return scopeNames +} + +func getModeName(mode types.RefreshMode) string { + switch mode { + case types.SYNC: + return "sync" + case types.ASYNC: + return "async" + case types.BLOCK_UI: + return "block-ui" + default: + return "unknown mode" + } +} + +func arrToMap(arr []types.RefreshableView) map[types.RefreshableView]bool { + output := map[types.RefreshableView]bool{} + for _, el := range arr { + output[el] = true + } + return output +} + +func (gui *Gui) Refresh(options types.RefreshOptions) error { + if options.Scope == nil { + gui.c.Log.Infof( + "refreshing all scopes in %s mode", + getModeName(options.Mode), + ) + } else { + gui.c.Log.Infof( + "refreshing the following scopes in %s mode: %s", + getModeName(options.Mode), + strings.Join(getScopeNames(options.Scope), ","), + ) + } + + wg := sync.WaitGroup{} + + f := func() { + var scopeMap map[types.RefreshableView]bool + if len(options.Scope) == 0 { + scopeMap = arrToMap([]types.RefreshableView{ + types.COMMITS, + types.BRANCHES, + types.FILES, + types.STASH, + types.REFLOG, + types.TAGS, + types.REMOTES, + types.STATUS, + types.BISECT_INFO, + }) + } else { + scopeMap = arrToMap(options.Scope) + } + + refresh := func(f func()) { + wg.Add(1) + func() { + if options.Mode == types.ASYNC { + go utils.Safe(f) + } else { + f() + } + wg.Done() + }() + } + + if scopeMap[types.COMMITS] || scopeMap[types.BRANCHES] || scopeMap[types.REFLOG] || scopeMap[types.BISECT_INFO] { + refresh(gui.refreshCommits) + } else if scopeMap[types.REBASE_COMMITS] { + // the above block handles rebase commits so we only need to call this one + // if we've asked specifically for rebase commits and not those other things + refresh(func() { _ = gui.refreshRebaseCommits() }) + } + + if scopeMap[types.FILES] || scopeMap[types.SUBMODULES] { + refresh(func() { _ = gui.refreshFilesAndSubmodules() }) + } + + if scopeMap[types.STASH] { + refresh(func() { _ = gui.refreshStashEntries() }) + } + + if scopeMap[types.TAGS] { + refresh(func() { _ = gui.refreshTags() }) + } + + if scopeMap[types.REMOTES] { + refresh(func() { _ = gui.refreshRemotes() }) + } + + wg.Wait() + + gui.refreshStatus() + + if options.Then != nil { + options.Then() + } + } + + if options.Mode == types.BLOCK_UI { + gui.OnUIThread(func() error { + f() + return nil + }) + } else { + f() + } + + return nil +} + +// during startup, the bottleneck is fetching the reflog entries. We need these +// on startup to sort the branches by recency. So we have two phases: INITIAL, and COMPLETE. +// In the initial phase we don't get any reflog commits, but we asynchronously get them +// and refresh the branches after that +func (gui *Gui) refreshReflogCommitsConsideringStartup() { + switch gui.State.StartupStage { + case INITIAL: + go utils.Safe(func() { + _ = gui.refreshReflogCommits() + gui.refreshBranches() + gui.State.StartupStage = COMPLETE + }) + + case COMPLETE: + _ = gui.refreshReflogCommits() + } +} + +// whenever we change commits, we should update branches because the upstream/downstream +// counts can change. Whenever we change branches we should probably also change commits +// e.g. in the case of switching branches. +func (gui *Gui) refreshCommits() { + wg := sync.WaitGroup{} + wg.Add(2) + + go utils.Safe(func() { + gui.refreshReflogCommitsConsideringStartup() + + gui.refreshBranches() + wg.Done() + }) + + go utils.Safe(func() { + _ = gui.refreshCommitsWithLimit() + ctx, ok := gui.State.Contexts.CommitFiles.GetParentContext() + if ok && ctx.GetKey() == context.BRANCH_COMMITS_CONTEXT_KEY { + // This makes sense when we've e.g. just amended a commit, meaning we get a new commit SHA at the same position. + // However if we've just added a brand new commit, it pushes the list down by one and so we would end up + // showing the contents of a different commit than the one we initially entered. + // Ideally we would know when to refresh the commit files context and when not to, + // or perhaps we could just pop that context off the stack whenever cycling windows. + // For now the awkwardness remains. + commit := gui.getSelectedLocalCommit() + if commit != nil { + gui.State.Panels.CommitFiles.refName = commit.RefName() + _ = gui.refreshCommitFilesView() + } + } + wg.Done() + }) + + wg.Wait() +} + +func (gui *Gui) refreshCommitsWithLimit() error { + gui.Mutexes.BranchCommitsMutex.Lock() + defer gui.Mutexes.BranchCommitsMutex.Unlock() + + commits, err := gui.git.Loaders.Commits.GetCommits( + loaders.GetCommitsOptions{ + Limit: gui.State.Panels.Commits.LimitCommits, + FilterPath: gui.State.Modes.Filtering.GetPath(), + IncludeRebaseCommits: true, + RefName: gui.refForLog(), + All: gui.ShowWholeGitGraph, + }, + ) + if err != nil { + return err + } + gui.State.Commits = commits + + return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) +} + +func (gui *Gui) refreshRebaseCommits() error { + gui.Mutexes.BranchCommitsMutex.Lock() + defer gui.Mutexes.BranchCommitsMutex.Unlock() + + updatedCommits, err := gui.git.Loaders.Commits.MergeRebasingCommits(gui.State.Commits) + if err != nil { + return err + } + gui.State.Commits = updatedCommits + + return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) +} + +func (self *Gui) refreshTags() error { + tags, err := self.git.Loaders.Tags.GetTags() + if err != nil { + return self.c.Error(err) + } + + self.State.Tags = tags + + return self.postRefreshUpdate(self.State.Contexts.Tags) +} + +func (gui *Gui) refreshStateSubmoduleConfigs() error { + configs, err := gui.git.Submodule.GetConfigs() + if err != nil { + return err + } + + gui.State.Submodules = configs + + return nil +} + +// gui.refreshStatus is called at the end of this because that's when we can +// be sure there is a state.Branches array to pick the current branch from +func (gui *Gui) refreshBranches() { + reflogCommits := gui.State.FilteredReflogCommits + if gui.State.Modes.Filtering.Active() { + // in filter mode we filter our reflog commits to just those containing the path + // however we need all the reflog entries to populate the recencies of our branches + // which allows us to order them correctly. So if we're filtering we'll just + // manually load all the reflog commits here + var err error + reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "") + if err != nil { + gui.c.Log.Error(err) + } + } + + branches, err := gui.git.Loaders.Branches.Load(reflogCommits) + if err != nil { + _ = gui.c.Error(err) + } + + gui.State.Branches = branches + + if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Branches); err != nil { + gui.c.Log.Error(err) + } + + gui.refreshStatus() +} + +func (gui *Gui) refreshFilesAndSubmodules() error { + gui.Mutexes.RefreshingFilesMutex.Lock() + gui.State.IsRefreshingFiles = true + defer func() { + gui.State.IsRefreshingFiles = false + gui.Mutexes.RefreshingFilesMutex.Unlock() + }() + + prevSelectedPath := gui.getSelectedPath() + + if err := gui.refreshStateSubmoduleConfigs(); err != nil { + return err + } + + if err := gui.refreshMergeState(); err != nil { + return err + } + + if err := gui.refreshStateFiles(); err != nil { + return err + } + + gui.OnUIThread(func() error { + if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil { + gui.c.Log.Error(err) + } + + if types.ContextKey(gui.Views.Files.Context) == context.FILES_CONTEXT_KEY { + // doing this a little custom (as opposed to using gui.c.PostRefreshUpdate) because we handle selecting the file explicitly below + if err := gui.State.Contexts.Files.HandleRender(); err != nil { + return err + } + } + + if gui.currentContext().GetKey() == context.FILES_CONTEXT_KEY { + currentSelectedPath := gui.getSelectedPath() + alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath + if !alreadySelected { + gui.takeOverMergeConflictScrolling() + } + + gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) + return gui.filesRenderToMain() + } + + return nil + }) + + return nil +} + +func (gui *Gui) refreshMergeState() error { + gui.State.Panels.Merging.Lock() + defer gui.State.Panels.Merging.Unlock() + + if gui.currentContext().GetKey() != context.MAIN_MERGING_CONTEXT_KEY { + return nil + } + + hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true) + if err != nil { + return gui.c.Error(err) + } + + if !hasConflicts { + return gui.escapeMerge() + } + + return nil +} + +func (gui *Gui) refreshStateFiles() error { + state := gui.State + + // keep track of where the cursor is currently and the current file names + // when we refresh, go looking for a matching name + // move the cursor to there. + + selectedNode := gui.getSelectedFileNode() + + prevNodes := gui.State.FileTreeViewModel.GetAllItems() + prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx + + // If git thinks any of our files have inline merge conflicts, but they actually don't, + // we stage them. + // Note that if files with merge conflicts have both arisen and have been resolved + // between refreshes, we won't stage them here. This is super unlikely though, + // and this approach spares us from having to call `git status` twice in a row. + // Although this also means that at startup we won't be staging anything until + // we call git status again. + pathsToStage := []string{} + prevConflictFileCount := 0 + for _, file := range state.FileTreeViewModel.GetAllFiles() { + if file.HasMergeConflicts { + prevConflictFileCount++ + } + if file.HasInlineMergeConflicts { + hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name) + if err != nil { + gui.Log.Error(err) + } else if !hasConflicts { + pathsToStage = append(pathsToStage, file.Name) + } + } + } + + if len(pathsToStage) > 0 { + gui.c.LogAction(gui.Tr.Actions.StageResolvedFiles) + if err := gui.git.WorkingTree.StageFiles(pathsToStage); err != nil { + return gui.c.Error(err) + } + } + + files := gui.git.Loaders.Files. + GetStatusFiles(loaders.GetStatusFileOptions{}) + + conflictFileCount := 0 + for _, file := range files { + if file.HasMergeConflicts { + conflictFileCount++ + } + } + + if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 { + gui.OnUIThread(func() error { return gui.promptToContinueRebase() }) + } + + // for when you stage the old file of a rename and the new file is in a collapsed dir + state.FileTreeViewModel.RWMutex.Lock() + for _, file := range files { + if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path { + state.FileTreeViewModel.ExpandToPath(file.Name) + } + } + + // only taking over the filter if it hasn't already been set by the user. + // Though this does make it impossible for the user to actually say they want to display all if + // conflicts are currently being shown. Hmm. Worth it I reckon. If we need to add some + // extra state here to see if the user's set the filter themselves we can do that, but + // I'd prefer to maintain as little state as possible. + if conflictFileCount > 0 { + if state.FileTreeViewModel.GetFilter() == filetree.DisplayAll { + state.FileTreeViewModel.SetFilter(filetree.DisplayConflicted) + } + } else if state.FileTreeViewModel.GetFilter() == filetree.DisplayConflicted { + state.FileTreeViewModel.SetFilter(filetree.DisplayAll) + } + + state.FileTreeViewModel.SetFiles(files) + state.FileTreeViewModel.RWMutex.Unlock() + + if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil { + return err + } + + if selectedNode != nil { + newIdx := gui.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], state.FileTreeViewModel.GetAllItems()) + if newIdx != -1 && newIdx != prevSelectedLineIdx { + newNode := state.FileTreeViewModel.GetItemAtIndex(newIdx) + // when not in tree mode, we show merge conflict files at the top, so you + // can work through them one by one without having to sift through a large + // set of files. If you have just fixed the merge conflicts of a file, we + // actually don't want to jump to that file's new position, because that + // file will now be ages away amidst the other files without merge + // conflicts: the user in this case would rather work on the next file + // with merge conflicts, which will have moved up to fill the gap left by + // the last file, meaning the cursor doesn't need to move at all. + leaveCursor := !state.FileTreeViewModel.InTreeMode() && newNode != nil && + selectedNode.File != nil && selectedNode.File.HasMergeConflicts && + newNode.File != nil && !newNode.File.HasMergeConflicts + + if !leaveCursor { + state.Panels.Files.SelectedLineIdx = newIdx + } + } + } + + gui.refreshSelectedLine(state.Panels.Files, state.FileTreeViewModel.GetItemsLength()) + return nil +} + +// the reflogs panel is the only panel where we cache data, in that we only +// load entries that have been created since we last ran the call. This means +// we need to be more careful with how we use this, and to ensure we're emptying +// the reflogs array when changing contexts. +// This method also manages two things: ReflogCommits and FilteredReflogCommits. +// FilteredReflogCommits are rendered in the reflogs panel, and ReflogCommits +// are used by the branches panel to obtain recency values for sorting. +func (gui *Gui) refreshReflogCommits() error { + // pulling state into its own variable incase it gets swapped out for another state + // and we get an out of bounds exception + state := gui.State + var lastReflogCommit *models.Commit + if len(state.ReflogCommits) > 0 { + lastReflogCommit = state.ReflogCommits[0] + } + + refresh := func(stateCommits *[]*models.Commit, filterPath string) error { + commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommits. + GetReflogCommits(lastReflogCommit, filterPath) + if err != nil { + return gui.c.Error(err) + } + + if onlyObtainedNewReflogCommits { + *stateCommits = append(commits, *stateCommits...) + } else { + *stateCommits = commits + } + return nil + } + + if err := refresh(&state.ReflogCommits, ""); err != nil { + return err + } + + if gui.State.Modes.Filtering.Active() { + if err := refresh(&state.FilteredReflogCommits, state.Modes.Filtering.GetPath()); err != nil { + return err + } + } else { + state.FilteredReflogCommits = state.ReflogCommits + } + + return gui.c.PostRefreshUpdate(gui.State.Contexts.ReflogCommits) +} + +func (gui *Gui) refreshRemotes() error { + prevSelectedRemote := gui.getSelectedRemote() + + remotes, err := gui.git.Loaders.Remotes.GetRemotes() + if err != nil { + return gui.c.Error(err) + } + + gui.State.Remotes = remotes + + // we need to ensure our selected remote branches aren't now outdated + if prevSelectedRemote != nil && gui.State.RemoteBranches != nil { + // find remote now + for _, remote := range remotes { + if remote.Name == prevSelectedRemote.Name { + gui.State.RemoteBranches = remote.Branches + } + } + } + + return gui.c.PostRefreshUpdate(gui.mustContextForContextKey(types.ContextKey(gui.Views.Branches.Context))) +} + +func (gui *Gui) refreshStashEntries() error { + gui.State.StashEntries = gui.git.Loaders.Stash. + GetStashEntries(gui.State.Modes.Filtering.GetPath()) + + return gui.postRefreshUpdate(gui.State.Contexts.Stash) +} + +// never call this on its own, it should only be called from within refreshCommits() +func (gui *Gui) refreshStatus() { + gui.Mutexes.RefreshingStatusMutex.Lock() + defer gui.Mutexes.RefreshingStatusMutex.Unlock() + + currentBranch := gui.getCheckedOutBranch() + if currentBranch == nil { + // need to wait for branches to refresh + return + } + status := "" + + if currentBranch.IsRealBranch() { + status += presentation.ColoredBranchStatus(currentBranch) + " " + } + + workingTreeState := gui.git.Status.WorkingTreeState() + if workingTreeState != enums.REBASE_MODE_NONE { + status += style.FgYellow.Sprintf("(%s) ", formatWorkingTreeState(workingTreeState)) + } + + name := presentation.GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name) + repoName := utils.GetCurrentRepoName() + status += fmt.Sprintf("%s → %s ", repoName, name) + + gui.setViewContent(gui.Views.Status, status) +} diff --git a/pkg/gui/remotes_panel.go b/pkg/gui/remotes_panel.go index 1c086e2fc..8e55cd33b 100644 --- a/pkg/gui/remotes_panel.go +++ b/pkg/gui/remotes_panel.go @@ -6,7 +6,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gui/style" - "github.com/jesseduffield/lazygit/pkg/gui/types" ) // list panel functions @@ -36,26 +35,3 @@ func (gui *Gui) remotesRenderToMain() error { }, }) } - -func (gui *Gui) refreshRemotes() error { - prevSelectedRemote := gui.getSelectedRemote() - - remotes, err := gui.git.Loaders.Remotes.GetRemotes() - if err != nil { - return gui.c.Error(err) - } - - gui.State.Remotes = remotes - - // we need to ensure our selected remote branches aren't now outdated - if prevSelectedRemote != nil && gui.State.RemoteBranches != nil { - // find remote now - for _, remote := range remotes { - if remote.Name == prevSelectedRemote.Name { - gui.State.RemoteBranches = remote.Branches - } - } - } - - return gui.c.PostRefreshUpdate(gui.mustContextForContextKey(types.ContextKey(gui.Views.Branches.Context))) -} diff --git a/pkg/gui/simple_context.go b/pkg/gui/simple_context.go new file mode 100644 index 000000000..e9a3ca933 --- /dev/null +++ b/pkg/gui/simple_context.go @@ -0,0 +1,74 @@ +package gui + +import ( + "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/types" +) + +type SimpleContext struct { + OnFocus func(opts ...types.OnFocusOpts) error + OnFocusLost func() error + OnRender func() error + // this is for pushing some content to the main view + OnRenderToMain func(opts ...types.OnFocusOpts) error + + *context.BaseContext +} + +type NewSimpleContextOpts struct { + OnFocus func(opts ...types.OnFocusOpts) error + OnFocusLost func() error + OnRender func() error + // this is for pushing some content to the main view + OnRenderToMain func(opts ...types.OnFocusOpts) error +} + +func NewSimpleContext(baseContext *context.BaseContext, opts NewSimpleContextOpts) *SimpleContext { + return &SimpleContext{ + OnFocus: opts.OnFocus, + OnFocusLost: opts.OnFocusLost, + OnRender: opts.OnRender, + OnRenderToMain: opts.OnRenderToMain, + BaseContext: baseContext, + } +} + +var _ types.Context = &SimpleContext{} + +func (self *SimpleContext) HandleRender() error { + if self.OnRender != nil { + return self.OnRender() + } + return nil +} + +func (self *SimpleContext) HandleFocus(opts ...types.OnFocusOpts) error { + if self.OnFocus != nil { + if err := self.OnFocus(opts...); err != nil { + return err + } + } + + if self.OnRenderToMain != nil { + if err := self.OnRenderToMain(opts...); err != nil { + return err + } + } + + return nil +} + +func (self *SimpleContext) HandleFocusLost() error { + if self.OnFocusLost != nil { + return self.OnFocusLost() + } + return nil +} + +func (self *SimpleContext) HandleRenderToMain() error { + if self.OnRenderToMain != nil { + return self.OnRenderToMain() + } + + return nil +} diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 50af13eef..d1c206587 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -34,13 +34,6 @@ func (gui *Gui) stashRenderToMain() error { }) } -func (gui *Gui) refreshStashEntries() error { - gui.State.StashEntries = gui.git.Loaders.Stash. - GetStashEntries(gui.State.Modes.Filtering.GetPath()) - - return gui.postRefreshUpdate(gui.State.Contexts.Stash) -} - // specific functions func (gui *Gui) handleStashApply() error { diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 0d65a0035..9ec5ec29a 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -13,34 +13,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -// never call this on its own, it should only be called from within refreshCommits() -func (gui *Gui) refreshStatus() { - gui.Mutexes.RefreshingStatusMutex.Lock() - defer gui.Mutexes.RefreshingStatusMutex.Unlock() - - currentBranch := gui.getCheckedOutBranch() - if currentBranch == nil { - // need to wait for branches to refresh - return - } - status := "" - - if currentBranch.IsRealBranch() { - status += presentation.ColoredBranchStatus(currentBranch) + " " - } - - workingTreeState := gui.git.Status.WorkingTreeState() - if workingTreeState != enums.REBASE_MODE_NONE { - status += style.FgYellow.Sprintf("(%s) ", formatWorkingTreeState(workingTreeState)) - } - - name := presentation.GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name) - repoName := utils.GetCurrentRepoName() - status += fmt.Sprintf("%s → %s ", repoName, name) - - gui.setViewContent(gui.Views.Status, status) -} - func runeCount(str string) int { return len([]rune(str)) } diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go index 7c29c7840..2817ef7a5 100644 --- a/pkg/gui/submodules_panel.go +++ b/pkg/gui/submodules_panel.go @@ -47,17 +47,6 @@ func (gui *Gui) submodulesRenderToMain() error { }) } -func (gui *Gui) refreshStateSubmoduleConfigs() error { - configs, err := gui.git.Submodule.GetConfigs() - if err != nil { - return err - } - - gui.State.Submodules = configs - - return nil -} - func (gui *Gui) enterSubmodule(submodule *models.SubmoduleConfig) error { wd, err := os.Getwd() if err != nil { diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go index 957743188..f452974d7 100644 --- a/pkg/gui/tags_panel.go +++ b/pkg/gui/tags_panel.go @@ -17,14 +17,3 @@ func (self *Gui) tagsRenderToMain() error { }, }) } - -func (self *Gui) refreshTags() error { - tags, err := self.git.Loaders.Tags.GetTags() - if err != nil { - return self.c.Error(err) - } - - self.State.Tags = tags - - return self.postRefreshUpdate(self.State.Contexts.Tags) -} diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 42ff9d5d8..202504c10 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -4,7 +4,6 @@ import ( "fmt" "sort" "strings" - "sync" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/gui/types" @@ -16,140 +15,6 @@ func (gui *Gui) getCyclableWindows() []string { return []string{"status", "files", "branches", "commits", "stash"} } -func getScopeNames(scopes []types.RefreshableView) []string { - scopeNameMap := map[types.RefreshableView]string{ - types.COMMITS: "commits", - types.BRANCHES: "branches", - types.FILES: "files", - types.SUBMODULES: "submodules", - types.STASH: "stash", - types.REFLOG: "reflog", - types.TAGS: "tags", - types.REMOTES: "remotes", - types.STATUS: "status", - types.BISECT_INFO: "bisect", - } - - scopeNames := make([]string, len(scopes)) - for i, scope := range scopes { - scopeNames[i] = scopeNameMap[scope] - } - - return scopeNames -} - -func getModeName(mode types.RefreshMode) string { - switch mode { - case types.SYNC: - return "sync" - case types.ASYNC: - return "async" - case types.BLOCK_UI: - return "block-ui" - default: - return "unknown mode" - } -} - -func arrToMap(arr []types.RefreshableView) map[types.RefreshableView]bool { - output := map[types.RefreshableView]bool{} - for _, el := range arr { - output[el] = true - } - return output -} - -func (gui *Gui) Refresh(options types.RefreshOptions) error { - if options.Scope == nil { - gui.c.Log.Infof( - "refreshing all scopes in %s mode", - getModeName(options.Mode), - ) - } else { - gui.c.Log.Infof( - "refreshing the following scopes in %s mode: %s", - getModeName(options.Mode), - strings.Join(getScopeNames(options.Scope), ","), - ) - } - - wg := sync.WaitGroup{} - - f := func() { - var scopeMap map[types.RefreshableView]bool - if len(options.Scope) == 0 { - scopeMap = arrToMap([]types.RefreshableView{ - types.COMMITS, - types.BRANCHES, - types.FILES, - types.STASH, - types.REFLOG, - types.TAGS, - types.REMOTES, - types.STATUS, - types.BISECT_INFO, - }) - } else { - scopeMap = arrToMap(options.Scope) - } - - refresh := func(f func()) { - wg.Add(1) - func() { - if options.Mode == types.ASYNC { - go utils.Safe(f) - } else { - f() - } - wg.Done() - }() - } - - if scopeMap[types.COMMITS] || scopeMap[types.BRANCHES] || scopeMap[types.REFLOG] || scopeMap[types.BISECT_INFO] { - refresh(gui.refreshCommits) - } else if scopeMap[types.REBASE_COMMITS] { - // the above block handles rebase commits so we only need to call this one - // if we've asked specifically for rebase commits and not those other things - refresh(func() { _ = gui.refreshRebaseCommits() }) - } - - if scopeMap[types.FILES] || scopeMap[types.SUBMODULES] { - refresh(func() { _ = gui.refreshFilesAndSubmodules() }) - } - - if scopeMap[types.STASH] { - refresh(func() { _ = gui.refreshStashEntries() }) - } - - if scopeMap[types.TAGS] { - refresh(func() { _ = gui.refreshTags() }) - } - - if scopeMap[types.REMOTES] { - refresh(func() { _ = gui.refreshRemotes() }) - } - - wg.Wait() - - gui.refreshStatus() - - if options.Then != nil { - options.Then() - } - } - - if options.Mode == types.BLOCK_UI { - gui.OnUIThread(func() error { - f() - return nil - }) - } else { - f() - } - - return nil -} - func (gui *Gui) resetOrigin(v *gocui.View) error { _ = v.SetCursor(0, 0) return v.SetOrigin(0, 0) -- cgit v1.2.3