Convert keybinding fields to Keybinding

Until now every keybinding config field was a plain string. That meant a user
couldn't ask for two keys to invoke a command — the config silently accepted
only one form.

Convert every string-typed field across all 13 KeybindingXxxConfig structs to
Keybinding so the union type extends to every command. Defaults wrap their
single-key value in Keybinding{...} so the generated Config.md still renders one
scalar key per binding.

The alt fields keep their separate Binding registrations for now: this commit
does not yet introduce the merge mechanism that folds them into the main field —
that comes in a follow-up. Consumers previously calling opts.GetKeys on a string
field now call opts.GetKeys on the Keybinding, or take .String() / Keys[0] where
a single value is needed.

Adds a Keybinding.String helper for rendering, schema-generator work that
inlines the Keybinding union into each consuming property, and a unit test
covering the user-facing scalar/sequence YAML forms for quit.
This commit is contained in:
Stefan Haller
2026-05-25 15:32:47 +02:00
parent 06b8d5a1e4
commit 5748d82073
66 changed files with 2370 additions and 674 deletions
+4 -1
View File
@@ -591,7 +591,10 @@ notARepository: prompt
# view the output of the subprocess before returning to Lazygit.
promptToReturnFromSubprocess: true
# Keybindings
# Keybindings.
# Each binding can be a single key or a list of keys; see
# https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
# for the syntax.
keybinding:
universal:
quit: q
+2 -2
View File
@@ -50,7 +50,7 @@ Custom command keybindings will appear alongside inbuilt keybindings when you vi
For a given custom command, here are the allowed fields:
| _field_ | _description_ | required |
|-----------------|----------------------|-|
| key | The key to trigger the command. Use a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md). Custom commands without a key specified can be triggered by selecting them from the keybindings (`?`) menu | no |
| key | The key to trigger the command. Use a single key or list of keys, as described in [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md). Custom commands without a key specified can be triggered by selecting them from the keybindings (`?`) menu | no |
| command | The command to run (using Go template syntax for placeholder values) | yes |
| context | The context in which to listen for the key (see [below](#contexts)) | yes |
| prompts | A list of prompts that will request user input before running the final command | no |
@@ -193,7 +193,7 @@ The permitted option fields are:
| name | The first part of the label | no |
| description | The second part of the label | no |
| value | the value that will be used in the command | yes |
| key | Keybinding to invoke this menu option without needing to navigate to it. Can be a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) | no |
| key | Keybinding to invoke this menu option without needing to navigate to it. Use a single key or list of keys, as described in [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md) | no |
If an option has no name the value will be displayed to the user in place of the name, so you're allowed to only include the value like so:
@@ -7,6 +7,8 @@ A keybinding is one of:
- A special key name in angle brackets, e.g. `<enter>`, `<f1>`, `<up>`.
- A key with modifiers in angle brackets, e.g. `<ctrl+c>`, `<ctrl+shift+up>`.
- The literal string `<disabled>` to disable a binding.
- A list of any of the above, to bind multiple keys to the same action:
`quit: [q, <ctrl+c>]`.
### Modifiers
+7
View File
@@ -3,6 +3,7 @@ package config
import (
"encoding/json"
"fmt"
"strings"
"github.com/karimkhaleel/jsonschema"
"github.com/samber/lo"
@@ -67,6 +68,12 @@ func (k Keybinding) MarshalJSON() ([]byte, error) {
return json.Marshal([]string(k))
}
// String renders the keybinding as a human-readable label, joining
// alternates with " or " for use in help text.
func (k Keybinding) String() string {
return strings.Join(k, " or ")
}
// JSONSchema lets the schema generator describe this type as a union of a
// string and an array of strings instead of just an array.
func (Keybinding) JSONSchema() *jsonschema.Schema {
+31
View File
@@ -149,3 +149,34 @@ func TestKeybindingYAMLRoundTrip(t *testing.T) {
assert.Equal(t, original, decoded)
}
}
func TestKeybindingConfigYAMLAcceptsBothForms(t *testing.T) {
scenarios := []struct {
name string
yaml string
expected Keybinding
}{
{
name: "scalar form",
yaml: "quit: q\n",
expected: Keybinding{"q"},
},
{
name: "sequence form",
yaml: "quit: [q, <esc>]\n",
expected: Keybinding{"q", "<esc>"},
},
{
name: "block sequence form",
yaml: "quit:\n - q\n - <esc>\n",
expected: Keybinding{"q", "<esc>"},
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
var cfg KeybindingUniversalConfig
assert.NoError(t, yaml.Unmarshal([]byte(s.yaml), &cfg))
assert.Equal(t, s.expected, cfg.Quit)
})
}
}
+5 -6
View File
@@ -206,10 +206,9 @@ func GetValidatedKeyBindingKey(label string) gocui.Key {
return key
}
func GetValidatedKeyBindingKeys(label string) []gocui.Key {
k := GetValidatedKeyBindingKey(label)
if !k.IsSet() {
return nil
}
return []gocui.Key{k}
func GetValidatedKeyBindingKeys(labels Keybinding) []gocui.Key {
return lo.FilterMap(labels, func(label string, _ int) (gocui.Key, bool) {
k := GetValidatedKeyBindingKey(label)
return k, k.IsSet()
})
}
+327 -326
View File
@@ -36,7 +36,8 @@ type UserConfig struct {
NotARepository string `yaml:"notARepository" jsonschema:"enum=prompt,enum=create,enum=skip,enum=quit"`
// If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
// Keybindings
// Keybindings.
// Each binding can be a single key or a list of keys; see https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md for the syntax.
Keybinding KeybindingConfig `yaml:"keybinding"`
}
@@ -422,202 +423,202 @@ type KeybindingConfig struct {
// damn looks like we have some inconsistencies here with -alt and -alt1
type KeybindingUniversalConfig struct {
Quit string `yaml:"quit"`
QuitAlt1 string `yaml:"quit-alt1"`
SuspendApp string `yaml:"suspendApp"`
Return string `yaml:"return"`
QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"`
TogglePanel string `yaml:"togglePanel"`
PrevItem string `yaml:"prevItem"`
NextItem string `yaml:"nextItem"`
PrevItemAlt string `yaml:"prevItem-alt"`
NextItemAlt string `yaml:"nextItem-alt"`
PrevPage string `yaml:"prevPage"`
NextPage string `yaml:"nextPage"`
ScrollLeft string `yaml:"scrollLeft"`
ScrollRight string `yaml:"scrollRight"`
GotoTop string `yaml:"gotoTop"`
GotoBottom string `yaml:"gotoBottom"`
GotoTopAlt string `yaml:"gotoTop-alt"`
GotoBottomAlt string `yaml:"gotoBottom-alt"`
ToggleRangeSelect string `yaml:"toggleRangeSelect"`
RangeSelectDown string `yaml:"rangeSelectDown"`
RangeSelectUp string `yaml:"rangeSelectUp"`
PrevBlock string `yaml:"prevBlock"`
NextBlock string `yaml:"nextBlock"`
PrevBlockAlt string `yaml:"prevBlock-alt"`
NextBlockAlt string `yaml:"nextBlock-alt"`
NextBlockAlt2 string `yaml:"nextBlock-alt2"`
PrevBlockAlt2 string `yaml:"prevBlock-alt2"`
JumpToBlock []string `yaml:"jumpToBlock"`
FocusMainView string `yaml:"focusMainView"`
NextMatch string `yaml:"nextMatch"`
PrevMatch string `yaml:"prevMatch"`
StartSearch string `yaml:"startSearch"`
MoveWordLeft string `yaml:"moveWordLeft"` // <alt+left> on Mac
MoveWordRight string `yaml:"moveWordRight"` // <alt+right> on Mac
BackspaceWord string `yaml:"backspaceWord"` // <alt+backspace> on Mac
ForwardDeleteWord string `yaml:"forwardDeleteWord"` // <alt+delete> on Mac
OptionMenu string `yaml:"optionMenu"`
Select string `yaml:"select"`
GoInto string `yaml:"goInto"`
Confirm string `yaml:"confirm"`
ConfirmMenu string `yaml:"confirmMenu"`
ConfirmSuggestion string `yaml:"confirmSuggestion"`
ConfirmInEditor string `yaml:"confirmInEditor"` // <meta+enter> on Mac
ConfirmInEditorAlt string `yaml:"confirmInEditor-alt"`
Remove string `yaml:"remove"`
New string `yaml:"new"`
Edit string `yaml:"edit"`
OpenFile string `yaml:"openFile"`
ScrollUpMain string `yaml:"scrollUpMain"`
ScrollDownMain string `yaml:"scrollDownMain"`
ScrollUpMainAlt1 string `yaml:"scrollUpMain-alt1"`
ScrollDownMainAlt1 string `yaml:"scrollDownMain-alt1"`
ScrollUpMainAlt2 string `yaml:"scrollUpMain-alt2"`
ScrollDownMainAlt2 string `yaml:"scrollDownMain-alt2"`
ExecuteShellCommand string `yaml:"executeShellCommand"`
CreateRebaseOptionsMenu string `yaml:"createRebaseOptionsMenu"`
Push string `yaml:"pushFiles"` // 'Files' appended for legacy reasons
Pull string `yaml:"pullFiles"` // 'Files' appended for legacy reasons
Refresh string `yaml:"refresh"`
CreatePatchOptionsMenu string `yaml:"createPatchOptionsMenu"`
NextTab string `yaml:"nextTab"`
PrevTab string `yaml:"prevTab"`
NextScreenMode string `yaml:"nextScreenMode"`
PrevScreenMode string `yaml:"prevScreenMode"`
CyclePagers string `yaml:"cyclePagers"`
Undo string `yaml:"undo"`
Redo string `yaml:"redo"`
FilteringMenu string `yaml:"filteringMenu"`
DiffingMenu string `yaml:"diffingMenu"`
DiffingMenuAlt string `yaml:"diffingMenu-alt"`
CopyToClipboard string `yaml:"copyToClipboard"`
OpenRecentRepos string `yaml:"openRecentRepos"`
SubmitEditorText string `yaml:"submitEditorText"`
ExtrasMenu string `yaml:"extrasMenu"`
ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"`
IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"`
DecreaseContextInDiffView string `yaml:"decreaseContextInDiffView"`
IncreaseRenameSimilarityThreshold string `yaml:"increaseRenameSimilarityThreshold"`
DecreaseRenameSimilarityThreshold string `yaml:"decreaseRenameSimilarityThreshold"`
OpenDiffTool string `yaml:"openDiffTool"`
Quit Keybinding `yaml:"quit"`
QuitAlt1 Keybinding `yaml:"quit-alt1"`
SuspendApp Keybinding `yaml:"suspendApp"`
Return Keybinding `yaml:"return"`
QuitWithoutChangingDirectory Keybinding `yaml:"quitWithoutChangingDirectory"`
TogglePanel Keybinding `yaml:"togglePanel"`
PrevItem Keybinding `yaml:"prevItem"`
NextItem Keybinding `yaml:"nextItem"`
PrevItemAlt Keybinding `yaml:"prevItem-alt"`
NextItemAlt Keybinding `yaml:"nextItem-alt"`
PrevPage Keybinding `yaml:"prevPage"`
NextPage Keybinding `yaml:"nextPage"`
ScrollLeft Keybinding `yaml:"scrollLeft"`
ScrollRight Keybinding `yaml:"scrollRight"`
GotoTop Keybinding `yaml:"gotoTop"`
GotoBottom Keybinding `yaml:"gotoBottom"`
GotoTopAlt Keybinding `yaml:"gotoTop-alt"`
GotoBottomAlt Keybinding `yaml:"gotoBottom-alt"`
ToggleRangeSelect Keybinding `yaml:"toggleRangeSelect"`
RangeSelectDown Keybinding `yaml:"rangeSelectDown"`
RangeSelectUp Keybinding `yaml:"rangeSelectUp"`
PrevBlock Keybinding `yaml:"prevBlock"`
NextBlock Keybinding `yaml:"nextBlock"`
PrevBlockAlt Keybinding `yaml:"prevBlock-alt"`
NextBlockAlt Keybinding `yaml:"nextBlock-alt"`
NextBlockAlt2 Keybinding `yaml:"nextBlock-alt2"`
PrevBlockAlt2 Keybinding `yaml:"prevBlock-alt2"`
JumpToBlock []string `yaml:"jumpToBlock"`
FocusMainView Keybinding `yaml:"focusMainView"`
NextMatch Keybinding `yaml:"nextMatch"`
PrevMatch Keybinding `yaml:"prevMatch"`
StartSearch Keybinding `yaml:"startSearch"`
MoveWordLeft Keybinding `yaml:"moveWordLeft"` // <alt+left> on Mac
MoveWordRight Keybinding `yaml:"moveWordRight"` // <alt+right> on Mac
BackspaceWord Keybinding `yaml:"backspaceWord"` // <alt+backspace> on Mac
ForwardDeleteWord Keybinding `yaml:"forwardDeleteWord"` // <alt+delete> on Mac
OptionMenu Keybinding `yaml:"optionMenu"`
Select Keybinding `yaml:"select"`
GoInto Keybinding `yaml:"goInto"`
Confirm Keybinding `yaml:"confirm"`
ConfirmMenu Keybinding `yaml:"confirmMenu"`
ConfirmSuggestion Keybinding `yaml:"confirmSuggestion"`
ConfirmInEditor Keybinding `yaml:"confirmInEditor"` // <meta+enter> on Mac
ConfirmInEditorAlt Keybinding `yaml:"confirmInEditor-alt"`
Remove Keybinding `yaml:"remove"`
New Keybinding `yaml:"new"`
Edit Keybinding `yaml:"edit"`
OpenFile Keybinding `yaml:"openFile"`
ScrollUpMain Keybinding `yaml:"scrollUpMain"`
ScrollDownMain Keybinding `yaml:"scrollDownMain"`
ScrollUpMainAlt1 Keybinding `yaml:"scrollUpMain-alt1"`
ScrollDownMainAlt1 Keybinding `yaml:"scrollDownMain-alt1"`
ScrollUpMainAlt2 Keybinding `yaml:"scrollUpMain-alt2"`
ScrollDownMainAlt2 Keybinding `yaml:"scrollDownMain-alt2"`
ExecuteShellCommand Keybinding `yaml:"executeShellCommand"`
CreateRebaseOptionsMenu Keybinding `yaml:"createRebaseOptionsMenu"`
Push Keybinding `yaml:"pushFiles"` // 'Files' appended for legacy reasons
Pull Keybinding `yaml:"pullFiles"` // 'Files' appended for legacy reasons
Refresh Keybinding `yaml:"refresh"`
CreatePatchOptionsMenu Keybinding `yaml:"createPatchOptionsMenu"`
NextTab Keybinding `yaml:"nextTab"`
PrevTab Keybinding `yaml:"prevTab"`
NextScreenMode Keybinding `yaml:"nextScreenMode"`
PrevScreenMode Keybinding `yaml:"prevScreenMode"`
CyclePagers Keybinding `yaml:"cyclePagers"`
Undo Keybinding `yaml:"undo"`
Redo Keybinding `yaml:"redo"`
FilteringMenu Keybinding `yaml:"filteringMenu"`
DiffingMenu Keybinding `yaml:"diffingMenu"`
DiffingMenuAlt Keybinding `yaml:"diffingMenu-alt"`
CopyToClipboard Keybinding `yaml:"copyToClipboard"`
OpenRecentRepos Keybinding `yaml:"openRecentRepos"`
SubmitEditorText Keybinding `yaml:"submitEditorText"`
ExtrasMenu Keybinding `yaml:"extrasMenu"`
ToggleWhitespaceInDiffView Keybinding `yaml:"toggleWhitespaceInDiffView"`
IncreaseContextInDiffView Keybinding `yaml:"increaseContextInDiffView"`
DecreaseContextInDiffView Keybinding `yaml:"decreaseContextInDiffView"`
IncreaseRenameSimilarityThreshold Keybinding `yaml:"increaseRenameSimilarityThreshold"`
DecreaseRenameSimilarityThreshold Keybinding `yaml:"decreaseRenameSimilarityThreshold"`
OpenDiffTool Keybinding `yaml:"openDiffTool"`
}
type KeybindingStatusConfig struct {
CheckForUpdate string `yaml:"checkForUpdate"`
RecentRepos string `yaml:"recentRepos"`
AllBranchesLogGraph string `yaml:"allBranchesLogGraph"`
AllBranchesLogGraphReverse string `yaml:"allBranchesLogGraphReverse"`
CheckForUpdate Keybinding `yaml:"checkForUpdate"`
RecentRepos Keybinding `yaml:"recentRepos"`
AllBranchesLogGraph Keybinding `yaml:"allBranchesLogGraph"`
AllBranchesLogGraphReverse Keybinding `yaml:"allBranchesLogGraphReverse"`
}
type KeybindingFilesConfig struct {
CommitChanges string `yaml:"commitChanges"`
CommitChangesWithoutHook string `yaml:"commitChangesWithoutHook"`
AmendLastCommit string `yaml:"amendLastCommit"`
CommitChangesWithEditor string `yaml:"commitChangesWithEditor"`
FindBaseCommitForFixup string `yaml:"findBaseCommitForFixup"`
ConfirmDiscard string `yaml:"confirmDiscard"`
IgnoreFile string `yaml:"ignoreFile"`
RefreshFiles string `yaml:"refreshFiles"`
StashAllChanges string `yaml:"stashAllChanges"`
ViewStashOptions string `yaml:"viewStashOptions"`
ToggleStagedAll string `yaml:"toggleStagedAll"`
ViewResetOptions string `yaml:"viewResetOptions"`
Fetch string `yaml:"fetch"`
ToggleTreeView string `yaml:"toggleTreeView"`
OpenMergeOptions string `yaml:"openMergeOptions"`
OpenStatusFilter string `yaml:"openStatusFilter"`
CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"`
CollapseAll string `yaml:"collapseAll"`
ExpandAll string `yaml:"expandAll"`
CommitChanges Keybinding `yaml:"commitChanges"`
CommitChangesWithoutHook Keybinding `yaml:"commitChangesWithoutHook"`
AmendLastCommit Keybinding `yaml:"amendLastCommit"`
CommitChangesWithEditor Keybinding `yaml:"commitChangesWithEditor"`
FindBaseCommitForFixup Keybinding `yaml:"findBaseCommitForFixup"`
ConfirmDiscard Keybinding `yaml:"confirmDiscard"`
IgnoreFile Keybinding `yaml:"ignoreFile"`
RefreshFiles Keybinding `yaml:"refreshFiles"`
StashAllChanges Keybinding `yaml:"stashAllChanges"`
ViewStashOptions Keybinding `yaml:"viewStashOptions"`
ToggleStagedAll Keybinding `yaml:"toggleStagedAll"`
ViewResetOptions Keybinding `yaml:"viewResetOptions"`
Fetch Keybinding `yaml:"fetch"`
ToggleTreeView Keybinding `yaml:"toggleTreeView"`
OpenMergeOptions Keybinding `yaml:"openMergeOptions"`
OpenStatusFilter Keybinding `yaml:"openStatusFilter"`
CopyFileInfoToClipboard Keybinding `yaml:"copyFileInfoToClipboard"`
CollapseAll Keybinding `yaml:"collapseAll"`
ExpandAll Keybinding `yaml:"expandAll"`
}
type KeybindingBranchesConfig struct {
CreatePullRequest string `yaml:"createPullRequest"`
ViewPullRequestOptions string `yaml:"viewPullRequestOptions"`
OpenPullRequestInBrowser string `yaml:"openPullRequestInBrowser"`
CopyPullRequestURL string `yaml:"copyPullRequestURL"`
CheckoutBranchByName string `yaml:"checkoutBranchByName"`
ForceCheckoutBranch string `yaml:"forceCheckoutBranch"`
CheckoutPreviousBranch string `yaml:"checkoutPreviousBranch"`
RebaseBranch string `yaml:"rebaseBranch"`
RenameBranch string `yaml:"renameBranch"`
MergeIntoCurrentBranch string `yaml:"mergeIntoCurrentBranch"`
MoveCommitsToNewBranch string `yaml:"moveCommitsToNewBranch"`
ViewGitFlowOptions string `yaml:"viewGitFlowOptions"`
FastForward string `yaml:"fastForward"`
CreateTag string `yaml:"createTag"`
PushTag string `yaml:"pushTag"`
SetUpstream string `yaml:"setUpstream"`
FetchRemote string `yaml:"fetchRemote"`
AddForkRemote string `yaml:"addForkRemote"`
SortOrder string `yaml:"sortOrder"`
CreatePullRequest Keybinding `yaml:"createPullRequest"`
ViewPullRequestOptions Keybinding `yaml:"viewPullRequestOptions"`
OpenPullRequestInBrowser Keybinding `yaml:"openPullRequestInBrowser"`
CopyPullRequestURL Keybinding `yaml:"copyPullRequestURL"`
CheckoutBranchByName Keybinding `yaml:"checkoutBranchByName"`
ForceCheckoutBranch Keybinding `yaml:"forceCheckoutBranch"`
CheckoutPreviousBranch Keybinding `yaml:"checkoutPreviousBranch"`
RebaseBranch Keybinding `yaml:"rebaseBranch"`
RenameBranch Keybinding `yaml:"renameBranch"`
MergeIntoCurrentBranch Keybinding `yaml:"mergeIntoCurrentBranch"`
MoveCommitsToNewBranch Keybinding `yaml:"moveCommitsToNewBranch"`
ViewGitFlowOptions Keybinding `yaml:"viewGitFlowOptions"`
FastForward Keybinding `yaml:"fastForward"`
CreateTag Keybinding `yaml:"createTag"`
PushTag Keybinding `yaml:"pushTag"`
SetUpstream Keybinding `yaml:"setUpstream"`
FetchRemote Keybinding `yaml:"fetchRemote"`
AddForkRemote Keybinding `yaml:"addForkRemote"`
SortOrder Keybinding `yaml:"sortOrder"`
}
type KeybindingWorktreesConfig struct {
ViewWorktreeOptions string `yaml:"viewWorktreeOptions"`
ViewWorktreeOptions Keybinding `yaml:"viewWorktreeOptions"`
}
type KeybindingCommitsConfig struct {
SquashDown string `yaml:"squashDown"`
RenameCommit string `yaml:"renameCommit"`
RenameCommitWithEditor string `yaml:"renameCommitWithEditor"`
ViewResetOptions string `yaml:"viewResetOptions"`
MarkCommitAsFixup string `yaml:"markCommitAsFixup"`
SetFixupMessage string `yaml:"setFixupMessage"`
CreateFixupCommit string `yaml:"createFixupCommit"`
SquashAboveCommits string `yaml:"squashAboveCommits"`
MoveDownCommit string `yaml:"moveDownCommit"`
MoveUpCommit string `yaml:"moveUpCommit"`
AmendToCommit string `yaml:"amendToCommit"`
ResetCommitAuthor string `yaml:"resetCommitAuthor"`
PickCommit string `yaml:"pickCommit"`
RevertCommit string `yaml:"revertCommit"`
CherryPickCopy string `yaml:"cherryPickCopy"`
PasteCommits string `yaml:"pasteCommits"`
MarkCommitAsBaseForRebase string `yaml:"markCommitAsBaseForRebase"`
CreateTag string `yaml:"tagCommit"`
CheckoutCommit string `yaml:"checkoutCommit"`
ResetCherryPick string `yaml:"resetCherryPick"`
CopyCommitAttributeToClipboard string `yaml:"copyCommitAttributeToClipboard"`
OpenLogMenu string `yaml:"openLogMenu"`
OpenInBrowser string `yaml:"openInBrowser"`
OpenPullRequestInBrowser string `yaml:"openPullRequestInBrowser"`
ViewBisectOptions string `yaml:"viewBisectOptions"`
StartInteractiveRebase string `yaml:"startInteractiveRebase"`
SelectCommitsOfCurrentBranch string `yaml:"selectCommitsOfCurrentBranch"`
SquashDown Keybinding `yaml:"squashDown"`
RenameCommit Keybinding `yaml:"renameCommit"`
RenameCommitWithEditor Keybinding `yaml:"renameCommitWithEditor"`
ViewResetOptions Keybinding `yaml:"viewResetOptions"`
MarkCommitAsFixup Keybinding `yaml:"markCommitAsFixup"`
SetFixupMessage Keybinding `yaml:"setFixupMessage"`
CreateFixupCommit Keybinding `yaml:"createFixupCommit"`
SquashAboveCommits Keybinding `yaml:"squashAboveCommits"`
MoveDownCommit Keybinding `yaml:"moveDownCommit"`
MoveUpCommit Keybinding `yaml:"moveUpCommit"`
AmendToCommit Keybinding `yaml:"amendToCommit"`
ResetCommitAuthor Keybinding `yaml:"resetCommitAuthor"`
PickCommit Keybinding `yaml:"pickCommit"`
RevertCommit Keybinding `yaml:"revertCommit"`
CherryPickCopy Keybinding `yaml:"cherryPickCopy"`
PasteCommits Keybinding `yaml:"pasteCommits"`
MarkCommitAsBaseForRebase Keybinding `yaml:"markCommitAsBaseForRebase"`
CreateTag Keybinding `yaml:"tagCommit"`
CheckoutCommit Keybinding `yaml:"checkoutCommit"`
ResetCherryPick Keybinding `yaml:"resetCherryPick"`
CopyCommitAttributeToClipboard Keybinding `yaml:"copyCommitAttributeToClipboard"`
OpenLogMenu Keybinding `yaml:"openLogMenu"`
OpenInBrowser Keybinding `yaml:"openInBrowser"`
OpenPullRequestInBrowser Keybinding `yaml:"openPullRequestInBrowser"`
ViewBisectOptions Keybinding `yaml:"viewBisectOptions"`
StartInteractiveRebase Keybinding `yaml:"startInteractiveRebase"`
SelectCommitsOfCurrentBranch Keybinding `yaml:"selectCommitsOfCurrentBranch"`
}
type KeybindingAmendAttributeConfig struct {
ResetAuthor string `yaml:"resetAuthor"`
SetAuthor string `yaml:"setAuthor"`
AddCoAuthor string `yaml:"addCoAuthor"`
ResetAuthor Keybinding `yaml:"resetAuthor"`
SetAuthor Keybinding `yaml:"setAuthor"`
AddCoAuthor Keybinding `yaml:"addCoAuthor"`
}
type KeybindingStashConfig struct {
PopStash string `yaml:"popStash"`
RenameStash string `yaml:"renameStash"`
PopStash Keybinding `yaml:"popStash"`
RenameStash Keybinding `yaml:"renameStash"`
}
type KeybindingCommitFilesConfig struct {
CheckoutCommitFile string `yaml:"checkoutCommitFile"`
CheckoutCommitFile Keybinding `yaml:"checkoutCommitFile"`
}
type KeybindingMainConfig struct {
ToggleSelectHunk string `yaml:"toggleSelectHunk"`
PickBothHunks string `yaml:"pickBothHunks"`
EditSelectHunk string `yaml:"editSelectHunk"`
ToggleSelectHunk Keybinding `yaml:"toggleSelectHunk"`
PickBothHunks Keybinding `yaml:"pickBothHunks"`
EditSelectHunk Keybinding `yaml:"editSelectHunk"`
}
type KeybindingSubmodulesConfig struct {
Init string `yaml:"init"`
Update string `yaml:"update"`
BulkMenu string `yaml:"bulkMenu"`
Init Keybinding `yaml:"init"`
Update Keybinding `yaml:"update"`
BulkMenu Keybinding `yaml:"bulkMenu"`
}
type KeybindingCommitMessageConfig struct {
CommitMenu string `yaml:"commitMenu"`
CommitMenu Keybinding `yaml:"commitMenu"`
}
// OSConfig contains config on the level of the os
@@ -904,191 +905,191 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
PromptToReturnFromSubprocess: true,
Keybinding: KeybindingConfig{
Universal: KeybindingUniversalConfig{
Quit: "q",
QuitAlt1: "<ctrl+c>",
SuspendApp: "<ctrl+z>",
Return: "<esc>",
QuitWithoutChangingDirectory: "Q",
TogglePanel: "<tab>",
PrevItem: "<up>",
NextItem: "<down>",
PrevItemAlt: "k",
NextItemAlt: "j",
PrevPage: ",",
NextPage: ".",
ScrollLeft: "H",
ScrollRight: "L",
GotoTop: "<",
GotoBottom: ">",
GotoTopAlt: "<home>",
GotoBottomAlt: "<end>",
ToggleRangeSelect: "v",
RangeSelectDown: "<shift+down>",
RangeSelectUp: "<shift+up>",
PrevBlock: "<left>",
NextBlock: "<right>",
PrevBlockAlt: "h",
NextBlockAlt: "l",
PrevBlockAlt2: "<backtab>",
NextBlockAlt2: "<tab>",
Quit: Keybinding{"q"},
QuitAlt1: Keybinding{"<ctrl+c>"},
SuspendApp: Keybinding{"<ctrl+z>"},
Return: Keybinding{"<esc>"},
QuitWithoutChangingDirectory: Keybinding{"Q"},
TogglePanel: Keybinding{"<tab>"},
PrevItem: Keybinding{"<up>"},
NextItem: Keybinding{"<down>"},
PrevItemAlt: Keybinding{"k"},
NextItemAlt: Keybinding{"j"},
PrevPage: Keybinding{","},
NextPage: Keybinding{"."},
ScrollLeft: Keybinding{"H"},
ScrollRight: Keybinding{"L"},
GotoTop: Keybinding{"<"},
GotoBottom: Keybinding{">"},
GotoTopAlt: Keybinding{"<home>"},
GotoBottomAlt: Keybinding{"<end>"},
ToggleRangeSelect: Keybinding{"v"},
RangeSelectDown: Keybinding{"<shift+down>"},
RangeSelectUp: Keybinding{"<shift+up>"},
PrevBlock: Keybinding{"<left>"},
NextBlock: Keybinding{"<right>"},
PrevBlockAlt: Keybinding{"h"},
NextBlockAlt: Keybinding{"l"},
PrevBlockAlt2: Keybinding{"<backtab>"},
NextBlockAlt2: Keybinding{"<tab>"},
JumpToBlock: []string{"1", "2", "3", "4", "5"},
FocusMainView: "0",
NextMatch: "n",
PrevMatch: "N",
StartSearch: "/",
MoveWordLeft: platformKeyBinding(platform, map[string]string{"darwin": "<alt+left>"}, "<ctrl+left>"),
MoveWordRight: platformKeyBinding(platform, map[string]string{"darwin": "<alt+right>"}, "<ctrl+right>"),
BackspaceWord: platformKeyBinding(platform, map[string]string{"darwin": "<alt+backspace>"}, "<ctrl+backspace>"),
ForwardDeleteWord: platformKeyBinding(platform, map[string]string{"darwin": "<alt+delete>"}, "<ctrl+delete>"),
OptionMenu: "?",
Select: "<space>",
GoInto: "<enter>",
Confirm: "<enter>",
ConfirmMenu: "<enter>",
ConfirmSuggestion: "<enter>",
ConfirmInEditor: platformKeyBinding(platform, map[string]string{"darwin": "<meta+enter>"}, "<ctrl+enter>"),
ConfirmInEditorAlt: "<ctrl+s>",
Remove: "d",
New: "n",
Edit: "e",
OpenFile: "o",
OpenRecentRepos: "<ctrl+r>",
ScrollUpMain: "<pgup>",
ScrollDownMain: "<pgdown>",
ScrollUpMainAlt1: "K",
ScrollDownMainAlt1: "J",
ScrollUpMainAlt2: "<ctrl+u>",
ScrollDownMainAlt2: "<ctrl+d>",
ExecuteShellCommand: ":",
CreateRebaseOptionsMenu: "m",
Push: "P",
Pull: "p",
Refresh: "R",
CreatePatchOptionsMenu: "<ctrl+p>",
NextTab: "]",
PrevTab: "[",
NextScreenMode: "+",
PrevScreenMode: "_",
CyclePagers: "|",
Undo: "z",
Redo: "Z",
FilteringMenu: "<ctrl+s>",
DiffingMenu: "W",
DiffingMenuAlt: "<ctrl+e>",
CopyToClipboard: "<ctrl+o>",
SubmitEditorText: "<enter>",
ExtrasMenu: "@",
ToggleWhitespaceInDiffView: "<ctrl+w>",
IncreaseContextInDiffView: "}",
DecreaseContextInDiffView: "{",
IncreaseRenameSimilarityThreshold: ")",
DecreaseRenameSimilarityThreshold: "(",
OpenDiffTool: "<ctrl+t>",
FocusMainView: Keybinding{"0"},
NextMatch: Keybinding{"n"},
PrevMatch: Keybinding{"N"},
StartSearch: Keybinding{"/"},
MoveWordLeft: Keybinding{platformKeyBinding(platform, map[string]string{"darwin": "<alt+left>"}, "<ctrl+left>")},
MoveWordRight: Keybinding{platformKeyBinding(platform, map[string]string{"darwin": "<alt+right>"}, "<ctrl+right>")},
BackspaceWord: Keybinding{platformKeyBinding(platform, map[string]string{"darwin": "<alt+backspace>"}, "<ctrl+backspace>")},
ForwardDeleteWord: Keybinding{platformKeyBinding(platform, map[string]string{"darwin": "<alt+delete>"}, "<ctrl+delete>")},
OptionMenu: Keybinding{"?"},
Select: Keybinding{"<space>"},
GoInto: Keybinding{"<enter>"},
Confirm: Keybinding{"<enter>"},
ConfirmMenu: Keybinding{"<enter>"},
ConfirmSuggestion: Keybinding{"<enter>"},
ConfirmInEditor: Keybinding{platformKeyBinding(platform, map[string]string{"darwin": "<meta+enter>"}, "<ctrl+enter>")},
ConfirmInEditorAlt: Keybinding{"<ctrl+s>"},
Remove: Keybinding{"d"},
New: Keybinding{"n"},
Edit: Keybinding{"e"},
OpenFile: Keybinding{"o"},
OpenRecentRepos: Keybinding{"<ctrl+r>"},
ScrollUpMain: Keybinding{"<pgup>"},
ScrollDownMain: Keybinding{"<pgdown>"},
ScrollUpMainAlt1: Keybinding{"K"},
ScrollDownMainAlt1: Keybinding{"J"},
ScrollUpMainAlt2: Keybinding{"<ctrl+u>"},
ScrollDownMainAlt2: Keybinding{"<ctrl+d>"},
ExecuteShellCommand: Keybinding{":"},
CreateRebaseOptionsMenu: Keybinding{"m"},
Push: Keybinding{"P"},
Pull: Keybinding{"p"},
Refresh: Keybinding{"R"},
CreatePatchOptionsMenu: Keybinding{"<ctrl+p>"},
NextTab: Keybinding{"]"},
PrevTab: Keybinding{"["},
NextScreenMode: Keybinding{"+"},
PrevScreenMode: Keybinding{"_"},
CyclePagers: Keybinding{"|"},
Undo: Keybinding{"z"},
Redo: Keybinding{"Z"},
FilteringMenu: Keybinding{"<ctrl+s>"},
DiffingMenu: Keybinding{"W"},
DiffingMenuAlt: Keybinding{"<ctrl+e>"},
CopyToClipboard: Keybinding{"<ctrl+o>"},
SubmitEditorText: Keybinding{"<enter>"},
ExtrasMenu: Keybinding{"@"},
ToggleWhitespaceInDiffView: Keybinding{"<ctrl+w>"},
IncreaseContextInDiffView: Keybinding{"}"},
DecreaseContextInDiffView: Keybinding{"{"},
IncreaseRenameSimilarityThreshold: Keybinding{")"},
DecreaseRenameSimilarityThreshold: Keybinding{"("},
OpenDiffTool: Keybinding{"<ctrl+t>"},
},
Status: KeybindingStatusConfig{
CheckForUpdate: "u",
RecentRepos: "<enter>",
AllBranchesLogGraph: "a",
AllBranchesLogGraphReverse: "A",
CheckForUpdate: Keybinding{"u"},
RecentRepos: Keybinding{"<enter>"},
AllBranchesLogGraph: Keybinding{"a"},
AllBranchesLogGraphReverse: Keybinding{"A"},
},
Files: KeybindingFilesConfig{
CommitChanges: "c",
CommitChangesWithoutHook: "w",
AmendLastCommit: "A",
CommitChangesWithEditor: "C",
FindBaseCommitForFixup: "<ctrl+f>",
IgnoreFile: "i",
RefreshFiles: "r",
StashAllChanges: "s",
ViewStashOptions: "S",
ToggleStagedAll: "a",
ViewResetOptions: "D",
Fetch: "f",
ToggleTreeView: "`",
OpenMergeOptions: "M",
OpenStatusFilter: "<ctrl+b>",
ConfirmDiscard: "x",
CopyFileInfoToClipboard: "y",
CollapseAll: "-",
ExpandAll: "=",
CommitChanges: Keybinding{"c"},
CommitChangesWithoutHook: Keybinding{"w"},
AmendLastCommit: Keybinding{"A"},
CommitChangesWithEditor: Keybinding{"C"},
FindBaseCommitForFixup: Keybinding{"<ctrl+f>"},
IgnoreFile: Keybinding{"i"},
RefreshFiles: Keybinding{"r"},
StashAllChanges: Keybinding{"s"},
ViewStashOptions: Keybinding{"S"},
ToggleStagedAll: Keybinding{"a"},
ViewResetOptions: Keybinding{"D"},
Fetch: Keybinding{"f"},
ToggleTreeView: Keybinding{"`"},
OpenMergeOptions: Keybinding{"M"},
OpenStatusFilter: Keybinding{"<ctrl+b>"},
ConfirmDiscard: Keybinding{"x"},
CopyFileInfoToClipboard: Keybinding{"y"},
CollapseAll: Keybinding{"-"},
ExpandAll: Keybinding{"="},
},
Branches: KeybindingBranchesConfig{
CopyPullRequestURL: "<ctrl+y>",
CreatePullRequest: "o",
ViewPullRequestOptions: "O",
OpenPullRequestInBrowser: "G",
CheckoutBranchByName: "c",
ForceCheckoutBranch: "F",
CheckoutPreviousBranch: "-",
RebaseBranch: "r",
RenameBranch: "R",
MergeIntoCurrentBranch: "M",
MoveCommitsToNewBranch: "N",
ViewGitFlowOptions: "i",
FastForward: "f",
CreateTag: "T",
PushTag: "P",
SetUpstream: "u",
FetchRemote: "f",
AddForkRemote: "F",
SortOrder: "s",
CopyPullRequestURL: Keybinding{"<ctrl+y>"},
CreatePullRequest: Keybinding{"o"},
ViewPullRequestOptions: Keybinding{"O"},
OpenPullRequestInBrowser: Keybinding{"G"},
CheckoutBranchByName: Keybinding{"c"},
ForceCheckoutBranch: Keybinding{"F"},
CheckoutPreviousBranch: Keybinding{"-"},
RebaseBranch: Keybinding{"r"},
RenameBranch: Keybinding{"R"},
MergeIntoCurrentBranch: Keybinding{"M"},
MoveCommitsToNewBranch: Keybinding{"N"},
ViewGitFlowOptions: Keybinding{"i"},
FastForward: Keybinding{"f"},
CreateTag: Keybinding{"T"},
PushTag: Keybinding{"P"},
SetUpstream: Keybinding{"u"},
FetchRemote: Keybinding{"f"},
AddForkRemote: Keybinding{"F"},
SortOrder: Keybinding{"s"},
},
Worktrees: KeybindingWorktreesConfig{
ViewWorktreeOptions: "w",
ViewWorktreeOptions: Keybinding{"w"},
},
Commits: KeybindingCommitsConfig{
SquashDown: "s",
RenameCommit: "r",
RenameCommitWithEditor: "R",
ViewResetOptions: "g",
MarkCommitAsFixup: "f",
SetFixupMessage: "c",
CreateFixupCommit: "F",
SquashAboveCommits: "S",
MoveDownCommit: "<ctrl+j>",
MoveUpCommit: "<ctrl+k>",
AmendToCommit: "A",
ResetCommitAuthor: "a",
PickCommit: "p",
RevertCommit: "t",
CherryPickCopy: "C",
PasteCommits: "V",
MarkCommitAsBaseForRebase: "B",
CreateTag: "T",
CheckoutCommit: "<space>",
ResetCherryPick: "<ctrl+r>",
CopyCommitAttributeToClipboard: "y",
OpenLogMenu: "<ctrl+l>",
OpenInBrowser: "o",
OpenPullRequestInBrowser: "G",
ViewBisectOptions: "b",
StartInteractiveRebase: "i",
SelectCommitsOfCurrentBranch: "*",
SquashDown: Keybinding{"s"},
RenameCommit: Keybinding{"r"},
RenameCommitWithEditor: Keybinding{"R"},
ViewResetOptions: Keybinding{"g"},
MarkCommitAsFixup: Keybinding{"f"},
SetFixupMessage: Keybinding{"c"},
CreateFixupCommit: Keybinding{"F"},
SquashAboveCommits: Keybinding{"S"},
MoveDownCommit: Keybinding{"<ctrl+j>"},
MoveUpCommit: Keybinding{"<ctrl+k>"},
AmendToCommit: Keybinding{"A"},
ResetCommitAuthor: Keybinding{"a"},
PickCommit: Keybinding{"p"},
RevertCommit: Keybinding{"t"},
CherryPickCopy: Keybinding{"C"},
PasteCommits: Keybinding{"V"},
MarkCommitAsBaseForRebase: Keybinding{"B"},
CreateTag: Keybinding{"T"},
CheckoutCommit: Keybinding{"<space>"},
ResetCherryPick: Keybinding{"<ctrl+r>"},
CopyCommitAttributeToClipboard: Keybinding{"y"},
OpenLogMenu: Keybinding{"<ctrl+l>"},
OpenInBrowser: Keybinding{"o"},
OpenPullRequestInBrowser: Keybinding{"G"},
ViewBisectOptions: Keybinding{"b"},
StartInteractiveRebase: Keybinding{"i"},
SelectCommitsOfCurrentBranch: Keybinding{"*"},
},
AmendAttribute: KeybindingAmendAttributeConfig{
ResetAuthor: "a",
SetAuthor: "A",
AddCoAuthor: "c",
ResetAuthor: Keybinding{"a"},
SetAuthor: Keybinding{"A"},
AddCoAuthor: Keybinding{"c"},
},
Stash: KeybindingStashConfig{
PopStash: "g",
RenameStash: "r",
PopStash: Keybinding{"g"},
RenameStash: Keybinding{"r"},
},
CommitFiles: KeybindingCommitFilesConfig{
CheckoutCommitFile: "c",
CheckoutCommitFile: Keybinding{"c"},
},
Main: KeybindingMainConfig{
ToggleSelectHunk: "a",
PickBothHunks: "b",
EditSelectHunk: "E",
ToggleSelectHunk: Keybinding{"a"},
PickBothHunks: Keybinding{"b"},
EditSelectHunk: Keybinding{"E"},
},
Submodules: KeybindingSubmodulesConfig{
Init: "i",
Update: "u",
BulkMenu: "b",
Init: Keybinding{"i"},
Update: Keybinding{"u"},
BulkMenu: Keybinding{"b"},
},
CommitMessage: KeybindingCommitMessageConfig{
CommitMenu: "<ctrl+o>",
CommitMenu: Keybinding{"<ctrl+o>"},
},
},
}
+1 -1
View File
@@ -114,7 +114,7 @@ func TestUserConfigValidate_enums(t *testing.T) {
{
name: "Keybindings",
setup: func(config *UserConfig, value string) {
config.Keybinding.Universal.Quit = value
config.Keybinding.Universal.Quit = Keybinding{value}
},
testCases: []testCase{
{value: "", valid: true},
+10 -8
View File
@@ -4,6 +4,8 @@
package gocui
import "github.com/samber/lo"
// Editor interface must be satisfied by gocui editors.
type Editor interface {
Edit(v *View, key Key) bool
@@ -23,19 +25,19 @@ func (f EditorFunc) Edit(v *View, key Key) bool {
var DefaultEditor Editor = EditorFunc(SimpleEditor)
var (
moveWordLeftKeybinding = NewKey(KeyArrowLeft, "", ModCtrl)
moveWordRightKeybinding = NewKey(KeyArrowRight, "", ModCtrl)
backspaceWordKeybinding = NewKey(KeyBackspace, "", ModCtrl)
forwardDeleteWordKeybinding = NewKey(KeyDelete, "", ModCtrl)
moveWordLeftKeybinding = []Key{NewKey(KeyArrowLeft, "", ModCtrl)}
moveWordRightKeybinding = []Key{NewKey(KeyArrowRight, "", ModCtrl)}
backspaceWordKeybinding = []Key{NewKey(KeyBackspace, "", ModCtrl)}
forwardDeleteWordKeybinding = []Key{NewKey(KeyDelete, "", ModCtrl)}
)
// SimpleEditor is used as the default gocui editor.
func SimpleEditor(v *View, key Key) bool {
switch {
case key.Equals(backspaceWordKeybinding),
case lo.SomeBy(backspaceWordKeybinding, func(k Key) bool { return key.Equals(k) }),
key.Equals(NewKeyStrMod("w", ModCtrl)):
v.TextArea.BackSpaceWord()
case key.Equals(forwardDeleteWordKeybinding),
case lo.SomeBy(forwardDeleteWordKeybinding, func(k Key) bool { return key.Equals(k) }),
key.Equals(NewKeyStrMod("d", ModAlt)):
v.TextArea.ForwardDeleteWord()
case key.Equals(NewKeyName(KeyBackspace)),
@@ -49,13 +51,13 @@ func SimpleEditor(v *View, key Key) bool {
case key.Equals(NewKeyName(KeyArrowUp)):
v.TextArea.MoveCursorUp()
case key.Equals(NewKeyStrMod("b", ModAlt)),
key.Equals(moveWordLeftKeybinding):
lo.SomeBy(moveWordLeftKeybinding, func(k Key) bool { return key.Equals(k) }):
v.TextArea.MoveLeftWord()
case key.Equals(NewKeyName(KeyArrowLeft)),
key.Equals(NewKeyStrMod("b", ModCtrl)):
v.TextArea.MoveCursorLeft()
case key.Equals(NewKeyStrMod("f", ModAlt)),
key.Equals(moveWordRightKeybinding):
lo.SomeBy(moveWordRightKeybinding, func(k Key) bool { return key.Equals(k) }):
v.TextArea.MoveRightWord()
case key.Equals(NewKeyName(KeyArrowRight)),
key.Equals(NewKeyStrMod("f", ModCtrl)):
+10 -10
View File
@@ -178,9 +178,9 @@ type Gui struct {
OnSearchEscape func() error
SearchEscapeKey Key
NextSearchMatchKey Key
PrevSearchMatchKey Key
SearchEscapeKeys []Key
NextSearchMatchKeys []Key
PrevSearchMatchKeys []Key
ErrorHandler func(error) error
@@ -256,9 +256,9 @@ func NewGui(opts NewGuiOpts) (*Gui, error) {
g.SupportOverlaps = opts.SupportOverlaps
// default keys for when searching strings in a view
g.SearchEscapeKey = NewKeyName(KeyEsc)
g.NextSearchMatchKey = NewKeyRune('n')
g.PrevSearchMatchKey = NewKeyRune('N')
g.SearchEscapeKeys = []Key{NewKeyName(KeyEsc)}
g.NextSearchMatchKeys = []Key{NewKeyRune('n')}
g.PrevSearchMatchKeys = []Key{NewKeyRune('N')}
g.playRecording = opts.PlayRecording
@@ -1525,11 +1525,11 @@ func (g *Gui) execKeybindings(v *View, ev *GocuiEvent) error {
// if we're searching, and we've hit n/N/Esc, we ignore the default keybinding
if v != nil && v.IsSearching() {
if ev.Key.Equals(g.NextSearchMatchKey) {
if lo.SomeBy(g.NextSearchMatchKeys, func(k Key) bool { return ev.Key.Equals(k) }) {
return v.gotoNextMatch()
} else if ev.Key.Equals(g.PrevSearchMatchKey) {
} else if lo.SomeBy(g.PrevSearchMatchKeys, func(k Key) bool { return ev.Key.Equals(k) }) {
return v.gotoPreviousMatch()
} else if ev.Key.Equals(g.SearchEscapeKey) {
} else if lo.SomeBy(g.SearchEscapeKeys, func(k Key) bool { return ev.Key.Equals(k) }) {
v.searcher.clearSearch()
if g.OnSearchEscape != nil {
if err := g.OnSearchEscape(); err != nil {
@@ -1669,7 +1669,7 @@ func (g *Gui) Snapshot() string {
return builder.String()
}
func (g *Gui) SetEditKeybindings(moveWordLeft, moveWordRight, backspaceWord, forwardDeleteWord Key) {
func (g *Gui) SetEditKeybindings(moveWordLeft, moveWordRight, backspaceWord, forwardDeleteWord []Key) {
moveWordLeftKeybinding = moveWordLeft
moveWordRightKeybinding = moveWordRight
backspaceWordKeybinding = backspaceWord
+2 -2
View File
@@ -166,8 +166,8 @@ func (self *CommitMessageContext) SetPanelState(
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionSubTitle,
map[string]string{
"togglePanelKeyBinding": self.c.UserConfig().Keybinding.Universal.TogglePanel,
"commitMenuKeybinding": self.c.UserConfig().Keybinding.CommitMessage.CommitMenu,
"togglePanelKeyBinding": self.c.UserConfig().Keybinding.Universal.TogglePanel.String(),
"commitMenuKeybinding": self.c.UserConfig().Keybinding.CommitMessage.CommitMenu.String(),
})
self.c.Views().CommitDescription.Visible = true
@@ -105,8 +105,8 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Description: self.c.Tr.CherryPickCopy,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.CherryPickCopyTooltip,
map[string]string{
"paste": opts.Config.Commits.PasteCommits,
"escape": opts.Config.Universal.Return,
"paste": opts.Config.Commits.PasteCommits.String(),
"escape": opts.Config.Universal.Return.String(),
},
),
DisplayOnScreen: true,
@@ -4,6 +4,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type CommitDescriptionController struct {
@@ -67,22 +68,24 @@ func (self *CommitDescriptionController) GetMouseKeybindings(opts types.Keybindi
func (self *CommitDescriptionController) GetOnFocus() func(types.OnFocusOpts) {
return func(types.OnFocusOpts) {
footer := ""
if self.c.UserConfig().Keybinding.Universal.ConfirmInEditor != "<disabled>" || self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt != "<disabled>" {
if self.c.UserConfig().Keybinding.Universal.ConfirmInEditor == "<disabled>" {
mainDisabled := len(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor) > 0
altDisabled := len(self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt) > 0
if !mainDisabled || !altDisabled {
if mainDisabled {
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
map[string]string{
"confirmInEditorKeybinding": self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt,
"confirmInEditorKeybinding": self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt.String(),
})
} else if self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt == "<disabled>" {
} else if altDisabled {
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
map[string]string{
"confirmInEditorKeybinding": self.c.UserConfig().Keybinding.Universal.ConfirmInEditor,
"confirmInEditorKeybinding": self.c.UserConfig().Keybinding.Universal.ConfirmInEditor.String(),
})
} else {
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooterTwoBindings,
map[string]string{
"confirmInEditorKeybinding1": self.c.UserConfig().Keybinding.Universal.ConfirmInEditor,
"confirmInEditorKeybinding2": self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt,
"confirmInEditorKeybinding1": self.c.UserConfig().Keybinding.Universal.ConfirmInEditor.String(),
"confirmInEditorKeybinding2": self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt.String(),
})
}
}
@@ -112,7 +115,7 @@ func (self *CommitDescriptionController) handleTogglePanel() error {
// ctrl key or fn key, which is unlikely to occur in pasted text. And if
// they mapped some *other* command to "<tab>", then we're totally out of
// luck.
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "<tab>" {
if self.c.GocuiGui().IsPasting && lo.Contains(self.c.UserConfig().Keybinding.Universal.TogglePanel, "<tab>") {
// Handling tabs in pasted commit messages is not optimal, but hopefully
// good enough for now. We simply insert 4 spaces without worrying about
// column alignment. This works well enough for leading indentation,
@@ -8,6 +8,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type CommitMessageController struct {
@@ -123,7 +124,7 @@ func (self *CommitMessageController) handleTogglePanel() error {
// ctrl key or fn key, which is unlikely to occur in pasted text. And if
// they mapped some *other* command to "<tab>", then we're totally out of
// luck.
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "<tab>" {
if self.c.GocuiGui().IsPasting && lo.Contains(self.c.UserConfig().Keybinding.Universal.TogglePanel, "<tab>") {
// It is unlikely that a pasted commit message contains a tab in the
// subject line, so it shouldn't matter too much how we handle it.
// Simply insert 4 spaces instead; all that matters is that we don't
@@ -183,7 +184,7 @@ func (self *CommitMessageController) confirm() error {
// to some ctrl key or fn key, which is unlikely to occur in pasted text.
// And if they mapped some *other* command to "<enter>", then we're totally
// out of luck.
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.SubmitEditorText == "<enter>" {
if self.c.GocuiGui().IsPasting && lo.Contains(self.c.UserConfig().Keybinding.Universal.SubmitEditorText, "<enter>") {
return self.switchToCommitDescription()
}
+2 -2
View File
@@ -28,8 +28,8 @@ func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error {
self.c.Tr.ForceTagPrompt,
map[string]string{
"tagName": tagName,
"cancelKey": self.c.UserConfig().Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm,
"cancelKey": self.c.UserConfig().Keybinding.Universal.Return.String(),
"confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm.String(),
},
)
force := self.c.Git().Tag.HasTag(tagName)
@@ -3,6 +3,7 @@ package controllers
import (
"log"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
@@ -39,7 +40,7 @@ func (self *JumpToSideWindowController) GetKeybindings(opts types.KeybindingsOpt
return &types.Binding{
ViewName: "",
// by default the keys are 1, 2, 3, etc
Keys: opts.GetKeys(opts.Config.Universal.JumpToBlock[index]),
Keys: opts.GetKeys(config.Keybinding{opts.Config.Universal.JumpToBlock[index]}),
Handler: opts.Guards.NoPopupPanel(self.goToSideWindow(window)),
}
})
@@ -141,7 +141,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
GetDisabledReason: self.require(self.notMidRebase(self.c.Tr.AlreadyRebasing), self.canFindCommitForQuickStart),
Description: self.c.Tr.QuickStartInteractiveRebase,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.QuickStartInteractiveRebaseTooltip, map[string]string{
"editKey": editCommitKey,
"editKey": editCommitKey.String(),
}),
},
{
@@ -161,7 +161,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.CreateFixupCommitTooltip,
map[string]string{
"squashAbove": opts.Config.Commits.SquashAboveCommits,
"squashAbove": opts.Config.Commits.SquashAboveCommits.String(),
},
),
},
@@ -679,7 +679,7 @@ func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (
if !ok || index == 0 {
errorMsg := utils.ResolvePlaceholderString(self.c.Tr.CannotQuickStartInteractiveRebase, map[string]string{
"editKey": self.c.UserConfig().Keybinding.Universal.Edit,
"editKey": self.c.UserConfig().Keybinding.Universal.Edit.String(),
})
return nil, errors.New(errorMsg)
+1 -1
View File
@@ -44,7 +44,7 @@ func (self *SubmodulesController) GetKeybindings(opts types.KeybindingsOpts) []*
GetDisabledReason: self.require(self.singleItemSelected()),
Description: self.c.Tr.Enter,
Tooltip: utils.ResolvePlaceholderString(self.c.Tr.EnterSubmoduleTooltip,
map[string]string{"escape": opts.Config.Universal.Return}),
map[string]string{"escape": opts.Config.Universal.Return.String()}),
DisplayOnScreen: true,
},
{
+2 -2
View File
@@ -256,8 +256,8 @@ func (self *SyncController) forcePushPrompt() string {
return utils.ResolvePlaceholderString(
self.c.Tr.ForcePushPrompt,
map[string]string{
"cancelKey": self.c.UserConfig().Keybinding.Universal.Return,
"confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm,
"cancelKey": self.c.UserConfig().Keybinding.Universal.Return.String(),
"confirmKey": self.c.UserConfig().Keybinding.Universal.Confirm.String(),
},
)
}
+8 -8
View File
@@ -472,15 +472,15 @@ func (gui *Gui) onUserConfigLoaded() error {
gui.setColorScheme()
gui.configureViewProperties()
gui.g.SearchEscapeKey = config.GetValidatedKeyBindingKey(userConfig.Keybinding.Universal.Return)
gui.g.NextSearchMatchKey = config.GetValidatedKeyBindingKey(userConfig.Keybinding.Universal.NextMatch)
gui.g.PrevSearchMatchKey = config.GetValidatedKeyBindingKey(userConfig.Keybinding.Universal.PrevMatch)
gui.g.SearchEscapeKeys = config.GetValidatedKeyBindingKeys(userConfig.Keybinding.Universal.Return)
gui.g.NextSearchMatchKeys = config.GetValidatedKeyBindingKeys(userConfig.Keybinding.Universal.NextMatch)
gui.g.PrevSearchMatchKeys = config.GetValidatedKeyBindingKeys(userConfig.Keybinding.Universal.PrevMatch)
gui.g.SetEditKeybindings(
config.GetValidatedKeyBindingKey(userConfig.Keybinding.Universal.MoveWordLeft),
config.GetValidatedKeyBindingKey(userConfig.Keybinding.Universal.MoveWordRight),
config.GetValidatedKeyBindingKey(userConfig.Keybinding.Universal.BackspaceWord),
config.GetValidatedKeyBindingKey(userConfig.Keybinding.Universal.ForwardDeleteWord),
config.GetValidatedKeyBindingKeys(userConfig.Keybinding.Universal.MoveWordLeft),
config.GetValidatedKeyBindingKeys(userConfig.Keybinding.Universal.MoveWordRight),
config.GetValidatedKeyBindingKeys(userConfig.Keybinding.Universal.BackspaceWord),
config.GetValidatedKeyBindingKeys(userConfig.Keybinding.Universal.ForwardDeleteWord),
)
gui.g.ShowListFooter = userConfig.Gui.ShowListFooter
@@ -1089,7 +1089,7 @@ func (gui *Gui) showIntroPopupMessage() {
introMessage := utils.ResolvePlaceholderString(
gui.c.Tr.IntroPopupMessage,
map[string]string{
"confirmationKey": gui.c.UserConfig().Keybinding.Universal.Confirm,
"confirmationKey": gui.c.UserConfig().Keybinding.Universal.Confirm.String(),
},
)
+4 -4
View File
@@ -28,10 +28,10 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
maxColumnSize := 1
essentialKeys := []gocui.Key{
config.GetValidatedKeyBindingKey(gui.c.UserConfig().Keybinding.Universal.ConfirmMenu),
config.GetValidatedKeyBindingKey(gui.c.UserConfig().Keybinding.Universal.Return),
config.GetValidatedKeyBindingKey(gui.c.UserConfig().Keybinding.Universal.PrevItem),
config.GetValidatedKeyBindingKey(gui.c.UserConfig().Keybinding.Universal.NextItem),
config.GetValidatedKeyBindingKeys(gui.c.UserConfig().Keybinding.Universal.ConfirmMenu)[0],
config.GetValidatedKeyBindingKeys(gui.c.UserConfig().Keybinding.Universal.Return)[0],
config.GetValidatedKeyBindingKeys(gui.c.UserConfig().Keybinding.Universal.PrevItem)[0],
config.GetValidatedKeyBindingKeys(gui.c.UserConfig().Keybinding.Universal.NextItem)[0],
}
for _, item := range opts.Items {
+4 -4
View File
@@ -70,7 +70,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
if currentContext.GetKey() == context.LOCAL_COMMITS_CONTEXT_KEY {
if self.c.Modes().CherryPicking.Active() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: self.c.KeybindingsOpts().Config.Commits.PasteCommits,
key: self.c.KeybindingsOpts().Config.Commits.PasteCommits.String(),
description: self.c.Tr.PasteCommits,
style: style.FgCyan,
})
@@ -78,7 +78,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
if self.c.Model().BisectInfo.Started() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: self.c.KeybindingsOpts().Config.Commits.ViewBisectOptions,
key: self.c.KeybindingsOpts().Config.Commits.ViewBisectOptions.String(),
description: self.c.Tr.ViewBisectOptions,
style: style.FgGreen,
})
@@ -88,7 +88,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
// Mode-specific global keybindings
if state := self.c.Model().WorkingTreeStateAtLastCommitRefresh; state.Any() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu,
key: self.c.KeybindingsOpts().Config.Universal.CreateRebaseOptionsMenu.String(),
description: state.OptionsMapTitle(self.c.Tr),
style: style.FgYellow,
})
@@ -96,7 +96,7 @@ func (self *OptionsMapMgr) renderContextOptionsMap() {
if self.c.Git().Patch.PatchBuilder.Active() {
optionsMap = utils.Prepend(optionsMap, bindingInfo{
key: self.c.KeybindingsOpts().Config.Universal.CreatePatchOptionsMenu,
key: self.c.KeybindingsOpts().Config.Universal.CreatePatchOptionsMenu.String(),
description: self.c.Tr.ViewPatchOptions,
style: style.FgYellow,
})
+1 -1
View File
@@ -239,7 +239,7 @@ type OnFocusLostOpts struct {
type ContextKey string
type KeybindingsOpts struct {
GetKeys func(key string) []gocui.Key
GetKeys func(keys config.Keybinding) []gocui.Key
Config config.KeybindingConfig
Guards KeybindingGuards
}
+1 -1
View File
@@ -236,7 +236,7 @@ func (gui *Gui) configureViewProperties() {
gui.Views.Stash.TitlePrefix = jumpLabels[4]
gui.Views.Main.TitlePrefix = keyToTitlePrefix(gui.c.UserConfig().Keybinding.Universal.FocusMainView)
gui.Views.Main.TitlePrefix = keyToTitlePrefix(gui.c.UserConfig().Keybinding.Universal.FocusMainView[0])
} else {
gui.Views.Status.TitlePrefix = ""
@@ -42,7 +42,7 @@ func (self *CommitDescriptionPanelDriver) GoToBeginning() *CommitDescriptionPane
}
func (self *CommitDescriptionPanelDriver) AddCoAuthor(author string) *CommitDescriptionPanelDriver {
self.t.press(self.t.keys.CommitMessage.CommitMenu)
self.t.press(self.t.keys.CommitMessage.CommitMenu[0])
self.t.ExpectPopup().Menu().Title(Equals("Commit Menu")).
Select(Contains("Add co-author")).
Confirm()
@@ -73,6 +73,6 @@ func (self *CommitMessagePanelDriver) SelectNextMessage() *CommitMessagePanelDri
}
func (self *CommitMessagePanelDriver) OpenCommitMenu() *CommitMessagePanelDriver {
self.t.press(self.t.keys.CommitMessage.CommitMenu)
self.t.press(self.t.keys.CommitMessage.CommitMenu[0])
return self
}
+6 -6
View File
@@ -68,7 +68,7 @@ func (self *PromptDriver) SuggestionTopLines(matchers ...*TextMatcher) *PromptDr
}
func (self *PromptDriver) ConfirmFirstSuggestion() {
self.t.press(self.t.keys.Universal.TogglePanel)
self.t.press(self.t.keys.Universal.TogglePanel[0])
self.t.Views().Suggestions().
IsFocused().
SelectedLineIdx(0).
@@ -76,7 +76,7 @@ func (self *PromptDriver) ConfirmFirstSuggestion() {
}
func (self *PromptDriver) ConfirmSuggestion(matcher *TextMatcher) {
self.t.press(self.t.keys.Universal.TogglePanel)
self.t.press(self.t.keys.Universal.TogglePanel[0])
self.t.Views().Suggestions().
IsFocused().
NavigateToLine(matcher).
@@ -84,19 +84,19 @@ func (self *PromptDriver) ConfirmSuggestion(matcher *TextMatcher) {
}
func (self *PromptDriver) DeleteSuggestion(matcher *TextMatcher) *PromptDriver {
self.t.press(self.t.keys.Universal.TogglePanel)
self.t.press(self.t.keys.Universal.TogglePanel[0])
self.t.Views().Suggestions().
IsFocused().
NavigateToLine(matcher)
self.t.press(self.t.keys.Universal.Remove)
self.t.press(self.t.keys.Universal.Remove[0])
return self
}
func (self *PromptDriver) EditSuggestion(matcher *TextMatcher) *PromptDriver {
self.t.press(self.t.keys.Universal.TogglePanel)
self.t.press(self.t.keys.Universal.TogglePanel[0])
self.t.Views().Suggestions().
IsFocused().
NavigateToLine(matcher)
self.t.press(self.t.keys.Universal.Edit)
self.t.press(self.t.keys.Universal.Edit[0])
return self
}
+2 -2
View File
@@ -52,8 +52,8 @@ func (self *TestDriver) click(x, y int) {
// Should only be used in specific cases where you're doing something weird!
// E.g. invoking a global keybinding from within a popup.
// You probably shouldn't use this function, and should instead go through a view like t.Views().Commit().Focus().Press(...)
func (self *TestDriver) GlobalPress(keyStr string) {
self.press(keyStr)
func (self *TestDriver) GlobalPress(key config.Keybinding) {
self.press(key[0])
}
func (self *TestDriver) typeContent(content string) {
+7 -6
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gocui"
"github.com/samber/lo"
)
@@ -376,11 +377,11 @@ func (self *ViewDriver) Focus() *ViewDriver {
currentViewTabIndex := lo.IndexOf(window.viewNames, currentViewName)
if tabIndex > currentViewTabIndex {
for range tabIndex - currentViewTabIndex {
self.t.press(self.t.keys.Universal.NextTab)
self.t.press(self.t.keys.Universal.NextTab[0])
}
} else if tabIndex < currentViewTabIndex {
for range currentViewTabIndex - tabIndex {
self.t.press(self.t.keys.Universal.PrevTab)
self.t.press(self.t.keys.Universal.PrevTab[0])
}
}
@@ -407,10 +408,10 @@ func (self *ViewDriver) IsFocused() *ViewDriver {
return self
}
func (self *ViewDriver) Press(keyStr string) *ViewDriver {
func (self *ViewDriver) Press(key config.Keybinding) *ViewDriver {
self.IsFocused()
self.t.press(keyStr)
self.t.press(key[0])
return self
}
@@ -423,10 +424,10 @@ func (self *ViewDriver) Delay() *ViewDriver {
// for use when typing or navigating, because in demos we want that to happen
// faster
func (self *ViewDriver) PressFast(keyStr string) *ViewDriver {
func (self *ViewDriver) PressFast(key config.Keybinding) *ViewDriver {
self.IsFocused()
self.t.pressFast(keyStr)
self.t.pressFast(key[0])
return self
}
+7 -7
View File
@@ -58,7 +58,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Contains("two").IsSelected(),
Contains("one"),
).
Press("n").
Press(config.Keybinding{"n"}).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (3 of 3)"))
}).
@@ -68,7 +68,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Contains("two"),
Contains("one").IsSelected(),
).
Press("n").
Press(config.Keybinding{"n"}).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)"))
}).
@@ -78,7 +78,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Contains("two"),
Contains("one"),
).
Press("n").
Press(config.Keybinding{"n"}).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (2 of 3)"))
}).
@@ -88,7 +88,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Contains("two").IsSelected(),
Contains("one"),
).
Press("N").
Press(config.Keybinding{"N"}).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)"))
}).
@@ -98,7 +98,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Contains("two"),
Contains("one"),
).
Press("N").
Press(config.Keybinding{"N"}).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (3 of 3)"))
}).
@@ -112,7 +112,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)"))
}).
Press("N").
Press(config.Keybinding{"N"}).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 'o' (1 of 3)"))
}).
@@ -146,7 +146,7 @@ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Contains("two"),
Contains("one"),
).
Press("n").
Press(config.Keybinding{"n"}).
Tap(func() {
t.Views().Search().IsVisible().Content(Contains("matches for 't' (1 of 2)"))
}).
@@ -48,13 +48,13 @@ customCommands:
).Confirm()
t.Views().Status().Content(Contains("other → master"))
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("../other/file.txt", Equals("global X"))
t.GlobalPress("Y")
t.GlobalPress(config.Keybinding{"Y"})
t.FileSystem().FileContent("../other/file.txt", Equals("local Y"))
t.GlobalPress("Z")
t.GlobalPress(config.Keybinding{"Z"})
t.FileSystem().FileContent("../other/file.txt", Equals("local Z"))
},
})
@@ -29,7 +29,7 @@ var AccessCommitProperties = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Contains("my change").IsSelected(),
).
Press("X")
Press(config.Keybinding{"X"})
hash := t.Git().GetCommitHash("HEAD")
t.FileSystem().FileContent("file.txt", Equals(fmt.Sprintf("my change\n%s\n%s", hash, hash)))
@@ -25,7 +25,7 @@ var BasicCommand = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
IsEmpty().
IsFocused().
Press("a").
Press(config.Keybinding{"a"}).
Lines(
Contains("myfile"),
)
@@ -35,7 +35,7 @@ var CheckForConflicts = NewIntegrationTest(NewIntegrationTestArgs{
Contains("second-change-branch"),
).
NavigateToLine(Contains("second-change-branch")).
Press("m")
Press(config.Keybinding{"m"})
t.Common().AcknowledgeConflicts()
},
@@ -55,7 +55,7 @@ var ConditionalPromptFalseString = NewIntegrationTest(NewIntegrationTestArgs{
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Menu().Title(Equals("Pick one")).Select(Contains("foo")).Confirm()
@@ -37,7 +37,7 @@ var ConditionalPromptFalseValue = NewIntegrationTest(NewIntegrationTestArgs{
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Prompt().Title(Equals("Enter a word")).Type("false").Confirm()
@@ -52,12 +52,12 @@ var ConditionalPrompts = NewIntegrationTest(NewIntegrationTestArgs{
// Test 1: Select "first" via key — conditional prompt should be skipped
t.Views().Files().
IsFocused().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Menu().
Title(Equals("Choose an option"))
t.Views().Menu().Press("1")
t.Views().Menu().Press(config.Keybinding{"1"})
// Detail prompt should be skipped, file should be created directly
t.Views().Files().
@@ -75,12 +75,12 @@ var ConditionalPrompts = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
IsEmpty().
IsFocused().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Menu().
Title(Equals("Choose an option"))
t.Views().Menu().Press("H")
t.Views().Menu().Press(config.Keybinding{"H"})
// Detail prompt should appear because Choice == "SECOND"
t.ExpectPopup().Prompt().Title(Equals("Enter detail for second option")).Type("extra").Confirm()
@@ -39,7 +39,7 @@ var CustomCommandsSubmenu = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
Focus().
IsEmpty().
Press("x").
Press(config.Keybinding{"x"}).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("My Custom Commands")).
@@ -55,7 +55,7 @@ var CustomCommandsSubmenu = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Commits().
Focus().
Press("x").
Press(config.Keybinding{"x"}).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("My Custom Commands")).
@@ -63,7 +63,7 @@ var CustomCommandsSubmenu = NewIntegrationTest(NewIntegrationTestArgs{
Contains("1 touch myfile-global"),
Contains("3 touch myfile-commits"),
)
t.GlobalPress("3")
t.GlobalPress(config.Keybinding{"3"})
})
t.Views().Files().
@@ -43,13 +43,13 @@ var CustomCommandsSubmenuWithSpecialKeybindings = NewIntegrationTest(NewIntegrat
},
},
}
cfg.GetUserConfig().Keybinding.Universal.ConfirmMenu = "y"
cfg.GetUserConfig().Keybinding.Universal.ConfirmMenu = config.Keybinding{"y"}
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
Focus().
IsEmpty().
Press("x").
Press(config.Keybinding{"x"}).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("My Custom Commands")).
@@ -59,14 +59,14 @@ var CustomCommandsSubmenuWithSpecialKeybindings = NewIntegrationTest(NewIntegrat
Contains(" echo y"),
Contains(" echo down"),
)
t.GlobalPress("j")
t.GlobalPress(config.Keybinding{"j"})
t.ExpectPopup().Alert().Title(Equals("echo j")).Content(Equals("j")).Confirm()
}).
Press("x").
Press(config.Keybinding{"x"}).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("My Custom Commands"))
t.GlobalPress("H")
t.GlobalPress(config.Keybinding{"H"})
t.ExpectPopup().Alert().Title(Equals("echo H")).Content(Equals("H")).Confirm()
})
},
@@ -59,7 +59,7 @@ var FormPrompts = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
IsEmpty().
IsFocused().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Prompt().Title(Equals("Enter a file name")).Type("my file").Confirm()
@@ -25,7 +25,7 @@ var GlobalContext = NewIntegrationTest(NewIntegrationTestArgs{
// commits
t.Views().Commits().
Focus().
Press("X")
Press(config.Keybinding{"X"})
t.Views().Files().
Focus().
@@ -37,7 +37,7 @@ var GlobalContext = NewIntegrationTest(NewIntegrationTestArgs{
// branches
t.Views().Branches().
Focus().
Press("X")
Press(config.Keybinding{"X"})
t.Views().Files().
Focus().
@@ -49,7 +49,7 @@ var GlobalContext = NewIntegrationTest(NewIntegrationTestArgs{
// files
t.Views().Files().
Focus().
Press("X")
Press(config.Keybinding{"X"})
t.Views().Files().
Focus().
@@ -48,7 +48,7 @@ var MenuFromCommand = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Branches().
Focus().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Menu().Title(Equals("Choose commit message")).Select(Contains("bar")).Confirm()
@@ -46,7 +46,7 @@ var MenuFromCommandsOutput = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Branches().
Focus().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Prompt().
Title(Equals("Which git command do you want to run?")).
@@ -51,14 +51,14 @@ var MenuPromptWithKeys = NewIntegrationTest(NewIntegrationTestArgs{
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Menu().
Title(Equals("Choose an option"))
// 'H' is normally a navigation key (ScrollLeft), so this tests that menu item
// keybindings have proper precedence over non-essential navigation keys
t.Views().Menu().Press("H")
t.Views().Menu().Press(config.Keybinding{"H"})
t.FileSystem().FileContent("result.txt", Equals("SECOND\n"))
},
@@ -25,7 +25,7 @@ var MultipleContexts = NewIntegrationTest(NewIntegrationTestArgs{
// commits
t.Views().Commits().
Focus().
Press("X")
Press(config.Keybinding{"X"})
t.Views().Files().
Focus().
@@ -37,7 +37,7 @@ var MultipleContexts = NewIntegrationTest(NewIntegrationTestArgs{
// branches
t.Views().Branches().
Focus().
Press("X")
Press(config.Keybinding{"X"})
t.Views().Files().
Focus().
@@ -46,7 +46,7 @@ var MultipleContexts = NewIntegrationTest(NewIntegrationTestArgs{
// files
t.Views().ReflogCommits().
Focus().
Press("X")
Press(config.Keybinding{"X"})
t.Views().Files().
Focus().
@@ -57,7 +57,7 @@ var MultiplePrompts = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
IsEmpty().
IsFocused().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Prompt().Title(Equals("Enter a file name")).Type("myfile").Confirm()
@@ -32,7 +32,7 @@ var RunCommand = NewIntegrationTest(NewIntegrationTestArgs{
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Branches().
Focus().
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Prompt().
Title(Equals("Enter a branch name")).
@@ -34,34 +34,34 @@ var SelectedCommit = NewIntegrationTest(NewIntegrationTestArgs{
NavigateToLine(Contains("commit 03"))
// SubCommits
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit 03"))
t.Views().SubCommits().PressEnter()
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit 03"))
// ReflogCommits
t.Views().ReflogCommits().Focus()
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit: commit 02"))
t.Views().ReflogCommits().PressEnter()
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit: commit 02"))
// LocalCommits
t.Views().Commits().Focus()
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit 01"))
t.Views().Commits().PressEnter()
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit 01"))
// None of these
t.Views().Files().Focus()
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit 01"))
},
})
@@ -29,13 +29,13 @@ var SelectedCommitRange = NewIntegrationTest(NewIntegrationTestArgs{
Contains("commit 01"),
)
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit 03\n"))
t.Views().Commits().Focus().
Press(keys.Universal.RangeSelectDown)
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("commit 03\ncommit 02\n"))
},
})
@@ -29,7 +29,7 @@ var SelectedPath = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().
Focus().
NavigateToLine(Contains("file2"))
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("folder2/file2"))
t.Views().Commits().
@@ -38,7 +38,7 @@ var SelectedPath = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().CommitFiles().
IsFocused().
NavigateToLine(Contains("file1"))
t.GlobalPress("X")
t.GlobalPress(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("folder1/file1"))
},
})
@@ -40,13 +40,13 @@ var SelectedSubmodule = NewIntegrationTest(NewIntegrationTestArgs{
Contains("submodule").IsSelected(),
)
t.Views().Submodules().Press("X")
t.Views().Submodules().Press(config.Keybinding{"X"})
t.FileSystem().FileContent("file.txt", Equals("path/submodule"))
t.Views().Submodules().Press("U")
t.Views().Submodules().Press(config.Keybinding{"U"})
t.FileSystem().FileContent("file.txt", Equals("../submodule"))
t.Views().Submodules().Press("N")
t.Views().Submodules().Press(config.Keybinding{"N"})
t.FileSystem().FileContent("file.txt", Equals("submodule"))
},
})
@@ -37,7 +37,7 @@ var ShowOutputInPanel = NewIntegrationTest(NewIntegrationTestArgs{
Lines(
Contains("my change").IsSelected(),
).
Press("X")
Press(config.Keybinding{"X"})
t.ExpectPopup().Alert().
// Uses cmd string as title if no outputTitle is provided
@@ -46,7 +46,7 @@ var ShowOutputInPanel = NewIntegrationTest(NewIntegrationTestArgs{
Confirm()
t.Views().Commits().
Press("Y")
Press(config.Keybinding{"Y"})
hash := t.Git().GetCommitHash("HEAD")
t.ExpectPopup().Alert().
@@ -49,7 +49,7 @@ var SuggestionsCommand = NewIntegrationTest(NewIntegrationTestArgs{
Contains("branch-three"),
Contains("branch-two"),
).
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Prompt().
Title(Equals("Enter a branch name")).
@@ -49,7 +49,7 @@ var SuggestionsPreset = NewIntegrationTest(NewIntegrationTestArgs{
Contains("branch-three"),
Contains("branch-two"),
).
Press("a")
Press(config.Keybinding{"a"})
t.ExpectPopup().Prompt().
Title(Equals("Enter a branch name")).
+1 -1
View File
@@ -63,7 +63,7 @@ var CustomCommand = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Branches().
Focus().
Wait(500).
Press("a").
Press(config.Keybinding{"a"}).
Tap(func() {
t.Wait(500)
@@ -9,8 +9,8 @@ var FilterMenuWithNoKeybindings = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Filtering the keybindings menu so that only entries without keybinding are left",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Keybinding.Universal.ToggleWhitespaceInDiffView = "<disabled>"
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Keybinding.Universal.ToggleWhitespaceInDiffView = nil
},
SetupRepo: func(shell *Shell) {
},
@@ -26,7 +26,7 @@ var ShowExecTodos = NewIntegrationTest(NewIntegrationTestArgs{
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Press("X").
Press(config.Keybinding{"X"}).
Tap(func() {
t.ExpectPopup().Alert().Title(Equals("Error")).Content(Contains("Rebasing (2/4)Executing: false")).Confirm()
}).
@@ -1,26 +0,0 @@
package misc
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var DisabledKeybindings = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Confirms you can disable keybindings by setting them to <disabled>",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Keybinding.Universal.PrevItem = "<disabled>"
config.GetUserConfig().Keybinding.Universal.NextItem = "<disabled>"
config.GetUserConfig().Keybinding.Universal.NextTab = "<up>"
config.GetUserConfig().Keybinding.Universal.PrevTab = "<down>"
},
SetupRepo: func(shell *Shell) {},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Press("<up>")
t.Views().Worktrees().IsFocused()
},
})
+1 -1
View File
@@ -44,7 +44,7 @@ var Enter = NewIntegrationTest(NewIntegrationTestArgs{
assertInSubmodule()
t.Views().Files().IsFocused().
Press("e").
Press(config.Keybinding{"e"}).
Tap(func() {
t.Views().Commits().Content(Contains("empty commit"))
}).
+1 -1
View File
@@ -46,7 +46,7 @@ var Reset = NewIntegrationTest(NewIntegrationTestArgs{
assertInSubmodule()
t.Views().Files().IsFocused().
Press("e").
Press(config.Keybinding{"e"}).
Tap(func() {
t.Views().Commits().Content(Contains("empty commit"))
t.Views().Files().Content(Contains("my_file"))
-1
View File
@@ -334,7 +334,6 @@ var tests = []*components.IntegrationTest{
misc.ConfirmOnQuit,
misc.CopyConfirmationMessageToClipboard,
misc.CopyToClipboard,
misc.DisabledKeybindings,
misc.InitialOpen,
misc.RecentReposOnLaunch,
patch_building.Apply,
@@ -15,9 +15,9 @@ var DisableSwitchTabWithPanelJumpKeys = NewIntegrationTest(NewIntegrationTestArg
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Status().Focus().
Press(keys.Universal.JumpToBlock[1])
Press(config.Keybinding{keys.Universal.JumpToBlock[1]})
t.Views().Files().IsFocused().
Press(keys.Universal.JumpToBlock[1])
Press(config.Keybinding{keys.Universal.JumpToBlock[1]})
// Despite jumping to an already focused panel,
// the tab should not change from the base files view
@@ -16,19 +16,19 @@ var SwitchTabWithPanelJumpKeys = NewIntegrationTest(NewIntegrationTestArgs{
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Worktrees().Focus().
Press(keys.Universal.JumpToBlock[2])
Press(config.Keybinding{keys.Universal.JumpToBlock[2]})
t.Views().Branches().IsFocused().
Press(keys.Universal.JumpToBlock[2])
Press(config.Keybinding{keys.Universal.JumpToBlock[2]})
t.Views().Remotes().IsFocused().
Press(keys.Universal.JumpToBlock[2])
Press(config.Keybinding{keys.Universal.JumpToBlock[2]})
t.Views().Tags().IsFocused().
Press(keys.Universal.JumpToBlock[2])
Press(config.Keybinding{keys.Universal.JumpToBlock[2]})
t.Views().Branches().IsFocused().
Press(keys.Universal.JumpToBlock[1])
Press(config.Keybinding{keys.Universal.JumpToBlock[1]})
// When jumping to a panel from a different one, keep its current tab:
t.Views().Worktrees().IsFocused()
@@ -32,7 +32,7 @@ var CustomCommand = NewIntegrationTest(NewIntegrationTestArgs{
Contains("linked-worktree"),
).
NavigateToLine(Contains("linked-worktree")).
Press("d").
Press(config.Keybinding{"d"}).
Lines(
Contains("(main worktree)"),
)
+52
View File
@@ -58,6 +58,7 @@ func customReflect(v *config.UserConfig) *jsonschema.Schema {
}
filterOutDevComments(r)
schema := r.Reflect(v)
inlineKeybindingRefs(schema)
defaultConfig := config.GetDefaultConfig()
userConfigSchema := schema.Definitions["UserConfig"]
@@ -77,6 +78,57 @@ func customReflect(v *config.UserConfig) *jsonschema.Schema {
return schema
}
// inlineKeybindingRefs replaces every `$ref: #/$defs/Keybinding` in the
// schema with the inlined oneOf union, then drops the Keybinding definition.
//
// The schema generator stores types that implement JSONSchema() as shared
// definitions and uses $ref to point at them. That works for most types
// (where every reference logically points at the same data), but for
// Keybinding fields each property carries its own description and default,
// and writing those onto the shared definition would clobber siblings.
// Inlining sidesteps the issue.
func inlineKeybindingRefs(schema *jsonschema.Schema) {
const ref = "#/$defs/Keybinding"
keybindingDef, ok := schema.Definitions["Keybinding"]
if !ok {
return
}
inline := func(s *jsonschema.Schema) {
desc := s.Description
*s = *keybindingDef
s.Description = desc
}
var visit func(s *jsonschema.Schema)
visit = func(s *jsonschema.Schema) {
if s == nil {
return
}
if s.Properties != nil {
for pair := s.Properties.Oldest(); pair != nil; pair = pair.Next() {
if pair.Value.Ref == ref {
inline(pair.Value)
} else {
visit(pair.Value)
}
}
}
if s.Items != nil {
if s.Items.Ref == ref {
inline(s.Items)
} else {
visit(s.Items)
}
}
if s.AdditionalProperties != nil {
visit(s.AdditionalProperties)
}
}
for _, def := range schema.Definitions {
visit(def)
}
delete(schema.Definitions, "Keybinding")
}
func filterOutDevComments(r *jsonschema.Reflector) {
for k, v := range r.CommentMap {
commentLines := strings.Split(v, "\n")
+1784 -164
View File
File diff suppressed because it is too large Load Diff