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
|
// Copyright 2013-2015 Bowery, Inc.
package prompt
import (
"bufio"
"os"
"syscall"
"unsafe"
)
const (
echoInputFlag = 0x0004
insertModeFlag = 0x0020
lineInputFlag = 0x0002
mouseInputFlag = 0x0010
processedInputFlag = 0x0001
windowInputFlag = 0x0008
errnoInvalidHandle = 0x6
)
var (
kernel = syscall.NewLazyDLL("kernel32.dll")
getConsoleScreenBufferInfo = kernel.NewProc("GetConsoleScreenBufferInfo")
setConsoleMode = kernel.NewProc("SetConsoleMode")
)
// consoleScreenBufferInfo contains various fields for the terminal.
type consoleScreenBufferInfo struct {
size coord
cursorPosition coord
attributes uint16
window smallRect
maximumWindowSize coord
}
// coord contains coords for positioning.
type coord struct {
x int16
y int16
}
// smallRect contains positions for the window edges.
type smallRect struct {
left int16
top int16
right int16
bottom int16
}
// TerminalSize retrieves the cols/rows for the terminal connected to out.
func TerminalSize(out *os.File) (int, int, error) {
csbi := new(consoleScreenBufferInfo)
ret, _, err := getConsoleScreenBufferInfo.Call(out.Fd(),
uintptr(unsafe.Pointer(csbi)))
if ret == 0 {
return 0, 0, err
}
// Results are always off by one.
cols := csbi.window.right - csbi.window.left + 1
rows := csbi.window.bottom - csbi.window.top + 1
return int(cols), int(rows), nil
}
// IsNotTerminal checks if an error is related to io not being a terminal.
func IsNotTerminal(err error) bool {
errno, ok := err.(syscall.Errno)
if ok && errno == errnoInvalidHandle {
return true
}
return false
}
// terminal contains the private fields for a Windows terminal.
type terminal struct {
isTerm bool
simpleReader *bufio.Reader
origMode uint32
}
// 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),
}
err := syscall.GetConsoleMode(syscall.Handle(term.In.Fd()), &term.origMode)
if err != nil {
return term, nil
}
mode := term.origMode
term.isTerm = true
// Set new mode flags.
mode &^= (echoInputFlag | insertModeFlag | lineInputFlag | mouseInputFlag |
processedInputFlag | windowInputFlag)
ret, _, err := setConsoleMode.Call(term.In.Fd(), uintptr(mode))
if ret == 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.isTerm {
return term.simplePrompt(prefix)
}
buf := NewBuffer(prefix, term.Out, true)
return term.prompt(buf, NewAnsiReader(term.In))
}
// GetPassword gets a line with the prefix and doesn't echo input.
func (term *Terminal) GetPassword(prefix string) (string, error) {
if !term.isTerm {
return term.simplePrompt(prefix)
}
buf := NewBuffer(prefix, term.Out, false)
return term.password(buf, NewAnsiReader(term.In))
}
// Close disables the terminals raw input.
func (term *Terminal) Close() error {
if term.isTerm {
ret, _, err := setConsoleMode.Call(term.In.Fd(), uintptr(term.origMode))
if ret == 0 {
return err
}
}
return nil
}
|