This repository has been archived by the owner on Mar 2, 2020. It is now read-only.
/
term_unix.go
148 lines (118 loc) · 3.24 KB
/
term_unix.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
// +build linux darwin freebsd openbsd netbsd dragonfly
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"bufio"
"os"
"syscall"
"unsafe"
)
var unsupported = []string{"", "dumb", "cons25"}
// supportedTerminal checks if the terminal supports ansi escapes.
func supportedTerminal() bool {
term := os.Getenv("TERM")
for _, t := range unsupported {
if t == term {
return false
}
}
return true
}
// winsize contains the size for the terminal.
type winsize struct {
rows uint16
cols uint16
xpixel uint16
ypixel uint16
}
// TerminalSize retrieves the cols/rows for the terminal connected to out.
func TerminalSize(out *os.File) (int, int, error) {
ws := new(winsize)
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, out.Fd(),
uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
if err != 0 {
return 0, 0, err
}
return int(ws.cols), int(ws.rows), nil
}
// IsNotTerminal checks if an error is related to io not being a terminal.
func IsNotTerminal(err error) bool {
if err == syscall.ENOTTY {
return true
}
return false
}
// terminal contains the private fields for a Unix terminal.
type terminal struct {
supported bool
simpleReader *bufio.Reader
origMode syscall.Termios
}
// NewTerminal creates a terminal and sets it to raw input mode.
func NewTerminal() (*Terminal, error) {
term := &Terminal{
In: os.Stdin,
Out: os.Stdout,
History: make([]string, 0, 10),
histIdx: -1,
terminal: new(terminal),
}
if !supportedTerminal() {
return term, nil
}
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, term.In.Fd(),
uintptr(tcgets), uintptr(unsafe.Pointer(&term.origMode)))
if err != 0 {
if IsNotTerminal(err) {
return term, nil
}
return nil, err
}
mode := term.origMode
term.supported = true
// Set new mode flags, for reference see cfmakeraw(3).
mode.Iflag &^= (syscall.BRKINT | syscall.IGNBRK | syscall.ICRNL |
syscall.INLCR | syscall.IGNCR | syscall.ISTRIP | syscall.IXON |
syscall.PARMRK)
mode.Oflag &^= syscall.OPOST
mode.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON |
syscall.ISIG | syscall.IEXTEN)
mode.Cflag &^= (syscall.CSIZE | syscall.PARENB)
mode.Cflag |= syscall.CS8
// Set controls; min num of bytes, and timeouts.
mode.Cc[syscall.VMIN] = 1
mode.Cc[syscall.VTIME] = 0
_, _, err = syscall.Syscall(syscall.SYS_IOCTL, term.In.Fd(),
uintptr(tcsetsf), uintptr(unsafe.Pointer(&mode)))
if err != 0 {
return nil, err
}
return term, nil
}
// GetPrompt gets a line with the prefix and echos input.
func (term *Terminal) GetPrompt(prefix string) (string, error) {
if !term.supported {
return term.simplePrompt(prefix)
}
buf := NewBuffer(prefix, term.Out, true)
return term.prompt(buf, term.In)
}
// GetPassword gets a line with the prefix and doesn't echo input.
func (term *Terminal) GetPassword(prefix string) (string, error) {
if !term.supported {
return term.simplePrompt(prefix)
}
buf := NewBuffer(prefix, term.Out, false)
return term.password(buf, term.In)
}
// Close disables the terminals raw input.
func (term *Terminal) Close() error {
if term.supported {
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, term.In.Fd(),
uintptr(tcsets), uintptr(unsafe.Pointer(&term.origMode)))
if err != 0 {
return err
}
}
return nil
}