-
Notifications
You must be signed in to change notification settings - Fork 29
/
gow_stdio.go
147 lines (120 loc) · 2.96 KB
/
gow_stdio.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package main
import (
"errors"
"io"
"os"
"syscall"
"time"
"github.com/mitranim/gg"
)
const DoubleInputDelay = time.Second
/*
Standard input/output adapter for terminal raw mode. Raw mode allows us to
support our own control codes, but we're also responsible for interpreting
common ASCII codes into OS signals, and optionally for echoing other characters
to stdout. This adapter is unnecessary in non-raw mode where we simply pipe
stdio to/from the child process.
*/
type Stdio struct {
Mained
LastChar byte
LastInst time.Time
}
/*
Doesn't require special cleanup before stopping `gow`. We run only one stdio
loop, without ever replacing it.
*/
func (*Stdio) Deinit() {}
/*
TODO: ideally, this should be used only in terminal raw mode. See the comment in
`Cmd.MakeCmd` that explains the issue.
*/
func (self *Stdio) Run() {
self.LastInst = time.Now()
for {
var buf [1]byte
size, err := os.Stdin.Read(buf[:])
if errors.Is(err, io.EOF) {
return
}
if err != nil {
log.Println(`error when reading stdin, shutting down stdio:`, err)
return
}
gg.Try(err)
if size <= 0 {
return
}
self.OnByte(buf[0])
}
}
/*
Interpret known ASCII codes as OS signals.
Otherwise forward the input to the subprocess.
*/
func (self *Stdio) OnByte(char byte) {
defer recLog()
defer self.AfterByte(char)
switch char {
case CODE_INTERRUPT:
self.OnCodeInterrupt()
case CODE_QUIT:
self.OnCodeQuit()
case CODE_PRINT_COMMAND:
self.OnCodePrintCommand()
case CODE_RESTART:
self.OnCodeRestart()
case CODE_STOP:
self.OnCodeStop()
case CODE_PRINT_HELP, CODE_PRINT_HELP_MACOS:
self.OnCodePrintHelp()
default:
self.OnByteAny(char)
}
}
func (self *Stdio) AfterByte(char byte) {
self.LastChar = char
self.LastInst = time.Now()
}
func (self *Stdio) OnCodeInterrupt() {
self.OnCodeSig(CODE_INTERRUPT, syscall.SIGINT, `^C`)
}
func (self *Stdio) OnCodeQuit() {
self.OnCodeSig(CODE_QUIT, syscall.SIGQUIT, `^\`)
}
func (*Stdio) OnCodePrintCommand() {
log.Printf(`current command: %q`, os.Args)
}
func (*Stdio) OnCodePrintHelp() { log.Println(HOTKEY_HELP) }
func (self *Stdio) OnCodeRestart() {
main := self.Main()
if main.Opt.Verb {
log.Println(`received ^R, restarting`)
}
main.Restart()
}
func (self *Stdio) OnCodeStop() {
self.OnCodeSig(CODE_STOP, syscall.SIGTERM, `^T`)
}
func (self *Stdio) OnByteAny(char byte) {
main := self.Main()
main.Cmd.WriteChar(char)
if main.Opt.GetEchoMode() == EchoModeGow {
gg.Nop2(writeByte(os.Stdout, char))
}
}
func (self *Stdio) OnCodeSig(code byte, sig syscall.Signal, desc string) {
main := self.Main()
if self.IsCodeRepeated(code) {
log.Println(`received ` + desc + desc + `, shutting down`)
main.Kill(sig)
return
}
if main.Opt.Verb {
log.Println(`broadcasting ` + desc + ` to subprocesses; repeat within ` + DoubleInputDelay.String() + ` to kill gow`)
}
main.Cmd.Broadcast(sig)
}
func (self *Stdio) IsCodeRepeated(char byte) bool {
return self.LastChar == char && time.Since(self.LastInst) < DoubleInputDelay
}