Skip to content

Commit 4ede0b8

Browse files
Implement suspending the app using ctrl-z
Co-authored-by: Stefan Haller <[email protected]>
1 parent dbd8687 commit 4ede0b8

File tree

12 files changed

+142
-1
lines changed

12 files changed

+142
-1
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ require (
3737
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
3838
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
3939
golang.org/x/sync v0.16.0
40+
golang.org/x/sys v0.34.0
4041
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
4142
gopkg.in/yaml.v3 v3.0.1
4243
)
@@ -77,7 +78,6 @@ require (
7778
github.com/xanzy/ssh-agent v0.3.3 // indirect
7879
golang.org/x/crypto v0.37.0 // indirect
7980
golang.org/x/net v0.39.0 // indirect
80-
golang.org/x/sys v0.34.0 // indirect
8181
golang.org/x/term v0.33.0 // indirect
8282
golang.org/x/text v0.27.0 // indirect
8383
gopkg.in/fsnotify.v1 v1.4.7 // indirect

pkg/config/user_config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ type KeybindingConfig struct {
390390
type KeybindingUniversalConfig struct {
391391
Quit string `yaml:"quit"`
392392
QuitAlt1 string `yaml:"quit-alt1"`
393+
SuspendApp string `yaml:"suspendApp"`
393394
Return string `yaml:"return"`
394395
QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"`
395396
TogglePanel string `yaml:"togglePanel"`
@@ -854,6 +855,7 @@ func GetDefaultConfig() *UserConfig {
854855
Universal: KeybindingUniversalConfig{
855856
Quit: "q",
856857
QuitAlt1: "<c-c>",
858+
SuspendApp: "<c-z>",
857859
Return: "<esc>",
858860
QuitWithoutChangingDirectory: "Q",
859861
TogglePanel: "<tab>",

pkg/gui/controllers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ func (gui *Gui) resetHelpersAndControllers() {
109109
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
110110
FixupHelper: helpers.NewFixupHelper(helperCommon),
111111
Commits: commitsHelper,
112+
SuspendResume: helpers.NewSuspendResumeHelper(helperCommon),
112113
Snake: helpers.NewSnakeHelper(helperCommon),
113114
Diff: diffHelper,
114115
Repos: reposHelper,

pkg/gui/controllers/global_controller.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
114114
Modifier: gocui.ModNone,
115115
Handler: self.quitWithoutChangingDirectory,
116116
},
117+
{
118+
Key: opts.GetKey(opts.Config.Universal.SuspendApp),
119+
Modifier: gocui.ModNone,
120+
Handler: self.c.Helpers().SuspendResume.SuspendApp,
121+
Description: self.c.Tr.SuspendApp,
122+
GetDisabledReason: func() *types.DisabledReason {
123+
if !self.c.Helpers().SuspendResume.CanSuspendApp() {
124+
return &types.DisabledReason{
125+
Text: self.c.Tr.CannotSuspendApp,
126+
}
127+
}
128+
return nil
129+
},
130+
},
117131
{
118132
Key: opts.GetKey(opts.Config.Universal.Return),
119133
Modifier: gocui.ModNone,

pkg/gui/controllers/helpers/helpers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Helpers struct {
3535
AmendHelper *AmendHelper
3636
FixupHelper *FixupHelper
3737
Commits *CommitsHelper
38+
SuspendResume *SuspendResumeHelper
3839
Snake *SnakeHelper
3940
// lives in context package because our contexts need it to render to main
4041
Diff *DiffHelper
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//go:build !windows
2+
3+
package helpers
4+
5+
import (
6+
"os"
7+
"os/signal"
8+
"syscall"
9+
10+
"github.com/sirupsen/logrus"
11+
"golang.org/x/sys/unix"
12+
)
13+
14+
func canSuspendApp() bool {
15+
return true
16+
}
17+
18+
func sendStopSignal() error {
19+
return syscall.Kill(0, syscall.SIGSTOP)
20+
}
21+
22+
// setForegroundPgrp sets the current process group as the foreground process group
23+
// for the terminal, allowing the program to read input after resuming from suspension.
24+
func setForegroundPgrp() error {
25+
fd, err := unix.Open("/dev/tty", unix.O_RDWR, 0)
26+
if err != nil {
27+
return err
28+
}
29+
defer unix.Close(fd)
30+
31+
pgid := syscall.Getpgrp()
32+
33+
return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pgid)
34+
}
35+
36+
func handleResumeSignal(log *logrus.Entry, onResume func() error) {
37+
if err := setForegroundPgrp(); err != nil {
38+
log.Warning(err)
39+
return
40+
}
41+
42+
if err := onResume(); err != nil {
43+
log.Warning(err)
44+
}
45+
}
46+
47+
func installResumeSignalHandler(log *logrus.Entry, onResume func() error) {
48+
go func() {
49+
sigs := make(chan os.Signal, 1)
50+
signal.Notify(sigs, syscall.SIGCONT)
51+
52+
for sig := range sigs {
53+
switch sig {
54+
case syscall.SIGCONT:
55+
handleResumeSignal(log, onResume)
56+
}
57+
}
58+
}()
59+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package helpers
2+
3+
import (
4+
"github.com/sirupsen/logrus"
5+
)
6+
7+
func canSuspendApp() bool {
8+
return false
9+
}
10+
11+
func sendStopSignal() error {
12+
return nil
13+
}
14+
15+
func installResumeSignalHandler(log *logrus.Entry, onResume func() error) {
16+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package helpers
2+
3+
type SuspendResumeHelper struct {
4+
c *HelperCommon
5+
}
6+
7+
func NewSuspendResumeHelper(c *HelperCommon) *SuspendResumeHelper {
8+
return &SuspendResumeHelper{
9+
c: c,
10+
}
11+
}
12+
13+
func (s *SuspendResumeHelper) CanSuspendApp() bool {
14+
return canSuspendApp()
15+
}
16+
17+
func (s *SuspendResumeHelper) SuspendApp() error {
18+
if !canSuspendApp() {
19+
return nil
20+
}
21+
22+
if err := s.c.Suspend(); err != nil {
23+
return err
24+
}
25+
26+
return sendStopSignal()
27+
}
28+
29+
func (s *SuspendResumeHelper) InstallResumeSignalHandler() {
30+
installResumeSignalHandler(s.c.Log, s.c.Resume)
31+
}

pkg/gui/gui.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
848848

849849
gui.BackgroundRoutineMgr.startBackgroundRoutines()
850850

851+
gui.Helpers().SuspendResume.InstallResumeSignalHandler()
852+
851853
gui.c.Log.Info("starting main loop")
852854

853855
// setting here so we can use it in layout.go

pkg/gui/gui_common.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ func (self *guiCommon) RunSubprocess(cmdObj *oscommands.CmdObj) (bool, error) {
4242
return self.gui.runSubprocessWithSuspense(cmdObj)
4343
}
4444

45+
func (self *guiCommon) Suspend() error {
46+
return self.gui.suspend()
47+
}
48+
49+
func (self *guiCommon) Resume() error {
50+
return self.gui.resume()
51+
}
52+
4553
func (self *guiCommon) Context() types.IContextMgr {
4654
return self.gui.State.ContextMgr
4755
}

0 commit comments

Comments
 (0)