summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2023-03-23 18:47:29 +1100
committerJesse Duffield <jessedduffield@gmail.com>2023-04-30 13:19:53 +1000
commitdb12853bbe825d69686ea71161497d1bbb120b8e (patch)
treeebfb066dfef8eb75acdc1ea2bd5f15ff4f4a6507 /pkg
parent711674f6cd68ed3a35e5b0329ff0cf3289fbc7d1 (diff)
lots of changes
Diffstat (limited to 'pkg')
-rw-r--r--pkg/gui/app_status_manager.go132
-rw-r--r--pkg/gui/arrangement.go97
-rw-r--r--pkg/gui/context.go40
-rw-r--r--pkg/gui/context/setup.go136
-rw-r--r--pkg/gui/context_config.go165
-rw-r--r--pkg/gui/controllers.go59
-rw-r--r--pkg/gui/controllers/basic_commits_controller.go26
-rw-r--r--pkg/gui/controllers/bisect_controller.go18
-rw-r--r--pkg/gui/controllers/branches_controller.go44
-rw-r--r--pkg/gui/controllers/command_log_controller.go8
-rw-r--r--pkg/gui/controllers/commit_message_controller.go10
-rw-r--r--pkg/gui/controllers/commits_files_controller.go16
-rw-r--r--pkg/gui/controllers/common.go20
-rw-r--r--pkg/gui/controllers/confirmation_controller.go10
-rw-r--r--pkg/gui/controllers/context_lines_controller.go8
-rw-r--r--pkg/gui/controllers/files_controller.go48
-rw-r--r--pkg/gui/controllers/files_remove_controller.go10
-rw-r--r--pkg/gui/controllers/git_flow_controller.go8
-rw-r--r--pkg/gui/controllers/global_controller.go8
-rw-r--r--pkg/gui/controllers/helpers/app_status_helper.go68
-rw-r--r--pkg/gui/controllers/helpers/helpers.go4
-rw-r--r--pkg/gui/controllers/helpers/mode_helper.go159
-rw-r--r--pkg/gui/controllers/list_controller.go14
-rw-r--r--pkg/gui/controllers/local_commits_controller.go37
-rw-r--r--pkg/gui/controllers/menu_controller.go8
-rw-r--r--pkg/gui/controllers/merge_conflicts_controller.go16
-rw-r--r--pkg/gui/controllers/patch_building_controller.go16
-rw-r--r--pkg/gui/controllers/patch_explorer_controller.go14
-rw-r--r--pkg/gui/controllers/reflog_commits_controller.go10
-rw-r--r--pkg/gui/controllers/remote_branches_controller.go20
-rw-r--r--pkg/gui/controllers/remotes_controller.go8
-rw-r--r--pkg/gui/controllers/snake_controller.go16
-rw-r--r--pkg/gui/controllers/staging_controller.go28
-rw-r--r--pkg/gui/controllers/stash_controller.go12
-rw-r--r--pkg/gui/controllers/status_controller.go24
-rw-r--r--pkg/gui/controllers/sub_commits_controller.go10
-rw-r--r--pkg/gui/controllers/submodules_controller.go14
-rw-r--r--pkg/gui/controllers/suggestions_controller.go10
-rw-r--r--pkg/gui/controllers/switch_to_diff_files_controller.go6
-rw-r--r--pkg/gui/controllers/switch_to_sub_commits_controller.go12
-rw-r--r--pkg/gui/controllers/sync_controller.go20
-rw-r--r--pkg/gui/controllers/tags_controller.go18
-rw-r--r--pkg/gui/controllers/undo_controller.go16
-rw-r--r--pkg/gui/controllers/vertical_scroll_controller.go10
-rw-r--r--pkg/gui/controllers/workspace_reset_controller.go2
-rw-r--r--pkg/gui/extras_panel.go8
-rw-r--r--pkg/gui/filtering.go15
-rw-r--r--pkg/gui/filtering_menu_panel.go2
-rw-r--r--pkg/gui/gui.go76
-rw-r--r--pkg/gui/gui_common.go22
-rw-r--r--pkg/gui/information_panel.go21
-rw-r--r--pkg/gui/layout.go3
-rw-r--r--pkg/gui/list_context_config.go76
-rw-r--r--pkg/gui/modes.go104
-rw-r--r--pkg/gui/popup/popup_handler.go7
-rw-r--r--pkg/gui/quitting.go6
-rw-r--r--pkg/gui/services/custom_commands/client.go12
-rw-r--r--pkg/gui/services/custom_commands/handler_creator.go14
-rw-r--r--pkg/gui/services/custom_commands/keybinding_creator.go9
-rw-r--r--pkg/gui/services/custom_commands/session_state_loader.go38
-rw-r--r--pkg/gui/status/status_manager.go94
-rw-r--r--pkg/gui/types/common.go14
-rw-r--r--pkg/gui/types/context.go11
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 (self *ContextMgr) IsCurrent(c types.Context) bool {
+ return self.Current().GetKey() == c.GetKey()
+}