Skip to content

Commit

Permalink
support suspend process (#48)
Browse files Browse the repository at this point in the history
* support suspend process

* fix suspend in windows

* add comments
  • Loading branch information
chzyer committed Apr 17, 2016
1 parent 52d8a65 commit 3ea5940
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 0 deletions.
1 change: 1 addition & 0 deletions char.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
CharTranspose = 20
CharCtrlU = 21
CharCtrlW = 23
CharCtrlZ = 26
CharEsc = 27
CharEscapeEx = 91
CharBackspace = 127
Expand Down
4 changes: 4 additions & 0 deletions operation.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ func (o *Operation) ioloop() {
if o.IsInCompleteMode() {
o.OnComplete()
}
case CharCtrlZ:
o.buf.Clean()
o.t.SleepToResume()
o.Refresh()
case MetaBackspace, CharCtrlW:
o.buf.BackEscapeWord()
case CharEnter, CharCtrlJ:
Expand Down
20 changes: 20 additions & 0 deletions terminal.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package readline
import (
"bufio"
"fmt"
"strings"
"sync"
"sync/atomic"
)
Expand All @@ -15,6 +16,7 @@ type Terminal struct {
kickChan chan struct{}
wg sync.WaitGroup
isReading int32
sleeping int32
}

func NewTerminal(cfg *Config) (*Terminal, error) {
Expand All @@ -32,6 +34,20 @@ func NewTerminal(cfg *Config) (*Terminal, error) {
return t, nil
}

// SleepToResume will sleep myself, and return only if I'm resumed.
func (t *Terminal) SleepToResume() {
if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {
return
}
defer atomic.StoreInt32(&t.sleeping, 0)

t.ExitRawMode()
ch := WaitForResume()
SuspendMe()
<-ch
t.EnterRawMode()
}

func (t *Terminal) EnterRawMode() (err error) {
return t.cfg.FuncMakeRaw()
}
Expand Down Expand Up @@ -99,6 +115,10 @@ func (t *Terminal) ioloop() {
expectNextChar = false
r, _, err := buf.ReadRune()
if err != nil {
if strings.Contains(err.Error(), "interrupted system call") {
expectNextChar = true
continue
}
break
}

Expand Down
27 changes: 27 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"bufio"
"bytes"
"strconv"
"sync"
"time"

"golang.org/x/crypto/ssh/terminal"
)
Expand All @@ -12,6 +14,31 @@ var (
isWindows = false
)

// WaitForResume need to call before current process got suspend.
// It will run a ticker until a long duration is occurs,
// which means this process is resumed.
func WaitForResume() chan struct{} {
ch := make(chan struct{})
var wg sync.WaitGroup
wg.Add(1)
go func() {
ticker := time.NewTicker(10 * time.Millisecond)
t := time.Now()
wg.Done()
for {
now := <-ticker.C
if now.Sub(t) > 100*time.Millisecond {
break
}
t = now
}
ticker.Stop()
ch <- struct{}{}
}()
wg.Wait()
return ch
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool {
return terminal.IsTerminal(fd)
Expand Down
10 changes: 10 additions & 0 deletions utils_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ type winsize struct {
Ypixel uint16
}

// SuspendMe use to send suspend signal to myself, when we in the raw mode.
// For OSX it need to send to parent's pid
// For Linux it need to send to myself
func SuspendMe() {
p, _ := os.FindProcess(os.Getppid())
p.Signal(syscall.SIGTSTP)
p, _ = os.FindProcess(os.Getpid())
p.Signal(syscall.SIGTSTP)
}

// get width of the terminal
func getWidth(stdoutFd int) int {
ws := &winsize{}
Expand Down
3 changes: 3 additions & 0 deletions utils_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ package readline

import "syscall"

func SuspendMe() {
}

func GetStdin() int {
return int(syscall.Stdin)
}
Expand Down

0 comments on commit 3ea5940

Please sign in to comment.