1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-08-02 01:26:53 +03:00

Add commandMenu property to custom commands

This commit is contained in:
Stefan Haller
2025-02-20 10:25:14 +01:00
parent e799976b8a
commit 22512d55a8
7 changed files with 191 additions and 8 deletions

View File

@ -614,6 +614,9 @@ type CustomCommandAfterHook struct {
type CustomCommand struct {
// The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
Key string `yaml:"key"`
// Instead of defining a single custom command, create a menu of custom commands. Useful for grouping related commands together under a single keybinding, and for keeping them out of the global keybindings menu.
// When using this, all other fields except Key and Description are ignored and must be empty.
CommandMenu []CustomCommand `yaml:"commandMenu"`
// The context in which to listen for the key. Valid values are: status, files, worktrees, localBranches, remotes, remoteBranches, tags, commits, reflogCommits, subCommits, commitFiles, stash, and global. Multiple contexts separated by comma are allowed; most useful for "commits, subCommits" or "files, commitFiles".
Context string `yaml:"context" jsonschema:"example=status,example=files,example=worktrees,example=localBranches,example=remotes,example=remoteBranches,example=tags,example=commits,example=reflogCommits,example=subCommits,example=commitFiles,example=stash,example=global"`
// The command to run (using Go template syntax for placeholder values)

View File

@ -1,15 +1,19 @@
package custom_commands
import (
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/samber/lo"
)
// Client is the entry point to this package. It returns a list of keybindings based on the config's user-defined custom commands.
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Command_Keybindings.md for more info.
type Client struct {
c *common.Common
c *helpers.HelperCommon
handlerCreator *HandlerCreator
keybindingCreator *KeybindingCreator
}
@ -28,7 +32,7 @@ func NewClient(
keybindingCreator := NewKeybindingCreator(c)
return &Client{
c: c.Common,
c: c,
keybindingCreator: keybindingCreator,
handlerCreator: handlerCreator,
}
@ -37,13 +41,81 @@ func NewClient(
func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) {
bindings := []*types.Binding{}
for _, customCommand := range self.c.UserConfig().CustomCommands {
handler := self.handlerCreator.call(customCommand)
compoundBindings, err := self.keybindingCreator.call(customCommand, handler)
if err != nil {
return nil, err
if len(customCommand.CommandMenu) > 0 {
handler := func() error {
return self.showCustomCommandsMenu(customCommand)
}
bindings = append(bindings, &types.Binding{
ViewName: "", // custom commands menus are global; we filter the commands inside by context
Key: keybindings.GetKey(customCommand.Key),
Modifier: gocui.ModNone,
Handler: handler,
Description: getCustomCommandsMenuDescription(customCommand, self.c.Tr),
OpensMenu: true,
})
} else {
handler := self.handlerCreator.call(customCommand)
compoundBindings, err := self.keybindingCreator.call(customCommand, handler)
if err != nil {
return nil, err
}
bindings = append(bindings, compoundBindings...)
}
bindings = append(bindings, compoundBindings...)
}
return bindings, nil
}
func (self *Client) showCustomCommandsMenu(customCommand config.CustomCommand) error {
menuItems := make([]*types.MenuItem, 0, len(customCommand.CommandMenu))
for _, subCommand := range customCommand.CommandMenu {
if len(subCommand.CommandMenu) > 0 {
handler := func() error {
return self.showCustomCommandsMenu(subCommand)
}
menuItems = append(menuItems, &types.MenuItem{
Label: subCommand.GetDescription(),
Key: keybindings.GetKey(subCommand.Key),
OnPress: handler,
OpensMenu: true,
})
} else {
if subCommand.Context != "" && subCommand.Context != "global" {
viewNames, err := self.keybindingCreator.getViewNamesAndContexts(subCommand)
if err != nil {
return err
}
currentView := self.c.GocuiGui().CurrentView()
enabled := currentView != nil && lo.Contains(viewNames, currentView.Name())
if !enabled {
continue
}
}
menuItems = append(menuItems, &types.MenuItem{
Label: subCommand.GetDescription(),
Key: keybindings.GetKey(subCommand.Key),
OnPress: self.handlerCreator.call(subCommand),
})
}
}
if len(menuItems) == 0 {
menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.NoApplicableCommandsInThisContext,
OnPress: func() error { return nil },
})
}
title := getCustomCommandsMenuDescription(customCommand, self.c.Tr)
return self.c.Menu(types.CreateMenuOptions{Title: title, Items: menuItems, HideCancel: true})
}
func getCustomCommandsMenuDescription(customCommand config.CustomCommand, tr *i18n.TranslationSet) string {
if customCommand.Description != "" {
return customCommand.Description
}
return tr.CustomCommands
}

View File

@ -843,6 +843,8 @@ type TranslationSet struct {
RangeSelectNotSupportedForSubmodules string
OldCherryPickKeyWarning string
CommandDoesNotSupportOpeningInEditor string
CustomCommands string
NoApplicableCommandsInThisContext string
Actions Actions
Bisect Bisect
Log Log
@ -1879,6 +1881,8 @@ func EnglishTranslationSet() *TranslationSet {
RangeSelectNotSupportedForSubmodules: "Range select not supported for submodules",
OldCherryPickKeyWarning: "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: <something other than v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
CommandDoesNotSupportOpeningInEditor: "This command doesn't support switching to the editor",
CustomCommands: "Custom commands",
NoApplicableCommandsInThisContext: "(No applicable commands in this context)",
Actions: Actions{
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)

View File

@ -0,0 +1,75 @@
package custom_commands
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var CustomCommandsSubmenu = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Using custom commands from a custom commands menu",
ExtraCmdArgs: []string{},
Skip: false,
SetupRepo: func(shell *Shell) {},
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
{
Key: "x",
Description: "My Custom Commands",
CommandMenu: []config.CustomCommand{
{
Key: "1",
Context: "global",
Command: "touch myfile-global",
},
{
Key: "2",
Context: "files",
Command: "touch myfile-files",
},
{
Key: "3",
Context: "commits",
Command: "touch myfile-commits",
},
},
},
}
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
Focus().
IsEmpty().
Press("x").
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("My Custom Commands")).
Lines(
Contains("1 touch myfile-global"),
Contains("2 touch myfile-files"),
).
Select(Contains("touch myfile-files")).Confirm()
}).
Lines(
Contains("myfile-files"),
)
t.Views().Commits().
Focus().
Press("x").
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("My Custom Commands")).
Lines(
Contains("1 touch myfile-global"),
Contains("3 touch myfile-commits"),
)
t.GlobalPress("3")
})
t.Views().Files().
Lines(
Contains("myfile-commits"),
Contains("myfile-files"),
)
},
})

View File

@ -140,6 +140,7 @@ var tests = []*components.IntegrationTest{
custom_commands.AccessCommitProperties,
custom_commands.BasicCommand,
custom_commands.CheckForConflicts,
custom_commands.CustomCommandsSubmenu,
custom_commands.FormPrompts,
custom_commands.GlobalContext,
custom_commands.MenuFromCommand,