From 6316094d581f6501ca4a1980e32f94aadf3288df Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Sun, 7 Jun 2026 11:37:02 +0200 Subject: [PATCH] Show pager name in the cycle-pager toast, and let users name pagers When cycling pagers, "Selected pager 2 of 3" gives no clue which pager you landed on; with several configured you have to remember the order. Include the pager's name in the toast instead. The name is normally derived from the first word of the pager command, but that isn't always enough: two entries can share a command but differ in options (e.g. "delta" and "delta --side-by-side"), and an entry may have no command at all (the default entry, or when using useExternalDiffGitConfig). So add an optional `name` field that overrides the derived name. The message was also hardcoded in English; localize it while we're here. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs-master/Config.md | 5 +++ pkg/config/pager_config.go | 39 ++++++++++++++++ pkg/config/pager_config_test.go | 57 ++++++++++++++++++++++++ pkg/config/user_config.go | 7 +++ pkg/gui/controllers/global_controller.go | 20 +++++++-- pkg/i18n/english.go | 6 +++ schema-master/config.json | 6 ++- 7 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 pkg/config/pager_config_test.go diff --git a/docs-master/Config.md b/docs-master/Config.md index 6de2ad978..07942c659 100644 --- a/docs-master/Config.md +++ b/docs-master/Config.md @@ -342,6 +342,11 @@ gui: git: # Array of pagers. Each entry has the following format: # + # # A name for the pager, shown in the notification when cycling pagers. + # # If not set, the name is derived from the first word of the pager + # # command (or of the external diff command). + # name: "" + # # # Value of the --color arg in the git diff command. Some pagers want # # this to be set to 'always' and some want it set to 'never' # colorArg: "always" diff --git a/pkg/config/pager_config.go b/pkg/config/pager_config.go index e721da0e8..1b562ccd7 100644 --- a/pkg/config/pager_config.go +++ b/pkg/config/pager_config.go @@ -2,6 +2,7 @@ package config import ( "strconv" + "strings" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -80,3 +81,41 @@ func (self *PagerConfig) CyclePagers() { func (self *PagerConfig) CurrentPagerIndex() (int, int) { return self.pagerIndex, len(self.getUserConfig().Git.Pagers) } + +// CurrentPagerName returns a name for the current pager, suitable for showing +// to the user. It returns an empty string if no name can be derived; callers +// should substitute a localized fallback in that case. +func (self *PagerConfig) CurrentPagerName() string { + currentPagerConfig := self.currentPagerConfig() + if currentPagerConfig == nil { + return "" + } + return currentPagerConfig.displayName() +} + +// CurrentPagerUsesGitConfigDiff reports whether the current pager defers to +// git's own external diff config. Such an entry has no name we can derive (the +// actual command may even vary per file via .gitattributes), so callers show a +// generic label rather than treating it like the default no-pager entry. +func (self *PagerConfig) CurrentPagerUsesGitConfigDiff() bool { + currentPagerConfig := self.currentPagerConfig() + return currentPagerConfig != nil && currentPagerConfig.UseExternalDiffGitConfig +} + +func (self *PagingConfig) displayName() string { + if self.Name != "" { + return self.Name + } + if word := firstWord(string(self.Pager)); word != "" { + return word + } + return firstWord(self.ExternalDiffCommand) +} + +func firstWord(command string) string { + fields := strings.Fields(command) + if len(fields) == 0 { + return "" + } + return fields[0] +} diff --git a/pkg/config/pager_config_test.go b/pkg/config/pager_config_test.go new file mode 100644 index 000000000..e2618d5bd --- /dev/null +++ b/pkg/config/pager_config_test.go @@ -0,0 +1,57 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCurrentPagerName(t *testing.T) { + scenarios := []struct { + name string + pager PagingConfig + expected string + }{ + { + name: "explicit name takes precedence over the command", + pager: PagingConfig{Name: "delta side-by-side", Pager: "delta --side-by-side"}, + expected: "delta side-by-side", + }, + { + name: "derived from the first word of the pager command", + pager: PagingConfig{Pager: "delta --side-by-side"}, + expected: "delta", + }, + { + name: "surrounding whitespace in the command is ignored", + pager: PagingConfig{Pager: " diff-so-fancy "}, + expected: "diff-so-fancy", + }, + { + name: "falls back to the external diff command when there is no pager", + pager: PagingConfig{ExternalDiffCommand: "difft --color=always"}, + expected: "difft", + }, + { + name: "no name can be derived", + pager: PagingConfig{UseExternalDiffGitConfig: true}, + expected: "", + }, + } + + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + userConfig := &UserConfig{} + userConfig.Git.Pagers = []PagingConfig{s.pager} + config := NewPagerConfig(func() *UserConfig { return userConfig }) + + assert.Equal(t, s.expected, config.CurrentPagerName()) + }) + } +} + +func TestCurrentPagerNameWithoutPagers(t *testing.T) { + config := NewPagerConfig(func() *UserConfig { return &UserConfig{} }) + + assert.Equal(t, "", config.CurrentPagerName()) +} diff --git a/pkg/config/user_config.go b/pkg/config/user_config.go index acadc8e80..aa2d945d6 100644 --- a/pkg/config/user_config.go +++ b/pkg/config/user_config.go @@ -256,6 +256,11 @@ type GitConfig struct { // Array of pagers. Each entry has the following format: // [dev] The following documentation is duplicated from the PagingConfig struct below. // + // # A name for the pager, shown in the notification when cycling pagers. + // # If not set, the name is derived from the first word of the pager + // # command (or of the external diff command). + // name: "" + // // # Value of the --color arg in the git diff command. Some pagers want // # this to be set to 'always' and some want it set to 'never' // colorArg: "always" @@ -347,6 +352,8 @@ func (PagerType) JSONSchemaExtend(schema *jsonschema.Schema) { // [dev] This documentation is duplicated in the GitConfig struct. If you make changes here, make them there too. type PagingConfig struct { + // A name for the pager, shown in the notification when cycling pagers. If not set, the name is derived from the first word of the pager command (or of the external diff command). + Name string `yaml:"name"` // Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never' ColorArg string `yaml:"colorArg" jsonschema:"enum=always,enum=never"` // e.g. diff --git a/pkg/gui/controllers/global_controller.go b/pkg/gui/controllers/global_controller.go index d2ca28c60..7879ab7ac 100644 --- a/pkg/gui/controllers/global_controller.go +++ b/pkg/gui/controllers/global_controller.go @@ -1,10 +1,11 @@ package controllers import ( - "fmt" + "strconv" "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/utils" ) type GlobalController struct { @@ -166,8 +167,21 @@ func (self *GlobalController) cyclePagers() error { currentSide.HandleRenderToMain() } - current, total := self.c.State().GetPagerConfig().CurrentPagerIndex() - self.c.Toast(fmt.Sprintf("Selected pager %d of %d", current+1, total)) + pagerConfig := self.c.State().GetPagerConfig() + current, total := pagerConfig.CurrentPagerIndex() + name := pagerConfig.CurrentPagerName() + if name == "" { + if pagerConfig.CurrentPagerUsesGitConfigDiff() { + name = self.c.Tr.ExternalDiffPagerName + } else { + name = self.c.Tr.DefaultPagerName + } + } + self.c.Toast(utils.ResolvePlaceholderString(self.c.Tr.SelectedPager, map[string]string{ + "name": name, + "current": strconv.Itoa(current + 1), + "total": strconv.Itoa(total), + })) return nil } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index ef996a198..7ff5137f6 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -608,6 +608,9 @@ type TranslationSet struct { CyclePagers string CyclePagersTooltip string CyclePagersDisabledReason string + SelectedPager string + DefaultPagerName string + ExternalDiffPagerName string StartSearch string StartFilter string SelectRemoteRepository string @@ -1739,6 +1742,9 @@ func EnglishTranslationSet() *TranslationSet { CyclePagers: "Cycle pagers", CyclePagersTooltip: "Choose the next pager in the list of configured pagers.", CyclePagersDisabledReason: "No other pagers configured", + SelectedPager: "Pager: {{.name}} ({{.current}} of {{.total}})", + DefaultPagerName: "(default)", + ExternalDiffPagerName: "(external diff)", StartSearch: "Search the current view by text", StartFilter: "Filter the current view by text", SelectRemoteRepository: "Select base repository for pull requests", diff --git a/schema-master/config.json b/schema-master/config.json index 9042986aa..816c46213 100644 --- a/schema-master/config.json +++ b/schema-master/config.json @@ -321,7 +321,7 @@ "$ref": "#/$defs/PagingConfig" }, "type": "array", - "description": "Array of pagers. Each entry has the following format:\n\n # Value of the --color arg in the git diff command. Some pagers want\n # this to be set to 'always' and some want it set to 'never'\n colorArg: \"always\"\n\n # e.g.\n # diff-so-fancy\n # delta --dark --paging=never\n # ydiff -p cat -s --wrap --width={{columnWidth}}\n pager: \"\"\n\n # e.g. 'difft --color=always'\n externalDiffCommand: \"\"\n\n # If true, Lazygit will use git's `diff.external` config for paging.\n # The advantage over `externalDiffCommand` is that this can be\n # configured per file type in .gitattributes; see\n # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.\n useExternalDiffGitConfig: false\n\n'pager', 'externalDiffCommand', and 'useExternalDiffGitConfig' are mutually exclusive; set at most one per entry.\n\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md for more information." + "description": "Array of pagers. Each entry has the following format:\n\n # A name for the pager, shown in the notification when cycling pagers.\n # If not set, the name is derived from the first word of the pager\n # command (or of the external diff command).\n name: \"\"\n\n # Value of the --color arg in the git diff command. Some pagers want\n # this to be set to 'always' and some want it set to 'never'\n colorArg: \"always\"\n\n # e.g.\n # diff-so-fancy\n # delta --dark --paging=never\n # ydiff -p cat -s --wrap --width={{columnWidth}}\n pager: \"\"\n\n # e.g. 'difft --color=always'\n externalDiffCommand: \"\"\n\n # If true, Lazygit will use git's `diff.external` config for paging.\n # The advantage over `externalDiffCommand` is that this can be\n # configured per file type in .gitattributes; see\n # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.\n useExternalDiffGitConfig: false\n\n'pager', 'externalDiffCommand', and 'useExternalDiffGitConfig' are mutually exclusive; set at most one per entry.\n\nSee https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md for more information." }, "commit": { "$ref": "#/$defs/CommitConfig", @@ -3488,6 +3488,10 @@ }, "PagingConfig": { "properties": { + "name": { + "type": "string", + "description": "A name for the pager, shown in the notification when cycling pagers. If not set, the name is derived from the first word of the pager command (or of the external diff command)." + }, "colorArg": { "type": "string", "enum": [