File: term_windows.go

package info (click to toggle)
golang-github-bowery-prompt 0.0~git20160808.0.d43c270-1
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 128 kB
  • ctags: 186
  • sloc: makefile: 2
file content (143 lines) | stat: -rw-r--r-- 3,297 bytes parent folder | download
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
}