summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pkg/config/app_config.go3
-rw-r--r--pkg/gui/branches_panel.go2
-rw-r--r--pkg/gui/confirmation_panel.go41
-rw-r--r--pkg/gui/diffing.go3
-rw-r--r--pkg/gui/files_panel.go25
-rw-r--r--pkg/gui/filtering_menu_panel.go2
-rw-r--r--pkg/gui/find_suggestions.go69
-rw-r--r--pkg/gui/layout.go1
-rw-r--r--pkg/i18n/chinese.go4
-rw-r--r--pkg/i18n/dutch.go2
-rw-r--r--pkg/i18n/english.go9
-rw-r--r--pkg/utils/slice.go28
-rw-r--r--pkg/utils/slice_test.go83
-rw-r--r--scripts/generate_cheatsheet.go2
14 files changed, 239 insertions, 35 deletions
diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go
index d41a9c202..74f675576 100644
--- a/pkg/config/app_config.go
+++ b/pkg/config/app_config.go
@@ -311,6 +311,9 @@ type AppState struct {
LastUpdateCheck int64
RecentRepos []string
StartupPopupVersion int
+
+ // these are for custom commands typed in directly, not for custom commands in the lazygit config
+ CustomCommandsHistory []string
}
func getDefaultAppState() *AppState {
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go
index e85e9fd05..e913f6f8d 100644
--- a/pkg/gui/branches_panel.go
+++ b/pkg/gui/branches_panel.go
@@ -218,7 +218,7 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
func (gui *Gui) handleCheckoutByName() error {
return gui.prompt(promptOpts{
title: gui.Tr.BranchName + ":",
- findSuggestionsFunc: gui.getBranchNameSuggestionsFunc(),
+ findSuggestionsFunc: gui.getRefsSuggestionsFunc(),
handleConfirm: func(response string) error {
return gui.handleCheckoutRef(response, handleCheckoutRefOptions{
span: "Checkout branch",
diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go
index 6664f765d..e1b78bc3a 100644
--- a/pkg/gui/confirmation_panel.go
+++ b/pkg/gui/confirmation_panel.go
@@ -3,6 +3,7 @@
package gui
import (
+ "fmt"
"strings"
"github.com/jesseduffield/gocui"
@@ -33,7 +34,6 @@ type askOpts struct {
handleConfirm func() error
handleClose func() error
handlersManageFocus bool
- findSuggestionsFunc func(string) []*types.Suggestion
}
type promptOpts struct {
@@ -50,7 +50,6 @@ func (gui *Gui) ask(opts askOpts) error {
handleConfirm: opts.handleConfirm,
handleClose: opts.handleClose,
handlersManageFocus: opts.handlersManageFocus,
- findSuggestionsFunc: opts.findSuggestionsFunc,
})
}
@@ -103,13 +102,6 @@ func (gui *Gui) wrappedPromptConfirmationFunction(handlersManageFocus bool, func
}
}
-func (gui *Gui) clearConfirmationViewKeyBindings() {
- keybindingConfig := gui.Config.GetUserConfig().Keybinding
- _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
- _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
- _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
-}
-
func (gui *Gui) closeConfirmationPrompt(handlersManageFocus bool) error {
// we've already closed it so we can just return
if !gui.Views.Confirmation.Visible {
@@ -165,7 +157,13 @@ func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, i
height/2 + panelHeight/2
}
-func (gui *Gui) prepareConfirmationPanel(title, prompt string, hasLoader bool, findSuggestionsFunc func(string) []*types.Suggestion, editable bool) error {
+func (gui *Gui) prepareConfirmationPanel(
+ title,
+ prompt string,
+ hasLoader bool,
+ findSuggestionsFunc func(string) []*types.Suggestion,
+ editable bool,
+) error {
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(true, prompt)
// calling SetView on an existing view returns the same view, so I'm not bothering
// to reassign to gui.Views.Confirmation
@@ -185,14 +183,15 @@ func (gui *Gui) prepareConfirmationPanel(title, prompt string, hasLoader bool, f
gui.findSuggestions = findSuggestionsFunc
if findSuggestionsFunc != nil {
suggestionsViewHeight := 11
- suggestionsView, err := gui.g.SetView("suggestions", x0, y1, x1, y1+suggestionsViewHeight, 0)
+ suggestionsView, err := gui.g.SetView("suggestions", x0, y1+1, x1, y1+suggestionsViewHeight, 0)
if err != nil {
return err
}
suggestionsView.Wrap = false
suggestionsView.FgColor = theme.GocuiDefaultTextColor
- gui.setSuggestions([]*types.Suggestion{})
+ gui.setSuggestions(findSuggestionsFunc(""))
suggestionsView.Visible = true
+ suggestionsView.Title = fmt.Sprintf(gui.Tr.SuggestionsTitle, gui.Config.GetUserConfig().Keybinding.Universal.TogglePanel)
}
gui.g.Update(func(g *gocui.Gui) error {
@@ -248,7 +247,7 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
gui.renderString(gui.Views.Options, actions)
var onConfirm func() error
if opts.handleConfirmPrompt != nil {
- onConfirm = gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.Views.Confirmation.Buffer() })
+ onConfirm = gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
} else {
onConfirm = gui.wrappedConfirmationFunction(opts.handlersManageFocus, opts.handleConfirm)
}
@@ -260,7 +259,11 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
}
keybindingConfig := gui.Config.GetUserConfig().Keybinding
- onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(opts.handlersManageFocus, opts.handleConfirmPrompt, func() string { return gui.getSelectedSuggestionValue() })
+ onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
+ opts.handlersManageFocus,
+ opts.handleConfirmPrompt,
+ gui.getSelectedSuggestionValue,
+ )
confirmationKeybindings := []confirmationKeybinding{
{
@@ -319,6 +322,16 @@ func (gui *Gui) setKeyBindings(opts createPopupPanelOpts) error {
return nil
}
+func (gui *Gui) clearConfirmationViewKeyBindings() {
+ keybindingConfig := gui.Config.GetUserConfig().Keybinding
+ _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("confirmation", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.ConfirmAlt1), gocui.ModNone)
+ _ = gui.g.DeleteKeybinding("suggestions", gui.getKey(keybindingConfig.Universal.Return), gocui.ModNone)
+}
+
func (gui *Gui) wrappedHandler(f func() error) func(g *gocui.Gui, v *gocui.View) error {
return func(g *gocui.Gui, v *gocui.View) error {
return f()
diff --git a/pkg/gui/diffing.go b/pkg/gui/diffing.go
index ef87b40da..e1f3afa88 100644
--- a/pkg/gui/diffing.go
+++ b/pkg/gui/diffing.go
@@ -125,7 +125,8 @@ func (gui *Gui) handleCreateDiffingMenuPanel() error {
displayString: gui.Tr.LcEnterRefToDiff,
onPress: func() error {
return gui.prompt(promptOpts{
- title: gui.Tr.LcEnteRefName,
+ title: gui.Tr.LcEnteRefName,
+ findSuggestionsFunc: gui.getRefsSuggestionsFunc(),
handleConfirm: func(response string) error {
gui.State.Modes.Diffing.Ref = strings.TrimSpace(response)
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go
index 41ac25fb8..0f9bbd65d 100644
--- a/pkg/gui/files_panel.go
+++ b/pkg/gui/files_panel.go
@@ -659,8 +659,9 @@ func (gui *Gui) handlePullFiles() error {
}
return gui.prompt(promptOpts{
- title: gui.Tr.EnterUpstream,
- initialContent: "origin/" + currentBranch.Name,
+ title: gui.Tr.EnterUpstream,
+ initialContent: "origin/" + currentBranch.Name,
+ findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc("/"),
handleConfirm: func(upstream string) error {
if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil {
errorMessage := err.Error()
@@ -798,8 +799,9 @@ func (gui *Gui) pushFiles() error {
return gui.push(pushOpts{setUpstream: true})
} else {
return gui.prompt(promptOpts{
- title: gui.Tr.EnterUpstream,
- initialContent: "origin " + currentBranch.Name,
+ title: gui.Tr.EnterUpstream,
+ initialContent: "origin " + currentBranch.Name,
+ findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc(" "),
handleConfirm: func(upstream string) error {
var upstreamBranch, upstreamRemote string
split := strings.Split(upstream, " ")
@@ -884,8 +886,21 @@ func (gui *Gui) anyFilesWithMergeConflicts() bool {
func (gui *Gui) handleCustomCommand() error {
return gui.prompt(promptOpts{
- title: gui.Tr.CustomCommand,
+ title: gui.Tr.CustomCommand,
+ findSuggestionsFunc: gui.getCustomCommandsHistorySuggestionsFunc(),
handleConfirm: func(command string) error {
+ gui.Config.GetAppState().CustomCommandsHistory = utils.Limit(
+ utils.Uniq(
+ append(gui.Config.GetAppState().CustomCommandsHistory, command),
+ ),
+ 1000,
+ )
+
+ err := gui.Config.SaveAppState()
+ if err != nil {
+ gui.Log.Error(err)
+ }
+
gui.OnRunCommand(oscommands.NewCmdLogEntry(command, gui.Tr.Spans.CustomCommand, true))
return gui.runSubprocessWithSuspenseAndRefresh(
gui.OSCommand.PrepareShellSubProcess(command),
diff --git a/pkg/gui/filtering_menu_panel.go b/pkg/gui/filtering_menu_panel.go
index c354f0996..2955f6e8d 100644
--- a/pkg/gui/filtering_menu_panel.go
+++ b/pkg/gui/filtering_menu_panel.go
@@ -36,7 +36,7 @@ func (gui *Gui) handleCreateFilteringMenuPanel() error {
onPress: func() error {
return gui.prompt(promptOpts{
findSuggestionsFunc: gui.getFilePathSuggestionsFunc(),
- title: gui.Tr.LcEnterFileName,
+ title: gui.Tr.EnterFileName,
handleConfirm: func(response string) error {
return gui.setFiltering(strings.TrimSpace(response))
},
diff --git a/pkg/gui/find_suggestions.go b/pkg/gui/find_suggestions.go
index b5ee0ecf2..343e087e8 100644
--- a/pkg/gui/find_suggestions.go
+++ b/pkg/gui/find_suggestions.go
@@ -1,6 +1,7 @@
package gui
import (
+ "fmt"
"os"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
@@ -42,11 +43,7 @@ func matchesToSuggestions(matches []string) []*types.Suggestion {
func (gui *Gui) getRemoteSuggestionsFunc() func(string) []*types.Suggestion {
remoteNames := gui.getRemoteNames()
- return func(input string) []*types.Suggestion {
- return matchesToSuggestions(
- utils.FuzzySearch(input, remoteNames),
- )
- }
+ return fuzzySearchFunc(remoteNames)
}
func (gui *Gui) getBranchNames() []string {
@@ -61,7 +58,12 @@ func (gui *Gui) getBranchNameSuggestionsFunc() func(string) []*types.Suggestion
branchNames := gui.getBranchNames()
return func(input string) []*types.Suggestion {
- matchingBranchNames := utils.FuzzySearch(sanitizedBranchName(input), branchNames)
+ var matchingBranchNames []string
+ if input == "" {
+ matchingBranchNames = branchNames
+ } else {
+ matchingBranchNames = utils.FuzzySearch(input, branchNames)
+ }
suggestions := make([]*types.Suggestion, len(matchingBranchNames))
for i, branchName := range matchingBranchNames {
@@ -79,6 +81,8 @@ func (gui *Gui) getBranchNameSuggestionsFunc() func(string) []*types.Suggestion
// gui.State.FilesTrie. On the main thread we'll be doing a fuzzy search via
// gui.State.FilesTrie. So if we've looked for a file previously, we'll start with
// the old trie and eventually it'll be swapped out for the new one.
+// Notably, unlike other suggestion functions we're not showing all the options
+// if nothing has been typed because there'll be too much to display efficiently
func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
_ = gui.WithWaitingStatus(gui.Tr.LcLoadingFileSuggestions, func() error {
trie := patricia.NewTrie()
@@ -131,3 +135,56 @@ func (gui *Gui) getFilePathSuggestionsFunc() func(string) []*types.Suggestion {
return suggestions
}
}
+
+func (gui *Gui) getRemoteBranchNames(separator string) []string {
+ result := []string{}
+ for _, remote := range gui.State.Remotes {
+ for _, branch := range remote.Branches {
+ result = append(result, fmt.Sprintf("%s%s%s", remote.Name, separator, branch.Name))
+ }
+ }
+ return result
+}
+
+func (gui *Gui) getRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
+ return fuzzySearchFunc(gui.getRemoteBranchNames(separator))
+}
+
+func (gui *Gui) getTagNames() []string {
+ result := make([]string, len(gui.State.Tags))
+ for i, tag := range gui.State.Tags {
+ result[i] = tag.Name
+ }
+ return result
+}
+
+func (gui *Gui) getRefsSuggestionsFunc() func(string) []*types.Suggestion {
+ remoteBranchNames := gui.getRemoteBranchNames("/")
+ localBranchNames := gui.getBranchNames()
+ tagNames := gui.getTagNames()
+ additionalRefNames := []string{"HEAD", "FETCH_HEAD", "MERGE_HEAD", "ORIG_HEAD"}
+
+ refNames := append(append(append(remoteBranchNames, localBranchNames...), tagNames...), additionalRefNames...)
+
+ return fuzzySearchFunc(refNames)
+}
+
+func (gui *Gui) getCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
+ // reversing so that we display the latest command first
+ history := utils.Reverse(gui.Config.GetAppState().CustomCommandsHistory)
+
+ return fuzzySearchFunc(history)
+}
+
+func fuzzySearchFunc(options []string) func(string) []*types.Suggestion {
+ return func(input string) []*types.Suggestion {
+ var matches []string
+ if input == "" {
+ matches = options
+ } else {
+ matches = utils.FuzzySearch(input, options)
+ }
+
+ return matchesToSuggestions(matches)
+ }
+}
diff --git a/pkg/gui/layout.go b/pkg/gui/layout.go
index c15ba340e..a7f308b7b 100644
--- a/pkg/gui/layout.go
+++ b/pkg/gui/layout.go
@@ -111,6 +111,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.Credentials.Editable = true
gui.Views.Suggestions.Visible = false
+ gui.Views.Suggestions.ContainsList = true
gui.Views.Menu.Visible = false
diff --git a/pkg/i18n/chinese.go b/pkg/i18n/chinese.go
index 5a5d885cd..f54f8d657 100644
--- a/pkg/i18n/chinese.go
+++ b/pkg/i18n/chinese.go
@@ -358,7 +358,7 @@ func chineseTranslationSet() TranslationSet {
LcFilterBy: "过滤",
LcExitFilterMode: "停止按路径过滤",
LcFilterPathOption: "输入要过滤的路径",
- LcEnterFileName: "输入路径:",
+ EnterFileName: "输入路径:",
FilteringMenuTitle: "正在过滤",
MustExitFilterModeTitle: "命令不可用",
MustExitFilterModePrompt: "命令在过滤模式下不可用。退出过滤模式?",
@@ -419,7 +419,7 @@ func chineseTranslationSet() TranslationSet {
SubCommitsTitle: "子提交",
SubmodulesTitle: "子模块",
NavigationTitle: "列表面板导航",
- SuggestionsTitle: "意见建议",
+ SuggestionsCheatsheetTitle: "意见建议",
PushingTagStatus: "推送标签",
PullRequestURLCopiedToClipboard: "抓取请求网址已复制到剪贴板",
CommitMessageCopiedToClipboard: "提交消息复制到剪贴板",
diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go
index f3d34d94c..fff0f7b31 100644
--- a/pkg/i18n/dutch.go
+++ b/pkg/i18n/dutch.go
@@ -327,7 +327,7 @@ func dutchTranslationSet() TranslationSet {
LcFilterBy: "filter bij",
LcExitFilterMode: "stop met filteren bij pad",
LcFilterPathOption: "vulin pad om op te filteren",
- LcEnterFileName: "vulin path:",
+ EnterFileName: "Vulin path:",
FilteringMenuTitle: "Filteren",
MustExitFilterModeTitle: "Command niet beschikbaar",
MustExitFilterModePrompt: "Command niet beschikbaar in filter modus. Sluit filter modus?",
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 50508d63a..a0a485c60 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -342,7 +342,7 @@ type TranslationSet struct {
LcFilterBy string
LcExitFilterMode string
LcFilterPathOption string
- LcEnterFileName string
+ EnterFileName string
FilteringMenuTitle string
MustExitFilterModeTitle string
MustExitFilterModePrompt string
@@ -404,6 +404,8 @@ type TranslationSet struct {
SubCommitsTitle string
SubmodulesTitle string
NavigationTitle string
+ SuggestionsCheatsheetTitle string
+ // Unlike the cheatsheet title above, the real suggestions title has a little message saying press tab to focus
SuggestionsTitle string
ExtrasTitle string
PushingTagStatus string
@@ -867,7 +869,7 @@ func englishTranslationSet() TranslationSet {
LcFilterBy: "filter by",
LcExitFilterMode: "stop filtering by path",
LcFilterPathOption: "enter path to filter by",
- LcEnterFileName: "enter path:",
+ EnterFileName: "Enter path:",
FilteringMenuTitle: "Filtering",
MustExitFilterModeTitle: "Command not available",
MustExitFilterModePrompt: "Command not available in filtered mode. Exit filtered mode?",
@@ -930,7 +932,8 @@ func englishTranslationSet() TranslationSet {
SubCommitsTitle: "Sub-commits",
SubmodulesTitle: "Submodules",
NavigationTitle: "List Panel Navigation",
- SuggestionsTitle: "Suggestions",
+ SuggestionsCheatsheetTitle: "Suggestions",
+ SuggestionsTitle: "Suggestions (press %s to focus)",
ExtrasTitle: "Extras",
PushingTagStatus: "pushing tag",
PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard",
diff --git a/pkg/utils/slice.go b/pkg/utils/slice.go
index f536ea056..48acdbd2d 100644
--- a/pkg/utils/slice.go
+++ b/pkg/utils/slice.go
@@ -116,3 +116,31 @@ func StringArraysOverlap(strArrA []string, strArrB []string) bool {
return false
}
+
+func Uniq(values []string) []string {
+ added := make(map[string]bool)
+ result := make([]string, 0, len(values))
+ for _, value := range values {
+ if added[value] {
+ continue
+ }
+ added[value] = true
+ result = append(result, value)
+ }
+ return result
+}
+
+func Limit(values []string, limit int) []string {
+ if len(values) > limit {
+ return values[:limit]
+ }
+ return values
+}
+
+func Reverse(values []string) []string {
+ result := make([]string, len(values))
+ for i, val := range values {
+ result[len(values)-i-1] = val
+ }
+ return result
+}
diff --git a/pkg/utils/slice_test.go b/pkg/utils/slice_test.go
index 491968cb4..3636f44cb 100644
--- a/pkg/utils/slice_test.go
+++ b/pkg/utils/slice_test.go
@@ -165,3 +165,86 @@ func TestEscapeSpecialChars(t *testing.T) {
})
}
}
+
+func TestUniq(t *testing.T) {
+ for _, test := range []struct {
+ values []string
+ want []string
+ }{
+ {
+ values: []string{"a", "b", "c"},
+ want: []string{"a", "b", "c"},
+ },
+ {
+ values: []string{"a", "b", "a", "b", "c"},
+ want: []string{"a", "b", "c"},
+ },
+ } {
+ if got := Uniq(test.values); !assert.EqualValues(t, got, test.want) {
+ t.Errorf("Uniq(%v) = %v; want %v", test.values, got, test.want)
+ }
+ }
+}
+
+func TestLimit(t *testing.T) {
+ for _, test := range []struct {
+ values []string
+ limit int
+ want []string
+ }{
+ {
+ values: []string{"a", "b", "c"},
+ limit: 3,
+ want: []string{"a", "b", "c"},
+ },
+ {
+ values: []string{"a", "b", "c"},
+ limit: 4,
+ want: []string{"a", "b", "c"},
+ },
+ {
+ values: []string{"a", "b", "c"},
+ limit: 2,
+ want: []string{"a", "b"},
+ },
+ {
+ values: []string{"a", "b", "c"},
+ limit: 1,
+ want: []string{"a"},
+ },
+ {
+ values: []string{"a", "b", "c"},
+ limit: 0,
+ want: []string{},
+ },
+ {
+ values: []string{},
+ limit: 0,
+ want: []string{},
+ },
+ } {
+ if got := Limit(test.values, test.limit); !assert.EqualValues(t, got, test.want) {
+ t.Errorf("Limit(%v, %d) = %v; want %v", test.values, test.limit, got, test.want)
+ }
+ }
+}
+
+func TestReverse(t *testing.T) {
+ for _, test := range []struct {
+ values []string
+ want []string
+ }{
+ {
+ values: []string{"a", "b", "c"},
+ want: []string{"c", "b", "a"},
+ },
+ {
+ values: []string{},
+ want: []string{},
+ },
+ } {
+ if got := Reverse(test.values); !assert.EqualValues(t, got, test.want) {
+ t.Errorf("Reverse(%v) = %v; want %v", test.values, got, test.want)
+ }
+ }
+}
diff --git a/scripts/generate_cheatsheet.go b/scripts/generate_cheatsheet.go
index e49156377..e2200b694 100644
--- a/scripts/generate_cheatsheet.go
+++ b/scripts/generate_cheatsheet.go
@@ -81,7 +81,7 @@ func localisedTitle(mApp *app.App, str string) string {
"search": tr.SearchTitle,
"secondary": tr.SecondaryTitle,
"stash": tr.StashTitle,
- "suggestions": tr.SuggestionsTitle,
+ "suggestions": tr.SuggestionsCheatsheetTitle,
"extras": tr.ExtrasTitle,
}