Compare commits
1 Commits
create-pul
...
copy-file-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bda225f429 |
@@ -1,6 +1,6 @@
|
||||
# Custom Command Keybindings
|
||||
|
||||
You can add custom command keybindings in your config.yml (accessible by pressing 'e' on the status panel from within lazygit) like so:
|
||||
You can add custom command keybindings in your config.yml (accessible by pressing 'o' on the status panel from within lazygit) like so:
|
||||
|
||||
```yml
|
||||
customCommands:
|
||||
@@ -324,27 +324,6 @@ We don't support accessing all elements of a range selection yet. We might add t
|
||||
|
||||
If your custom keybinding collides with an inbuilt keybinding that is defined for the same context, only the custom keybinding will be executed. This also applies to the global context. However, one caveat is that if you have a custom keybinding defined on the global context for some key, and there is an in-built keybinding defined for the same key and for a specific context (say the 'files' context), then the in-built keybinding will take precedence. See how to change in-built keybindings [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#keybindings)
|
||||
|
||||
## Menus of custom commands
|
||||
|
||||
For custom commands that are not used very frequently it may be preferable to hide them in a menu; you can assign a key to open the menu, and the commands will appear inside. This has the advantage that you don't have to come up with individual unique keybindings for all those commands that you don't use often; the keybindings for the commands in the menu only need to be unique within the menu. Here is an example:
|
||||
|
||||
```yml
|
||||
customCommands:
|
||||
- key: X
|
||||
description: "Copy/paste commits across repos"
|
||||
commandMenu:
|
||||
- key: c
|
||||
command: 'git format-patch --stdout {{.SelectedCommitRange.From}}^..{{.SelectedCommitRange.To}} | pbcopy'
|
||||
context: commits, subCommits
|
||||
description: "Copy selected commits to clipboard"
|
||||
- key: v
|
||||
command: 'pbpaste | git am'
|
||||
context: "commits"
|
||||
description: "Paste selected commits from clipboard"
|
||||
```
|
||||
|
||||
If you use the commandMenu property, none of the other properties except key and description can be used.
|
||||
|
||||
## Debugging
|
||||
|
||||
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `showOutput: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved.
|
||||
|
||||
@@ -279,6 +279,13 @@ func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommand
|
||||
return self.cmd.New(cmdArgs).DontLog()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) ShowFileContentCmdObj(hash string, filePath string) oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("show").
|
||||
Arg(fmt.Sprintf("%s:%s", hash, filePath)).
|
||||
ToArgv()
|
||||
return self.cmd.New(cmdArgs).DontLog()
|
||||
}
|
||||
|
||||
// Revert reverts the selected commit by hash
|
||||
func (self *CommitCommands) Revert(hash string) error {
|
||||
cmdArgs := NewGitCmd("revert").Arg(hash).ToArgv()
|
||||
|
||||
@@ -614,16 +614,12 @@ 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)
|
||||
Command string `yaml:"command" jsonschema:"example=git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD"`
|
||||
// If true, run the command in a subprocess (e.g. if the command requires user input)
|
||||
// [dev] Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
Subprocess *bool `yaml:"subprocess"`
|
||||
Subprocess bool `yaml:"subprocess"`
|
||||
// A list of prompts that will request user input before running the final command
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
// Text to display while waiting for command to finish
|
||||
@@ -631,24 +627,13 @@ type CustomCommand struct {
|
||||
// Label for the custom command when displayed in the keybindings menu
|
||||
Description string `yaml:"description"`
|
||||
// If true, stream the command's output to the Command Log panel
|
||||
// [dev] Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
Stream *bool `yaml:"stream"`
|
||||
Stream bool `yaml:"stream"`
|
||||
// If true, show the command's output in a popup within Lazygit
|
||||
// [dev] Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
ShowOutput *bool `yaml:"showOutput"`
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
// The title to display in the popup panel if showOutput is true. If left unset, the command will be used as the title.
|
||||
OutputTitle string `yaml:"outputTitle"`
|
||||
// Actions to take after the command has completed
|
||||
// [dev] Pointer so that we can tell whether it appears in the config file
|
||||
After *CustomCommandAfterHook `yaml:"after"`
|
||||
}
|
||||
|
||||
func (c *CustomCommand) GetDescription() string {
|
||||
if c.Description != "" {
|
||||
return c.Description
|
||||
}
|
||||
|
||||
return c.Command
|
||||
After CustomCommandAfterHook `yaml:"after"`
|
||||
}
|
||||
|
||||
type CustomCommandPrompt struct {
|
||||
|
||||
@@ -96,23 +96,6 @@ func validateCustomCommands(customCommands []CustomCommand) error {
|
||||
if err := validateCustomCommandKey(customCommand.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(customCommand.CommandMenu) > 0 &&
|
||||
(len(customCommand.Context) > 0 ||
|
||||
len(customCommand.Command) > 0 ||
|
||||
customCommand.Subprocess != nil ||
|
||||
len(customCommand.Prompts) > 0 ||
|
||||
len(customCommand.LoadingText) > 0 ||
|
||||
customCommand.Stream != nil ||
|
||||
customCommand.ShowOutput != nil ||
|
||||
len(customCommand.OutputTitle) > 0 ||
|
||||
customCommand.After != nil) {
|
||||
commandRef := ""
|
||||
if len(customCommand.Key) > 0 {
|
||||
commandRef = fmt.Sprintf(" with key '%s'", customCommand.Key)
|
||||
}
|
||||
return fmt.Errorf("Error with custom command%s: it is not allowed to use both commandMenu and any of the other fields except key and description.", commandRef)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -74,58 +74,6 @@ func TestUserConfigValidate_enums(t *testing.T) {
|
||||
{value: "invalid_value", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom command sub menu",
|
||||
setup: func(config *UserConfig, _ string) {
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Description: "My Custom Commands",
|
||||
CommandMenu: []CustomCommand{
|
||||
{Key: "1", Command: "echo 'hello'", Context: "global"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom command sub menu",
|
||||
setup: func(config *UserConfig, _ string) {
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Context: "global",
|
||||
CommandMenu: []CustomCommand{
|
||||
{Key: "1", Command: "echo 'hello'", Context: "global"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom command sub menu",
|
||||
setup: func(config *UserConfig, _ string) {
|
||||
falseVal := false
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Subprocess: &falseVal,
|
||||
CommandMenu: []CustomCommand{
|
||||
{Key: "1", Command: "echo 'hello'", Context: "global"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
|
||||
@@ -203,6 +203,16 @@ func (self *CommitFilesController) copyDiffToClipboard(path string, toastMessage
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) copyFileContentToClipboard(path string) error {
|
||||
_, to := self.context().GetFromAndToForDiff()
|
||||
cmdObj := self.c.Git().Commit.ShowFileContentCmdObj(to, path)
|
||||
diff, err := cmdObj.RunWithOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.OS().CopyToClipboard(diff)
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) openCopyMenu() error {
|
||||
node := self.context().GetSelected()
|
||||
|
||||
@@ -246,6 +256,27 @@ func (self *CommitFilesController) openCopyMenu() error {
|
||||
DisabledReason: self.require(self.itemsSelected())(),
|
||||
Key: 'a',
|
||||
}
|
||||
copyFileContentItem := &types.MenuItem{
|
||||
Label: self.c.Tr.CopyFileContent,
|
||||
OnPress: func() error {
|
||||
if err := self.copyFileContentToClipboard(node.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Toast(self.c.Tr.FileContentCopiedToast)
|
||||
return nil
|
||||
},
|
||||
DisabledReason: self.require(self.singleItemSelected(
|
||||
func(node *filetree.CommitFileNode) *types.DisabledReason {
|
||||
if !node.IsFile() {
|
||||
return &types.DisabledReason{
|
||||
Text: self.c.Tr.ErrCannotCopyContentOfDirectory,
|
||||
ShowErrorInPanel: true,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))(),
|
||||
Key: 'c',
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.CopyToClipboardMenu,
|
||||
@@ -254,6 +285,7 @@ func (self *CommitFilesController) openCopyMenu() error {
|
||||
copyPathItem,
|
||||
copyFileDiffItem,
|
||||
copyAllDiff,
|
||||
copyFileContentItem,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package custom_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"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 *helpers.HelperCommon
|
||||
c *common.Common
|
||||
handlerCreator *HandlerCreator
|
||||
keybindingCreator *KeybindingCreator
|
||||
}
|
||||
@@ -32,7 +28,7 @@ func NewClient(
|
||||
keybindingCreator := NewKeybindingCreator(c)
|
||||
|
||||
return &Client{
|
||||
c: c,
|
||||
c: c.Common,
|
||||
keybindingCreator: keybindingCreator,
|
||||
handlerCreator: handlerCreator,
|
||||
}
|
||||
@@ -41,81 +37,13 @@ func NewClient(
|
||||
func (self *Client) GetCustomCommandKeybindings() ([]*types.Binding, error) {
|
||||
bindings := []*types.Binding{}
|
||||
for _, customCommand := range self.c.UserConfig().CustomCommands {
|
||||
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...)
|
||||
handler := self.handlerCreator.call(customCommand)
|
||||
compoundBindings, err := self.keybindingCreator.call(customCommand, handler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
|
||||
|
||||
cmdObj := self.c.OS().Cmd.NewShell(cmdStr)
|
||||
|
||||
if customCommand.Subprocess != nil && *customCommand.Subprocess {
|
||||
if customCommand.Subprocess {
|
||||
return self.c.RunSubprocessAndRefresh(cmdObj)
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
|
||||
return self.c.WithWaitingStatus(loadingText, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
|
||||
|
||||
if customCommand.Stream != nil && *customCommand.Stream {
|
||||
if customCommand.Stream {
|
||||
cmdObj.StreamOutput()
|
||||
}
|
||||
output, err := cmdObj.RunWithOutput()
|
||||
@@ -283,14 +283,14 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if customCommand.After != nil && customCommand.After.CheckForConflicts {
|
||||
if customCommand.After.CheckForConflicts {
|
||||
return self.mergeAndRebaseHelper.CheckForConflicts(err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if customCommand.ShowOutput != nil && *customCommand.ShowOutput {
|
||||
if customCommand.ShowOutput {
|
||||
if strings.TrimSpace(output) == "" {
|
||||
output = self.c.Tr.EmptyOutput
|
||||
}
|
||||
|
||||
@@ -34,13 +34,18 @@ func (self *KeybindingCreator) call(customCommand config.CustomCommand, handler
|
||||
return nil, err
|
||||
}
|
||||
|
||||
description := customCommand.Description
|
||||
if description == "" {
|
||||
description = customCommand.Command
|
||||
}
|
||||
|
||||
return lo.Map(viewNames, func(viewName string, _ int) *types.Binding {
|
||||
return &types.Binding{
|
||||
ViewName: viewName,
|
||||
Key: keybindings.GetKey(customCommand.Key),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: handler,
|
||||
Description: customCommand.GetDescription(),
|
||||
Description: description,
|
||||
}
|
||||
}), nil
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
// compatibility. We already did this for Commit.Sha, which was renamed to Hash.
|
||||
|
||||
type Commit struct {
|
||||
Hash string
|
||||
Sha string // deprecated: use Hash
|
||||
Hash string // deprecated: use Sha
|
||||
Sha string
|
||||
Name string
|
||||
Status models.CommitStatus
|
||||
Action todo.TodoCommand
|
||||
|
||||
@@ -80,11 +80,13 @@ type TranslationSet struct {
|
||||
CopyFileDiffTooltip string
|
||||
CopySelectedDiff string
|
||||
CopyAllFilesDiff string
|
||||
CopyFileContent string
|
||||
NoContentToCopyError string
|
||||
FileNameCopiedToast string
|
||||
FilePathCopiedToast string
|
||||
FileDiffCopiedToast string
|
||||
AllFilesDiffCopiedToast string
|
||||
FileContentCopiedToast string
|
||||
FilterStagedFiles string
|
||||
FilterUnstagedFiles string
|
||||
FilterTrackedFiles string
|
||||
@@ -695,6 +697,7 @@ type TranslationSet struct {
|
||||
PatchCopiedToClipboard string
|
||||
CopiedToClipboard string
|
||||
ErrCannotEditDirectory string
|
||||
ErrCannotCopyContentOfDirectory string
|
||||
ErrStageDirWithInlineMergeConflicts string
|
||||
ErrRepositoryMovedOrDeleted string
|
||||
ErrWorktreeMovedOrRemoved string
|
||||
@@ -843,8 +846,6 @@ type TranslationSet struct {
|
||||
RangeSelectNotSupportedForSubmodules string
|
||||
OldCherryPickKeyWarning string
|
||||
CommandDoesNotSupportOpeningInEditor string
|
||||
CustomCommands string
|
||||
NoApplicableCommandsInThisContext string
|
||||
Actions Actions
|
||||
Bisect Bisect
|
||||
Log Log
|
||||
@@ -1119,11 +1120,13 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
CopyFileDiffTooltip: "If there are staged items, this command considers only them. Otherwise, it considers all the unstaged ones.",
|
||||
CopySelectedDiff: "Diff of selected file",
|
||||
CopyAllFilesDiff: "Diff of all files",
|
||||
CopyFileContent: "Content of selected file",
|
||||
NoContentToCopyError: "Nothing to copy",
|
||||
FileNameCopiedToast: "File name copied to clipboard",
|
||||
FilePathCopiedToast: "File path copied to clipboard",
|
||||
FileDiffCopiedToast: "File diff copied to clipboard",
|
||||
AllFilesDiffCopiedToast: "All files diff copied to clipboard",
|
||||
FileContentCopiedToast: "File content copied to clipboard",
|
||||
FilterStagedFiles: "Show only staged files",
|
||||
FilterUnstagedFiles: "Show only unstaged files",
|
||||
FilterTrackedFiles: "Show only tracked files",
|
||||
@@ -1735,6 +1738,7 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
PatchCopiedToClipboard: "Patch copied to clipboard",
|
||||
CopiedToClipboard: "copied to clipboard",
|
||||
ErrCannotEditDirectory: "Cannot edit directories: you can only edit individual files",
|
||||
ErrCannotCopyContentOfDirectory: "Cannot copy content of directories: you can only copy content of individual files",
|
||||
ErrStageDirWithInlineMergeConflicts: "Cannot stage/unstage directory containing files with inline merge conflicts. Please fix up the merge conflicts first",
|
||||
ErrRepositoryMovedOrDeleted: "Cannot find repo. It might have been moved or deleted ¯\\_(ツ)_/¯",
|
||||
CommandLog: "Command log",
|
||||
@@ -1881,8 +1885,6 @@ 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)
|
||||
|
||||
@@ -19,7 +19,7 @@ var CheckForConflicts = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Key: "m",
|
||||
Context: "localBranches",
|
||||
Command: "git merge {{ .SelectedLocalBranch.Name | quote }}",
|
||||
After: &config.CustomCommandAfterHook{
|
||||
After: config.CustomCommandAfterHook{
|
||||
CheckForConflicts: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
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"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -15,9 +15,10 @@ var GlobalContext = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Context: "global",
|
||||
Command: "touch myfile",
|
||||
Key: "X",
|
||||
Context: "global",
|
||||
Command: "touch myfile",
|
||||
ShowOutput: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -15,9 +15,10 @@ var MultipleContexts = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Context: "commits, reflogCommits",
|
||||
Command: "touch myfile",
|
||||
Key: "X",
|
||||
Context: "commits, reflogCommits",
|
||||
Command: "touch myfile",
|
||||
ShowOutput: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
@@ -15,19 +15,18 @@ var ShowOutputInPanel = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.EmptyCommit("my change")
|
||||
},
|
||||
SetupConfig: func(cfg *config.AppConfig) {
|
||||
trueVal := true
|
||||
cfg.GetUserConfig().CustomCommands = []config.CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Context: "commits",
|
||||
Command: "printf '%s' '{{ .SelectedLocalCommit.Name }}'",
|
||||
ShowOutput: &trueVal,
|
||||
ShowOutput: true,
|
||||
},
|
||||
{
|
||||
Key: "Y",
|
||||
Context: "commits",
|
||||
Command: "printf '%s' '{{ .SelectedLocalCommit.Name }}'",
|
||||
ShowOutput: &trueVal,
|
||||
ShowOutput: true,
|
||||
OutputTitle: "Subject of commit {{ .SelectedLocalCommit.Hash }}",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -23,17 +23,21 @@ var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.CreateDir("dir")
|
||||
shell.CreateFileAndAdd("dir/file1", "1st line\n")
|
||||
shell.Commit("1")
|
||||
shell.CreateFileAndAdd("dir/file1", "1st line\n2nd line\n")
|
||||
shell.UpdateFileAndAdd("dir/file1", "1st line\n2nd line\n")
|
||||
shell.CreateFileAndAdd("dir/file2", "file2\n")
|
||||
shell.Commit("2")
|
||||
shell.UpdateFileAndAdd("dir/file1", "1st line\n2nd line\n3rd line\n")
|
||||
shell.Commit("3")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("2").IsSelected(),
|
||||
Contains("3").IsSelected(),
|
||||
Contains("2"),
|
||||
Contains("1"),
|
||||
).
|
||||
SelectNextItem().
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
@@ -91,11 +95,22 @@ var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Contains("diff --git a/dir/file1 b/dir/file1").Contains("+2nd line").DoesNotContain("+1st line").
|
||||
Contains("diff --git a/dir/file2 b/dir/file2").Contains("+file2"))
|
||||
})
|
||||
}).
|
||||
Press(keys.Files.CopyFileInfoToClipboard).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Copy to clipboard")).
|
||||
Select(Contains("Content of selected file")).
|
||||
Confirm().
|
||||
Tap(func() {
|
||||
t.ExpectToast(Equals("File content copied to clipboard"))
|
||||
expectClipboard(t, Equals("1st line\n2nd line\n"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
// Select both commits
|
||||
// Select commits 1 and 2
|
||||
Press(keys.Universal.RangeSelectDown).
|
||||
PressEnter()
|
||||
|
||||
@@ -118,6 +133,17 @@ var CopyToClipboard = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
expectClipboard(t,
|
||||
Contains("diff --git a/dir/file1 b/dir/file1").Contains("+1st line").Contains("+2nd line"))
|
||||
})
|
||||
}).
|
||||
Press(keys.Files.CopyFileInfoToClipboard).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().
|
||||
Title(Equals("Copy to clipboard")).
|
||||
Select(Contains("Content of selected file")).
|
||||
Confirm().
|
||||
Tap(func() {
|
||||
t.ExpectToast(Equals("File content copied to clipboard"))
|
||||
expectClipboard(t, Equals("1st line\n2nd line\n"))
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -140,7 +140,6 @@ 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,
|
||||
|
||||
@@ -63,13 +63,6 @@
|
||||
"type": "string",
|
||||
"description": "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"
|
||||
},
|
||||
"commandMenu": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/CustomCommand"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "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.\nWhen using this, all other fields except Key and Description are ignored and must be empty."
|
||||
},
|
||||
"context": {
|
||||
"type": "string",
|
||||
"description": "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\".",
|
||||
|
||||
Reference in New Issue
Block a user