diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2023-03-23 18:47:29 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2023-04-30 13:19:53 +1000 |
commit | db12853bbe825d69686ea71161497d1bbb120b8e (patch) | |
tree | ebfb066dfef8eb75acdc1ea2bd5f15ff4f4a6507 /pkg/gui | |
parent | 711674f6cd68ed3a35e5b0329ff0cf3289fbc7d1 (diff) |
lots of changes
Diffstat (limited to 'pkg/gui')
63 files changed, 1024 insertions, 943 deletions
diff --git a/pkg/gui/app_status_manager.go b/pkg/gui/app_status_manager.go deleted file mode 100644 index 02ba7779a..000000000 --- a/pkg/gui/app_status_manager.go +++ /dev/null @@ -1,132 +0,0 @@ -package gui - -import ( - "time" - - "github.com/jesseduffield/generics/slices" - "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/sasha-s/go-deadlock" -) - -// statusManager's job is to handle rendering of loading states and toast notifications -// that you see at the bottom left of the screen. -type statusManager struct { - statuses []appStatus - nextId int - mutex deadlock.Mutex -} - -type appStatus struct { - message string - statusType string - id int -} - -func (m *statusManager) removeStatus(id int) { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.statuses = slices.Filter(m.statuses, func(status appStatus) bool { - return status.id != id - }) -} - -func (m *statusManager) addWaitingStatus(message string) int { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.nextId += 1 - id := m.nextId - - newStatus := appStatus{ - message: message, - statusType: "waiting", - id: id, - } - m.statuses = append([]appStatus{newStatus}, m.statuses...) - - return id -} - -func (m *statusManager) addToastStatus(message string) int { - m.mutex.Lock() - defer m.mutex.Unlock() - - m.nextId++ - id := m.nextId - - newStatus := appStatus{ - message: message, - statusType: "toast", - id: id, - } - m.statuses = append([]appStatus{newStatus}, m.statuses...) - - go func() { - time.Sleep(time.Second * 2) - - m.removeStatus(id) - }() - - return id -} - -func (m *statusManager) getStatusString() string { - if len(m.statuses) == 0 { - return "" - } - topStatus := m.statuses[0] - if topStatus.statusType == "waiting" { - return topStatus.message + " " + utils.Loader() - } - return topStatus.message -} - -func (m *statusManager) showStatus() bool { - return len(m.statuses) > 0 -} - -func (gui *Gui) toast(message string) { - gui.statusManager.addToastStatus(message) - - gui.renderAppStatus() -} - -func (gui *Gui) renderAppStatus() { - go utils.Safe(func() { - ticker := time.NewTicker(time.Millisecond * 50) - defer ticker.Stop() - for range ticker.C { - appStatus := gui.statusManager.getStatusString() - gui.c.OnUIThread(func() error { - gui.c.SetViewContent(gui.Views.AppStatus, appStatus) - return nil - }) - - if appStatus == "" { - return - } - } - }) -} - -// withWaitingStatus wraps a function and shows a waiting status while the function is still executing -func (gui *Gui) withWaitingStatus(message string, f func() error) error { - go utils.Safe(func() { - id := gui.statusManager.addWaitingStatus(message) - - defer func() { - gui.statusManager.removeStatus(id) - }() - - gui.renderAppStatus() - - if err := f(); err != nil { - gui.c.OnUIThread(func() error { - return gui.c.Error(err) - }) - } - }) - - return nil -} diff --git a/pkg/gui/arrangement.go b/pkg/gui/arrangement.go index 5177d4683..2b1a5333d 100644 --- a/pkg/gui/arrangement.go +++ b/pkg/gui/arrangement.go @@ -3,6 +3,7 @@ package gui import ( "github.com/jesseduffield/lazycore/pkg/boxlayout" "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" "github.com/mattn/go-runewidth" @@ -14,11 +15,28 @@ import ( const INFO_SECTION_PADDING = " " type WindowArranger struct { - gui *Gui + c *helpers.HelperCommon + windowHelper *helpers.WindowHelper + modeHelper *helpers.ModeHelper + appStatusHelper *helpers.AppStatusHelper +} + +func NewWindowArranger( + c *helpers.HelperCommon, + windowHelper *helpers.WindowHelper, + modeHelper *helpers.ModeHelper, + appStatusHelper *helpers.AppStatusHelper, +) *WindowArranger { + return &WindowArranger{ + c: c, + windowHelper: windowHelper, + modeHelper: modeHelper, + appStatusHelper: appStatusHelper, + } } func (self *WindowArranger) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions { - width, height := self.gui.g.Size() + width, height := self.c.GocuiGui().Size() sideSectionWeight, mainSectionWeight := self.getMidSectionWeights() @@ -35,7 +53,12 @@ func (self *WindowArranger) getWindowDimensions(informationStr string, appStatus extrasWindowSize := self.getExtrasWindowSize(height) - showInfoSection := self.gui.c.UserConfig.Gui.ShowBottomLine || self.gui.State.Searching.isSearching || self.gui.isAnyModeActive() || self.gui.statusManager.showStatus() + self.c.Modes().Filtering.Active() + + showInfoSection := self.c.UserConfig.Gui.ShowBottomLine || + self.c.State().GetRepoState().IsSearching() || + self.modeHelper.IsAnyModeActive() || + self.appStatusHelper.HasStatus() infoSectionSize := 0 if showInfoSection { infoSectionSize = 1 @@ -96,11 +119,11 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V { } func (self *WindowArranger) mainSectionChildren() []*boxlayout.Box { - currentWindow := self.gui.helpers.Window.CurrentWindow() + currentWindow := self.windowHelper.CurrentWindow() // if we're not in split mode we can just show the one main panel. Likewise if // the main panel is focused and we're in full-screen mode - if !self.gui.isMainPanelSplit() || (self.gui.State.ScreenMode == types.SCREEN_FULL && currentWindow == "main") { + if !self.c.State().GetRepoState().GetSplitMainPanel() || (self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_FULL && currentWindow == "main") { return []*boxlayout.Box{ { Window: "main", @@ -122,10 +145,10 @@ func (self *WindowArranger) mainSectionChildren() []*boxlayout.Box { } func (self *WindowArranger) getMidSectionWeights() (int, int) { - currentWindow := self.gui.helpers.Window.CurrentWindow() + currentWindow := self.windowHelper.CurrentWindow() // we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4 - sidePanelWidthRatio := self.gui.c.UserConfig.Gui.SidePanelWidth + sidePanelWidthRatio := self.c.UserConfig.Gui.SidePanelWidth // we could make this better by creating ratios like 2:3 rather than always 1:something mainSectionWeight := int(1/sidePanelWidthRatio) - 1 sideSectionWeight := 1 @@ -134,14 +157,16 @@ func (self *WindowArranger) getMidSectionWeights() (int, int) { mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side } + screenMode := self.c.State().GetRepoState().GetScreenMode() + if currentWindow == "main" { - if self.gui.State.ScreenMode == types.SCREEN_HALF || self.gui.State.ScreenMode == types.SCREEN_FULL { + if screenMode == types.SCREEN_HALF || screenMode == types.SCREEN_FULL { sideSectionWeight = 0 } } else { - if self.gui.State.ScreenMode == types.SCREEN_HALF { + if screenMode == types.SCREEN_HALF { mainSectionWeight = 1 - } else if self.gui.State.ScreenMode == types.SCREEN_FULL { + } else if screenMode == types.SCREEN_FULL { mainSectionWeight = 0 } } @@ -150,7 +175,7 @@ func (self *WindowArranger) getMidSectionWeights() (int, int) { } func (self *WindowArranger) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box { - if self.gui.State.Searching.isSearching { + if self.c.State().GetRepoState().IsSearching() { return []*boxlayout.Box{ { Window: "searchPrefix", @@ -166,7 +191,7 @@ func (self *WindowArranger) infoSectionChildren(informationStr string, appStatus appStatusBox := &boxlayout.Box{Window: "appStatus"} optionsBox := &boxlayout.Box{Window: "options"} - if !self.gui.c.UserConfig.Gui.ShowBottomLine { + if !self.c.UserConfig.Gui.ShowBottomLine { optionsBox.Weight = 0 appStatusBox.Weight = 1 } else { @@ -176,7 +201,7 @@ func (self *WindowArranger) infoSectionChildren(informationStr string, appStatus result := []*boxlayout.Box{appStatusBox, optionsBox} - if self.gui.c.UserConfig.Gui.ShowBottomLine || self.gui.isAnyModeActive() { + if self.c.UserConfig.Gui.ShowBottomLine || self.modeHelper.IsAnyModeActive() { result = append(result, &boxlayout.Box{ Window: "information", // unlike appStatus, informationStr has various colors so we need to decolorise before taking the length @@ -188,12 +213,12 @@ func (self *WindowArranger) infoSectionChildren(informationStr string, appStatus } func (self *WindowArranger) splitMainPanelSideBySide() bool { - if !self.gui.isMainPanelSplit() { + if !self.c.State().GetRepoState().GetSplitMainPanel() { return false } - mainPanelSplitMode := self.gui.c.UserConfig.Gui.MainPanelSplitMode - width, height := self.gui.g.Size() + mainPanelSplitMode := self.c.UserConfig.Gui.MainPanelSplitMode + width, height := self.c.GocuiGui().Size() switch mainPanelSplitMode { case "vertical": @@ -210,17 +235,17 @@ func (self *WindowArranger) splitMainPanelSideBySide() bool { } func (self *WindowArranger) getExtrasWindowSize(screenHeight int) int { - if !self.gui.ShowExtrasWindow { + if !self.c.State().GetShowExtrasWindow() { return 0 } var baseSize int - if self.gui.c.CurrentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY { + if self.c.CurrentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY { baseSize = 1000 // my way of saying 'fill the available space' } else if screenHeight < 40 { baseSize = 1 } else { - baseSize = self.gui.c.UserConfig.Gui.CommandLogSize + baseSize = self.c.UserConfig.Gui.CommandLogSize } frameSize := 2 @@ -232,16 +257,14 @@ func (self *WindowArranger) getExtrasWindowSize(screenHeight int) int { // the default behaviour when accordion mode is NOT in effect. If it is in effect // then when it's accessed it will have weight 2, not 1. func (self *WindowArranger) getDefaultStashWindowBox() *boxlayout.Box { - self.gui.State.ContextMgr.RLock() - defer self.gui.State.ContextMgr.RUnlock() - - box := &boxlayout.Box{Window: "stash"} stashWindowAccessed := false - for _, context := range self.gui.State.ContextMgr.ContextStack { + self.c.Context().ForEach(func(context types.Context) { if context.GetWindowName() == "stash" { stashWindowAccessed = true } - } + }) + + box := &boxlayout.Box{Window: "stash"} // if the stash window is anywhere in our stack we should enlargen it if stashWindowAccessed { box.Weight = 1 @@ -253,9 +276,10 @@ func (self *WindowArranger) getDefaultStashWindowBox() *boxlayout.Box { } func (self *WindowArranger) sidePanelChildren(width int, height int) []*boxlayout.Box { - currentWindow := self.currentSideWindowName() + currentWindow := self.c.CurrentSideContext().GetWindowName() - if self.gui.State.ScreenMode == types.SCREEN_FULL || self.gui.State.ScreenMode == types.SCREEN_HALF { + screenMode := self.c.State().GetRepoState().GetScreenMode() + if screenMode == types.SCREEN_FULL || screenMode == types.SCREEN_HALF { fullHeightBox := func(window string) *boxlayout.Box { if window == currentWindow { return &boxlayout.Box{ @@ -278,7 +302,7 @@ func (self *WindowArranger) sidePanelChildren(width int, height int) []*boxlayou fullHeightBox("stash"), } } else if height >= 28 { - accordionMode := self.gui.c.UserConfig.Gui.ExpandFocusedSidePanel + accordionMode := self.c.UserConfig.Gui.ExpandFocusedSidePanel accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box { if accordionMode && defaultBox.Window == currentWindow { return &boxlayout.Box{ @@ -329,20 +353,3 @@ func (self *WindowArranger) sidePanelChildren(width int, height int) []*boxlayou } } } - -func (self *WindowArranger) currentSideWindowName() string { - // there is always one and only one cyclable context in the context stack. We'll look from top to bottom - self.gui.State.ContextMgr.RLock() - defer self.gui.State.ContextMgr.RUnlock() - - for idx := range self.gui.State.ContextMgr.ContextStack { - reversedIdx := len(self.gui.State.ContextMgr.ContextStack) - 1 - idx - context := self.gui.State.ContextMgr.ContextStack[reversedIdx] - - if context.GetKind() == types.SIDE_CONTEXT { - return context.GetWindowName() - } - } - - return "files" // default -} diff --git a/pkg/gui/context.go b/pkg/gui/context.go index 3adafd710..3550e9eef 100644 --- a/pkg/gui/context.go +++ b/pkg/gui/context.go @@ -20,17 +20,17 @@ type ContextMgr struct { gui *Gui } -func NewContextMgr(initialContext types.Context, gui *Gui) ContextMgr { - return ContextMgr{ - ContextStack: []types.Context{}, +func NewContextMgr(initialContext types.Context, gui *Gui) *ContextMgr { + return &ContextMgr{ + ContextStack: []types.Context{initialContext}, RWMutex: sync.RWMutex{}, gui: gui, } } -// use replaceContext when you don't want to return to the original context upon +// use when you don't want to return to the original context upon // hitting escape: you want to go that context's parent instead. -func (self *ContextMgr) replaceContext(c types.Context) error { +func (self *ContextMgr) Replace(c types.Context) error { if !c.IsFocusable() { return nil } @@ -49,9 +49,9 @@ func (self *ContextMgr) replaceContext(c types.Context) error { return self.activateContext(c, types.OnFocusOpts{}) } -func (self *ContextMgr) pushContext(c types.Context, opts ...types.OnFocusOpts) error { +func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error { if len(opts) > 1 { - return errors.New("cannot pass multiple opts to pushContext") + return errors.New("cannot pass multiple opts to Push") } singleOpts := types.OnFocusOpts{} @@ -135,7 +135,7 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty return contextsToDeactivate, c } -func (self *ContextMgr) popContext() error { +func (self *ContextMgr) Pop() error { self.Lock() if len(self.ContextStack) == 1 { @@ -213,7 +213,7 @@ func (self *ContextMgr) activateContext(c types.Context, opts types.OnFocusOpts) return nil } -func (self *ContextMgr) currentContext() types.Context { +func (self *ContextMgr) Current() types.Context { self.RLock() defer self.RUnlock() @@ -229,17 +229,12 @@ func (self *ContextMgr) currentContextWithoutLock() types.Context { } // Note that this could return the 'status' context which is not itself a list context. -func (self *ContextMgr) currentSideContext() types.Context { +func (self *ContextMgr) CurrentSide() types.Context { self.RLock() defer self.RUnlock() stack := self.ContextStack - // on startup the stack can be empty so we'll return an empty string in that case - if len(stack) == 0 { - return self.gui.defaultSideContext() - } - // find the first context in the stack with the type of types.SIDE_CONTEXT for i := range stack { context := stack[len(stack)-1-i] @@ -253,7 +248,7 @@ func (self *ContextMgr) currentSideContext() types.Context { } // static as opposed to popup -func (self *ContextMgr) currentStaticContext() types.Context { +func (self *ContextMgr) CurrentStatic() types.Context { self.RLock() defer self.RUnlock() @@ -278,3 +273,16 @@ func (self *ContextMgr) currentStaticContextWithoutLock() types.Context { return self.gui.defaultSideContext() } + +func (self *ContextMgr) ForEach(f func(types.Context)) { + self.RLock() + defer self.RUnlock() + + for _, context := range self.gui.State.ContextMgr.ContextStack { + f(context) + } +} + +func (s |