Commit graph

204 commits

Author SHA1 Message Date
Stefan Haller 5748d82073 Convert keybinding fields to Keybinding
Until now every keybinding config field was a plain string. That meant a user
couldn't ask for two keys to invoke a command — the config silently accepted
only one form.

Convert every string-typed field across all 13 KeybindingXxxConfig structs to
Keybinding so the union type extends to every command. Defaults wrap their
single-key value in Keybinding{...} so the generated Config.md still renders one
scalar key per binding.

The alt fields keep their separate Binding registrations for now: this commit
does not yet introduce the merge mechanism that folds them into the main field —
that comes in a follow-up. Consumers previously calling opts.GetKeys on a string
field now call opts.GetKeys on the Keybinding, or take .String() / Keys[0] where
a single value is needed.

Adds a Keybinding.String helper for rendering, schema-generator work that
inlines the Keybinding union into each consuming property, and a unit test
covering the user-facing scalar/sequence YAML forms for quit.
2026-05-25 15:32:47 +02:00
Stefan Haller 26366641c0 Rename Key to Keys in Binding, KeybindingsOpts, and MenuItem
This is a straight rename with no other code changes. Doing it in a separate
commit to keep the diff of the previous one somewhat readable.
2026-05-25 15:18:18 +02:00
Stefan Haller 3d18ee8f91 Use a slice of keys for each binding
This is a pure refactor in preparation for letting users configure multiple
alternate bindings for a single command. Every Binding still has exactly one
key, so nothing changes visibly: the cheatsheet, the on-screen options bar,
and the keybindings menu all render identically.

When a Binding ends up with multiple keys, the on-screen options bar will
show only the first (to avoid clutter); the cheatsheet will show all of them (in
a later commit). For now both paths take Key[0].

MenuItem.Key is changed in the same way, it also has a slice of keys now.

In this commit we keep the name `Key` in Binding, KeybindingOpts and MenuItem,
instead of renaming them to `Keys` right away, in order to keep the diff a bit
more readable. We'll do the rename separately in the next commit.
2026-05-25 15:18:18 +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
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 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 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 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 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 28affa3399 Add an optional onCancel hook for menus 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 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 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
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 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 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 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 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 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
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
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 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 5a630aeda1 Refactor: add a separate Prompt view
So far, confirmations and prompts were handled by the same view, context, and
controller, with a bunch of conditional code based on whether the view is
editable. This was more or less ok so far, since it does save a little bit of
code duplication; however, now we need separate views, because we don't have
dynamic keybindings, but we want to map "confirm" to different keys in
confirmations (the "universal.confirm" user config) and prompts (hard-coded to
enter, because it doesn't make sense to customize it there).

It also allows us to get rid of the conditional code, which is a nice benefit;
and the code duplication is actually not *that* bad.
2025-09-05 10:42:03 +02:00
cowboy8625 0f38d2d61e Implement suspending the app using ctrl-z
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-08-14 20:41:46 +02:00
Stefan Haller a08799ac15 Allow filterable contexts to customize the filter label 2025-08-14 15:02:48 +02:00
Stefan Haller 2ed11336b5 Allow filtering for keybindings by prepending filter string with '@' 2025-08-14 15:02:48 +02:00
Stefan Haller 75afa099e9 Add support for dynamic binding descriptions 2025-08-01 10:35:16 +02:00
Stefan Haller 9eb1e3a729 Add a GetShortDescription() method to Binding 2025-08-01 10:35:16 +02:00
Stefan Haller d0438b7d33 Cleanup: better receiver name
No need for this to be uppercase.
2025-08-01 10:35:16 +02:00
Stefan Haller 4b9921d0a4 Move the Ref interface from gui/types to models
This is a type that can be useful for model/backend stuff, so move it there. We
are going to use it in the API of the commit loader.
2025-07-31 15:21:34 +02:00
Stefan Haller 934276ac40 Use the non-filtered reflog for undoing
Using the filtered one is probably not a good idea. It didn't do much harm
because the split of ReflogCommits and FilteredReflogCommits doesn't really work
right now (FilteredReflogCommits is always the same as ReflogCommits, even in
filtering mode), but we'll fix this in the next commit.
2025-07-27 12:05:41 +02:00
Stefan Haller 4dfa4e8aa2 Allow more than one controller to attach OnFocus/OnFocusLost functions
Trying to do this would previously have the second one silently overwrite the
first one's.

We don't currently have this in lazygit, but I ran into the situation once
during development, and it can lead to bugs that are hard to diagnose.

Instead of holding a list of functions, we could also have added a panic in case
the function was set already; this would have been good enough for the current
state, and enough to catch mistakes early in the future. However, I decided to
allow multiple controllers to attach these functions, because I can't see a
reason not to.
2025-07-11 10:51:44 +02:00
Stefan Haller b3ca944c9e Remove GetOnRenderToMain, GetOnFocus, and GetOnFocusLost from BaseContext
These are never called on the context, they only exist to satisfy the
HasKeybindings interface. But that's wrong, HasKeybindings has nothing to do
with focus handling or rendering the main view. Remove them from that interface
and add them to IController instead.
2025-07-11 10:47:07 +02:00
Stefan Haller 20600b9b5c Add convenience function ConfirmIf
It's a very common pattern in the code base to have some code that we want to
run either directly, or with a confirmation, depending on some condition. In
most cases this is solved by creating a local helper function that we call
either directly or from within the HandleConfirm of the confirmation; provide a
convenience helper that makes this easier.
2025-07-08 16:41:22 +02:00
Stefan Haller 29fc46dc62 Cleanup: remove unused method Focus from PatchExplorerContext 2025-07-07 13:37:41 +02:00
Stefan Haller ef16867ff1 Cleanup: add NewRunPtyTaskWithPrefix funxtion instead of setting Prefix manually
This is consistent with NewRunCommandTaskWithPrefix.
2025-07-02 16:15:19 +02:00
Stefan Haller d82852a909 Change Refresh to not return an error
Refresh is one of those functions that shouldn't require error handling (similar
to triggering a redraw of the UI, see
https://github.com/jesseduffield/lazygit/issues/3887).

As far as I see, the only reason why Refresh can currently return an error is
that the Then function returns one. The actual refresh errors, e.g. from the git
calls that are made to fetch data, are already logged and swallowed. Most of the
Then functions do only UI stuff such as selecting a list item, and always return
nil; there's only one that can return an error (updating the rebase todo file in
LocalCommitsController.startInteractiveRebaseWithEdit); it's not a critical
error if this fails, it is only used for setting rebase todo items to "edit"
when you start an interactive rebase by pressing 'e' on a range selection of
commits. We simply log this error instead of returning it.
2025-07-02 16:09:42 +02:00
Stefan Haller 5ee5d42511 Instantiate Mutexes's fields by value
Instead, pass the entire Mutexes struct by pointer to controllers.

This is better because Mutexes now has a zero value and doesn't need to be
initialized.
2025-06-19 18:39:46 +02:00
Stefan Haller 3e26be9845 Optionally pass disabled commands on to next handler
If a DisabledReason has its AllowFurtherDispatching flag set, it is returned as
a ErrKeybindingNotHandled error, instead of shown as a toast right away. This
allows gocui to continue to dispatch the keybinding, and we can unwrap the error
at the other end (in our global ErrorHandler) and display it then.

This allows having keybindings for the same key at the local and global levels,
and they will continue to be dispatched even if the first one returns a
DisabledReason. It is opt-in, so we only use it for cases where we know that a
local and a global handler share the same (default) keybinding.
2025-06-05 13:20:38 +02:00
Stefan Haller bbd17abc43 Add ContextMgr.NextInStack and use it to access side panel of focused main view
This way we don't have to abuse the parent context mechanism, which isn't meant
for this purpose.
2025-05-22 14:38:40 +02:00
Stefan Haller 636b94cf22 Make '>' first jump to the beginning of the branch, and only then to the first commit 2025-05-11 13:55:43 +02:00
Stefan Haller a400ef0079 Remove ICmdObj interface
It is only implemented by *CmdObj, so use that directly in client code.
2025-05-01 15:21:37 +02:00
Stefan Haller e27bc15bbd Store Commit.Hash by pointer (kept in a pool of hashes)
This in itself is not an improvement, because hashes are unique (they are shared
between real commits and rebase todos, but there are so few of those that it
doesn't matter). However, it becomes an improvement once we also store parent
hashes in the same pool; but the real motivation for this change is to also
reuse the hash pointers in Pipe objects later in the branch. This will be a big
win because in a merge-heavy git repo there are many more Pipe instances than
commits.
2025-04-29 14:57:15 +02:00
Stefan Haller fbb8b2e17e Allow clicking in focused main view to go to staging
Only works if a file is selected.
2025-04-21 18:03:24 +02:00
Stefan Haller 7b96615792 Add IGuiCommon.GetViewBufferManagerForView
So that we don't have to pass the map to controllers.
2025-04-21 18:03:18 +02:00
Stefan Haller e90a3dc666 Fix view selection running out of sync with list selection when view isn't focused
When rerendering a view at the end of a refresh, we call HandleFocus only if the
view has the focus. This is so that we rerender the main view for the new
selection.

What was missing here is to update the view selection from the list selection if
the view doesn't have the focus, so that the selection is painted properly.
Normally this is not relevant because you don't see the selection if another
side panel has the focus; however, you do see it as an inactive selection when
e.g. a popup is shown, in which case it does matter.

This will become more important when we introduce section headers for commits,
because in that case the view selection needs to change when the working copy
state changes from normal to rebasing or vice versa, even if the list selection
stays the same.

The changed test submodule/reset.go shows how this was wrong before: when
entering the submodule again after resetting, there is a refresh which keeps the
same branch selected as before (master); however, since the branches panel is
not focused, the view didn't notice and kept thinking that the detached head is
selected (which it isn't, you can tell by running the test in sandbox mode and
focusing the branches panel at the end: you'll see that master is selected). So
the change in this commit fixes that.
2025-04-20 16:06:15 +02:00
Stefan Haller 8af8f7754b Move types/enums/enums.go to working_tree_state.go
It looks like enums.go was supposed to be file that collects a bunch of enums,
but actually there's only one in there, and since it has methods, it deserves to
be in a file of its own, named after the type.
2025-04-20 15:53:17 +02:00
Stefan Haller cd36e95a82 Rename RebaseMode to WorkingTreeState
We're about to add more possible values (reverting and cherry-picking), so
working tree state seems like a more suitable name.
2025-04-20 15:53:17 +02:00