1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-08-24 19:39:16 +02:00

Implement suspending the app using ctrl-z

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
This commit is contained in:
cowboy8625
2025-07-19 07:17:30 -05:00
committed by Stefan Haller
parent af190ad280
commit 0f38d2d61e
12 changed files with 142 additions and 1 deletions

2
go.mod
View File

@@ -37,6 +37,7 @@ require (
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/sync v0.16.0 golang.org/x/sync v0.16.0
golang.org/x/sys v0.34.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0 gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@@ -77,7 +78,6 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect golang.org/x/text v0.27.0 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect gopkg.in/fsnotify.v1 v1.4.7 // indirect

View File

@@ -390,6 +390,7 @@ type KeybindingConfig struct {
type KeybindingUniversalConfig struct { type KeybindingUniversalConfig struct {
Quit string `yaml:"quit"` Quit string `yaml:"quit"`
QuitAlt1 string `yaml:"quit-alt1"` QuitAlt1 string `yaml:"quit-alt1"`
SuspendApp string `yaml:"suspendApp"`
Return string `yaml:"return"` Return string `yaml:"return"`
QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"` QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"`
TogglePanel string `yaml:"togglePanel"` TogglePanel string `yaml:"togglePanel"`
@@ -854,6 +855,7 @@ func GetDefaultConfig() *UserConfig {
Universal: KeybindingUniversalConfig{ Universal: KeybindingUniversalConfig{
Quit: "q", Quit: "q",
QuitAlt1: "<c-c>", QuitAlt1: "<c-c>",
SuspendApp: "<c-z>",
Return: "<esc>", Return: "<esc>",
QuitWithoutChangingDirectory: "Q", QuitWithoutChangingDirectory: "Q",
TogglePanel: "<tab>", TogglePanel: "<tab>",

View File

@@ -109,6 +109,7 @@ func (gui *Gui) resetHelpersAndControllers() {
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper), AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
FixupHelper: helpers.NewFixupHelper(helperCommon), FixupHelper: helpers.NewFixupHelper(helperCommon),
Commits: commitsHelper, Commits: commitsHelper,
SuspendResume: helpers.NewSuspendResumeHelper(helperCommon),
Snake: helpers.NewSnakeHelper(helperCommon), Snake: helpers.NewSnakeHelper(helperCommon),
Diff: diffHelper, Diff: diffHelper,
Repos: reposHelper, Repos: reposHelper,

View File

@@ -123,6 +123,20 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
Modifier: gocui.ModNone, Modifier: gocui.ModNone,
Handler: self.quitWithoutChangingDirectory, Handler: self.quitWithoutChangingDirectory,
}, },
{
Key: opts.GetKey(opts.Config.Universal.SuspendApp),
Modifier: gocui.ModNone,
Handler: self.c.Helpers().SuspendResume.SuspendApp,
Description: self.c.Tr.SuspendApp,
GetDisabledReason: func() *types.DisabledReason {
if !self.c.Helpers().SuspendResume.CanSuspendApp() {
return &types.DisabledReason{
Text: self.c.Tr.CannotSuspendApp,
}
}
return nil
},
},
{ {
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView), Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
Handler: self.toggleWhitespace, Handler: self.toggleWhitespace,

View File

@@ -35,6 +35,7 @@ type Helpers struct {
AmendHelper *AmendHelper AmendHelper *AmendHelper
FixupHelper *FixupHelper FixupHelper *FixupHelper
Commits *CommitsHelper Commits *CommitsHelper
SuspendResume *SuspendResumeHelper
Snake *SnakeHelper Snake *SnakeHelper
// lives in context package because our contexts need it to render to main // lives in context package because our contexts need it to render to main
Diff *DiffHelper Diff *DiffHelper

View File

@@ -0,0 +1,59 @@
//go:build !windows
package helpers
import (
"os"
"os/signal"
"syscall"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func canSuspendApp() bool {
return true
}
func sendStopSignal() error {
return syscall.Kill(0, syscall.SIGSTOP)
}
// setForegroundPgrp sets the current process group as the foreground process group
// for the terminal, allowing the program to read input after resuming from suspension.
func setForegroundPgrp() error {
fd, err := unix.Open("/dev/tty", unix.O_RDWR, 0)
if err != nil {
return err
}
defer unix.Close(fd)
pgid := syscall.Getpgrp()
return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pgid)
}
func handleResumeSignal(log *logrus.Entry, onResume func() error) {
if err := setForegroundPgrp(); err != nil {
log.Warning(err)
return
}
if err := onResume(); err != nil {
log.Warning(err)
}
}
func installResumeSignalHandler(log *logrus.Entry, onResume func() error) {
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGCONT)
for sig := range sigs {
switch sig {
case syscall.SIGCONT:
handleResumeSignal(log, onResume)
}
}
}()
}

View File

@@ -0,0 +1,16 @@
package helpers
import (
"github.com/sirupsen/logrus"
)
func canSuspendApp() bool {
return false
}
func sendStopSignal() error {
return nil
}
func installResumeSignalHandler(log *logrus.Entry, onResume func() error) {
}

View File

@@ -0,0 +1,31 @@
package helpers
type SuspendResumeHelper struct {
c *HelperCommon
}
func NewSuspendResumeHelper(c *HelperCommon) *SuspendResumeHelper {
return &SuspendResumeHelper{
c: c,
}
}
func (s *SuspendResumeHelper) CanSuspendApp() bool {
return canSuspendApp()
}
func (s *SuspendResumeHelper) SuspendApp() error {
if !canSuspendApp() {
return nil
}
if err := s.c.Suspend(); err != nil {
return err
}
return sendStopSignal()
}
func (s *SuspendResumeHelper) InstallResumeSignalHandler() {
installResumeSignalHandler(s.c.Log, s.c.Resume)
}

View File

@@ -848,6 +848,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
gui.BackgroundRoutineMgr.startBackgroundRoutines() gui.BackgroundRoutineMgr.startBackgroundRoutines()
gui.Helpers().SuspendResume.InstallResumeSignalHandler()
gui.c.Log.Info("starting main loop") gui.c.Log.Info("starting main loop")
// setting here so we can use it in layout.go // setting here so we can use it in layout.go

View File

@@ -42,6 +42,14 @@ func (self *guiCommon) RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error) {
return self.gui.runSubprocessWithSuspense(cmdObj) return self.gui.runSubprocessWithSuspense(cmdObj)
} }
func (self *guiCommon) Suspend() error {
return self.gui.suspend()
}
func (self *guiCommon) Resume() error {
return self.gui.resume()
}
func (self *guiCommon) Context() types.IContextMgr { func (self *guiCommon) Context() types.IContextMgr {
return self.gui.State.ContextMgr return self.gui.State.ContextMgr
} }

View File

@@ -56,6 +56,9 @@ type IGuiCommon interface {
RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error) RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error)
RunSubprocessAndRefresh(*oscommands.CmdObj) error RunSubprocessAndRefresh(*oscommands.CmdObj) error
Suspend() error
Resume() error
Context() IContextMgr Context() IContextMgr
ContextForKey(key ContextKey) Context ContextForKey(key ContextKey) Context

View File

@@ -382,6 +382,8 @@ type TranslationSet struct {
ScrollUp string ScrollUp string
ScrollUpMainWindow string ScrollUpMainWindow string
ScrollDownMainWindow string ScrollDownMainWindow string
SuspendApp string
CannotSuspendApp string
AmendCommitTitle string AmendCommitTitle string
AmendCommitPrompt string AmendCommitPrompt string
AmendCommitWithConflictsMenuPrompt string AmendCommitWithConflictsMenuPrompt string
@@ -1456,6 +1458,8 @@ func EnglishTranslationSet() *TranslationSet {
ScrollUp: "Scroll up", ScrollUp: "Scroll up",
ScrollUpMainWindow: "Scroll up main window", ScrollUpMainWindow: "Scroll up main window",
ScrollDownMainWindow: "Scroll down main window", ScrollDownMainWindow: "Scroll down main window",
SuspendApp: "Suspend the application",
CannotSuspendApp: "Suspending the application is not supported on Windows",
AmendCommitTitle: "Amend commit", AmendCommitTitle: "Amend commit",
AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?", AmendCommitPrompt: "Are you sure you want to amend this commit with your staged files?",
AmendCommitWithConflictsMenuPrompt: "WARNING: you are about to amend the last finished commit with your resolved conflicts. This is very unlikely to be what you want at this point. More likely, you simply want to continue the rebase instead.\n\nDo you still want to amend the previous commit?", AmendCommitWithConflictsMenuPrompt: "WARNING: you are about to amend the last finished commit with your resolved conflicts. This is very unlikely to be what you want at this point. More likely, you simply want to continue the rebase instead.\n\nDo you still want to amend the previous commit?",