`ctrl+o` in the focused main view copies the selection to the clipboard,
mirroring the staging view's copy. Unlike stage / discard, copying is the same
for every diff panel — it just reads the text shown — so it's a direct
MainViewController command rather than a FocusedMainViewActions method. That
keeps it free of per-panel duplication and lets it work over panels that have
no actions, notably the reflog.
The +/- prefix handling is metadata-aware where it matters: with no pager the
main view shows the raw diff, so a homogeneous selection has its +/-/space
column stripped (dropDiffPrefix) to ease pasting into code. With a pager
configured the rendered lines carry no such column, so stripping a leading
character would eat real content — copy verbatim instead. (Refining the raw
case to classify header lines via the diff-line metadata, fixing the
'--- a/file' edge case noted in 159bbb0825, is left for later.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wire the focused main view's `d` binding to a DiscardSelection action on the
side panel beneath, the discard counterpart of the primary action. As with
stage/toggle, what discard means is the panel's business:
- the files panel discards from the working tree, mirroring the staging
view's `d` — reverse-apply not cached on the unstaged side (destructive, so
confirmed), reverse-apply cached on the staged side (i.e. unstage);
- the commit panels (commit files, and the whole-commit diff of the local
commits / sub-commits / stash panels) remove the selected lines from the
commit via a rebase, mirroring the patch builder's "discard lines from
commit" — building a one-off patch from the selection and deleting it.
The commit-discard backend and its guard are shared free functions in
patch_building_from_main_view.go, paralleling the patch toggle. Discarding
from a commit is only possible on a local branch, so DiscardSelection grows a
disabled-reason companion on the FocusedMainViewActions interface: the `d`
binding is greyed with a reason on stash and other-branch sub-commits (never
rebaseable) and while a rebase is in progress, exactly as the patch builder
greys it. The files panel is always available (a zero-context diff is the one
error it reports inline, matching the staging view).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Step 1 made the focused main view a staging surface for a single line. This
adds the range and hunk selection the staging view has, so you can stage (or
unstage) a range or a whole hunk in place — `v` starts a range, shift-up/down
extend one, `a` toggles hunk selection — without diving into the staging view.
The selection mode (line / range / hunk, plus sticky-range and "the user turned
hunk mode on themselves") lives on MainContext rather than the controller,
because three places need a pane's mode: the main view controller that drives
it, the focus controller that resets it when you focus the view, and togglePanel
which sets it on the *other* pane. The selected line and the range anchor stay
in the gocui view itself (its cursor and rangeSelectStartY), so the existing
native range-select rendering draws the highlight — no new highlight machinery.
Hunk bounds come from the diff-line metadata (the isChange run around the
cursor, via ChangeBlockBounds), not from a parsed patch, so they work over any
conforming rendering. The up/down keys are mode-aware: hunk mode steps hunk to
hunk, a non-sticky range collapses on a plain move, a sticky range extends.
GetOnStageFocusedMainView now takes the selected view-line range instead of one
line; the files handler collects the change lines in the range and applies a
single patch. It identifies each change line's patch line by scanning the parsed
patch and matching (file line number, deletion?) identities, rather than looking
each number up with PatchLineForLineNumber. That lookup can't tell an addition at
the very start of a hunk from the deletion above it, nor an addition from the
deletion it replaces on a modified line — both of which a range routinely spans.
As a side effect this also fixes staging a change on the first line of a file.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The focused main view already lets you point at a diff line and act on it
(dive into staging, edit it, open it in a PR). This makes it a staging surface
in its own right: press space to stage — or unstage — the selected line without
diving into the separate staging view.
To make that usable, the selection is now shown automatically whenever the main
view holds a diff, anchored on the first change line already visible (so the
view doesn't jump), rather than being toggled on demand from the middle of the
view. That frees space for staging and means there's always a line to act on.
Which contexts show a diff is marked by a new types.DiffMainViewContext, because
"shows a diff" is the right signal, not "is stageable": reflog shows a diff (its
selection drives edit/PR/navigation) but can't be staged, while a branch's log
or the status dashboard show no diff and get no selection at all.
Staging is delegated to the side panel beneath via a GetOnStageFocusedMainView
handler mirroring GetOnClickFocusedMainView, so what "stage" means stays the
panel's concern (the working tree stages/unstages; commits will later add to a
custom patch). The files handler resolves the line's patch identity from the
diff-line metadata, maps it to a patch line, and applies a one-line patch,
choosing stage vs unstage from whether the shown diff is the unstaged or staged
side (diffSplitState).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add file and change-block ("hunk") navigation to the focused main view,
mirroring the staging view's hunk keys: `<left>`/`<right>` jump to the
previous/next hunk and `n`/`N` to the next/previous file. A "hunk" here is
lazygit's notion — a run of consecutive added/deleted lines separated by
context, not a git `@@` section — matching what the staging view jumps
between.
This is a consumer of the diff-line primitive in its forward direction:
resolve each rendered row's patch identity, then scan for the next/previous
change block (by the line type) or file boundary (by the path changing). The
file scan lands on the top of the neighbouring file even when a restructuring
pager leaves the header rows untagged, by backing up over them from the
file's first identifiable row — which is impossible without the per-line
metadata once the pager stops emitting a parseable unified diff. The
boundary arithmetic is pulled out into pure functions and unit-tested.
The anchor is the selected line if a selection is showing, else the top
visible line. With a selection we move it to the target and scroll it into
view, like the staging view; with none we stay in scroll mode, bringing the
target to the top without creating a selection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instead of a user config that always shows a selection on focus, the
focused main view starts without a selection (matching master). Pressing
<space> shows a selection in the middle of the view (no scrolling);
pressing <esc> hides it again before falling through to exiting the view.
For a long time lazygit has used the term "custom pager" to refer to
what's really a "diff renderer". A pager is a program that allows you to
view output page by page (hence the name), e.g. less; lazygit's custom
diff renderers are not pagers. It used the term only because the feature
is implemented using git's GIT_PAGER env var, but that's an
implementation detail.
Rename the 'git.pagers' config to 'git.diffRenderers', and restructure
its elements while we're at it to make things clearer:
- Add a 'type' field to explicitly specify which type of diff renderer
it is (the two fundamentally different ones are 'stdinFilter' and
'extDiff').
- Add a third type, 'rawGit', which has an 'args' field that makes it
easy to use 'git --color-words' as a custom renderer
- Unify the old 'pager' and 'externalDiffCommand' fields to a single
'command' field for both types
Existing config files are migrated automatically.
The old flow forced an up-front "normal vs detached" menu (meaningless for
commits, tags and stashes), then asked the user to type a worktree path from
scratch — easy to get wrong, and ambiguous about what relative paths resolve
against. It also offered the same two actions everywhere regardless of what
was selected.
Replace it with per-context "Worktree" menus whose items imply the intent
(new branch + worktree, worktree for an existing branch, detached worktree),
each feeding a shared name -> location -> create pipeline. The location menu
offers candidate parent directories as absolute paths instead of a blank
field, and "Worktree for a branch" is disabled (with a reason) when that
branch is already checked out somewhere, rather than failing after the fact.
Each ref/commit panel binds 'w' in its own controller and calls the matching
typed entry point on the worktree helper, so which menu opens is decided
statically by the call site rather than by dispatching on a ref's dynamic
type. The three commit panels share one menu through BasicCommitsController;
there is no longer a shared worktree-options controller.
The worktrees-panel 'n' flow and its old core (NewWorktree /
NewWorktreeCheckout) are left untouched here so nothing is written and then
rewritten; they migrate, and the dead i18n strings get removed, in a
follow-up commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`b` on a merge conflict is meant to keep both sides. With the diff3
conflict style git additionally renders the common ancestor between the
two sides, and the old ALL selection kept everything between the
outermost markers, dragging that ancestor into the resolved file.
Rename the selection from ALL to BOTH and restrict it to the top and
bottom hunks so the common base is dropped. Without the diff3 style
there is no ancestor section, so the behaviour there is unchanged.
The user-facing keybinding config was already named pickBothHunks; only
the internal enum, handler, translation and log string still said "all".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The status panel already binds the universal edit key to "edit config
file", but that's only reachable while the status panel is focused. Add
a dedicated global binding (alt+shift+c) so the config file can be
opened from anywhere.
OpenFile (`o`) is for opening a file as if it was double-clicked in
Finder/Explorer; this is useful for binary files like PNGs, but never
for text files. You want to edit them, and there's `e` for that.
With more than a couple of pagers, having to cycle forward through all
of them to reach the previous one (or to back out of an accidental press
of `|`) is tedious. Add a second binding that cycles backward.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert the remaining *Alt/*Alt[12] sibling fields (PrevItem/NextItem,
GotoTop/GotoBottom, PrevBlock/NextBlock, ScrollUpMain/ScrollDownMain,
OptionMenu, ConfirmInEditor, DiffingMenu) so the merge mechanism folds
their values into the corresponding main multi-key binding at config
load. The redundant alt-only Binding registrations across the various
controllers and the global keybindings file are gone: the merged main
field already carries every key, so the for-loop in SetKeybinding
registers them all.
Previously the patch_explorer and merge_conflicts controllers reused
Universal.PrevBlock/NextBlock for moving between hunks (or conflicts) in the
main view, sharing keys with the global side-window cycle. The two operations
are conceptually distinct: cycling side windows is a global navigation gesture,
while next/prev hunk acts on the diff in the main view. Tying them together also
blocks adding <tab>/<backtab> as side- window-cycle keys, because <tab> already
means "toggle panel" in the staging view.
Add Main.PrevHunk/NextHunk to the existing KeybindingMainConfig (which already
groups bindings for the main view across staging, patch building, and merge
conflicts) and switch both controllers to it. The defaults match the active key
set those controllers had before (<left>/<right>/h/l), so the user-visible
behavior is unchanged.
Now that quit accepts multiple keys, the historical quit-alt1 field is
redundant: existing configs that set it should keep working without the user
having to migrate, but the lazygit code shouldn't have to register the alt
binding separately.
Add a merge step that runs after the user config is loaded (and from
NewDummyAppConfig, which the cheatsheet generator and integration tests go
through) folding the alt value into the main key list. Mark QuitAlt1 deprecated
so it disappears from the generated Config.md example, while staying in the JSON
schema with a description so editors can still steer users toward the new form.
Note that instead of marking the alt config as deprecated, we could have added a
migrator that changes users' config files and gets rid of the alt config for
good. I decided not to do that, because this would render the config file
invalid for older versions of lazygit, which would then refuse to start; and
that's annoying when bisecting bugs. We'll keep the deprecated configs in the
code for a year or so, and then add the migrator.
The next commit will fold the remaining ~15 -alt-style fields the same way; the
helper is shaped to keep that mechanical.
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.
While I strongly prefer the alt-arrow bindings myself, I'm worried that existing
users might not be happy about the change, so I'm reverting it.
I'm working on supporting multiple keybindings for every command, so once that's
in, we can change the default to include both.
The previous version only enumerated the supported (non-rune) bindings.
Replace it with a description of the syntax: how to spell single-rune
keys, special keys, and modified keys; how to combine modifiers; the
keyword forms for `<space>`, `<minus>`, `<plus>`; and which combinations
are rejected because terminals can't deliver them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This changes not only how we store modifiers (inside of Key instead of passing
it separately), but also how we parse keybinding strings: it supports all
combinations of modifiers now (if the terminal supports it, that is).
For the branches panel we might consider unifying it with the existing `o`
command for creating a PR: it could check if there is a PR already, and open it
if so, or create a new one if not.
However, I also want the command in the local commits panel for the checked out
branch, and there's no existing "Create PR" command there; and the `o` command
opens the selected commit in the browser, so it's unrelated.
This commit implements the ability to cycle backward through different
all branches log visualization modes, complementing the existing forward
cycling functionality. Users can now press 'A' (Shift+a) to cycle in
reverse through the available log graph views, improving navigation
efficiency when they overshoot their desired view.
Changes:
- Added RotateAllBranchesLogIdxBackward() method to BranchCommands for
backward rotation through log command candidates
- Introduced AllBranchesLogGraphReverse keybinding configuration with
default key 'A' (uppercase)
- Implemented switchToOrRotateAllBranchesLogsBackward() handler in
StatusController that mirrors forward cycling logic
- Added English translation for "Show/cycle all branch logs (reverse)"
The implementation uses modulo arithmetic with proper handling of
negative indices to ensure seamless backward cycling through the
available log visualization options.
Change working tree files and commit files panels to use filtering
(reducing the list) instead of search (highlighting matches). This
matches the behavior of other filterable views.
The text filter matches against the full file path, not just the
filename, which is more useful for navigating large directory trees.
When toggling a directory for a custom patch while a text filter is
active, only the visible filtered files in the directory are affected,
consistent with how staging a directory in the files panel works.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
I always press 'd' in the patch building view, expecting that I can do
exactly what I can do in the staging view, to find out I need to go
space -> ctrl+p -> d and I think it's time to honour the muscle memory
and support this convenience keybinding.
I've optimised for muscle memory backwards compatibility here:
- Outside interactive rebase: press 'f' then instead of a confirmation
panel, a menu appears where you can choose to keep the selected commit's
message
- Inside interactive rebase: press 'f' then press 'c' to see the menu
for keeping the message, where if you press 'c' again it will retain the
current message. so 'fcc' is the chord to press.
We're also now showing the -C flag (which is what enables the behaviour)
against the todo.
I've picked the 'c' keybinding because 'C' was taken and it corresponds
to the flag. Previously that showed a warning about a change in
keybinding for cherry picking but it's been ages since we've made that
change so I'm happy to retire it.
The plan is to keep the original docs and schema folders unchanged for the
duration of a release; we'll only continuously update the -master copies. Right
before a new release we will copy them over.