Commit Graph
8578 Commits
Author SHA1 Message Date
Stefan HallerandClaude Opus 5.5 42f7bf66a6 Remove the unused View.Line and View.Word
Nothing in lazygit calls View.Line or View.Word; they came with the
original gocui code and have never been used. They are also the only
callers of realPosition. That function ignores the inclusion gutter when
it maps a view position to the buffer. On top of that, Word uses the
returned column as an index into the line's cells, and this goes wrong
for wide characters.

Remove them, together with realPosition and the indexFunc helper. This
leaves contentX as the only code that maps a column of the view to a
column of the content.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:40:23 +02:00
Stefan HallerandClaude Opus 5.5 fda5abdfb3 Account for the inclusion gutter when mapping view columns to content
Search matches beside the inclusion gutter are highlighted in the wrong
place, and hovering over or clicking a hyperlink there acts on the wrong
cell. The draw loop starts the content after the gutter, but the code
that maps a view column back to a column of the content only adds the
horizontal origin and ignores the gutter.

Add a helper that does both, and use it for search highlights, hover
and clicks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:40:23 +02:00
Stefan HallerandClaude Opus 5.5 07995e630b Add tests for search matches and hyperlinks beside the inclusion gutter
If the inclusion gutter is shown, as it is in the focused main view
while building a custom patch, search matches are highlighted two
columns to the left of where they are. Hovering over a hyperlink
underlines the wrong columns, and a click acts on the cell two columns
to the right of the one that was clicked.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:40:23 +02:00
Stefan Haller 8771f1fc5d Fix double-width characters in the last column drawing over the view's frame (#6106)
A double-width character that starts in the last column of a view is
drawn over the view's frame. One that is scrolled halfway out to the
left is skipped, and the column it reaches into isn't drawn at all. The
draw loop clips cells against the edges of the view as if every cell
were one column wide.

A terminal can't show half of a double-width character. So draw a blank
in the column that is on screen, in the character's colors, and let the
selection highlight cover that column like any other.
2026-10-10 13:40:20 +02:00
Stefan HallerandClaude Opus 5.5 e314ad884a Draw a blank for a double-width character cut off at the edge of a view
A double-width character that starts in the last column of a view is
drawn over the view's frame. One that is scrolled halfway out to the
left is skipped, and the column it reaches into isn't drawn at all. The
draw loop clips cells against the edges of the view as if every cell
were one column wide.

A terminal can't show half of a double-width character. So draw a blank
in the column that is on screen, in the character's colors, and let the
selection highlight cover that column like any other.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:35:25 +02:00
Stefan HallerandClaude Opus 5.5 bab90de2fd Add tests for double-width characters at the edges of a view
If a double-width character starts in the last column of a view that
doesn't wrap, its second half is drawn over the view's frame. If one is
scrolled halfway out to the left, the column it still reaches into isn't
drawn at all. This column then has neither the selection highlight nor
the line's background. Both happen in every view that doesn't wrap: the
side panels, and the main view with gui.wrapLinesInDiffView set to
false.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:35:25 +02:00
Stefan Haller 7fc43006c0 Show errors for unresolvable diff lines (#6100)
If lazygit can't work out what diff lines it is supposed to act on when
staging a hunk (for example because of bugs like the one in #6099, or
maybe because of a misbehaving OSC-1717 diff renderer), it used to do
nothing and silently ignore the error. Now it shows an error popup to
the user in such a case, so that they can report the bug more easily.
2026-10-10 13:07:29 +02:00
Stefan HallerandClaude Opus 5.5 2a3883f502 Show an error if the commit to discard lines from can't be found
Discarding lines from a commit's diff rewrites the commit with a
rebase. For this we look up the commit among the commits of the
current branch. If it isn't there, pressing the key does nothing. The
disabled reason only allows discarding in a diff of the commits panel,
so this shouldn't happen. If it does, the user should see it.

Show an error that names the commit instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:04:41 +02:00
Stefan HallerandClaude Opus 5.5 56e234e94c Show an error if the selected diff lines can't be found in the diff
When we stage, discard or edit lines of the working tree's diff, we
read the file's diff again and look for the selected lines in it. If
none of them are there, nothing happens. The same goes for adding
lines of a commit's diff to a custom patch, or taking lines out of
it. This can happen if the file changed since its diff was shown, or
if we read the line numbers out of the diff on screen wrongly.

Show an error that names the file instead. As with a file that can't
be found, don't act on any file if the lines of one are missing.

Unlike the file check, this one happens after a new custom patch has
been started. If the error leaves that patch without any lines, reset
it, as we do when the last line is taken out of a patch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:04:41 +02:00
Stefan HallerandClaude Opus 5.5 15ac3c28c9 Show an error if the file of the selected diff lines can't be found
If the path in the diff header of a selected line doesn't name a file
of the files panel, staging or discarding the line does nothing. The
command log shows "Apply patch", but nothing else happens, so the user
has nothing to go on when reporting it. Adding a line of a commit's
diff to a custom patch fails in the same silent way if the path
doesn't name a file of that diff. A diff renderer that names files
differently can cause this, and so can a bug in how we read the path,
like the one we had with diff.mnemonicPrefix.

Show an error that names the path instead, and do nothing else. If a
selection spans several files, don't act on the files we did find,
because that would do only part of what the user asked for. Do the
same for opening a line of a diff in its pull request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:04:41 +02:00
Stefan HallerandClaude Opus 5.5 a29c70f1f3 Build the patches of all selected files before applying any of them
When staging, discarding or taking lines out of a custom patch spans
several files, we read the diff of a file and apply its patch before
we look at the next file. The next commits report an error if the
selected lines of a file can't be found. Without this change, the
files before it would already be acted on by then.

So find the lines in every file first, and only then apply anything.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:04:41 +02:00
Stefan HallerandClaude Opus 5.5 e5eb4086c5 Don't ignore a git error when listing the files of a commit's diff
If git fails to list the files of the diff that a custom patch is
built from, we carry on as if the diff had no files. Then we don't
know which files were renamed, added or deleted, and build the patch
from the wrong lines or without the file's creation or deletion.

Return the error instead, so that the user sees it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:04:41 +02:00
Stefan HallerandClaude Opus 5.5 6be2bd9031 Resolve the files of the selected diff lines before acting on them
The focused diff actions look up the file of a selected line in
different places. In the files panel, staging and discarding do it
per file while applying the patches, and editing a hunk does it for
the first line. For a commit's diff, adding lines to a custom patch or
discarding them from the commit does it only after the confirmation
prompt and after a new patch has been started. The next commits
report a line whose file can't be found as an error. For that, the
lookup has to happen once, before we log an action, ask for
confirmation or start a patch.

So let diffLineSelection group the selected lines by their file, in
the order the diff shows the files, and do the same for a commit's
diff in selectionByFile. A line whose file can't be found is still
skipped, as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 13:04:41 +02:00
Stefan Haller 47e7a133a5 Fix staging diff hunks when the diff.mnemonic git config is true (#6099)
This fixes a regression in v0.66.0: when the `diff.mnemonicPrefix` git
config is true, staging or unstaging a diff hunk didn't work. It printed
"Apply patch" in the commands log, but would otherwise do nothing.

Fixes #6096.
2026-10-10 13:03:31 +02:00
Stefan HallerandClaude Opus 5.5 243ede84d7 Fix staging lines in the main view when diff.mnemonicPrefix is set
If diff.mnemonicPrefix is set, pressing space on a line in the focused
main view does nothing. The command log shows "Apply patch", but the
line is not staged. Other commands that read a path out of the working
tree's diff go wrong as well, such as jumping to a file. Building a
custom patch from a commit's diff still works, because git uses
mnemonic prefixes only when one side of the diff is the index or the
working tree.

With this config, the paths in the working tree's diff start with i/
and w/ instead of a/ and b/. We only strip a/ and b/, so the path we
read out of the diff header is not a file of the files panel, and we
skip the line without a message.

Users who set the config want to see these prefixes, so keep them.
Strip the first component of the path instead, whatever it is. `git
apply` does the same by default, and ParseableDiffPrefixes makes sure
there is such a component.

A section without +++ and --- lines needs more than that, because we
find the new path on its "diff --git" line by searching for " b/".
Take the path from the "rename to" or "copy to" line instead where
there is one. Otherwise the two paths on the "diff --git" line are the
same. Split the line where the two halves match after stripping their
prefixes, like `git apply` does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:57:25 +02:00
Stefan HallerandClaude Opus 5.5 d7ffcd02b4 Extract the decoding of a path in a diff header into unquoteDiffPath
The "rename to" and "copy to" lines of a diff header spell a path the
same way as the other header lines, but without a prefix. The next
commit reads paths from these lines too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:57:00 +02:00
Stefan HallerandClaude Opus 5.5 c0d4be6b66 Add a test for staging a line with diff.mnemonicPrefix set
If diff.mnemonicPrefix is set, pressing space on a line in the focused
main view does nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:53:25 +02:00
Stefan HallerandClaude Opus 5.5 e67a85a075 Fix diff paths when diff.srcPrefix and diff.dstPrefix are set
If diff.srcPrefix and diff.dstPrefix are set (git 2.45 and later),
pressing space on a line in the focused main view does nothing. These
configs change the diff of a commit too, so the other commands that
read a path out of a diff go wrong as well.

The configs replace the a/ and b/ in front of the paths of a diff with
any string. We only know how to strip a/ and b/, so the paths we read
out of the diff header don't name a file.

We can't tell from the diff where such a prefix ends, so override the
two configs along with diff.noprefix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:53:03 +02:00
Stefan HallerandClaude Opus 5.5 c0d230777a Add a test for staging a line with diff.srcPrefix and diff.dstPrefix set
If diff.srcPrefix and diff.dstPrefix are set, pressing space on a line
in the focused main view does nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:52:02 +02:00
Stefan HallerandClaude Opus 5.5 5cf1fbf168 Fix diff paths under a top-level a/ or b/ when diff.noprefix is set
If diff.noprefix is set, pressing space on a line of a file under a
top-level directory named a or b does nothing. In a stash entry's diff,
the menu for jumping to a file shows such a file without its directory.

We read the path of a file out of the diff header and strip the a/ or
b/ in front of it. Most diff commands override diff.noprefix so that
this prefix is there, but the diff of the working tree and the diff of
a stash entry don't. With the config set, we strip the file's own
directory instead.

Use ParseableDiffPrefixes for these two commands too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:51:16 +02:00
Stefan HallerandClaude Opus 5.5 8d3cfac12d Add a test for jumping to a file of a stash with diff.noprefix set
If diff.noprefix is set, the menu for jumping to a file of a stash
entry's diff leaves out the directory of a file under a top-level
directory named a or b.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:50:30 +02:00
Stefan HallerandClaude Opus 5.5 621d7993b2 Add a test for staging a line with diff.noprefix set
If diff.noprefix is set, pressing space on a line of a file under a
top-level directory named a or b does nothing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:49:41 +02:00
Stefan HallerandClaude Opus 5.5 1243d3d5a8 Extract the diff.noprefix override into a GitCommandBuilder method
Seven diff commands pass -c diff.noprefix=false so that the paths in
their output carry a prefix. The next commits use the override for
more commands and add more config to it, so give it a single home
first.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 13:48:57 +02:00
Stefan Haller c5f7158154 Update docs and schema for release (#6093) v0.66.0 2026-10-05 18:54:24 +02:00
Stefan Haller dec5c91de6 Update docs and schema for release 2026-10-05 18:49:54 +02:00
Stefan Haller 5b8f1c98e3 Update translations from Crowdin (#6092) 2026-10-05 18:47:03 +02:00
Stefan Haller 2cff798d10 Update translations from Crowdin 2026-10-05 18:44:25 +02:00
Stefan Haller a214743a1e Change the demo gifs to videos and update them for the newly added features (#6073)
The demo videos were rendered as animated gifs, which is less than
ideal, because they start playing automatically, they loop but users
can't tell when they start over from the beginning, and they can't be
paused or rewound.

Convert them to videos instead so that users see a playhead and the
usual player controls. Leave a bit of room at the bottom for those
controls; different browsers render those differently, so it's a bit of
a best effort solution.

Also take this opportunity to update some of the demos for the changed
functionality of #6035, #6039 and others.
2026-10-05 18:42:30 +02:00
Stefan HallerandClaude Opus 5.5 40a1027b89 Draw the commit graph in the demos with the detailed style
The 'auto' style doesn't recognize the terminal that vhs records, so it
would draw the classic graph. Now that the recordings have a font for
the branch drawing symbols, use the detailed graph in all demos.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 18:29:35 +02:00
Stefan HallerandClaude Opus 5.5 ff8d74971f Add a font with the branch drawing symbols for the demo recordings
The terminal that vhs records is xterm.js running in a browser, and the
xterm.js that ttyd bundles doesn't draw the branch drawing symbols
itself. So they have to come from a font, and SauceCodePro NF doesn't
have them, nor does any font that macOS comes with. The detailed commit
graph shows up as garbage.

Newer versions of xterm.js do draw the symbols, but only in the WebGL
renderer. vhs forces the canvas renderer, and xterm.js has removed that
since, so moving to a newer xterm.js would mean patching both ttyd and
vhs.

The Flog Symbols font has the symbols, but it draws its lines for a
smaller cell than the one xterm.js uses at our font settings. As a
fallback font, it leaves a gap at the edges of every cell, and each line
of the graph comes out dashed.

Add a copy of the font that fits the cells of the recordings. xterm.js
clips each character to its row, so the vertical strokes reach well
past the top and bottom of the cell and end exactly at the row's edges.
It doesn't clip a character to its cell horizontally, so the horizontal
strokes reach only a little way into the neighbouring cells. Any
further, and they would show past the start of a bend in the next cell.

Give the font a bold face with the same outlines. lazygit draws the
lines of the selected commit in bold, and without a bold face the
browser makes the symbols bold itself by thickening them. That leaves
gaps where they meet.

Add the script that made the font, and list the font after
SauceCodePro NF in demo/settings.tape so that the browser takes the
symbols from it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 18:29:35 +02:00
Stefan HallerandClaude Opus 5 5a56c609c2 Add a script for re-recording every demo a page embeds
Changing demo/settings.tape, or anything about how lazygit looks, dates
every recording at once, and re-recording them one at a time means
running the recorder fifteen times and pasting fifteen new URLs.
Attachment URLs say nothing about where they came from, so there is also
nothing to tell you which demo a video in the README is of.

Name the demo in a comment above each video. GitHub drops the comment
when it renders the page, so it costs the reader nothing, and it gives
us a way back from a page to the demo that produced it. Then walk those
comments, re-record each demo and rewrite the URL below it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 18:29:35 +02:00
Stefan Haller 9c86ef15b2 Change the demos in the README to videos 2026-10-05 18:29:35 +02:00
Stefan HallerandClaude Opus 5 d24bb08384 Record demos with vhs and publish them to GitHub's attachment store
The demo gifs in the README start on their own, loop without telling the
reader where a run begins, and give no way to pause, seek or replay. A
video with the browser's own controls fixes all three, but GitHub plays
a video in a README only when it is served from its own attachment
store. If you commit one to the assets branch and link it the way we
link the images, GitHub drops the whole <video> element when it renders
the page.

Replace terminalizer with vhs. vhs records the demo straight to mp4
rather than going through a gif, and it takes the terminal size, font
and colours from demo/settings.tape. Then upload the result from the
endpoint that GitHub's own drag-and-drop upload posts to, and print the
tag to paste into the page.

Uploading alone is not enough. An attachment is readable only by people
who are signed in to GitHub until a posted comment in the repository
refers to it, and a README on a branch does not count. So post each
recording to a collecting issue and wait until the video can be fetched
without a token. Miss that step and the video plays for whoever recorded
it and 404s for every other reader.

Two things about the frame. Pad the bottom, because the browser draws
its playback controls over the video and they are tall enough to cover
the line where the demos put their captions. And cut the end: vhs
records until the marker reaches the screen, by which time lazygit has
exited and the shell has painted its prompt back over the demo. A
browser holds the last frame of a video once it has played to the end,
so leaving those frames in would end every demo on a terminal prompt
and leave it there.

Pass --no-upload while you are still working on how a demo looks. That
writes the video to demo/output and stops, so trying out a colour or a
font costs nothing but the recording itself.

The recording is sharper and smaller than the gif it replaces, at
1866x1230 and 25 fps against 1140x828 and about 5 fps. It also costs the
reader nothing until they press play, whereas the gifs are fetched every
time the README is opened.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 18:04:50 +02:00
Stefan HallerandClaude Opus 5 0026f853c7 Give the tests a visible inactive border colour
The integration test config asks for `black` inactive borders. In a
recording that comes out as a mid grey, because the recording theme
remaps the terminal's black to #7a7a7a. In an ordinary terminal it is
real black, so when you watch a test with `just e2e-cli` the inactive
frames all but disappear against the background.

Name the grey directly instead (but a little bit brighter than the #7a
we had before), so that the frames look the same either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 18:04:50 +02:00
Stefan HallerandClaude Opus 5 212142ff74 Confirm the nuke prompt in the nuke_working_tree demo
The recording of this demo stops with the confirmation popup still on
screen. The explosion animation and the emptied Files panel that the demo
exists to show never happen at all. Nuking the working tree has asked for
confirmation since 238fdd573c, and the demo only selects the menu item, so
the nuke never runs. The demo asserted nothing about the outcome, so it
stayed green all the while.

Answer the confirmation, and assert that the Files panel ends up empty.
That assertion also holds the recording open until the animation has
played out and the panel has been refreshed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 18:04:50 +02:00
Stefan Haller 115e3fb389 Update demos and readme for new staging-in-main-view functionality
We don't bother rendering the updated or new demos yet, because we are
about to change them to a different format.
2026-10-05 17:13:31 +02:00
Stefan HallerandClaude Opus 5 f3e8d88019 Run demos at the size they are recorded at
How a demo behaves depends on how much of a list fits on the screen, and
`just e2e` ran demos on a 150x100 screen while we record them at 120x35.
So a demo could pass the test suite and still fail partway through a
recording, and there was no way to find out short of recording it.

Give a demo the recording size when it doesn't ask for a size of its
own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 17:13:31 +02:00
Stefan HallerandClaude Opus 5 a2dd3327eb Check the line we start on when navigating to a list item
NavigateToLine looks for its target among the lines the view has
rendered. When a list is scrolled, the view holds only the part of it
that is on screen, so the target may not be among them. For that case
the helper jumps to the top of the list and walks down instead.

That walk presses a key before it looks, so it never sees the item it
starts on, which after jumping is the first item of the list. A target
sitting there is reported as missing. The shorter the terminal, the more
of a list is scrolled out of view and the more often that walk is
needed, so this surfaced once the demos began running in a 35 line
terminal.

Check the line the walk starts on before moving off it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-05 17:13:31 +02:00
Stefan Haller dd0029dd35 Use accordion mode in diff_commits demo
When running the demos at a hight of 35, as the new recording mechanism
will, this demo failed because the commits list was too small to show
both commits at the same time, and NavigateToLine has a bug that
prevents it from finding it by going to the top and pressing down until
it matches. We are going to fix that bug in the next commit for similar
future situations, but we also solve the problem here by setting the
side panels to accordion mode so that more commits are visible; this
looks better for this demo anyway.
2026-10-05 17:13:31 +02:00
Stefan Haller 257b3757d3 Fix the rule below a pull request's header being rendered with the wrong width (#6091)
When selecting a branch that has a pull request associated with it, the
main view shows a PR header and a rule below it that separates the
header from the branch log. That rule didn't always have the correct
width: one scenario where it didn't was when you look at a diff with
both staged and unstaged changes whose view is split side-by-side (e.g.
because you use `gui.mainPanelSplitMode: horizontal`), and then jumping
to the branches panel with a PR branch selected. In that case, the rule
was still rendered to the shorter width of the split view.
2026-10-05 17:12:27 +02:00
Stefan HallerandClaude Opus 5.5 576017c00b Lay out the rule below a pull request's header to the main view's width
The rule was as wide as the main view was when the render was asked
for. If the layout then changed the width of the main view, the rule
came out too long or too short.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 16:38:13 +02:00
Stefan HallerandClaude Opus 5.5 5235083bd2 Let the prefix of a render be laid out to the width it renders at
The rule below the header of a branch's pull request is drawn as wide as
the main view. Its width is read when the render is asked for, before
the layout has settled it. If the layout then changes the width of the
main view, for example because it unsplits the view for the content
that comes next, the rule comes out as wide as the view used to be.

Make a prefix a function that is called with the width the render is
laid out to, once the layout has run. The function it returns produces
the text on the render's own goroutine before the command starts. This
way a prefix that takes a while to produce, such as one that runs
commands of its own, holds up only the render and not the UI.

All prefixes are still static here; the next commit lays out the rule
of the pull request header to the width.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 16:38:13 +02:00
Stefan HallerandClaude Opus 5.5 2b57733541 Hoist the check for a stopped task to the top of NewCmdTask
The next commit checks whether the task was stopped once more before
the command starts. Use the same check there as in the loop that reads
the command's output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 16:38:13 +02:00
Stefan Haller a5497bacf4 Keep copied commits when switching to another worktree or repo (#6090)
If you copy commits with shift-C and then switch to another worktree,
the copied commits are gone, so you can't paste them there with shift-V.
This is a common workflow: copy commits from the branch that is checked
out in one worktree, maybe drop them there right away, then switch to
the worktree of another branch and paste them.

Create the cherry-picking mode once and share it between all repo
states. This also lets you paste in a different clone of the same repo,
as long as that clone has the copied commits (for example because you
fetched them there). If the current repo doesn't have them, git
cherry-pick fails with "bad object", and we show this as an error.
2026-10-05 16:26:20 +02:00
Stefan HallerandClaude Opus 5.5 1f05a6b152 Keep copied commits when switching to another worktree or repo
If you copy commits with shift-C and then switch to another worktree,
the copied commits are gone, so you can't paste them there with
shift-V. This is a common workflow: copy commits from the branch that
is checked out in one worktree, maybe drop them there right away, then
switch to the worktree of another branch and paste them.

The copied commits live in the cherry-picking mode, and each repo state
creates its own. We keep a separate repo state per worktree, so every
worktree starts with an empty clipboard.

Create the cherry-picking mode once and share it between all repo
states. This also lets you paste in a different clone of the same repo,
as long as that clone has the copied commits (for example because you
fetched them there). If the current repo doesn't have them, git
cherry-pick fails with "bad object", and we show this as an error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 16:05:40 +02:00
Stefan Haller fc85e9353c Fix wrong main view content in rare edge case situations (#6089)
See the individual commit messages for what exactly is fixed here.
2026-10-05 15:54:12 +02:00
Stefan HallerandClaude Opus 5.5 205fad57fa Keep an emptied pane empty
Emptying a pane of the main view clears it right away, but leaves the
view's tasks alone. If a diff was asked for before, and its task is
only created after the layout, that task fills the pane again. A diff's
task that is still reading can also write into the emptied view.

Give the pane an empty render as well. The render takes its place among
the view's tasks, so the earlier diff's task isn't created, and it stops
a task that is still reading. Once that task has stopped, the render
empties the view again.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:13 +02:00
Stefan HallerandClaude Opus 5.5 f8fa6ee1a4 Add a test for emptying a pane before its diff's task is created
If the lower pane of the main view is asked to show a diff and is then
emptied before the next layout, it ends up holding the diff. Selecting
a file with staged changes and moving back to one without any in rapid
succession does this. The pane is hidden then, but when it is shown
again, it shows the other file's diff until its next render replaces
it.

Emptying a pane clears the view right away, but leaves the view's tasks
alone. So the diff's task, which is only created after the layout,
fills the pane again. A task that is still reading a diff into the pane
isn't stopped either, so it can write into the emptied view.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:13 +02:00
Stefan HallerandClaude Opus 5.5 edf30f6455 Tie the loading state of a view to the task asked for last
The loading state is set when a command task is asked for, and cleared
when a command task reaches the end of its input, whichever task that
is. A task that is stopped before then doesn't clear it, so a message
that replaces the task leaves the view loading. And a task that was
asked for earlier clears the state that a later one has set.

Count the view as loading only while the task asked for last is a
command task that is still reading its input. When a task reaches the
end of its input, let it end the loading only if it is that task.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:13 +02:00
Stefan HallerandClaude Opus 5.5 17d1db2e6c Add tests for when a view counts as loading
If a message replaces a command's output while the command is still
being read, the view keeps counting as loading until another command
has been read to the end. Until then, the layout doesn't clamp the
view's scroll position to its content, and IsSingleHunkForWholeFile
returns false.

If an earlier command reaches the end of its input after a later one
has been asked for, the view stops counting as loading, although the
later command hasn't started yet. The layout can then clamp the scroll
position to the earlier command's output before the later command has
been read.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 14:55:13 +02:00