mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-13 17:16:23 -04:00
* Add a keybinding to push the selected branch in the Branches panel Push (shift+P) always targeted the checked-out branch, even with a different branch highlighted in the Branches panel. That made pushing any other local branch require checking it out first. git push with no refspec always targets whatever is checked out, so reaching a non-current branch requires naming it explicitly in the refspec. Add that explicit-refspec path and wire a new keybinding in the Branches panel that pushes whichever branch is selected, leaving the existing global push (used from any other panel) untouched. * Add a keybinding to pull the selected branch's linked worktree Real git has no way to merge or rebase into a branch that isn't checked out anywhere - that always needs a working tree. Fast-forward (already available via the F key) covers the clean case, but a diverged branch needs a real pull, which is only possible if the branch has its own linked worktree. Add p in the Branches panel for that case: it runs the pull in the branch's linked worktree rather than the currently open one, and is disabled when the branch has no worktree. If the pull hits a conflict in a worktree other than the one currently open, we can't drop the user into the interactive conflict-resolution flow (that UI operates on the open worktree), so we report the failure and point them at switching to it instead. * Remove the No PRs rule from AGENTS.md That rule guarded against an agent opening PRs against the upstream jesseduffield/lazygit repo. This is a personal fork with its own workflow, so the guard no longer applies here. --------- Co-authored-by: Khanh Truong <khanh_t@arbin.com>
430 lines
15 KiB
Go
430 lines
15 KiB
Go
package gui
|
|
|
|
import (
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
)
|
|
|
|
func (gui *Gui) Helpers() *helpers.Helpers {
|
|
return gui.helpers
|
|
}
|
|
|
|
// Note, the order of controllers determines the order in which keybindings appear
|
|
// in the keybinding menu: the earlier that the controller is attached to a context,
|
|
// the lower in the list the keybindings will appear.
|
|
func (gui *Gui) resetHelpersAndControllers() {
|
|
for _, context := range gui.Contexts().Flatten() {
|
|
context.ClearAllAttachedControllerFunctions()
|
|
}
|
|
|
|
helperCommon := gui.c
|
|
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
|
|
reposHelper := helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onSwitchToNewRepo)
|
|
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon)
|
|
refsHelper := helpers.NewRefsHelper(helperCommon, rebaseHelper)
|
|
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon)
|
|
worktreeHelper := helpers.NewWorktreeHelper(helperCommon, reposHelper, refsHelper, suggestionsHelper)
|
|
|
|
setCommitSummary := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
|
|
setCommitDescription := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitDescription })
|
|
getCommitSummary := func() string {
|
|
return gui.Views.CommitMessage.TextArea.GetContent()
|
|
}
|
|
|
|
getCommitDescription := func() string {
|
|
return gui.Views.CommitDescription.TextArea.GetContent()
|
|
}
|
|
getUnwrappedCommitDescription := func() string {
|
|
return gui.Views.CommitDescription.TextArea.GetUnwrappedContent()
|
|
}
|
|
commitsHelper := helpers.NewCommitsHelper(helperCommon,
|
|
getCommitSummary,
|
|
setCommitSummary,
|
|
getCommitDescription,
|
|
getUnwrappedCommitDescription,
|
|
setCommitDescription,
|
|
)
|
|
|
|
gpgHelper := helpers.NewGpgHelper(helperCommon)
|
|
viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts)
|
|
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
|
|
stagingHelper := helpers.NewStagingHelper(helperCommon)
|
|
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
|
|
searchHelper := helpers.NewSearchHelper(helperCommon)
|
|
|
|
refreshHelper := helpers.NewRefreshHelper(
|
|
helperCommon,
|
|
refsHelper,
|
|
rebaseHelper,
|
|
patchBuildingHelper,
|
|
stagingHelper,
|
|
mergeConflictsHelper,
|
|
worktreeHelper,
|
|
searchHelper,
|
|
)
|
|
diffHelper := helpers.NewDiffHelper(helperCommon)
|
|
cherryPickHelper := helpers.NewCherryPickHelper(
|
|
helperCommon,
|
|
rebaseHelper,
|
|
)
|
|
bisectHelper := helpers.NewBisectHelper(helperCommon)
|
|
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
|
|
modeHelper := helpers.NewModeHelper(
|
|
helperCommon,
|
|
diffHelper,
|
|
patchBuildingHelper,
|
|
cherryPickHelper,
|
|
rebaseHelper,
|
|
bisectHelper,
|
|
)
|
|
appStatusHelper := helpers.NewAppStatusHelper(
|
|
helperCommon,
|
|
func() *status.StatusManager { return gui.statusManager },
|
|
modeHelper,
|
|
)
|
|
|
|
gui.helpers = &helpers.Helpers{
|
|
Refs: refsHelper,
|
|
Host: helpers.NewHostHelper(helperCommon),
|
|
PatchBuilding: patchBuildingHelper,
|
|
Staging: stagingHelper,
|
|
Bisect: bisectHelper,
|
|
Suggestions: suggestionsHelper,
|
|
Files: helpers.NewFilesHelper(helperCommon),
|
|
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper, rebaseHelper),
|
|
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper, gpgHelper),
|
|
BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper),
|
|
GPG: helpers.NewGpgHelper(helperCommon),
|
|
MergeAndRebase: rebaseHelper,
|
|
MergeConflicts: mergeConflictsHelper,
|
|
CherryPick: cherryPickHelper,
|
|
Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
|
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
|
|
FixupHelper: helpers.NewFixupHelper(helperCommon),
|
|
Commits: commitsHelper,
|
|
SuspendResume: helpers.NewSuspendResumeHelper(helperCommon),
|
|
Snake: helpers.NewSnakeHelper(helperCommon),
|
|
Diff: diffHelper,
|
|
Repos: reposHelper,
|
|
RecordDirectory: recordDirectoryHelper,
|
|
Update: helpers.NewUpdateHelper(helperCommon, gui.Updater),
|
|
Window: windowHelper,
|
|
View: viewHelper,
|
|
Refresh: refreshHelper,
|
|
Confirmation: helpers.NewConfirmationHelper(helperCommon),
|
|
Mode: modeHelper,
|
|
AppStatus: appStatusHelper,
|
|
InlineStatus: helpers.NewInlineStatusHelper(helperCommon, windowHelper),
|
|
WindowArrangement: helpers.NewWindowArrangementHelper(
|
|
gui.c,
|
|
windowHelper,
|
|
modeHelper,
|
|
appStatusHelper,
|
|
),
|
|
Search: searchHelper,
|
|
Worktree: worktreeHelper,
|
|
SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper),
|
|
}
|
|
|
|
gui.CustomCommandsClient = custom_commands.NewClient(
|
|
helperCommon,
|
|
gui.helpers,
|
|
)
|
|
|
|
common := controllers.NewControllerCommon(helperCommon, gui)
|
|
|
|
syncController := controllers.NewSyncController(
|
|
common,
|
|
)
|
|
|
|
submodulesController := controllers.NewSubmodulesController(common)
|
|
|
|
bisectController := controllers.NewBisectController(common)
|
|
|
|
commitMessageController := controllers.NewCommitMessageController(
|
|
common,
|
|
)
|
|
|
|
commitDescriptionController := controllers.NewCommitDescriptionController(
|
|
common,
|
|
)
|
|
|
|
remoteBranchesController := controllers.NewRemoteBranchesController(common)
|
|
|
|
menuController := controllers.NewMenuController(common)
|
|
localCommitsController := controllers.NewLocalCommitsController(common, syncController.HandlePull)
|
|
tagsController := controllers.NewTagsController(common)
|
|
filesController := controllers.NewFilesController(
|
|
common,
|
|
)
|
|
mergeConflictsController := controllers.NewMergeConflictsController(common)
|
|
remotesController := controllers.NewRemotesController(
|
|
common,
|
|
func(branches []*models.RemoteBranch) { gui.State.Model.RemoteBranches = branches },
|
|
)
|
|
worktreesController := controllers.NewWorktreesController(common)
|
|
undoController := controllers.NewUndoController(common)
|
|
globalController := controllers.NewGlobalController(common)
|
|
contextLinesController := controllers.NewContextLinesController(common)
|
|
renameSimilarityThresholdController := controllers.NewRenameSimilarityThresholdController(common)
|
|
verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common)
|
|
viewSelectionControllerFactory := controllers.NewViewSelectionControllerFactory(common)
|
|
|
|
branchesController := controllers.NewBranchesController(common, syncController.PushBranch, syncController.PullBranch)
|
|
gitFlowController := controllers.NewGitFlowController(common)
|
|
stashController := controllers.NewStashController(common)
|
|
commitFilesController := controllers.NewCommitFilesController(common)
|
|
patchExplorerControllerFactory := controllers.NewPatchExplorerControllerFactory(common)
|
|
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)
|
|
mainViewController := controllers.NewMainViewController(common, gui.State.Contexts.Normal, gui.State.Contexts.NormalSecondary)
|
|
secondaryViewController := controllers.NewMainViewController(common, gui.State.Contexts.NormalSecondary, gui.State.Contexts.Normal)
|
|
patchBuildingController := controllers.NewPatchBuildingController(common)
|
|
snakeController := controllers.NewSnakeController(common)
|
|
reflogCommitsController := controllers.NewReflogCommitsController(common)
|
|
subCommitsController := controllers.NewSubCommitsController(common)
|
|
statusController := controllers.NewStatusController(common)
|
|
commandLogController := controllers.NewCommandLogController(common)
|
|
confirmationController := controllers.NewConfirmationController(common)
|
|
promptController := controllers.NewPromptController(common)
|
|
suggestionsController := controllers.NewSuggestionsController(common)
|
|
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common, gui.handleNextTab)
|
|
|
|
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
|
|
|
|
filterControllerFactory := controllers.NewFilterControllerFactory(common)
|
|
for _, context := range gui.c.Context().AllFilterable() {
|
|
controllers.AttachControllers(context, filterControllerFactory.Create(context))
|
|
}
|
|
|
|
searchControllerFactory := controllers.NewSearchControllerFactory(common)
|
|
for _, context := range gui.c.Context().AllSearchable() {
|
|
controllers.AttachControllers(context, searchControllerFactory.Create(context))
|
|
}
|
|
|
|
// allow for navigating between side window contexts
|
|
for _, context := range []types.Context{
|
|
gui.State.Contexts.Status,
|
|
gui.State.Contexts.Remotes,
|
|
gui.State.Contexts.Worktrees,
|
|
gui.State.Contexts.Tags,
|
|
gui.State.Contexts.Branches,
|
|
gui.State.Contexts.RemoteBranches,
|
|
gui.State.Contexts.Files,
|
|
gui.State.Contexts.Submodules,
|
|
gui.State.Contexts.ReflogCommits,
|
|
gui.State.Contexts.LocalCommits,
|
|
gui.State.Contexts.CommitFiles,
|
|
gui.State.Contexts.SubCommits,
|
|
gui.State.Contexts.Stash,
|
|
} {
|
|
controllers.AttachControllers(context, sideWindowControllerFactory.Create(context))
|
|
}
|
|
|
|
for _, context := range []controllers.CanSwitchToSubCommits{
|
|
gui.State.Contexts.Branches,
|
|
gui.State.Contexts.RemoteBranches,
|
|
gui.State.Contexts.Tags,
|
|
gui.State.Contexts.ReflogCommits,
|
|
} {
|
|
controllers.AttachControllers(context, controllers.NewSwitchToSubCommitsController(
|
|
common, context,
|
|
))
|
|
}
|
|
|
|
for _, context := range []controllers.CanSwitchToDiffFiles{
|
|
gui.State.Contexts.LocalCommits,
|
|
gui.State.Contexts.SubCommits,
|
|
gui.State.Contexts.Stash,
|
|
} {
|
|
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
|
|
common, context,
|
|
))
|
|
}
|
|
|
|
for _, context := range []types.Context{
|
|
gui.State.Contexts.Status,
|
|
gui.State.Contexts.Files,
|
|
gui.State.Contexts.Branches,
|
|
gui.State.Contexts.RemoteBranches,
|
|
gui.State.Contexts.Tags,
|
|
gui.State.Contexts.LocalCommits,
|
|
gui.State.Contexts.ReflogCommits,
|
|
gui.State.Contexts.SubCommits,
|
|
gui.State.Contexts.CommitFiles,
|
|
gui.State.Contexts.Stash,
|
|
} {
|
|
controllers.AttachControllers(context, controllers.NewSwitchToFocusedMainViewController(
|
|
common, context,
|
|
))
|
|
}
|
|
|
|
for _, context := range []controllers.ContainsCommits{
|
|
gui.State.Contexts.LocalCommits,
|
|
gui.State.Contexts.ReflogCommits,
|
|
gui.State.Contexts.SubCommits,
|
|
} {
|
|
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,
|
|
patchExplorerControllerFactory.Create(gui.State.Contexts.Staging),
|
|
verticalScrollControllerFactory.Create(gui.State.Contexts.Staging),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.StagingSecondary,
|
|
stagingSecondaryController,
|
|
patchExplorerControllerFactory.Create(gui.State.Contexts.StagingSecondary),
|
|
verticalScrollControllerFactory.Create(gui.State.Contexts.StagingSecondary),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilder,
|
|
patchBuildingController,
|
|
patchExplorerControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
|
|
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilderSecondary,
|
|
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilderSecondary),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.MergeConflicts,
|
|
mergeConflictsController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Normal,
|
|
mainViewController,
|
|
verticalScrollControllerFactory.Create(gui.State.Contexts.Normal),
|
|
viewSelectionControllerFactory.Create(gui.State.Contexts.Normal),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.NormalSecondary,
|
|
secondaryViewController,
|
|
verticalScrollControllerFactory.Create(gui.State.Contexts.NormalSecondary),
|
|
viewSelectionControllerFactory.Create(gui.State.Contexts.NormalSecondary),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Files,
|
|
filesController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Tags,
|
|
tagsController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Submodules,
|
|
submodulesController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Branches,
|
|
branchesController,
|
|
gitFlowController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.LocalCommits,
|
|
localCommitsController,
|
|
bisectController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.CommitFiles,
|
|
commitFilesController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Remotes,
|
|
remotesController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Worktrees,
|
|
worktreesController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Stash,
|
|
stashController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Menu,
|
|
menuController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.CommitMessage,
|
|
commitMessageController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.CommitDescription,
|
|
commitDescriptionController,
|
|
verticalScrollControllerFactory.Create(gui.State.Contexts.CommitDescription),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.RemoteBranches,
|
|
remoteBranchesController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Status,
|
|
statusController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.CommandLog,
|
|
commandLogController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Confirmation,
|
|
confirmationController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Prompt,
|
|
promptController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Suggestions,
|
|
suggestionsController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Search,
|
|
controllers.NewSearchPromptController(common),
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Global,
|
|
undoController,
|
|
globalController,
|
|
contextLinesController,
|
|
renameSimilarityThresholdController,
|
|
jumpToSideWindowController,
|
|
syncController,
|
|
)
|
|
|
|
controllers.AttachControllers(gui.State.Contexts.Snake,
|
|
snakeController,
|
|
)
|
|
|
|
// this must come last so that we've got our click handlers defined against the context
|
|
listControllerFactory := controllers.NewListControllerFactory(common)
|
|
for _, context := range gui.c.Context().AllList() {
|
|
controllers.AttachControllers(context, listControllerFactory.Create(context))
|
|
}
|
|
}
|
|
|
|
func (gui *Gui) getCommitMessageSetTextareaTextFn(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)
|
|
view.RenderTextArea()
|
|
}
|
|
}
|