mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-02-07 13:42:01 +02:00
switching repos without restarting the gui
This commit is contained in:
parent
bc9a99387f
commit
f1d7f59e49
@ -239,7 +239,7 @@ func (app *App) Run() error {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := app.Gui.RunWithRestarts()
|
err := app.Gui.RunAndHandleError()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,9 +181,12 @@ func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map
|
|||||||
// the default behaviour when accordian mode is NOT in effect. If it is in effect
|
// the default behaviour when accordian mode is NOT in effect. If it is in effect
|
||||||
// then when it's accessed it will have weight 2, not 1.
|
// then when it's accessed it will have weight 2, not 1.
|
||||||
func (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
|
func (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
|
||||||
|
gui.State.ContextManager.Lock()
|
||||||
|
defer gui.State.ContextManager.Unlock()
|
||||||
|
|
||||||
box := &boxlayout.Box{Window: "stash"}
|
box := &boxlayout.Box{Window: "stash"}
|
||||||
stashWindowAccessed := false
|
stashWindowAccessed := false
|
||||||
for _, context := range gui.State.ContextStack {
|
for _, context := range gui.State.ContextManager.ContextStack {
|
||||||
if context.GetWindowName() == "stash" {
|
if context.GetWindowName() == "stash" {
|
||||||
stashWindowAccessed = true
|
stashWindowAccessed = true
|
||||||
}
|
}
|
||||||
@ -278,9 +281,12 @@ func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
|||||||
|
|
||||||
func (gui *Gui) currentSideWindowName() string {
|
func (gui *Gui) currentSideWindowName() string {
|
||||||
// there is always one and only one cyclable context in the context stack. We'll look from top to bottom
|
// there is always one and only one cyclable context in the context stack. We'll look from top to bottom
|
||||||
for idx := range gui.State.ContextStack {
|
gui.State.ContextManager.Lock()
|
||||||
reversedIdx := len(gui.State.ContextStack) - 1 - idx
|
defer gui.State.ContextManager.Unlock()
|
||||||
context := gui.State.ContextStack[reversedIdx]
|
|
||||||
|
for idx := range gui.State.ContextManager.ContextStack {
|
||||||
|
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
|
||||||
|
context := gui.State.ContextManager.ContextStack[reversedIdx]
|
||||||
|
|
||||||
if context.GetKind() == SIDE_CONTEXT {
|
if context.GetKind() == SIDE_CONTEXT {
|
||||||
return context.GetWindowName()
|
return context.GetWindowName()
|
||||||
|
@ -296,7 +296,18 @@ func (gui *Gui) initialViewContextMap() map[string]Context {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) viewTabContextMap() map[string][]tabContext {
|
func (gui *Gui) popupViewNames() []string {
|
||||||
|
result := []string{}
|
||||||
|
for _, context := range gui.allContexts() {
|
||||||
|
if context.GetKind() == PERSISTENT_POPUP || context.GetKind() == TEMPORARY_POPUP {
|
||||||
|
result = append(result, context.GetViewName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) initialViewTabContextMap() map[string][]tabContext {
|
||||||
return map[string][]tabContext{
|
return map[string][]tabContext{
|
||||||
"branches": {
|
"branches": {
|
||||||
{
|
{
|
||||||
@ -343,7 +354,10 @@ func (gui *Gui) viewTabContextMap() map[string][]tabContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) currentContextKeyIgnoringPopups() string {
|
func (gui *Gui) currentContextKeyIgnoringPopups() string {
|
||||||
stack := gui.State.ContextStack
|
gui.State.ContextManager.Lock()
|
||||||
|
defer gui.State.ContextManager.Unlock()
|
||||||
|
|
||||||
|
stack := gui.State.ContextManager.ContextStack
|
||||||
|
|
||||||
for i := range stack {
|
for i := range stack {
|
||||||
reversedIndex := len(stack) - 1 - i
|
reversedIndex := len(stack) - 1 - i
|
||||||
@ -361,11 +375,14 @@ func (gui *Gui) currentContextKeyIgnoringPopups() string {
|
|||||||
// hitting escape: you want to go that context's parent instead.
|
// hitting escape: you want to go that context's parent instead.
|
||||||
func (gui *Gui) replaceContext(c Context) error {
|
func (gui *Gui) replaceContext(c Context) error {
|
||||||
gui.g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
if len(gui.State.ContextStack) == 0 {
|
gui.State.ContextManager.Lock()
|
||||||
gui.State.ContextStack = []Context{c}
|
defer gui.State.ContextManager.Unlock()
|
||||||
|
|
||||||
|
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||||
|
gui.State.ContextManager.ContextStack = []Context{c}
|
||||||
} else {
|
} else {
|
||||||
// replace the last item with the given item
|
// replace the last item with the given item
|
||||||
gui.State.ContextStack = append(gui.State.ContextStack[0:len(gui.State.ContextStack)-1], c)
|
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.activateContext(c)
|
return gui.activateContext(c)
|
||||||
@ -376,28 +393,37 @@ func (gui *Gui) replaceContext(c Context) error {
|
|||||||
|
|
||||||
func (gui *Gui) pushContext(c Context) error {
|
func (gui *Gui) pushContext(c Context) error {
|
||||||
gui.g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
// push onto stack
|
gui.State.ContextManager.Lock()
|
||||||
// if we are switching to a side context, remove all other contexts in the stack
|
defer gui.State.ContextManager.Unlock()
|
||||||
if c.GetKind() == SIDE_CONTEXT {
|
|
||||||
for _, stackContext := range gui.State.ContextStack {
|
|
||||||
if stackContext.GetKey() != c.GetKey() {
|
|
||||||
if err := gui.deactivateContext(stackContext); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gui.State.ContextStack = []Context{c}
|
|
||||||
} else {
|
|
||||||
// TODO: think about other exceptional cases
|
|
||||||
gui.State.ContextStack = append(gui.State.ContextStack, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return gui.activateContext(c)
|
return gui.pushContextDirect(c)
|
||||||
})
|
})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gui *Gui) pushContextDirect(c Context) error {
|
||||||
|
// push onto stack
|
||||||
|
// if we are switching to a side context, remove all other contexts in the stack
|
||||||
|
if c.GetKind() == SIDE_CONTEXT {
|
||||||
|
for _, stackContext := range gui.State.ContextManager.ContextStack {
|
||||||
|
if stackContext.GetKey() != c.GetKey() {
|
||||||
|
if err := gui.deactivateContext(stackContext); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gui.State.ContextManager.ContextStack = []Context{c}
|
||||||
|
} else {
|
||||||
|
// TODO: think about other exceptional cases
|
||||||
|
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gui.activateContext(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// asynchronous code idea: functions return an error via a channel, when done
|
||||||
|
|
||||||
// pushContextWithView is to be used when you don't know which context you
|
// pushContextWithView is to be used when you don't know which context you
|
||||||
// want to switch to: you only know the view that you want to switch to. It will
|
// want to switch to: you only know the view that you want to switch to. It will
|
||||||
// look up the context currently active for that view and switch to that context
|
// look up the context currently active for that view and switch to that context
|
||||||
@ -407,19 +433,20 @@ func (gui *Gui) pushContextWithView(viewName string) error {
|
|||||||
|
|
||||||
func (gui *Gui) returnFromContext() error {
|
func (gui *Gui) returnFromContext() error {
|
||||||
gui.g.Update(func(*gocui.Gui) error {
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
// TODO: add mutexes
|
gui.State.ContextManager.Lock()
|
||||||
|
defer gui.State.ContextManager.Unlock()
|
||||||
|
|
||||||
if len(gui.State.ContextStack) == 1 {
|
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||||
// cannot escape from bottommost context
|
// cannot escape from bottommost context
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
n := len(gui.State.ContextStack) - 1
|
n := len(gui.State.ContextManager.ContextStack) - 1
|
||||||
|
|
||||||
currentContext := gui.State.ContextStack[n]
|
currentContext := gui.State.ContextManager.ContextStack[n]
|
||||||
newContext := gui.State.ContextStack[n-1]
|
newContext := gui.State.ContextManager.ContextStack[n-1]
|
||||||
|
|
||||||
gui.State.ContextStack = gui.State.ContextStack[:n]
|
gui.State.ContextManager.ContextStack = gui.State.ContextManager.ContextStack[:n]
|
||||||
|
|
||||||
if err := gui.deactivateContext(currentContext); err != nil {
|
if err := gui.deactivateContext(currentContext); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -529,24 +556,30 @@ func (gui *Gui) activateContext(c Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// currently unused
|
// currently unused
|
||||||
// func (gui *Gui) renderContextStack() string {
|
func (gui *Gui) renderContextStack() string {
|
||||||
// result := ""
|
result := ""
|
||||||
// for _, context := range gui.State.ContextStack {
|
for _, context := range gui.State.ContextManager.ContextStack {
|
||||||
// result += context.GetKey() + "\n"
|
result += context.GetKey() + "\n"
|
||||||
// }
|
}
|
||||||
// return result
|
return result
|
||||||
// }
|
}
|
||||||
|
|
||||||
func (gui *Gui) currentContext() Context {
|
func (gui *Gui) currentContext() Context {
|
||||||
if len(gui.State.ContextStack) == 0 {
|
gui.State.ContextManager.Lock()
|
||||||
|
defer gui.State.ContextManager.Unlock()
|
||||||
|
|
||||||
|
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||||
return gui.defaultSideContext()
|
return gui.defaultSideContext()
|
||||||
}
|
}
|
||||||
|
|
||||||
return gui.State.ContextStack[len(gui.State.ContextStack)-1]
|
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) currentSideContext() *ListContext {
|
func (gui *Gui) currentSideContext() *ListContext {
|
||||||
stack := gui.State.ContextStack
|
gui.State.ContextManager.Lock()
|
||||||
|
defer gui.State.ContextManager.Unlock()
|
||||||
|
|
||||||
|
stack := gui.State.ContextManager.ContextStack
|
||||||
|
|
||||||
// on startup the stack can be empty so we'll return an empty string in that case
|
// on startup the stack can be empty so we'll return an empty string in that case
|
||||||
if len(stack) == 0 {
|
if len(stack) == 0 {
|
||||||
|
@ -5,9 +5,7 @@ import "github.com/go-errors/errors"
|
|||||||
// SentinelErrors are the errors that have special meaning and need to be checked
|
// SentinelErrors are the errors that have special meaning and need to be checked
|
||||||
// by calling functions. The less of these, the better
|
// by calling functions. The less of these, the better
|
||||||
type SentinelErrors struct {
|
type SentinelErrors struct {
|
||||||
ErrNoFiles error
|
ErrNoFiles error
|
||||||
ErrSwitchRepo error
|
|
||||||
ErrRestart error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const UNKNOWN_VIEW_ERROR_MSG = "unknown view"
|
const UNKNOWN_VIEW_ERROR_MSG = "unknown view"
|
||||||
@ -24,14 +22,12 @@ const UNKNOWN_VIEW_ERROR_MSG = "unknown view"
|
|||||||
// localising things in the code.
|
// localising things in the code.
|
||||||
func (gui *Gui) GenerateSentinelErrors() {
|
func (gui *Gui) GenerateSentinelErrors() {
|
||||||
gui.Errors = SentinelErrors{
|
gui.Errors = SentinelErrors{
|
||||||
ErrNoFiles: errors.New(gui.Tr.NoChangedFiles),
|
ErrNoFiles: errors.New(gui.Tr.NoChangedFiles),
|
||||||
ErrSwitchRepo: errors.New("switching repo"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) sentinelErrorsArr() []error {
|
func (gui *Gui) sentinelErrorsArr() []error {
|
||||||
return []error{
|
return []error{
|
||||||
gui.Errors.ErrNoFiles,
|
gui.Errors.ErrNoFiles,
|
||||||
gui.Errors.ErrSwitchRepo,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,10 @@ func (n *CommitFileNode) Flatten(collapsedPaths map[string]bool) []*CommitFileNo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node *CommitFileNode) GetNodeAtIndex(index int, collapsedPaths map[string]bool) *CommitFileNode {
|
func (node *CommitFileNode) GetNodeAtIndex(index int, collapsedPaths map[string]bool) *CommitFileNode {
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return getNodeAtIndex(node, index, collapsedPaths).(*CommitFileNode)
|
return getNodeAtIndex(node, index, collapsedPaths).(*CommitFileNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,6 +93,10 @@ func (n *FileNode) Flatten(collapsedPaths map[string]bool) []*FileNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (node *FileNode) GetNodeAtIndex(index int, collapsedPaths map[string]bool) *FileNode {
|
func (node *FileNode) GetNodeAtIndex(index int, collapsedPaths map[string]bool) *FileNode {
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return getNodeAtIndex(node, index, collapsedPaths).(*FileNode)
|
return getNodeAtIndex(node, index, collapsedPaths).(*FileNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
104
pkg/gui/gui.go
104
pkg/gui/gui.go
@ -48,6 +48,18 @@ const StartupPopupVersion = 3
|
|||||||
// OverlappingEdges determines if panel edges overlap
|
// OverlappingEdges determines if panel edges overlap
|
||||||
var OverlappingEdges = false
|
var OverlappingEdges = false
|
||||||
|
|
||||||
|
type ContextManager struct {
|
||||||
|
ContextStack []Context
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContextManager(contexts ContextTree) ContextManager {
|
||||||
|
return ContextManager{
|
||||||
|
ContextStack: []Context{contexts.Files},
|
||||||
|
Mutex: sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Gui wraps the gocui Gui object which handles rendering and events
|
// Gui wraps the gocui Gui object which handles rendering and events
|
||||||
type Gui struct {
|
type Gui struct {
|
||||||
g *gocui.Gui
|
g *gocui.Gui
|
||||||
@ -83,6 +95,10 @@ type Gui struct {
|
|||||||
// findSuggestions will take a string that the user has typed into a prompt
|
// findSuggestions will take a string that the user has typed into a prompt
|
||||||
// and return a slice of suggestions which match that string.
|
// and return a slice of suggestions which match that string.
|
||||||
findSuggestions func(string) []*types.Suggestion
|
findSuggestions func(string) []*types.Suggestion
|
||||||
|
|
||||||
|
// when you enter into a submodule we'll append the superproject's path to this array
|
||||||
|
// so that you can return to the superproject
|
||||||
|
RepoPathStack []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecordedEvent struct {
|
type RecordedEvent struct {
|
||||||
@ -298,7 +314,7 @@ type guiState struct {
|
|||||||
|
|
||||||
Modes Modes
|
Modes Modes
|
||||||
|
|
||||||
ContextStack []Context
|
ContextManager ContextManager
|
||||||
ViewContextMap map[string]Context
|
ViewContextMap map[string]Context
|
||||||
|
|
||||||
// WindowViewNameMap is a mapping of windows to the current view of that window.
|
// WindowViewNameMap is a mapping of windows to the current view of that window.
|
||||||
@ -306,35 +322,18 @@ type guiState struct {
|
|||||||
// side windows we need to know which view to give focus to for a given window
|
// side windows we need to know which view to give focus to for a given window
|
||||||
WindowViewNameMap map[string]string
|
WindowViewNameMap map[string]string
|
||||||
|
|
||||||
// when you enter into a submodule we'll append the superproject's path to this array
|
// tells us whether we've set up our views. We only do this once per repo
|
||||||
// so that you can return to the superproject
|
ViewsSetup bool
|
||||||
RepoPathStack []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) resetState() {
|
func (gui *Gui) resetState(filterPath string) {
|
||||||
// we carry over the filter path and diff state
|
|
||||||
prevFiltering := filtering.NewFiltering()
|
|
||||||
prevDiff := Diffing{}
|
|
||||||
prevCherryPicking := CherryPicking{
|
|
||||||
CherryPickedCommits: make([]*models.Commit, 0),
|
|
||||||
ContextKey: "",
|
|
||||||
}
|
|
||||||
prevRepoPathStack := []string{}
|
|
||||||
if gui.State != nil {
|
|
||||||
prevFiltering = gui.State.Modes.Filtering
|
|
||||||
prevDiff = gui.State.Modes.Diffing
|
|
||||||
prevCherryPicking = gui.State.Modes.CherryPicking
|
|
||||||
prevRepoPathStack = gui.State.RepoPathStack
|
|
||||||
}
|
|
||||||
|
|
||||||
modes := Modes{
|
|
||||||
Filtering: prevFiltering,
|
|
||||||
CherryPicking: prevCherryPicking,
|
|
||||||
Diffing: prevDiff,
|
|
||||||
}
|
|
||||||
|
|
||||||
showTree := gui.Config.GetUserConfig().Gui.ShowFileTree
|
showTree := gui.Config.GetUserConfig().Gui.ShowFileTree
|
||||||
|
|
||||||
|
screenMode := SCREEN_NORMAL
|
||||||
|
if filterPath != "" {
|
||||||
|
screenMode = SCREEN_HALF
|
||||||
|
}
|
||||||
|
|
||||||
gui.State = &guiState{
|
gui.State = &guiState{
|
||||||
FileManager: filetree.NewFileManager(make([]*models.File, 0), gui.Log, showTree),
|
FileManager: filetree.NewFileManager(make([]*models.File, 0), gui.Log, showTree),
|
||||||
CommitFileManager: filetree.NewCommitFileManager(make([]*models.CommitFile, 0), gui.Log, showTree),
|
CommitFileManager: filetree.NewCommitFileManager(make([]*models.CommitFile, 0), gui.Log, showTree),
|
||||||
@ -365,18 +364,22 @@ func (gui *Gui) resetState() {
|
|||||||
ConflictsMutex: sync.Mutex{},
|
ConflictsMutex: sync.Mutex{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
SideView: nil,
|
SideView: nil,
|
||||||
Ptmx: nil,
|
Ptmx: nil,
|
||||||
Modes: modes,
|
Modes: Modes{
|
||||||
|
Filtering: filtering.NewFiltering(),
|
||||||
|
CherryPicking: CherryPicking{
|
||||||
|
CherryPickedCommits: make([]*models.Commit, 0),
|
||||||
|
ContextKey: "",
|
||||||
|
},
|
||||||
|
Diffing: Diffing{},
|
||||||
|
},
|
||||||
ViewContextMap: gui.initialViewContextMap(),
|
ViewContextMap: gui.initialViewContextMap(),
|
||||||
RepoPathStack: prevRepoPathStack,
|
ScreenMode: screenMode,
|
||||||
|
ContextManager: NewContextManager(gui.Contexts),
|
||||||
}
|
}
|
||||||
|
|
||||||
if gui.State.Modes.Filtering.Active() {
|
gui.ViewTabContextMap = gui.initialViewTabContextMap()
|
||||||
gui.State.ScreenMode = SCREEN_HALF
|
|
||||||
} else {
|
|
||||||
gui.State.ScreenMode = SCREEN_NORMAL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for now the split view will always be on
|
// for now the split view will always be on
|
||||||
@ -393,12 +396,11 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *oscom
|
|||||||
viewBufferManagerMap: map[string]*tasks.ViewBufferManager{},
|
viewBufferManagerMap: map[string]*tasks.ViewBufferManager{},
|
||||||
showRecentRepos: showRecentRepos,
|
showRecentRepos: showRecentRepos,
|
||||||
RecordedEvents: []RecordedEvent{},
|
RecordedEvents: []RecordedEvent{},
|
||||||
|
RepoPathStack: []string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.resetState()
|
|
||||||
gui.State.Modes.Filtering.SetPath(filterPath)
|
|
||||||
gui.Contexts = gui.contextTree()
|
gui.Contexts = gui.contextTree()
|
||||||
gui.ViewTabContextMap = gui.viewTabContextMap()
|
gui.resetState(filterPath)
|
||||||
|
|
||||||
gui.watchFilesForChanges()
|
gui.watchFilesForChanges()
|
||||||
|
|
||||||
@ -409,8 +411,6 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *oscom
|
|||||||
|
|
||||||
// Run setup the gui with keybindings and start the mainloop
|
// Run setup the gui with keybindings and start the mainloop
|
||||||
func (gui *Gui) Run() error {
|
func (gui *Gui) Run() error {
|
||||||
gui.resetState()
|
|
||||||
|
|
||||||
recordEvents := recordingEvents()
|
recordEvents := recordingEvents()
|
||||||
|
|
||||||
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, recordEvents)
|
g, err := gocui.NewGui(gocui.OutputTrue, OverlappingEdges, recordEvents)
|
||||||
@ -457,6 +457,11 @@ func (gui *Gui) Run() error {
|
|||||||
go utils.Safe(gui.startBackgroundFetch)
|
go utils.Safe(gui.startBackgroundFetch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
||||||
|
gui.waitForIntro.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.RefreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
|
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.RefreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
|
||||||
|
|
||||||
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
|
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
|
||||||
@ -467,19 +472,17 @@ func (gui *Gui) Run() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunWithRestarts loops, instantiating a new gocui.Gui with each iteration
|
// RunAndHandleError
|
||||||
// (i.e. when switching repos or restarting). If it's a random error, we quit
|
func (gui *Gui) RunAndHandleError() error {
|
||||||
func (gui *Gui) RunWithRestarts() error {
|
|
||||||
gui.StartTime = time.Now()
|
gui.StartTime = time.Now()
|
||||||
go utils.Safe(gui.replayRecordedEvents)
|
go utils.Safe(gui.replayRecordedEvents)
|
||||||
|
|
||||||
for {
|
gui.stopChan = make(chan struct{})
|
||||||
gui.stopChan = make(chan struct{})
|
return utils.SafeWithError(func() error {
|
||||||
if err := gui.Run(); err != nil {
|
if err := gui.Run(); err != nil {
|
||||||
for _, manager := range gui.viewBufferManagerMap {
|
for _, manager := range gui.viewBufferManagerMap {
|
||||||
manager.Close()
|
manager.Close()
|
||||||
}
|
}
|
||||||
gui.viewBufferManagerMap = map[string]*tasks.ViewBufferManager{}
|
|
||||||
|
|
||||||
if !gui.fileWatcher.Disabled {
|
if !gui.fileWatcher.Disabled {
|
||||||
gui.fileWatcher.Watcher.Close()
|
gui.fileWatcher.Watcher.Close()
|
||||||
@ -500,13 +503,14 @@ func (gui *Gui) RunWithRestarts() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
case gui.Errors.ErrSwitchRepo:
|
|
||||||
continue
|
|
||||||
default:
|
default:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) runSubprocessWithSuspense(subprocess *exec.Cmd) error {
|
func (gui *Gui) runSubprocessWithSuspense(subprocess *exec.Cmd) error {
|
||||||
@ -551,11 +555,9 @@ func (gui *Gui) runSubprocess(subprocess *exec.Cmd) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gui *Gui) loadNewRepo() error {
|
func (gui *Gui) loadNewRepo() error {
|
||||||
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
|
||||||
if err := gui.updateRecentRepoList(); err != nil {
|
if err := gui.updateRecentRepoList(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gui.waitForIntro.Done()
|
|
||||||
|
|
||||||
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
|
if err := gui.refreshSidePanels(refreshOptions{mode: ASYNC}); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1706,7 +1706,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
|
|||||||
bindings = append(bindings, &Binding{ViewName: "", Key: rune(i+1) + '0', Modifier: gocui.ModNone, Handler: gui.goToSideWindow(window)})
|
bindings = append(bindings, &Binding{ViewName: "", Key: rune(i+1) + '0', Modifier: gocui.ModNone, Handler: gui.goToSideWindow(window)})
|
||||||
}
|
}
|
||||||
|
|
||||||
for viewName := range gui.viewTabContextMap() {
|
for viewName := range gui.initialViewTabContextMap() {
|
||||||
bindings = append(bindings, []*Binding{
|
bindings = append(bindings, []*Binding{
|
||||||
{
|
{
|
||||||
ViewName: viewName,
|
ViewName: viewName,
|
||||||
@ -1741,7 +1741,7 @@ func (gui *Gui) keybindings() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for viewName := range gui.viewTabContextMap() {
|
for viewName := range gui.initialViewTabContextMap() {
|
||||||
viewName := viewName
|
viewName := viewName
|
||||||
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(viewName, tabIndex) }
|
tabClickCallback := func(tabIndex int) error { return gui.onViewTabClick(viewName, tabIndex) }
|
||||||
|
|
||||||
|
@ -216,11 +216,6 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
}
|
}
|
||||||
v.Frame = false
|
v.Frame = false
|
||||||
v.FgColor = theme.OptionsColor
|
v.FgColor = theme.OptionsColor
|
||||||
|
|
||||||
// doing this here because it'll only happen once
|
|
||||||
if err := gui.onInitialViewsCreation(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this view takes up one character. Its only purpose is to show the slash when searching
|
// this view takes up one character. Its only purpose is to show the slash when searching
|
||||||
@ -271,6 +266,14 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
gui.State.OldInformation = informationStr
|
gui.State.OldInformation = informationStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !gui.State.ViewsSetup {
|
||||||
|
if err := gui.onInitialViewsCreation(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gui.State.ViewsSetup = true
|
||||||
|
}
|
||||||
|
|
||||||
if gui.g.CurrentView() == nil {
|
if gui.g.CurrentView() == nil {
|
||||||
initialContext := gui.Contexts.Files
|
initialContext := gui.Contexts.Files
|
||||||
if gui.State.Modes.Filtering.Active() {
|
if gui.State.Modes.Filtering.Active() {
|
||||||
@ -323,8 +326,13 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
|||||||
func (gui *Gui) onInitialViewsCreation() error {
|
func (gui *Gui) onInitialViewsCreation() error {
|
||||||
gui.setInitialViewContexts()
|
gui.setInitialViewContexts()
|
||||||
|
|
||||||
// add tabs to views
|
// hide any popup views. This only applies when we've just switched repos
|
||||||
|
for _, viewName := range gui.popupViewNames() {
|
||||||
|
_, _ = gui.g.SetViewOnBottom(viewName)
|
||||||
|
}
|
||||||
|
|
||||||
gui.g.Mutexes.ViewsMutex.Lock()
|
gui.g.Mutexes.ViewsMutex.Lock()
|
||||||
|
// add tabs to views
|
||||||
for _, view := range gui.g.Views() {
|
for _, view := range gui.g.Views() {
|
||||||
tabs := gui.viewTabNames(view.Name())
|
tabs := gui.viewTabNames(view.Name())
|
||||||
if len(tabs) == 0 {
|
if len(tabs) == 0 {
|
||||||
|
@ -49,13 +49,13 @@ func (gui *Gui) handleTopLevelReturn() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repoPathStack := gui.State.RepoPathStack
|
repoPathStack := gui.RepoPathStack
|
||||||
if len(repoPathStack) > 0 {
|
if len(repoPathStack) > 0 {
|
||||||
n := len(repoPathStack) - 1
|
n := len(repoPathStack) - 1
|
||||||
|
|
||||||
path := repoPathStack[n]
|
path := repoPathStack[n]
|
||||||
|
|
||||||
gui.State.RepoPathStack = repoPathStack[:n]
|
gui.RepoPathStack = repoPathStack[:n]
|
||||||
|
|
||||||
return gui.dispatchSwitchToRepo(path)
|
return gui.dispatchSwitchToRepo(path)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||||
"github.com/jesseduffield/lazygit/pkg/env"
|
"github.com/jesseduffield/lazygit/pkg/env"
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
@ -24,6 +25,9 @@ func (gui *Gui) handleCreateRecentReposMenu() error {
|
|||||||
yellow.Sprint(path),
|
yellow.Sprint(path),
|
||||||
},
|
},
|
||||||
onPress: func() error {
|
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 = []string{}
|
||||||
return gui.dispatchSwitchToRepo(path)
|
return gui.dispatchSwitchToRepo(path)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -73,8 +77,14 @@ func (gui *Gui) dispatchSwitchToRepo(path string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gui.GitCommand = newGitCommand
|
gui.GitCommand = newGitCommand
|
||||||
gui.State.Modes.Filtering.Reset()
|
|
||||||
return gui.Errors.ErrSwitchRepo
|
gui.g.Update(func(*gocui.Gui) error {
|
||||||
|
gui.resetState("")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
||||||
|
@ -71,7 +71,7 @@ func (gui *Gui) enterSubmodule(submodule *models.SubmoduleConfig) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
gui.State.RepoPathStack = append(gui.State.RepoPathStack, wd)
|
gui.RepoPathStack = append(gui.RepoPathStack, wd)
|
||||||
|
|
||||||
return gui.dispatchSwitchToRepo(submodule.Path)
|
return gui.dispatchSwitchToRepo(submodule.Path)
|
||||||
}
|
}
|
||||||
|
@ -365,6 +365,10 @@ func ResolveTemplate(templateStr string, object interface{}) (string, error) {
|
|||||||
// Safe will close tcell if a panic occurs so that we don't end up in a malformed
|
// Safe will close tcell if a panic occurs so that we don't end up in a malformed
|
||||||
// terminal state
|
// terminal state
|
||||||
func Safe(f func()) {
|
func Safe(f func()) {
|
||||||
|
_ = SafeWithError(func() error { f(); return nil })
|
||||||
|
}
|
||||||
|
|
||||||
|
func SafeWithError(f func() error) error {
|
||||||
panicking := true
|
panicking := true
|
||||||
defer func() {
|
defer func() {
|
||||||
if panicking && gocui.Screen != nil {
|
if panicking && gocui.Screen != nil {
|
||||||
@ -372,7 +376,9 @@ func Safe(f func()) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
f()
|
err := f()
|
||||||
|
|
||||||
panicking = false
|
panicking = false
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user