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:
committed by
Stefan Haller
parent
af190ad280
commit
0f38d2d61e
2
go.mod
2
go.mod
@@ -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
|
||||||
|
@@ -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>",
|
||||||
|
@@ -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,
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
59
pkg/gui/controllers/helpers/signal_handling.go
Normal file
59
pkg/gui/controllers/helpers/signal_handling.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
16
pkg/gui/controllers/helpers/signal_handling_windows.go
Normal file
16
pkg/gui/controllers/helpers/signal_handling_windows.go
Normal 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) {
|
||||||
|
}
|
31
pkg/gui/controllers/helpers/suspend_resume_helper.go
Normal file
31
pkg/gui/controllers/helpers/suspend_resume_helper.go
Normal 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)
|
||||||
|
}
|
@@ -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
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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?",
|
||||||
|
Reference in New Issue
Block a user