support suspend process (#48)

* support suspend process

* fix suspend in windows

* add comments
This commit is contained in:
chzyer 2016-04-17 21:05:00 +08:00
parent 52d8a65723
commit 3ea5940c39
6 changed files with 65 additions and 0 deletions

View File

@ -20,6 +20,7 @@ const (
CharTranspose = 20
CharCtrlU = 21
CharCtrlW = 23
CharCtrlZ = 26
CharEsc = 27
CharEscapeEx = 91
CharBackspace = 127

View File

@ -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:

View File

@ -3,6 +3,7 @@ package readline
import (
"bufio"
"fmt"
"strings"
"sync"
"sync/atomic"
)
@ -15,6 +16,7 @@ type Terminal struct {
kickChan chan struct{}
wg sync.WaitGroup
isReading int32
sleeping int32
}
func NewTerminal(cfg *Config) (*Terminal, error) {
@ -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()
}
@ -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
}

View File

@ -4,6 +4,8 @@ import (
"bufio"
"bytes"
"strconv"
"sync"
"time"
"golang.org/x/crypto/ssh/terminal"
)
@ -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)

View File

@ -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{}

View File

@ -4,6 +4,9 @@ package readline
import "syscall"
func SuspendMe() {
}
func GetStdin() int {
return int(syscall.Stdin)
}