This change reduces the number of calls during application startup to
one, calling GetRepoPaths() earlier than previously and plumbing the
repoPaths struct around to achieve this end.
The current behaviour when creating a new branch off of a remote branch
is to always track the branch it was created from.
For example, if a branch 'my_branch' is created off of the remote branch
'fix_crash_13', then 'my_branch' will be tracking the remote
'fix_crash_13' branch.
It is common practice to have both the local and remote branches named
the same when the local is tracking the remote one. Therefore, it is
reasonable to expect that 'my_branch' should not track the remote
'fix_crash_13' branch.
The new behaviour when creating a new branch off of a remote branch is
to track the branch it was created from only if the branch names match.
If the branch names DO NOT match then the newly created branch will not
track the remote branch it was created from.
For example, if a user creates a new branch 'fix_crash_13' off of the
remote branch 'fix_crash_13', then the local 'fix_crash_13' branch will
track the remote 'fix_crash_13' branch.
However, if the user creates a new branch called 'other_branch_name' off
of the remote branch 'fix_crash_13', then the local 'other_branch_name'
branch will NOT track the remote 'fix_crash_13' branch.
The default shortcut to open git difftool (ctrl+t) is not available on
the "Local Branches" window. It is available when selecting a commit
from a local branch, a remote branch, or a tag from the "Local Branches"
window.
This is inconsistent since branches or tags are also commits, the
shortcut should also work on them directly.
This commit remedies this inconsistency by allowing the use of the
shortcut directly on a branch or a tag. The shortcut works both in the
"standard" mode and the "diffing" mode.
This reverts commit 3af545daf7, reversing
changes made to 629b7ba1b8.
We changed our mind about this and want to provide different options for
achieving the same thing, but with more flexibility.
The highlight is normally turned off in HandleFocusLost, but that's not called
when using ReplaceContext (and changing this would be a lot of work, it seems),
so turn it off manually here for now.
Probably not the most import feature in the world, but when resizing the
terminal window while multiple popup panels were open at the same time, we would
only resize the topmost one.
The main reason for changing this is because it makes the next commit easier to
implement.
This is how we do it for confirmation with suggestions too, so be consistent. It
will make things easier later in this branch if we only have one context per
"panel" on the stack, even if the panel consists of two views.
Concretely this means:
- only push the message context onto the stack when opening the panel (this
requires making the description view visible manually; we do the same for
suggestions)
- when switching between message and description, use ReplaceContext rather than
PushContext
We forgot to handle the "suggestions" and "commitDescription" view names.
Instead of listing all the names of views that can appear in popups though,
let's use the context kind for this, which feels more robust.
This is a change in behavior: previously, clicking outside of the search or
filter prompt would close the prompt, now it no longer does (because search has
a persistent popup kind, but it wasn't listed in the list of view names before).
An inactive selection is one where the view is part of the context stack, but
not the active view. For example, the files view when you enter the staging
panel, or any view when you open a panel.
Remove the old mechanism of clearing the highlight in Layout.
This fixes a problem with a wrong highlight showing up in the staging panel when
entering a file with only staged changes.
Reproduction recipe:
1. stage all changes in a file by pressing space on it in the files panel
2. enter the staged changes panel by pressing enter
3. unstage one of the changes
This makes the unstaged changes panel visible, but keeps the focus in the staged
changes panel. However, the highlight in the unstaged changes view becomes
visible, as if it were focused.
To explain why this happens, you need to know how the selection highlighting of
a view is turned on or off. It is turned on when it gains the focus, i.e. when
ActivateFocus is called on it, which in turn happens when PushContext is called.
It is turned off in Layout when gocui sees that the current view is no longer
the same as last time, in which case it calls onViewFocusLost on the previous
current view.
This mechanism only works reliably when there is at most one PushContext call
per event handler. If there is more than one, then the first one gets its
highlight turned on, then the second one, but since gocui has never seen the
first one as the active view in Layout, it doesn't get the highlight turned off
again even though it should.
And this happens in the above scenario. When pressing enter on a file with only
staged changes, we first push the staging context (in
FilesController.EnterFile), and then later we push the stagingSecondary context
when we realize we only have staged changes. This leaves the highlight of the
staging context on.
runewidth.StringWidth is an expensive call, even if the input string is pure
ASCII. Improve this by providing a wrapper that short-circuits the call to len
if the input is ASCII.
Benchmark results show that for non-ASCII strings it makes no noticable
difference, but for ASCII strings it provides a more than 200x speedup.
BenchmarkStringWidthAsciiOriginal-10 718135 1637 ns/op
BenchmarkStringWidthAsciiOptimized-10 159197538 7.545 ns/op
BenchmarkStringWidthNonAsciiOriginal-10 486290 2391 ns/op
BenchmarkStringWidthNonAsciiOptimized-10 502286 2383 ns/op
In d5b4f7bb3e and 58a83b0862 we introduced a combined mechanism for rerendering
views when either their width changes (needed for the branches view which
truncates long branch names), or the screen mode (needed for those views that
display more information in half or full screen mode, e.g. the commits view).
This was a bad idea, because it unnecessarily rerenders too many views when just
their width changes, which causes a noticable lag. This is a problem, for
example, when selecting a file in the files panel that has only unstaged
changes, and then going to one that has both staged and unstaged changes; this
splits the main view, causing the side panels to become a bit narrower, and
rerendering all those views took almost 500ms on my machine. Another similar
example is entering or leaving staging mode.
Fix this by being more specific about which views need rerendering under what
conditions; this improves the time it takes to rerender in the above scenarios
from 450-500s down to about 20ms.
This reintroduces the code that was removed in 58a83b0862, but in a slightly
different way.
The rendering of remote branches is in no way dependent on the width of the view
(or the screen mode). Unlike in the local branches view, we don't truncate long
branch names here (because there's no more information after them).
This is an error introduced in d5b4f7bb3e.
For checkboxes it probably doesn't really make sense to use them yet, because
we'd have to find a way how you can toggle them without closing the dialog; but
we already provide rendering for them to lay the ground.
But radio buttons can be used already, because for those it is ok to close the
dialog when choosing a different option (as long as there is only one grounp of
radio buttons in the panel, that is).
Strike it through if not applicable. This will hopefully help with confusion
about the meaning of "all" in the "Discard all changes" entry; some people
misunderstand this to mean all changes in the working copy. Seeing the "Discard
unstaged changes" item next to it hopefully makes it clearer that "all" is meant
in contrast to that.
Several custom patch commands on parts of an added file would fail with the
confusing error message "error: new file XXX depends on old contents". These
were dropping the custom patch from the original commit, moving the patch to a
new commit, moving it to a later commit, or moving it to the index.
We fix this by converting the patch header from an added file to a diff against
an empty file. We do this not just for the purpose of applying the patch, but
also for rendering it and copying it to the clip board. I'm not sure it matters
much in these cases, but it does feel more correct for a filtered patch to be
presented this way.
We're going to add another argument in the next commit, and that's getting a bit
much, especially when most of the arguments are bool and you only see true and
false at the call sites without knowing what they mean.
This is important when using a pager that draws a horizontal line across the
entire width of the view; when changing from a file or directory that has only
unstaged (or only staged) changes to one that has both, the main view is split
in half, but the PTY task would be run on the view in its old state, so the
horizonal line would be too long and wrap around.
All PTYs were created with the size of the main view, on the assumption that
main and secondary always have the same size. That's not true though; in
horizontal split mode, the width of the two views can differ by one because of
rounding, and when using a pager that draws a horizontal line across the width
of the view, this is visible and looks very ugly.
When switching to a repo that was open before, the context tree is reused, so
before adding keybinding functions to those contexts again, we need to clear the
old ones.
When refreshViewportOnChange is true, we would refresh the viewport once at the
end of FocusLine, and then we would check at the end of AfterLayout if the
origin has changed, and refresh again if so. That's unnecessarily complicated,
let's just unconditionally refresh at the end of AfterLayout only.
We want to add an additional method to ISearchableContext later in this branch,
and this will make sure that we don't forget to implement it in any concrete
context.
Searching in the "Divergence from upstream" view would select the wrong lines.
The OnSearchSelect function gets passed a view index, and uses it to select a
model line. In most views these are the same, but not in the divergence view
(because of the Remote/Local section headers).
ListContextTrait.OnSearchSelect was introduced in 138be04e65, but it was never
called. I can only guess that a planned refactoring wasn't finished here.
Unfortunately it isn't possible to delete them. This would often be useful, but
our todo rewriting mechanisms rely on being able to find todos by some
identifier (hash for pick, ref for update-ref), and exec todos don't have a
unique identifier.
If exactly one candidate from inside the current branch is found, we return that
one even if there are also hunks belonging to master commits; we disregard those
in this case.
Copy the slice into a variable and use that throughout the whole operation; this
makes us a little more robust against the model refreshing concurrently.
Put it into the individual menu items instead.
Again, this is necessary because we are going to add another entry to the menu
that is independent of the selected branch.
Instead, disable the individual entries in the menu.
This is necessary because we are going to add another entry to the menu that is
independent of the selected branch.
Previously the entire status was colored in a single color, so the API made
sense. This is going to change in the next commit, so now we must include the
color in the string returned from BranchStatus(), which means that callers who
need to do hit detection or measure the length need to decolorize it.
While we're at it, switch the order of ↑3↓7 to ↓7↑3. For some reason that I
can't really explain I find it more logical this way. The software out there is
pretty undecided about it, it seems: VS Code puts ↓7 first, and so does the
shell prompt that comes with git; git status and git branch -v put "ahead" first
though. Shrug.
Use Equals instead of Contains for asserting the status view content. This
solves the problem that we might assert Contains("↓2 repo"), but what it really
shows is "↑1↓2 repo", and the test still succeeds. At best this is confusing.
Also, this way we don't have to use the awkward DoesNotContain to check that it
really doesn't show a checkmark.
To do this, we need to fix two whitespace problems:
- there was always a space at the end for no reason. Simply remove it. It was
added in efb51eee96, but from looking at that diff it seems it was added
accidentally.
- there was a space at the beginning if the branch status was empty. This is
actually a cosmetic problem, for branches without a status the text was
indented by once space. Change this so that the space is added conditionally.
It's a bit awkward that we have to use Decolorise here, but this will go away
again later in this branch.
We aren't using them, yet, except for deciding whether to show the warning about
hunks with only added lines.
Add a bit of test coverage for parseDiff while we're at it.
This broke with 81b497d186 (#3387). In that PR I claimed that we never want to
ask for force-pushing if the server rejected the update, on the assumption that
this can only happen because the remote tracking branch is not up to date, and
users should just fetch in this case. However, I didn't realize it's even
possible to have a branch whose upstream branch is not stored locally; in this
case we can't tell ahead of time whether a force push is going to be necessary,
so we _have_ to rely on the server response to find out. But we only want to do
that in this specific case, so this is not quite an exact revert of 81b497d186.
When branches are sorted by recency we have this logic that first loads the
branches so that they can be rendered quickly; in parallel, it starts loading
the reflog in the background, and when that's done, it loads the branches again
so that they get their recency values. This means that branches are loaded twice
at startup.
We don't need this logic when branches are not sorted by recency, so we can
simply load branches and reflog in parallel like everything else.
This shouldn't change any user observable behavior, it just avoids doing
unnecessary work at startup.
For tooltips that are just one or two characters longer than the available
width, the last word would be cut off. On my screen this happened for the
tooltip for the fixup command.
It can optionally be used to set the title of the panel that shows the output of
a command (when showOutput is true). If left unset, the command string is used
as the title.
To determine whether we need to ask for force pushing, we need to query the push
branch rather than the upstream branch, in case they are not the same.
In a triangular workflow the branch that you're pulling from is not the same as
the one that you are pushing to. For example, some people find it useful to set
the upstream branch to origin/master so that pulling effectively rebases onto
master, and set the push.default git config to "current" so that "feature"
pushes to origin/feature.
Another example is a fork-based workflow where "feature" has upstream set to
upstream/main, and the repo has remote.pushDefault set to "origin", so pushing
on "feature" pushes to origin/feature.
This commit adds new fields to models.Branch that store the ahead/behind
information against the push branch; for the "normal" workflow where you pull
and push from/to the upstream branch, AheadForPush/BehindForPush will be the
same as AheadForPull/BehindForPull.
This guards against accidentally renaming a model field and thereby breaking
user's custom commands. With this change we'll get a build failure when we do
that.
In go 1.22, loop variables are redeclared with each iteration of the
loop, rather than simple updated on each iteration. This means that we
no longer need to manually redeclare variables when they're closed over
by a function.
For custom commands it is useful to select an earlier command and have it copied
to the prompt for further editing. This can be done by hitting 'e' now.
For other types of suggestion panels we don't enable this behavior, as you can't
create arbitrary new items there that don't already exist as a suggestion.
In the custom commands panel you can now tab to the suggestions and hit 'd' to
delete items from there. Useful if you mistyped a command and don't want it to
appear in your history any more.
Deadlock reporting broke in e1ceb6892a; since then, it was *off* when running
debug builds normally, but *on* when debugging an integration test. Both of
which are exactly opposite of what we want.
Change `func displayCommit()` so all the individual strings are built first,
then the whole thing `cols` is put together. Before, most strings were built
prior to constructing `cols`, but a few were built inside the `cols`
construction.
Sometimes it takes a while to get PRs accepted upstream, and this blocks our
progress. Since I'm pretty much the only one making changes there anyway, it
makes sense to point to my fork directly.
It is now only used as the error handler that is passed to gocui.Gui on
construction; it's not a client-facing API any more. Also, it doesn't have to
handle gocui.ErrQuit, as gocui takes care of that.
This lets us get rid of a few more calls to Error(), and it simplifies things
for clients of OnWorker: they can simply return an error from their callback
like we do everywhere else.
In Gui.onWorker we only make the minimum possible change to get things to
compile after the API-breaking change of the gocui update; we'll make this
cleaner later in this branch.
We upgraded our minimum Go version to 1.21 in commit
57ac9c2189. We can now replace our
`utils.Min` and `utils.Max` functions with the built-in `min` and `max`.
Reference: https://go.dev/ref/spec#Min_and_max
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
Make it recognize URLs wrapped in angle brackets, and followed by punktuation.
We don't need this for the status panel, but we will need it for confirmation
panels.
Changing globals in the init() function of a test file is a bad idea, as it
affects all other tests that run after it. Do it explicitly in each test
function that needs it, and take care of restoring the previous value
afterwards.
When exiting filtering mode, we currently keep the selection index the same in
the commits panel. This doesn't make sense at all, since the index in the
filtered view has no relation to the index in the unfiltered view.
I often use filtering mode (either by path or by author) to find a given commit
faster than I would otherwise be able to. When exiting filtering mode, it's
useful to keep the same commit selected, so that I can look at the surrounding
commits, see which branch it was a part of, etc. So reselect the commit again
after exiting filtering mode.
Sometimes this is not possible, most likely when the commit is so long ago that
it's outside of the initial 300 range. In that case, at least select the commit
again that was selected before I entered filtering; this is still better than
arbitrarily keeping the same selection index.
It would crash when some keybindings are set to null, and the filter string is
such that only those keybindings remain visible.
The reason for the crash is that when inserting non-model items (menu section
headers in this case) you specify a column to align them to. This works on the
assumption that the number of columns is always the same. It can cope with the
case that columns are removed because they are empty for all items; but it can't
cope with the case that the getDisplayStrings function returns a lower number of
columns.
And this is what happened here: MenuViewModel.GetDisplayStrings would omit the
keybinding column when none of the entries have a keybinding. This logic is
unnecessary, the generic list rendering mechanism takes care of this, so
removing this logic fixes the crash.
We do have to make sure though that the column is really empty when there's no
keybinding, so change the logic to use FgCyan only when there's a keybinding.
I often copy hashes in the commits panel in order to paste them into Github
comments (or other places), and I can't stand it when they have the full length.
I picked a default of 12 for this; I find this to be a good middle ground
between being reliable in large repos (12 still works in the linux kernel repo
today, but it might not be enough in really huge repos) and not being too ugly
(many smaller repos can probably get away with less).
We deliberately don't change this for the "Copy to clipboard" menu, since this
gives users a way to copy the unabbreviated sha if they need this occasionally.
To support this, we turn the confirmation prompt of the "Create fixup commit"
command into a menu; creating a fixup commit is the first entry, so that
"shift-F, enter" behaves the same as before. But there are additional entries
for creating "amend!" commits, either with or without file changes. These make
it easy to reword commit messages of existing commits.
We pass all of them to a single editor command, hoping that the editor will be
able to handle multiple files (VS Code and vim do).
We ignore directories that happen to be in the selection range; this makes it
easier to edit multiple files in different folders in tree view. We show an
error if only directories are selected, though.
We've seen a lot of issues recently where people complain that lazygit doesn't
behave as documented, but that was only because they were running the latest
release but were looking at the documentation of master. Make the documentation
links in the status panel point to the release that they are using in the hope
that this will help a little bit with this problem.
Some operations don't support switching to the editor from the commit message
panel; an example is the commit message panel that appears when moving a custom
patch into a new commit. Disable the "open in editor" menu entry in this case,
instead of silently doing nothing.
Previously we would hide it if no onSwitchToEditor function was set; that was
from a time when <c-o> was bound directly to the switch-to-editor command. Now
it is bound to showing a menu, and that menu is always available even if no
onSwitchToEditor function is set. (We rather need to disable the switch to
editor item _within_ that menu, see next commit.)
For die-hard fuzzy-searching fans it's probably in the way, so taking it out
makes fuzzy filtering work better. For substring filtering it always retains the
sort order anyway.
By default we now search for substrings; you can search for multiple substrings
by separating them with spaces. Add a config option gui.filterMode that can be
set to 'fuzzy' to switch back to the previous behavior.
Lazygit has two ways to decide whether it needs to ask the user to force-push:
1. if it knows ahead of time that the push will fail because the branch has
diverged, by looking at the incoming/outgoing information that it shows as ↑3↓7.
2. by examining the error that comes back when the push has failed.
The second situation should happen only rarely, because lazygit fetches every
minute by default, so the ↑3↓7 information is usually up to date. It might not
be if the user turned off auto-fetch (or increased the auto-fetch interval).
However, in this case it's almost always harmful to prompt the user to
force-push, because we know that the reason for diverging is that something was
pushed to the remote, and we would wipe it out by force-pushing. In such a
situation, the more likely user action is to pull the remote changes and then
push normally again.
So just remove the second prompt, and replace it by a better error message when
we detect that updates were rejected remotely.
A little bit of history archeology reveals that the second prompt was added at a
time where we didn't have the first one yet, so at that time it made sense to
have it; but when the first prompt was added, we should have removed the second.
So far, the only situation where we called SetSelectionRangeAndMode was one
where the range could only get larger (in startInteractiveRebaseWithEdit, in
which case update-ref todos can be inserted by the rebase). However, in the last
commit we introduced a new call site where the range can get smaller, including
being reduced to a single item. Since this is indistinguishable from a single
selection, set the mode to none in this case; without this, hitting escape would
seemingly do nothing because it collapses the empty range selection.
... and when recalling a commit message from an old commit by pressing up-arrow.
This is necessary because committing turns our soft line breaks into real ones,
but when rewording we want to turn them back into soft ones again, so that it's
possible to insert words at the beginning of a paragraph and have everything
rewrap nicely.
This is only a best effort; the algorithm only removes those hard line breaks
that can be removed without changing the way the message looks. This works well
when the previous commit message was wrapped at the same width, which for most
users should be the most common case; but if it wasn't, the result is not great.
Specifically, if the old wrap width was smaller, some hard line breaks just
won't be removed; if it was wider though, you'll get an unpleasant comb effect
with alternating long and short lines. In such a case it's best to switch to the
editor and use whatever wrapping features you have there (e.g. alt-Q).
This should arguably have been done in b133318b40 already; it's becoming more
important now because we're going to extend the common code with more logic in
the next commit.
It starts a rebase on the bottom-most commit of the range, and sets all the
selected commits to "edit" (skipping merge commits, because they can't be
edited).
The additional branch head icon is more confusing than useful in this situation.
The update-ref entries show very clearly where the branch heads will go when
continuing the rebase; the information where the branch heads used to be before
the rebase is not really needed here, and just makes the display more confusing.
I'm not adding more tests here because the changes to the existing tests
demonstrate the change clearly enough.
A common workflow for me is to create a fixup commit from only some of my
current changes; to do that, I enter a file, stage a few hunks, and then want to
invoke ctrl-f to find the base commit for these changes. Currently I need to esc
back to the files panel in order to do that; it's more convenient to be able to
do this right from the staging panel.
This commit introduces a new feature to the commit view, allowing users
to filter commits based on the author's name or email address. Similar
to the existing path filtering functionality, accessible through <c-s>,
this feature allows users to filter the commit history by the currently
selected commit's author if the commit view is focused, or by typing in
the author's name or email address.
This feature adds an entry to the filtering menu, to provide users with
a familiar and intuitive experience
Calling "git reset" on the command line (without further arguments) defaults to
--mixed, which is reason enough to make it the default for us, too.
But I also find myself using --mixed more often than --soft. The main use case
for me is that I made a bunch of WIP commits, and want to turn them into real
commits when I'm done hacking. I select the last commit before the WIP commits
and reset to it, leaving all changes of all those commits in the working
directory. Since I want to start staging things from there, I prefer those
modifications to be unstaged at that point, which is what --mixed does.
For some lists it is useful to keep the same sort order when filtering (rather
than sorting by best match like we usually do). Add an optional function to
FilteredList to make this possible, and use it whenever we show lists of things
sorted by date (branches, stashes, reflog entries), as well as menu items
because this allows us to keep the section headers in the keybindings menu,
which is useful for understanding what you are looking at when filtering.
After #3283 we need to read more lines initially so that the scrollbar goes to
its minimal height of 1 for long diffs. Without this, it would start with a
height of 2 and then become smaller after you scroll down half the window
height.
This fixes two problems:
- each time the custom commands panel was opened, the history of commands would
be shown in reversed order compared to last time. (The reason is that
lo.Reverse modifies the slice in place rather than just returning a new,
reversed slice.)
- when executing a previous command again (either by typing it in again, or by
picking it from the history), it should move to the beginning of the history,
but didn't.
We fix this by storing the history in reversed order (as the user sees it in
the panel), this makes the logic simpler. We just have to prepend rather
than append newly added commands now.
While this is theoretically a breaking change, it's not worth bothering because
the order was wrong for existing users in 50% of the cases anyway.
Scenario:
- show the files of a commit, escape out of it again
- start an interactive rebase of a stack of branches, with the rebase.updateRefs
git config set to true
- select one of the update-ref todos
- trigger a refresh (either manually or by bringing lazygit's terminal window to
the front)
This results in an error message "fatal: ambiguous argument '': unknown revision
or path not in the working tree."
Fix this by putting another band-aid on the check for the commit files refresh.
This is the easiest way to fix the problem, but I don't think it's the best one.
We shouldn't be refreshing the commit files context at all if it isn't visible,
because it's pointless; there's no way to switch to it again except by calling
viewFiles again with a specific ref. But I'm too lazy too figure out how to do
that right now.
After discarding file changes from the commit, the was still referencing
these indexes as being part of the range select. The consequence was
needing to hit escape twice to exit commit files in some situations.
Canceling the range select after discarding changes fixes that.
The waiting status shouldn't happen until after the user has responded
to the popup.
Since we're not giving a standalone prompt about clearing the patch, all
of the business in `discard` doesn't need to be in a function any more
It can be tedious after each cherry-pick opearation to clear the
selection by pressing escape in order for lazygit to stop displaying
info about copied commits. Also, it seems to be a rare case to
cherry-pick commits to more than one destination.
The simplest solution to address this issue is to clear the selection
upon paste.
The only exception is a merge conflict. Initially, I wanted to clear
selected commits in this scenario too. During a discussion we found out
that it may be convenient to have the copied commits still around.
Aborting the rebase and pasting the commits in the middle of a branch
can be a valid use case.
To do that, change the "Apply fixup commits" command to show a menu with the two
choices "in current branch" and "above the selected commit"; we make "in current
branch" the default, as it's the more useful one most of the time, even though
it is a breaking change for those who are used to "shift-S enter" meaning
"squash above selected".
Before this commit, we had pkg/integration/tests/submodule/add.go
failing with a panic. I'm pretty sure the issue is this: we're now
calling quite a few GetDisabledReason calls on each layout() call,
and if a background thread happens to update a model slice while
we're doing this, we can end up with a selection index that's now
out of bounds because it hasn't been clamped to match the new list
length.
Specifically, here we had the selected index being -1 (the list starts
empty and somehow the value is -1 in this case) and then the list
gets a new submodule so the length is now 1, but the list cursor
doesn't know about this so remains on the old value. Then we confirm
the length is greater than zero and try to get the selected submodule
and get an out of bounds error.
This commit fixes the issue by clamping the selected index whenever
we get the length of the list so that it stays in-sync. This is not
a perfect solution because the length can change at any time, but
it seems to reliably fix the test, and using mutexes didn't seem to
make a difference.
Note that we're swapping the order of IFileTree and IListCursor in
the file tree view model to ensure that the list cursor's Len()
method is called (which performs the clamping).
Also, comment from the PR:
This 'trait' pattern we're using is convenient but can lead to awkward
situations. In this case we have both the list view model and the
(embedded) list cursor with a Len() method. The list cursor Len()
method just calls the list view model Len() method. But I wanted
to make it that the list view model now calls ClampSelection() on the
list cursor whenever it obtains the length. This will cause an
infinite loop because ClampSelection() internally calls Len()
(which calls the list view model's Len() method which in turn
calls ClampSelection() again, etc).
The only reason we were passing the list view model into the list
cursor was to supply the length method, so now we're just doing
that directly, and letting the list view model delegate the Len()
call to the list cursor, which now itself calls ClampSelection.
As part of making lazygit more discoverable, there are certain keys which you almost certainly
need to press when you're in a given mode e.g. 'v' to paste commits when cherry-picking. This
commit prominently shows these keybinding suggestions alongside the others in the option view.
I'm using the same colours for these keybindings as is associated with the mode elsewhere e.g.
yellow for rebasing and cyan for cherry-picking. The cherry-picking one is a bit weird because
we also use cyan text to show loaders and app status at the bottom left so it may be confusing,
but I haven't personally found it awkward from having tested it out myself.
Previously we would render these options whenever a new context was activated, but now that we
need to re-render options whenever a mode changes, I'm instead rendering them on each screen
re-render (i.e. in the layout function). Given how cheap it is to render this text, I think
it's fine performance-wise.
This adds a bunch of tooltips to keybindings and updates some keybinding descriptions (i.e. labels).
It's in preparation for displaying more keybindings on-screen (in the bottom right of the screen),
and so due in part to laziness it shortens some descriptions so that we don't need to manage both
a short and long description (for on-screen vs in-menu). Nonetheless I've added a ShortDescription
field for when we do want to have both a short and long description.
You'll notice that some keybindings I deemed unworthy of the options view have longer descriptions,
because I could get away with it.
This is useful if you want to move a range of commits, so you select them, and
then realize it's better to do it in an interactive rebase. Pressing 'i'
preserves the range now.
This helps work around bugs in editors that may get confused about relative
paths (like nvim-remote, see https://github.com/neovim/neovim/issues/18519), and
shouldn't have any negative effect on others.
As part of this, you must now press enter on a merge conflict file
to focus the merge view; you can no longer press space and if you do
it will raise an error.
I prefer this because I almost never use sticky range selections. Also, this
fixes the issue that just clicking a line in a diff (without dragging) already
creates a range selection. It still does, technically, but it's no longer a
problem because a non-sticky one-line range selection behaves the same as a
non-range selection.
Something dumb that we're currently doing is expecting list items
to define an ID method which returns a string. We use that when copying
items to clipboard with ctrl+o and when getting a ref name for diffing.
This commit gets us a little deeper into that hole by explicitly requiring
list items to implement that method so that we can easily use the new
helper functions in list_controller_trait.go.
In future we need to just remove the whole ID thing entirely but I'm too
lazy to do that right now.
Fixes https://github.com/jesseduffield/lazygit/issues/3077
Show unstaged file names in default colour
Previously, we had the following rules:
* file names were in red when unstaged or partially staged
* directory names were in red if unstaged, yellow if partially staged,
and
green if fully staged
Red text on a black background can be hard to read, so instead I'm
changing it
so that unstaged files have their names in the default text colour.
I'm also making it so that partially staged files are in yellow, just
like how
partially staged directories are yellow (same deal with the commit files
view
when adding to a custom patch).
So the new rules are:
* unstaged files/directories use the default colour
* partially staged files/directories are in yellow
* fully staged files/directories are in green
I've also done a refactor on the code clean up some dead code from when
the file tree
outline was drawn with box characters, and I've made it so that the
indentation in
each line is handled inside the function that draws the line rather than
in the recursive
parent function. This makes it easier to experiment with things like
showing the file
status characters on the left edge of the view (admittedly after
experimenting with it,
I decided I didn't like it). Apologies for having a refactor and a
functional change
in the one commit but by the time I was done, I couldn't be bothered
going back and
retroactively splitting it into two halves.
Previously, we had the following rules:
* file names were in red when unstaged or partially staged
* directory names were in red if unstaged, yellow if partially staged, and
green if fully staged
Red text on a black background can be hard to read, so instead I'm changing it
so that unstaged files have their names in the default text colour.
I'm also making it so that partially staged files are in yellow, just like how
partially staged directories are yellow (same deal with the commit files view
when adding to a custom patch).
So the new rules are:
* unstaged files/directories use the default colour
* partially staged files/directories are in yellow
* fully staged files/directories are in green
I've also done a refactor on the code clean up some dead code from when the file tree
outline was drawn with box characters, and I've made it so that the indentation in
each line is handled inside the function that draws the line rather than in the recursive
parent function. This makes it easier to experiment with things like showing the file
status characters on the left edge of the view (admittedly after experimenting with it,
I decided I didn't like it). Apologies for having a refactor and a functional change
in the one commit but by the time I was done, I couldn't be bothered going back and
retroactively splitting it into two halves.
This wasn't necessary before, because the only available branch sorting option
was by recency, so the sort order couldn't change except by checking out
branches. Now, you can sort by committer date, so the branch order can change by
fetching; in this case it's important to keep the same branch selected. One
important use case is to rebase the checked-out branch onto master; you select
master, press "f" to fetch it (this can now change its position in the list),
and then press "r" to rebase. To make this work smoothly it's important to keep
master selected after pressing "f".
Often if a test fails and there's an unaknowledged toast message, that message will
explain why the test failed. Given that we don't display toast messages in
integration tests when they run (for reasons I can't recall right now), we need to
log it as part of the error message.
We don't need it there so no need to enable it.
I'm leaving the disabled reason checks there, even though they're now redundant,
because they're only one-liners and they communicate intent.
We want to show an error when the user tries to invoke an action that expects only
a single item to be selected.
We're using the GetDisabledReason field to enforce this (as well as DisabledReason
on menu items).
I've created a ListControllerTrait to store some shared convenience functions for this.
This requires us to change the 'v' keybinding for paste to something else,
now that 'v' is used globally for toggling range select. So I'm using
'shift+v' and I'm likewise changing 'c' to 'shift+c' for copying, so
that they're consistent.
We will need to clearly communicate this change in keybindings.
The only time we should call SetSelectedLineIdx is when we are happy for a
select range to be retained which means things like moving the selected line
index to top top/bottom or up/down a page as the user navigates.
But in every other case we should now call SetSelection because that will
set the selected index and cancel the range which is almost always what we
want.
This is the highest priority of the escape actions because it's the thing you're
most likely to want to do upon hitting escape if you have a range selected.
Applying this to the staging/patch-building views is tricky: if we want this logic
for when a range of lines is selected, we'll also need to apply it when a hunk
is selected too. I still think it's worth it though: I've often accidentally
escaped from the staging view when trying to cancel a range selection.
We're not fully standardising here: different contexts can store their range state however
they like. What we are standardising on is that now the view is always responsible for
highlighting the selected lines, meaning the context/controller needs to tell the view
where the range start is.
Two convenient benefits from this change:
1) we no longer need bespoke code in integration tests for asserting on selected lines because
we can just ask the view
2) line selection in staging/patch-building/merge-conflicts views now look the same as in
list views i.e. the highlight applies to the whole line (including trailing space)
I also noticed a bug with merge conflicts not rendering the selection on focus though I suspect
it wasn't a bug with any real consequences when the view wasn't displaying the selection.
I'm going to scrap the selectedRangeBgColor config and just let it use the single line
background color. Hopefully nobody cares, but there's really no need for an extra config.
This adds range select ability in two ways:
1) Sticky: like what we already have with the staging view i.e. press v then use arrow keys
2) Non-sticky: where you just use shift+up/down to expand the range
The state machine works like this:
(no range, press 'v') -> sticky range
(no range, press arrow) -> no range
(no range, press shift+arrow) -> nonsticky range
(sticky range, press 'v') -> no range
(sticky range, press arrow) -> sticky range
(sticky range, press shift+arrow) -> nonsticky range
(nonsticky range, press 'v') -> no range
(nonsticky range, press arrow) -> no range
(nonsticky range, press shift+arrow) -> nonsticky range
Previously we included all navigation keybindings from all views in the keybindings menu, meaning
if you pressed enter on 'next page' in the commits view, you'd end up triggering the action
in the sub-commits view.