Commit graph

3175 commits

Author SHA1 Message Date
Stefan Haller 22a508fdba Add menuKey helper to reduce noise on menu item literals
Constructing a menu item key from a literal character requires
gocui.NewKeyRune('r'), which is a bit noisy. Add a private menuKey helper in
both the controllers and helpers packages so the common case in either reads as
menuKey('r'). Duplicating the one-liner is cheaper than a cross-package import
dependency and avoids forcing every controller file to qualify the call.

The reason for doing this now is that we are going to change MenuItem.Key to a
slice of keys later in the branch, which means we'd have to add `[]gocui.Key{`
at each call site, making them even more noisy. With the menuKey helper we can
just change its signature and leave all clients unchanged.
2026-05-25 15:18:18 +02:00
Stefan Haller 12cfb9be1f Remove OptionMenuAlt1
For legacy reasons, OptionMenu was set to `<disabled>`, and OptionMenuAlt1 to
`?`. This doesn't make a lot of sense any more; get rid of OptionMenuAlt1 and
bind OptionMenu to `?` by default. This is a breaking change for users who
rebound OptionMenuAlt1 in their config, but it doesn't strike me as very likely,
and it's easy enough to fix.
2026-05-25 15:18:18 +02:00
Stefan Haller 3c279614bf Change SetKeybinding to not return an error
It always returned nil.
2026-05-25 15:18:18 +02:00
Stefan Haller 4f6cdedb1e Refresh worktrees before auto-forwarding branches
AutoForwardBranches relies on the worktree model to skip any branch
that's currently checked out in another worktree (so we don't update
its ref behind the worktree's back). The post-fetch refresh wasn't
including the worktrees scope, so any external change to the worktree
list between lazygit's startup and the fetch — a `git worktree add`,
a `git checkout` in a linked worktree, a branch rename — left the
in-memory model stale and the skip check returned false negatives.

Add WORKTREES to the post-fetch refresh scope when auto-forwarding is
enabled. We gate on the config so users with auto-forward disabled
don't pay for an extra `git worktree list` plus per-worktree rev-parse
on every fetch tick.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:59:18 +02:00
Stefan Haller f032ee8b0f Extract BranchesHelper.PostFetchRefresh to unify the two fetch paths
The post-fetch logic was duplicated in `backgroundFetch` and the manual
fetch handler: refresh a fixed set of views, then auto-forward branches
if the fetch succeeded. The two had already drifted on the refresh
scope; folding them into a single helper makes the duplication go
away and prevents it from drifting again.

Pass the fetch error through so we preserve the previous behaviour of
refreshing unconditionally but only auto-forwarding on success.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:57:46 +02:00
Stefan Haller 77652863c4 Refresh pull requests after a manual fetch
The background fetch path already includes PULL_REQUESTS in its
post-fetch refresh scope, but the manual fetch from the files view
doesn't. As far as I can tell that's an oversight from when
PULL_REQUESTS was added — there's no reason the two paths should
differ. Align them so both refresh PRs after fetching.

This also sets up the next commit to extract a shared helper for
the post-fetch refresh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:57:46 +02:00
Stefan Haller 7d1d90ae4d Preserve empty Worktrees slice when worktree list fails to load
If `git worktree list` fails, we want the Worktrees model to fall back
to an empty slice so callers iterating over it stay correct. The error
branch was setting it to `[]`, but the line below unconditionally
overwrote it with the nil `worktrees` value from the failed call.

Use an else branch so the empty-slice fallback actually sticks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:57:46 +02:00
Stefan Haller 58abf24862 Log errors from fetching GitHub PRs to the debug log, not to the Command Log
In the Command Log we only want to see errors for user-initiated actions, not
from background activity.
2026-05-10 15:47:02 +02:00
Stefan Haller d9aceaf0da Keep GitHub PR refresh error logging in one place 2026-05-10 15:40:09 +02:00
Stefan Haller 1c79fe24d0 Support GitHub Enterprise for the pull-requests feature
The branches-panel PR icons only worked for github.com remotes. There was no
fundamental reason — the auth library we already vendor (cli/go-gh) supports
enterprise tokens out of the box (GH_ENTERPRISE_TOKEN, gh auth's keyring),
and the user-facing 'services' config has long been the documented way to
tell lazygit "this domain is a github service" for the View-PR-URL feature.

The fetcher just hardcoded github.com in three places:
- a substring check on the remote URL to decide we're "in a github repo",
- the GraphQL endpoint (always api.github.com/graphql), and
- the auth lookup (always against the default host).

Plumb the resolved web domain through instead. Detection now goes through
the hosting_service ("is this remote's provider 'github'?"), which means a
user with services: { 'git.acme.com': 'github:git.acme.com' } configured
gets PR icons on their GHE remotes too.

Replacing the substring check with a provider check also tightens a latent
bug in getGithubRemotes: it previously accepted any remote whose URL parsed
with the default regex, including gitlab and bitbucket — masked today only
by the InGithubRepo gate, but exposed once the gate goes away.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:28:14 +02:00
Stefan Haller 7a2d1ab544 Clear cherry-pick buffer when copying after a paste
After a successful paste DidPaste is true, hiding the "X commits copied"
indicator but leaving the buffer populated. From the user's perspective
this looks like a clean slate, so a new shift+C should start fresh. It is
important to reset DidPaste first, before populating the buffer with the new
commits, because otherwise each loop iteration would overwrite the previous one
since Add() rebuilds the set via SelectedHashSet() which returns empty while
DidPaste is set.
2026-05-09 14:02:42 +02:00
Stefan Haller 4734bab896 Improve performance of inline status spinner
Now that HandleRender no longer does an implicit Render(), we can use it inside
OnUIThreadContentOnly to save performance, on the assumption that rerendering a
view that contains an inline spinner never changes the layout.
2026-05-09 13:58:33 +02:00
Stefan Haller 76211eea68 Remove Render() from ListContextTrait.HandleRender
self.c.Render() at the end of HandleRender was there to schedule a gocui Update
tick so the view content modified above would actually get drawn. For UI-thread
callers (the great majority -- keybinding handlers, the layout function itself,
popup resize, etc.) this was unnecessary work, since gocui already runs a
layout/redraw cycle after every event. SimpleContext.HandleRender doesn't call
Render() either, so this aligns the two implementations.

The few callers that drove HandleRender from a worker goroutine and relied on
Render() for the flush were wrapped in OnUIThread in the preceding commits, so
the implicit Render is no longer needed.

Also, Render() being called *before* setFooter() looks like it might have been a
theoretical race; this is no longer an issue now.
2026-05-09 13:58:33 +02:00
Antoine Gaudreau Simard 8675862228 Bounce setGithubPullRequests' Branches render to UI thread
Fixes an issue mostly noticeable using the go -race mode, this resolves
them.
2026-05-09 13:58:33 +02:00
Antoine Gaudreau Simard 225bcaa619 Bounce commit files selection render to UI thread
The redraw of the selection color (using <space>) would be tied to the
spinner drawing. To reproduce, having a high spinner refresh rate and
toggling a file would see a delay equivalent to the time spinner refresh
rate.
2026-05-09 13:58:33 +02:00
Stefan Haller 3d324ed7fb Bounce SuggestionsContext.SetSuggestions to UI thread
SetSuggestions has two callers: prepareConfirmationPanel calls it directly on
the UI thread, while editors.promptEditor and
SuggestionsContext.RefreshSuggestions call it via AsyncHandler, which runs the
result closure on a worker goroutine. The worker path currently relies on
HandleRender's self.c.Render() to flush the view update. Wrap the body in
OnUIThread so the worker path stays correct when Render() is removed; for the
UI-thread caller the extra bounce is harmless.
2026-05-09 13:58:33 +02:00
Stefan Haller a364a8d75c Bounce explicit LocalCommits render in refreshBranches to UI thread
refreshBranches runs on a worker goroutine and re-renders the commits view
directly to refresh the branch-head visualization. As with refreshView, this
currently only flushes because HandleRender ends with self.c.Render(). Wrap the
explicit HandleRender call (along with the LocalCommitsMutex pair around it) in
OnUIThread so it keeps working once Render() is removed.
2026-05-09 13:58:33 +02:00
Stefan Haller 0d1caf5c22 Bounce refreshView to the UI thread
An async refresh dispatches refreshXyz on a worker goroutine, which then calls
refreshView -> PostRefreshUpdate -> HandleRender. Today the final
self.c.Render() inside ListContextTrait.HandleRender is what triggers a UI flush
from the worker. We're going to remove that Render() call, so prepare by
wrapping refreshView's body in OnUIThread.

This moves the entire rendering of the view (and the ReApplyFilter/ReApplySearch
stuff) to the UI thread, not just the layout. I don't expect this to make a
difference in practice, and it is already one step towards my long-term goal of
moving all view rendering to the UI thread (see
https://github.com/jesseduffield/lazygit/issues/2974#issuecomment-1729154768).
2026-05-09 13:58:33 +02:00
Stefan Haller 79440c0945 Set the view color on the UI thread too
Unrelated to this branch, just because we're touching this code: there's little
reason to set the color on the background thread but the text on the UI thread.
Set them both together on the UI thread. Avoids a data race (unlikely to be a
problem in practice, we're talking about a 64-bit int, but still).
2026-05-09 13:58:33 +02:00
Stefan Haller 670565c175 Have renderAppStatus trigger a full layout when the appStatus width changes
In 0d195077e4 we improved the performance of the status bar spinner by
avoiding a layout. This is fine from one spinner tick to the next, but it's a
problem when spinning starts or ends (or in the hypothetical case that the
status text changes in the middle of the operation, which we never do in
lazygit, but theoretically could). In this case a layout is needed so that the
rest of the status bar gets pushed over appropriately (or moves back to the left
when the spinner ends), and also so that the bottom line is shown or hidden
properly for users who set gui.showBottomLine to false.

To fix this, keep track of the status string width and force a layout whenever
it changes. This includes the beginning and end of an operation when it changes
from empty to non-empty or vice versa.

There is currently no observable misbehavior from this bug, but that's only
because we must have a HandleRender call somewhere that forces a full layout
when an operation starts or ends. We will remove the Render() call from
HandleRender at the end of this branch, at which point the misbehavior would be
visible if we didn't fix it here.
2026-05-09 13:58:33 +02:00
Stefan Haller 4f0393f97b Fix visual glitch with status bar spinner at very slow spinner rates
With a very slow spinner rate (seconds), you can see that at the beginning of an
operation the bottom line leaves a gap for where the status will go, but the
status (and spinner) is only drawn the first time the spinner ticks. The reason
is that layout looks at GetStatusString to decide how much room to leave, rather
than at the actual content of the AppStatus view; the view content is only set
by renderAppStatus the first time the spinner ticks.

Fix this by making layout look at the actual content of the view so that the
layout is in sync with what is drawn. This also avoids flicker if an operation
is so fast that it finishes before the spinner ticks for the first time,
especially for users who set gui.showBottomLine to false.
2026-05-08 22:42:23 +02:00
Antoine Gaudreau Simard b7edcbad3a Improve performance of spinner in Synchronized events
Instead of redrawing the two views on each tick, only redraw the spinner
2026-05-06 20:38:53 +02:00
Antoine Gaudreau Simard 0d195077e4 Improve performance when drawing the spinner on background events 2026-05-06 18:53:32 +02:00
Antoine Gaudreau Simard 9c3e7dac88 Support to request a content-only UI refresh
This skips the whole-UI layout calculations, and lets tcell's dirty cell
handling redraw only changed cells.
2026-05-06 18:53:32 +02:00
Antoine Gaudreau Simard 79727f1780 Honor the spinner rate configurate with sync spinner 2026-05-06 18:53:32 +02:00
Stefan Haller 164f7da33d Fix missing layout call after switching repos
This didn't cause a bug so far because switching repos always happens from
within an OnWaitingStatus, so the spinner would take care of calling layout and
draw. However, later in this branch we are going to optimize the spinner so that
it no longer calls layout, at which point this would break, so make sure we
rerender at the point where it's needed.
2026-05-06 18:53:32 +02:00
Stefan Haller ee94e215e7 Remove dead Modifier field from keybindings
Modifiers were moved into Key in 22169e22f, but the separate Modifier field
on types.Binding and gocui.keybinding was left behind. The keypress matcher
already compares modifiers via Key.Equals, so the old field is never read on
the dispatch path; it just got passed through SetKeybinding and stored.

Drop it from gocui.keybinding, types.Binding, and the SetKeybinding /
DeleteKeybinding signatures, and remove every now-redundant Modifier:
gocui.ModNone struct field. Mouse bindings keep their own Modifier (on
ViewMouseBinding) since that path still consults it.
2026-05-06 09:51:23 +02:00
Stefan Haller 127dd9d325 Expose PushUrls to custom commands 2026-05-04 13:09:47 +02:00
Stefan Haller 6f1c57b604 Show PushUrls for selected remote if there are any 2026-05-04 13:09:47 +02:00
Stefan Haller 35d3659cce Use more widely-supported Unicode symbols for the commit graph
The commit graph used '⏣' (U+23E3 BENZENE RING WITH CIRCLE) for merge
commits and '◯' (U+25EF LARGE CIRCLE) for regular commits. Both have
very poor coverage in popular monospace fonts:

- '⏣' lives in the Misc Technical block and is essentially absent from
  every common monospace font (Source Code Pro, JetBrains Mono, Fira
  Code, Cascadia Code, Hack, Iosevka, Menlo, Consolas, Monaco, IBM
  Plex Mono, Ubuntu Mono, Noto Sans Mono, Inconsolata). It is always
  drawn from a system fallback font.
- '◯' is the late-addition LARGE CIRCLE codepoint. It is present in
  some fonts (Cascadia, Fira Code, Hack, Iosevka, Menlo, Noto Sans
  Mono) but missing from many others, including Source Code Pro and
  most Nerd Font derivatives based on it.

This is why the graph renders inconsistently across platforms even
when the same monospace font is configured: each OS picks a different
fallback font (Apple Symbols on macOS, Segoe UI Symbol on Windows,
Noto/DejaVu/Symbola on Linux), and the substituted glyphs differ in
shape, weight, and advance width. '◯' is also East Asian Ambiguous
width, so some terminals render it wider than one cell, exaggerating
the misalignment.

Replace the symbols with codepoints from the foundational 1991
Geometric Shapes block, which has far broader font coverage:

- Merge: '◎' U+25CE BULLSEYE -- concentric circles, the visually
  closest cousin to the previous benzene-ring glyph.
- Commit: '○' U+25CB WHITE CIRCLE -- the same hollow-circle silhouette
  as before, just a more universally available codepoint.

The new symbols are present in the font directly in significantly
more cases; and when fallback is still required, they are universally
well-drawn (unlike '⏣', which many fallback fonts also lack).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:06:29 +02:00
Stefan Haller 8dbdd74400 Fix linter warnings about ignoring errors from bufio.Scanner 2026-05-03 16:57:24 +02:00
Stefan Haller 562d0541a1 Fix linter warning about inefficient string concatenation 2026-05-03 16:39:41 +02:00
stk 3ffc4ac832 Remove now unnecessary Setenv call
The call was meant to guarantee a UTC time zone to make the tests deterministic,
but as the previous commit explained, this only worked on Mac and Linux, not on
Windows. Since we now have a better fix, this workaround is no longer needed.
2026-05-02 17:59:27 +02:00
Stefan Haller bfdea2bb4f Add a forwardDeleteWord keybinding
Bound to alt-delete on Mac, and ctrl-delete on Windows and Linux.
2026-04-30 22:31:44 +02:00
Stefan Haller 976dbec9ff Make edit keybindings for move-by-word and backspace-word configurable
And make them platform dependent so that they are with ctrl on Windows and
Linux, but alt on Mac.
2026-04-30 22:31:44 +02:00
Stefan Haller 22169e22ff Move modifiers into Key
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).
2026-04-30 22:22:53 +02:00
Stefan Haller 30b619b3db Get rid of pkg/gui/keybindings package
Move keybindings.GetKey to config.GetValidatedKeyBindingKey
2026-04-30 22:22:53 +02:00
Stefan Haller 69e99ffd64 Move keybindings.LabelFromKey to config package
So that it is next to KeyFromLabel.
2026-04-30 22:22:53 +02:00
Stefan Haller c455fca062 Add config.KeyFromLabel 2026-04-30 22:22:53 +02:00
Stefan Haller 5d3715f96b Bump tcell dependency to v3 2026-04-30 22:14:26 +02:00
Stefan Haller 64996d12d9 Add Key type
This bundles the keyName and a rune, so that we don't have to pass these around
separately everywhere. This should make it easier to swap out the rune for a
string when we upgrade to tcell v3.
2026-04-30 22:14:26 +02:00
Stefan Haller bff77fc4f9 Rename Key to KeyName
This is to free up the name Key for a type that can be either a KeyName or a
rune (or later a string when we upgrade to tcell v3).
2026-04-30 22:14:26 +02:00
Stefan Haller ff58687b68 Cleanup: remove unnecessary function Label()
It's a no-op. (I think this might not have been the case in the past, where
Label() was needed to "normalize" a keybinding or something.)
2026-04-30 22:14:26 +02:00
Stefan Haller f866f04b7c Cleanup: simplify code
The wrappedHandler business is no longer needed (and maybe never was).
2026-04-30 22:14:26 +02:00
Stefan Haller e2e58e3811 Cleanup: remove obsolete code
Should have been deleted in b955002d17.
2026-04-30 22:14:26 +02:00
Stefan Haller 196e0a3c17 Copy gocui files into lazygit repo under pkg/gocui
I copied all files except dot files (.github and .gitignore), the _examples
folder, and go.mod/go.sum.

At some point we may want to copy the files back to the gocui repo when other
clients (e.g. lazydocker) want to use the newer versions of them.
2026-04-30 14:29:08 +02:00
Stefan Haller cb36455f87 Define PR colors as hex colors to make them the same as GitHub's
This is so that they look the same no matter what color palette the terminal is
using. (One user complained that the text for the Open state is barely readable,
because they are using a palette that has a very pale green.)

GitHub uses slightly different colors depending on light vs. dark mode;
fortunately they are very close, so hopefully we can ignore this. I picked the
ones for dark mode here, on the assumption that this is more common.

Also, not all terminals support true hex colors; for example, Terminal.app on
macOS doesn't, so it maps the colors to the closest ones in the Xterm-256
palette. This shouldn't be a huge problem, but for some reason it displays draft
PRs as something closer to Cyan than grey, and I don't understand why.
2026-04-26 17:00:43 +02:00
Stefan Haller cef1f8fc2c If multiple remotes exist but only one is a Github remote, pick it without prompting
If the repo has multiple remotes, but only one of them is on Github (the others
might for example point to a self-hosted Critic server or something like that),
lazygit would still present a menu to choose the remote for pull requests, but
it would contain only that single entry. That's pointless, pick it automatically
without prompting.

We add some tests while we're at it; these wouldn't have caught the problem,
because they only test getGithubBaseRemote which already takes the filtered
github remotes. It's still better than not having any tests; the real issue
could only have been caught with an integration test, which we don't bother
adding.
2026-04-26 16:55:51 +02:00
Stefan Haller 5b596bb62b Stop showing cached PRs when we no longer have a base remote
If we used to have a base remote (e.g. because there was only one), but now we
don't (e.g. because the user has added a second one, and we don't know yet which
one is the base), stop showing the cached PRs. This is especially important in
the case that the user cancels the prompt, in which case we don't have a base
remote for the rest of the session, but would keep showing the (potentially
stale) cached PRs forever.
2026-04-25 14:23:43 +02:00
Stefan Haller 227081f1d1 Preserve whitespace when remembering a commit message
This is useful when cancelling out of the commit panel mid-sentence (after
having typed the space for the next word); when entering the commit message
panel again, the space was gone and you had to type it again. Small thing, but
it just seems better to resume the panel in exactly the state that you left it
in. (Which we actually don't do; we don't remember the cursor position, or which
of the subject/description panels was active. That would be a separate
improvement.)

The save path and the load path used to be asymmetric. On save, the textarea
getters applied strings.TrimSpace, which stripped any leading blank lines, a
trailing newline after the cursor, or indentation on the very first line of the
description — all of which are legitimate user content. On load,
SplitCommitMessageAndDescription did TrimSpace on the description as well, and
the preserved message was routed through that same git-format split because
HandleCommitPress passed it as OpenCommitMessagePanel's InitialMessage. The
result: every round-trip through "escape and reopen" silently mutated the
message.

The fix is to treat our own preservation file as its own format, distinct from
git's canonical "summary\n\nbody" format:

- The textarea getters return raw content. strings.TrimSpace moves to the one
  place that still needs it: the empty-summary check in HandleCommitConfirm (git
  itself strips trailing whitespace and blank lines, so no pre-trim is needed
  before -m).
- SplitPreservedCommitMessage / SetPreservedMessageInView split on the single
  "\n" our Join uses, without any trimming — truly lossless.
- SplitCommitMessageAndDescription keeps its git-format behavior but replaces
  TrimSpace with TrimPrefix("\n"), so it strips only the blank-line separator
  and leaves body indentation intact.
- HandleCommitPress now mirrors HandleWIPCommitPress: it no longer passes the
  preserved message as InitialMessage. OpenCommitMessagePanel resolves the
  preserved content itself, uses it for display via the preservation-format
  setter, and stores it as the initial message so the close-time "did the user
  change anything?" check still correctly detects a cleared panel.
- GetInitialMessage no longer trims. With raw getters on both sides of the
  comparison, trimming here caused spurious non-matches (e.g. for preserved
  content with trailing whitespace). The original motivation — matching a
  "WIP: " prefix with trailing space — works unchanged.
- UpdateCommitPanelView becomes dead code and is removed; its one remaining
  caller (history cycling, always git-format) goes directly through
  SetMessageAndDescriptionInView.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 18:51:17 +02:00
Stefan Haller 337ab2a276 Preserve commit message when quitting while the commit message panel is open
This isn't possible with the default quit binding ('q'), but there's an
alternative binding (ctrl-c) that does work while the panel is open; also, users
can rebind quit to something like ctrl-q.
2026-04-21 11:25:29 +02:00
Stefan Haller afbfbb27a4 Add OnQuit hook for controllers
This allows the controller of the currently focused context to do some last
minute cleanup before quitting.
2026-04-21 11:25:29 +02:00
Stefan Haller 3de2a2d96c Extract PreserveCommitMessage from CloseCommitMessagePanel
We'll want to call just the preservation step when the app is quitting
with the commit message panel open: at that point the view-hiding and
context-popping in CloseCommitMessagePanel are pointless (the app is
about to exit), and some of the focus-lost side effects could be
surprising during teardown.
2026-04-21 11:25:29 +02:00
Stefan Haller 5c61126c4b Remove unnecessary clearing of the commit message panel on close
There's no reason for this; when opening the panel next time, it will be
populated again. Also, it doesn't make sense to do this only when not preserving
the message.
2026-04-21 11:23:37 +02:00
Stefan Haller d3095f6fd4 Fix copying a file's absolute path when running in a linked worktree 2026-04-19 11:16:02 +02:00
Stefan Haller 4218f97a21 Don't default the base repo for pull requests to "origin"
When no base repository is configured using gh, we have several heuristics for
choosing a likely base repo before prompting the user. One of these was that if
the origin remote exists, we'd choose that. This may be the right choice in some
cases, but in many others it's not; for example, in the common scenario of a
forking setup where the upstream repo's remote is called after its owner, and
the fork remote is called origin, we would choose origin as the base repo, which
results in not showing any PRs. Don't do that, and instead prompt the user for
the base repo in this case.
2026-04-13 22:00:54 +02:00
Stefan Haller c0d9640c0b Hide closed pull requests on main branches
The assumption is that if a pull request exists on a main branch, it was usually
created by mistake and then closed, and showing it serves no purpose and is only
distracting.

We keep showing open pull requests for main branches though, because this allows
you to notice that there is one that you probably want to close.

This only affects the display (in the branches list and in the main view);
opening the PR in the browser using shift-G is still possible, as is copying its
URL to the clipboard.
2026-04-13 21:56:49 +02:00
Martin T. 4cb6fafb08 Add condition field to custom command prompts
When building multi-step custom command forms, some prompts are only
relevant depending on earlier answers. Without conditional logic,
users must dismiss irrelevant prompts manually.

Prompts now accept a `condition` field with a template expression
evaluated against prior form values. Skipped prompts default to
an empty string.

The template expression is a string pre- and suffixed with double curly
braces - {{}}.

Form keys can be reused, a guard ensures that skipped prompts do not
reset already set form keys with an empty string. This allows the
conditional flow to remind a user to set a key that was left empty
because additional conditions want that key to be set. This removes the
need to have additional if checks in the command that uses the form
keys.
2026-04-06 19:14:51 +02:00
Stefan Haller e4309f101c Log hashes of local branches when deleting them
This makes it easier to recover them if they were deleted accidentally.

We only do this for local branches for now, we don't bother for remote branches;
it would be trickier there, because the branch on the server might not actually
point to the same hash as our local remote tracking branch does.
2026-04-06 19:06:25 +02:00
Stefan Haller 7918858023 Localize "Dropping stash" log
This was forgotten in #4850.
2026-04-06 19:06:25 +02:00
Stefan Haller e92b04e1dc Don't refresh pull requests when checking out a local branch
For esthetic reasons, checking out a branch (or other ref) blocks the UI until
the refresh is done, so it's important that the refresh doesn't do unnecessary
work. Refreshing pull requests is unnecessary (but costly, when waiting for it)
when a branch is checked out that already existed locally. However, it is
required when checking out a remote branch for the first time, so that the PR
icon appears immediately when there is one.
2026-04-01 09:13:55 +02:00
Stefan Haller 6ba46ad604 Change "Copy pull request URL to clipboard" command to use existing PR if there is one 2026-04-01 09:13:55 +02:00
Stefan Haller a462b7c420 Add commands for opening a Github PR in the browser
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.
2026-04-01 09:13:55 +02:00
Stefan Haller 6145eaf939 Prompt only once per session for each repo
If the user hits escape in the "Select base repository for pull requests"
prompt, don't bother them again for this repo at the next refresh.
2026-04-01 09:13:55 +02:00
Stefan Haller 28affa3399 Add an optional onCancel hook for menus 2026-04-01 09:13:55 +02:00
Stefan Haller 7a0320b4ec Call OnMenuPress(nil) when hitting esc in a menu
OnMenuPress can already deal with the selected item being nil, so this allows us
to add common code to it that is run when cancelling the menu.
2026-04-01 09:13:55 +02:00
Stefan Haller 2ab78ad039 Show PR information in main view, above the branch log 2026-04-01 09:13:55 +02:00
Jesse Duffield fff6003044 Cache PRs in AppState so that they appear immediately at startup
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-04-01 09:13:55 +02:00
Jesse Duffield ca9eeebea3 Show PR icons in branches list
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-04-01 09:13:55 +02:00
Jesse Duffield d33fa5bb05 Add pull requests to lazygit's model and refresh them
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-04-01 09:13:55 +02:00
Stefan Haller 649679c33a Remove branch icons from Branches list
The only information that this carries is whether an entry is a real branch or a
detached head. Detached heads can only be at the top, and they are easy to tell
apart from the other branches by their name, so the icon is not really very
useful.

We are going to show PR icons in this column.
2026-03-31 15:48:28 +02:00
Stefan Haller 968c44b02f Add missing entries to scopeNameMap 2026-03-31 15:48:28 +02:00
Stefan Haller 47db2c24cf Allow turning off nerd fonts without restarting
Not a terribly important feature for users, probably, but it is useful for
developers when testing a new feature with or without nerd fonts; and it does go
against or policy of all configs being hot-reloadable.

Note that switching from nerd fonts version 2 to 3 is *not* supported without
restarting, but I find this less important.
2026-03-31 15:48:28 +02:00
Stefan Haller fde4bc1fb2 Allow customizing the window width/height thresholds for when to use portrait mode 2026-03-31 13:08:46 +02:00
Stefan Haller f8fd8d04bc Fix typo 2026-03-31 13:08:46 +02:00
Stefan Haller b8d23dd5a5 Make file sort order and case sensitivity configurable
Set the sort order's default from the former foldersFirst to mixed, so this is a
change in behavior. I find this useful because it now matches git's order, so if
you look at the diff of a commit, the TOC at the top has the same order as the
file tree you see when entering the commit.
2026-03-30 17:53:25 +02:00
Stefan Haller 9f38d02a4e Don't stage out-of-date submodules when asking user to auto-stage after resolving conflicts
When you rebase a branch and there are conflicts, lazygit asks you to continue
the rebase when it detects that all conflicts have been resolved. However, it is
common to make additional changes beyond just fixing the conflicts (e.g. to fix
build failures), and when doing that while the "Continue the rebase?" prompt is
showing, lazygit detects that too and asks you if you want to stage those newly
modified files too. This is all well and good (and can be disabled for those who
don't like it); however, lazygit would treat out-of-date submodules as unstaged
changes and would offer to stage those as well, and this is pretty bad and
almost never what you want. Fix this by excluding submodules from that second
check.
2026-03-30 17:41:53 +02:00
Stefan Haller 781105c49b Scroll to top when showing subcommits
Previously, when showing subcommits of a branch or tag, scrolling down, and then
going out and entering subcommits again, it would select the first commit but
keep the previous scroll position, so that the selection was not visible. Always
scroll to the top to fix this.
2026-03-27 14:55:02 +01:00
Stefan Haller 4d46f5af94 Improve performance for discarding range selection of many files
This is probably the less severe case, but it could still be an issue for people
who have many modified top-level files they want to discard, and have
showRootItemInFileTree set to false; they could select all those files by
pressing 'v' and '>'.
2026-03-27 14:34:47 +01:00
Stefan Haller 4a8ab6f64e Cleanup: simplify by using lo.FilterMap instead of separate calls to Filter and Map 2026-03-27 14:34:47 +01:00
Stefan Haller 3eb5841b83 Fix searching commits or main view after switching repos
When switching to a different repo, and then back to the original one, searching
would no longer work. The reason is that our contexts set callbacks on their
views; when switching to a different repo we instantiate a new set of contexts,
so they will overwrite the views' callbacks with their new ones, but when
switching back to the original repo, we reuse the old contexts because they are
still in memory, but they won't set their callbacks again since they only do
this on construction.

To fix this, replace the view-local callbacks with a global one on the gui that
takes the view as an argument, so that the callback can look up the associated
context dynamically.
2026-03-27 14:26:20 +01:00
blakemckeany ee3bb06b2a Add support for clicking on arrows in the file list to expand/collapse directories
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-03-25 17:08:35 +01:00
blakemckeany 4567840198 Add GetOnClick to HasKeybindings
Can be used for doing additional click handling in list views.

Like the GetOnDoubleClick hook we should try to find a better design for this
than putting it in HasKeybindings and BaseContext, since it is only used by list
contexts.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-03-25 17:08:02 +01:00
blakemckeany 61b72cef38 Add GetVisualDepth method to FileTree/CommitFileTree 2026-03-25 16:41:12 +01:00
Stefan Haller fe5df2334b Document some of the methods of HasKeybindings
We have some documentation for the corresponding setters in IBaseContext, but
that's part of the controller infrastructure and not client facing. For somebody
implementing a new view, this is where they will probably look for what methods
they can override.
2026-03-25 16:41:10 +01:00
Stefan Haller 72bff90822 Rename GetOnClick (et al) to GetOnDoubleClick
When this was originally introduced, it handled single clicks on a list entry
(treating them similar to a double-click by checking whether the click was on
the selected entry). Arguably it should have been called OnDoubleClick back then
already; but when we later changed it to do actual double-click detection (see
37197b8e9a), we should have renamed the methods.
2026-03-25 14:58:53 +01:00
Stefan Haller d9487bf31c Fix the expanded layout of the branches panel (half and full screen modes)
If the showDivergenceFromBaseBranch config is not none, the expanded branches
panel would no longer show the remote branch and subject; the reason was that we
padded the name all the way to the right edge of the view so that the divergence
information is right-aligned.

To fix this, we simply don't right-align the divergence in half or full screen
mode. This is a bit unfortunate, but it's hard to do any better, because we
don't know the width of our column in advance. One thing I tried is to make the
divergence a separate column in that case (with an explicit right-alignment set
on it via getColumnAlignments; however, this makes it harder to properly
truncate the name when the window is too narrow.
2026-03-23 17:25:41 +01:00
Stefan Haller 6bfcab3d89 Fix selection after staging an added line
In some cases, staging an added line could result in the previous deleted line
to become selected, rather than the next added line.
2026-03-19 16:57:53 +01:00
Stefan Haller 5b23f80398 Re-render main view when changing screen mode
This is needed for diffs in case a custom pager is used and its output depends
on the window width (e.g. the horizontal lines in delta).
2026-03-09 19:42:28 +01:00
Stefan Haller 46c53ea34f Cleanup: use CurrentSide() instead of private currentSidePanel
They both do the same.
2026-03-09 19:42:28 +01:00
Ruud Kamphuis 6ad56d5852
Show worktree name next to branch in branches list
When a branch is checked out by another worktree, show the worktree
name in the label, e.g. "(worktree cosmos2)" instead of just
"(worktree)", so you can immediately see which worktree holds it.
2026-03-09 11:34:34 +01:00
Stefan Haller 9dd121dfa3 Enable { and } to change diff context size in branches and tags panels in diffing mode
Actually, we simply enable them in any panel, even those that can never show a
diff. Since the commands show a toast, that's enough feedback to understand what
happened; the change will take effect the next time you switch to a panel that
does show diffs.

Same for ( and ) to change the rename threshold.
2026-03-08 17:51:00 +01:00
Stefan Haller b955002d17 Use new ShouldHandleMouseEvent hook to prevent clicks in views behind panels
This fixes two problems:
- if the previously focused view (behind the panel) was a list view, it would
  look like the click would select a different row, because gocui would still
  set the view's cursor position, which is used to draw the highlighted row
- it was still possible to click on tab headers, and this would dismiss the
  panel
2026-03-08 17:32:09 +01:00
Stefan Haller da065b5b63 Add comments
I got confused when I investigated the bug that was fixed in the previous
commit, so hopefully these will be helpful for the next person reading this
code.
2026-03-08 17:24:26 +01:00
Stefan Haller bcfb4e11c0 Fix off-by-one error when calculating popup panel dimensions
The symptom of the bug was that confirmation panels with wrapped text were one
character too wide, which could sometimes result in them being one line too
heigh. One concrete example is the "Discard file changes" confirmation that
appears when pressing `d` in the commit files panel.

I got very confused when investigating this bug, and the reason is that
getPopupPanelDimensionsAux took two parameters panelWidth and panelHeight, but
the second was actually contentHeight. Rename it to contentHeight, and change
the first one to also be the content width rather than the panel width. This is
easier for clients, and less confusing.
2026-03-08 17:24:26 +01:00
Zak Siddiqui 7d32e45f73 Add backward cycling support for all branches log view
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.
2026-03-08 17:06:17 +01:00
Ruud Kamphuis 727b4b56dd Show branch name and detached HEAD in worktrees tab
The worktrees tab now displays the checked-out branch (or detached HEAD
state) for each worktree, making it much easier to see which worktree
holds which branch.

This is useful when you need to "unlock" a branch that's occupied by
another worktree: you can now clearly see which worktrees are on a
branch vs detached, without having to select each one individually.

Also rename the "(main)" label to "(main worktree)" to avoid confusion
with the common "main" branch name, and move it after the branch column.
2026-03-08 12:51:46 +01:00
Jesse Duffield 615c566ac4 Filter file views rather than search
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>
2026-03-07 20:04:31 +01:00
Jesse Duffield 8728da7985 Only reset selection in ReApplyFilter when search prompt is active
Without this check, the selection was being reset to 0 whenever
ReApplyFilter was called for the current filter context, even when
the user wasn't actively typing in the search prompt (e.g. when the
model updates in the background). This was causing unexpected cursor
jumps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:04:31 +01:00
Jesse Duffield ed9693ebcc Reset PrevSearchIndex when opening filter prompt
When opening a filter prompt, reset PrevSearchIndex to -1 to avoid
stale search state from a previous search/filter session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:04:31 +01:00
Jesse Duffield 60316388c0 Rename GetFilter to GetStatusFilter on IFileTree
This avoids a naming collision with GetFilter from the
IFilterableContext interface, which will be implemented by
FileTreeViewModel in the next commit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:04:31 +01:00
Jesse Duffield 5b6a8a304e Prevent patch building panel from briefly appearing when discarding change from commit 2026-03-07 20:47:20 +11:00
Jesse Duffield 0f2f20b900 Allow discarding lines from patch directly
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.
2026-03-07 20:47:20 +11:00
Stefan Haller b3d81a0c7c Add DisabledReason for discarding from a multiselection of commits 2026-03-07 20:47:20 +11:00
Stefan Haller 35d06c32d7 Turn error checks in discard handler into DisabledReasons
That's how we usually do this; it's useful to see in the keybindings menu that
the command is disabled.
2026-03-07 20:47:19 +11:00
Stefan Haller 2b65bee675 Remove redundant CantPatchWhileRebasingError checks in handlers
We don't add the commands to the menu when we're rebasing, so it's pointless to
check this in the handlers.

I guess alternatively we could add all commands always, and strike out the ones
that are not applicable using DisabledReasons. But we don't usually do this in
menus (except the global keybindings menu).
2026-03-07 20:47:19 +11:00
Stefan Haller 7f9bfaec08 Warn more prominently about resetting the patch when discarding changes
Only mention resetting the patch when there actually is one. This way users have
to read less text in the normal case, and the added note hopefully stands out
more if there is one. Also, separate the note from the previous text by a blank
line.
2026-03-07 20:47:19 +11:00
Stefan Haller b256215397 Say "discard" instead of "remove" when discard changes from a commit
"Remove" can be confusing when a deleted file is selected; in this cases it
actually "un-removes" it. "Discard" hopefully makes it clearer that we are
talking about the change to a file, and not the file itself.
2026-03-07 20:47:19 +11:00
Stefan Haller 641467bc79 Hide the "Fetching..." status of the auto-fetch when bottom line is not showing
If the bottom line is hidden (by setting gui.showBottomLine to false in the
config), don't show the "Fetching..." status because it changes the layout,
which should happen only in response to user actions.

However, keep the status for the first fetch after startup, and right after
switching repos, because in these cases it is useful to see when the fetch is
done.
2026-03-02 10:46:09 +01:00
Daniel Wennberg 8821770711 Fix matching of lazygit-edit URLs without line numbers
Tweak regexp such that a trailing ":" is not included in the file path
extracted from a lazygit-edit URL.

Previously, when matching a URL containing the ":" separator but no line
number, such as `lazygit-edit:///path/to/file.ext:`, the trailing
separator would be included in the matched file path, so lazygit would
open the non-existent file `/path/to/file.ext:`. Notably, such urls are
created when using delta with the hyperlink feature, as suggested in
https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md#delta,
and clicking a file path rather than a line number.
2026-02-22 17:14:23 +01:00
Stefan Haller d97531aeef Fix rendering of the Reflog panel when using up/down to scroll it
This is a regression introduced with #5135. I didn't notice it because I am
using `gui.scrollOffBehavior: jump`, in which case the problem wouldn't appear;
it only happened with `scrollOffBehavior: margin` (which is the default). In
that case, if you used the arrow keys (or j/k) to move the selection so that the
view would start to scroll, empty space would appear.
2026-01-29 14:23:16 +01:00
Stefan Haller 06a9ff12ab Bump gocui
This brings in https://github.com/jesseduffield/gocui/pull/98 with the following
fix:

Fix rendering of CRLF sequence ('\r\n')

The FirstGraphemeCluster call returns this as a single character; we want to
treat it the same way as a single \n.

This would be a problem if e.g. a progress bar used \r repeatedly to paint over
the same line, and then printed a \n to move on to the next line; the last pair
of \r and \n was swallowed.

Another scenario where this was a problem was if you stream output of a command
to the log, and the command used \r\n as line feeds. This happens for example
for a background fetch that fails with an error; in that case we print the
combined output (stdout plus stderr) to the log after the command finished, and
for some reason it uses \r\n in that case (I can't actually explain why; when I
do `git fetch --all | xxd` I see only bare \n characters). All output would
appear on one line then.

Also, filter out escape sequences for character set designation; there's nothing
useful we can do with them. In practice, the only one that you are likely to see
is `ESC ( B`, which is sent as part of tput sgr0, which is sometimes used in
scripts to reset all graphics attributes to defaults.
2026-01-28 20:57:09 +01:00
Stefan Haller 02997d30d9 When moving commits up/down and the selection leaves the view, scroll to make it visible again
This used to work before 0.58, but broke with efd4298b5e
(#5134).
2026-01-28 20:44:09 +01:00
Jesse Duffield f317a97ac1 Support using the selected commit's message in a fixup
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.
2026-01-28 10:28:52 +01:00
Jesse Duffield 6f6545e44f Pass todo flag to EditRebaseTodo
Not used yet, we pass an empty string everywhere, to match the previous
behavior. Just extracting this into a separate commit to make the next one
smaller.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:51 +01:00
Jesse Duffield 99e26be3d9 Show -C for fixup todos in rebase if present
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:48 +01:00
Jesse Duffield 3bf833aea7 Remove the "old cherry-pick key" warning
It's been ages since we changed the key, users should hopefully be used to it by
now, and we want to reuse the key for something else later in the branch.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-01-28 10:15:15 +01:00
Stefan Haller 1ede4e6eb9 Limit popup panel widths to a maximum width
On very large screens they would get ridiculously wide.

The limits we chose here are a little arbitrary:
- 80 for confirmations and prompts
- 90 for menus
- auto-wrap width plus 25 for the commit message editor (or 100 if auto-wrap is
  off)
2026-01-28 09:02:27 +01:00
Stefan Haller c10dfcefcf Fix opening a menu when a previous menu was scrolled down
To reproduce: open the keybindings menu ('?'), press '>' to select the last
line, esc to close it, open it again. The view would appear still scrolled down,
so the selected first line was out of view. Even worse, if you open a different,
shorter menu (e.g. the "View upstream reset options..." menu in the Files
panel), you'd only see the Cancel item, the other ones were scrolled out of
view.

This is a regression introduced with efd4298b5e (#5134).
2026-01-25 16:17:13 +01:00
Stefan Haller 268d1934f8 Ignore fixup commits for a found base commit when doing ctrl-f 2026-01-24 16:04:35 +01:00
Stefan Haller 4bcf3e181c Extract a helper method getCommitsForHashes 2026-01-24 16:04:35 +01:00
Stefan Haller df1e5512da Make getHashesAndSubjects a free-standing function
There's no reason for this to be a method.
2026-01-24 16:04:35 +01:00
Stefan Haller 2ba3c1dc3f Move isFixupCommit to FixupHelper 2026-01-24 16:04:35 +01:00
Stefan Haller 1f7e5d7767 When doing ctrl-f, and the resulting commit is not visible, scroll it into view
This used to work in the last version, but broke with efd4298b5e (#5134).
2026-01-06 16:17:33 +01:00
Stefan Haller 65edd99fd0 Update search position (match x of y) when changing the selection in a list view
or in the staging view.
2026-01-04 18:51:45 +01:00
Stefan Haller 98bf8f86d1 Bump gocui and adapt code accordingly 2026-01-04 18:48:47 +01:00
Stefan Haller 3ccd33b388 Show an error when checking out a file would overwrite local modifications 2026-01-03 20:09:55 +01:00
Stefan Haller bb816d7001 Call gocui's SimpleEditor instead of duplicating its code 2025-12-23 16:49:16 +01:00
Stefan Haller 5423e7459c Replace go-runewidth with uniseg 2025-12-23 16:49:16 +01:00
Stefan Haller fec7e9ce61 Bump gocui (and tcell)
and adapt lazygit's client code accordingly.
2025-12-23 16:49:16 +01:00
Stefan Haller 84be082fb5 Draw only visible part of the reflogs panel
Since the reflog can get very long, this saves some memory but especially some
UI thread lag. In one of my repos I had over 11'000 reflog entries (I guess I
should prune them more regularly...), and rendering them took ~600ms; since this
happens on the UI thread, there was an annoying stall for half a second after
every background fetch, for example.
2025-12-23 16:20:32 +01:00
Stefan Haller f7d4efc59e Rerender visible lines when scrolling by page
This fixes a bug in ListContextTrait.FocusLine whereby the view would go blank
when scrolling by page (using ',' or '.') in views that have
renderOnlyVisibleLines set to true but refreshViewportOnChange set to false.
Currently we don't have any such views; the only ones who use
renderOnlyVisibleLines are commits and subcommits, and they also use
refreshViewportOnChange. However, we are going to add one in the next commit,
and eventually it might be a good idea to convert all our list views to that by
default, and get rid of the renderOnlyVisibleLines flag.
2025-12-23 16:20:32 +01:00
Stefan Haller b4b21f9c65 Fix race condition in HandleRender
Move SetContentLineCount into OverwriteLinesAndClearEverythingElse. Calling it
separately beforehand is not concurrency safe; we need both to happen
when the view's writeMutex is locked.
2025-12-23 16:20:32 +01:00
Stefan Haller 478d51c83e When pressing up or down, scroll selection into view if it is outside, even if it didn't change
We have this logic to avoid constantly rerendering the main view when hitting
up-arrow when you are already at the top of the list. That's good, but we do
want to scroll the selection into view if it is outside and you hit up or down,
no matter if it changed.
2025-12-23 15:34:38 +01:00
Stefan Haller efd4298b5e Avoid scrolling the selection into view on refresh
It is possible to scroll the selection out of view using the mouse wheel; after
doing this, it would sometimes scroll into view by itself again, for example
when a background fetch occurred. In the files panel this would even happen
every 10s with every regular files refresh.

Fix this by adding a scrollIntoView parameter to HandleFocus, which is false by
default, and is only set to true from controllers that change the selection.
2025-12-23 15:34:38 +01:00
Stefan Haller 37bc0dfc44 Extract a method for selecting the first branch (and first commit)
We want to do this whenever we switch branches; it wasn't done consistently
though. There are many different ways to switch branches, and only some of these
would reset the selection of all three panels (branches, commits, and reflog).
2025-12-23 15:08:08 +01:00
Stefan Haller a415d1b529 Fix the main view display after reverting a commit
We move the selection down by the number of commits that were reverted (to keep
the same commits selected). However, this only happens after refreshing, which
has rendered the main view with the wrong commit, so we need to render it again
after moving the selection.

There are many other callers of MoveSelection in LocalCommitsController, but all
of them happen before the refresh. Revert is special because it needs to move
the selection after refreshing, e.g. when reverting the only commit of a branch.
2025-12-23 14:55:58 +01:00
Karl Wikström 62913ee25d
feat: support custom keybinds in custom command prompt menus 2025-12-22 13:43:52 +01:00
Stefan Haller 6043eb52db Revert "Update resolve conflict using p instead of k"
Now that we can use 'k' as a menu item binding (this was fixed in #5131), use it
for the "keep" entry in the merge menu. I don't think this will be a problem for
people's muscle memory, given that this menu is not encountered every day; and
it's simply the better keybinding.

This reverts commit b32b55201e.
2025-12-22 09:49:04 +01:00
Stefan Haller 344d3866a6 Make menu keybindings take precedence over builtin ones, except for confirm/esc
This makes it possible to use 'j', 'k', 'H' or 'L' as menu item keybindings.
2025-12-22 09:37:06 +01:00
Stefan Haller f996e47199 Rename KeepConfirmKeybindings to KeepConflictingKeybindings
After the change in the previous commit this expresses better what it is about.
2025-12-22 09:37:06 +01:00
Stefan Haller 7e3b24d496 In menus, remove not just the confirm binding, but also esc and up/down
This is not really super important because we are very unlikely to assign a key
such as esc or up/down to a menu item. However, users might do this in a custom
commands menu, and in that case it is important that the builtin keys still
work; or they might remap those builtin commands to other keys, in which case
they might conflict with single-letter keys in normal menus.
2025-12-22 09:37:06 +01:00
Peter Cardenas a41bd6a255 feat: add keys for command log menu items 2025-12-14 17:12:47 +01:00
Stefan Haller f6cba966d0 Remove confirmation for opening the merge tool
The confirmation used to make sense back when the Open MergeTool command was its
own top-level command; however, that command was changed in 703f053a7e to open a
menu instead, and Open MergeTool is now just a submenu entry in that menu, so it
no longer needs a confirmation.
2025-12-07 20:55:25 +01:00
riyueguang 13b4e16e6a chore: fix function name in comment
Signed-off-by: riyueguang <rustruby@outlook.com>
2025-12-06 18:28:58 +08:00
boqishan 89a0542a97 refactor: use strings.Builder and strings.Repeat to simplify code
Signed-off-by: boqishan <boqishan@126.com>
2025-12-03 18:34:21 +01:00
Stefan Haller d274474c61 Band-aid fix for rare crashes when refreshing files
Users have filed issues with crash reports that seem to indicate that the
FileTreeViewModel gets swapped out (by a refresh) while a call to itemsSelected
is in progress, iterating over the previous items. Guard against this by locking
the mutex that we already have for this for the duration of the call.

I don't have a good way of testing whether the fix helps, because the crashes
only occurred very infrequently. Let's just see if the crash reports stop coming
in after we ship this.

Note also that this is only the minimal fix for the crashes that were reported.
Theoretically, the same problem could happen for a key handler itself, but we
never saw reports about that, so we don't bother doing anything about that yet.

Note also that long-term I envision a different solution to this class of
problems (discussed in https://github.com/jesseduffield/lazygit/issues/2974),
that's why I want to avoid locking mutexes more than necessary now.
2025-12-03 08:08:21 +01:00
Stefan Haller e2b3601c57 Fix order of fixup base commits shown in ctrl-f error message 2025-11-30 14:06:31 +01:00
Stefan Haller 9334bf0333 Don't use "HEADLESS" environment variable for running tests
It is a bit generic, it seems that users sometimes set it for other reasons, and
then they are confused why they don't see anything. Use a more specific name
instead.
2025-11-28 12:06:42 +01:00
Stefan Haller 1c1676a5d9 Use git ls-files to list all file suggestions 2025-11-21 07:57:56 +01:00
Stefan Haller 221e025ee6 Switch to branches view when checking out a commit
We move the code to push the branches context into CheckoutRef, this way it
works consistently no matter where we call it from. Previously, checking out
remote branches or tags would switch to the branches view, but checking out a
commit did not.

Note that it now also takes effect for undoing or redoing a checkout, which may
be a bit questionable; but I still think it makes sense for this, too.
2025-11-16 21:23:40 +01:00
Stefan Haller 2af56de5d2 Trigger background fetch on repo switch only if enough time has passed since the last one 2025-11-16 17:32:37 +01:00
Stefan Haller d45f27b6ee Add methods for converting RefresherConfig times to time.Durations 2025-11-16 17:32:37 +01:00
Stefan Haller 472b964da3 Cleanup: don't pass refreshInterval to startBackgroundFilesRefresh
It has access to UserConfig, so it can easily get it from there. This is how we
do it for startBackgroundFetch too, so be consistent.
2025-11-16 17:32:37 +01:00
Stefan Haller aff6b642ea Trigger immediate background fetch when switching repos 2025-11-16 17:32:37 +01:00
Stefan Haller 8f4bf49aff Cleanup: remove unused field 2025-11-16 17:32:37 +01:00
Karol Zwolak 3892c40666 feat: add fork remote command
The command allows you to quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote.
2025-11-16 17:26:00 +01:00
Stefan Haller 95e5ed6f47 Remove TrimSpace calls that are no longer needed
The prompt code takes care of this now.
2025-11-15 15:38:24 +01:00
Stefan Haller 7c126cd2fc Strip leading/trailing whitespace from prompt input
This doesn't really solve a pressing problem, because I guess it's unlikely that
users add spaces at the beginning or end of what they type into a prompt; but it
could happen, and in this case we almost always want to strip it. Just adding
this here for completeness while I was working on this code.

The only exception is the input prompt of custom commands, because who knows
what users want to use that input for in their custom command.
2025-11-15 15:38:24 +01:00
Stefan Haller d7e733cd56 Remove now unnecessary check for empty string
As of the previous commit, branchName can no longer be empty, so no need to
handle this.
2025-11-15 15:38:24 +01:00
Stefan Haller b3435bd59c Add AllowEmptyInput flag to PromptOpts
Most of our prompts don't (shouldn't) allow empty input, but most callers didn't
check, and would run into cryptic errors when the user pressed enter at an empty
prompt (e.g. when creating a new branch). Now we simply don't allow hitting
enter in this case, and show an error toast instead.

This behavior is opt-out, because there are a few cases where empty input is
supported (e.g. creating a stash).
2025-11-15 15:36:43 +01:00
Stefan Haller a7bc1012b9 Cleanup: fix incorrect comment
The comment was apparently copy/pasted from above; the branch name cannot be
blank in this case.
2025-11-15 14:46:19 +01:00
Stefan Haller 48330141bd Cleanup: put the IsPasting logic in the right place
Previously it was used both for the Confirm handler and the Cancel handler, as
well as for the Confirm handler of confirmation popups (not prompts). There was
no other way to do it given how wrappedConfirmationFunction was shared between
all these; but now there is. The logic is really only needed for the Confirm
handler of prompts.

This doesn't fix anything, it just makes things clearer.
2025-11-15 14:13:24 +01:00
Stefan Haller 8d8cf42786 Refactor: extract body of wrappedConfirmationFunction into a helper function
And call this new helper function from both wrappedConfirmationFunction and
wrappedPromptConfirmationFunction; this gives us more flexibility to do
different things in each of those.
2025-11-15 14:07:36 +01:00
phanium d88f95275f Modernize all codes
go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -fix -test ./...
2025-11-15 10:46:23 +01:00
rlkandela 374f0f0766 Add SelectedSubmodule to SessionState
Introduce the 'SelectedSubmodule' struct and allow using it as a
placeholder value.
Add a corresponding test.
Update the documentation to include it among the listed placeholder
values.
2025-11-12 08:54:34 +01:00
Raphaele Salvatore Licciardo 8114943cb1 Fix page navigation scroll behavior to keep cursor at top/bottom
When pressing '.' (next page) or ',' (previous page), the selection
now stays at the bottom or top of the viewport respectively, instead
of being centered which caused items to scroll off. If the selection is not
already on the last/first line of the view, '.'/',' moves it there without
scrolling.

This implements a special case for page navigation as suggested by
the maintainer in issue #5017, keeping the cursor position consistent
with user expectations for page-based navigation.

Fixes #5017

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-11-09 19:37:38 +01:00
Stefan Haller 2576258ba3 Cleanup: remove unnecessary code
The method is only called from keybinding handlers, so the view must be focused
already, otherwise the binding wouldn't have been dispatched to it.
2025-11-09 19:37:38 +01:00
Anton Bobov 4c4929acad Fix fixup's false filename detection in hunks containing dashed lines
The existing diff parser incorrectly treated subsequent lines beginning
with "---" as filename headers while processing hunks.  This caused
corruption when dashed lines appeared within diffs themselves.

Restrict filename detection to only occur between hunks.
2025-11-01 09:40:04 +01:00
Brandon 263ad11b3b Use a PTY when using external diff command from git config
Same logic as https://github.com/jesseduffield/lazygit/pull/3120 but for new external diff option
2025-11-01 09:33:52 +01:00
kyu08 beb05d4a61 Fix makeAtomic in branches_test
When replacing the naked return with a `return result`, the linter starts to
complain about "return copies lock value: sync/atomic.Int32 contains
sync/atomic.noCopy". I suspect this is also a problem when using a naked return,
and the linter just doesn't catch it in that case. Either way, it's better to
use a pointer to ensure that the atomic is not copied.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-10-20 07:56:56 +02:00
Stefan Haller 62854026a3 Add no-ff merge option
This will put whatever git's default merge variant is as the first menu item,
and add a second item which is the opposite (no-ff if the default is ff, and
vice versa).

If users prefer to always have the same option first no matter whether it's
applicable, they can make ff always appear first by setting git's "merge.ff"
config to "true" or "only", or by setting lazygit's "git.merging.args" config to
"--ff" or "--ff-only"; if they want no-ff to appear first, they can do that by
setting git's "merge.ff" config to "false", or by setting lazygit's
"git.merging.args" config to "--no-ff". Which of these they choose depends on
whether they want the config to also apply to other git clients including the
cli, or only to lazygit.
2025-10-19 21:24:28 +02:00
Stefan Haller c88381b9ce Rename texts to be consistent with our conventions 2025-10-19 21:24:28 +02:00
Stefan Haller 4e0194d8f7 Replace MergeOpts struct with MergeVariant enum
- Squash and FastForwardOnly are mutually exclusive, and instead of asserting
  this at runtime, model the API so that they can't be passed together.
- FastForwardOnly is unused, so remove it; however, we are going to need --ff
  and --no-ff in the next commit, so add those instead.
- Instead of putting the enum into the MergeOpts struct, replace the struct by
  the enum. We can reintroduce the struct when we add more arguments, but for
  now it's an unnecessary indirection.
2025-10-19 21:24:28 +02:00
Stefan Haller 40cc008a5a Avoid auto-stashing when only submodules are out of date
Stashing doesn't affect submodules, so if you have a working copy that has
out-of-date submodules but no other changes, and then you revert or paste a
commit (or invoke one of the many other lazygit commands that auto-stash, e.g.
undo), lazygit would previously try to stash changes (which did nothing, but
also didn't return an error), perform the operation, and then pop the stash
again. If no stashes existed before, then this would only cause a confusing
error popup ("error: refs/stash@{0} is not a valid reference"), but if there
were stashes, this would try to pop the newest one of these, which is very
undesirable and confusing.
2025-10-16 12:00:31 +02:00
Stefan Haller 80f860beb7 Add config option gui.skipSwitchWorktreeOnCheckoutWarning 2025-10-14 14:19:54 +02:00
Stefan Haller e858bad3f8 Fix lazygit getting unresponsive when pasting commits that become empty
Whenever git returns the error "The previous cherry-pick is now empty", we would
previously continue the rebase; this works for rebase because it behaves the
same as "git rebase --skip" in this case. That's not true for cherry-pick
though; if you continue a cherry-pick where the current commit is empty, it will
return the same error again, causing lazygit to be stuck in an endless loop.

Fix this by skipping instead of continuing; this shouldn't make a difference for
rebase, but works for cherry-pick.

Theoretically we could have a similar problem for revert (if you are trying to
revert a commit that has already been undone through some other means); this
should then be fixed in the same way with this change. However, the change is
not relevant for revert because git returns a different error in this case.
2025-10-14 12:27:32 +02:00
Stefan Haller 8eeb16c8da Fix window arrangement when a popup or the search prompt is open
When focusing the main view, going into full screen mode by pressing '+' twice,
and then opening the search prompt ('/') or a menu (e.g. '?' or ':'), the full
screen display would switch to the focused side panel.

Fix this by always excluding popups from the window arrangement logic. No popup
should ever have any influence on how the views beneath it are laid out.
2025-10-14 12:22:58 +02:00
Stefan Haller 3415ed975b Offer to force-delete a worktree if it contains submodules 2025-10-14 12:19:49 +02:00
Stefan Haller e44d6ec330 Replace paging config with an array of pagers 2025-10-14 12:17:13 +02:00
Stefan Haller 765c9eb85c Add PagerConfig
This is an object that is owned by Gui, is accessible through GuiCommon.State(),
and also passed down to GitCommand, where it is mostly needed. Right now it
simply wraps access to the Git.Paging config, which isn't very exciting, but
we'll extend it in the next commit to handle a slice of pagers (and maintain the
currently selected pager index), and doing this refactoring up front allows us
to make that change without having to touch clients.
2025-10-14 12:17:13 +02:00
Stefan Haller fbaea583a5 When pasting multi-line text into a prompt, don't treat the line feeds as "confirm"
This is likely to do bad things; for example, if the prompt is the shell command
prompt, then we would run into what looks like a deadlock bug in tcell. In other
cases, the characters in the following lines might be treated as random commands
after the prompt is confirmed.
2025-10-14 12:10:51 +02:00
Stefan Haller 5d02cba721 Show "Log (x of y)" in the title bar when there is more than one branch log command 2025-10-10 12:49:42 +02:00
Stefan Haller bf56a61b71 Change condition that determines whether we are showing the dashboard
Doesn't make a difference currently, since the title is either StatusTitle when
the dashboard is showing, or LogTitle when one of the branch logs is showing.
This is going to change in the next commit, though.
2025-10-10 12:49:42 +02:00
stk 43811cdad8 Export a LAZYGIT_COLUMNS variable to "pty" tasks on Windows
This makes it possible to pass it to an external diff command that is
used like a pager. An example for this can be seen in the added
documentation in the next commit.
2025-10-09 10:04:29 +02:00
Stefan Haller 7fe73c1ee2 When entering a commit in path filtering mode, select the filtered path 2025-10-09 09:58:15 +02:00
Stefan Haller 5811f2945c Extract a InternalTreePathForFilePath helper function 2025-10-09 09:58:10 +02:00
Stefan Haller 5fad3ab047 Refresh the main view for the current side panel when a popup is showing
The logic in postRefreshUpdate would only rerender the main view if the context
being updated is the current view. This is not the case when a popup is showing;
but we still want to render the main view in that case, behind the popup. This
happens for example when we refresh the Files scope, we determine that all
conflicts have been resolved and show a popup asking to continue the merge or
rebase, but the postRefreshUpdate of the Files context only happens when the
popup is already showing, so we would still see the conflict markers behind the
popup, which is rather confusing.
2025-10-09 09:02:03 +02:00
Krystof Gartner 703f053a7e Add merge options menu
Replace merge-tool with merge options menu that allows resolving all
conflicts for selected files as ours, theirs, or union, while still
providing access to the merge tool.
2025-10-09 08:45:58 +02:00
Stefan Haller 302b621b68 Fix: make isDescendentOfSelectedNodes work for the root item
The root item's path is ".", and the path of a file at top level is "./file".
When using GetPath, this gives us "." and "file", respectively, and
isDescendentOfSelectedNodes would return false for these.

Working with the internal paths (i.e. without stripping the leading "./") fixes
this.
2025-10-08 15:09:35 +02:00
Stefan Haller d0c6e27fee Cleanup: move variable assignment out of the loop
It never changes inside this function, so there's no need to recompute it with
every loop iteration.
2025-10-08 15:09:35 +02:00
Marco Zingoni b32b55201e Update resolve conflict using p instead of k 2025-10-05 10:23:03 +02:00
Stefan Haller e585accd37 Give better feedback when checking out the previous branch
Previously, the feedback you got when pressing "-" was just a "Checking out..."
status in the bottom line. This was both easy to miss if you are used to looking
for an inline status in the branches panel, and it didn't provide information
about which branch was being checked out, which can be annoying in very large
repos where checking out takes a while, and you only see at the end if you are
now on the right branch.

Improve this by trying to figure out which branch was the previously checked out
one, and then checking it out normally so that you get an inline status next to
it (as if you had pressed space on it). There are cases where this fails, e.g.
when the previously checked out ref was a detached head, in which case we fall
back to the previous behavior.
2025-10-02 18:28:17 +02:00
Stefan Haller 851afe94f5 Hide the cursor when the password prompt is showing
The cursor is a bit of a security concern, because it reveals the length (and
typing pattern) of the password.
2025-09-20 11:11:46 +02:00
Stefan Haller 26096d9dd9 Don't hide keybindings that match the confirmMenu key in the keybindings menu
In all other menus besides the keybindings menu it makes sense to hide
keybindings that match the confirmMenu binding. This is important to make it
clear which action will be triggered when you press the key.

In the keybindings menu this is different; the main purpose of that menu is not
to allow triggering commands by their key while the menu is open, but to serve
as a reference for what the keybindings are when it is not open. Because of
this, it is more important to show all bindings in this menu, even if they
conflict with the confirmMenu key.

This fixes a regression introduced in b3a3410a1a.
2025-09-07 12:43:54 +02:00
Stefan Haller b3a3410a1a Remove keybindings for menu items that are the same as the menu confirm key
This is needed when remapping the confirmMenu key to, say, "y", and there's a
menu that has an item with a "y" binding. This already worked correctly (confirm
takes precedence, as desired), but it's still confusing to see the item binding.
2025-09-05 10:42:04 +02:00
Stefan Haller b413710d8c Add separate keybindings for confirmMenu and confirmSuggestion
It seems useful to have the flexibility to remap "enter" in confirmations to
"y", but keep "enter" for menus and suggestions (even though we sometimes use
menus as confirmations, but it's still good to give users the choice).
2025-09-05 10:42:04 +02:00
Stefan Haller ea7050437d Fix <enter> keybinding for switching to a worktree
The universal.confirm keybinding is the wrong one to use for this, we want
universal.goInto instead. They are both bound to "enter" by default, but when
remapping confirm to "y" we don't want to use that for entering worktrees.
2025-09-05 10:42:04 +02:00
Stefan Haller 6303c64232 Hard-code "enter" for search prompt
Like with the previous commit, it doesn't make sense to use any other key than
enter for the search prompt.
2025-09-05 10:42:04 +02:00
Stefan Haller b006c83181 Hard-code "enter" for editable prompts
Rebinding the universal.confirm keybinding currently doesn't make sense, because
the rebound key would also be used for editable prompts, which means you would
only be able to bind it to a ctrl key (not "y", which is desirable for some
people), and also it would allow you to enter a line feed in a branch name.

Fix this by always using enter for editable prompts.
2025-09-05 10:42:04 +02:00