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 CharTranspose = 20
CharCtrlU = 21 CharCtrlU = 21
CharCtrlW = 23 CharCtrlW = 23
CharCtrlZ = 26
CharEsc = 27 CharEsc = 27
CharEscapeEx = 91 CharEscapeEx = 91
CharBackspace = 127 CharBackspace = 127

View File

@ -186,6 +186,10 @@ func (o *Operation) ioloop() {
if o.IsInCompleteMode() { if o.IsInCompleteMode() {
o.OnComplete() o.OnComplete()
} }
case CharCtrlZ:
o.buf.Clean()
o.t.SleepToResume()
o.Refresh()
case MetaBackspace, CharCtrlW: case MetaBackspace, CharCtrlW:
o.buf.BackEscapeWord() o.buf.BackEscapeWord()
case CharEnter, CharCtrlJ: case CharEnter, CharCtrlJ:

View File

@ -3,6 +3,7 @@ package readline
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
) )
@ -15,6 +16,7 @@ type Terminal struct {
kickChan chan struct{} kickChan chan struct{}
wg sync.WaitGroup wg sync.WaitGroup
isReading int32 isReading int32
sleeping int32
} }
func NewTerminal(cfg *Config) (*Terminal, error) { func NewTerminal(cfg *Config) (*Terminal, error) {
@ -32,6 +34,20 @@ func NewTerminal(cfg *Config) (*Terminal, error) {
return t, nil 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) { func (t *Terminal) EnterRawMode() (err error) {
return t.cfg.FuncMakeRaw() return t.cfg.FuncMakeRaw()
} }
@ -99,6 +115,10 @@ func (t *Terminal) ioloop() {
expectNextChar = false expectNextChar = false
r, _, err := buf.ReadRune() r, _, err := buf.ReadRune()
if err != nil { if err != nil {
if strings.Contains(err.Error(), "interrupted system call") {
expectNextChar = true
continue
}
break break
} }

View File

@ -4,6 +4,8 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"strconv" "strconv"
"sync"
"time"
"golang.org/x/crypto/ssh/terminal" "golang.org/x/crypto/ssh/terminal"
) )
@ -12,6 +14,31 @@ var (
isWindows = false 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. // IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd int) bool { func IsTerminal(fd int) bool {
return terminal.IsTerminal(fd) return terminal.IsTerminal(fd)

View File

@ -17,6 +17,16 @@ type winsize struct {
Ypixel uint16 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 // get width of the terminal
func getWidth(stdoutFd int) int { func getWidth(stdoutFd int) int {
ws := &winsize{} ws := &winsize{}

View File

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