Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs-master/Config.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ gui:
- [commits, reflog]
- [stash]

# The side panel that is focused when lazygit starts, or when you switch to
# another repository.
# Must be one of the names listed in `sidePanels`; you can't focus a panel you
# have hidden.
# Starting lazygit with a path filter or with a git subcommand (e.g. `lazygit
# log`) takes precedence over this setting.
initialSidePanel: files

# Sometimes the main window is split in two (e.g. when the selected file has
# both staged and unstaged changes). This setting controls how the two sections
# are split.
Expand Down
16 changes: 14 additions & 2 deletions pkg/config/side_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ var ValidSidePanelTabs = []string{
"stash",
}

// SidePanelName is the name of a single side panel, i.e. one of
// ValidSidePanelTabs.
type SidePanelName string

// JSONSchema restricts a side panel name to the known names.
func (SidePanelName) JSONSchema() *jsonschema.Schema {
return &jsonschema.Schema{Type: "string", Enum: validSidePanelTabsAsAny()}
}

func validSidePanelTabsAsAny() []any {
return lo.Map(ValidSidePanelTabs, func(name string, _ int) any { return name })
}

func (p SidePanel) MarshalYAML() (any, error) {
// Render in flow style (`[a, b]`) rather than the default block style, which
// is more compact and reads better in the generated docs.
Expand All @@ -46,9 +59,8 @@ func (p SidePanel) MarshalYAML() (any, error) {
// JSONSchema describes a side panel as a list of tab names, restricted to the
// known names.
func (SidePanel) JSONSchema() *jsonschema.Schema {
names := lo.Map(ValidSidePanelTabs, func(name string, _ int) any { return name })
return &jsonschema.Schema{
Type: "array",
Items: &jsonschema.Schema{Type: "string", Enum: names},
Items: &jsonschema.Schema{Type: "string", Enum: validSidePanelTabsAsAny()},
}
}
5 changes: 5 additions & 0 deletions pkg/config/user_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ type GuiConfig struct {
// Omit a name to hide it; give a name its own one-element list to promote a tab to a top-level panel.
// Valid names are: 'status', 'files', 'worktrees', 'submodules', 'branches', 'remotes', 'tags', 'commits', 'reflog', 'stash'. 'files', 'branches', and 'commits' must always be included; they can't be hidden.
SidePanels []SidePanel `yaml:"sidePanels"`
// The side panel that is focused when lazygit starts, or when you switch to another repository.
// Must be one of the names listed in `sidePanels`; you can't focus a panel you have hidden.
// Starting lazygit with a path filter or with a git subcommand (e.g. `lazygit log`) takes precedence over this setting.
InitialSidePanel SidePanelName `yaml:"initialSidePanel"`
// Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
// Options are:
// - 'horizontal': split the window horizontally
Expand Down Expand Up @@ -869,6 +873,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
{"commits", "reflog"},
{"stash"},
},
InitialSidePanel: "files",
MainPanelSplitMode: "flexible",
EnlargedSideViewLocation: "left",
WrapLinesInStagingView: true,
Expand Down
17 changes: 17 additions & 0 deletions pkg/config/user_config_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,26 @@ func (config *UserConfig) Validate() error {
if err := validateSidePanels(config.Gui.SidePanels); err != nil {
return err
}
if err := validateInitialSidePanel(config.Gui.SidePanels, config.Gui.InitialSidePanel); err != nil {
return err
}
return nil
}

func validateInitialSidePanel(panels []SidePanel, initial SidePanelName) error {
name := string(initial)
if !slices.Contains(ValidSidePanelTabs, name) {
return fmt.Errorf("gui.initialSidePanel: unknown side panel '%s'. Allowed values: %s",
name, strings.Join(ValidSidePanelTabs, ", "))
}
for _, panel := range panels {
if slices.Contains(panel, name) {
return nil
}
}
return fmt.Errorf("gui.initialSidePanel: '%s' is not listed in gui.sidePanels; a hidden side panel can't be focused.", name)
}

func validateSidePanels(panels []SidePanel) error {
seen := map[string]bool{}
total := 0
Expand Down
32 changes: 32 additions & 0 deletions pkg/config/user_config_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,38 @@ func TestUserConfigValidate_sidePanels(t *testing.T) {
}
}

func TestUserConfigValidate_initialSidePanel(t *testing.T) {
scenarios := []struct {
name string
panels []SidePanel
initial SidePanelName
valid bool
}{
{name: "default", panels: []SidePanel{{"files"}, {"branches"}, {"commits"}}, initial: "files", valid: true},
{name: "non-default panel", panels: []SidePanel{{"files"}, {"branches"}, {"commits"}}, initial: "commits", valid: true},
{name: "non-first tab of a panel", panels: []SidePanel{{"files"}, {"branches"}, {"reflog", "commits"}}, initial: "commits", valid: true},
{name: "status panel", panels: []SidePanel{{"status"}, {"files"}, {"branches"}, {"commits"}}, initial: "status", valid: true},
{name: "empty", panels: []SidePanel{{"files"}, {"branches"}, {"commits"}}, initial: "", valid: false},
{name: "unknown name", panels: []SidePanel{{"files"}, {"branches"}, {"commits"}}, initial: "bogus", valid: false},
{name: "hidden panel", panels: []SidePanel{{"files"}, {"branches"}, {"commits"}}, initial: "stash", valid: false},
}

for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
config := GetDefaultConfig()
config.Gui.SidePanels = s.panels
config.Gui.InitialSidePanel = s.initial
err := config.Validate()

if s.valid {
assert.NoError(t, err)
} else {
assert.Error(t, err)
}
})
}
}

func TestUserConfigValidate_pagers(t *testing.T) {
scenarios := []struct {
name string
Expand Down
10 changes: 7 additions & 3 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {

gui.applySidePanelConfig()

return initialContext(contextTree, startArgs)
return initialContext(contextTree, startArgs, gui.c.UserConfig().Gui.InitialSidePanel)
}

func (gui *Gui) loadCachedPullRequests() []*models.GithubPullRequest {
Expand Down Expand Up @@ -745,8 +745,12 @@ func parseScreenModeArg(screenModeArg string) types.ScreenMode {
}
}

func initialContext(contextTree *context.ContextTree, startArgs appTypes.StartArgs) types.IListContext {
var initialContext types.IListContext = contextTree.Files
func initialContext(
contextTree *context.ContextTree,
startArgs appTypes.StartArgs,
initialSidePanel config.SidePanelName,
) types.Context {
initialContext := sidePanelContexts(contextTree)[string(initialSidePanel)]

if startArgs.FilterPath != "" {
initialContext = contextTree.LocalCommits
Expand Down
2 changes: 2 additions & 0 deletions pkg/integration/tests/test_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,8 @@ var tests = []*components.IntegrationTest{
ui.DisableSwitchTabWithPanelJumpKeys,
ui.EmptyMenu,
ui.HideSidePanel,
ui.InitialSidePanel,
ui.InitialSidePanelNotFirstTab,
ui.KeybindingSuggestionsDontCrashOnDisabledBindings,
ui.KeybindingSuggestionsWhenSwitchingRepos,
ui.ModeSpecificKeybindingSuggestions,
Expand Down
25 changes: 25 additions & 0 deletions pkg/integration/tests/ui/initial_side_panel.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ui

import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var InitialSidePanel = NewIntegrationTest(NewIntegrationTestArgs{
Description: "The side panel named by gui.initialSidePanel is focused at startup",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Gui.InitialSidePanel = "commits"
},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("one")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
IsFocused().
Lines(
Contains("one"),
)
},
})
35 changes: 35 additions & 0 deletions pkg/integration/tests/ui/initial_side_panel_not_first_tab.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ui

import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var InitialSidePanelNotFirstTab = NewIntegrationTest(NewIntegrationTestArgs{
Description: "gui.initialSidePanel names a tab that isn't the first one of its panel, so it must be brought to the front",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(cfg *config.AppConfig) {
cfg.GetUserConfig().Gui.SidePanels = []config.SidePanel{
{"status"},
{"files"},
{"branches"},
{"reflog", "commits"},
{"stash"},
}
cfg.GetUserConfig().Gui.InitialSidePanel = "commits"
},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("one")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
// Commits is shown in front of the reflog tab it shares a panel with,
// rather than being focused behind it.
t.Views().Commits().
IsActiveTab().
IsFocused().
Lines(
Contains("one"),
)
},
})
21 changes: 21 additions & 0 deletions schema-master/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,10 @@
]
]
},
"initialSidePanel": {
"$ref": "#/$defs/SidePanelName",
"description": "The side panel that is focused when lazygit starts, or when you switch to another repository.\nMust be one of the names listed in `sidePanels`; you can't focus a panel you have hidden.\nStarting lazygit with a path filter or with a git subcommand (e.g. `lazygit log`) takes precedence over this setting."
},
"mainPanelSplitMode": {
"type": "string",
"enum": [
Expand Down Expand Up @@ -3622,6 +3626,23 @@
},
"type": "array"
},
"SidePanelName": {
"type": "string",
"enum": [
"status",
"files",
"worktrees",
"submodules",
"branches",
"remotes",
"tags",
"commits",
"reflog",
"stash"
],
"description": "The side panel that is focused when lazygit starts, or when you switch to another repository.\nMust be one of the names listed in `sidePanels`; you can't focus a panel you have hidden.\nStarting lazygit with a path filter or with a git subcommand (e.g. `lazygit log`) takes precedence over this setting.",
"default": "files"
},
"SpinnerConfig": {
"properties": {
"frames": {
Expand Down