Skip to content

Conversation

cowboy8625
Copy link
Contributor

@cowboy8625 cowboy8625 commented Jul 19, 2025

✨ Add Ctrl+Z suspend support for LazyGit on Unix-like systems

πŸ“ Summary

This PR adds support for suspending LazyGit when the user presses Ctrl+Z, making it behave like common CLI tools (e.g., Vim, less, htop):

  • Pressing Ctrl+Z sends a SIGTSTP signal to suspend LazyGit.
  • After resuming with fg, LazyGit redraws and continues working without hanging.
  • During suspension, background routines are paused to avoid running while LazyGit is stopped.

βœ… Motivation

Make LazyGit feel more native on Unix-like systems by supporting standard terminal suspend/resume (Ctrl+Z / fg) behavior.

  • Please check if the PR fulfills these requirements
  • Cheatsheets are up-to-date (run go generate ./...)
  • Code has been formatted (see here)
  • Tests have been added/updated (see here for the integration test guide)
  • Text is internationalised (see here)
  • If a new UserConfig entry was added, make sure it can be hot-reloaded (see here)
  • Docs have been updated if necessary
  • You've read through your own file changes for silly mistakes etc

Closes #3906

@cowboy8625 cowboy8625 changed the title Allow ctrl-z to suspend lazygit Add support for suspending LazyGit with Ctrl+Z on Unix systems Jul 19, 2025
@cowboy8625
Copy link
Contributor Author

@stefanhaller I’ve addressed the earlier issues you pointed out. I’m not sure if a test is needed for this change, or if there are any other steps you’d expect before merging. Let me know! πŸ™‚

@stefanhaller
Copy link
Collaborator

@cowboy8625 The behavior is still the same for me, as described in #3906 (comment). After typing bg, I get the "+ suspended (tty output)" error, and then fg hangs.

(We had a similar problem back when we used $SHELL -i for running custom shell commands; see #3903 (comment) for some discussion. Not sure how much of a connection there is between the two cases; just mentioning it.)

And finally, when I use make run to start lazygit, then ctrl-z doesn't work at all; lazygit does suspend, but the shell prompt doesn't appear. This may be only relevant for developers, not users, but the same could happen for users if they launch lazygit through a wrapper script. I suppose in this case we'd have to send the SIGTSTP to the wrapper script (or to the make process in my case), not to lazygit; but I'm not sure what the criterion for that is. Walk the chain of parent processes until we find one whose name matches $SHELL?

Until these problems are solved I don't feel we can merge this. But I'm also not sure how much time is justified for putting into this, to be honest.

@cowboy8625
Copy link
Contributor Author

@stefanhaller I was able to better handle the issue where running bg followed by fg would cause lazygit to hang.

Here’s how it looks now on macOS (Kitty terminal):

image

And here’s how Neovim behaves:

image

I also resolved the issue that happened when running via make run.
By looking at how Neovim handles suspension, I noticed they call kill(0, SIGSTOP) in their code:
https://github.com/neovim/neovim/blob/c3a4d125296caaf15ff424e7609e731a8b4d37e7/src/nvim/tui/tui.c#L1611-L1627

Previously, I was only sending a stop signal to the lazygit process itself, which doesn’t work properly if it’s launched inside a wrapper or make run.
Using kill(0, SIGSTOP) sends the signal to the whole process group, which solves this.

Thanks a lot for your help!

@stefanhaller
Copy link
Collaborator

Nice, seems like we're getting somewhere with this. πŸ˜„

I had to make quite a few changes, and I took the opportunity to also restructure the commits to make them reviewable. I didn't want to force-push your master branch, so I pushed a new branch suspend-on-ctrl-z in this repo; you can hard-reset yours to that one after reviewing it.

A few notes about what I changed:

  • we need a keybinding config; not because users may want to use a different key (that wouldn't make much sense), but because users might want to disable it
  • we need to hook this into the normal keybinding mechanism; simply calling SetKeybinding like you did in your branch doesn't work, this gets erased again the next time you open a different repo or even just a menu
  • I decided to handle the keybinding in GlobalController; unfortunately this requires some indirections to call things on Gui, which I'm not entirely happy about, but we do similar things all over the place. Another option would have been to handle it in GetInitialKeybindings, that way we would have been able to work with Gui directly; however, I really wanted the binding to appear right below 'Quit' in the keybindings menu, and handling it in GlobalController seems like the only way to achieve that.
  • When you type ctrl-z on Windows, there's an error toast in the status bar.

During testing I still had one or two cases where lazygit didn't come back to the foreground after typing fg, and I had to kill it from another terminal tab. This makes me a little nervous, but it might also have been an intermediate state during refactoring; I can no longer reproduce it. (Shrug)

@cowboy8625
Copy link
Contributor Author

Yeah, this is a lot cleaner.

@cowboy8625
Copy link
Contributor Author

@stefanhaller Am I good to open this up for review? πŸ˜„

@stefanhaller
Copy link
Collaborator

@stefanhaller Am I good to open this up for review? πŸ˜„

I'd say so, yes. I'll hold off merging this for now until after we released 0.54.1, which I expect to happen tomorrow or the day after; that's a hotfix (needed because of #4793), and I don't want to include any other changes there.

To be honest I don't really fully understand what we're doing here when we wake up, like what this /dev/tty business is or why we need it. I was trying a little bit to read up on what vim is doing in that case, and it makes me dizzy πŸ˜….

But it seems to work well, so I'm fine with going with this for now.

@stefanhaller stefanhaller added the enhancement New feature or request label Aug 5, 2025
@cowboy8625
Copy link
Contributor Author

To my knowledge this is the most reliable way to handle it. By opening /dev/tty, we can always access the controlling terminal regardless of how Lazygit is launched. Standard input/output might be redirected depending on how the program is run, so relying on them isn’t safe. Using /dev/tty ensures we can always call TIOCSPGRP to regain foreground status after receiving a SIGCONT.

@cowboy8625 cowboy8625 marked this pull request as ready for review August 5, 2025 23:41
stefanhaller and others added 7 commits August 14, 2025 20:40
- Remove old-style build tags (the +build syntax has become obsolete with 1.17)
- Remove redundant build tags from '*_windows.go' files
This should have been removed in 8c574f8, where I renamed it back to 'gui'
to fix a linter warning.
This frees up ctrl-z for suspend. Hopefully, redo is not such a frequently used
operation that the change annoys people.

Co-authored-by: Stefan Haller <[email protected]>
These suspend/resume the gocui layer and pause/unpause background refreshes.
@stefanhaller
Copy link
Collaborator

@cowboy8625 Merging this at last. I rebased onto master for you to get rid of the "megred-from-master" commits that result in a very ugly commit history. For future PRs, please never merge master into a branch, always rebase onto master instead, thanks.

@stefanhaller stefanhaller merged commit 82daedb into jesseduffield:master Aug 14, 2025
13 checks passed
@stefanhaller
Copy link
Collaborator

@cowboy8625 It seems we still have some sort of race condition here. Today I typed ctrl-z (just to check), and when typing fg it would hang there, not coming to the front, so I had to kill it from another terminal window. Then I tried again like 20 times until I got tired, and it worked fine every time.

Would be really nice if we could make this more robust, but without a better reproduction recipe there may not be much we can do. Just letting you know.

tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Sep 9, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jesseduffield/lazygit](https://github.com/jesseduffield/lazygit) | minor | `v0.54.2` -> `v0.55.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>jesseduffield/lazygit (jesseduffield/lazygit)</summary>

### [`v0.55.0`](https://github.com/jesseduffield/lazygit/releases/tag/v0.55.0)

[Compare Source](jesseduffield/lazygit@v0.54.2...v0.55.0)

<!-- Release notes generated using configuration in .github/release.yml at v0.55.0 -->

#### What's Changed

##### Enhancements πŸ”₯

- Allow filtering the keybindings menu by keybinding by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4821](jesseduffield/lazygit#4821)
- Add support for suspending LazyGit with Ctrl+Z on Unix systems by [@&#8203;cowboy8625](https://github.com/cowboy8625) in [#&#8203;4757](jesseduffield/lazygit#4757)
- Add "CopyToClipboard" command to `ConfirmationController` by [@&#8203;kyu08](https://github.com/kyu08) in [#&#8203;4810](jesseduffield/lazygit#4810)
- Add a user config for using git's external diff command for paging by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4832](jesseduffield/lazygit#4832)
- Log the hash of dropped stashes by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4850](jesseduffield/lazygit#4850)

##### Fixes πŸ”§

- Fix right-alignment of divergence from base branch for branch checked out in a worktree by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4824](jesseduffield/lazygit#4824)
- Support Azure DevOps vs-ssh.visualstudio.com SSH remotes as hosting provider by [@&#8203;Kahitar](https://github.com/Kahitar) in [#&#8203;4822](jesseduffield/lazygit#4822)
- Improve display of "esc" keybinding in the keybindings status bar by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4819](jesseduffield/lazygit#4819)
- Use external diff command in stashes panel by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4836](jesseduffield/lazygit#4836)
- Remove the git.paging.useConfig option by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4837](jesseduffield/lazygit#4837)
- Don't auto-forward branches that are checked out in another worktree by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4833](jesseduffield/lazygit#4833)
- Fix dropping range selection of filtered stashes by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4849](jesseduffield/lazygit#4849)
- Fix rare crash in interactive rebase (merge command without comment) by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4872](jesseduffield/lazygit#4872)
- Make it possible to rebind the Confirm keybinding by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4860](jesseduffield/lazygit#4860)

##### Maintenance βš™οΈ

- Pass only Git-tracked Go files to gofumpt by [@&#8203;kyu08](https://github.com/kyu08) in [#&#8203;4809](jesseduffield/lazygit#4809)
- Update donation wording so that it's clear there's no strings attached by [@&#8203;jesseduffield](https://github.com/jesseduffield) in [#&#8203;4827](jesseduffield/lazygit#4827)
- Enhance MR/Issue templates readability by [@&#8203;kyu08](https://github.com/kyu08) in [#&#8203;4829](jesseduffield/lazygit#4829)
- Run label check workflow only on label events and open pr event by [@&#8203;kyu08](https://github.com/kyu08) in [#&#8203;4830](jesseduffield/lazygit#4830)

##### Docs πŸ“–

- Add installation with gah by [@&#8203;marverix](https://github.com/marverix) in [#&#8203;4820](jesseduffield/lazygit#4820)
- docs(VISION): fix "Dicoverability" typo by [@&#8203;Rudxain](https://github.com/Rudxain) in [#&#8203;4866](jesseduffield/lazygit#4866)
- Add dev container feature as installation method to README by [@&#8203;HenningLorenzen-ext-bayer](https://github.com/HenningLorenzen-ext-bayer) in [#&#8203;4876](jesseduffield/lazygit#4876)

##### I18n 🌎

- Update translations from Crowdin by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4873](jesseduffield/lazygit#4873)

#### New Contributors

- [@&#8203;marverix](https://github.com/marverix) made their first contribution in [#&#8203;4820](jesseduffield/lazygit#4820)
- [@&#8203;Kahitar](https://github.com/Kahitar) made their first contribution in [#&#8203;4822](jesseduffield/lazygit#4822)
- [@&#8203;cowboy8625](https://github.com/cowboy8625) made their first contribution in [#&#8203;4757](jesseduffield/lazygit#4757)
- [@&#8203;Rudxain](https://github.com/Rudxain) made their first contribution in [#&#8203;4866](jesseduffield/lazygit#4866)
- [@&#8203;HenningLorenzen-ext-bayer](https://github.com/HenningLorenzen-ext-bayer) made their first contribution in [#&#8203;4876](jesseduffield/lazygit#4876)

**Full Changelog**: <jesseduffield/lazygit@v0.54.2...v0.55.0>

</details>

---

### Configuration

πŸ“… **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

β™» **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

πŸ”• **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS45Ny40IiwidXBkYXRlZEluVmVyIjoiNDEuOTcuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90Il19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Allow ctrl-z to suspend lazygit
2 participants