mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-01-20 05:19:24 +02:00
Begin refactoring gui
This begins a big refactor of moving more code out of the Gui struct into contexts, controllers, and helpers. We also move some code into structs in the gui package purely for the sake of better encapsulation
This commit is contained in:
parent
826128a8e0
commit
8edad826ca
@ -33,7 +33,6 @@ type App struct {
|
||||
Config config.AppConfigurer
|
||||
OSCommand *oscommands.OSCommand
|
||||
Gui *gui.Gui
|
||||
Updater *updates.Updater // may only need this on the Gui
|
||||
}
|
||||
|
||||
func Run(
|
||||
@ -87,8 +86,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
|
||||
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
|
||||
|
||||
var err error
|
||||
app.Updater, err = updates.NewUpdater(common, config, app.OSCommand)
|
||||
updater, err := updates.NewUpdater(common, config, app.OSCommand)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
@ -108,7 +106,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
return app, err
|
||||
}
|
||||
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
|
@ -99,7 +99,8 @@ func (gui *Gui) renderAppStatus() {
|
||||
for range ticker.C {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.renderString(gui.Views.AppStatus, appStatus)
|
||||
gui.c.SetViewContent(gui.Views.AppStatus, appStatus)
|
||||
return nil
|
||||
})
|
||||
|
||||
if appStatus == "" {
|
||||
|
@ -13,10 +13,14 @@ import (
|
||||
|
||||
const INFO_SECTION_PADDING = " "
|
||||
|
||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := gui.g.Size()
|
||||
type WindowArranger struct {
|
||||
gui *Gui
|
||||
}
|
||||
|
||||
sideSectionWeight, mainSectionWeight := gui.getMidSectionWeights()
|
||||
func (self *WindowArranger) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := self.gui.g.Size()
|
||||
|
||||
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
|
||||
|
||||
sidePanelsDirection := boxlayout.COLUMN
|
||||
portraitMode := width <= 84 && height > 45
|
||||
@ -25,13 +29,13 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
}
|
||||
|
||||
mainPanelsDirection := boxlayout.ROW
|
||||
if gui.splitMainPanelSideBySide() {
|
||||
if self.splitMainPanelSideBySide() {
|
||||
mainPanelsDirection = boxlayout.COLUMN
|
||||
}
|
||||
|
||||
extrasWindowSize := gui.getExtrasWindowSize(height)
|
||||
extrasWindowSize := self.getExtrasWindowSize(height)
|
||||
|
||||
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching || gui.isAnyModeActive() || gui.statusManager.showStatus()
|
||||
showInfoSection := self.gui.c.UserConfig.Gui.ShowBottomLine || self.gui.State.Searching.isSearching || self.gui.isAnyModeActive() || self.gui.statusManager.showStatus()
|
||||
infoSectionSize := 0
|
||||
if showInfoSection {
|
||||
infoSectionSize = 1
|
||||
@ -47,7 +51,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
Weight: sideSectionWeight,
|
||||
ConditionalChildren: gui.sidePanelChildren,
|
||||
ConditionalChildren: self.sidePanelChildren,
|
||||
},
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
@ -55,7 +59,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
Children: []*boxlayout.Box{
|
||||
{
|
||||
Direction: mainPanelsDirection,
|
||||
Children: gui.mainSectionChildren(),
|
||||
Children: self.mainSectionChildren(),
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
@ -69,7 +73,7 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
||||
{
|
||||
Direction: boxlayout.COLUMN,
|
||||
Size: infoSectionSize,
|
||||
Children: gui.infoSectionChildren(informationStr, appStatus),
|
||||
Children: self.infoSectionChildren(informationStr, appStatus),
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -91,12 +95,12 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
|
||||
currentWindow := gui.currentWindow()
|
||||
func (self *WindowArranger) mainSectionChildren() []*boxlayout.Box {
|
||||
currentWindow := self.gui.helpers.Window.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 !gui.isMainPanelSplit() || (gui.State.ScreenMode == SCREEN_FULL && currentWindow == "main") {
|
||||
if !self.gui.isMainPanelSplit() || (self.gui.State.ScreenMode == SCREEN_FULL && currentWindow == "main") {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "main",
|
||||
@ -117,27 +121,27 @@ func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getMidSectionWeights() (int, int) {
|
||||
currentWindow := gui.currentWindow()
|
||||
func (self *WindowArranger) getMidSectionWeights() (int, int) {
|
||||
currentWindow := self.gui.helpers.Window.CurrentWindow()
|
||||
|
||||
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
|
||||
sidePanelWidthRatio := gui.c.UserConfig.Gui.SidePanelWidth
|
||||
sidePanelWidthRatio := self.gui.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
|
||||
|
||||
if gui.splitMainPanelSideBySide() {
|
||||
if self.splitMainPanelSideBySide() {
|
||||
mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side
|
||||
}
|
||||
|
||||
if currentWindow == "main" {
|
||||
if gui.State.ScreenMode == SCREEN_HALF || gui.State.ScreenMode == SCREEN_FULL {
|
||||
if self.gui.State.ScreenMode == SCREEN_HALF || self.gui.State.ScreenMode == SCREEN_FULL {
|
||||
sideSectionWeight = 0
|
||||
}
|
||||
} else {
|
||||
if gui.State.ScreenMode == SCREEN_HALF {
|
||||
if self.gui.State.ScreenMode == SCREEN_HALF {
|
||||
mainSectionWeight = 1
|
||||
} else if gui.State.ScreenMode == SCREEN_FULL {
|
||||
} else if self.gui.State.ScreenMode == SCREEN_FULL {
|
||||
mainSectionWeight = 0
|
||||
}
|
||||
}
|
||||
@ -145,8 +149,8 @@ func (gui *Gui) getMidSectionWeights() (int, int) {
|
||||
return sideSectionWeight, mainSectionWeight
|
||||
}
|
||||
|
||||
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if gui.State.Searching.isSearching {
|
||||
func (self *WindowArranger) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if self.gui.State.Searching.isSearching {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "searchPrefix",
|
||||
@ -162,7 +166,7 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
appStatusBox := &boxlayout.Box{Window: "appStatus"}
|
||||
optionsBox := &boxlayout.Box{Window: "options"}
|
||||
|
||||
if !gui.c.UserConfig.Gui.ShowBottomLine {
|
||||
if !self.gui.c.UserConfig.Gui.ShowBottomLine {
|
||||
optionsBox.Weight = 0
|
||||
appStatusBox.Weight = 1
|
||||
} else {
|
||||
@ -172,7 +176,7 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
|
||||
result := []*boxlayout.Box{appStatusBox, optionsBox}
|
||||
|
||||
if gui.c.UserConfig.Gui.ShowBottomLine || gui.isAnyModeActive() {
|
||||
if self.gui.c.UserConfig.Gui.ShowBottomLine || self.gui.isAnyModeActive() {
|
||||
result = append(result, &boxlayout.Box{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
@ -183,13 +187,13 @@ func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) splitMainPanelSideBySide() bool {
|
||||
if !gui.isMainPanelSplit() {
|
||||
func (self *WindowArranger) splitMainPanelSideBySide() bool {
|
||||
if !self.gui.isMainPanelSplit() {
|
||||
return false
|
||||
}
|
||||
|
||||
mainPanelSplitMode := gui.c.UserConfig.Gui.MainPanelSplitMode
|
||||
width, height := gui.g.Size()
|
||||
mainPanelSplitMode := self.gui.c.UserConfig.Gui.MainPanelSplitMode
|
||||
width, height := self.gui.g.Size()
|
||||
|
||||
switch mainPanelSplitMode {
|
||||
case "vertical":
|
||||
@ -205,18 +209,18 @@ func (gui *Gui) splitMainPanelSideBySide() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
|
||||
if !gui.ShowExtrasWindow {
|
||||
func (self *WindowArranger) getExtrasWindowSize(screenHeight int) int {
|
||||
if !self.gui.ShowExtrasWindow {
|
||||
return 0
|
||||
}
|
||||
|
||||
var baseSize int
|
||||
if gui.currentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
|
||||
if self.gui.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 = gui.c.UserConfig.Gui.CommandLogSize
|
||||
baseSize = self.gui.c.UserConfig.Gui.CommandLogSize
|
||||
}
|
||||
|
||||
frameSize := 2
|
||||
@ -227,13 +231,13 @@ func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
|
||||
// too much space, but if you access it it should take up some space. This is
|
||||
// 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 (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
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 gui.State.ContextManager.ContextStack {
|
||||
for _, context := range self.gui.State.ContextMgr.ContextStack {
|
||||
if context.GetWindowName() == "stash" {
|
||||
stashWindowAccessed = true
|
||||
}
|
||||
@ -248,10 +252,10 @@ func (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
return box
|
||||
}
|
||||
|
||||
func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := gui.currentSideWindowName()
|
||||
func (self *WindowArranger) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := self.currentSideWindowName()
|
||||
|
||||
if gui.State.ScreenMode == SCREEN_FULL || gui.State.ScreenMode == SCREEN_HALF {
|
||||
if self.gui.State.ScreenMode == SCREEN_FULL || self.gui.State.ScreenMode == SCREEN_HALF {
|
||||
fullHeightBox := func(window string) *boxlayout.Box {
|
||||
if window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
@ -274,7 +278,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
fullHeightBox("stash"),
|
||||
}
|
||||
} else if height >= 28 {
|
||||
accordionMode := gui.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionMode := self.gui.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
||||
if accordionMode && defaultBox.Window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
@ -294,7 +298,7 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
accordionBox(&boxlayout.Box{Window: "files", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
|
||||
accordionBox(gui.getDefaultStashWindowBox()),
|
||||
accordionBox(self.getDefaultStashWindowBox()),
|
||||
}
|
||||
} else {
|
||||
squashedHeight := 1
|
||||
@ -326,18 +330,14 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getCyclableWindows() []string {
|
||||
return []string{"status", "files", "branches", "commits", "stash"}
|
||||
}
|
||||
|
||||
func (gui *Gui) currentSideWindowName() string {
|
||||
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
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
self.gui.State.ContextMgr.RLock()
|
||||
defer self.gui.State.ContextMgr.RUnlock()
|
||||
|
||||
for idx := range gui.State.ContextManager.ContextStack {
|
||||
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
|
||||
context := gui.State.ContextManager.ContextStack[reversedIdx]
|
||||
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()
|
||||
|
@ -4,18 +4,33 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) startBackgroundRoutines() {
|
||||
userConfig := gui.UserConfig
|
||||
type BackgroundRoutineMgr struct {
|
||||
gui *Gui
|
||||
|
||||
// if we've suspended the gui (e.g. because we've switched to a subprocess)
|
||||
// we typically want to pause some things that are running like background
|
||||
// file refreshes
|
||||
pauseBackgroundThreads bool
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) PauseBackgroundThreads(pause bool) {
|
||||
self.pauseBackgroundThreads = pause
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
userConfig := self.gui.UserConfig
|
||||
|
||||
if userConfig.Git.AutoFetch {
|
||||
fetchInterval := userConfig.Refresher.FetchInterval
|
||||
if fetchInterval > 0 {
|
||||
go utils.Safe(gui.startBackgroundFetch)
|
||||
go utils.Safe(self.startBackgroundFetch)
|
||||
} else {
|
||||
gui.c.Log.Errorf(
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch",
|
||||
fetchInterval)
|
||||
}
|
||||
@ -24,42 +39,44 @@ func (gui *Gui) startBackgroundRoutines() {
|
||||
if userConfig.Git.AutoRefresh {
|
||||
refreshInterval := userConfig.Refresher.RefreshInterval
|
||||
if refreshInterval > 0 {
|
||||
gui.goEvery(time.Second*time.Duration(refreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
} else {
|
||||
gui.c.Log.Errorf(
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
|
||||
refreshInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) startBackgroundFetch() {
|
||||
gui.waitForIntro.Wait()
|
||||
isNew := gui.IsNewRepo
|
||||
userConfig := gui.UserConfig
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := gui.backgroundFetch()
|
||||
err := self.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
_ = gui.c.Alert(gui.c.Tr.NoAutomaticGitFetchTitle, gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
|
||||
err := gui.backgroundFetch()
|
||||
gui.render()
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if gui.PauseBackgroundThreads {
|
||||
if self.pauseBackgroundThreads {
|
||||
continue
|
||||
}
|
||||
_ = function()
|
||||
@ -69,3 +86,11 @@ func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function fun
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
||||
|
||||
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
|
||||
return err
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) branchesRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
branch := gui.State.Contexts.Branches.GetSelected()
|
||||
if branch == nil {
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) commitFilesRenderToMain() error {
|
||||
node := gui.State.Contexts.CommitFiles.GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := gui.State.Contexts.CommitFiles.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false,
|
||||
gui.IgnoreWhitespaceInDiffView)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := gui.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = gui.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.Tr.Patch,
|
||||
Task: task,
|
||||
},
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesContextOpts) error {
|
||||
gui.State.Contexts.CommitFiles.SetSelectedLineIdx(0)
|
||||
gui.State.Contexts.CommitFiles.SetRef(opts.Ref)
|
||||
gui.State.Contexts.CommitFiles.SetTitleRef(opts.Ref.Description())
|
||||
gui.State.Contexts.CommitFiles.SetCanRebase(opts.CanRebase)
|
||||
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
|
||||
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
|
||||
|
||||
if err := gui.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.COMMIT_FILES},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||
}
|
@ -21,7 +21,8 @@ func (gui *Gui) handleCommitMessageFocused() error {
|
||||
|
||||
gui.RenderCommitLength()
|
||||
|
||||
return gui.renderString(gui.Views.Options, message)
|
||||
gui.c.SetViewContent(gui.Views.Options, message)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) RenderCommitLength() {
|
||||
|
@ -1,88 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// after selecting the 200th commit, we'll load in all the rest
|
||||
const COMMIT_THRESHOLD = 200
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedLocalCommit() *models.Commit {
|
||||
return gui.State.Contexts.LocalCommits.GetSelected()
|
||||
}
|
||||
|
||||
func (gui *Gui) onCommitFocus() error {
|
||||
context := gui.State.Contexts.LocalCommits
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
if err := gui.refreshCommitsWithLimit(); err != nil {
|
||||
_ = gui.c.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) branchCommitsRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
commit := gui.State.Contexts.LocalCommits.GetSelected()
|
||||
if commit == nil {
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
|
||||
} else if commit.Action == todo.UpdateRef {
|
||||
task = types.NewRenderStringTask(
|
||||
utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.UpdateRefHere,
|
||||
map[string]string{
|
||||
"ref": commit.Name,
|
||||
}))
|
||||
} else {
|
||||
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath(),
|
||||
gui.IgnoreWhitespaceInDiffView)
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Patch",
|
||||
Task: task,
|
||||
},
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) secondaryPatchPanelUpdateOpts() *types.ViewUpdateOpts {
|
||||
if gui.git.Patch.PatchBuilder.Active() {
|
||||
patch := gui.git.Patch.PatchBuilder.RenderAggregatedPatch(false)
|
||||
|
||||
return &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(patch),
|
||||
Title: gui.Tr.CustomPatch,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refForLog() string {
|
||||
bisectInfo := gui.git.Bisect.GetInfo()
|
||||
gui.State.Model.BisectInfo = bisectInfo
|
||||
|
||||
if !bisectInfo.Started() {
|
||||
return "HEAD"
|
||||
}
|
||||
|
||||
// need to see if our bisect's current commit is reachable from our 'new' ref.
|
||||
if bisectInfo.Bisecting() && !gui.git.Bisect.ReachableFromStart(bisectInfo) {
|
||||
return bisectInfo.GetNewSha()
|
||||
}
|
||||
|
||||
return bisectInfo.GetStartSha()
|
||||
}
|
@ -195,10 +195,8 @@ func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPane
|
||||
gui.resizeConfirmationPanel()
|
||||
confirmationView.RenderTextArea()
|
||||
} else {
|
||||
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
gui.c.ResetViewOrigin(confirmationView)
|
||||
gui.c.SetViewContent(confirmationView, style.AttrBold.Sprint(opts.Prompt))
|
||||
}
|
||||
|
||||
if err := gui.setKeyBindings(cancel, opts); err != nil {
|
||||
@ -220,7 +218,7 @@ func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopup
|
||||
},
|
||||
)
|
||||
|
||||
_ = gui.renderString(gui.Views.Options, actions)
|
||||
gui.c.SetViewContent(gui.Views.Options, actions)
|
||||
var onConfirm func() error
|
||||
if opts.HandleConfirmPrompt != nil {
|
||||
onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
|
||||
@ -251,7 +249,7 @@ func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopup
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(gui.State.Suggestions) > 0 {
|
||||
return gui.replaceContext(gui.State.Contexts.Suggestions)
|
||||
return gui.c.ReplaceContext(gui.State.Contexts.Suggestions)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@ -269,7 +267,7 @@ func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopup
|
||||
{
|
||||
ViewName: "suggestions",
|
||||
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
|
||||
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
|
||||
Handler: func() error { return gui.c.ReplaceContext(gui.State.Contexts.Confirmation) },
|
||||
},
|
||||
}
|
||||
|
||||
@ -313,5 +311,6 @@ func (gui *Gui) handleAskFocused() error {
|
||||
},
|
||||
)
|
||||
|
||||
return gui.renderString(gui.Views.Options, message)
|
||||
gui.c.SetViewContent(gui.Views.Options, message)
|
||||
return nil
|
||||
}
|
||||
|
@ -1,12 +1,10 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@ -16,46 +14,60 @@ import (
|
||||
// you in the menu context. When contexts are activated/deactivated certain things need
|
||||
// to happen like showing/hiding views and rendering content.
|
||||
|
||||
func (gui *Gui) popupViewNames() []string {
|
||||
popups := slices.Filter(gui.State.Contexts.Flatten(), func(c types.Context) bool {
|
||||
return c.GetKind() == types.PERSISTENT_POPUP || c.GetKind() == types.TEMPORARY_POPUP
|
||||
})
|
||||
type ContextMgr struct {
|
||||
ContextStack []types.Context
|
||||
sync.RWMutex
|
||||
gui *Gui
|
||||
}
|
||||
|
||||
return slices.Map(popups, func(c types.Context) string {
|
||||
return c.GetViewName()
|
||||
})
|
||||
func NewContextMgr(initialContext types.Context, gui *Gui) ContextMgr {
|
||||
return ContextMgr{
|
||||
ContextStack: []types.Context{},
|
||||
RWMutex: sync.RWMutex{},
|
||||
gui: gui,
|
||||
}
|
||||
}
|
||||
|
||||
// use replaceContext when you don't want to return to the original context upon
|
||||
// hitting escape: you want to go that context's parent instead.
|
||||
func (gui *Gui) replaceContext(c types.Context) error {
|
||||
func (self *ContextMgr) replaceContext(c types.Context) error {
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
self.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = []types.Context{c}
|
||||
} else {
|
||||
// replace the last item with the given item
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
|
||||
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
|
||||
}
|
||||
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
defer self.Unlock()
|
||||
|
||||
return gui.activateContext(c, types.OnFocusOpts{})
|
||||
return self.activateContext(c, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
if len(opts) > 1 {
|
||||
return errors.New("cannot pass multiple opts to pushContext")
|
||||
}
|
||||
|
||||
singleOpts := types.OnFocusOpts{}
|
||||
if len(opts) > 0 {
|
||||
// using triple dot but you should only ever pass one of these opt structs
|
||||
singleOpts = opts[0]
|
||||
}
|
||||
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
contextsToDeactivate, contextToActivate := gui.pushToContextStack(c)
|
||||
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
|
||||
|
||||
for _, contextToDeactivate := range contextsToDeactivate {
|
||||
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -64,43 +76,43 @@ func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.activateContext(contextToActivate, opts)
|
||||
return self.activateContext(contextToActivate, singleOpts)
|
||||
}
|
||||
|
||||
// Adjusts the context stack based on the context that's being pushed and
|
||||
// returns (contexts to deactivate, context to activate)
|
||||
func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
contextsToDeactivate := []types.Context{}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) > 0 &&
|
||||
c == gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1] {
|
||||
if len(self.ContextStack) > 0 &&
|
||||
c == self.ContextStack[len(self.ContextStack)-1] {
|
||||
// Context being pushed is already on top of the stack: nothing to
|
||||
// deactivate or activate
|
||||
return contextsToDeactivate, nil
|
||||
}
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
} else if c.GetKind() == types.SIDE_CONTEXT {
|
||||
// if we are switching to a side context, remove all other contexts in the stack
|
||||
contextsToDeactivate = gui.State.ContextManager.ContextStack
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
contextsToDeactivate = self.ContextStack
|
||||
self.ContextStack = []types.Context{c}
|
||||
} else if c.GetKind() == types.MAIN_CONTEXT {
|
||||
// if we're switching to a main context, remove all other main contexts in the stack
|
||||
contextsToKeep := []types.Context{}
|
||||
for _, stackContext := range gui.State.ContextManager.ContextStack {
|
||||
for _, stackContext := range self.ContextStack {
|
||||
if stackContext.GetKind() == types.MAIN_CONTEXT {
|
||||
contextsToDeactivate = append(contextsToDeactivate, stackContext)
|
||||
} else {
|
||||
contextsToKeep = append(contextsToKeep, stackContext)
|
||||
}
|
||||
}
|
||||
gui.State.ContextManager.ContextStack = append(contextsToKeep, c)
|
||||
self.ContextStack = append(contextsToKeep, c)
|
||||
} else {
|
||||
topContext := gui.currentContextWithoutLock()
|
||||
topContext := self.currentContextWithoutLock()
|
||||
|
||||
// if we're pushing the same context on, we do nothing.
|
||||
if topContext.GetKey() != c.GetKey() {
|
||||
@ -113,44 +125,44 @@ func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Cont
|
||||
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
|
||||
|
||||
contextsToDeactivate = append(contextsToDeactivate, topContext)
|
||||
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
_, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
}
|
||||
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
}
|
||||
}
|
||||
|
||||
return contextsToDeactivate, c
|
||||
}
|
||||
|
||||
func (gui *Gui) popContext() error {
|
||||
gui.State.ContextManager.Lock()
|
||||
func (self *ContextMgr) popContext() error {
|
||||
self.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||
if len(self.ContextStack) == 1 {
|
||||
// cannot escape from bottommost context
|
||||
gui.State.ContextManager.Unlock()
|
||||
self.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentContext types.Context
|
||||
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
|
||||
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
newContext := self.ContextStack[len(self.ContextStack)-1]
|
||||
|
||||
gui.State.ContextManager.Unlock()
|
||||
self.Unlock()
|
||||
|
||||
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.activateContext(newContext, types.OnFocusOpts{})
|
||||
return self.activateContext(newContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := gui.g.View(c.GetViewName())
|
||||
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
|
||||
|
||||
if view != nil && view.IsSearching() {
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
if err := self.gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -169,34 +181,17 @@ func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
|
||||
// if the context's view is set to another context we do nothing.
|
||||
// if the context's view is the current view we trigger a focus; re-selecting the current item.
|
||||
func (gui *Gui) postRefreshUpdate(c types.Context) error {
|
||||
if err := c.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.currentViewName() == c.GetViewName() {
|
||||
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
viewName := c.GetViewName()
|
||||
v, err := gui.g.View(viewName)
|
||||
v, err := self.gui.c.GocuiGui().View(viewName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.setWindowContext(c)
|
||||
self.gui.helpers.Window.SetWindowContext(c)
|
||||
|
||||
gui.moveToTopOfWindow(c)
|
||||
if _, err := gui.g.SetCurrentView(viewName); err != nil {
|
||||
self.gui.helpers.Window.MoveToTopOfWindow(c)
|
||||
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -207,14 +202,9 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
|
||||
v.Visible = true
|
||||
|
||||
gui.g.Cursor = v.Editable
|
||||
self.gui.c.GocuiGui().Cursor = v.Editable
|
||||
|
||||
// render the options available for the current context at the bottom of the screen
|
||||
optionsMap := c.GetOptionsMap()
|
||||
if optionsMap == nil {
|
||||
optionsMap = gui.globalOptionsMap()
|
||||
}
|
||||
gui.renderOptionsMap(optionsMap)
|
||||
self.gui.renderContextOptionsMap(c)
|
||||
|
||||
if err := c.HandleFocus(opts); err != nil {
|
||||
return err
|
||||
@ -223,62 +213,31 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||
options := maps.MapToSlice(optionsMap, func(key string, description string) string {
|
||||
return key + ": " + description
|
||||
})
|
||||
sort.Strings(options)
|
||||
return strings.Join(options, ", ")
|
||||
func (self *ContextMgr) currentContext() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return self.currentContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
|
||||
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
|
||||
}
|
||||
|
||||
// // currently unused
|
||||
// func (gui *Gui) renderContextStack() string {
|
||||
// result := ""
|
||||
// for _, context := range gui.State.ContextManager.ContextStack {
|
||||
// result += string(context.GetKey()) + "\n"
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
|
||||
func (gui *Gui) currentContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
return gui.currentContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentContextWithoutLock() types.Context {
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
func (self *ContextMgr) currentContextWithoutLock() types.Context {
|
||||
if len(self.ContextStack) == 0 {
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
return self.ContextStack[len(self.ContextStack)-1]
|
||||
}
|
||||
|
||||
// the status panel is not yet a list context (and may never be), so this method is not
|
||||
// quite the same as currentSideContext()
|
||||
func (gui *Gui) currentSideListContext() types.IListContext {
|
||||
context := gui.currentSideContext()
|
||||
listContext, ok := context.(types.IListContext)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return listContext
|
||||
}
|
||||
// Note that this could return the 'status' context which is not itself a list context.
|
||||
func (self *ContextMgr) currentSideContext() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
func (gui *Gui) currentSideContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
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 gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// find the first context in the stack with the type of types.SIDE_CONTEXT
|
||||
@ -290,22 +249,22 @@ func (gui *Gui) currentSideContext() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// static as opposed to popup
|
||||
func (gui *Gui) currentStaticContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
func (self *ContextMgr) currentStaticContext() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return gui.currentStaticContextWithoutLock()
|
||||
return self.currentStaticContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
|
||||
stack := self.ContextStack
|
||||
|
||||
if len(stack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// find the first context in the stack without a popup type
|
||||
@ -317,88 +276,5 @@ func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
}
|
||||
}
|
||||
|
||||
// getFocusLayout returns a manager function for when view gain and lose focus
|
||||
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
var previousView *gocui.View
|
||||
return func(g *gocui.Gui) error {
|
||||
newView := gui.g.CurrentView()
|
||||
// for now we don't consider losing focus to a popup panel as actually losing focus
|
||||
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
|
||||
if err := gui.onViewFocusLost(previousView); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
previousView = newView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
|
||||
if oldView == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldView.Highlight = false
|
||||
|
||||
_ = oldView.SetOriginX(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) TransientContexts() []types.Context {
|
||||
return slices.Filter(gui.State.Contexts.Flatten(), func(context types.Context) bool {
|
||||
return context.IsTransient()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) rerenderView(view *gocui.View) error {
|
||||
context, ok := gui.contextForView(view.Name())
|
||||
if !ok {
|
||||
gui.Log.Errorf("no context found for view %s", view.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
return context.HandleRender()
|
||||
}
|
||||
|
||||
func (gui *Gui) getSideContextSelectedItemId() string {
|
||||
currentSideContext := gui.currentSideListContext()
|
||||
if currentSideContext == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return currentSideContext.GetSelectedItemId()
|
||||
}
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) getCurrentSideView() *gocui.View {
|
||||
// currentSideContext := gui.currentSideContext()
|
||||
// if currentSideContext == nil {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// view, _ := gui.g.View(currentSideContext.GetViewName())
|
||||
|
||||
// return view
|
||||
// }
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) renderContextStack() string {
|
||||
// result := ""
|
||||
// for _, context := range gui.State.ContextManager.ContextStack {
|
||||
// result += context.GetViewName() + "\n"
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
|
@ -16,6 +16,9 @@ type BaseContext struct {
|
||||
keybindingsFns []types.KeybindingsFn
|
||||
mouseKeybindingsFns []types.MouseKeybindingsFn
|
||||
onClickFn func() error
|
||||
onRenderToMainFn func() error
|
||||
onFocusFn onFocusFn
|
||||
onFocusLostFn onFocusLostFn
|
||||
|
||||
focusable bool
|
||||
transient bool
|
||||
@ -25,6 +28,11 @@ type BaseContext struct {
|
||||
*ParentContextMgr
|
||||
}
|
||||
|
||||
type (
|
||||
onFocusFn = func(types.OnFocusOpts) error
|
||||
onFocusLostFn = func(types.OnFocusLostOpts) error
|
||||
)
|
||||
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
||||
type NewBaseContextOpts struct {
|
||||
@ -129,6 +137,36 @@ func (self *BaseContext) GetOnClick() func() error {
|
||||
return self.onClickFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnRenderToMainFn(fn func() error) {
|
||||
if fn != nil {
|
||||
self.onRenderToMainFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnRenderToMain() func() error {
|
||||
return self.onRenderToMainFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnFocusFn(fn onFocusFn) {
|
||||
if fn != nil {
|
||||
self.onFocusFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnFocus() onFocusFn {
|
||||
return self.onFocusFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnFocusLostFn(fn onFocusLostFn) {
|
||||
if fn != nil {
|
||||
self.onFocusLostFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnFocusLost() onFocusLostFn {
|
||||
return self.onFocusLostFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
bindings := []*gocui.ViewMouseBinding{}
|
||||
for i := range self.mouseKeybindingsFns {
|
||||
|
@ -11,22 +11,21 @@ type BranchesContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*BranchesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*BranchesContext)(nil)
|
||||
_ types.DiffableContext = (*BranchesContext)(nil)
|
||||
)
|
||||
|
||||
func NewBranchesContext(
|
||||
getModel func() []*models.Branch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &BranchesContext{
|
||||
self := &BranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
@ -35,16 +34,14 @@ func NewBranchesContext(
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetSelectedItemId() string {
|
||||
@ -63,3 +60,16 @@ func (self *BranchesContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return branch
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetDiffTerminals() []string {
|
||||
// for our local branches we want to include both the branch and its upstream
|
||||
branch := self.GetSelected()
|
||||
if branch != nil {
|
||||
names := []string{branch.ID()}
|
||||
if branch.IsTrackingRemote() {
|
||||
names = append(names, branch.ID()+"@{u}")
|
||||
}
|
||||
return names
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -13,17 +13,16 @@ type CommitFilesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*CommitFilesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffableContext = (*CommitFilesContext)(nil)
|
||||
)
|
||||
|
||||
func NewCommitFilesContext(
|
||||
getModel func() []*models.CommitFile,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||
@ -41,11 +40,7 @@ func NewCommitFilesContext(
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}),
|
||||
ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -61,3 +56,50 @@ func (self *CommitFilesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) renderToMain() error {
|
||||
node := self.GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := self.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(
|
||||
from, to, reverse, node.GetPath(), false, self.c.State().GetIgnoreWhitespaceInDiffView(),
|
||||
)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := self.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = self.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.Patch,
|
||||
Task: task,
|
||||
},
|
||||
Secondary: secondaryPatchPanelUpdateOpts(self.c),
|
||||
})
|
||||
}
|
||||
|
||||
func secondaryPatchPanelUpdateOpts(c *types.HelperCommon) *types.ViewUpdateOpts {
|
||||
if c.Git().Patch.PatchBuilder.Active() {
|
||||
patch := c.Git().Patch.PatchBuilder.RenderAggregatedPatch(false)
|
||||
|
||||
return &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(patch),
|
||||
Title: c.Tr.CustomPatch,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -11,17 +11,16 @@ type LocalCommitsContext struct {
|
||||
*ViewportListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*LocalCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewLocalCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *LocalCommitsContext {
|
||||
viewModel := NewLocalCommitsViewModel(getModel, c)
|
||||
@ -36,11 +35,7 @@ func NewLocalCommitsContext(
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -91,6 +86,12 @@ func (self *LocalCommitsContext) GetSelectedRef() types.Ref {
|
||||
return commit
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
|
||||
self.limitCommits = value
|
||||
}
|
||||
|
@ -24,12 +24,6 @@ func NewMenuContext(
|
||||
) *MenuContext {
|
||||
viewModel := NewMenuViewModel()
|
||||
|
||||
onFocus := func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := viewModel.GetSelected()
|
||||
renderToDescriptionView(selectedMenuItem.Tooltip)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &MenuContext{
|
||||
MenuViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
@ -41,9 +35,7 @@ func NewMenuContext(
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
list: viewModel,
|
||||
c: c,
|
||||
|
@ -11,17 +11,16 @@ type ReflogCommitsContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewReflogCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
@ -35,11 +34,7 @@ func NewReflogCommitsContext(
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -71,3 +66,9 @@ func (self *ReflogCommitsContext) GetSelectedRef() types.Ref {
|
||||
func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -12,17 +12,16 @@ type RemoteBranchesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
_ types.DiffableContext = (*RemoteBranchesContext)(nil)
|
||||
)
|
||||
|
||||
func NewRemoteBranchesContext(
|
||||
getModel func() []*models.RemoteBranch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *RemoteBranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
@ -38,11 +37,7 @@ func NewRemoteBranchesContext(
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -66,3 +61,9 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return remoteBranch
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -11,17 +11,16 @@ type RemotesContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*RemotesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*RemotesContext)(nil)
|
||||
_ types.DiffableContext = (*RemotesContext)(nil)
|
||||
)
|
||||
|
||||
func NewRemotesContext(
|
||||
getModel func() []*models.Remote,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
@ -35,11 +34,7 @@ func NewRemotesContext(
|
||||
Key: REMOTES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -55,3 +50,9 @@ func (self *RemotesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *RemotesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -6,29 +6,19 @@ import (
|
||||
)
|
||||
|
||||
type SimpleContext struct {
|
||||
OnFocus func(opts types.OnFocusOpts) error
|
||||
OnFocusLost func(opts types.OnFocusLostOpts) error
|
||||
OnRender func() error
|
||||
// this is for pushing some content to the main view
|
||||
OnRenderToMain func() error
|
||||
OnRender func() error
|
||||
|
||||
*BaseContext
|
||||
}
|
||||
|
||||
type ContextCallbackOpts struct {
|
||||
OnFocus func(opts types.OnFocusOpts) error
|
||||
OnFocusLost func(opts types.OnFocusLostOpts) error
|
||||
OnRender func() error
|
||||
OnRenderToMain func() error
|
||||
OnRender func() error
|
||||
}
|
||||
|
||||
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
|
||||
return &SimpleContext{
|
||||
OnFocus: opts.OnFocus,
|
||||
OnFocusLost: opts.OnFocusLost,
|
||||
OnRender: opts.OnRender,
|
||||
OnRenderToMain: opts.OnRenderToMain,
|
||||
BaseContext: baseContext,
|
||||
OnRender: opts.OnRender,
|
||||
BaseContext: baseContext,
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,14 +44,14 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
self.GetViewTrait().SetHighlight(true)
|
||||
}
|
||||
|
||||
if self.OnFocus != nil {
|
||||
if err := self.OnFocus(opts); err != nil {
|
||||
if self.onFocusFn != nil {
|
||||
if err := self.onFocusFn(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if self.OnRenderToMain != nil {
|
||||
if err := self.OnRenderToMain(); err != nil {
|
||||
if self.onRenderToMainFn != nil {
|
||||
if err := self.onRenderToMainFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -70,8 +60,8 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
|
||||
if self.OnFocusLost != nil {
|
||||
return self.OnFocusLost(opts)
|
||||
if self.onFocusLostFn != nil {
|
||||
return self.onFocusLostFn(opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -84,8 +74,8 @@ func (self *SimpleContext) HandleRender() error {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() error {
|
||||
if self.OnRenderToMain != nil {
|
||||
return self.OnRenderToMain()
|
||||
if self.onRenderToMainFn != nil {
|
||||
return self.onRenderToMainFn()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -11,17 +11,16 @@ type StashContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*StashContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*StashContext)(nil)
|
||||
_ types.DiffableContext = (*StashContext)(nil)
|
||||
)
|
||||
|
||||
func NewStashContext(
|
||||
getModel func() []*models.StashEntry,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *StashContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
@ -35,11 +34,7 @@ func NewStashContext(
|
||||
Key: STASH_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -67,3 +62,9 @@ func (self *StashContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return stash
|
||||
}
|
||||
|
||||
func (self *StashContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -15,17 +15,16 @@ type SubCommitsContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*SubCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*SubCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*SubCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewSubCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *SubCommitsContext {
|
||||
viewModel := &SubCommitsViewModel{
|
||||
@ -46,11 +45,7 @@ func NewSubCommitsContext(
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -111,3 +106,9 @@ func (self *SubCommitsContext) SetLimitCommits(value bool) {
|
||||
func (self *SubCommitsContext) GetLimitCommits() bool {
|
||||
return self.limitCommits
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -18,10 +18,6 @@ func NewSubmodulesContext(
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
@ -35,11 +31,7 @@ func NewSubmodulesContext(
|
||||
Key: SUBMODULES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
|
@ -17,10 +17,6 @@ func NewSuggestionsContext(
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *SuggestionsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
@ -35,11 +31,7 @@ func NewSuggestionsContext(
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
|
@ -11,17 +11,16 @@ type TagsContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*TagsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*TagsContext)(nil)
|
||||
_ types.DiffableContext = (*TagsContext)(nil)
|
||||
)
|
||||
|
||||
func NewTagsContext(
|
||||
getModel func() []*models.Tag,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *TagsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
@ -35,11 +34,7 @@ func NewTagsContext(
|
||||
Key: TAGS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@ -63,3 +58,9 @@ func (self *TagsContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func (self *TagsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
@ -19,10 +19,6 @@ func NewWorkingTreeContext(
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||
@ -36,11 +32,7 @@ func NewWorkingTreeContext(
|
||||
Key: FILES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
}), ContextCallbackOpts{}),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@ -16,9 +17,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
Focusable: false,
|
||||
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnRenderToMain: gui.statusRenderToMain,
|
||||
},
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
Status: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
@ -28,9 +27,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
Key: context.STATUS_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnRenderToMain: gui.statusRenderToMain,
|
||||
},
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
Snake: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
@ -40,17 +37,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
Key: context.SNAKE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
gui.startSnake()
|
||||
return nil
|
||||
},
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.snakeGame.Exit()
|
||||
gui.moveToTopOfWindow(gui.State.Contexts.Submodules)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
Files: gui.filesListContext(),
|
||||
Submodules: gui.submodulesListContext(),
|
||||
@ -73,11 +60,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
Key: context.NORMAL_MAIN_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
return nil // TODO: should we do something here? We should allow for scrolling the panel
|
||||
},
|
||||
},
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
NormalSecondary: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
@ -97,7 +80,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
gui.Views.Staging.Wrap = false
|
||||
gui.Views.StagingSecondary.Wrap = false
|
||||
|
||||
return gui.refreshStagingPanel(opts)
|
||||
return gui.helpers.Staging.RefreshStagingPanel(opts)
|
||||
},
|
||||
func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.Staging.SetState(nil)
|
||||
@ -121,7 +104,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
gui.Views.Staging.Wrap = false
|
||||
gui.Views.StagingSecondary.Wrap = false
|
||||
|
||||
return gui.refreshStagingPanel(opts)
|
||||
return gui.helpers.Staging.RefreshStagingPanel(opts)
|
||||
},
|
||||
func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.StagingSecondary.SetState(nil)
|
||||
@ -145,7 +128,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
// no need to change wrap on the secondary view because it can't be interacted with
|
||||
gui.Views.PatchBuilding.Wrap = false
|
||||
|
||||
return gui.refreshPatchBuildingPanel(opts)
|
||||
return gui.helpers.PatchBuilding.RefreshPatchBuildingPanel(opts)
|
||||
},
|
||||
func(opts types.OnFocusLostOpts) error {
|
||||
gui.Views.PatchBuilding.Wrap = true
|
||||
@ -180,20 +163,7 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
),
|
||||
MergeConflicts: context.NewMergeConflictsContext(
|
||||
gui.Views.MergeConflicts,
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(func() error {
|
||||
gui.Views.MergeConflicts.Wrap = false
|
||||
|
||||
return gui.refreshMergePanel(true)
|
||||
}),
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
|
||||
gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
|
||||
gui.Views.MergeConflicts.Wrap = true
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
context.ContextCallbackOpts{},
|
||||
gui.c,
|
||||
func() map[string]string {
|
||||
// wrapping in a function because contexts are initialized before helpers
|
||||
@ -211,10 +181,6 @@ func (gui *Gui) contextTree() *context.ContextTree {
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(gui.handleAskFocused),
|
||||
OnFocusLost: func(types.OnFocusLostOpts) error {
|
||||
gui.deactivateConfirmationPrompt()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
CommitMessage: context.NewSimpleContext(
|
||||
@ -278,3 +244,27 @@ func (gui *Gui) getPatchExplorerContexts() []types.IPatchExplorerContext {
|
||||
gui.State.Contexts.CustomPatchBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) popupViewNames() []string {
|
||||
popups := slices.Filter(gui.State.Contexts.Flatten(), func(c types.Context) bool {
|
||||
return c.GetKind() == types.PERSISTENT_POPUP || c.GetKind() == types.TEMPORARY_POPUP
|
||||
})
|
||||
|
||||
return slices.Map(popups, func(c types.Context) string {
|
||||
return c.GetViewName()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) TransientContexts() []types.Context {
|
||||
return slices.Filter(gui.State.Contexts.Flatten(), func(context types.Context) bool {
|
||||
return context.IsTransient()
|
||||
})
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
)
|
||||
|
||||
func (gui *Gui) resetControllers() {
|
||||
@ -31,10 +30,17 @@ func (gui *Gui) resetControllers() {
|
||||
return gui.State.savedCommitMessage
|
||||
}
|
||||
gpgHelper := helpers.NewGpgHelper(helperCommon, gui.os, gui.git)
|
||||
viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts)
|
||||
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
|
||||
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon, gui.git, gui.State.Contexts)
|
||||
stagingHelper := helpers.NewStagingHelper(helperCommon, gui.git, gui.State.Contexts)
|
||||
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git)
|
||||
refreshHelper := helpers.NewRefreshHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper, rebaseHelper, patchBuildingHelper, stagingHelper, mergeConflictsHelper, gui.fileWatcher)
|
||||
gui.helpers = &helpers.Helpers{
|
||||
Refs: refsHelper,
|
||||
Host: helpers.NewHostHelper(helperCommon, gui.git),
|
||||
PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git, gui.State.Contexts),
|
||||
PatchBuilding: patchBuildingHelper,
|
||||
Staging: stagingHelper,
|
||||
Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
|
||||
Suggestions: suggestionsHelper,
|
||||
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
|
||||
@ -42,7 +48,7 @@ func (gui *Gui) resetControllers() {
|
||||
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
|
||||
GPG: gpgHelper,
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git),
|
||||
MergeConflicts: mergeConflictsHelper,
|
||||
CherryPick: helpers.NewCherryPickHelper(
|
||||
helperCommon,
|
||||
gui.git,
|
||||
@ -50,8 +56,16 @@ func (gui *Gui) resetControllers() {
|
||||
func() *cherrypicking.CherryPicking { return gui.State.Modes.CherryPicking },
|
||||
rebaseHelper,
|
||||
),
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
|
||||
Snake: helpers.NewSnakeHelper(helperCommon),
|
||||
Diff: helpers.NewDiffHelper(helperCommon),
|
||||
Repos: helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo),
|
||||
RecordDirectory: recordDirectoryHelper,
|
||||
Update: helpers.NewUpdateHelper(helperCommon, gui.Updater),
|
||||
Window: helpers.NewWindowHelper(helperCommon, viewHelper, gui.State.Contexts),
|
||||
View: viewHelper,
|
||||
Refresh: refreshHelper,
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
@ -77,10 +91,7 @@ func (gui *Gui) resetControllers() {
|
||||
common,
|
||||
)
|
||||
|
||||
submodulesController := controllers.NewSubmodulesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
)
|
||||
submodulesController := controllers.NewSubmodulesController(common)
|
||||
|
||||
bisectController := controllers.NewBisectController(common)
|
||||
|
||||
@ -114,7 +125,6 @@ func (gui *Gui) resetControllers() {
|
||||
tagsController := controllers.NewTagsController(common)
|
||||
filesController := controllers.NewFilesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
setCommitMessage,
|
||||
getSavedCommitMessage,
|
||||
)
|
||||
@ -137,7 +147,10 @@ func (gui *Gui) resetControllers() {
|
||||
stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false)
|
||||
stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true)
|
||||
patchBuildingController := controllers.NewPatchBuildingController(common)
|
||||
snakeController := controllers.NewSnakeController(common, func() *snake.Game { return gui.snakeGame })
|
||||
snakeController := controllers.NewSnakeController(common)
|
||||
reflogCommitsController := controllers.NewReflogCommitsController(common, gui.State.Contexts.ReflogCommits)
|
||||
subCommitsController := controllers.NewSubCommitsController(common, gui.State.Contexts.SubCommits)
|
||||
statusController := controllers.NewStatusController(common)
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
@ -163,7 +176,7 @@ func (gui *Gui) resetControllers() {
|
||||
gui.State.Contexts.Stash,
|
||||
} {
|
||||
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
|
||||
common, gui.SwitchToCommitFilesContext, context,
|
||||
common, context, gui.State.Contexts.CommitFiles,
|
||||
))
|
||||
}
|
||||
|
||||
@ -175,6 +188,14 @@ func (gui *Gui) resetControllers() {
|
||||
controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context))
|
||||
}
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.ReflogCommits,
|
||||
reflogCommitsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.SubCommits,
|
||||
subCommitsController,
|
||||
)
|
||||
|
||||
// TODO: add scroll controllers for main panels (need to bring some more functionality across for that e.g. reading more from the currently displayed git command)
|
||||
controllers.AttachControllers(gui.State.Contexts.Staging,
|
||||
stagingController,
|
||||
@ -254,6 +275,10 @@ func (gui *Gui) resetControllers() {
|
||||
remoteBranchesController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Status,
|
||||
statusController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Global,
|
||||
syncController,
|
||||
undoController,
|
||||
@ -271,3 +296,14 @@ func (gui *Gui) resetControllers() {
|
||||
controllers.AttachControllers(context, listControllerFactory.Create(context))
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
|
||||
return func(text string) {
|
||||
// using a getView function so that we don't need to worry about when the view is created
|
||||
view := getView()
|
||||
view.ClearTextArea()
|
||||
view.TextArea.TypeString(text)
|
||||
_ = gui.resizePopupPanel(view, view.TextArea.GetContent())
|
||||
view.RenderTextArea()
|
||||
}
|
||||
}
|
||||
|
@ -7,5 +7,8 @@ func AttachControllers(context types.Context, controllers ...types.IController)
|
||||
context.AddKeybindingsFn(controller.GetKeybindings)
|
||||
context.AddMouseKeybindingsFn(controller.GetMouseKeybindings)
|
||||
context.AddOnClickFn(controller.GetOnClick())
|
||||
context.AddOnRenderToMainFn(controller.GetOnRenderToMain())
|
||||
context.AddOnFocusFn(controller.GetOnFocus())
|
||||
context.AddOnFocusLostFn(controller.GetOnFocusLost())
|
||||
}
|
||||
}
|
||||
|
@ -18,3 +18,15 @@ func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*g
|
||||
func (self *baseController) GetOnClick() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnRenderToMain() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -111,6 +111,30 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
branch := self.context().GetSelected()
|
||||
if branch == nil {
|
||||
task = types.NewRenderStringTask(self.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := self.c.Git().Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error {
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.SetUnsetUpstream,
|
||||
|
53
pkg/gui/controllers/confirmation_controller.go
Normal file
53
pkg/gui/controllers/confirmation_controller.go
Normal file
@ -0,0 +1,53 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ConfirmationController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &ConfirmationController{}
|
||||
|
||||
func NewConfirmationController(
|
||||
common *controllerCommon,
|
||||
) *ConfirmationController {
|
||||
return &ConfirmationController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
deactivateConfirmationPrompt(self.controllerCommon)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) context() types.Context {
|
||||
return self.contexts.Confirmation
|
||||
}
|
||||
|
||||
func deactivateConfirmationPrompt(c *controllerCommon) {
|
||||
c.mutexes.PopupMutex.Lock()
|
||||
c.c.State().GetRepoState().SetCurrentPopupOpts(nil)
|
||||
c.mutexes.PopupMutex.Unlock()
|
||||
|
||||
c.c.Views().Confirmation.Visible = false
|
||||
c.c.Views().Suggestions.Visible = false
|
||||
|
||||
gui.clearConfirmationViewKeyBindings()
|
||||
}
|
@ -15,7 +15,6 @@ type FilesController struct {
|
||||
baseController // nolint: unused
|
||||
*controllerCommon
|
||||
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||
setCommitMessage func(message string)
|
||||
getSavedCommitMessage func() string
|
||||
}
|
||||
@ -24,13 +23,11 @@ var _ types.IController = &FilesController{}
|
||||
|
||||
func NewFilesController(
|
||||
common *controllerCommon,
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||
setCommitMessage func(message string),
|
||||
getSavedCommitMessage func() string,
|
||||
) *FilesController {
|
||||
return &FilesController{
|
||||
controllerCommon: common,
|
||||
enterSubmodule: enterSubmodule,
|
||||
setCommitMessage: setCommitMessage,
|
||||
getSavedCommitMessage: getSavedCommitMessage,
|
||||
}
|
||||
@ -175,6 +172,74 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
node := self.context().GetSelected()
|
||||
|
||||
if node == nil {
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.DiffTitle,
|
||||
Task: types.NewRenderStringTask(self.c.Tr.NoChangedFiles),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.File != nil && node.File.HasInlineMergeConflicts {
|
||||
hasConflicts, err := self.helpers.MergeConflicts.SetMergeState(node.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return self.helpers.MergeConflicts.Render(false)
|
||||
}
|
||||
}
|
||||
|
||||
self.helpers.MergeConflicts.ResetMergeState()
|
||||
|
||||
pair := self.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = self.c.MainViewPairs().Staging
|
||||
}
|
||||
|
||||
split := self.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
|
||||
mainShowsStaged := !split && node.GetHasStagedChanges()
|
||||
|
||||
cmdObj := self.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
title := self.c.Tr.UnstagedChanges
|
||||
if mainShowsStaged {
|
||||
title = self.c.Tr.StagedChanges
|
||||
}
|
||||
refreshOpts := types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
Title: title,
|
||||
},
|
||||
}
|
||||
|
||||
if split {
|
||||
cmdObj := self.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
|
||||
title := self.c.Tr.StagedChanges
|
||||
if mainShowsStaged {
|
||||
title = self.c.Tr.UnstagedChanges
|
||||
}
|
||||
|
||||
refreshOpts.Secondary = &types.ViewUpdateOpts{
|
||||
Title: title,
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(refreshOpts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnClick() func() error {
|
||||
return self.checkSelectedFileNode(self.press)
|
||||
}
|
||||
@ -379,7 +444,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||
submoduleConfigs := self.model.Submodules
|
||||
if file.IsSubmodule(submoduleConfigs) {
|
||||
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
|
||||
return self.enterSubmodule(submoduleConfig)
|
||||
return self.helpers.Repos.EnterSubmodule(submoduleConfig)
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
|
114
pkg/gui/controllers/helpers/diff_helper.go
Normal file
114
pkg/gui/controllers/helpers/diff_helper.go
Normal file
@ -0,0 +1,114 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type DiffHelper struct {
|
||||
c *types.HelperCommon
|
||||
}
|
||||
|
||||
func NewDiffHelper(c *types.HelperCommon) *DiffHelper {
|
||||
return &DiffHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *DiffHelper) DiffStr() string {
|
||||
output := self.c.Modes().Diffing.Ref
|
||||
|
||||
right := self.currentDiffTerminal()
|
||||
if right != "" {
|
||||
output += " " + right
|
||||
}
|
||||
|
||||
if self.c.Modes().Diffing.Reverse {
|
||||
output += " -R"
|
||||
}
|
||||
|
||||
if self.c.State().GetIgnoreWhitespaceInDiffView() {
|
||||
output += " --ignore-all-space"
|
||||
}
|
||||
|
||||
file := self.currentlySelectedFilename()
|
||||
if file != "" {
|
||||
output += " -- " + file
|
||||
} else if self.c.Modes().Filtering.Active() {
|
||||
output += " -- " + self.c.Modes().Filtering.GetPath()
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (self *DiffHelper) ExitDiffMode() error {
|
||||
self.c.Modes().Diffing = diffing.New()
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *DiffHelper) RenderDiff() error {
|
||||
cmdObj := self.c.OS().Cmd.New(
|
||||
fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", self.DiffStr()),
|
||||
)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Diff",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// CurrentDiffTerminals returns the current diff terminals of the currently selected item.
|
||||
// in the case of a branch it returns both the branch and it's upstream name,
|
||||
// which becomes an option when you bring up the diff menu, but when you're just
|
||||
// flicking through branches it will be using the local branch name.
|
||||
func (self *DiffHelper) CurrentDiffTerminals() []string {
|
||||
c := self.c.CurrentSideContext()
|
||||
|
||||
if c.GetKey() == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := c.(type) {
|
||||
case types.DiffableContext:
|
||||
return v.GetDiffTerminals()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *DiffHelper) currentDiffTerminal() string {
|
||||
names := self.CurrentDiffTerminals()
|
||||
if len(names) == 0 {
|
||||
return ""
|
||||
}
|
||||
return names[0]
|
||||
}
|
||||
|
||||
func (self *DiffHelper) currentlySelectedFilename() string {
|
||||
currentContext := self.c.CurrentContext()
|
||||
|
||||
switch currentContext := currentContext.(type) {
|
||||
case types.IListContext:
|
||||
if lo.Contains([]types.ContextKey{context.FILES_CONTEXT_KEY, context.COMMIT_FILES_CONTEXT_KEY}, currentContext.GetKey()) {
|
||||
return currentContext.GetSelectedItemId()
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *DiffHelper) WithDiffModeCheck(f func() error) error {
|
||||
if self.c.Modes().Diffing.Active() {
|
||||
return self.RenderDiff()
|
||||
}
|
||||
|
||||
return f()
|
||||
}
|
@ -12,26 +12,45 @@ type Helpers struct {
|
||||
CherryPick *CherryPickHelper
|
||||
Host *HostHelper
|
||||
PatchBuilding *PatchBuildingHelper
|
||||
Staging *StagingHelper
|
||||
GPG *GpgHelper
|
||||
Upstream *UpstreamHelper
|
||||
AmendHelper *AmendHelper
|
||||
Snake *SnakeHelper
|
||||
// lives in context package because our contexts need it to render to main
|
||||
Diff *DiffHelper
|
||||
Repos *ReposHelper
|
||||
RecordDirectory *RecordDirectoryHelper
|
||||
Update *UpdateHelper
|
||||
Window *WindowHelper
|
||||
View *ViewHelper
|
||||
Refresh *RefreshHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
return &Helpers{
|
||||
Refs: &RefsHelper{},
|
||||
Bisect: &BisectHelper{},
|
||||
Suggestions: &SuggestionsHelper{},
|
||||
Files: &FilesHelper{},
|
||||
WorkingTree: &WorkingTreeHelper{},
|
||||
Tags: &TagsHelper{},
|
||||
MergeAndRebase: &MergeAndRebaseHelper{},
|
||||
MergeConflicts: &MergeConflictsHelper{},
|
||||
CherryPick: &CherryPickHelper{},
|
||||
Host: &HostHelper{},
|
||||
PatchBuilding: &PatchBuildingHelper{},
|
||||
GPG: &GpgHelper{},
|
||||
Upstream: &UpstreamHelper{},
|
||||
AmendHelper: &AmendHelper{},
|
||||
Refs: &RefsHelper{},
|
||||
Bisect: &BisectHelper{},
|
||||
Suggestions: &SuggestionsHelper{},
|
||||
Files: &FilesHelper{},
|
||||
WorkingTree: &WorkingTreeHelper{},
|
||||
Tags: &TagsHelper{},
|
||||
MergeAndRebase: &MergeAndRebaseHelper{},
|
||||
MergeConflicts: &MergeConflictsHelper{},
|
||||
CherryPick: &CherryPickHelper{},
|
||||
Host: &HostHelper{},
|
||||
PatchBuilding: &PatchBuildingHelper{},
|
||||
Staging: &StagingHelper{},
|
||||
GPG: &GpgHelper{},
|
||||
Upstream: &UpstreamHelper{},
|
||||
AmendHelper: &AmendHelper{},
|
||||
Snake: &SnakeHelper{},
|
||||
Diff: &DiffHelper{},
|
||||
Repos: &ReposHelper{},
|
||||
RecordDirectory: &RecordDirectoryHelper{},
|
||||
Update: &UpdateHelper{},
|
||||
Window: &WindowHelper{},
|
||||
View: &ViewHelper{},
|
||||
Refresh: &RefreshHelper{},
|
||||
}
|
||||
}
|
||||
|
@ -113,3 +113,42 @@ func (self *MergeConflictsHelper) SwitchToMerge(path string) error {
|
||||
func (self *MergeConflictsHelper) context() *context.MergeConflictsContext {
|
||||
return self.contexts.MergeConflicts
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) Render(isFocused bool) error {
|
||||
content := self.context().GetContentToRender(isFocused)
|
||||
|
||||
var task types.UpdateTask
|
||||
if self.context().IsUserScrolling() {
|
||||
task = types.NewRenderStringWithoutScrollTask(content)
|
||||
} else {
|
||||
originY := self.context().GetOriginY()
|
||||
task = types.NewRenderStringWithScrollTask(content, 0, originY)
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().MergeConflicts,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) RefreshMergeState() error {
|
||||
self.contexts.MergeConflicts.GetMutex().Lock()
|
||||
defer self.contexts.MergeConflicts.GetMutex().Unlock()
|
||||
|
||||
if self.c.CurrentContext().GetKey() != context.MERGE_CONFLICTS_CONTEXT_KEY {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasConflicts, err := self.SetConflictsAndRender(self.contexts.MergeConflicts.GetState().GetPath(), true)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if !hasConflicts {
|
||||
return self.EscapeMerge()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -60,3 +61,59 @@ func (self *PatchBuildingHelper) Reset() error {
|
||||
// refreshing the current context so that the secondary panel is hidden if necessary.
|
||||
return self.c.PostRefreshUpdate(self.c.CurrentContext())
|
||||
}
|
||||
|
||||
func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpts) error {
|
||||
selectedLineIdx := -1
|
||||
if opts.ClickedWindowName == "main" {
|
||||
selectedLineIdx = opts.ClickedViewLineIdx
|
||||
}
|
||||
|
||||
if !self.git.Patch.PatchBuilder.Active() {
|
||||
return self.Escape()
|
||||
}
|
||||
|
||||
// get diff from commit file that's currently selected
|
||||
path := self.contexts.CommitFiles.GetSelectedPath()
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := self.contexts.CommitFiles.CommitFileTreeViewModel.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
diff, err := self.git.WorkingTree.ShowFileDiff(from, to, reverse, path, true, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secondaryDiff := self.git.Patch.PatchBuilder.RenderPatchForFile(path, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context := self.contexts.CustomPatchBuilder
|
||||
|
||||
oldState := context.GetState()
|
||||
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, oldState, self.c.Log)
|
||||
context.SetState(state)
|
||||
if state == nil {
|
||||
return self.Escape()
|
||||
}
|
||||
|
||||
mainContent := context.GetContentToRender(true)
|
||||
|
||||
self.contexts.CustomPatchBuilder.FocusSelection()
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().PatchBuilding,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: self.c.Tr.Patch,
|
||||
},
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryDiff),
|
||||
Title: self.c.Tr.CustomPatch,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
38
pkg/gui/controllers/helpers/record_directory_helper.go
Normal file
38
pkg/gui/controllers/helpers/record_directory_helper.go
Normal file
@ -0,0 +1,38 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type RecordDirectoryHelper struct {
|
||||
c *types.HelperCommon
|
||||
}
|
||||
|
||||
func NewRecordDirectoryHelper(c *types.HelperCommon) *RecordDirectoryHelper {
|
||||
return &RecordDirectoryHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
// when a user runs lazygit with the LAZYGIT_NEW_DIR_FILE env variable defined
|
||||
// we will write the current directory to that file on exit so that their
|
||||
// shell can then change to that directory. That means you don't get kicked
|
||||
// back to the directory that you started with.
|
||||
func (self *RecordDirectoryHelper) RecordCurrentDirectory() error {
|
||||
// determine current directory, set it in LAZYGIT_NEW_DIR_FILE
|
||||
dirName, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.RecordDirectory(dirName)
|
||||
}
|
||||
|
||||
func (self *RecordDirectoryHelper) RecordDirectory(dirName string) error {
|
||||
newDirFilePath := os.Getenv("LAZYGIT_NEW_DIR_FILE")
|
||||
if newDirFilePath == "" {
|
||||
return nil
|
||||
}
|
||||
return self.c.OS().CreateFileWithContent(newDirFilePath, dirName)
|
||||
}
|
617
pkg/gui/controllers/helpers/refresh_helper.go
Normal file
617
pkg/gui/controllers/helpers/refresh_helper.go
Normal file
@ -0,0 +1,617 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"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"
|
||||
)
|
||||
|
||||
type RefreshHelper struct {
|
||||
c *types.HelperCommon
|
||||
contexts *context.ContextTree
|
||||
git *commands.GitCommand
|
||||
refsHelper *RefsHelper
|
||||
mergeAndRebaseHelper *MergeAndRebaseHelper
|
||||
patchBuildingHelper *PatchBuildingHelper
|
||||
stagingHelper *StagingHelper
|
||||
mergeConflictsHelper *MergeConflictsHelper
|
||||
fileWatcher types.IFileWatcher
|
||||
}
|
||||
|
||||
func NewRefreshHelper(
|
||||
c *types.HelperCommon,
|
||||
contexts *context.ContextTree,
|
||||
git *commands.GitCommand,
|
||||
refsHelper *RefsHelper,
|
||||
mergeAndRebaseHelper *MergeAndRebaseHelper,
|
||||
patchBuildingHelper *PatchBuildingHelper,
|
||||
stagingHelper *StagingHelper,
|
||||
mergeConflictsHelper *MergeConflictsHelper,
|
||||
fileWatcher types.IFileWatcher,
|
||||
) *RefreshHelper {
|
||||
return &RefreshHelper{
|
||||
c: c,
|
||||
contexts: contexts,
|
||||
git: git,
|
||||
refsHelper: refsHelper,
|
||||
mergeAndRebaseHelper: mergeAndRebaseHelper,
|
||||
patchBuildingHelper: patchBuildingHelper,
|
||||
stagingHelper: stagingHelper,
|
||||
mergeConflictsHelper: mergeConflictsHelper,
|
||||
fileWatcher: fileWatcher,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
if options.Scope == nil {
|
||||
self.c.Log.Infof(
|
||||
"refreshing all scopes in %s mode",
|
||||
getModeName(options.Mode),
|
||||
)
|
||||
} else {
|
||||
self.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 scopeSet *set.Set[types.RefreshableView]
|
||||
if len(options.Scope) == 0 {
|
||||
// not refreshing staging/patch-building unless explicitly requested because we only need
|
||||
// to refresh those while focused.
|
||||
scopeSet = set.NewFromSlice([]types.RefreshableView{
|
||||
types.COMMITS,
|
||||
types.BRANCHES,
|
||||
types.FILES,
|
||||
types.STASH,
|
||||
types.REFLOG,
|
||||
types.TAGS,
|
||||
types.REMOTES,
|
||||
types.STATUS,
|
||||
types.BISECT_INFO,
|
||||
})
|
||||
} else {
|
||||
scopeSet = set.NewFromSlice(options.Scope)
|
||||
}
|
||||
|
||||
refresh := func(f func()) {
|
||||
wg.Add(1)
|
||||
func() {
|
||||
if options.Mode == types.ASYNC {
|
||||
go utils.Safe(f)
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
|
||||
refresh(self.refreshCommits)
|
||||
} else if scopeSet.Includes(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() { _ = self.refreshRebaseCommits() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.SUB_COMMITS) {
|
||||
refresh(func() { _ = self.refreshSubCommitsWithLimit() })
|
||||
}
|
||||
|
||||
// reason we're not doing this if the COMMITS type is included is that if the COMMITS type _is_ included we will refresh the commit files context anyway
|
||||
if scopeSet.Includes(types.COMMIT_FILES) && !scopeSet.Includes(types.COMMITS) {
|
||||
refresh(func() { _ = self.refreshCommitFilesContext() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.FILES) || scopeSet.Includes(types.SUBMODULES) {
|
||||
refresh(func() { _ = self.refreshFilesAndSubmodules() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STASH) {
|
||||
refresh(func() { _ = self.refreshStashEntries() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.TAGS) {
|
||||
refresh(func() { _ = self.refreshTags() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.REMOTES) {
|
||||
refresh(func() { _ = self.refreshRemotes() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STAGING) {
|
||||
refresh(func() { _ = self.stagingHelper.RefreshStagingPanel(types.OnFocusOpts{}) })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.PATCH_BUILDING) {
|
||||
refresh(func() { _ = self.patchBuildingHelper.RefreshPatchBuildingPanel(types.OnFocusOpts{}) })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.MERGE_CONFLICTS) || scopeSet.Includes(types.FILES) {
|
||||
refresh(func() { _ = self.mergeConflictsHelper.RefreshMergeState() })
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
self.refreshStatus()
|
||||
|
||||
if options.Then != nil {
|
||||
options.Then()
|
||||
}
|
||||
}
|
||||
|
||||
if options.Mode == types.BLOCK_UI {
|
||||
self.c.OnUIThread(func() error {
|
||||
f()
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getScopeNames(scopes []types.RefreshableView) []string {
|
||||
scopeNameMap := map[types.RefreshableView]string{
|
||||
types.COMMITS: "commits",
|
||||
types.BRANCHES: "branches",
|
||||
types.FILES: "files",
|
||||
types.SUBMODULES: "submodules",
|
||||
types.SUB_COMMITS: "subCommits",
|
||||
types.STASH: "stash",
|
||||
types.REFLOG: "reflog",
|
||||
types.TAGS: "tags",
|
||||
types.REMOTES: "remotes",
|
||||
types.STATUS: "status",
|
||||
types.BISECT_INFO: "bisect",
|
||||
types.STAGING: "staging",
|
||||
types.MERGE_CONFLICTS: "mergeConflicts",
|
||||
}
|
||||
|
||||
return slices.Map(scopes, func(scope types.RefreshableView) string {
|
||||
return scopeNameMap[scope]
|
||||
})
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
// 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 (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||
switch self.c.State().GetRepoState().GetStartupStage() {
|
||||
case types.INITIAL:
|
||||
go utils.Safe(func() {
|
||||
_ = self.refreshReflogCommits()
|
||||
self.refreshBranches()
|
||||
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
||||
})
|
||||
|
||||
case types.COMPLETE:
|
||||
_ = self.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 (self *RefreshHelper) refreshCommits() {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
go utils.Safe(func() {
|
||||
self.refreshReflogCommitsConsideringStartup()
|
||||
|
||||
self.refreshBranches()
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
go utils.Safe(func() {
|
||||
_ = self.refreshCommitsWithLimit()
|
||||
ctx, ok := self.contexts.CommitFiles.GetParentContext()
|
||||
if ok && ctx.GetKey() == context.LOCAL_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 := self.contexts.LocalCommits.GetSelected()
|
||||
if commit != nil {
|
||||
self.contexts.CommitFiles.SetRef(commit)
|
||||
self.contexts.CommitFiles.SetTitleRef(commit.RefName())
|
||||
_ = self.refreshCommitFilesContext()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshCommitsWithLimit() error {
|
||||
self.c.Mutexes().LocalCommitsMutex.Lock()
|
||||
defer self.c.Mutexes().LocalCommitsMutex.Unlock()
|
||||
|
||||
commits, err := self.git.Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: self.contexts.LocalCommits.GetLimitCommits(),
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: true,
|
||||
RefName: self.refForLog(),
|
||||
All: self.contexts.LocalCommits.GetShowWholeGitGraph(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Model().Commits = commits
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.git.Status.WorkingTreeState()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.contexts.LocalCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
||||
self.c.Mutexes().SubCommitsMutex.Lock()
|
||||
defer self.c.Mutexes().SubCommitsMutex.Unlock()
|
||||
|
||||
commits, err := self.git.Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: self.contexts.SubCommits.GetLimitCommits(),
|
||||
FilterPath: self.c.Modes().Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
RefName: self.contexts.SubCommits.GetRef().FullRefName(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Model().SubCommits = commits
|
||||
|
||||
return self.c.PostRefreshUpdate(self.contexts.SubCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshCommitFilesContext() error {
|
||||
ref := self.contexts.CommitFiles.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
files, err := self.git.Loaders.CommitFileLoader.GetFilesInDiff(from, to, reverse)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.c.Model().CommitFiles = files
|
||||
self.contexts.CommitFiles.CommitFileTreeViewModel.SetTree()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.contexts.CommitFiles)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRebaseCommits() error {
|
||||
self.c.Mutexes().LocalCommitsMutex.Lock()
|
||||
defer self.c.Mutexes().LocalCommitsMutex.Unlock()
|
||||
|
||||
updatedCommits, err := self.git.Loaders.CommitLoader.MergeRebasingCommits(self.c.Model().Commits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Model().Commits = updatedCommits
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.git.Status.WorkingTreeState()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.contexts.LocalCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshTags() error {
|
||||
tags, err := self.git.Loaders.TagLoader.GetTags()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Model().Tags = tags
|
||||
|
||||
return self.c.PostRefreshUpdate(self.contexts.Tags)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||
configs, err := self.git.Submodule.GetConfigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Model().Submodules = configs
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// self.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a State.Model.Branches array to pick the current branch from
|
||||
func (self *RefreshHelper) refreshBranches() {
|
||||
reflogCommits := self.c.Model().FilteredReflogCommits
|
||||
if self.c.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 = self.git.Loaders.ReflogCommitLoader.GetReflogCommits(nil, "")
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
branches, err := self.git.Loaders.BranchLoader.Load(reflogCommits)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Model().Branches = branches
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Branches); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
self.refreshStatus()
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshFilesAndSubmodules() error {
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
self.c.State().SetIsRefreshingFiles(true)
|
||||
defer func() {
|
||||
self.c.State().SetIsRefreshingFiles(false)
|
||||
self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
}()
|
||||
|
||||
if err := self.refreshStateSubmoduleConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.OnUIThread(func() error {
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Submodules); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStateFiles() error {
|
||||
fileTreeViewModel := self.contexts.Files.FileTreeViewModel
|
||||
|
||||
// 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 self.c.Model().Files {
|
||||
if file.HasMergeConflicts {
|
||||
prevConflictFileCount++
|
||||
}
|
||||
if file.HasInlineMergeConflicts {
|
||||
hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name)
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
} else if !hasConflicts {
|
||||
pathsToStage = append(pathsToStage, file.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(pathsToStage) > 0 {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageResolvedFiles)
|
||||
if err := self.git.WorkingTree.StageFiles(pathsToStage); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
files := self.git.Loaders.FileLoader.
|
||||
GetStatusFiles(git_commands.GetStatusFileOptions{})
|
||||
|
||||
conflictFileCount := 0
|
||||
for _, file := range files {
|
||||
if file.HasMergeConflicts {
|
||||
conflictFileCount++
|
||||
}
|
||||
}
|
||||
|
||||
if self.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 {
|
||||
self.c.OnUIThread(func() error { return self.mergeAndRebaseHelper.PromptToContinueRebase() })
|
||||
}
|
||||
|
||||
fileTreeViewModel.RWMutex.Lock()
|
||||
|
||||
// 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 fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayConflicted)
|
||||
}
|
||||
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayAll)
|
||||
}
|
||||
|
||||
self.c.Model().Files = files
|
||||
fileTreeViewModel.SetTree()
|
||||
fileTreeViewModel.RWMutex.Unlock()
|
||||
|
||||
if err := self.fileWatcher.AddFilesToFileWatcher(files); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 (self *RefreshHelper) refreshReflogCommits() error {
|
||||
// pulling state into its own variable incase it gets swapped out for another state
|
||||
// and we get an out of bounds exception
|
||||
model := self.c.Model()
|
||||
var lastReflogCommit *models.Commit
|
||||
if len(model.ReflogCommits) > 0 {
|
||||
lastReflogCommit = model.ReflogCommits[0]
|
||||
}
|
||||
|
||||
refresh := func(stateCommits *[]*models.Commit, filterPath string) error {
|
||||
commits, onlyObtainedNewReflogCommits, err := self.git.Loaders.ReflogCommitLoader.
|
||||
GetReflogCommits(lastReflogCommit, filterPath)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if onlyObtainedNewReflogCommits {
|
||||
*stateCommits = append(commits, *stateCommits...)
|
||||
} else {
|
||||
*stateCommits = commits
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := refresh(&model.ReflogCommits, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if self.c.Modes().Filtering.Active() {
|
||||
if err := refresh(&model.FilteredReflogCommits, self.c.Modes().Filtering.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
model.FilteredReflogCommits = model.ReflogCommits
|
||||
}
|
||||
|
||||
return self.c.PostRefreshUpdate(self.contexts.ReflogCommits)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRemotes() error {
|
||||
prevSelectedRemote := self.contexts.Remotes.GetSelected()
|
||||
|
||||
remotes, err := self.git.Loaders.RemoteLoader.GetRemotes()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Model().Remotes = remotes
|
||||
|
||||
// we need to ensure our selected remote branches aren't now outdated
|
||||
if prevSelectedRemote != nil && self.c.Model().RemoteBranches != nil {
|
||||
// find remote now
|
||||
for _, remote := range remotes {
|
||||
if remote.Name == prevSelectedRemote.Name {
|
||||
self.c.Model().RemoteBranches = remote.Branches
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Remotes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.RemoteBranches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStashEntries() error {
|
||||
self.c.Model().StashEntries = self.git.Loaders.StashLoader.
|
||||
GetStashEntries(self.c.Modes().Filtering.GetPath())
|
||||
|
||||
return self.c.PostRefreshUpdate(self.contexts.Stash)
|
||||
}
|
||||
|
||||
// never call this on its own, it should only be called from within refreshCommits()
|
||||
func (self *RefreshHelper) refreshStatus() {
|
||||
self.c.Mutexes().RefreshingStatusMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingStatusMutex.Unlock()
|
||||
|
||||
currentBranch := self.refsHelper.GetCheckedOutRef()
|
||||
if currentBranch == nil {
|
||||
// need to wait for branches to refresh
|
||||
return
|
||||
}
|
||||
status := ""
|
||||
|
||||
if currentBranch.IsRealBranch() {
|
||||
status += presentation.ColoredBranchStatus(currentBranch, self.c.Tr) + " "
|
||||
}
|
||||
|
||||
workingTreeState := self.git.Status.WorkingTreeState()
|
||||
if workingTreeState != enums.REBASE_MODE_NONE {
|
||||
status += style.FgYellow.Sprintf("(%s) ", presentation.FormatWorkingTreeState(workingTreeState))
|
||||
}
|
||||
|
||||
name := presentation.GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name)
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
status += fmt.Sprintf("%s → %s ", repoName, name)
|
||||
|
||||
self.c.SetViewContent(self.c.Views().Status, status)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refForLog() string {
|
||||
bisectInfo := self.git.Bisect.GetInfo()
|
||||
self.c.Model().BisectInfo = bisectInfo
|
||||
|
||||
if !bisectInfo.Started() {
|
||||
return "HEAD"
|
||||
}
|
||||
|
||||
// need to see if our bisect's current commit is reachable from our 'new' ref.
|
||||
if bisectInfo.Bisecting() && !self.git.Bisect.ReachableFromStart(bisectInfo) {
|
||||
return bisectInfo.GetNewSha()
|
||||
}
|
||||
|
||||
return bisectInfo.GetStartSha()
|
||||
}
|
175
pkg/gui/controllers/helpers/repos_helper.go
Normal file
175
pkg/gui/controllers/helpers/repos_helper.go
Normal file
@ -0,0 +1,175 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type onNewRepoFn func(startArgs appTypes.StartArgs, reuseState bool) error
|
||||
|
||||
// helps switch back and forth between repos
|
||||
type ReposHelper struct {
|
||||
c *types.HelperCommon
|
||||
recordDirectoryHelper *RecordDirectoryHelper
|
||||
onNewRepo onNewRepoFn
|
||||
}
|
||||
|
||||
func NewRecentReposHelper(
|
||||
c *types.HelperCommon,
|
||||
recordDirectoryHelper *RecordDirectoryHelper,
|
||||
onNewRepo onNewRepoFn,
|
||||
) *ReposHelper {
|
||||
return &ReposHelper{
|
||||
c: c,
|
||||
recordDirectoryHelper: recordDirectoryHelper,
|
||||
onNewRepo: onNewRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ReposHelper) EnterSubmodule(submodule *models.SubmoduleConfig) error {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.State().GetRepoPathStack().Push(wd)
|
||||
|
||||
return self.DispatchSwitchToRepo(submodule.Path, true)
|
||||
}
|
||||
|
||||
func (self *ReposHelper) getCurrentBranch(path string) string {
|
||||
readHeadFile := func(path string) (string, error) {
|
||||
headFile, err := os.ReadFile(filepath.Join(path, "HEAD"))
|
||||
if err == nil {
|
||||
content := strings.TrimSpace(string(headFile))
|
||||
refsPrefix := "ref: refs/heads/"
|
||||
var branchDisplay string
|
||||
if strings.HasPrefix(content, refsPrefix) {
|
||||
// is a branch
|
||||
branchDisplay = strings.TrimPrefix(content, refsPrefix)
|
||||
} else {
|
||||
// detached HEAD state, displaying short SHA
|
||||
branchDisplay = utils.ShortSha(content)
|
||||
}
|
||||
return branchDisplay, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
gitDirPath := filepath.Join(path, ".git")
|
||||
|
||||
if gitDir, err := os.Stat(gitDirPath); err == nil {
|
||||
if gitDir.IsDir() {
|
||||
// ordinary repo
|
||||
if branch, err := readHeadFile(gitDirPath); err == nil {
|
||||
return branch
|
||||
}
|
||||
} else {
|
||||
// worktree
|
||||
if worktreeGitDir, err := os.ReadFile(gitDirPath); err == nil {
|
||||
content := strings.TrimSpace(string(worktreeGitDir))
|
||||
worktreePath := strings.TrimPrefix(content, "gitdir: ")
|
||||
if branch, err := readHeadFile(worktreePath); err == nil {
|
||||
return branch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.Tr.LcBranchUnknown
|
||||
}
|
||||
|
||||
func (self *ReposHelper) CreateRecentReposMenu() error {
|
||||
// we'll show an empty panel if there are no recent repos
|
||||
recentRepoPaths := []string{}
|
||||
if len(self.c.GetAppState().RecentRepos) > 0 {
|
||||
// we skip the first one because we're currently in it
|
||||
recentRepoPaths = self.c.GetAppState().RecentRepos[1:]
|
||||
}
|
||||
|
||||
currentBranches := sync.Map{}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(recentRepoPaths))
|
||||
|
||||
for _, path := range recentRepoPaths {
|
||||
go func(path string) {
|
||||
defer wg.Done()
|
||||
currentBranches.Store(path, self.getCurrentBranch(path))
|
||||
}(path)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
menuItems := slices.Map(recentRepoPaths, func(path string) *types.MenuItem {
|
||||
branchName, _ := currentBranches.Load(path)
|
||||
if icons.IsIconEnabled() {
|
||||
branchName = icons.BRANCH_ICON + " " + fmt.Sprintf("%v", branchName)
|
||||
}
|
||||
|
||||
return &types.MenuItem{
|
||||
LabelColumns: []string{
|
||||
filepath.Base(path),
|
||||
style.FgCyan.Sprint(branchName),
|
||||
style.FgMagenta.Sprint(path),
|
||||
},
|
||||
OnPress: func() error {
|
||||
// if we were in a submodule, we want to forget about that stack of repos
|
||||
// so that hitting escape in the new repo does nothing
|
||||
self.c.State().GetRepoPathStack().Clear()
|
||||
return self.DispatchSwitchToRepo(path, false)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.RecentRepos, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *ReposHelper) DispatchSwitchToRepo(path string, reuse bool) error {
|
||||
env.UnsetGitDirEnvs()
|
||||
originalPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return self.c.ErrorMsg(self.c.Tr.ErrRepositoryMovedOrDeleted)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := commands.VerifyInGitRepo(self.c.OS()); err != nil {
|
||||
if err := os.Chdir(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.recordDirectoryHelper.RecordCurrentDirectory(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// these two mutexes are used by our background goroutines (triggered via `self.goEvery`. We don't want to
|
||||
// switch to a repo while one of these goroutines is in the process of updating something
|
||||
self.c.Mutexes().SyncMutex.Lock()
|
||||
defer self.c.Mutexes().SyncMutex.Unlock()
|
||||
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
return self.onNewRepo(appTypes.StartArgs{}, reuse)
|
||||
}
|
76
pkg/gui/controllers/helpers/snake_helper.go
Normal file
76
pkg/gui/controllers/helpers/snake_helper.go
Normal file
@ -0,0 +1,76 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
)
|
||||
|
||||
type SnakeHelper struct {
|
||||
c *types.HelperCommon
|
||||
game *snake.Game
|
||||
}
|
||||
|
||||
func NewSnakeHelper(c *types.HelperCommon) *SnakeHelper {
|
||||
return &SnakeHelper{
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) StartGame() {
|
||||
view := self.c.Views().Snake
|
||||
|
||||
game := snake.NewGame(view.Width(), view.Height(), self.renderSnakeGame, self.c.LogAction)
|
||||
self.game = game
|
||||
game.Start()
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) ExitGame() {
|
||||
self.game.Exit()
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) SetDirection(direction snake.Direction) {
|
||||
self.game.SetDirection(direction)
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) renderSnakeGame(cells [][]snake.CellType, alive bool) {
|
||||
view := self.c.Views().Snake
|
||||
|
||||
if !alive {
|
||||
_ = self.c.ErrorMsg(self.c.Tr.YouDied)
|
||||
return
|
||||
}
|
||||
|
||||
output := self.drawSnakeGame(cells)
|
||||
|
||||
view.Clear()
|
||||
fmt.Fprint(view, output)
|
||||
self.c.Render()
|
||||
}
|
||||
|
||||
func (self *SnakeHelper) drawSnakeGame(cells [][]snake.CellType) string {
|
||||
writer := &strings.Builder{}
|
||||
|
||||
for i, row := range cells {
|
||||
for _, cell := range row {
|
||||
switch cell {
|
||||
case snake.None:
|
||||
writer.WriteString(" ")
|
||||
case snake.Snake:
|
||||
writer.WriteString("█")
|
||||
case snake.Food:
|
||||
writer.WriteString(style.FgMagenta.Sprint("█"))
|
||||
}
|
||||
}
|
||||
|
||||
if i < len(cells) {
|
||||
writer.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
output := writer.String()
|
||||
return output
|
||||
}
|
119
pkg/gui/controllers/helpers/staging_helper.go
Normal file
119
pkg/gui/controllers/helpers/staging_helper.go
Normal file
@ -0,0 +1,119 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/patch_exploring"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type StagingHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
contexts *context.ContextTree
|
||||
}
|
||||
|
||||
func NewStagingHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
contexts *context.ContextTree,
|
||||
) *StagingHelper {
|
||||
return &StagingHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
contexts: contexts,
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: used from outside this file
|
||||
func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) error {
|
||||
secondaryFocused := self.secondaryStagingFocused()
|
||||
|
||||
mainSelectedLineIdx := -1
|
||||
secondarySelectedLineIdx := -1
|
||||
if focusOpts.ClickedViewLineIdx > 0 {
|
||||
if secondaryFocused {
|
||||
secondarySelectedLineIdx = focusOpts.ClickedViewLineIdx
|
||||
} else {
|
||||
mainSelectedLineIdx = focusOpts.ClickedViewLineIdx
|
||||
}
|
||||
}
|
||||
|
||||
mainContext := self.contexts.Staging
|
||||
secondaryContext := self.contexts.StagingSecondary
|
||||
|
||||
var file *models.File
|
||||
node := self.contexts.Files.GetSelected()
|
||||
if node != nil {
|
||||
file = node.File
|
||||
}
|
||||
|
||||
if file == nil || (!file.HasUnstagedChanges && !file.HasStagedChanges) {
|
||||
return self.handleStagingEscape()
|
||||
}
|
||||
|
||||
mainDiff := self.git.WorkingTree.WorktreeFileDiff(file, true, false, false)
|
||||
secondaryDiff := self.git.WorkingTree.WorktreeFileDiff(file, true, true, false)
|
||||
|
||||
// grabbing locks here and releasing before we finish the function
|
||||
// because pushing say the secondary context could mean entering this function
|
||||
// again, and we don't want to have a deadlock
|
||||
mainContext.GetMutex().Lock()
|
||||
secondaryContext.GetMutex().Lock()
|
||||
|
||||
mainContext.SetState(
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetState(), self.c.Log),
|
||||
)
|
||||
|
||||
secondaryContext.SetState(
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetState(), self.c.Log),
|
||||
)
|
||||
|
||||
mainState := mainContext.GetState()
|
||||
secondaryState := secondaryContext.GetState()
|
||||
|
||||
mainContent := mainContext.GetContentToRender(!secondaryFocused)
|
||||
secondaryContent := secondaryContext.GetContentToRender(secondaryFocused)
|
||||
|
||||
mainContext.GetMutex().Unlock()
|
||||
secondaryContext.GetMutex().Unlock()
|
||||
|
||||
if mainState == nil && secondaryState == nil {
|
||||
return self.handleStagingEscape()
|
||||
}
|
||||
|
||||
if mainState == nil && !secondaryFocused {
|
||||
return self.c.PushContext(secondaryContext, focusOpts)
|
||||
}
|
||||
|
||||
if secondaryState == nil && secondaryFocused {
|
||||
return self.c.PushContext(mainContext, focusOpts)
|
||||
}
|
||||
|
||||
if secondaryFocused {
|
||||
self.contexts.StagingSecondary.FocusSelection()
|
||||
} else {
|
||||
self.contexts.Staging.FocusSelection()
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Staging,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: self.c.Tr.UnstagedChanges,
|
||||
},
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryContent),
|
||||
Title: self.c.Tr.StagedChanges,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *StagingHelper) handleStagingEscape() error {
|
||||
return self.c.PushContext(self.contexts.Files)
|
||||
}
|
||||
|
||||
func (self *StagingHelper) secondaryStagingFocused() bool {
|
||||
return self.c.CurrentStaticContext().GetKey() == self.contexts.StagingSecondary.GetKey()
|
||||
}
|
98
pkg/gui/controllers/helpers/update_helper.go
Normal file
98
pkg/gui/controllers/helpers/update_helper.go
Normal file
@ -0,0 +1,98 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type UpdateHelper struct {
|
||||
c *types.HelperCommon
|
||||
updater *updates.Updater
|
||||
}
|
||||
|
||||
func NewUpdateHelper(c *types.HelperCommon, updater *updates.Updater) *UpdateHelper {
|
||||
return &UpdateHelper{
|
||||
c: c,
|
||||
updater: updater,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) CheckForUpdateInBackground() error {
|
||||
self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
// ignoring the error for now so that I'm not annoying users
|
||||
self.c.Log.Error(err.Error())
|
||||
return nil
|
||||
}
|
||||
if newVersion == "" {
|
||||
return nil
|
||||
}
|
||||
if self.c.UserConfig.Update.Method == "background" {
|
||||
self.startUpdating(newVersion)
|
||||
return nil
|
||||
}
|
||||
return self.showUpdatePrompt(newVersion)
|
||||
}, false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) CheckForUpdateInForeground() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func() error {
|
||||
self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if newVersion == "" {
|
||||
return self.c.ErrorMsg(self.c.Tr.FailedToRetrieveLatestVersionErr)
|
||||
}
|
||||
return self.showUpdatePrompt(newVersion)
|
||||
}, true)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) startUpdating(newVersion string) {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func() error {
|
||||
self.c.State().SetUpdating(true)
|
||||
err := self.updater.Update(newVersion)
|
||||
return self.onUpdateFinish(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) onUpdateFinish(err error) error {
|
||||
self.c.State().SetUpdating(false)
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.SetViewContent(self.c.Views().AppStatus, "")
|
||||
if err != nil {
|
||||
errMessage := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.UpdateFailedErr, map[string]string{
|
||||
"errMessage": err.Error(),
|
||||
},
|
||||
)
|
||||
return self.c.ErrorMsg(errMessage)
|
||||
}
|
||||
return self.c.Alert(self.c.Tr.UpdateCompletedTitle, self.c.Tr.UpdateCompleted)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) showUpdatePrompt(newVersion string) error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.UpdateAvailable, map[string]string{
|
||||
"newVersion": newVersion,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.UpdateAvailableTitle,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
self.startUpdating(newVersion)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
33
pkg/gui/controllers/helpers/view_helper.go
Normal file
33
pkg/gui/controllers/helpers/view_helper.go
Normal file
@ -0,0 +1,33 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ViewHelper struct {
|
||||
c *types.HelperCommon
|
||||
contexts *context.ContextTree
|
||||
}
|
||||
|
||||
func NewViewHelper(c *types.HelperCommon, contexts *context.ContextTree) *ViewHelper {
|
||||
return &ViewHelper{
|
||||
c: c,
|
||||
contexts: contexts,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ViewHelper) ContextForView(viewName string) (types.Context, bool) {
|
||||
view, err := self.c.GocuiGui().View(viewName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, context := range self.contexts.Flatten() {
|
||||
if context.GetViewName() == view.Name() {
|
||||
return context, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
138
pkg/gui/controllers/helpers/window_helper.go
Normal file
138
pkg/gui/controllers/helpers/window_helper.go
Normal file
@ -0,0 +1,138 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type WindowHelper struct {
|
||||
c *types.HelperCommon
|
||||
viewHelper *ViewHelper
|
||||
contexts *context.ContextTree
|
||||
}
|
||||
|
||||
func NewWindowHelper(c *types.HelperCommon, viewHelper *ViewHelper, contexts *context.ContextTree) *WindowHelper {
|
||||
return &WindowHelper{
|
||||
c: c,
|
||||
viewHelper: viewHelper,
|
||||
contexts: contexts,
|
||||
}
|
||||
}
|
||||
|
||||
// A window refers to a place on the screen which can hold one or more views.
|
||||
// A view is a box that renders content, and within a window only one view will
|
||||
// appear at a time. When a view appears within a window, it occupies the whole
|
||||
// space. Right now most windows are 1:1 with views, except for commitFiles which
|
||||
// is a view that moves between windows
|
||||
|
||||
func (self *WindowHelper) GetViewNameForWindow(window string) string {
|
||||
viewName, ok := self.windowViewNameMap().Get(window)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Viewname not found for window: %s", window))
|
||||
}
|
||||
|
||||
return viewName
|
||||
}
|
||||
|
||||
func (self *WindowHelper) GetContextForWindow(window string) types.Context {
|
||||
viewName := self.GetViewNameForWindow(window)
|
||||
|
||||
context, ok := self.viewHelper.ContextForView(viewName)
|
||||
if !ok {
|
||||
panic("TODO: fix this")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
// for now all we actually care about is the context's view so we're storing that
|
||||
func (self *WindowHelper) SetWindowContext(c types.Context) {
|
||||
if c.IsTransient() {
|
||||
self.resetWindowContext(c)
|
||||
}
|
||||
|
||||
self.windowViewNameMap().Set(c.GetWindowName(), c.GetViewName())
|
||||
}
|
||||
|
||||
func (self *WindowHelper) windowViewNameMap() *utils.ThreadSafeMap[string, string] {
|
||||
return self.c.State().GetRepoState().GetWindowViewNameMap()
|
||||
}
|
||||
|
||||
func (self *WindowHelper) CurrentWindow() string {
|
||||
return self.c.CurrentContext().GetWindowName()
|
||||
}
|
||||
|
||||
// assumes the context's windowName has been set to the new window if necessary
|
||||
func (self *WindowHelper) resetWindowContext(c types.Context) {
|
||||
for _, windowName := range self.windowViewNameMap().Keys() {
|
||||
viewName, ok := self.windowViewNameMap().Get(windowName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
|
||||
for _, context := range self.contexts.Flatten() {
|
||||
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
|
||||
self.windowViewNameMap().Set(windowName, context.GetViewName())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// moves given context's view to the top of the window
|
||||
func (self *WindowHelper) MoveToTopOfWindow(context types.Context) {
|
||||
view := context.GetView()
|
||||
if view == nil {
|
||||
return
|
||||
}
|
||||
|
||||
window := context.GetWindowName()
|
||||
|
||||
topView := self.TopViewInWindow(window)
|
||||
|
||||
if view.Name() != topView.Name() {
|
||||
if err := self.c.GocuiGui().SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowHelper) TopViewInWindow(windowName string) *gocui.View {
|
||||
// now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list.
|
||||
viewNamesInWindow := self.viewNamesInWindow(windowName)
|
||||
|
||||
// The views list is ordered highest-last, so we're grabbing the last view of the window
|
||||
var topView *gocui.View
|
||||
for _, currentView := range self.c.GocuiGui().Views() {
|
||||
if lo.Contains(viewNamesInWindow, currentView.Name()) {
|
||||
topView = currentView
|
||||
}
|
||||
}
|
||||
|
||||
return topView
|
||||
}
|
||||
|
||||
func (self *WindowHelper) viewNamesInWindow(windowName string) []string {
|
||||
result := []string{}
|
||||
for _, context := range self.contexts.Flatten() {
|
||||
if context.GetWindowName() == windowName {
|
||||
result = append(result, context.GetViewName())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *WindowHelper) WindowForView(viewName string) string {
|
||||
context, ok := self.viewHelper.ContextForView(viewName)
|
||||
if !ok {
|
||||
panic("todo: deal with this")
|
||||
}
|
||||
|
||||
return context.GetWindowName()
|
||||
}
|
@ -12,6 +12,9 @@ import (
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// after selecting the 200th commit, we'll load in all the rest
|
||||
const COMMIT_THRESHOLD = 200
|
||||
|
||||
type (
|
||||
PullFilesFn func() error
|
||||
)
|
||||
@ -150,6 +153,50 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
commit := self.context().GetSelected()
|
||||
if commit == nil {
|
||||
task = types.NewRenderStringTask(self.c.Tr.NoCommitsThisBranch)
|
||||
} else if commit.Action == todo.UpdateRef {
|
||||
task = types.NewRenderStringTask(
|
||||
utils.ResolvePlaceholderString(
|
||||
self.c.Tr.UpdateRefHere,
|
||||
map[string]string{
|
||||
"ref": commit.Name,
|
||||
}))
|
||||
} else {
|
||||
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Patch",
|
||||
Task: task,
|
||||
},
|
||||
Secondary: secondaryPatchPanelUpdateOpts(self.c),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func secondaryPatchPanelUpdateOpts(c *types.HelperCommon) *types.ViewUpdateOpts {
|
||||
if c.Git().Patch.PatchBuilder.Active() {
|
||||
patch := c.Git().Patch.PatchBuilder.RenderAggregatedPatch(false)
|
||||
|
||||
return &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(patch),
|
||||
Title: c.Tr.CustomPatch,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
if self.context().GetSelectedLineIdx() >= len(self.model.Commits)-1 {
|
||||
return self.c.ErrorMsg(self.c.Tr.CannotSquashOrFixupFirstCommit)
|
||||
@ -753,6 +800,22 @@ func (self *LocalCommitsController) checkSelected(callback func(*models.Commit)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
context := self.context()
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
@ -44,6 +44,14 @@ func (self *MenuController) GetOnClick() func() error {
|
||||
return self.press
|
||||
}
|
||||
|
||||
func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := self.context().GetSelected()
|
||||
self.c.Views().Tooltip.SetContent(selectedMenuItem.Tooltip)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MenuController) press() error {
|
||||
return self.context().OnMenuPress(self.context().GetSelected())
|
||||
}
|
||||
|
@ -134,6 +134,24 @@ func (self *MergeConflictsController) GetMouseKeybindings(opts types.Keybindings
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
self.c.Views().MergeConflicts.Wrap = false
|
||||
|
||||
return self.helpers.MergeConflicts.Render(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.context().SetUserScrolling(false)
|
||||
self.context().GetState().ResetConflictSelection()
|
||||
self.c.Views().MergeConflicts.Wrap = true
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeConflictsController) HandleScrollUp() error {
|
||||
self.context().SetUserScrolling(true)
|
||||
self.context().GetViewTrait().ScrollUp(self.c.UserConfig.Gui.ScrollHeight)
|
||||
|
53
pkg/gui/controllers/reflog_commits_controller.go
Normal file
53
pkg/gui/controllers/reflog_commits_controller.go
Normal file
@ -0,0 +1,53 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ReflogCommitsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
context *context.ReflogCommitsContext
|
||||
}
|
||||
|
||||
var _ types.IController = &ReflogCommitsController{}
|
||||
|
||||
func NewReflogCommitsController(
|
||||
common *controllerCommon,
|
||||
context *context.ReflogCommitsContext,
|
||||
) *ReflogCommitsController {
|
||||
return &ReflogCommitsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsController) Context() types.Context {
|
||||
return self.context
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
commit := self.context.GetSelected()
|
||||
var task types.UpdateTask
|
||||
if commit == nil {
|
||||
task = types.NewRenderStringTask("No reflog history")
|
||||
} else {
|
||||
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Sha, self.c.Modes().Filtering.GetPath(), self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Reflog Entry",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -73,6 +73,29 @@ func (self *RemoteBranchesController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
remoteBranch := self.context().GetSelected()
|
||||
if remoteBranch == nil {
|
||||
task = types.NewRenderStringTask("No branches for this remote")
|
||||
} else {
|
||||
cmdObj := self.git.Branch.GetGraphCmdObj(remoteBranch.FullRefName())
|
||||
task = types.NewRunCommandTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Remote Branch",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@ -60,6 +64,28 @@ func (self *RemotesController) GetKeybindings(opts types.KeybindingsOpts) []*typ
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *RemotesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
remote := self.context.GetSelected()
|
||||
if remote == nil {
|
||||
task = types.NewRenderStringTask("No remotes")
|
||||
} else {
|
||||
task = types.NewRenderStringTask(fmt.Sprintf("%s\nUrls:\n%s", style.FgGreen.Sprint(remote.Name), strings.Join(remote.Urls, "\n")))
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Remote",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RemotesController) GetOnClick() func() error {
|
||||
return self.checkSelected(self.enter)
|
||||
}
|
||||
|
@ -8,20 +8,16 @@ import (
|
||||
type SnakeController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
|
||||
getGame func() *snake.Game
|
||||
}
|
||||
|
||||
var _ types.IController = &SnakeController{}
|
||||
|
||||
func NewSnakeController(
|
||||
common *controllerCommon,
|
||||
getGame func() *snake.Game,
|
||||
) *SnakeController {
|
||||
return &SnakeController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
getGame: getGame,
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,9 +52,24 @@ func (self *SnakeController) Context() types.Context {
|
||||
return self.contexts.Snake
|
||||
}
|
||||
|
||||
func (self *SnakeController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return func(types.OnFocusOpts) error {
|
||||
self.helpers.Snake.StartGame()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SnakeController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.helpers.Snake.ExitGame()
|
||||
self.helpers.Window.MoveToTopOfWindow(self.contexts.Submodules)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SnakeController) SetDirection(direction snake.Direction) func() error {
|
||||
return func() error {
|
||||
self.getGame().SetDirection(direction)
|
||||
self.helpers.Snake.SetDirection(direction)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,28 @@ func (self *StashController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *StashController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
stashEntry := self.context().GetSelected()
|
||||
if stashEntry == nil {
|
||||
task = types.NewRenderStringTask(self.c.Tr.NoStashEntries)
|
||||
} else {
|
||||
task = types.NewRunPtyTask(self.git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Stash",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StashController) checkSelected(callback func(*models.StashEntry) error) func() error {
|
||||
return func() error {
|
||||
item := self.context().GetSelected()
|
||||
|
198
pkg/gui/controllers/status_controller.go
Normal file
198
pkg/gui/controllers/status_controller.go
Normal file
@ -0,0 +1,198 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"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"
|
||||
)
|
||||
|
||||
type StatusController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &StatusController{}
|
||||
|
||||
func NewStatusController(
|
||||
common *controllerCommon,
|
||||
) *StatusController {
|
||||
return &StatusController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StatusController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.OpenFile),
|
||||
Handler: self.openConfig,
|
||||
Description: self.c.Tr.OpenConfig,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Edit),
|
||||
Handler: self.editConfig,
|
||||
Description: self.c.Tr.EditConfig,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Status.CheckForUpdate),
|
||||
Handler: self.handleCheckForUpdate,
|
||||
Description: self.c.Tr.LcCheckForUpdate,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Status.RecentRepos),
|
||||
Handler: self.helpers.Repos.CreateRecentReposMenu,
|
||||
Description: self.c.Tr.SwitchRepo,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph),
|
||||
Handler: self.showAllBranchLogs,
|
||||
Description: self.c.Tr.LcAllBranchesLogGraph,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *StatusController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
dashboardString := strings.Join(
|
||||
[]string{
|
||||
lazygitTitle(),
|
||||
"Copyright 2022 Jesse Duffield",
|
||||
fmt.Sprintf("Keybindings: %s", constants.Links.Docs.Keybindings),
|
||||
fmt.Sprintf("Config Options: %s", constants.Links.Docs.Config),
|
||||
fmt.Sprintf("Tutorial: %s", constants.Links.Docs.Tutorial),
|
||||
fmt.Sprintf("Raise an Issue: %s", constants.Links.Issues),
|
||||
fmt.Sprintf("Release Notes: %s", constants.Links.Releases),
|
||||
style.FgMagenta.Sprintf("Become a sponsor: %s", constants.Links.Donate), // caffeine ain't free
|
||||
}, "\n\n")
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.StatusTitle,
|
||||
Task: types.NewRenderStringTask(dashboardString),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StatusController) GetOnClick() func() error {
|
||||
return self.onClick
|
||||
}
|
||||
|
||||
func (self *StatusController) Context() types.Context {
|
||||
return self.contexts.Status
|
||||
}
|
||||
|
||||
func (self *StatusController) onClick() error {
|
||||
// TODO: move into some abstraction (status is currently not a listViewContext where a lot of this code lives)
|
||||
currentBranch := self.helpers.Refs.GetCheckedOutRef()
|
||||
if currentBranch == nil {
|
||||
// need to wait for branches to refresh
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.c.PushContext(self.Context()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cx, _ := self.c.Views().Status.Cursor()
|
||||
upstreamStatus := presentation.BranchStatus(currentBranch, self.c.Tr)
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
workingTreeState := self.git.Status.WorkingTreeState()
|
||||
switch workingTreeState {
|
||||
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
|
||||
workingTreeStatus := fmt.Sprintf("(%s)", presentation.FormatWorkingTreeState(workingTreeState))
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) {
|
||||
return self.helpers.MergeAndRebase.CreateRebaseOptionsMenu()
|
||||
}
|
||||
if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
|
||||
return self.helpers.Repos.CreateRecentReposMenu()
|
||||
}
|
||||
default:
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", repoName) {
|
||||
return self.helpers.Repos.CreateRecentReposMenu()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runeCount(str string) int {
|
||||
return len([]rune(str))
|
||||
}
|
||||
|
||||
func cursorInSubstring(cx int, prefix string, substring string) bool {
|
||||
return cx >= runeCount(prefix) && cx < runeCount(prefix+substring)
|
||||
}
|
||||
|
||||
func lazygitTitle() string {
|
||||
return `
|
||||
_ _ _
|
||||
| | (_) |
|
||||
| | __ _ _____ _ __ _ _| |_
|
||||
| |/ _` + "`" + ` |_ / | | |/ _` + "`" + ` | | __|
|
||||
| | (_| |/ /| |_| | (_| | | |_
|
||||
|_|\__,_/___|\__, |\__, |_|\__|
|
||||
__/ | __/ |
|
||||
|___/ |___/ `
|
||||
}
|
||||
|
||||
func (self *StatusController) askForConfigFile(action func(file string) error) error {
|
||||
confPaths := self.c.GetConfig().GetUserConfigPaths()
|
||||
switch len(confPaths) {
|
||||
case 0:
|
||||
return errors.New(self.c.Tr.NoConfigFileFoundErr)
|
||||
case 1:
|
||||
return action(confPaths[0])
|
||||
default:
|
||||
menuItems := slices.Map(confPaths, func(path string) *types.MenuItem {
|
||||
return &types.MenuItem{
|
||||
Label: path,
|
||||
OnPress: func() error {
|
||||
return action(path)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.SelectConfigFile,
|
||||
Items: menuItems,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StatusController) openConfig() error {
|
||||
return self.askForConfigFile(self.helpers.Files.OpenFile)
|
||||
}
|
||||
|
||||
func (self *StatusController) editConfig() error {
|
||||
return self.askForConfigFile(self.helpers.Files.EditFile)
|
||||
}
|
||||
|
||||
func (self *StatusController) showAllBranchLogs() error {
|
||||
cmdObj := self.git.Branch.AllBranchesLogCmdObj()
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *StatusController) handleCheckForUpdate() error {
|
||||
return self.helpers.Update.CheckForUpdateInForeground()
|
||||
}
|
70
pkg/gui/controllers/sub_commits_controller.go
Normal file
70
pkg/gui/controllers/sub_commits_controller.go
Normal file
@ -0,0 +1,70 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type SubCommitsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
context *context.SubCommitsContext
|
||||
}
|
||||
|
||||
var _ types.IController = &SubCommitsController{}
|
||||
|
||||
func NewSubCommitsController(
|
||||
common *controllerCommon,
|
||||
context *context.SubCommitsContext,
|
||||
) *SubCommitsController {
|
||||
return &SubCommitsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SubCommitsController) Context() types.Context {
|
||||
return self.context
|
||||
}
|
||||
|
||||
func (self *SubCommitsController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
commit := self.context.GetSelected()
|
||||
var task types.UpdateTask
|
||||
if commit == nil {
|
||||
task = types.NewRenderStringTask("No commits")
|
||||
} else {
|
||||
cmdObj := self.git.Commit.ShowCmdObj(commit.Sha, self.modes.Filtering.GetPath(), self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Commit",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SubCommitsController) GetOnFocus() func() error {
|
||||
return func() error {
|
||||
context := self.context
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
@ -14,20 +14,16 @@ import (
|
||||
type SubmodulesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||
}
|
||||
|
||||
var _ types.IController = &SubmodulesController{}
|
||||
|
||||
func NewSubmodulesController(
|
||||
controllerCommon *controllerCommon,
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||
) *SubmodulesController {
|
||||
return &SubmodulesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: controllerCommon,
|
||||
enterSubmodule: enterSubmodule,
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,8 +77,43 @@ func (self *SubmodulesController) GetOnClick() func() error {
|
||||
return self.checkSelected(self.enter)
|
||||
}
|
||||
|
||||
func (self *SubmodulesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
submodule := self.context().GetSelected()
|
||||
if submodule == nil {
|
||||
task = types.NewRenderStringTask("No submodules")
|
||||
} else {
|
||||
prefix := fmt.Sprintf(
|
||||
"Name: %s\nPath: %s\nUrl: %s\n\n",
|
||||
style.FgGreen.Sprint(submodule.Name),
|
||||
style.FgYellow.Sprint(submodule.Path),
|
||||
style.FgCyan.Sprint(submodule.Url),
|
||||
)
|
||||
|
||||
file := self.helpers.WorkingTree.FileForSubmodule(submodule)
|
||||
if file == nil {
|
||||
task = types.NewRenderStringTask(prefix)
|
||||
} else {
|
||||
cmdObj := self.git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Submodule",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SubmodulesController) enter(submodule *models.SubmoduleConfig) error {
|
||||
return self.enterSubmodule(submodule)
|
||||
return self.helpers.Repos.EnterSubmodule(submodule)
|
||||
}
|
||||
|
||||
func (self *SubmodulesController) add() error {
|
||||
|
43
pkg/gui/controllers/suggestions_controller.go
Normal file
43
pkg/gui/controllers/suggestions_controller.go
Normal file
@ -0,0 +1,43 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type SuggestionsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &SuggestionsController{}
|
||||
|
||||
func NewSuggestionsController(
|
||||
common *controllerCommon,
|
||||
) *SuggestionsController {
|
||||
return &SuggestionsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *SuggestionsController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
deactivateConfirmationPrompt
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SuggestionsController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *SuggestionsController) context() *context.SuggestionsContext {
|
||||
return self.contexts.Suggestions
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -17,20 +18,20 @@ type CanSwitchToDiffFiles interface {
|
||||
type SwitchToDiffFilesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
context CanSwitchToDiffFiles
|
||||
viewFiles func(SwitchToCommitFilesContextOpts) error
|
||||
context CanSwitchToDiffFiles
|
||||
diffFilesContext *context.CommitFilesContext
|
||||
}
|
||||
|
||||
func NewSwitchToDiffFilesController(
|
||||
controllerCommon *controllerCommon,
|
||||
viewFiles func(SwitchToCommitFilesContextOpts) error,
|
||||
context CanSwitchToDiffFiles,
|
||||
diffFilesContext *context.CommitFilesContext,
|
||||
) *SwitchToDiffFilesController {
|
||||
return &SwitchToDiffFilesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: controllerCommon,
|
||||
context: context,
|
||||
viewFiles: viewFiles,
|
||||
diffFilesContext: diffFilesContext,
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,3 +73,22 @@ func (self *SwitchToDiffFilesController) enter(ref types.Ref) error {
|
||||
func (self *SwitchToDiffFilesController) Context() types.Context {
|
||||
return self.context
|
||||
}
|
||||
|
||||
func (self *SwitchToDiffFilesController) viewFiles(opts SwitchToCommitFilesContextOpts) error {
|
||||
diffFilesContext := self.diffFilesContext
|
||||
|
||||
diffFilesContext.SetSelectedLineIdx(0)
|
||||
diffFilesContext.SetRef(opts.Ref)
|
||||
diffFilesContext.SetTitleRef(opts.Ref.Description())
|
||||
diffFilesContext.SetCanRebase(opts.CanRebase)
|
||||
diffFilesContext.SetParentContext(opts.Context)
|
||||
diffFilesContext.SetWindowName(opts.Context.GetWindowName())
|
||||
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.COMMIT_FILES},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.PushContext(diffFilesContext)
|
||||
}
|
||||
|
@ -56,6 +56,29 @@ func (self *TagsController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *TagsController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.helpers.Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
tag := self.context().GetSelected()
|
||||
if tag == nil {
|
||||
task = types.NewRenderStringTask("No tags")
|
||||
} else {
|
||||
cmdObj := self.git.Branch.GetGraphCmdObj(tag.FullRefName())
|
||||
task = types.NewRunCommandTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Tag",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TagsController) checkout(tag *models.Tag) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutTag)
|
||||
if err := self.helpers.Refs.CheckoutRef(tag.Name, types.CheckoutRefOptions{}); err != nil {
|
||||
|
@ -49,8 +49,8 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
|
||||
},
|
||||
}...)
|
||||
|
||||
if gui.currentContext().GetKey() == gui.State.Contexts.LocalCommits.GetKey() {
|
||||
selectedCommit := gui.getSelectedLocalCommit()
|
||||
if gui.c.CurrentContext().GetKey() == gui.State.Contexts.LocalCommits.GetKey() {
|
||||
selectedCommit := gui.State.Contexts.LocalCommits.GetSelected()
|
||||
if selectedCommit != nil && gui.git.Patch.PatchBuilder.To != selectedCommit.Sha {
|
||||
// adding this option to index 1
|
||||
menuItems = append(
|
||||
@ -97,7 +97,7 @@ func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
||||
}
|
||||
|
||||
func (gui *Gui) returnFocusFromPatchExplorerIfNecessary() error {
|
||||
if gui.currentContext().GetKey() == gui.State.Contexts.CustomPatchBuilder.GetKey() {
|
||||
if gui.c.CurrentContext().GetKey() == gui.State.Contexts.CustomPatchBuilder.GetKey() {
|
||||
return gui.helpers.PatchBuilding.Escape()
|
||||
}
|
||||
return nil
|
||||
|
@ -1,182 +0,0 @@
|
||||
package gui
|
||||
|
||||
// const diffForTest = `diff --git a/pkg/gui/diff_context_size.go b/pkg/gui/diff_context_size.go
|
||||
// index 0da0a982..742b7dcf 100644
|
||||
// --- a/pkg/gui/diff_context_size.go
|
||||
// +++ b/pkg/gui/diff_context_size.go
|
||||
// @@ -9,12 +9,12 @@ func getRefreshFunction(gui *Gui) func()error {
|
||||
// }
|
||||
// } else if key == context.MAIN_STAGING_CONTEXT_KEY {
|
||||
// return func() error {
|
||||
// - selectedLine := gui.Views.Secondary.SelectedLineIdx()
|
||||
// + selectedLine := gui.State.Panels.LineByLine.GetSelectedLineIdx()
|
||||
// return gui.handleRefreshStagingPanel(false, selectedLine)
|
||||
// }
|
||||
// } else if key == context.MAIN_PATCH_BUILDING_CONTEXT_KEY {
|
||||
// `
|
||||
|
||||
// func setupGuiForTest(gui *Gui) {
|
||||
// gui.g = &gocui.Gui{}
|
||||
// gui.Views.Main, _ = gui.prepareView("main")
|
||||
// gui.Views.Secondary, _ = gui.prepareView("secondary")
|
||||
// gui.Views.Options, _ = gui.prepareView("options")
|
||||
// gui.git.Patch.PatchManager = &patch.PatchManager{}
|
||||
// _, _ = gui.refreshLineByLinePanel(diffForTest, "", false, 11)
|
||||
// }
|
||||
|
||||
// func TestIncreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) {
|
||||
// contexts := []func(gui *Gui) types.Context{
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Files },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.BranchCommits },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.CommitFiles },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Stash },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Staging },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.PatchBuilding },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.SubCommits },
|
||||
// }
|
||||
|
||||
// for _, c := range contexts {
|
||||
// gui := NewDummyGui()
|
||||
// context := c(gui)
|
||||
// setupGuiForTest(gui)
|
||||
// gui.c.UserConfig.Git.DiffContextSize = 1
|
||||
// _ = gui.c.PushContext(context)
|
||||
|
||||
// _ = gui.IncreaseContextInDiffView()
|
||||
|
||||
// assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDoesntIncreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
|
||||
// contexts := []func(gui *Gui) types.Context{
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Status },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Submodules },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Remotes },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Normal },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.ReflogCommits },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.RemoteBranches },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Tags },
|
||||
// // not testing this because it will kick straight back to the files context
|
||||
// // upon pushing the context
|
||||
// // func(gui *Gui) types.Context { return gui.State.Contexts.Merging },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.CommandLog },
|
||||
// }
|
||||
|
||||
// for _, c := range contexts {
|
||||
// gui := NewDummyGui()
|
||||
// context := c(gui)
|
||||
// setupGuiForTest(gui)
|
||||
// gui.c.UserConfig.Git.DiffContextSize = 1
|
||||
// _ = gui.c.PushContext(context)
|
||||
|
||||
// _ = gui.IncreaseContextInDiffView()
|
||||
|
||||
// assert.Equal(t, 1, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDecreasesContextInDiffViewByOneInContextWithDiff(t *testing.T) {
|
||||
// contexts := []func(gui *Gui) types.Context{
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Files },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.BranchCommits },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.CommitFiles },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Stash },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Staging },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.PatchBuilding },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.SubCommits },
|
||||
// }
|
||||
|
||||
// for _, c := range contexts {
|
||||
// gui := NewDummyGui()
|
||||
// context := c(gui)
|
||||
// setupGuiForTest(gui)
|
||||
// gui.c.UserConfig.Git.DiffContextSize = 2
|
||||
// _ = gui.c.PushContext(context)
|
||||
|
||||
// _ = gui.DecreaseContextInDiffView()
|
||||
|
||||
// assert.Equal(t, 1, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDoesntDecreaseContextInDiffViewInContextWithoutDiff(t *testing.T) {
|
||||
// contexts := []func(gui *Gui) types.Context{
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Status },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Submodules },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Remotes },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Normal },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.ReflogCommits },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.RemoteBranches },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.Tags },
|
||||
// // not testing this because it will kick straight back to the files context
|
||||
// // upon pushing the context
|
||||
// // func(gui *Gui) types.Context { return gui.State.Contexts.Merging },
|
||||
// func(gui *Gui) types.Context { return gui.State.Contexts.CommandLog },
|
||||
// }
|
||||
|
||||
// for _, c := range contexts {
|
||||
// gui := NewDummyGui()
|
||||
// context := c(gui)
|
||||
// setupGuiForTest(gui)
|
||||
// gui.c.UserConfig.Git.DiffContextSize = 2
|
||||
// _ = gui.c.PushContext(context)
|
||||
|
||||
// _ = gui.DecreaseContextInDiffView()
|
||||
|
||||
// assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize, string(context.GetKey()))
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) {
|
||||
// gui := NewDummyGui()
|
||||
// setupGuiForTest(gui)
|
||||
// gui.c.UserConfig.Git.DiffContextSize = 2
|
||||
// _ = gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||
// gui.git.Patch.PatchManager.Start("from", "to", false, false)
|
||||
|
||||
// errorCount := 0
|
||||
// gui.PopupHandler = &popup.TestPopupHandler{
|
||||
// OnErrorMsg: func(message string) error {
|
||||
// assert.Equal(t, gui.c.Tr.CantChangeContextSizeError, message)
|
||||
// errorCount += 1
|
||||
// return nil
|
||||
// },
|
||||
// }
|
||||
|
||||
// _ = gui.IncreaseContextInDiffView()
|
||||
|
||||
// assert.Equal(t, 1, errorCount)
|
||||
// assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize)
|
||||
// }
|
||||
|
||||
// func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *testing.T) {
|
||||
// gui := NewDummyGui()
|
||||
// setupGuiForTest(gui)
|
||||
// gui.c.UserConfig.Git.DiffContextSize = 2
|
||||
// _ = gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||
// gui.git.Patch.PatchManager.Start("from", "to", false, false)
|
||||
|
||||
// errorCount := 0
|
||||
// gui.PopupHandler = &popup.TestPopupHandler{
|
||||
// OnErrorMsg: func(message string) error {
|
||||
// assert.Equal(t, gui.c.Tr.CantChangeContextSizeError, message)
|
||||
// errorCount += 1
|
||||
// return nil
|
||||
// },
|
||||
// }
|
||||
|
||||
// _ = gui.DecreaseContextInDiffView()
|
||||
|
||||
// assert.Equal(t, 2, gui.c.UserConfig.Git.DiffContextSize)
|
||||
// }
|
||||
|
||||
// func TestDecreasesContextInDiffViewNoFurtherThanOne(t *testing.T) {
|
||||
// gui := NewDummyGui()
|
||||
// setupGuiForTest(gui)
|
||||
// gui.c.UserConfig.Git.DiffContextSize = 1
|
||||
|
||||
// _ = gui.DecreaseContextInDiffView()
|
||||
|
||||
// assert.Equal(t, 1, gui.c.UserConfig.Git.DiffContextSize)
|
||||
// }
|
@ -4,113 +4,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) exitDiffMode() error {
|
||||
gui.State.Modes.Diffing = diffing.New()
|
||||
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (gui *Gui) renderDiff() error {
|
||||
cmdObj := gui.os.Cmd.New(
|
||||
fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", gui.diffStr()),
|
||||
)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Diff",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// currentDiffTerminals returns the current diff terminals of the currently selected item.
|
||||
// in the case of a branch it returns both the branch and it's upstream name,
|
||||
// which becomes an option when you bring up the diff menu, but when you're just
|
||||
// flicking through branches it will be using the local branch name.
|
||||
func (gui *Gui) currentDiffTerminals() []string {
|
||||
c := gui.currentSideContext()
|
||||
|
||||
if c.GetKey() == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch v := c.(type) {
|
||||
case *context.WorkingTreeContext, *context.SubmodulesContext:
|
||||
// TODO: should we just return nil here?
|
||||
return []string{""}
|
||||
case *context.CommitFilesContext:
|
||||
return []string{v.GetRef().RefName()}
|
||||
case *context.BranchesContext:
|
||||
// for our local branches we want to include both the branch and its upstream
|
||||
branch := gui.State.Contexts.Branches.GetSelected()
|
||||
if branch != nil {
|
||||
names := []string{branch.ID()}
|
||||
if branch.IsTrackingRemote() {
|
||||
names = append(names, branch.ID()+"@{u}")
|
||||
}
|
||||
return names
|
||||
}
|
||||
return nil
|
||||
case types.IListContext:
|
||||
itemId := v.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) currentDiffTerminal() string {
|
||||
names := gui.currentDiffTerminals()
|
||||
if len(names) == 0 {
|
||||
return ""
|
||||
}
|
||||
return names[0]
|
||||
}
|
||||
|
||||
func (gui *Gui) currentlySelectedFilename() string {
|
||||
switch gui.currentContext().GetKey() {
|
||||
case context.FILES_CONTEXT_KEY, context.COMMIT_FILES_CONTEXT_KEY:
|
||||
return gui.getSideContextSelectedItemId()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) diffStr() string {
|
||||
output := gui.State.Modes.Diffing.Ref
|
||||
|
||||
right := gui.currentDiffTerminal()
|
||||
if right != "" {
|
||||
output += " " + right
|
||||
}
|
||||
|
||||
if gui.State.Modes.Diffing.Reverse {
|
||||
output += " -R"
|
||||
}
|
||||
|
||||
if gui.IgnoreWhitespaceInDiffView {
|
||||
output += " --ignore-all-space"
|
||||
}
|
||||
|
||||
file := gui.currentlySelectedFilename()
|
||||
if file != "" {
|
||||
output += " -- " + file
|
||||
} else if gui.State.Modes.Filtering.Active() {
|
||||
output += " -- " + gui.State.Modes.Filtering.GetPath()
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateDiffingMenuPanel() error {
|
||||
names := gui.currentDiffTerminals()
|
||||
names := gui.helpers.Diff.CurrentDiffTerminals()
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
for _, name := range names {
|
||||
|
@ -15,7 +15,7 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
|
||||
{
|
||||
Label: gui.c.Tr.ToggleShowCommandLog,
|
||||
OnPress: func() error {
|
||||
currentContext := gui.currentStaticContext()
|
||||
currentContext := gui.c.CurrentStaticContext()
|
||||
if gui.ShowExtrasWindow && currentContext.GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
@ -39,7 +39,7 @@ func (gui *Gui) handleCreateExtrasMenuPanel() error {
|
||||
func (gui *Gui) handleFocusCommandLog() error {
|
||||
gui.ShowExtrasWindow = true
|
||||
// TODO: is this necessary? Can't I just call 'return from context'?
|
||||
gui.State.Contexts.CommandLog.SetParentContext(gui.currentSideContext())
|
||||
gui.State.Contexts.CommandLog.SetParentContext(gui.c.CurrentSideContext())
|
||||
return gui.c.PushContext(gui.State.Contexts.CommandLog)
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,8 @@ import (
|
||||
// file watching is only really an added bonus for faster refreshing.
|
||||
const MAX_WATCHED_FILES = 50
|
||||
|
||||
var _ types.IFileWatcher = new(fileWatcher)
|
||||
|
||||
type fileWatcher struct {
|
||||
Watcher *fsnotify.Watcher
|
||||
WatchedFilenames []string
|
||||
@ -60,7 +62,7 @@ func (w *fileWatcher) watchFilename(filename string) {
|
||||
w.WatchedFilenames = append(w.WatchedFilenames, filename)
|
||||
}
|
||||
|
||||
func (w *fileWatcher) addFilesToFileWatcher(files []*models.File) error {
|
||||
func (w *fileWatcher) AddFilesToFileWatcher(files []*models.File) error {
|
||||
if w.Disabled {
|
||||
return nil
|
||||
}
|
||||
@ -102,7 +104,7 @@ func min(a int, b int) int {
|
||||
|
||||
// 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() {
|
||||
func (gui *Gui) WatchFilesForChanges() {
|
||||
gui.fileWatcher = NewFileWatcher(gui.Log)
|
||||
if gui.fileWatcher.Disabled {
|
||||
return
|
||||
@ -117,7 +119,7 @@ func (gui *Gui) watchFilesForChanges() {
|
||||
continue
|
||||
}
|
||||
// only refresh if we're not already
|
||||
if !gui.State.IsRefreshingFiles {
|
||||
if !gui.IsRefreshingFiles {
|
||||
_ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
}
|
||||
|
||||
|
@ -1,95 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) getSelectedFileNode() *filetree.FileNode {
|
||||
return gui.State.Contexts.Files.GetSelected()
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedFile() *models.File {
|
||||
node := gui.getSelectedFileNode()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return node.File
|
||||
}
|
||||
|
||||
func (gui *Gui) filesRenderToMain() error {
|
||||
node := gui.getSelectedFileNode()
|
||||
|
||||
if node == nil {
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.DiffTitle,
|
||||
Task: types.NewRenderStringTask(gui.c.Tr.NoChangedFiles),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.File != nil && node.File.HasInlineMergeConflicts {
|
||||
hasConflicts, err := gui.helpers.MergeConflicts.SetMergeState(node.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return gui.refreshMergePanel(false)
|
||||
}
|
||||
}
|
||||
|
||||
gui.helpers.MergeConflicts.ResetMergeState()
|
||||
|
||||
pair := gui.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = gui.c.MainViewPairs().Staging
|
||||
}
|
||||
|
||||
split := gui.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
|
||||
mainShowsStaged := !split && node.GetHasStagedChanges()
|
||||
|
||||
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, gui.IgnoreWhitespaceInDiffView)
|
||||
title := gui.c.Tr.UnstagedChanges
|
||||
if mainShowsStaged {
|
||||
title = gui.c.Tr.StagedChanges
|
||||
}
|
||||
refreshOpts := types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
Title: title,
|
||||
},
|
||||
}
|
||||
|
||||
if split {
|
||||
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.IgnoreWhitespaceInDiffView)
|
||||
|
||||
title := gui.c.Tr.StagedChanges
|
||||
if mainShowsStaged {
|
||||
title = gui.c.Tr.UnstagedChanges
|
||||
}
|
||||
|
||||
refreshOpts.Secondary = &types.ViewUpdateOpts{
|
||||
Title: title,
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(refreshOpts)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSetTextareaTextFn(getView func() *gocui.View) func(string) {
|
||||
return func(text string) {
|
||||
// using a getView function so that we don't need to worry about when the view is created
|
||||
view := getView()
|
||||
view.ClearTextArea()
|
||||
view.TextArea.TypeString(text)
|
||||
_ = gui.resizePopupPanel(view, view.TextArea.GetContent())
|
||||
view.RenderTextArea()
|
||||
}
|
||||
}
|
@ -9,9 +9,9 @@ import (
|
||||
|
||||
func (gui *Gui) handleCreateFilteringMenuPanel() error {
|
||||
fileName := ""
|
||||
switch gui.currentSideListContext() {
|
||||
switch gui.c.CurrentSideContext() {
|
||||
case gui.State.Contexts.Files:
|
||||
node := gui.getSelectedFileNode()
|
||||
node := gui.State.Contexts.Files.GetSelected()
|
||||
if node != nil {
|
||||
fileName = node.GetPath()
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@ -113,13 +112,13 @@ func (gui *Gui) scrollDownMain() error {
|
||||
}
|
||||
|
||||
func (gui *Gui) mainView() *gocui.View {
|
||||
viewName := gui.getViewNameForWindow("main")
|
||||
viewName := gui.helpers.Window.GetViewNameForWindow("main")
|
||||
view, _ := gui.g.View(viewName)
|
||||
return view
|
||||
}
|
||||
|
||||
func (gui *Gui) secondaryView() *gocui.View {
|
||||
viewName := gui.getViewNameForWindow("secondary")
|
||||
viewName := gui.helpers.Window.GetViewNameForWindow("secondary")
|
||||
view, _ := gui.g.View(viewName)
|
||||
return view
|
||||
}
|
||||
@ -162,17 +161,19 @@ func (gui *Gui) handleRefresh() error {
|
||||
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (gui *Gui) backgroundFetch() (err error) {
|
||||
err = gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
||||
|
||||
_ = gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
|
||||
// important to note that this assumes we've selected an item in a side context
|
||||
itemId := gui.getSideContextSelectedItemId()
|
||||
currentSideContext := gui.c.CurrentSideContext()
|
||||
if currentSideContext == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
listContext, ok := currentSideContext.(types.IListContext)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
itemId := listContext.GetSelectedItemId()
|
||||
|
||||
if itemId == "" {
|
||||
return nil
|
||||
@ -189,3 +190,13 @@ func (gui *Gui) handleCopySelectedSideContextItemToClipboard() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) rerenderView(view *gocui.View) error {
|
||||
context, ok := gui.helpers.View.ContextForView(view.Name())
|
||||
if !ok {
|
||||
gui.Log.Errorf("no context found for view %s", view.Name())
|
||||
return nil
|
||||
}
|
||||
|
||||
return context.HandleRender()
|
||||
}
|
||||
|
177
pkg/gui/gui.go
177
pkg/gui/gui.go
@ -8,6 +8,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
@ -32,7 +33,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
@ -58,18 +58,6 @@ const StartupPopupVersion = 5
|
||||
// OverlappingEdges determines if panel edges overlap
|
||||
var OverlappingEdges = false
|
||||
|
||||
type ContextManager struct {
|
||||
ContextStack []types.Context
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func NewContextManager() ContextManager {
|
||||
return ContextManager{
|
||||
ContextStack: []types.Context{},
|
||||
RWMutex: sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
type Repo string
|
||||
|
||||
// Gui wraps the gocui Gui object which handles rendering and events
|
||||
@ -117,12 +105,7 @@ type Gui struct {
|
||||
// this tells us whether our views have been initially set up
|
||||
ViewsSetup bool
|
||||
|
||||
Views Views
|
||||
|
||||
// if we've suspended the gui (e.g. because we've switched to a subprocess)
|
||||
// we typically want to pause some things that are running like background
|
||||
// file refreshes
|
||||
PauseBackgroundThreads bool
|
||||
Views types.Views
|
||||
|
||||
// Log of the commands that get run, to be displayed to the user.
|
||||
CmdLog []string
|
||||
@ -139,6 +122,8 @@ type Gui struct {
|
||||
// flag as to whether or not the diff view should ignore whitespace
|
||||
IgnoreWhitespaceInDiffView bool
|
||||
|
||||
IsRefreshingFiles bool
|
||||
|
||||
// we use this to decide whether we'll return to the original directory that
|
||||
// lazygit was opened in, or if we'll retain the one we're currently in.
|
||||
RetainOriginalDir bool
|
||||
@ -153,10 +138,52 @@ type Gui struct {
|
||||
// process
|
||||
InitialDir string
|
||||
|
||||
BackgroundRoutineMgr *BackgroundRoutineMgr
|
||||
// for accessing the gui's state from outside this package
|
||||
stateAccessor *StateAccessor
|
||||
|
||||
Updating bool
|
||||
|
||||
c *types.HelperCommon
|
||||
helpers *helpers.Helpers
|
||||
}
|
||||
|
||||
snakeGame *snake.Game
|
||||
type StateAccessor struct {
|
||||
gui *Gui
|
||||
}
|
||||
|
||||
var _ types.IStateAccessor = new(StateAccessor)
|
||||
|
||||
func (self *StateAccessor) GetIgnoreWhitespaceInDiffView() bool {
|
||||
return self.gui.IgnoreWhitespaceInDiffView
|
||||
}
|
||||
|
||||
func (self *StateAccessor) SetIgnoreWhitespaceInDiffView(value bool) {
|
||||
self.gui.IgnoreWhitespaceInDiffView = value
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetRepoPathStack() *utils.StringStack {
|
||||
return self.gui.RepoPathStack
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetUpdating() bool {
|
||||
return self.gui.Updating
|
||||
}
|
||||
|
||||
func (self *StateAccessor) SetUpdating(value bool) {
|
||||
self.gui.Updating = value
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetRepoState() types.IRepoStateAccessor {
|
||||
return self.gui.State
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetIsRefreshingFiles() bool {
|
||||
return self.gui.IsRefreshingFiles
|
||||
}
|
||||
|
||||
func (self *StateAccessor) SetIsRefreshingFiles(value bool) {
|
||||
self.gui.IsRefreshingFiles = value
|
||||
}
|
||||
|
||||
// we keep track of some stuff from one render to the next to see if certain
|
||||
@ -174,16 +201,14 @@ type GuiRepoState struct {
|
||||
// Suggestions will sometimes appear when typing into a prompt
|
||||
Suggestions []*types.Suggestion
|
||||
|
||||
Updating bool
|
||||
SplitMainPanel bool
|
||||
LimitCommits bool
|
||||
|
||||
IsRefreshingFiles bool
|
||||
Searching searchingState
|
||||
StartupStage StartupStage // Allows us to not load everything at once
|
||||
Searching searchingState
|
||||
StartupStage types.StartupStage // Allows us to not load everything at once
|
||||
|
||||
ContextManager ContextManager
|
||||
Contexts *context.ContextTree
|
||||
ContextMgr ContextMgr
|
||||
Contexts *context.ContextTree
|
||||
|
||||
// WindowViewNameMap is a mapping of windows to the current view of that window.
|
||||
// Some views move between windows for example the commitFiles view and when cycling through
|
||||
@ -204,20 +229,38 @@ type GuiRepoState struct {
|
||||
CurrentPopupOpts *types.CreatePopupPanelOpts
|
||||
}
|
||||
|
||||
var _ types.IRepoStateAccessor = new(GuiRepoState)
|
||||
|
||||
func (self *GuiRepoState) GetViewsSetup() bool {
|
||||
return self.ViewsSetup
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetWindowViewNameMap() *utils.ThreadSafeMap[string, string] {
|
||||
return self.WindowViewNameMap
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetStartupStage() types.StartupStage {
|
||||
return self.StartupStage
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) SetStartupStage(value types.StartupStage) {
|
||||
self.StartupStage = value
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetCurrentPopupOpts() *types.CreatePopupPanelOpts {
|
||||
return self.CurrentPopupOpts
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) SetCurrentPopupOpts(value *types.CreatePopupPanelOpts) {
|
||||
self.CurrentPopupOpts = value
|
||||
}
|
||||
|
||||
type searchingState struct {
|
||||
view *gocui.View
|
||||
isSearching bool
|
||||
searchString string
|
||||
}
|
||||
|
||||
// startup stages so we don't need to load everything at once
|
||||
type StartupStage int
|
||||
|
||||
const (
|
||||
INITIAL StartupStage = iota
|
||||
COMPLETE
|
||||
)
|
||||
|
||||
func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, reuseState bool) error {
|
||||
var err error
|
||||
gui.git, err = commands.NewGitCommand(
|
||||
@ -278,8 +321,6 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs, reuseState bool) {
|
||||
initialContext := initialContext(contextTree, startArgs)
|
||||
initialScreenMode := initialScreenMode(startArgs, gui.Config)
|
||||
|
||||
initialWindowViewNameMap := gui.initialWindowViewNameMap(contextTree)
|
||||
|
||||
gui.State = &GuiRepoState{
|
||||
Model: &types.Model{
|
||||
CommitFiles: nil,
|
||||
@ -298,9 +339,9 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs, reuseState bool) {
|
||||
},
|
||||
ScreenMode: initialScreenMode,
|
||||
// TODO: put contexts in the context manager
|
||||
ContextManager: NewContextManager(),
|
||||
ContextMgr: NewContextMgr(initialContext, gui),
|
||||
Contexts: contextTree,
|
||||
WindowViewNameMap: initialWindowViewNameMap,
|
||||
WindowViewNameMap: initialWindowViewNameMap(contextTree),
|
||||
}
|
||||
|
||||
if err := gui.c.PushContext(initialContext); err != nil {
|
||||
@ -310,6 +351,16 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs, reuseState bool) {
|
||||
gui.RepoStateMap[Repo(currentDir)] = gui.State
|
||||
}
|
||||
|
||||
func initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
|
||||
result := utils.NewThreadSafeMap[string, string]()
|
||||
|
||||
for _, context := range contextTree.Flatten() {
|
||||
result.Set(context.GetWindowName(), context.GetViewName())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func initialScreenMode(startArgs appTypes.StartArgs, config config.AppConfigurer) WindowMaximisation {
|
||||
if startArgs.FilterPath != "" || startArgs.GitArg != appTypes.GitArgNone {
|
||||
return SCREEN_HALF
|
||||
@ -391,7 +442,7 @@ func NewGui(
|
||||
InitialDir: initialDir,
|
||||
}
|
||||
|
||||
gui.watchFilesForChanges()
|
||||
gui.WatchFilesForChanges()
|
||||
|
||||
gui.PopupHandler = popup.NewPopupHandler(
|
||||
cmn,
|
||||
@ -429,6 +480,9 @@ func NewGui(
|
||||
icons.SetIconEnabled(gui.UserConfig.Gui.ShowIcons)
|
||||
presentation.SetCustomBranches(gui.UserConfig.Gui.BranchColors)
|
||||
|
||||
gui.BackgroundRoutineMgr = &BackgroundRoutineMgr{gui: gui}
|
||||
gui.stateAccessor = &StateAccessor{gui: gui}
|
||||
|
||||
return gui, nil
|
||||
}
|
||||
|
||||
@ -539,7 +593,7 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
|
||||
gui.waitForIntro.Add(1)
|
||||
|
||||
gui.startBackgroundRoutines()
|
||||
gui.BackgroundRoutineMgr.startBackgroundRoutines()
|
||||
|
||||
gui.c.Log.Info("starting main loop")
|
||||
|
||||
@ -565,11 +619,11 @@ func (gui *Gui) RunAndHandleError(startArgs appTypes.StartArgs) error {
|
||||
switch err {
|
||||
case gocui.ErrQuit:
|
||||
if gui.RetainOriginalDir {
|
||||
if err := gui.recordDirectory(gui.InitialDir); err != nil {
|
||||
if err := gui.helpers.RecordDirectory.RecordDirectory(gui.InitialDir); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := gui.recordCurrentDirectory(); err != nil {
|
||||
if err := gui.helpers.RecordDirectory.RecordCurrentDirectory(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -639,7 +693,8 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
|
||||
return false, gui.c.Error(err)
|
||||
}
|
||||
|
||||
gui.PauseBackgroundThreads = true
|
||||
gui.BackgroundRoutineMgr.PauseBackgroundThreads(true)
|
||||
defer gui.BackgroundRoutineMgr.PauseBackgroundThreads(false)
|
||||
|
||||
cmdErr := gui.runSubprocess(subprocess)
|
||||
|
||||
@ -647,8 +702,6 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
|
||||
return false, err
|
||||
}
|
||||
|
||||
gui.PauseBackgroundThreads = false
|
||||
|
||||
if cmdErr != nil {
|
||||
return false, gui.c.Error(cmdErr)
|
||||
}
|
||||
@ -751,3 +804,37 @@ func (gui *Gui) onUIThread(f func() error) {
|
||||
return f()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) startBackgroundRoutines() {
|
||||
mgr := &BackgroundRoutineMgr{gui: gui}
|
||||
mgr.startBackgroundRoutines()
|
||||
}
|
||||
|
||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
windowArranger := &WindowArranger{gui: gui}
|
||||
return windowArranger.getWindowDimensions(informationStr, appStatus)
|
||||
}
|
||||
|
||||
func (gui *Gui) replaceContext(c types.Context) error {
|
||||
return gui.State.ContextMgr.replaceContext(c)
|
||||
}
|
||||
|
||||
func (gui *Gui) pushContext(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
return gui.State.ContextMgr.pushContext(c, opts...)
|
||||
}
|
||||
|
||||
func (gui *Gui) popContext() error {
|
||||
return gui.State.ContextMgr.popContext()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentContext() types.Context {
|
||||
return gui.State.ContextMgr.currentContext()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentSideContext() types.Context {
|
||||
return gui.State.ContextMgr.currentSideContext()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentStaticContext() types.Context {
|
||||
return gui.State.ContextMgr.currentStaticContext()
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@ -41,23 +41,17 @@ func (self *guiCommon) RunSubprocess(cmdObj oscommands.ICmdObj) (bool, error) {
|
||||
}
|
||||
|
||||
func (self *guiCommon) PushContext(context types.Context, opts ...types.OnFocusOpts) error {
|
||||
singleOpts := types.OnFocusOpts{}
|
||||
if len(opts) > 0 {
|
||||
// using triple dot but you should only ever pass one of these opt structs
|
||||
if len(opts) > 1 {
|
||||
return errors.New("cannot pass multiple opts to pushContext")
|
||||
}
|
||||
|
||||
singleOpts = opts[0]
|
||||
}
|
||||
|
||||
return self.gui.pushContext(context, singleOpts)
|
||||
return self.gui.pushContext(context, opts...)
|
||||
}
|
||||
|
||||
func (self *guiCommon) PopContext() error {
|
||||
return self.gui.popContext()
|
||||
}
|
||||
|
||||
func (self *guiCommon) ReplaceContext(context types.Context) error {
|
||||
return self.gui.replaceContext(context)
|
||||
}
|
||||
|
||||
func (self *guiCommon) CurrentContext() types.Context {
|
||||
return self.gui.currentContext()
|
||||
}
|
||||
@ -66,6 +60,10 @@ func (self *guiCommon) CurrentStaticContext() types.Context {
|
||||
return self.gui.currentStaticContext()
|
||||
}
|
||||
|
||||
func (self *guiCommon) CurrentSideContext() types.Context {
|
||||
return self.gui.currentSideContext()
|
||||
}
|
||||
|
||||
func (self *guiCommon) IsCurrentContext(c types.Context) bool {
|
||||
return self.CurrentContext().GetKey() == c.GetKey()
|
||||
}
|
||||
@ -78,14 +76,54 @@ func (self *guiCommon) SaveAppState() error {
|
||||
return self.gui.Config.SaveAppState()
|
||||
}
|
||||
|
||||
func (self *guiCommon) GetConfig() config.AppConfigurer {
|
||||
return self.gui.Config
|
||||
}
|
||||
|
||||
func (self *guiCommon) ResetViewOrigin(view *gocui.View) {
|
||||
self.gui.resetViewOrigin(view)
|
||||
}
|
||||
|
||||
func (self *guiCommon) SetViewContent(view *gocui.View, content string) {
|
||||
self.gui.setViewContent(view, content)
|
||||
}
|
||||
|
||||
func (self *guiCommon) Render() {
|
||||
self.gui.render()
|
||||
}
|
||||
|
||||
func (self *guiCommon) Views() types.Views {
|
||||
return self.gui.Views
|
||||
}
|
||||
|
||||
func (self *guiCommon) Git() *commands.GitCommand {
|
||||
return self.gui.git
|
||||
}
|
||||
|
||||
func (self *guiCommon) OS() *oscommands.OSCommand {
|
||||
return self.gui.os
|
||||
}
|
||||
|
||||
func (self *guiCommon) Modes() *types.Modes {
|
||||
return self.gui.State.Modes
|
||||
}
|
||||
|
||||
func (self *guiCommon) Model() *types.Model {
|
||||
return self.gui.State.Model
|
||||
}
|
||||
|
||||
func (self *guiCommon) Mutexes() types.Mutexes {
|
||||
return self.gui.Mutexes
|
||||
}
|
||||
|
||||
func (self *guiCommon) OpenSearch() {
|
||||
_ = self.gui.handleOpenSearch(self.gui.currentViewName())
|
||||
}
|
||||
|
||||
func (self *guiCommon) GocuiGui() *gocui.Gui {
|
||||
return self.gui.g
|
||||
}
|
||||
|
||||
func (self *guiCommon) OnUIThread(f func() error) {
|
||||
self.gui.onUIThread(f)
|
||||
}
|
||||
@ -102,3 +140,7 @@ func (self *guiCommon) MainViewPairs() types.MainViewPairs {
|
||||
MergeConflicts: self.gui.mergingMainContextPair(),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *guiCommon) State() types.IStateAccessor {
|
||||
return self.gui.stateAccessor
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.OpenRecentRepos),
|
||||
Handler: self.handleCreateRecentReposMenu,
|
||||
Handler: self.helpers.Repos.CreateRecentReposMenu,
|
||||
Description: self.c.Tr.SwitchRepo,
|
||||
},
|
||||
{
|
||||
@ -153,12 +153,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Description: self.c.Tr.LcOpenMenu,
|
||||
Handler: self.handleCreateOptionsMenu,
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: opts.GetKey(opts.Config.Universal.Edit),
|
||||
Handler: self.handleEditConfig,
|
||||
Description: self.c.Tr.EditConfig,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.NextScreenMode),
|
||||
@ -171,30 +165,7 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Handler: self.prevScreenMode,
|
||||
Description: self.c.Tr.LcPrevScreenMode,
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: opts.GetKey(opts.Config.Universal.OpenFile),
|
||||
Handler: self.handleOpenConfig,
|
||||
Description: self.c.Tr.OpenConfig,
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: opts.GetKey(opts.Config.Status.CheckForUpdate),
|
||||
Handler: self.handleCheckForUpdate,
|
||||
Description: self.c.Tr.LcCheckForUpdate,
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: opts.GetKey(opts.Config.Status.RecentRepos),
|
||||
Handler: self.handleCreateRecentReposMenu,
|
||||
Description: self.c.Tr.SwitchRepo,
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: opts.GetKey(opts.Config.Status.AllBranchesLogGraph),
|
||||
Handler: self.handleShowAllBranchLogs,
|
||||
Description: self.c.Tr.LcAllBranchesLogGraph,
|
||||
},
|
||||
|
||||
{
|
||||
ViewName: "files",
|
||||
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
|
||||
@ -309,12 +280,6 @@ func (self *Gui) GetInitialKeybindings() ([]*types.Binding, []*gocui.ViewMouseBi
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.scrollUpSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "status",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.handleStatusClick,
|
||||
},
|
||||
{
|
||||
ViewName: "search",
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
@ -496,7 +461,9 @@ func (gui *Gui) resetKeybindings() error {
|
||||
for _, values := range gui.viewTabMap() {
|
||||
for _, value := range values {
|
||||
viewName := value.ViewName
|
||||
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(gui.windowForView(viewName), tabIndex) }
|
||||
tabClickCallback := func(tabIndex int) error {
|
||||
return gui.onViewTabClick(gui.helpers.Window.WindowForView(viewName), tabIndex)
|
||||
}
|
||||
|
||||
if err := gui.g.SetTabClickBinding(viewName, tabClickCallback); err != nil {
|
||||
return err
|
||||
|
@ -101,11 +101,11 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if err != nil && !gocui.IsUnknownView(err) {
|
||||
return err
|
||||
}
|
||||
view.Visible = gui.getViewNameForWindow(context.GetWindowName()) == context.GetViewName()
|
||||
view.Visible = gui.helpers.Window.GetViewNameForWindow(context.GetWindowName()) == context.GetViewName()
|
||||
}
|
||||
|
||||
if gui.PrevLayout.Information != informationStr {
|
||||
gui.setViewContent(gui.Views.Information, informationStr)
|
||||
gui.c.SetViewContent(gui.Views.Information, informationStr)
|
||||
gui.PrevLayout.Information = informationStr
|
||||
}
|
||||
|
||||
@ -181,7 +181,7 @@ func (gui *Gui) onInitialViewsCreationForRepo() error {
|
||||
}
|
||||
}
|
||||
|
||||
initialContext := gui.currentSideContext()
|
||||
initialContext := gui.c.CurrentSideContext()
|
||||
if err := gui.c.PushContext(initialContext); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -226,15 +226,44 @@ func (gui *Gui) onInitialViewsCreation() error {
|
||||
}
|
||||
|
||||
if gui.showRecentRepos {
|
||||
if err := gui.handleCreateRecentReposMenu(); err != nil {
|
||||
if err := gui.helpers.Repos.CreateRecentReposMenu(); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.showRecentRepos = false
|
||||
}
|
||||
|
||||
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
||||
gui.helpers.Update.CheckForUpdateInBackground()
|
||||
|
||||
gui.waitForIntro.Done()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFocusLayout returns a manager function for when view gain and lose focus
|
||||
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
var previousView *gocui.View
|
||||
return func(g *gocui.Gui) error {
|
||||
newView := gui.g.CurrentView()
|
||||
// for now we don't consider losing focus to a popup panel as actually losing focus
|
||||
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
|
||||
if err := gui.onViewFocusLost(previousView); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
previousView = newView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
|
||||
if oldView == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldView.Highlight = false
|
||||
|
||||
_ = oldView.SetOriginX(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -34,9 +34,6 @@ func (gui *Gui) filesListContext() *context.WorkingTreeContext {
|
||||
return []string{line}
|
||||
})
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.filesRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -48,9 +45,6 @@ func (gui *Gui) branchesListContext() *context.BranchesContext {
|
||||
func(startIdx int, length int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(gui.State.Model.Branches, gui.State.ScreenMode != SCREEN_NORMAL, gui.State.Modes.Diffing.Ref, gui.Tr)
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.branchesRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -62,9 +56,6 @@ func (gui *Gui) remotesListContext() *context.RemotesContext {
|
||||
func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(gui.State.Model.Remotes, gui.State.Modes.Diffing.Ref)
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.remotesRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -76,9 +67,6 @@ func (gui *Gui) remoteBranchesListContext() *context.RemoteBranchesContext {
|
||||
func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteBranchListDisplayStrings(gui.State.Model.RemoteBranches, gui.State.Modes.Diffing.Ref)
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.remoteBranchesRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -86,7 +74,7 @@ func (gui *Gui) remoteBranchesListContext() *context.RemoteBranchesContext {
|
||||
func (gui *Gui) withDiffModeCheck(f func() error) func() error {
|
||||
return func() error {
|
||||
if gui.State.Modes.Diffing.Active() {
|
||||
return gui.renderDiff()
|
||||
return gui.helpers.Diff.RenderDiff()
|
||||
}
|
||||
|
||||
return f()
|
||||
@ -100,9 +88,6 @@ func (gui *Gui) tagsListContext() *context.TagsContext {
|
||||
func(startIdx int, length int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(gui.State.Model.Tags, gui.State.Modes.Diffing.Ref)
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.tagsRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -113,7 +98,7 @@ func (gui *Gui) branchCommitsListContext() *context.LocalCommitsContext {
|
||||
gui.Views.Commits,
|
||||
func(startIdx int, length int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
if gui.currentContext().GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY {
|
||||
if gui.c.CurrentContext().GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := gui.State.Contexts.LocalCommits.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitSha = selectedCommit.Sha
|
||||
@ -138,9 +123,6 @@ func (gui *Gui) branchCommitsListContext() *context.LocalCommitsContext {
|
||||
showYouAreHereLabel,
|
||||
)
|
||||
},
|
||||
OnFocusWrapper(gui.onCommitFocus),
|
||||
gui.withDiffModeCheck(gui.branchCommitsRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -151,7 +133,7 @@ func (gui *Gui) subCommitsListContext() *context.SubCommitsContext {
|
||||
gui.Views.SubCommits,
|
||||
func(startIdx int, length int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
if gui.currentContext().GetKey() == context.SUB_COMMITS_CONTEXT_KEY {
|
||||
if gui.c.CurrentContext().GetKey() == context.SUB_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := gui.State.Contexts.SubCommits.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitSha = selectedCommit.Sha
|
||||
@ -173,9 +155,6 @@ func (gui *Gui) subCommitsListContext() *context.SubCommitsContext {
|
||||
false,
|
||||
)
|
||||
},
|
||||
OnFocusWrapper(gui.onSubCommitFocus),
|
||||
gui.withDiffModeCheck(gui.subCommitsRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -213,9 +192,6 @@ func (gui *Gui) reflogCommitsListContext() *context.ReflogCommitsContext {
|
||||
gui.c.UserConfig.Git.ParseEmoji,
|
||||
)
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.reflogCommitsRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -227,9 +203,6 @@ func (gui *Gui) stashListContext() *context.StashContext {
|
||||
func(startIdx int, length int) [][]string {
|
||||
return presentation.GetStashEntryListDisplayStrings(gui.State.Model.StashEntries, gui.State.Modes.Diffing.Ref)
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.stashRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -248,9 +221,6 @@ func (gui *Gui) commitFilesListContext() *context.CommitFilesContext {
|
||||
return []string{line}
|
||||
})
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.commitFilesRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -262,9 +232,6 @@ func (gui *Gui) submodulesListContext() *context.SubmodulesContext {
|
||||
func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSubmoduleListDisplayStrings(gui.State.Model.Submodules)
|
||||
},
|
||||
nil,
|
||||
gui.withDiffModeCheck(gui.submodulesRenderToMain),
|
||||
nil,
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
@ -276,12 +243,6 @@ func (gui *Gui) suggestionsListContext() *context.SuggestionsContext {
|
||||
func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSuggestionListDisplayStrings(gui.State.Suggestions)
|
||||
},
|
||||
nil,
|
||||
nil,
|
||||
func(types.OnFocusLostOpts) error {
|
||||
gui.deactivateConfirmationPrompt()
|
||||
return nil
|
||||
},
|
||||
gui.c,
|
||||
)
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ func (gui *Gui) moveMainContextPairToTop(pair types.MainContextPair) {
|
||||
}
|
||||
|
||||
func (gui *Gui) moveMainContextToTop(context types.Context) {
|
||||
gui.setWindowContext(context)
|
||||
gui.helpers.Window.SetWindowContext(context)
|
||||
|
||||
view := context.GetView()
|
||||
|
||||
topView := gui.topViewInWindow(context.GetWindowName())
|
||||
topView := gui.helpers.Window.TopViewInWindow(context.GetWindowName())
|
||||
if topView == nil {
|
||||
gui.Log.Error("unexpected: topView is nil")
|
||||
return
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
)
|
||||
|
||||
@ -22,12 +23,12 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
||||
fmt.Sprintf(
|
||||
"%s %s",
|
||||
gui.c.Tr.LcShowingGitDiff,
|
||||
"git diff "+gui.diffStr(),
|
||||
"git diff "+gui.helpers.Diff.DiffStr(),
|
||||
),
|
||||
style.FgMagenta,
|
||||
)
|
||||
},
|
||||
reset: gui.exitDiffMode,
|
||||
reset: gui.helpers.Diff.ExitDiffMode,
|
||||
},
|
||||
{
|
||||
isActive: gui.git.Patch.PatchBuilder.Active,
|
||||
@ -77,7 +78,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
|
||||
description: func() string {
|
||||
workingTreeState := gui.git.Status.WorkingTreeState()
|
||||
return gui.withResetButton(
|
||||
formatWorkingTreeState(workingTreeState), style.FgYellow,
|
||||
presentation.FormatWorkingTreeState(workingTreeState), style.FgYellow,
|
||||
)
|
||||
},
|
||||
reset: gui.helpers.MergeAndRebase.AbortMergeOrRebaseWithConfirm,
|
||||
|
56
pkg/gui/options_map.go
Normal file
56
pkg/gui/options_map.go
Normal file
@ -0,0 +1,56 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type OptionsMapMgr struct {
|
||||
c *types.HelperCommon
|
||||
}
|
||||
|
||||
func (gui *Gui) renderContextOptionsMap(c types.Context) {
|
||||
mgr := OptionsMapMgr{c: gui.c}
|
||||
mgr.renderContextOptionsMap(c)
|
||||
}
|
||||
|
||||
// render the options available for the current context at the bottom of the screen
|
||||
func (self *OptionsMapMgr) renderContextOptionsMap(c types.Context) {
|
||||
optionsMap := c.GetOptionsMap()
|
||||
if optionsMap == nil {
|
||||
optionsMap = self.globalOptionsMap()
|
||||
}
|
||||
|
||||
self.renderOptions(self.optionsMapToString(optionsMap))
|
||||
}
|
||||
|
||||
func (self *OptionsMapMgr) optionsMapToString(optionsMap map[string]string) string {
|
||||
options := maps.MapToSlice(optionsMap, func(key string, description string) string {
|
||||
return key + ": " + description
|
||||
})
|
||||
sort.Strings(options)
|
||||
return strings.Join(options, ", ")
|
||||
}
|
||||
|
||||
func (self *OptionsMapMgr) renderOptions(options string) {
|
||||
self.c.SetViewContent(self.c.Views().Options, options)
|
||||
}
|
||||
|
||||
func (self *OptionsMapMgr) globalOptionsMap() map[string]string {
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
|
||||
return map[string]string{
|
||||
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)): self.c.Tr.LcScroll,
|
||||
fmt.Sprintf("%s %s %s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock), keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): self.c.Tr.LcNavigate,
|
||||
keybindings.Label(keybindingConfig.Universal.Return): self.c.Tr.LcCancel,
|
||||
keybindings.Label(keybindingConfig.Universal.Quit): self.c.Tr.LcQuit,
|
||||
keybindings.Label(keybindingConfig.Universal.OptionMenuAlt1): self.c.Tr.LcMenu,
|
||||
fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): self.c.Tr.LcJump,
|
||||
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)): self.c.Tr.LcScrollLeftRight,
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ func uniqueBindings(bindings []*types.Binding) []*types.Binding {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateOptionsMenu() error {
|
||||
ctx := gui.currentContext()
|
||||
ctx := gui.c.CurrentContext()
|
||||
// Don't show menu while displaying popup.
|
||||
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
|
||||
return nil
|
||||
|
14
pkg/gui/presentation/working_tree.go
Normal file
14
pkg/gui/presentation/working_tree.go
Normal file
@ -0,0 +1,14 @@
|
||||
package presentation
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
|
||||
func FormatWorkingTreeState(rebaseMode enums.RebaseMode) string {
|
||||
switch rebaseMode {
|
||||
case enums.REBASE_MODE_REBASING:
|
||||
return "rebasing"
|
||||
case enums.REBASE_MODE_MERGING:
|
||||
return "merging"
|
||||
default:
|
||||
return "none"
|
||||
}
|
||||
}
|
@ -1,33 +1,10 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
// when a user runs lazygit with the LAZYGIT_NEW_DIR_FILE env variable defined
|
||||
// we will write the current directory to that file on exit so that their
|
||||
// shell can then change to that directory. That means you don't get kicked
|
||||
// back to the directory that you started with.
|
||||
func (gui *Gui) recordCurrentDirectory() error {
|
||||
// determine current directory, set it in LAZYGIT_NEW_DIR_FILE
|
||||
dirName, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.recordDirectory(dirName)
|
||||
}
|
||||
|
||||
func (gui *Gui) recordDirectory(dirName string) error {
|
||||
newDirFilePath := os.Getenv("LAZYGIT_NEW_DIR_FILE")
|
||||
if newDirFilePath == "" {
|
||||
return nil
|
||||
}
|
||||
return gui.os.CreateFileWithContent(newDirFilePath, dirName)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleQuitWithoutChangingDirectory() error {
|
||||
gui.RetainOriginalDir = true
|
||||
return gui.quit()
|
||||
@ -39,7 +16,7 @@ func (gui *Gui) handleQuit() error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleTopLevelReturn() error {
|
||||
currentContext := gui.currentContext()
|
||||
currentContext := gui.c.CurrentContext()
|
||||
|
||||
parentContext, hasParent := currentContext.GetParentContext()
|
||||
if hasParent && currentContext != nil && parentContext != nil {
|
||||
@ -53,11 +30,9 @@ func (gui *Gui) handleTopLevelReturn() error {
|
||||
}
|
||||
}
|
||||
|
||||
repoPathStack := gui.RepoPathStack
|
||||
repoPathStack := gui.c.State().GetRepoPathStack()
|
||||
if !repoPathStack.IsEmpty() {
|
||||
path := repoPathStack.Pop()
|
||||
|
||||
return gui.dispatchSwitchToRepo(path, true)
|
||||
return gui.helpers.Repos.DispatchSwitchToRepo(repoPathStack.Pop(), true)
|
||||
}
|
||||
|
||||
if gui.c.UserConfig.QuitOnTopLevelReturn {
|
||||
@ -68,7 +43,7 @@ func (gui *Gui) handleTopLevelReturn() error {
|
||||
}
|
||||
|
||||
func (gui *Gui) quit() error {
|
||||
if gui.State.Updating {
|
||||
if gui.c.State().GetUpdating() {
|
||||
return gui.createUpdateQuitConfirmation()
|
||||
}
|
||||
|
||||
@ -84,3 +59,13 @@ func (gui *Gui) quit() error {
|
||||
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
|
||||
func (gui *Gui) createUpdateQuitConfirmation() error {
|
||||
return gui.c.Confirm(types.ConfirmOpts{
|
||||
Title: gui.Tr.ConfirmQuitDuringUpdateTitle,
|
||||
Prompt: gui.Tr.ConfirmQuitDuringUpdate,
|
||||
HandleConfirm: func() error {
|
||||
return gocui.ErrQuit
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -1,160 +1,10 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) getCurrentBranch(path string) string {
|
||||
readHeadFile := func(path string) (string, error) {
|
||||
headFile, err := os.ReadFile(filepath.Join(path, "HEAD"))
|
||||
if err == nil {
|
||||
content := strings.TrimSpace(string(headFile))
|
||||
refsPrefix := "ref: refs/heads/"
|
||||
var branchDisplay string
|
||||
if strings.HasPrefix(content, refsPrefix) {
|
||||
// is a branch
|
||||
branchDisplay = strings.TrimPrefix(content, refsPrefix)
|
||||
} else {
|
||||
// detached HEAD state, displaying short SHA
|
||||
branchDisplay = utils.ShortSha(content)
|
||||
}
|
||||
return branchDisplay, nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
gitDirPath := filepath.Join(path, ".git")
|
||||
|
||||
if gitDir, err := os.Stat(gitDirPath); err == nil {
|
||||
if gitDir.IsDir() {
|
||||
// ordinary repo
|
||||
if branch, err := readHeadFile(gitDirPath); err == nil {
|
||||
return branch
|
||||
}
|
||||
} else {
|
||||
// worktree
|
||||
if worktreeGitDir, err := os.ReadFile(gitDirPath); err == nil {
|
||||
content := strings.TrimSpace(string(worktreeGitDir))
|
||||
worktreePath := strings.TrimPrefix(content, "gitdir: ")
|
||||
if branch, err := readHeadFile(worktreePath); err == nil {
|
||||
return branch
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return gui.c.Tr.LcBranchUnknown
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateRecentReposMenu() error {
|
||||
// we'll show an empty panel if there are no recent repos
|
||||
recentRepoPaths := []string{}
|
||||
if len(gui.c.GetAppState().RecentRepos) > 0 {
|
||||
// we skip the first one because we're currently in it
|
||||
recentRepoPaths = gui.c.GetAppState().RecentRepos[1:]
|
||||
}
|
||||
|
||||
currentBranches := sync.Map{}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(recentRepoPaths))
|
||||
|
||||
for _, path := range recentRepoPaths {
|
||||
go func(path string) {
|
||||
defer wg.Done()
|
||||
currentBranches.Store(path, gui.getCurrentBranch(path))
|
||||
}(path)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
menuItems := slices.Map(recentRepoPaths, func(path string) *types.MenuItem {
|
||||
branchName, _ := currentBranches.Load(path)
|
||||
if icons.IsIconEnabled() {
|
||||
branchName = icons.BRANCH_ICON + " " + fmt.Sprintf("%v", branchName)
|
||||
}
|
||||
|
||||
return &types.MenuItem{
|
||||
LabelColumns: []string{
|
||||
filepath.Base(path),
|
||||
style.FgCyan.Sprint(branchName),
|
||||
style.FgMagenta.Sprint(path),
|
||||
},
|
||||
OnPress: func() error {
|
||||
// if we were in a submodule, we want to forget about that stack of repos
|
||||
// so that hitting escape in the new repo does nothing
|
||||
gui.RepoPathStack.Clear()
|
||||
return gui.dispatchSwitchToRepo(path, false)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return gui.c.Menu(types.CreateMenuOptions{Title: gui.c.Tr.RecentRepos, Items: menuItems})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleShowAllBranchLogs() error {
|
||||
cmdObj := gui.git.Branch.AllBranchesLogCmdObj()
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
|
||||
env.UnsetGitDirEnvs()
|
||||
originalPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return gui.c.ErrorMsg(gui.c.Tr.ErrRepositoryMovedOrDeleted)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := commands.VerifyInGitRepo(gui.os); err != nil {
|
||||
if err := os.Chdir(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.recordCurrentDirectory(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// these two mutexes are used by our background goroutines (triggered via `gui.goEvery`. We don't want to
|
||||
// switch to a repo while one of these goroutines is in the process of updating something
|
||||
gui.Mutexes.SyncMutex.Lock()
|
||||
defer gui.Mutexes.SyncMutex.Unlock()
|
||||
|
||||
gui.Mutexes.RefreshingFilesMutex.Lock()
|
||||
defer gui.Mutexes.RefreshingFilesMutex.Unlock()
|
||||
|
||||
return gui.onNewRepo(appTypes.StartArgs{}, reuse)
|
||||
}
|
||||
|
||||
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
||||
// so that we can open the same repo via the 'recent repos' menu
|
||||
func (gui *Gui) updateRecentRepoList() error {
|
||||
|
@ -1,746 +1,9 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"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/patch_exploring"
|
||||
"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",
|
||||
types.STAGING: "staging",
|
||||
types.MERGE_CONFLICTS: "mergeConflicts",
|
||||
}
|
||||
|
||||
return slices.Map(scopes, func(scope types.RefreshableView) string {
|
||||
return scopeNameMap[scope]
|
||||
})
|
||||
}
|
||||
|
||||
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 (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 scopeSet *set.Set[types.RefreshableView]
|
||||
if len(options.Scope) == 0 {
|
||||
// not refreshing staging/patch-building unless explicitly requested because we only need
|
||||
// to refresh those while focused.
|
||||
scopeSet = set.NewFromSlice([]types.RefreshableView{
|
||||
types.COMMITS,
|
||||
types.BRANCHES,
|
||||
types.FILES,
|
||||
types.STASH,
|
||||
types.REFLOG,
|
||||
types.TAGS,
|
||||
types.REMOTES,
|
||||
types.STATUS,
|
||||
types.BISECT_INFO,
|
||||
})
|
||||
} else {
|
||||
scopeSet = set.NewFromSlice(options.Scope)
|
||||
}
|
||||
|
||||
refresh := func(f func()) {
|
||||
wg.Add(1)
|
||||
func() {
|
||||
if options.Mode == types.ASYNC {
|
||||
go utils.Safe(f)
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
|
||||
refresh(gui.refreshCommits)
|
||||
} else if scopeSet.Includes(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() })
|
||||
}
|
||||
|
||||
// reason we're not doing this if the COMMITS type is included is that if the COMMITS type _is_ included we will refresh the commit files context anyway
|
||||
if scopeSet.Includes(types.COMMIT_FILES) && !scopeSet.Includes(types.COMMITS) {
|
||||
refresh(func() { _ = gui.refreshCommitFilesContext() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.FILES) || scopeSet.Includes(types.SUBMODULES) {
|
||||
refresh(func() { _ = gui.refreshFilesAndSubmodules() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STASH) {
|
||||
refresh(func() { _ = gui.refreshStashEntries() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.TAGS) {
|
||||
refresh(func() { _ = gui.refreshTags() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.REMOTES) {
|
||||
refresh(func() { _ = gui.refreshRemotes() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STAGING) {
|
||||
refresh(func() { _ = gui.refreshStagingPanel(types.OnFocusOpts{}) })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.PATCH_BUILDING) {
|
||||
refresh(func() { _ = gui.refreshPatchBuildingPanel(types.OnFocusOpts{}) })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.MERGE_CONFLICTS) || scopeSet.Includes(types.FILES) {
|
||||
refresh(func() { _ = gui.refreshMergeState() })
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
gui.refreshStatus()
|
||||
|
||||
if options.Then != nil {
|
||||
options.Then()
|
||||
}
|
||||
}
|
||||
|
||||
if options.Mode == types.BLOCK_UI {
|
||||
gui.c.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.LOCAL_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.Contexts.CommitFiles.SetRef(commit)
|
||||
gui.State.Contexts.CommitFiles.SetTitleRef(commit.RefName())
|
||||
_ = gui.refreshCommitFilesContext()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommitsWithLimit() error {
|
||||
gui.Mutexes.LocalCommitsMutex.Lock()
|
||||
defer gui.Mutexes.LocalCommitsMutex.Unlock()
|
||||
|
||||
commits, err := gui.git.Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: gui.State.Contexts.LocalCommits.GetLimitCommits(),
|
||||
FilterPath: gui.State.Modes.Filtering.GetPath(),
|
||||
IncludeRebaseCommits: true,
|
||||
RefName: gui.refForLog(),
|
||||
All: gui.State.Contexts.LocalCommits.GetShowWholeGitGraph(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Model.Commits = commits
|
||||
gui.State.Model.WorkingTreeStateAtLastCommitRefresh = gui.git.Status.WorkingTreeState()
|
||||
|
||||
return gui.c.PostRefreshUpdate(gui.State.Contexts.LocalCommits)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommitFilesContext() error {
|
||||
ref := gui.State.Contexts.CommitFiles.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
files, err := gui.git.Loaders.CommitFileLoader.GetFilesInDiff(from, to, reverse)
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
gui.State.Model.CommitFiles = files
|
||||
gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.SetTree()
|
||||
|
||||
return gui.c.PostRefreshUpdate(gui.State.Contexts.CommitFiles)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshRebaseCommits() error {
|
||||
gui.Mutexes.LocalCommitsMutex.Lock()
|
||||
defer gui.Mutexes.LocalCommitsMutex.Unlock()
|
||||
|
||||
updatedCommits, err := gui.git.Loaders.CommitLoader.MergeRebasingCommits(gui.State.Model.Commits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Model.Commits = updatedCommits
|
||||
gui.State.Model.WorkingTreeStateAtLastCommitRefresh = gui.git.Status.WorkingTreeState()
|
||||
|
||||
return gui.c.PostRefreshUpdate(gui.State.Contexts.LocalCommits)
|
||||
}
|
||||
|
||||
func (self *Gui) refreshTags() error {
|
||||
tags, err := self.git.Loaders.TagLoader.GetTags()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.State.Model.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.Model.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.Model.Branches array to pick the current branch from
|
||||
func (gui *Gui) refreshBranches() {
|
||||
reflogCommits := gui.State.Model.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.ReflogCommitLoader.GetReflogCommits(nil, "")
|
||||
if err != nil {
|
||||
gui.c.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
branches, err := gui.git.Loaders.BranchLoader.Load(reflogCommits)
|
||||
if err != nil {
|
||||
_ = gui.c.Error(err)
|
||||
}
|
||||
|
||||
gui.State.Model.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()
|
||||
}()
|
||||
|
||||
if err := gui.refreshStateSubmoduleConfigs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.c.OnUIThread(func() error {
|
||||
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil {
|
||||
gui.c.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Files); err != nil {
|
||||
gui.c.Log.Error(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMergeState() error {
|
||||
gui.State.Contexts.MergeConflicts.GetMutex().Lock()
|
||||
defer gui.State.Contexts.MergeConflicts.GetMutex().Unlock()
|
||||
|
||||
if gui.currentContext().GetKey() != context.MERGE_CONFLICTS_CONTEXT_KEY {
|
||||
return nil
|
||||
}
|
||||
|
||||
hasConflicts, err := gui.helpers.MergeConflicts.SetConflictsAndRender(gui.State.Contexts.MergeConflicts.GetState().GetPath(), true)
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
|
||||
if !hasConflicts {
|
||||
return gui.helpers.MergeConflicts.EscapeMerge()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshStateFiles() error {
|
||||
state := gui.State
|
||||
|
||||
fileTreeViewModel := state.Contexts.Files.FileTreeViewModel
|
||||
|
||||
// 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 gui.State.Model.Files {
|
||||
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.FileLoader.
|
||||
GetStatusFiles(git_commands.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.c.OnUIThread(func() error { return gui.helpers.MergeAndRebase.PromptToContinueRebase() })
|
||||
}
|
||||
|
||||
fileTreeViewModel.RWMutex.Lock()
|
||||
|
||||
// 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 fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayConflicted)
|
||||
}
|
||||
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.SetFilter(filetree.DisplayAll)
|
||||
}
|
||||
|
||||
state.Model.Files = files
|
||||
fileTreeViewModel.SetTree()
|
||||
fileTreeViewModel.RWMutex.Unlock()
|
||||
|
||||
if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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.Model.ReflogCommits) > 0 {
|
||||
lastReflogCommit = state.Model.ReflogCommits[0]
|
||||
}
|
||||
|
||||
refresh := func(stateCommits *[]*models.Commit, filterPath string) error {
|
||||
commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommitLoader.
|
||||
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.Model.ReflogCommits, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
if err := refresh(&state.Model.FilteredReflogCommits, state.Modes.Filtering.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
state.Model.FilteredReflogCommits = state.Model.ReflogCommits
|
||||
}
|
||||
|
||||
return gui.c.PostRefreshUpdate(gui.State.Contexts.ReflogCommits)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshRemotes() error {
|
||||
prevSelectedRemote := gui.State.Contexts.Remotes.GetSelected()
|
||||
|
||||
remotes, err := gui.git.Loaders.RemoteLoader.GetRemotes()
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
|
||||
gui.State.Model.Remotes = remotes
|
||||
|
||||
// we need to ensure our selected remote branches aren't now outdated
|
||||
if prevSelectedRemote != nil && gui.State.Model.RemoteBranches != nil {
|
||||
// find remote now
|
||||
for _, remote := range remotes {
|
||||
if remote.Name == prevSelectedRemote.Name {
|
||||
gui.State.Model.RemoteBranches = remote.Branches
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Remotes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.c.PostRefreshUpdate(gui.State.Contexts.RemoteBranches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshStashEntries() error {
|
||||
gui.State.Model.StashEntries = gui.git.Loaders.StashLoader.
|
||||
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.helpers.Refs.GetCheckedOutRef()
|
||||
if currentBranch == nil {
|
||||
// need to wait for branches to refresh
|
||||
return
|
||||
}
|
||||
status := ""
|
||||
|
||||
if currentBranch.IsRealBranch() {
|
||||
status += presentation.ColoredBranchStatus(currentBranch, gui.Tr) + " "
|
||||
}
|
||||
|
||||
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 (gui *Gui) refreshStagingPanel(focusOpts types.OnFocusOpts) error {
|
||||
secondaryFocused := gui.secondaryStagingFocused()
|
||||
|
||||
mainSelectedLineIdx := -1
|
||||
secondarySelectedLineIdx := -1
|
||||
if focusOpts.ClickedViewLineIdx > 0 {
|
||||
if secondaryFocused {
|
||||
secondarySelectedLineIdx = focusOpts.ClickedViewLineIdx
|
||||
} else {
|
||||
mainSelectedLineIdx = focusOpts.ClickedViewLineIdx
|
||||
}
|
||||
}
|
||||
|
||||
mainContext := gui.State.Contexts.Staging
|
||||
secondaryContext := gui.State.Contexts.StagingSecondary
|
||||
|
||||
file := gui.getSelectedFile()
|
||||
if file == nil || (!file.HasUnstagedChanges && !file.HasStagedChanges) {
|
||||
return gui.handleStagingEscape()
|
||||
}
|
||||
|
||||
mainDiff := gui.git.WorkingTree.WorktreeFileDiff(file, true, false, false)
|
||||
secondaryDiff := gui.git.WorkingTree.WorktreeFileDiff(file, true, true, false)
|
||||
|
||||
// grabbing locks here and releasing before we finish the function
|
||||
// because pushing say the secondary context could mean entering this function
|
||||
// again, and we don't want to have a deadlock
|
||||
mainContext.GetMutex().Lock()
|
||||
secondaryContext.GetMutex().Lock()
|
||||
|
||||
mainContext.SetState(
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetState(), gui.Log),
|
||||
)
|
||||
|
||||
secondaryContext.SetState(
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetState(), gui.Log),
|
||||
)
|
||||
|
||||
mainState := mainContext.GetState()
|
||||
secondaryState := secondaryContext.GetState()
|
||||
|
||||
mainContent := mainContext.GetContentToRender(!secondaryFocused)
|
||||
secondaryContent := secondaryContext.GetContentToRender(secondaryFocused)
|
||||
|
||||
mainContext.GetMutex().Unlock()
|
||||
secondaryContext.GetMutex().Unlock()
|
||||
|
||||
if mainState == nil && secondaryState == nil {
|
||||
return gui.handleStagingEscape()
|
||||
}
|
||||
|
||||
if mainState == nil && !secondaryFocused {
|
||||
return gui.c.PushContext(secondaryContext, focusOpts)
|
||||
}
|
||||
|
||||
if secondaryState == nil && secondaryFocused {
|
||||
return gui.c.PushContext(mainContext, focusOpts)
|
||||
}
|
||||
|
||||
if secondaryFocused {
|
||||
gui.State.Contexts.StagingSecondary.FocusSelection()
|
||||
} else {
|
||||
gui.State.Contexts.Staging.FocusSelection()
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Staging,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: gui.Tr.UnstagedChanges,
|
||||
},
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryContent),
|
||||
Title: gui.Tr.StagedChanges,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStagingEscape() error {
|
||||
return gui.c.PushContext(gui.State.Contexts.Files)
|
||||
}
|
||||
|
||||
func (gui *Gui) secondaryStagingFocused() bool {
|
||||
return gui.currentStaticContext().GetKey() == gui.State.Contexts.StagingSecondary.GetKey()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshPatchBuildingPanel(opts types.OnFocusOpts) error {
|
||||
selectedLineIdx := -1
|
||||
if opts.ClickedWindowName == "main" {
|
||||
selectedLineIdx = opts.ClickedViewLineIdx
|
||||
}
|
||||
|
||||
if !gui.git.Patch.PatchBuilder.Active() {
|
||||
return gui.helpers.PatchBuilding.Escape()
|
||||
}
|
||||
|
||||
// get diff from commit file that's currently selected
|
||||
path := gui.State.Contexts.CommitFiles.GetSelectedPath()
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := gui.State.Contexts.CommitFiles.CommitFileTreeViewModel.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
diff, err := gui.git.WorkingTree.ShowFileDiff(from, to, reverse, path, true,
|
||||
gui.IgnoreWhitespaceInDiffView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secondaryDiff := gui.git.Patch.PatchBuilder.RenderPatchForFile(path, false, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
context := gui.State.Contexts.CustomPatchBuilder
|
||||
|
||||
oldState := context.GetState()
|
||||
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, oldState, gui.Log)
|
||||
context.SetState(state)
|
||||
if state == nil {
|
||||
return gui.helpers.PatchBuilding.Escape()
|
||||
}
|
||||
|
||||
gui.State.Contexts.CustomPatchBuilder.FocusSelection()
|
||||
|
||||
mainContent := context.GetContentToRender(true)
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().PatchBuilding,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(mainContent),
|
||||
Title: gui.Tr.Patch,
|
||||
},
|
||||
Secondary: &types.ViewUpdateOpts{
|
||||
Task: types.NewRenderStringWithoutScrollTask(secondaryDiff),
|
||||
Title: gui.Tr.CustomPatch,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMergePanel(isFocused bool) error {
|
||||
content := gui.State.Contexts.MergeConflicts.GetContentToRender(isFocused)
|
||||
|
||||
var task types.UpdateTask
|
||||
if gui.State.Contexts.MergeConflicts.IsUserScrolling() {
|
||||
task = types.NewRenderStringWithoutScrollTask(content)
|
||||
} else {
|
||||
originY := gui.State.Contexts.MergeConflicts.GetOriginY()
|
||||
task = types.NewRenderStringWithScrollTask(content, 0, originY)
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().MergeConflicts,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshSubCommitsWithLimit() error {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
defer gui.Mutexes.SubCommitsMutex.Unlock()
|
||||
|
||||
context := gui.State.Contexts.SubCommits
|
||||
|
||||
commits, err := gui.git.Loaders.CommitLoader.GetCommits(
|
||||
git_commands.GetCommitsOptions{
|
||||
Limit: context.GetLimitCommits(),
|
||||
FilterPath: gui.State.Modes.Filtering.GetPath(),
|
||||
IncludeRebaseCommits: false,
|
||||
RefName: context.GetRef().FullRefName(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Model.SubCommits = commits
|
||||
|
||||
return gui.c.PostRefreshUpdate(gui.State.Contexts.SubCommits)
|
||||
return gui.helpers.Refresh.Refresh(options)
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) remoteBranchesRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
remoteBranch := gui.State.Contexts.RemoteBranches.GetSelected()
|
||||
if remoteBranch == nil {
|
||||
task = types.NewRenderStringTask("No branches for this remote")
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(remoteBranch.FullRefName())
|
||||
task = types.NewRunCommandTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Remote Branch",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) remotesRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
remote := gui.State.Contexts.Remotes.GetSelected()
|
||||
if remote == nil {
|
||||
task = types.NewRenderStringTask("No remotes")
|
||||
} else {
|
||||
task = types.NewRenderStringTask(fmt.Sprintf("%s\nUrls:\n%s", style.FgGreen.Sprint(remote.Name), strings.Join(remote.Urls, "\n")))
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Remote",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
@ -48,7 +48,7 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
|
||||
|
||||
return func(y int, index int, total int) error {
|
||||
if total == 0 {
|
||||
return gui.renderString(
|
||||
gui.c.SetViewContent(
|
||||
gui.Views.Search,
|
||||
fmt.Sprintf(
|
||||
"no matches for '%s' %s",
|
||||
@ -56,8 +56,9 @@ func (gui *Gui) onSelectItemWrapper(innerFunc func(int) error) func(int, int, in
|
||||
theme.OptionsFgColor.Sprintf("%s: exit search mode", keybindings.Label(keybindingConfig.Universal.Return)),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
_ = gui.renderString(
|
||||
gui.c.SetViewContent(
|
||||
gui.Views.Search,
|
||||
fmt.Sprintf(
|
||||
"matches for '%s' (%d of %d) %s",
|
||||
|
@ -2,7 +2,7 @@ package gui
|
||||
|
||||
func (gui *Gui) nextSideWindow() error {
|
||||
windows := gui.getCyclableWindows()
|
||||
currentWindow := gui.currentWindow()
|
||||
currentWindow := gui.helpers.Window.CurrentWindow()
|
||||
var newWindow string
|
||||
if currentWindow == "" || currentWindow == windows[len(windows)-1] {
|
||||
newWindow = windows[0]
|
||||
@ -17,18 +17,16 @@ func (gui *Gui) nextSideWindow() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := gui.resetOrigin(gui.Views.Main); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.c.ResetViewOrigin(gui.Views.Main)
|
||||
|
||||
context := gui.getContextForWindow(newWindow)
|
||||
context := gui.helpers.Window.GetContextForWindow(newWindow)
|
||||
|
||||
return gui.c.PushContext(context)
|
||||
}
|
||||
|
||||
func (gui *Gui) previousSideWindow() error {
|
||||
windows := gui.getCyclableWindows()
|
||||
currentWindow := gui.currentWindow()
|
||||
currentWindow := gui.helpers.Window.CurrentWindow()
|
||||
var newWindow string
|
||||
if currentWindow == "" || currentWindow == windows[0] {
|
||||
newWindow = windows[len(windows)-1]
|
||||
@ -43,19 +41,21 @@ func (gui *Gui) previousSideWindow() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := gui.resetOrigin(gui.Views.Main); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.c.ResetViewOrigin(gui.Views.Main)
|
||||
|
||||
context := gui.getContextForWindow(newWindow)
|
||||
context := gui.helpers.Window.GetContextForWindow(newWindow)
|
||||
|
||||
return gui.c.PushContext(context)
|
||||
}
|
||||
|
||||
func (gui *Gui) goToSideWindow(window string) func() error {
|
||||
return func() error {
|
||||
context := gui.getContextForWindow(window)
|
||||
context := gui.helpers.Window.GetContextForWindow(window)
|
||||
|
||||
return gui.c.PushContext(context)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getCyclableWindows() []string {
|
||||
return []string{"status", "files", "branches", "commits", "stash"}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
)
|
||||
|
||||
func (gui *Gui) startSnake() {
|
||||
view := gui.Views.Snake
|
||||
|
||||
game := snake.NewGame(view.Width(), view.Height(), gui.renderSnakeGame, gui.c.LogAction)
|
||||
gui.snakeGame = game
|
||||
game.Start()
|
||||
}
|
||||
|
||||
func (gui *Gui) renderSnakeGame(cells [][]snake.CellType, alive bool) {
|
||||
view := gui.Views.Snake
|
||||
|
||||
if !alive {
|
||||
_ = gui.c.ErrorMsg(gui.Tr.YouDied)
|
||||
return
|
||||
}
|
||||
|
||||
output := drawSnakeGame(cells)
|
||||
|
||||
view.Clear()
|
||||
fmt.Fprint(view, output)
|
||||
gui.c.Render()
|
||||
}
|
||||
|
||||
func drawSnakeGame(cells [][]snake.CellType) string {
|
||||
writer := &strings.Builder{}
|
||||
|
||||
for i, row := range cells {
|
||||
for _, cell := range row {
|
||||
switch cell {
|
||||
case snake.None:
|
||||
writer.WriteString(" ")
|
||||
case snake.Snake:
|
||||
writer.WriteString("█")
|
||||
case snake.Food:
|
||||
writer.WriteString(style.FgMagenta.Sprint("█"))
|
||||
}
|
||||
}
|
||||
|
||||
if i < len(cells) {
|
||||
writer.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
output := writer.String()
|
||||
return output
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) stashRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
stashEntry := gui.State.Contexts.Stash.GetSelected()
|
||||
if stashEntry == nil {
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoStashEntries)
|
||||
} else {
|
||||
task = types.NewRunPtyTask(gui.git.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Stash",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
"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 runeCount(str string) int {
|
||||
return len([]rune(str))
|
||||
}
|
||||
|
||||
func cursorInSubstring(cx int, prefix string, substring string) bool {
|
||||
return cx >= runeCount(prefix) && cx < runeCount(prefix+substring)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckForUpdate() error {
|
||||
return gui.c.WithWaitingStatus(gui.c.Tr.CheckingForUpdates, func() error {
|
||||
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStatusClick() error {
|
||||
// TODO: move into some abstraction (status is currently not a listViewContext where a lot of this code lives)
|
||||
currentBranch := gui.helpers.Refs.GetCheckedOutRef()
|
||||
if currentBranch == nil {
|
||||
// need to wait for branches to refresh
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := gui.c.PushContext(gui.State.Contexts.Status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cx, _ := gui.Views.Status.Cursor()
|
||||
upstreamStatus := presentation.BranchStatus(currentBranch, gui.Tr)
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
workingTreeState := gui.git.Status.WorkingTreeState()
|
||||
switch workingTreeState {
|
||||
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
|
||||
workingTreeStatus := fmt.Sprintf("(%s)", formatWorkingTreeState(workingTreeState))
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) {
|
||||
return gui.helpers.MergeAndRebase.CreateRebaseOptionsMenu()
|
||||
}
|
||||
if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
|
||||
return gui.handleCreateRecentReposMenu()
|
||||
}
|
||||
default:
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", repoName) {
|
||||
return gui.handleCreateRecentReposMenu()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatWorkingTreeState(rebaseMode enums.RebaseMode) string {
|
||||
switch rebaseMode {
|
||||
case enums.REBASE_MODE_REBASING:
|
||||
return "rebasing"
|
||||
case enums.REBASE_MODE_MERGING:
|
||||
return "merging"
|
||||
default:
|
||||
return "none"
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) statusRenderToMain() error {
|
||||
dashboardString := strings.Join(
|
||||
[]string{
|
||||
lazygitTitle(),
|
||||
"Copyright 2022 Jesse Duffield",
|
||||
fmt.Sprintf("Keybindings: %s", constants.Links.Docs.Keybindings),
|
||||
fmt.Sprintf("Config Options: %s", constants.Links.Docs.Config),
|
||||
fmt.Sprintf("Tutorial: %s", constants.Links.Docs.Tutorial),
|
||||
fmt.Sprintf("Raise an Issue: %s", constants.Links.Issues),
|
||||
fmt.Sprintf("Release Notes: %s", constants.Links.Releases),
|
||||
style.FgMagenta.Sprintf("Become a sponsor: %s", constants.Links.Donate), // caffeine ain't free
|
||||
}, "\n\n")
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.StatusTitle,
|
||||
Task: types.NewRenderStringTask(dashboardString),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) askForConfigFile(action func(file string) error) error {
|
||||
confPaths := gui.Config.GetUserConfigPaths()
|
||||
switch len(confPaths) {
|
||||
case 0:
|
||||
return errors.New(gui.c.Tr.NoConfigFileFoundErr)
|
||||
case 1:
|
||||
return action(confPaths[0])
|
||||
default:
|
||||
menuItems := slices.Map(confPaths, func(path string) *types.MenuItem {
|
||||
return &types.MenuItem{
|
||||
Label: path,
|
||||
OnPress: func() error {
|
||||
return action(path)
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
return gui.c.Menu(types.CreateMenuOptions{
|
||||
Title: gui.c.Tr.SelectConfigFile,
|
||||
Items: menuItems,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenConfig() error {
|
||||
return gui.askForConfigFile(gui.helpers.Files.OpenFile)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEditConfig() error {
|
||||
return gui.askForConfigFile(gui.helpers.Files.EditFile)
|
||||
}
|
||||
|
||||
func lazygitTitle() string {
|
||||
return `
|
||||
_ _ _
|
||||
| | (_) |
|
||||
| | __ _ _____ _ __ _ _| |_
|
||||
| |/ _` + "`" + ` |_ / | | |/ _` + "`" + ` | | __|
|
||||
| | (_| |/ /| |_| | (_| | | |_
|
||||
|_|\__,_/___|\__, |\__, |_|\__|
|
||||
__/ | __/ |
|
||||
|___/ |___/ `
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) onSubCommitFocus() error {
|
||||
context := gui.State.Contexts.SubCommits
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
if err := gui.refreshSubCommitsWithLimit(); err != nil {
|
||||
_ = gui.c.Error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) subCommitsRenderToMain() error {
|
||||
commit := gui.State.Contexts.SubCommits.GetSelected()
|
||||
var task types.UpdateTask
|
||||
if commit == nil {
|
||||
task = types.NewRenderStringTask("No commits")
|
||||
} else {
|
||||
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath(),
|
||||
gui.IgnoreWhitespaceInDiffView)
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Commit",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) submodulesRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
submodule := gui.State.Contexts.Submodules.GetSelected()
|
||||
if submodule == nil {
|
||||
task = types.NewRenderStringTask("No submodules")
|
||||
} else {
|
||||
prefix := fmt.Sprintf(
|
||||
"Name: %s\nPath: %s\nUrl: %s\n\n",
|
||||
style.FgGreen.Sprint(submodule.Name),
|
||||
style.FgYellow.Sprint(submodule.Path),
|
||||
style.FgCyan.Sprint(submodule.Url),
|
||||
)
|
||||
|
||||
file := gui.helpers.WorkingTree.FileForSubmodule(submodule)
|
||||
if file == nil {
|
||||
task = types.NewRenderStringTask(prefix)
|
||||
} else {
|
||||
cmdObj := gui.git.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.IgnoreWhitespaceInDiffView)
|
||||
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
||||
}
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Submodule",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) enterSubmodule(submodule *models.SubmoduleConfig) error {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.RepoPathStack.Push(wd)
|
||||
|
||||
return gui.dispatchSwitchToRepo(submodule.Path, true)
|
||||
}
|
@ -21,6 +21,6 @@ func (gui *Gui) getSelectedSuggestion() *types.Suggestion {
|
||||
func (gui *Gui) setSuggestions(suggestions []*types.Suggestion) {
|
||||
gui.State.Suggestions = suggestions
|
||||
gui.State.Contexts.Suggestions.SetSelectedLineIdx(0)
|
||||
_ = gui.resetOrigin(gui.Views.Suggestions)
|
||||
gui.c.ResetViewOrigin(gui.Views.Suggestions)
|
||||
_ = gui.State.Contexts.Suggestions.HandleRender()
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) tagsRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
tag := gui.State.Contexts.Tags.GetSelected()
|
||||
if tag == nil {
|
||||
task = types.NewRenderStringTask("No tags")
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(tag.FullRefName())
|
||||
task = types.NewRunCommandTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: "Tag",
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
@ -49,7 +49,7 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
gui.setViewContent(view, str)
|
||||
gui.c.SetViewContent(view, str)
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX in
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
gui.setViewContent(view, str)
|
||||
gui.c.SetViewContent(view, str)
|
||||
_ = view.SetOrigin(originX, originY)
|
||||
return nil
|
||||
}
|
||||
@ -82,7 +82,9 @@ func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) e
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
return gui.renderString(view, str)
|
||||
gui.c.ResetViewOrigin(view)
|
||||
gui.c.SetViewContent(view, str)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := manager.NewTask(f, key); err != nil {
|
||||
|
@ -1,12 +1,15 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"gopkg.in/ozeidan/fuzzy-patricia.v3/patricia"
|
||||
)
|
||||
@ -27,6 +30,12 @@ type IGuiCommon interface {
|
||||
// e.g. expanding or collapsing a folder in a file view. Calling 'Refresh' in this
|
||||
// case would be overkill, although refresh will internally call 'PostRefreshUpdate'
|
||||
PostRefreshUpdate(Context) error
|
||||
|
||||
// renders string to a view without resetting its origin
|
||||
SetViewContent(view *gocui.View, content string)
|
||||
// resets cursor and origin of view. Often used before calling SetViewContent
|
||||
ResetViewOrigin(view *gocui.View)
|
||||
|
||||
// this just re-renders the screen
|
||||
Render()
|
||||
// allows rendering to main views (i.e. the ones to the right of the side panel)
|
||||
@ -42,12 +51,15 @@ type IGuiCommon interface {
|
||||
|
||||
PushContext(context Context, opts ...OnFocusOpts) error
|
||||
PopContext() error
|
||||
ReplaceContext(context Context) error
|
||||
CurrentContext() Context
|
||||
CurrentStaticContext() Context
|
||||
CurrentSideContext() Context
|
||||
IsCurrentContext(Context) bool
|
||||
// enters search mode for the current view
|
||||
OpenSearch()
|
||||
|
||||
GetConfig() config.AppConfigurer
|
||||
GetAppState() *config.AppState
|
||||
SaveAppState() error
|
||||
|
||||
@ -55,6 +67,21 @@ type IGuiCommon interface {
|
||||
// Only necessary to call if you're not already on the UI thread i.e. you're inside a goroutine.
|
||||
// All controller handlers are executed on the UI thread.
|
||||
OnUIThread(f func() error)
|
||||
|
||||
// returns the gocui Gui struct. There is a good chance you don't actually want to use
|
||||
// this struct and instead want to use another method above
|
||||
GocuiGui() *gocui.Gui
|
||||
|
||||
Views() Views
|
||||
|
||||
Git() *commands.GitCommand
|
||||
OS() *oscommands.OSCommand
|
||||
Model() *Model
|
||||
Modes() *Modes
|
||||
|
||||
Mutexes() Mutexes
|
||||
|
||||
State() IStateAccessor
|
||||
}
|
||||
|
||||
type IPopupHandler interface {
|
||||
@ -176,3 +203,36 @@ type Mutexes struct {
|
||||
PopupMutex *deadlock.Mutex
|
||||
PtyMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
type IStateAccessor interface {
|
||||
GetIgnoreWhitespaceInDiffView() bool
|
||||
SetIgnoreWhitespaceInDiffView(value bool)
|
||||
GetRepoPathStack() *utils.StringStack
|
||||
GetRepoState() IRepoStateAccessor
|
||||
// tells us whether we're currently updating lazygit
|
||||
GetUpdating() bool
|
||||
SetUpdating(bool)
|
||||
SetIsRefreshingFiles(bool)
|
||||
GetIsRefreshingFiles() bool
|
||||
}
|
||||
|
||||
type IRepoStateAccessor interface {
|
||||
GetViewsSetup() bool
|
||||
GetWindowViewNameMap() *utils.ThreadSafeMap[string, string]
|
||||
GetStartupStage() StartupStage
|
||||
SetStartupStage(stage StartupStage)
|
||||
GetCurrentPopupOpts() *CreatePopupPanelOpts
|
||||
SetCurrentPopupOpts(*CreatePopupPanelOpts)
|
||||
}
|
||||
|
||||
// startup stages so we don't need to load everything at once
|
||||
type StartupStage int
|
||||
|
||||
const (
|
||||
INITIAL StartupStage = iota
|
||||
COMPLETE
|
||||
)
|
||||
|
||||
type IFileWatcher interface {
|
||||
AddFilesToFileWatcher(files []*models.File) error
|
||||
}
|
||||
|
@ -72,6 +72,10 @@ type IBaseContext interface {
|
||||
// our list controller can come along and wrap it in a list-specific click handler.
|
||||
// We'll need to think of a better way to do this.
|
||||
AddOnClickFn(func() error)
|
||||
|
||||
AddOnRenderToMainFn(func() error)
|
||||
AddOnFocusFn(func(OnFocusOpts) error)
|
||||
AddOnFocusLostFn(func(OnFocusLostOpts) error)
|
||||
}
|
||||
|
||||
type Context interface {
|
||||
@ -83,6 +87,16 @@ type Context interface {
|
||||
HandleRenderToMain() error
|
||||
}
|
||||
|
||||
type DiffableContext interface {
|
||||
Context
|
||||
|
||||
// Returns the current diff terminals of the currently selected item.
|
||||
// in the case of a branch it returns both the branch and it's upstream name,
|
||||
// which becomes an option when you bring up the diff menu, but when you're just
|
||||
// flicking through branches it will be using the local branch name.
|
||||
GetDiffTerminals() []string
|
||||
}
|
||||
|
||||
type IListContext interface {
|
||||
Context
|
||||
|
||||
@ -150,6 +164,9 @@ type HasKeybindings interface {
|
||||
GetKeybindings(opts KeybindingsOpts) []*Binding
|
||||
GetMouseKeybindings(opts KeybindingsOpts) []*gocui.ViewMouseBinding
|
||||
GetOnClick() func() error
|
||||
GetOnRenderToMain() func() error
|
||||
GetOnFocus() func(OnFocusOpts) error
|
||||
GetOnFocusLost() func(OnFocusLostOpts) error
|
||||
}
|
||||
|
||||
type IController interface {
|
||||
|
@ -6,6 +6,7 @@ type RefreshableView int
|
||||
const (
|
||||
COMMITS RefreshableView = iota
|
||||
REBASE_COMMITS
|
||||
SUB_COMMITS
|
||||
BRANCHES
|
||||
FILES
|
||||
STASH
|
||||
@ -32,6 +33,6 @@ const (
|
||||
|
||||
type RefreshOptions struct {
|
||||
Then func()
|
||||
Scope []RefreshableView // e.g. []int{COMMITS, BRANCHES}. Leave empty to refresh everything
|
||||
Scope []RefreshableView // e.g. []RefreshableView{COMMITS, BRANCHES}. Leave empty to refresh everything
|
||||
Mode RefreshMode // one of SYNC (default), ASYNC, and BLOCK_UI
|
||||
}
|
||||
|
42
pkg/gui/types/views.go
Normal file
42
pkg/gui/types/views.go
Normal file
@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import "github.com/jesseduffield/gocui"
|
||||
|
||||
type Views struct {
|
||||
Status *gocui.View
|
||||
Submodules *gocui.View
|
||||
Files *gocui.View
|
||||
Branches *gocui.View
|
||||
Remotes *gocui.View
|
||||
Tags *gocui.View
|
||||
RemoteBranches *gocui.View
|
||||
ReflogCommits *gocui.View
|
||||
Commits *gocui.View
|
||||
Stash *gocui.View
|
||||
|
||||
Main *gocui.View
|
||||
Secondary *gocui.View
|
||||
Staging *gocui.View
|
||||
StagingSecondary *gocui.View
|
||||
PatchBuilding *gocui.View
|
||||
PatchBuildingSecondary *gocui.View
|
||||
MergeConflicts *gocui.View
|
||||
|
||||
Options *gocui.View
|
||||
Confirmation *gocui.View
|
||||
Menu *gocui.View
|
||||
CommitMessage *gocui.View
|
||||
CommitFiles *gocui.View
|
||||
SubCommits *gocui.View
|
||||
Information *gocui.View
|
||||
AppStatus *gocui.View
|
||||
Search *gocui.View
|
||||
SearchPrefix *gocui.View
|
||||
Limit *gocui.View
|
||||
Suggestions *gocui.View
|
||||
Tooltip *gocui.View
|
||||
Extras *gocui.View
|
||||
|
||||
// for playing the easter egg snake game
|
||||
Snake *gocui.View
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) showUpdatePrompt(newVersion string) error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
gui.Tr.UpdateAvailable, map[string]string{
|
||||
"newVersion": newVersion,
|
||||
},
|
||||
)
|
||||
|
||||
return gui.c.Confirm(types.ConfirmOpts{
|
||||
Title: gui.Tr.UpdateAvailableTitle,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
gui.startUpdating(newVersion)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onUserUpdateCheckFinish(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
if newVersion == "" {
|
||||
return gui.c.ErrorMsg(gui.Tr.FailedToRetrieveLatestVersionErr)
|
||||
}
|
||||
return gui.showUpdatePrompt(newVersion)
|
||||
}
|
||||
|
||||
func (gui *Gui) onBackgroundUpdateCheckFinish(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
// ignoring the error for now so that I'm not annoying users
|
||||
gui.c.Log.Error(err.Error())
|
||||
return nil
|
||||
}
|
||||
if newVersion == "" {
|
||||
return nil
|
||||
}
|
||||
if gui.c.UserConfig.Update.Method == "background" {
|
||||
gui.startUpdating(newVersion)
|
||||
return nil
|
||||
}
|
||||
return gui.showUpdatePrompt(newVersion)
|
||||
}
|
||||
|
||||
func (gui *Gui) startUpdating(newVersion string) {
|
||||
gui.State.Updating = true
|
||||
statusId := gui.statusManager.addWaitingStatus(gui.Tr.UpdateInProgressWaitingStatus)
|
||||
gui.Updater.Update(newVersion, func(err error) error { return gui.onUpdateFinish(statusId, err) })
|
||||
}
|
||||
|
||||
func (gui *Gui) onUpdateFinish(statusId int, err error) error {
|
||||
gui.State.Updating = false
|
||||
gui.statusManager.removeStatus(statusId)
|
||||
gui.c.OnUIThread(func() error {
|
||||
_ = gui.renderString(gui.Views.AppStatus, "")
|
||||
if err != nil {
|
||||
errMessage := utils.ResolvePlaceholderString(
|
||||
gui.Tr.UpdateFailedErr, map[string]string{
|
||||
"errMessage": err.Error(),
|
||||
},
|
||||
)
|
||||
return gui.c.ErrorMsg(errMessage)
|
||||
}
|
||||
return gui.c.Alert(gui.Tr.UpdateCompletedTitle, gui.Tr.UpdateCompleted)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) createUpdateQuitConfirmation() error {
|
||||
return gui.c.Confirm(types.ConfirmOpts{
|
||||
Title: gui.Tr.ConfirmQuitDuringUpdateTitle,
|
||||
Prompt: gui.Tr.ConfirmQuitDuringUpdate,
|
||||
HandleConfirm: func() error {
|
||||
return gocui.ErrQuit
|
||||
},
|
||||
})
|
||||
}
|
@ -1,19 +1,21 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/spkg/bom"
|
||||
)
|
||||
|
||||
func (gui *Gui) resetOrigin(v *gocui.View) error {
|
||||
_ = v.SetCursor(0, 0)
|
||||
return v.SetOrigin(0, 0)
|
||||
func (gui *Gui) resetViewOrigin(v *gocui.View) {
|
||||
if err := v.SetCursor(0, 0); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := v.SetOrigin(0, 0); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the number of lines that we should read initially from a cmd task so
|
||||
@ -52,18 +54,6 @@ func (gui *Gui) setViewContent(v *gocui.View, s string) {
|
||||
v.SetContent(gui.cleanString(s))
|
||||
}
|
||||
|
||||
// renderString resets the origin of a view and sets its content
|
||||
func (gui *Gui) renderString(view *gocui.View, s string) error {
|
||||
if err := view.SetOrigin(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := view.SetCursor(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.setViewContent(view, s)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) currentViewName() string {
|
||||
currentView := gui.g.CurrentView()
|
||||
if currentView == nil {
|
||||
@ -129,20 +119,6 @@ func (gui *Gui) resizeConfirmationPanel() {
|
||||
_, _ = gui.g.SetView(gui.Views.Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
|
||||
}
|
||||
|
||||
func (gui *Gui) globalOptionsMap() map[string]string {
|
||||
keybindingConfig := gui.c.UserConfig.Keybinding
|
||||
|
||||
return map[string]string{
|
||||
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollUpMain), keybindings.Label(keybindingConfig.Universal.ScrollDownMain)): gui.c.Tr.LcScroll,
|
||||
fmt.Sprintf("%s %s %s %s", keybindings.Label(keybindingConfig.Universal.PrevBlock), keybindings.Label(keybindingConfig.Universal.NextBlock), keybindings.Label(keybindingConfig.Universal.PrevItem), keybindings.Label(keybindingConfig.Universal.NextItem)): gui.c.Tr.LcNavigate,
|
||||
keybindings.Label(keybindingConfig.Universal.Return): gui.c.Tr.LcCancel,
|
||||
keybindings.Label(keybindingConfig.Universal.Quit): gui.c.Tr.LcQuit,
|
||||
keybindings.Label(keybindingConfig.Universal.OptionMenuAlt1): gui.c.Tr.LcMenu,
|
||||
fmt.Sprintf("%s-%s", keybindings.Label(keybindingConfig.Universal.JumpToBlock[0]), keybindings.Label(keybindingConfig.Universal.JumpToBlock[len(keybindingConfig.Universal.JumpToBlock)-1])): gui.c.Tr.LcJump,
|
||||
fmt.Sprintf("%s/%s", keybindings.Label(keybindingConfig.Universal.ScrollLeft), keybindings.Label(keybindingConfig.Universal.ScrollRight)): gui.c.Tr.LcScrollLeftRight,
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) isPopupPanel(viewName string) bool {
|
||||
return viewName == "commitMessage" || viewName == "confirmation" || viewName == "menu"
|
||||
}
|
||||
@ -159,7 +135,7 @@ func (gui *Gui) onViewTabClick(windowName string, tabIndex int) error {
|
||||
|
||||
viewName := tabs[tabIndex].ViewName
|
||||
|
||||
context, ok := gui.contextForView(viewName)
|
||||
context, ok := gui.helpers.View.ContextForView(viewName)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
@ -167,21 +143,6 @@ func (gui *Gui) onViewTabClick(windowName string, tabIndex int) error {
|
||||
return gui.c.PushContext(context)
|
||||
}
|
||||
|
||||
func (gui *Gui) contextForView(viewName string) (types.Context, bool) {
|
||||
view, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
for _, context := range gui.State.Contexts.Flatten() {
|
||||
if context.GetViewName() == view.Name() {
|
||||
return context, true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (gui *Gui) handleNextTab() error {
|
||||
view := getTabbedView(gui)
|
||||
if view == nil {
|
||||
@ -220,7 +181,7 @@ func (gui *Gui) handlePrevTab() error {
|
||||
|
||||
func getTabbedView(gui *Gui) *gocui.View {
|
||||
// It safe assumption that only static contexts have tabs
|
||||
context := gui.currentStaticContext()
|
||||
context := gui.c.CurrentStaticContext()
|
||||
view, _ := gui.g.View(context.GetViewName())
|
||||
return view
|
||||
}
|
||||
@ -228,3 +189,20 @@ func getTabbedView(gui *Gui) *gocui.View {
|
||||
func (gui *Gui) render() {
|
||||
gui.c.OnUIThread(func() error { return nil })
|
||||
}
|
||||
|
||||
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
|
||||
// if the context's view is set to another context we do nothing.
|
||||
// if the context's view is the current view we trigger a focus; re-selecting the current item.
|
||||
func (gui *Gui) postRefreshUpdate(c types.Context) error {
|
||||
if err := c.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.currentViewName() == c.GetViewName() {
|
||||
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -6,45 +6,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
type Views struct {
|
||||
Status *gocui.View
|
||||
Submodules *gocui.View
|
||||
Files *gocui.View
|
||||
Branches *gocui.View
|
||||
Remotes *gocui.View
|
||||
Tags *gocui.View
|
||||
RemoteBranches *gocui.View
|
||||
ReflogCommits *gocui.View
|
||||
Commits *gocui.View
|
||||
Stash *gocui.View
|
||||
|
||||
Main *gocui.View
|
||||
Secondary *gocui.View
|
||||
Staging *gocui.View
|
||||
StagingSecondary *gocui.View
|
||||
PatchBuilding *gocui.View
|
||||
PatchBuildingSecondary *gocui.View
|
||||
MergeConflicts *gocui.View
|
||||
|
||||
Options *gocui.View
|
||||
Confirmation *gocui.View
|
||||
Menu *gocui.View
|
||||
CommitMessage *gocui.View
|
||||
CommitFiles *gocui.View
|
||||
SubCommits *gocui.View
|
||||
Information *gocui.View
|
||||
AppStatus *gocui.View
|
||||
Search *gocui.View
|
||||
SearchPrefix *gocui.View
|
||||
Limit *gocui.View
|
||||
Suggestions *gocui.View
|
||||
Tooltip *gocui.View
|
||||
Extras *gocui.View
|
||||
|
||||
// for playing the easter egg snake game
|
||||
Snake *gocui.View
|
||||
}
|
||||
|
||||
type viewNameMapping struct {
|
||||
viewPtr **gocui.View
|
||||
name string
|
||||
@ -104,15 +65,6 @@ func (gui *Gui) orderedViewNameMappings() []viewNameMapping {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) windowForView(viewName string) string {
|
||||
context, ok := gui.contextForView(viewName)
|
||||
if !ok {
|
||||
panic("todo: deal with this")
|
||||
}
|
||||
|
||||
return context.GetWindowName()
|
||||
}
|
||||
|
||||
func (gui *Gui) createAllViews() error {
|
||||
frameRunes := []rune{'─', '│', '┌', '┐', '└', '┘'}
|
||||
switch gui.c.UserConfig.Gui.Border {
|
||||
@ -140,7 +92,7 @@ func (gui *Gui) createAllViews() error {
|
||||
gui.Views.SearchPrefix.BgColor = gocui.ColorDefault
|
||||
gui.Views.SearchPrefix.FgColor = gocui.ColorGreen
|
||||
gui.Views.SearchPrefix.Frame = false
|
||||
gui.setViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
|
||||
gui.c.SetViewContent(gui.Views.SearchPrefix, SEARCH_PREFIX)
|
||||
|
||||
gui.Views.Stash.Title = gui.c.Tr.StashTitle
|
||||
|
||||
|
@ -13,5 +13,5 @@ func (gui *Gui) toggleWhitespaceInDiffView() error {
|
||||
}
|
||||
gui.c.Toast(toastMessage)
|
||||
|
||||
return gui.currentSideListContext().HandleFocus(types.OnFocusOpts{})
|
||||
return gui.c.CurrentSideContext().HandleFocus(types.OnFocusOpts{})
|
||||
}
|
||||
|
@ -1,121 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// A window refers to a place on the screen which can hold one or more views.
|
||||
// A view is a box that renders content, and within a window only one view will
|
||||
// appear at a time. When a view appears within a window, it occupies the whole
|
||||
// space. Right now most windows are 1:1 with views, except for commitFiles which
|
||||
// is a view that moves between windows
|
||||
|
||||
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
|
||||
result := utils.NewThreadSafeMap[string, string]()
|
||||
|
||||
for _, context := range contextTree.Flatten() {
|
||||
result.Set(context.GetWindowName(), context.GetViewName())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) getViewNameForWindow(window string) string {
|
||||
viewName, ok := gui.State.WindowViewNameMap.Get(window)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Viewname not found for window: %s", window))
|
||||
}
|
||||
|
||||
return viewName
|
||||
}
|
||||
|
||||
func (gui *Gui) getContextForWindow(window string) types.Context {
|
||||
viewName := gui.getViewNameForWindow(window)
|
||||
|
||||
context, ok := gui.contextForView(viewName)
|
||||
if !ok {
|
||||
panic("TODO: fix this")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
// for now all we actually care about is the context's view so we're storing that
|
||||
func (gui *Gui) setWindowContext(c types.Context) {
|
||||
if c.IsTransient() {
|
||||
gui.resetWindowContext(c)
|
||||
}
|
||||
|
||||
gui.State.WindowViewNameMap.Set(c.GetWindowName(), c.GetViewName())
|
||||
}
|
||||
|
||||
func (gui *Gui) currentWindow() string {
|
||||
return gui.currentContext().GetWindowName()
|
||||
}
|
||||
|
||||
// assumes the context's windowName has been set to the new window if necessary
|
||||
func (gui *Gui) resetWindowContext(c types.Context) {
|
||||
for _, windowName := range gui.State.WindowViewNameMap.Keys() {
|
||||
viewName, ok := gui.State.WindowViewNameMap.Get(windowName)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if viewName == c.GetViewName() && windowName != c.GetWindowName() {
|
||||
for _, context := range gui.State.Contexts.Flatten() {
|
||||
if context.GetKey() != c.GetKey() && context.GetWindowName() == windowName {
|
||||
gui.State.WindowViewNameMap.Set(windowName, context.GetViewName())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// moves given context's view to the top of the window
|
||||
func (gui *Gui) moveToTopOfWindow(context types.Context) {
|
||||
view := context.GetView()
|
||||
if view == nil {
|
||||
return
|
||||
}
|
||||
|
||||
window := context.GetWindowName()
|
||||
|
||||
topView := gui.topViewInWindow(window)
|
||||
|
||||
if view.Name() != topView.Name() {
|
||||
if err := gui.g.SetViewOnTopOf(view.Name(), topView.Name()); err != nil {
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) topViewInWindow(windowName string) *gocui.View {
|
||||
// now I need to find all views in that same window, via contexts. And I guess then I need to find the index of the highest view in that list.
|
||||
viewNamesInWindow := gui.viewNamesInWindow(windowName)
|
||||
|
||||
// The views list is ordered highest-last, so we're grabbing the last view of the window
|
||||
var topView *gocui.View
|
||||
for _, currentView := range gui.g.Views() {
|
||||
if lo.Contains(viewNamesInWindow, currentView.Name()) {
|
||||
topView = currentView
|
||||
}
|
||||
}
|
||||
|
||||
return topView
|
||||
}
|
||||
|
||||
func (gui *Gui) viewNamesInWindow(windowName string) []string {
|
||||
result := []string{}
|
||||
for _, context := range gui.State.Contexts.Flatten() {
|
||||
if context.GetWindowName() == windowName {
|
||||
result = append(result, context.GetViewName())
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user