Repository navigation
Expand file tree
/
Copy pathgow_stdio.go
More file actions
151 lines (122 loc) · 3.09 KB
/
Copy pathgow_stdio.go
File metadata and controls
151 lines (122 loc) · 3.09 KB
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
148
149
150
151
package main
import (
"errors"
"io"
"os"
"syscall"
"time"
"github.com/mitranim/gg"
)
// Used by the ^C hotkey.
const DoubleInputDelay = time.Second
/*
Standard input/output adapter for terminal raw mode. Raw mode allows us to
interpret ASCII control codes, sent via various standard key combinations,
in our own way. This allows to support hotkeys such as "restart subprocess".
This also makes us responsible for interpreting standard essential hotkeys,
such as converting `ctrl+c` to `SIGINT`.
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, sigQuit, `^\`)
}
// TODO include all current subproces with their args.
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) {
if self.Main().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
}