1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-31 14:24:25 +03:00

add submodules context

This commit is contained in:
Jesse Duffield
2020-09-30 08:27:23 +10:00
parent 3b93b5dde4
commit 7b4a0f20b2
14 changed files with 367 additions and 93 deletions

View File

@ -5,3 +5,15 @@ type SubmoduleConfig struct {
Path string Path string
Url string Url string
} }
func (r *SubmoduleConfig) RefName() string {
return r.Name
}
func (r *SubmoduleConfig) ID() string {
return r.RefName()
}
func (r *SubmoduleConfig) Description() string {
return r.RefName()
}

View File

@ -34,6 +34,7 @@ const (
CONFIRMATION_CONTEXT_KEY = "confirmation" CONFIRMATION_CONTEXT_KEY = "confirmation"
SEARCH_CONTEXT_KEY = "search" SEARCH_CONTEXT_KEY = "search"
COMMIT_MESSAGE_CONTEXT_KEY = "commitMessage" COMMIT_MESSAGE_CONTEXT_KEY = "commitMessage"
SUBMODULES_CONTEXT_KEY = "submodules"
) )
var allContextKeys = []string{ var allContextKeys = []string{
@ -57,6 +58,64 @@ var allContextKeys = []string{
CONFIRMATION_CONTEXT_KEY, CONFIRMATION_CONTEXT_KEY,
SEARCH_CONTEXT_KEY, SEARCH_CONTEXT_KEY,
COMMIT_MESSAGE_CONTEXT_KEY, COMMIT_MESSAGE_CONTEXT_KEY,
SUBMODULES_CONTEXT_KEY,
}
type SimpleContextNode struct {
Context Context
}
type RemotesContextNode struct {
Context Context
Branches SimpleContextNode
}
type ContextTree struct {
Status SimpleContextNode
Files SimpleContextNode
Submodules SimpleContextNode
Menu SimpleContextNode
Branches SimpleContextNode
Remotes RemotesContextNode
Tags SimpleContextNode
BranchCommits SimpleContextNode
CommitFiles SimpleContextNode
ReflogCommits SimpleContextNode
SubCommits SimpleContextNode
Stash SimpleContextNode
Normal SimpleContextNode
Staging SimpleContextNode
PatchBuilding SimpleContextNode
Merging SimpleContextNode
Credentials SimpleContextNode
Confirmation SimpleContextNode
CommitMessage SimpleContextNode
Search SimpleContextNode
}
func (gui *Gui) allContexts() []Context {
return []Context{
gui.Contexts.Status.Context,
gui.Contexts.Files.Context,
gui.Contexts.Submodules.Context,
gui.Contexts.Branches.Context,
gui.Contexts.Remotes.Context,
gui.Contexts.Remotes.Branches.Context,
gui.Contexts.Tags.Context,
gui.Contexts.BranchCommits.Context,
gui.Contexts.CommitFiles.Context,
gui.Contexts.ReflogCommits.Context,
gui.Contexts.Stash.Context,
gui.Contexts.Menu.Context,
gui.Contexts.Confirmation.Context,
gui.Contexts.Credentials.Context,
gui.Contexts.CommitMessage.Context,
gui.Contexts.Normal.Context,
gui.Contexts.Staging.Context,
gui.Contexts.Merging.Context,
gui.Contexts.PatchBuilding.Context,
gui.Contexts.SubCommits.Context,
}
} }
type Context interface { type Context interface {
@ -139,61 +198,6 @@ func (c BasicContext) GetKey() string {
return c.Key return c.Key
} }
type SimpleContextNode struct {
Context Context
}
type RemotesContextNode struct {
Context Context
Branches SimpleContextNode
}
type ContextTree struct {
Status SimpleContextNode
Files SimpleContextNode
Menu SimpleContextNode
Branches SimpleContextNode
Remotes RemotesContextNode
Tags SimpleContextNode
BranchCommits SimpleContextNode
CommitFiles SimpleContextNode
ReflogCommits SimpleContextNode
SubCommits SimpleContextNode
Stash SimpleContextNode
Normal SimpleContextNode
Staging SimpleContextNode
PatchBuilding SimpleContextNode
Merging SimpleContextNode
Credentials SimpleContextNode
Confirmation SimpleContextNode
CommitMessage SimpleContextNode
Search SimpleContextNode
}
func (gui *Gui) allContexts() []Context {
return []Context{
gui.Contexts.Status.Context,
gui.Contexts.Files.Context,
gui.Contexts.Branches.Context,
gui.Contexts.Remotes.Context,
gui.Contexts.Remotes.Branches.Context,
gui.Contexts.Tags.Context,
gui.Contexts.BranchCommits.Context,
gui.Contexts.CommitFiles.Context,
gui.Contexts.ReflogCommits.Context,
gui.Contexts.Stash.Context,
gui.Contexts.Menu.Context,
gui.Contexts.Confirmation.Context,
gui.Contexts.Credentials.Context,
gui.Contexts.CommitMessage.Context,
gui.Contexts.Normal.Context,
gui.Contexts.Staging.Context,
gui.Contexts.Merging.Context,
gui.Contexts.PatchBuilding.Context,
gui.Contexts.SubCommits.Context,
}
}
func (gui *Gui) contextTree() ContextTree { func (gui *Gui) contextTree() ContextTree {
return ContextTree{ return ContextTree{
Status: SimpleContextNode{ Status: SimpleContextNode{
@ -207,6 +211,9 @@ func (gui *Gui) contextTree() ContextTree {
Files: SimpleContextNode{ Files: SimpleContextNode{
Context: gui.filesListContext(), Context: gui.filesListContext(),
}, },
Submodules: SimpleContextNode{
Context: gui.submodulesListContext(),
},
Menu: SimpleContextNode{ Menu: SimpleContextNode{
Context: gui.menuListContext(), Context: gui.menuListContext(),
}, },
@ -365,6 +372,18 @@ func (gui *Gui) viewTabContextMap() map[string][]tabContext {
}, },
}, },
}, },
"files": {
{
tab: "Files",
contexts: []Context{gui.Contexts.Files.Context},
},
{
tab: "Submodules",
contexts: []Context{
gui.Contexts.Submodules.Context,
},
},
},
} }
} }

View File

@ -34,7 +34,8 @@ func (gui *Gui) currentDiffTerminals() []string {
switch gui.currentContext().GetKey() { switch gui.currentContext().GetKey() {
case "": case "":
return nil return nil
case FILES_CONTEXT_KEY: case FILES_CONTEXT_KEY, SUBMODULES_CONTEXT_KEY:
// TODO: should we just return nil here?
return []string{""} return []string{""}
case COMMIT_FILES_CONTEXT_KEY: case COMMIT_FILES_CONTEXT_KEY:
return []string{gui.State.Panels.CommitFiles.refName} return []string{gui.State.Panels.CommitFiles.refName}

View File

@ -6,7 +6,7 @@ import (
) )
func (gui *Gui) submoduleFromFile(file *models.File) *models.SubmoduleConfig { func (gui *Gui) submoduleFromFile(file *models.File) *models.SubmoduleConfig {
for _, config := range gui.State.SubmoduleConfigs { for _, config := range gui.State.Submodules {
if config.Name == file.Name { if config.Name == file.Name {
return config return config
} }
@ -23,7 +23,7 @@ func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
var menuItems []*menuItem var menuItems []*menuItem
submoduleConfigs := gui.State.SubmoduleConfigs submoduleConfigs := gui.State.Submodules
if file.IsSubmodule(submoduleConfigs) { if file.IsSubmodule(submoduleConfigs) {
submoduleConfig := file.SubmoduleConfig(submoduleConfigs) submoduleConfig := file.SubmoduleConfig(submoduleConfigs)

View File

@ -8,7 +8,6 @@ import (
// "strings" // "strings"
"fmt" "fmt"
"os"
"regexp" "regexp"
"strings" "strings"
@ -80,7 +79,7 @@ func (gui *Gui) selectFile(alreadySelected bool) error {
return gui.refreshMainViews(refreshOpts) return gui.refreshMainViews(refreshOpts)
} }
func (gui *Gui) refreshFiles() error { func (gui *Gui) refreshFilesAndSubmodules() error {
gui.State.RefreshingFilesMutex.Lock() gui.State.RefreshingFilesMutex.Lock()
gui.State.IsRefreshingFiles = true gui.State.IsRefreshingFiles = true
defer func() { defer func() {
@ -103,15 +102,25 @@ func (gui *Gui) refreshFiles() error {
} }
gui.g.Update(func(g *gocui.Gui) error { gui.g.Update(func(g *gocui.Gui) error {
if err := gui.Contexts.Files.Context.HandleRender(); err != nil { if err := gui.postRefreshUpdate(gui.Contexts.Submodules.Context); err != nil {
return err gui.Log.Error(err)
} }
if g.CurrentView() == filesView || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == MAIN_MERGING_CONTEXT_KEY) { if gui.getFilesView().Context == FILES_CONTEXT_KEY {
// doing this a little custom (as opposed to using gui.postRefreshUpdate) because we handle selecting the file explicitly below
if err := gui.Contexts.Files.Context.HandleRender(); err != nil {
return err
}
}
if gui.currentContext().GetKey() == FILES_CONTEXT_KEY || (g.CurrentView() == gui.getMainView() && g.CurrentView().Context == MAIN_MERGING_CONTEXT_KEY) {
newSelectedFile := gui.getSelectedFile() newSelectedFile := gui.getSelectedFile()
alreadySelected := selectedFile != nil && newSelectedFile != nil && newSelectedFile.Name == selectedFile.Name alreadySelected := selectedFile != nil && newSelectedFile != nil && newSelectedFile.Name == selectedFile.Name
return gui.selectFile(alreadySelected) if err := gui.selectFile(alreadySelected); err != nil {
return err
}
} }
return nil return nil
}) })
@ -161,15 +170,10 @@ func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error
return nil return nil
} }
submoduleConfigs := gui.State.SubmoduleConfigs submoduleConfigs := gui.State.Submodules
if file.IsSubmodule(submoduleConfigs) { if file.IsSubmodule(submoduleConfigs) {
wd, err := os.Getwd()
if err != nil {
return err
}
gui.State.RepoPathStack = append(gui.State.RepoPathStack, wd)
submoduleConfig := file.SubmoduleConfig(submoduleConfigs) submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
return gui.dispatchSwitchToRepo(submoduleConfig.Path) return gui.enterSubmodule(submoduleConfig)
} }
if file.HasInlineMergeConflicts { if file.HasInlineMergeConflicts {
@ -325,7 +329,7 @@ func (gui *Gui) promptToStageAllAndRetry(retry func() error) error {
if err := gui.GitCommand.StageAll(); err != nil { if err := gui.GitCommand.StageAll(); err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
if err := gui.refreshFiles(); err != nil { if err := gui.refreshFilesAndSubmodules(); err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
@ -448,7 +452,7 @@ func (gui *Gui) refreshStateSubmoduleConfigs() error {
return err return err
} }
gui.State.SubmoduleConfigs = configs gui.State.Submodules = configs
return nil return nil
} }

View File

@ -209,6 +209,10 @@ type commitFilesPanelState struct {
canRebase bool canRebase bool
} }
type submodulePanelState struct {
listPanelState
}
type panelStates struct { type panelStates struct {
Files *filePanelState Files *filePanelState
Branches *branchPanelState Branches *branchPanelState
@ -223,6 +227,7 @@ type panelStates struct {
LineByLine *lineByLinePanelState LineByLine *lineByLinePanelState
Merging *mergingPanelState Merging *mergingPanelState
CommitFiles *commitFilesPanelState CommitFiles *commitFilesPanelState
Submodules *submodulePanelState
} }
type searchingState struct { type searchingState struct {
@ -273,12 +278,12 @@ type Modes struct {
} }
type guiState struct { type guiState struct {
Files []*models.File Files []*models.File
SubmoduleConfigs []*models.SubmoduleConfig Submodules []*models.SubmoduleConfig
Branches []*models.Branch Branches []*models.Branch
Commits []*models.Commit Commits []*models.Commit
StashEntries []*models.StashEntry StashEntries []*models.StashEntry
CommitFiles []*models.CommitFile CommitFiles []*models.CommitFile
// FilteredReflogCommits are the ones that appear in the reflog panel. // FilteredReflogCommits are the ones that appear in the reflog panel.
// when in filtering mode we only include the ones that match the given path // when in filtering mode we only include the ones that match the given path
FilteredReflogCommits []*models.Commit FilteredReflogCommits []*models.Commit
@ -358,6 +363,7 @@ func (gui *Gui) resetState() {
Panels: &panelStates{ Panels: &panelStates{
// TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now // TODO: work out why some of these are -1 and some are 0. Last time I checked there was a good reason but I'm less certain now
Files: &filePanelState{listPanelState{SelectedLineIdx: -1}}, Files: &filePanelState{listPanelState{SelectedLineIdx: -1}},
Submodules: &submodulePanelState{listPanelState{SelectedLineIdx: -1}},
Branches: &branchPanelState{listPanelState{SelectedLineIdx: 0}}, Branches: &branchPanelState{listPanelState{SelectedLineIdx: 0}},
Remotes: &remotePanelState{listPanelState{SelectedLineIdx: 0}}, Remotes: &remotePanelState{listPanelState{SelectedLineIdx: 0}},
RemoteBranches: &remoteBranchesState{listPanelState{SelectedLineIdx: -1}}, RemoteBranches: &remoteBranchesState{listPanelState{SelectedLineIdx: -1}},
@ -456,7 +462,7 @@ func (gui *Gui) Run() error {
go gui.startBackgroundFetch() go gui.startBackgroundFetch()
} }
gui.goEvery(time.Second*10, gui.stopChan, gui.refreshFiles) gui.goEvery(time.Second*10, gui.stopChan, gui.refreshFilesAndSubmodules)
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout())) g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))

View File

@ -363,102 +363,119 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.commitChanges"), Key: gui.getKey("files.commitChanges"),
Handler: gui.wrappedHandler(gui.handleCommitPress), Handler: gui.wrappedHandler(gui.handleCommitPress),
Description: gui.Tr.SLocalize("CommitChanges"), Description: gui.Tr.SLocalize("CommitChanges"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.commitChangesWithoutHook"), Key: gui.getKey("files.commitChangesWithoutHook"),
Handler: gui.handleWIPCommitPress, Handler: gui.handleWIPCommitPress,
Description: gui.Tr.SLocalize("commitChangesWithoutHook"), Description: gui.Tr.SLocalize("commitChangesWithoutHook"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.amendLastCommit"), Key: gui.getKey("files.amendLastCommit"),
Handler: gui.wrappedHandler(gui.handleAmendCommitPress), Handler: gui.wrappedHandler(gui.handleAmendCommitPress),
Description: gui.Tr.SLocalize("AmendLastCommit"), Description: gui.Tr.SLocalize("AmendLastCommit"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.commitChangesWithEditor"), Key: gui.getKey("files.commitChangesWithEditor"),
Handler: gui.wrappedHandler(gui.handleCommitEditorPress), Handler: gui.wrappedHandler(gui.handleCommitEditorPress),
Description: gui.Tr.SLocalize("CommitChangesWithEditor"), Description: gui.Tr.SLocalize("CommitChangesWithEditor"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("universal.select"), Key: gui.getKey("universal.select"),
Handler: gui.wrappedHandler(gui.handleFilePress), Handler: gui.wrappedHandler(gui.handleFilePress),
Description: gui.Tr.SLocalize("toggleStaged"), Description: gui.Tr.SLocalize("toggleStaged"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("universal.remove"), Key: gui.getKey("universal.remove"),
Handler: gui.handleCreateDiscardMenu, Handler: gui.handleCreateDiscardMenu,
Description: gui.Tr.SLocalize("viewDiscardOptions"), Description: gui.Tr.SLocalize("viewDiscardOptions"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("universal.edit"), Key: gui.getKey("universal.edit"),
Handler: gui.handleFileEdit, Handler: gui.handleFileEdit,
Description: gui.Tr.SLocalize("editFile"), Description: gui.Tr.SLocalize("editFile"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("universal.openFile"), Key: gui.getKey("universal.openFile"),
Handler: gui.handleFileOpen, Handler: gui.handleFileOpen,
Description: gui.Tr.SLocalize("openFile"), Description: gui.Tr.SLocalize("openFile"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.ignoreFile"), Key: gui.getKey("files.ignoreFile"),
Handler: gui.handleIgnoreFile, Handler: gui.handleIgnoreFile,
Description: gui.Tr.SLocalize("ignoreFile"), Description: gui.Tr.SLocalize("ignoreFile"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.refreshFiles"), Key: gui.getKey("files.refreshFiles"),
Handler: gui.handleRefreshFiles, Handler: gui.handleRefreshFiles,
Description: gui.Tr.SLocalize("refreshFiles"), Description: gui.Tr.SLocalize("refreshFiles"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.stashAllChanges"), Key: gui.getKey("files.stashAllChanges"),
Handler: gui.handleStashChanges, Handler: gui.handleStashChanges,
Description: gui.Tr.SLocalize("stashAllChanges"), Description: gui.Tr.SLocalize("stashAllChanges"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.viewStashOptions"), Key: gui.getKey("files.viewStashOptions"),
Handler: gui.handleCreateStashMenu, Handler: gui.handleCreateStashMenu,
Description: gui.Tr.SLocalize("viewStashOptions"), Description: gui.Tr.SLocalize("viewStashOptions"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.toggleStagedAll"), Key: gui.getKey("files.toggleStagedAll"),
Handler: gui.handleStageAll, Handler: gui.handleStageAll,
Description: gui.Tr.SLocalize("toggleStagedAll"), Description: gui.Tr.SLocalize("toggleStagedAll"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.viewResetOptions"), Key: gui.getKey("files.viewResetOptions"),
Handler: gui.handleCreateResetMenu, Handler: gui.handleCreateResetMenu,
Description: gui.Tr.SLocalize("viewResetOptions"), Description: gui.Tr.SLocalize("viewResetOptions"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("universal.goInto"), Key: gui.getKey("universal.goInto"),
Handler: gui.handleEnterFile, Handler: gui.handleEnterFile,
Description: gui.Tr.SLocalize("StageLines"), Description: gui.Tr.SLocalize("StageLines"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("files.fetch"), Key: gui.getKey("files.fetch"),
Handler: gui.handleGitFetch, Handler: gui.handleGitFetch,
Description: gui.Tr.SLocalize("fetch"), Description: gui.Tr.SLocalize("fetch"),
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("universal.copyToClipboard"), Key: gui.getKey("universal.copyToClipboard"),
Handler: gui.wrappedHandler(gui.handleCopySelectedSideContextItemToClipboard), Handler: gui.wrappedHandler(gui.handleCopySelectedSideContextItemToClipboard),
Description: gui.Tr.SLocalize("copyFileNameToClipboard"), Description: gui.Tr.SLocalize("copyFileNameToClipboard"),
@ -471,6 +488,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
}, },
{ {
ViewName: "files", ViewName: "files",
Contexts: []string{FILES_CONTEXT_KEY},
Key: gui.getKey("commits.viewResetOptions"), Key: gui.getKey("commits.viewResetOptions"),
Handler: gui.handleCreateResetToUpstreamMenu, Handler: gui.handleCreateResetToUpstreamMenu,
Description: gui.Tr.SLocalize("viewResetToUpstreamOptions"), Description: gui.Tr.SLocalize("viewResetToUpstreamOptions"),
@ -1542,6 +1560,32 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: gui.wrappedHandler(gui.onMenuPress), Handler: gui.wrappedHandler(gui.onMenuPress),
}, },
{
ViewName: "files",
Contexts: []string{SUBMODULES_CONTEXT_KEY},
Key: gui.getKey("universal.goInto"),
Handler: gui.wrappedHandler(gui.handleSubmoduleEnter),
Description: gui.Tr.SLocalize("enterSubmodule"),
},
{
ViewName: "files",
Key: gui.getKey("universal.nextTab"),
Handler: gui.handleNextTab,
Description: gui.Tr.SLocalize("nextTab"),
},
{
ViewName: "files",
Key: gui.getKey("universal.prevTab"),
Handler: gui.handlePrevTab,
Description: gui.Tr.SLocalize("prevTab"),
},
{
ViewName: "files",
Contexts: []string{SUBMODULES_CONTEXT_KEY},
Key: gui.getKey("universal.copyToClipboard"),
Handler: gui.wrappedHandler(gui.handleCopySelectedSideContextItemToClipboard),
Description: gui.Tr.SLocalize("copySubmoduleNameToClipboard"),
},
} }
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} { for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {

View File

@ -285,8 +285,10 @@ func (gui *Gui) layout(g *gocui.Gui) error {
listContext *ListContext listContext *ListContext
} }
// TODO: don't we already have the view included in the context object itself? Or might that change in a way we don't want reflected here?
listContextStates := []listContextState{ listContextStates := []listContextState{
{view: filesView, listContext: gui.filesListContext()}, {view: filesView, listContext: gui.filesListContext()},
{view: filesView, listContext: gui.submodulesListContext()},
{view: branchesView, listContext: gui.branchesListContext()}, {view: branchesView, listContext: gui.branchesListContext()},
{view: branchesView, listContext: gui.remotesListContext()}, {view: branchesView, listContext: gui.remotesListContext()},
{view: branchesView, listContext: gui.remoteBranchesListContext()}, {view: branchesView, listContext: gui.remoteBranchesListContext()},

View File

@ -270,7 +270,7 @@ func (gui *Gui) filesListContext() *ListContext {
ResetMainViewOriginOnFocus: false, ResetMainViewOriginOnFocus: false,
Kind: SIDE_CONTEXT, Kind: SIDE_CONTEXT,
GetDisplayStrings: func() [][]string { GetDisplayStrings: func() [][]string {
return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Modes.Diffing.Ref, gui.State.SubmoduleConfigs) return presentation.GetFileListDisplayStrings(gui.State.Files, gui.State.Modes.Diffing.Ref, gui.State.Submodules)
}, },
SelectedItem: func() (ListItem, bool) { SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedFile() item := gui.getSelectedFile()
@ -462,6 +462,27 @@ func (gui *Gui) commitFilesListContext() *ListContext {
} }
} }
func (gui *Gui) submodulesListContext() *ListContext {
return &ListContext{
ViewName: "files",
WindowName: "files",
ContextKey: SUBMODULES_CONTEXT_KEY,
GetItemsLength: func() int { return len(gui.State.Submodules) },
GetPanelState: func() IListPanelState { return gui.State.Panels.Submodules },
OnFocus: gui.handleSubmoduleSelect,
Gui: gui,
ResetMainViewOriginOnFocus: true,
Kind: SIDE_CONTEXT,
GetDisplayStrings: func() [][]string {
return presentation.GetSubmoduleListDisplayStrings(gui.State.Submodules)
},
SelectedItem: func() (ListItem, bool) {
item := gui.getSelectedSubmodule()
return item, item != nil
},
}
}
func (gui *Gui) getListContexts() []*ListContext { func (gui *Gui) getListContexts() []*ListContext {
return []*ListContext{ return []*ListContext{
gui.menuListContext(), gui.menuListContext(),
@ -475,6 +496,7 @@ func (gui *Gui) getListContexts() []*ListContext {
gui.subCommitsListContext(), gui.subCommitsListContext(),
gui.stashListContext(), gui.stashListContext(),
gui.commitFilesListContext(), gui.commitFilesListContext(),
gui.submodulesListContext(),
} }
} }

View File

@ -0,0 +1,21 @@
package presentation
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func GetSubmoduleListDisplayStrings(submodules []*models.SubmoduleConfig) [][]string {
lines := make([][]string, len(submodules))
for i := range submodules {
lines[i] = getSubmoduleDisplayStrings(submodules[i])
}
return lines
}
func getSubmoduleDisplayStrings(s *models.SubmoduleConfig) []string {
return []string{utils.ColoredString(s.Name, theme.DefaultTextColor)}
}

135
pkg/gui/submodules_panel.go Normal file
View File

@ -0,0 +1,135 @@
package gui
import (
"fmt"
"os"
"github.com/fatih/color"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) getSelectedSubmodule() *models.SubmoduleConfig {
selectedLine := gui.State.Panels.Submodules.SelectedLineIdx
if selectedLine == -1 || len(gui.State.Submodules) == 0 {
return nil
}
return gui.State.Submodules[selectedLine]
}
func (gui *Gui) handleSubmoduleSelect() error {
var task updateTask
submodule := gui.getSelectedSubmodule()
if submodule == nil {
task = gui.createRenderStringTask("No submodules")
} else {
// TODO: we want to display the path, name, url, and a diff. We really need to be able to pipe commands together. We can always pipe commands together and just not do it asynchronously, but what if it's an expensive diff to obtain? I think that makes the most sense now though.
task = gui.createRenderStringTask(
fmt.Sprintf(
"Name: %s\nPath: %s\nUrl: %s\n",
utils.ColoredString(submodule.Name, color.FgGreen),
utils.ColoredString(submodule.Path, color.FgYellow),
utils.ColoredString(submodule.Url, color.FgCyan),
),
)
}
return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{
title: "Submodule",
task: task,
},
})
}
func (gui *Gui) handleSubmoduleEnter() error {
submodule := gui.getSelectedSubmodule()
if submodule == nil {
return nil
}
return gui.enterSubmodule(submodule)
}
func (gui *Gui) enterSubmodule(submodule *models.SubmoduleConfig) error {
wd, err := os.Getwd()
if err != nil {
return err
}
gui.State.RepoPathStack = append(gui.State.RepoPathStack, wd)
return gui.dispatchSwitchToRepo(submodule.Path)
}
// func (gui *Gui) handleAddRemote(g *gocui.Gui, v *gocui.View) error {
// return gui.prompt(gui.Tr.SLocalize("newRemoteName"), "", func(remoteName string) error {
// return gui.prompt(gui.Tr.SLocalize("newRemoteUrl"), "", func(remoteUrl string) error {
// if err := gui.GitCommand.AddRemote(remoteName, remoteUrl); err != nil {
// return err
// }
// return gui.refreshSidePanels(refreshOptions{scope: []int{REMOTES}})
// })
// })
// }
// func (gui *Gui) handleRemoveRemote(g *gocui.Gui, v *gocui.View) error {
// remote := gui.getSelectedSubmodule()
// if remote == nil {
// return nil
// }
// return gui.ask(askOpts{
// title: gui.Tr.SLocalize("removeRemote"),
// prompt: gui.Tr.SLocalize("removeRemotePrompt") + " '" + remote.Name + "'?",
// handleConfirm: func() error {
// if err := gui.GitCommand.RemoveRemote(remote.Name); err != nil {
// return err
// }
// return gui.refreshSidePanels(refreshOptions{scope: []int{BRANCHES, REMOTES}})
// },
// })
// }
// func (gui *Gui) handleEditRemote(g *gocui.Gui, v *gocui.View) error {
// remote := gui.getSelectedSubmodule()
// if remote == nil {
// return nil
// }
// editNameMessage := gui.Tr.TemplateLocalize(
// "editRemoteName",
// Teml{
// "remoteName": remote.Name,
// },
// )
// return gui.prompt(editNameMessage, remote.Name, func(updatedRemoteName string) error {
// if updatedRemoteName != remote.Name {
// if err := gui.GitCommand.RenameRemote(remote.Name, updatedRemoteName); err != nil {
// return gui.surfaceError(err)
// }
// }
// editUrlMessage := gui.Tr.TemplateLocalize(
// "editRemoteUrl",
// Teml{
// "remoteName": updatedRemoteName,
// },
// )
// urls := remote.Urls
// url := ""
// if len(urls) > 0 {
// url = urls[0]
// }
// return gui.prompt(editUrlMessage, url, func(updatedRemoteUrl string) error {
// if err := gui.GitCommand.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
// return gui.surfaceError(err)
// }
// return gui.refreshSidePanels(refreshOptions{scope: []int{BRANCHES, REMOTES}})
// })
// })
// }

View File

@ -25,18 +25,20 @@ const (
TAGS TAGS
REMOTES REMOTES
STATUS STATUS
SUBMODULES
) )
func getScopeNames(scopes []int) []string { func getScopeNames(scopes []int) []string {
scopeNameMap := map[int]string{ scopeNameMap := map[int]string{
COMMITS: "commits", COMMITS: "commits",
BRANCHES: "branches", BRANCHES: "branches",
FILES: "files", FILES: "files",
STASH: "stash", SUBMODULES: "submodules",
REFLOG: "reflog", STASH: "stash",
TAGS: "tags", REFLOG: "reflog",
REMOTES: "remotes", TAGS: "tags",
STATUS: "status", REMOTES: "remotes",
STATUS: "status",
} }
scopeNames := make([]string, len(scopes)) scopeNames := make([]string, len(scopes))
@ -116,13 +118,13 @@ func (gui *Gui) refreshSidePanels(options refreshOptions) error {
}() }()
} }
if scopeMap[FILES] { if scopeMap[FILES] || scopeMap[SUBMODULES] {
wg.Add(1) wg.Add(1)
func() { func() {
if options.mode == ASYNC { if options.mode == ASYNC {
go gui.refreshFiles() go gui.refreshFilesAndSubmodules()
} else { } else {
gui.refreshFiles() gui.refreshFilesAndSubmodules()
} }
wg.Done() wg.Done()
}() }()

View File

@ -11,7 +11,7 @@ func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
red := color.New(color.FgRed) red := color.New(color.FgRed)
nukeStr := "reset --hard HEAD && git clean -fd" nukeStr := "reset --hard HEAD && git clean -fd"
if len(gui.State.SubmoduleConfigs) > 0 { if len(gui.State.Submodules) > 0 {
nukeStr = fmt.Sprintf("%s (%s)", nukeStr, gui.Tr.SLocalize("andResetSubmodules")) nukeStr = fmt.Sprintf("%s (%s)", nukeStr, gui.Tr.SLocalize("andResetSubmodules"))
} }

View File

@ -1197,6 +1197,12 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{ }, &i18n.Message{
ID: "andResetSubmodules", ID: "andResetSubmodules",
Other: "and reset submodules", Other: "and reset submodules",
}, &i18n.Message{
ID: "enterSubmodule",
Other: "enter submodule",
}, &i18n.Message{
ID: "copySubmoduleNameToClipboard",
Other: "copy submodule name to clipboard",
}, },
) )
} }