Make the side panels configurable#5702
Conversation
Record the working preference that calls which come up during implementation (and weren't settled in planning) should be raised and decided together, not made unilaterally and discovered later in the diff. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The three branches of sidePanelChildren each spelled out the five side windows by name, so the panel order lived in three places and the status/stash sizing special-cases were tangled into positional literals. Map each branch over one `windows` slice instead, and fold the normal-height special-cases (status's fixed height, stash's collapse-unless-focused) into a single per-window function. Behavior is unchanged; this isolates the ordering so it can later come from config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The remote-branches context is a transient guest that takes over a host
window when you drill into a remote. SubCommits and CommitFiles already
adopt their parent's window via SetWindowName when shown; RemoteBranches
relied instead on its static window name ("branches") matching the remotes
context's window. That assumption only holds while remotes lives in the
branches panel. Adopt the parent's window like the other transient guests
so remote branches render in the right place once panels are configurable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The jump-label prefixes were assigned to each side view by name, twice (once for the on case, once for off), so the panel-to-views grouping and the panel order were baked into 28 positional statements. Express the grouping once as a slice of view groups and loop over it, deriving each panel's label from its index. The label lookup is now bounds-checked, so it no longer assumes exactly as many jump bindings as panels. Behavior is unchanged; this prepares the grouping to come from config. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This adds the user-facing surface for configuring the side panels: their order, which ones are visible, and how tabs are grouped into panels. Each entry is either a single panel name or a list of names sharing one panel as tabs, mirroring how the Keybinding type accepts a scalar or a sequence; the JSON schema restricts the names to the known set so editors can offer completion and catch typos. The default reproduces today's layout exactly. Validation rejects unknown or duplicated names, and requires the files, branches, and commits panels to always be present: a lot of code focuses those directly (e.g. after resolving a conflict or popping a stash), so allowing them to be hidden would let that code focus a hidden panel. Nothing reads the option yet; the layout still uses the hard-coded order. Wiring follows in a later commit so the inert surface (and its generated docs and schema) can be reviewed on its own. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The number of side panels is about to become configurable, so a fixed count of jump-to-panel keys no longer makes sense: a user who configures six panels shouldn't be forced to also extend jumpToBlock, and one who hides a panel shouldn't have to trim it. Drop the count check entirely (individual keys are still validated) and assign keys to panels positionally, for as many panels as there are keys. Surplus panels go without a jump key but remain reachable via the next/previous-panel keys. This also removes the log.Fatal that the count check guarded against. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the hard-coded side panel order, tab groupings, and window assignments with values resolved from the gui.sidePanels config. The panel order (SideWindows and the layout boxes), the tab strips (viewTabMap), the per-context window names, each window's default view, and the jump-label groups all now come from the config rather than from five separate hard-coded lists. A panel's window name is the name of its first tab, and panels not listed in the config get their own window name so their views stay hidden instead of overlapping a visible panel. Three small lookups translate config names into views, tab titles, and contexts; a test keeps them in sync with the set of valid names. The lookups are split this way (rather than one resolver) because configureViewProperties runs before the context tree exists, so the title/view lookups must not depend on it. The config is applied to a repo's contexts via applySidePanelConfig on every repo entry, including the cached-repo path: a repo's per-repo config can differ from the previously visited one's, so each repo's contexts must be (re)assigned from its own config rather than kept from when they were first built. With the default config this reproduces today's layout exactly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These two views only ever appeared as tabs (of the files and commits panels), so unlike the other side views they had no title set; the tab strip supplied their label. Once a tab can be promoted to its own panel they can appear without a tab strip, so set their titles like the others. This has no effect in the default layout, where both are always tabs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In squashed mode (short terminals) the unfocused side panels each reserve a row and the focused panel takes whatever is left, so once the unfocused panels' rows fill the height the focused panel collapses to nothing and panels below it render off-screen. The fixed floor of 9 was tuned for five panels; with the panel count now configurable (and promotion allowing up to ten), grow the floor by one per panel so we show the "not enough space" view instead of a broken layout. Five panels still floor at 9. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Within a window the visible tab is whichever view sits on top in the z-order, and onRepoViewReset establishes that z-order from a fixed list that needn't agree with the configured tab order. After ordering the views, bring each panel's first configured tab to the top so that, for a panel whose tabs have been reordered, the configured first tab is the one shown before the panel is focused. No effect on the default layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cover the three things gui.sidePanels enables: reordering the panels (swapping branches and commits, checked via their jump keys), hiding a panel (omitting stash, checked by cycling past the last panel and wrapping to the first), and promoting a tab to its own panel (worktrees becomes a top-level panel reachable by a jump key, and the files panel's remaining tabs cycle straight to submodules). The tests drive focus with explicit jump keys rather than ViewDriver.Focus, which assumes the default panel layout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tab-assignment loop only ever set a view's tabs; it never cleared them. That was fine when the groupings were fixed, but with gui.sidePanels a config reload can turn a tab into a standalone panel, and the old tab strip would linger on its title. Index the tab strips by view name and assign to every view, so views that dropped out of a multi-tab panel get their tabs cleared. No change for a given config; this only matters across a reload. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lazygit reloads changed config files when its terminal window regains focus, but the test harness had no way to simulate that focus event, so the live config-reload path was untestable. Add a focus event to the replayed-events queue and expose it through the GuiDriver as FocusIn. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Side panel tabs share a window, so which tab is shown is decided by view z-order rather than the visibility flag (every tab in a window is 'visible'). Tests had no way to assert which tab is actually drawn in front, which is distinct from which view has keyboard focus. Expose the window's top view and add an IsActiveTab assertion built on it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When the config file changes and lazygit regains focus it reloads the config, but the side panel window assignments, default views, tab strips, and z-order were only ever set up on repo entry, so a changed sidePanels wouldn't take effect until restart. Re-apply it from the reload path: reassign windows and default views and restore each panel's default tab. The focused panel needs care: resetting it to its default tab would leave the focused tab hidden behind that default tab, so the panel looks unfocused even though its tab is selected. Re-focus the current context so its tab stays shown and highlighted; only when the new config hides the focused panel entirely do we move focus to the default side panel. Tab strips are already refreshed via configureViewProperties.
Exercises the path the live reload relies on: a per-repo lazygit.yml sets a different side panel layout, and switching between repos re-applies each one's own layout (the new-repo path for the cloned repo, the cached-repo path on switching back). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7b37c2b to
c6b8220
Compare
|
Cool, when will this be released? |
I don't know yet; we roughly have an every-first-saturday-of-a-month release schedule, but sometimes it slips by a week or two. In the meantime, building from master is very easy. |
|
omg wow... thank you so much!! this is a gamechanger!! the status panel has always seemed useless to me, and I use stashes somewhat rarely so maybe I jam them as a tab into files. very good change! <3 |
Hm, I use it pretty often actually, mainly for opening the config file ( |
|
I solve both usecases of the status panel in more generic ways. Since I can't expect every program to have a hotkey to edit its config file, I have always open an editor opened in my dotfiles repo, where I store all my significant dotfiles, that are then symlinked to their expected locations. So it's more natural for me to go edit the lazygit config file from there, than from lazygit. Especially considering that lazygit hot-reloads the config, which is awesome. Then the capability of jumping across recently lazygitted repositories is neat, but it's more natural for me to jump to the directory with zoxide, and then open lazygit there. Because maybe I need to open helix, maybe yazi, maybe start a build command; I usually go to some directory and then it hits me what I want to do with it, so using lazygit as a means of travelling I haven't found useful. Especially considering that if I pick a directory in lazygit to jump to, once I quit lazygit, I will still be left in the original, not the new directory; so it's a bit effortful to travel within lazygit only to re-travel outside of it again. Cool that both exist, but yeah I haven't found a usecase for the actions personally. |
|
would it be possible to have panels appear and disappear based on some condition? like only showing stashes if you have stashed something |
|
thank you so much @stefanhaller! you took my original idea to another level, love this version as it's super flexible now <3 |
Lazygit's left-hand side panels — and the tabs within them — have always had a fixed arrangement. This adds a
gui.sidePanelsoption so you can lay them out the way you want.You can:
How it works
gui.sidePanelsis a top-to-bottom list. Each entry is a list of names that share one panel as tabs. The default isIf you want to promote
worktreesto a side panel of its own, and never want to see stashes, use