-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathutil_darwin.go
180 lines (153 loc) · 3.71 KB
/
util_darwin.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
//go:build darwin
package console
import (
"os"
"syscall"
"unicode/utf8"
"unsafe"
"golang.org/x/sys/unix"
)
const ioctlReadTermios = unix.TIOCGETA
const ioctlWriteTermios = unix.TIOCSETA
func withoutEcho(f func() error) error {
fd := int(os.Stdin.Fd())
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
if err != nil {
return err
}
newState := *termios
newState.Lflag &^= unix.ECHO
newState.Lflag |= unix.ICANON | unix.ISIG
newState.Iflag |= unix.ICRNL
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
return err
}
defer unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
return f()
}
func supportsColors() bool {
return true
}
var (
ttyIn *os.File
ttyOut *os.File
ttyOldTermios syscall.Termios
ttyBuffer []byte
)
func beginReadKey() error {
in, err := os.OpenFile("/dev/tty", syscall.O_RDONLY, 0)
if err != nil {
return err
}
ttyIn = in
out, err := os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
if err != nil {
ttyIn.Close()
return err
}
ttyOut = out
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(in.Fd()), ioctlReadTermios, uintptr(unsafe.Pointer(&ttyOldTermios))); err != 0 {
ttyIn.Close()
ttyOut.Close()
return err
}
newTermios := ttyOldTermios
newTermios.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXOFF
newTermios.Lflag &^= syscall.ECHO | syscall.ICANON
//TODO catch Ctrl+C
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(in.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&newTermios))); err != 0 {
ttyIn.Close()
ttyOut.Close()
return err
}
ttyBuffer = []byte{}
return nil
}
func readKey() (Key, rune, error) {
buf := make([]byte, 4)
var bufLen int
if len(ttyBuffer) > 0 {
// use buffered data from previous readKey call
bufLen = len(ttyBuffer)
for i := 0; i < len(ttyBuffer); i++ {
buf[i] = ttyBuffer[i]
}
ttyBuffer = []byte{}
} else {
// read new buffer
len, err := ttyIn.Read(buf)
if err != nil {
return 0, 0, err
}
// huge amounts of data leading to multiple keys at once in the buffer will probably be caused by inserting text
// thus, explicit actions keys are assumed to be received in a single read call
// handle escape key sequences
if buf[0] == 27 {
if len == 1 {
return KeyEscape, '^', nil
}
if len == 2 {
// malformed escape sequence
return KeyEscape, rune(buf[1]), nil
}
switch buf[2] {
case 65:
return KeyUp, 0, nil
case 66:
return KeyDown, 0, nil
case 68:
return KeyLeft, 0, nil
case 67:
return KeyRight, 0, nil
default:
// unknown escape sequence
return KeyEscape, rune(buf[2]), nil
}
}
bufLen = len
}
// handle some special chars
switch buf[0] {
case '\r':
ttyBuffer = buf[1:bufLen]
return KeyEnter, '\n', nil
case '\u007f':
ttyBuffer = buf[1:bufLen]
return KeyBackspace, '\r', nil
case '\t':
ttyBuffer = buf[1:bufLen]
return KeyTab, '\t', nil
case ' ':
ttyBuffer = buf[1:bufLen]
return KeySpace, ' ', nil
case '\x03':
ttyBuffer = buf[1:bufLen]
return KeyCtrlC, 0, nil
}
// assemble utf8-rune
for i := 1; i < 4; i++ {
// return complete buffer rune
if utf8.FullRune(buf[:i]) {
// return remainder after rune
ttyBuffer = buf[i:bufLen]
break
}
if i >= bufLen {
// need to fill the buffer from tty input to complete the rune
if _, err := ttyIn.Read(buf[i : i+1]); err != nil {
return 0, 0, err
}
bufLen++
}
}
r, _ := utf8.DecodeRune(buf)
return 0, r, nil
}
func endReadKey() error {
if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(ttyIn.Fd()), ioctlWriteTermios, uintptr(unsafe.Pointer(&ttyOldTermios))); err != 0 {
return err
}
ttyIn.Close()
ttyOut.Close()
return nil
}