Browse code

Merge pull request #14838 from Microsoft/10662-ansirewrite

Windows: CLI Improvement (TP3)

Jessie Frazelle authored on 2015/07/28 09:30:14
Showing 38 changed files
... ...
@@ -6,6 +6,7 @@ rm -rf vendor/
6 6
 source 'hack/.vendor-helpers.sh'
7 7
 
8 8
 # the following lines are in sorted order, FYI
9
+clone git github.com/Azure/go-ansiterm 0a9ca7117fc3e5629da85238ede560cb5e749783
9 10
 clone git github.com/Sirupsen/logrus v0.8.2 # logrus is a common dependency among multiple deps
10 11
 clone git github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
11 12
 clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
... ...
@@ -3,11 +3,14 @@
3 3
 package term
4 4
 
5 5
 import (
6
+	"fmt"
6 7
 	"io"
7 8
 	"os"
9
+	"os/signal"
8 10
 
11
+	"github.com/Azure/go-ansiterm/winterm"
9 12
 	"github.com/Sirupsen/logrus"
10
-	"github.com/docker/docker/pkg/term/winconsole"
13
+	"github.com/docker/docker/pkg/term/windows"
11 14
 )
12 15
 
13 16
 // State holds the console mode for the terminal.
... ...
@@ -31,53 +34,97 @@ func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
31 31
 		return os.Stdin, os.Stdout, os.Stderr
32 32
 	case os.Getenv("MSYSTEM") != "":
33 33
 		// MSYS (mingw) does not emulate ANSI well.
34
-		return winconsole.WinConsoleStreams()
34
+		return windows.ConsoleStreams()
35 35
 	default:
36
-		return winconsole.WinConsoleStreams()
36
+		return windows.ConsoleStreams()
37 37
 	}
38 38
 }
39 39
 
40 40
 // GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal.
41 41
 func GetFdInfo(in interface{}) (uintptr, bool) {
42
-	return winconsole.GetHandleInfo(in)
42
+	return windows.GetHandleInfo(in)
43 43
 }
44 44
 
45 45
 // GetWinsize returns the window size based on the specified file descriptor.
46 46
 func GetWinsize(fd uintptr) (*Winsize, error) {
47
-	info, err := winconsole.GetConsoleScreenBufferInfo(fd)
47
+
48
+	info, err := winterm.GetConsoleScreenBufferInfo(fd)
48 49
 	if err != nil {
49 50
 		return nil, err
50 51
 	}
51 52
 
52
-	// TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller)
53
-	return &Winsize{
53
+	winsize := &Winsize{
54 54
 		Width:  uint16(info.Window.Right - info.Window.Left + 1),
55 55
 		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
56 56
 		x:      0,
57
-		y:      0}, nil
57
+		y:      0}
58
+
59
+	// Note: GetWinsize is called frequently -- uncomment only for excessive details
60
+	// logrus.Debugf("[windows] GetWinsize: Console(%v)", info.String())
61
+	// logrus.Debugf("[windows] GetWinsize: Width(%v), Height(%v), x(%v), y(%v)", winsize.Width, winsize.Height, winsize.x, winsize.y)
62
+	return winsize, nil
58 63
 }
59 64
 
60 65
 // SetWinsize tries to set the specified window size for the specified file descriptor.
61 66
 func SetWinsize(fd uintptr, ws *Winsize) error {
62
-	// TODO(azlinux): Implement SetWinsize
63
-	logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked")
64
-	return nil
67
+
68
+	// Ensure the requested dimensions are no larger than the maximum window size
69
+	info, err := winterm.GetConsoleScreenBufferInfo(fd)
70
+	if err != nil {
71
+		return err
72
+	}
73
+
74
+	if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) {
75
+		return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)",
76
+			ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y)
77
+	}
78
+
79
+	// Narrow the sizes to that used by Windows
80
+	var width winterm.SHORT = winterm.SHORT(ws.Width)
81
+	var height winterm.SHORT = winterm.SHORT(ws.Height)
82
+
83
+	// Set the dimensions while ensuring they remain within the bounds of the backing console buffer
84
+	// -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs,
85
+	//    shift the upper left just enough to keep the new window within the buffer.
86
+	rect := info.Window
87
+	if width < rect.Right-rect.Left+1 {
88
+		rect.Right = rect.Left + width - 1
89
+	} else if width > rect.Right-rect.Left+1 {
90
+		rect.Right = rect.Left + width - 1
91
+		if rect.Right >= info.Size.X {
92
+			rect.Left = info.Size.X - width
93
+			rect.Right = info.Size.X - 1
94
+		}
95
+	}
96
+
97
+	if height < rect.Bottom-rect.Top+1 {
98
+		rect.Bottom = rect.Top + height - 1
99
+	} else if height > rect.Bottom-rect.Top+1 {
100
+		rect.Bottom = rect.Top + height - 1
101
+		if rect.Bottom >= info.Size.Y {
102
+			rect.Top = info.Size.Y - height
103
+			rect.Bottom = info.Size.Y - 1
104
+		}
105
+	}
106
+	logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect)
107
+
108
+	return winterm.SetConsoleWindowInfo(fd, true, rect)
65 109
 }
66 110
 
67 111
 // IsTerminal returns true if the given file descriptor is a terminal.
68 112
 func IsTerminal(fd uintptr) bool {
69
-	return winconsole.IsConsole(fd)
113
+	return windows.IsConsole(fd)
70 114
 }
71 115
 
72 116
 // RestoreTerminal restores the terminal connected to the given file descriptor
73 117
 // to a previous state.
74 118
 func RestoreTerminal(fd uintptr, state *State) error {
75
-	return winconsole.SetConsoleMode(fd, state.mode)
119
+	return winterm.SetConsoleMode(fd, state.mode)
76 120
 }
77 121
 
78 122
 // SaveState saves the state of the terminal connected to the given file descriptor.
79 123
 func SaveState(fd uintptr) (*State, error) {
80
-	mode, e := winconsole.GetConsoleMode(fd)
124
+	mode, e := winterm.GetConsoleMode(fd)
81 125
 	if e != nil {
82 126
 		return nil, e
83 127
 	}
... ...
@@ -85,13 +132,20 @@ func SaveState(fd uintptr) (*State, error) {
85 85
 }
86 86
 
87 87
 // DisableEcho disables echo for the terminal connected to the given file descriptor.
88
-// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
88
+// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
89 89
 func DisableEcho(fd uintptr, state *State) error {
90 90
 	mode := state.mode
91
-	mode &^= winconsole.ENABLE_ECHO_INPUT
92
-	mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT
93
-	// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
94
-	return winconsole.SetConsoleMode(fd, mode)
91
+	mode &^= winterm.ENABLE_ECHO_INPUT
92
+	mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT
93
+
94
+	err := winterm.SetConsoleMode(fd, mode)
95
+	if err != nil {
96
+		return err
97
+	}
98
+
99
+	// Register an interrupt handler to catch and restore prior state
100
+	restoreAtInterrupt(fd, state)
101
+	return nil
95 102
 }
96 103
 
97 104
 // SetRawTerminal puts the terminal connected to the given file descriptor into raw
... ...
@@ -101,13 +155,14 @@ func SetRawTerminal(fd uintptr) (*State, error) {
101 101
 	if err != nil {
102 102
 		return nil, err
103 103
 	}
104
-	// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
104
+
105
+	// Register an interrupt handler to catch and restore prior state
106
+	restoreAtInterrupt(fd, state)
105 107
 	return state, err
106 108
 }
107 109
 
108
-// MakeRaw puts the terminal connected to the given file descriptor into raw
109
-// mode and returns the previous state of the terminal so that it can be
110
-// restored.
110
+// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw
111
+// mode and returns the previous state of the terminal so that it can be restored.
111 112
 func MakeRaw(fd uintptr) (*State, error) {
112 113
 	state, err := SaveState(fd)
113 114
 	if err != nil {
... ...
@@ -120,20 +175,31 @@ func MakeRaw(fd uintptr) (*State, error) {
120 120
 	mode := state.mode
121 121
 
122 122
 	// Disable these modes
123
-	mode &^= winconsole.ENABLE_ECHO_INPUT
124
-	mode &^= winconsole.ENABLE_LINE_INPUT
125
-	mode &^= winconsole.ENABLE_MOUSE_INPUT
126
-	mode &^= winconsole.ENABLE_WINDOW_INPUT
127
-	mode &^= winconsole.ENABLE_PROCESSED_INPUT
123
+	mode &^= winterm.ENABLE_ECHO_INPUT
124
+	mode &^= winterm.ENABLE_LINE_INPUT
125
+	mode &^= winterm.ENABLE_MOUSE_INPUT
126
+	mode &^= winterm.ENABLE_WINDOW_INPUT
127
+	mode &^= winterm.ENABLE_PROCESSED_INPUT
128 128
 
129 129
 	// Enable these modes
130
-	mode |= winconsole.ENABLE_EXTENDED_FLAGS
131
-	mode |= winconsole.ENABLE_INSERT_MODE
132
-	mode |= winconsole.ENABLE_QUICK_EDIT_MODE
130
+	mode |= winterm.ENABLE_EXTENDED_FLAGS
131
+	mode |= winterm.ENABLE_INSERT_MODE
132
+	mode |= winterm.ENABLE_QUICK_EDIT_MODE
133 133
 
134
-	err = winconsole.SetConsoleMode(fd, mode)
134
+	err = winterm.SetConsoleMode(fd, mode)
135 135
 	if err != nil {
136 136
 		return nil, err
137 137
 	}
138 138
 	return state, nil
139 139
 }
140
+
141
+func restoreAtInterrupt(fd uintptr, state *State) {
142
+	sigchan := make(chan os.Signal, 1)
143
+	signal.Notify(sigchan, os.Interrupt)
144
+
145
+	go func() {
146
+		_ = <-sigchan
147
+		RestoreTerminal(fd, state)
148
+		os.Exit(0)
149
+	}()
150
+}
140 151
deleted file mode 100644
... ...
@@ -1,1053 +0,0 @@
1
-// +build windows
2
-
3
-package winconsole
4
-
5
-import (
6
-	"bytes"
7
-	"fmt"
8
-	"io"
9
-	"os"
10
-	"strconv"
11
-	"strings"
12
-	"sync"
13
-	"syscall"
14
-	"unsafe"
15
-
16
-	"github.com/Sirupsen/logrus"
17
-)
18
-
19
-const (
20
-	// Consts for Get/SetConsoleMode function
21
-	// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
22
-	ENABLE_PROCESSED_INPUT = 0x0001
23
-	ENABLE_LINE_INPUT      = 0x0002
24
-	ENABLE_ECHO_INPUT      = 0x0004
25
-	ENABLE_WINDOW_INPUT    = 0x0008
26
-	ENABLE_MOUSE_INPUT     = 0x0010
27
-	ENABLE_INSERT_MODE     = 0x0020
28
-	ENABLE_QUICK_EDIT_MODE = 0x0040
29
-	ENABLE_EXTENDED_FLAGS  = 0x0080
30
-
31
-	// If parameter is a screen buffer handle, additional values
32
-	ENABLE_PROCESSED_OUTPUT   = 0x0001
33
-	ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
34
-
35
-	//http://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes
36
-	FOREGROUND_BLUE       = 1
37
-	FOREGROUND_GREEN      = 2
38
-	FOREGROUND_RED        = 4
39
-	FOREGROUND_INTENSITY  = 8
40
-	FOREGROUND_MASK_SET   = 0x000F
41
-	FOREGROUND_MASK_UNSET = 0xFFF0
42
-
43
-	BACKGROUND_BLUE       = 16
44
-	BACKGROUND_GREEN      = 32
45
-	BACKGROUND_RED        = 64
46
-	BACKGROUND_INTENSITY  = 128
47
-	BACKGROUND_MASK_SET   = 0x00F0
48
-	BACKGROUND_MASK_UNSET = 0xFF0F
49
-
50
-	COMMON_LVB_REVERSE_VIDEO = 0x4000
51
-	COMMON_LVB_UNDERSCORE    = 0x8000
52
-
53
-	// http://man7.org/linux/man-pages/man4/console_codes.4.html
54
-	// ECMA-48 Set Graphics Rendition
55
-	ANSI_ATTR_RESET     = 0
56
-	ANSI_ATTR_BOLD      = 1
57
-	ANSI_ATTR_DIM       = 2
58
-	ANSI_ATTR_UNDERLINE = 4
59
-	ANSI_ATTR_BLINK     = 5
60
-	ANSI_ATTR_REVERSE   = 7
61
-	ANSI_ATTR_INVISIBLE = 8
62
-
63
-	ANSI_ATTR_UNDERLINE_OFF = 24
64
-	ANSI_ATTR_BLINK_OFF     = 25
65
-	ANSI_ATTR_REVERSE_OFF   = 27
66
-	ANSI_ATTR_INVISIBLE_OFF = 8
67
-
68
-	ANSI_FOREGROUND_BLACK   = 30
69
-	ANSI_FOREGROUND_RED     = 31
70
-	ANSI_FOREGROUND_GREEN   = 32
71
-	ANSI_FOREGROUND_YELLOW  = 33
72
-	ANSI_FOREGROUND_BLUE    = 34
73
-	ANSI_FOREGROUND_MAGENTA = 35
74
-	ANSI_FOREGROUND_CYAN    = 36
75
-	ANSI_FOREGROUND_WHITE   = 37
76
-	ANSI_FOREGROUND_DEFAULT = 39
77
-
78
-	ANSI_BACKGROUND_BLACK   = 40
79
-	ANSI_BACKGROUND_RED     = 41
80
-	ANSI_BACKGROUND_GREEN   = 42
81
-	ANSI_BACKGROUND_YELLOW  = 43
82
-	ANSI_BACKGROUND_BLUE    = 44
83
-	ANSI_BACKGROUND_MAGENTA = 45
84
-	ANSI_BACKGROUND_CYAN    = 46
85
-	ANSI_BACKGROUND_WHITE   = 47
86
-	ANSI_BACKGROUND_DEFAULT = 49
87
-
88
-	ANSI_MAX_CMD_LENGTH = 256
89
-
90
-	MAX_INPUT_EVENTS = 128
91
-	MAX_INPUT_BUFFER = 1024
92
-	DEFAULT_WIDTH    = 80
93
-	DEFAULT_HEIGHT   = 24
94
-)
95
-
96
-// http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
97
-const (
98
-	VK_PRIOR    = 0x21 // PAGE UP key
99
-	VK_NEXT     = 0x22 // PAGE DOWN key
100
-	VK_END      = 0x23 // END key
101
-	VK_HOME     = 0x24 // HOME key
102
-	VK_LEFT     = 0x25 // LEFT ARROW key
103
-	VK_UP       = 0x26 // UP ARROW key
104
-	VK_RIGHT    = 0x27 // RIGHT ARROW key
105
-	VK_DOWN     = 0x28 // DOWN ARROW key
106
-	VK_SELECT   = 0x29 // SELECT key
107
-	VK_PRINT    = 0x2A // PRINT key
108
-	VK_EXECUTE  = 0x2B // EXECUTE key
109
-	VK_SNAPSHOT = 0x2C // PRINT SCREEN key
110
-	VK_INSERT   = 0x2D // INS key
111
-	VK_DELETE   = 0x2E // DEL key
112
-	VK_HELP     = 0x2F // HELP key
113
-	VK_F1       = 0x70 // F1 key
114
-	VK_F2       = 0x71 // F2 key
115
-	VK_F3       = 0x72 // F3 key
116
-	VK_F4       = 0x73 // F4 key
117
-	VK_F5       = 0x74 // F5 key
118
-	VK_F6       = 0x75 // F6 key
119
-	VK_F7       = 0x76 // F7 key
120
-	VK_F8       = 0x77 // F8 key
121
-	VK_F9       = 0x78 // F9 key
122
-	VK_F10      = 0x79 // F10 key
123
-	VK_F11      = 0x7A // F11 key
124
-	VK_F12      = 0x7B // F12 key
125
-)
126
-
127
-var kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
128
-
129
-var (
130
-	setConsoleModeProc                = kernel32DLL.NewProc("SetConsoleMode")
131
-	getConsoleScreenBufferInfoProc    = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
132
-	setConsoleCursorPositionProc      = kernel32DLL.NewProc("SetConsoleCursorPosition")
133
-	setConsoleTextAttributeProc       = kernel32DLL.NewProc("SetConsoleTextAttribute")
134
-	fillConsoleOutputCharacterProc    = kernel32DLL.NewProc("FillConsoleOutputCharacterW")
135
-	writeConsoleOutputProc            = kernel32DLL.NewProc("WriteConsoleOutputW")
136
-	readConsoleInputProc              = kernel32DLL.NewProc("ReadConsoleInputW")
137
-	getNumberOfConsoleInputEventsProc = kernel32DLL.NewProc("GetNumberOfConsoleInputEvents")
138
-	getConsoleCursorInfoProc          = kernel32DLL.NewProc("GetConsoleCursorInfo")
139
-	setConsoleCursorInfoProc          = kernel32DLL.NewProc("SetConsoleCursorInfo")
140
-	setConsoleWindowInfoProc          = kernel32DLL.NewProc("SetConsoleWindowInfo")
141
-	setConsoleScreenBufferSizeProc    = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
142
-)
143
-
144
-// types for calling various windows API
145
-// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx
146
-type (
147
-	SHORT int16
148
-	BOOL  int32
149
-	WORD  uint16
150
-	WCHAR uint16
151
-	DWORD uint32
152
-
153
-	SMALL_RECT struct {
154
-		Left   SHORT
155
-		Top    SHORT
156
-		Right  SHORT
157
-		Bottom SHORT
158
-	}
159
-
160
-	COORD struct {
161
-		X SHORT
162
-		Y SHORT
163
-	}
164
-
165
-	CONSOLE_SCREEN_BUFFER_INFO struct {
166
-		Size              COORD
167
-		CursorPosition    COORD
168
-		Attributes        WORD
169
-		Window            SMALL_RECT
170
-		MaximumWindowSize COORD
171
-	}
172
-
173
-	CONSOLE_CURSOR_INFO struct {
174
-		Size    DWORD
175
-		Visible BOOL
176
-	}
177
-
178
-	// http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx
179
-	KEY_EVENT_RECORD struct {
180
-		KeyDown         BOOL
181
-		RepeatCount     WORD
182
-		VirtualKeyCode  WORD
183
-		VirtualScanCode WORD
184
-		UnicodeChar     WCHAR
185
-		ControlKeyState DWORD
186
-	}
187
-
188
-	INPUT_RECORD struct {
189
-		EventType WORD
190
-		KeyEvent  KEY_EVENT_RECORD
191
-	}
192
-
193
-	CHAR_INFO struct {
194
-		UnicodeChar WCHAR
195
-		Attributes  WORD
196
-	}
197
-)
198
-
199
-// TODO(azlinux): Basic type clean-up
200
-// -- Convert all uses of uintptr to syscall.Handle to be consistent with Windows syscall
201
-// -- Convert, as appropriate, types to use defined Windows types (e.g., DWORD instead of uint32)
202
-
203
-// Implements the TerminalEmulator interface
204
-type WindowsTerminal struct {
205
-	outMutex            sync.Mutex
206
-	inMutex             sync.Mutex
207
-	inputBuffer         []byte
208
-	inputSize           int
209
-	inputEvents         []INPUT_RECORD
210
-	screenBufferInfo    *CONSOLE_SCREEN_BUFFER_INFO
211
-	inputEscapeSequence []byte
212
-}
213
-
214
-func getStdHandle(stdhandle int) uintptr {
215
-	handle, err := syscall.GetStdHandle(stdhandle)
216
-	if err != nil {
217
-		panic(fmt.Errorf("could not get standard io handle %d", stdhandle))
218
-	}
219
-	return uintptr(handle)
220
-}
221
-
222
-func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
223
-	handler := &WindowsTerminal{
224
-		inputBuffer:         make([]byte, MAX_INPUT_BUFFER),
225
-		inputEscapeSequence: []byte(KEY_ESC_CSI),
226
-		inputEvents:         make([]INPUT_RECORD, MAX_INPUT_EVENTS),
227
-	}
228
-
229
-	if IsConsole(os.Stdin.Fd()) {
230
-		stdIn = &terminalReader{
231
-			wrappedReader: os.Stdin,
232
-			emulator:      handler,
233
-			command:       make([]byte, 0, ANSI_MAX_CMD_LENGTH),
234
-			fd:            getStdHandle(syscall.STD_INPUT_HANDLE),
235
-		}
236
-	} else {
237
-		stdIn = os.Stdin
238
-	}
239
-
240
-	if IsConsole(os.Stdout.Fd()) {
241
-		stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE)
242
-
243
-		// Save current screen buffer info
244
-		screenBufferInfo, err := GetConsoleScreenBufferInfo(stdoutHandle)
245
-		if err != nil {
246
-			// If GetConsoleScreenBufferInfo returns a nil error, it usually means that stdout is not a TTY.
247
-			// However, this is in the branch where stdout is a TTY, hence the panic.
248
-			panic("could not get console screen buffer info")
249
-		}
250
-		handler.screenBufferInfo = screenBufferInfo
251
-
252
-		buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y)
253
-
254
-		stdOut = &terminalWriter{
255
-			wrappedWriter: os.Stdout,
256
-			emulator:      handler,
257
-			command:       make([]byte, 0, ANSI_MAX_CMD_LENGTH),
258
-			fd:            stdoutHandle,
259
-		}
260
-	} else {
261
-		stdOut = os.Stdout
262
-	}
263
-
264
-	if IsConsole(os.Stderr.Fd()) {
265
-		stdErr = &terminalWriter{
266
-			wrappedWriter: os.Stderr,
267
-			emulator:      handler,
268
-			command:       make([]byte, 0, ANSI_MAX_CMD_LENGTH),
269
-			fd:            getStdHandle(syscall.STD_ERROR_HANDLE),
270
-		}
271
-	} else {
272
-		stdErr = os.Stderr
273
-	}
274
-
275
-	return stdIn, stdOut, stdErr
276
-}
277
-
278
-// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
279
-func GetHandleInfo(in interface{}) (uintptr, bool) {
280
-	var inFd uintptr
281
-	var isTerminalIn bool
282
-
283
-	switch t := in.(type) {
284
-	case *terminalReader:
285
-		in = t.wrappedReader
286
-	case *terminalWriter:
287
-		in = t.wrappedWriter
288
-	}
289
-
290
-	if file, ok := in.(*os.File); ok {
291
-		inFd = file.Fd()
292
-		isTerminalIn = IsConsole(inFd)
293
-	}
294
-	return inFd, isTerminalIn
295
-}
296
-
297
-func getError(r1, r2 uintptr, lastErr error) error {
298
-	// If the function fails, the return value is zero.
299
-	if r1 == 0 {
300
-		if lastErr != nil {
301
-			return lastErr
302
-		}
303
-		return syscall.EINVAL
304
-	}
305
-	return nil
306
-}
307
-
308
-// GetConsoleMode gets the console mode for given file descriptor
309
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx
310
-func GetConsoleMode(handle uintptr) (uint32, error) {
311
-	var mode uint32
312
-	err := syscall.GetConsoleMode(syscall.Handle(handle), &mode)
313
-	return mode, err
314
-}
315
-
316
-// SetConsoleMode sets the console mode for given file descriptor
317
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
318
-func SetConsoleMode(handle uintptr, mode uint32) error {
319
-	return getError(setConsoleModeProc.Call(handle, uintptr(mode), 0))
320
-}
321
-
322
-// SetCursorVisible sets the cursor visbility
323
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx
324
-func SetCursorVisible(handle uintptr, isVisible BOOL) (bool, error) {
325
-	var cursorInfo *CONSOLE_CURSOR_INFO = &CONSOLE_CURSOR_INFO{}
326
-	if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
327
-		return false, err
328
-	}
329
-	cursorInfo.Visible = isVisible
330
-	if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil {
331
-		return false, err
332
-	}
333
-	return true, nil
334
-}
335
-
336
-// SetWindowSize sets the size of the console window.
337
-func SetWindowSize(handle uintptr, width, height, max SHORT) (bool, error) {
338
-	window := SMALL_RECT{Left: 0, Top: 0, Right: width - 1, Bottom: height - 1}
339
-	coord := COORD{X: width - 1, Y: max}
340
-	if err := getError(setConsoleWindowInfoProc.Call(handle, uintptr(1), uintptr(unsafe.Pointer(&window)))); err != nil {
341
-		return false, err
342
-	}
343
-	if err := getError(setConsoleScreenBufferSizeProc.Call(handle, marshal(coord))); err != nil {
344
-		return false, err
345
-	}
346
-	return true, nil
347
-}
348
-
349
-// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
350
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx
351
-func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
352
-	var info CONSOLE_SCREEN_BUFFER_INFO
353
-	if err := getError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0)); err != nil {
354
-		return nil, err
355
-	}
356
-	return &info, nil
357
-}
358
-
359
-// setConsoleTextAttribute sets the attributes of characters written to the
360
-// console screen buffer by the WriteFile or WriteConsole function,
361
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx
362
-func setConsoleTextAttribute(handle uintptr, attribute WORD) error {
363
-	return getError(setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0))
364
-}
365
-
366
-func writeConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) (bool, error) {
367
-	if err := getError(writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), marshal(bufferSize), marshal(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))); err != nil {
368
-		return false, err
369
-	}
370
-	return true, nil
371
-}
372
-
373
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms682663(v=vs.85).aspx
374
-func fillConsoleOutputCharacter(handle uintptr, fillChar byte, length uint32, writeCord COORD) (bool, error) {
375
-	out := int64(0)
376
-	if err := getError(fillConsoleOutputCharacterProc.Call(handle, uintptr(fillChar), uintptr(length), marshal(writeCord), uintptr(unsafe.Pointer(&out)))); err != nil {
377
-		return false, err
378
-	}
379
-	return true, nil
380
-}
381
-
382
-// Gets the number of space characters to write for "clearing" the section of terminal
383
-func getNumberOfChars(fromCoord COORD, toCoord COORD, screenSize COORD) uint32 {
384
-	// must be valid cursor position
385
-	if fromCoord.X < 0 || fromCoord.Y < 0 || toCoord.X < 0 || toCoord.Y < 0 {
386
-		return 0
387
-	}
388
-	if fromCoord.X >= screenSize.X || fromCoord.Y >= screenSize.Y || toCoord.X >= screenSize.X || toCoord.Y >= screenSize.Y {
389
-		return 0
390
-	}
391
-	// can't be backwards
392
-	if fromCoord.Y > toCoord.Y {
393
-		return 0
394
-	}
395
-	// same line
396
-	if fromCoord.Y == toCoord.Y {
397
-		return uint32(toCoord.X-fromCoord.X) + 1
398
-	}
399
-	// spans more than one line
400
-	if fromCoord.Y < toCoord.Y {
401
-		// from start till end of line for first line +  from start of line till end
402
-		retValue := uint32(screenSize.X-fromCoord.X) + uint32(toCoord.X) + 1
403
-		// don't count first and last line
404
-		linesBetween := toCoord.Y - fromCoord.Y - 1
405
-		if linesBetween > 0 {
406
-			retValue = retValue + uint32(linesBetween*screenSize.X)
407
-		}
408
-		return retValue
409
-	}
410
-	return 0
411
-}
412
-
413
-var buffer []CHAR_INFO
414
-
415
-func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
416
-	var writeRegion SMALL_RECT
417
-	writeRegion.Left = fromCoord.X
418
-	writeRegion.Top = fromCoord.Y
419
-	writeRegion.Right = toCoord.X
420
-	writeRegion.Bottom = toCoord.Y
421
-
422
-	// allocate and initialize buffer
423
-	width := toCoord.X - fromCoord.X + 1
424
-	height := toCoord.Y - fromCoord.Y + 1
425
-	size := uint32(width) * uint32(height)
426
-	if size > 0 {
427
-		buffer := make([]CHAR_INFO, size)
428
-		for i := range buffer {
429
-			buffer[i] = CHAR_INFO{WCHAR(' '), attributes}
430
-		}
431
-
432
-		// Write to buffer
433
-		r, err := writeConsoleOutput(handle, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &writeRegion)
434
-		if !r {
435
-			if err != nil {
436
-				return 0, err
437
-			}
438
-			return 0, syscall.EINVAL
439
-		}
440
-	}
441
-	return uint32(size), nil
442
-}
443
-
444
-func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) {
445
-	nw := uint32(0)
446
-	// start and end on same line
447
-	if fromCoord.Y == toCoord.Y {
448
-		return clearDisplayRect(handle, attributes, fromCoord, toCoord)
449
-	}
450
-	// TODO(azlinux): if full screen, optimize
451
-
452
-	// spans more than one line
453
-	if fromCoord.Y < toCoord.Y {
454
-		// from start position till end of line for first line
455
-		n, err := clearDisplayRect(handle, attributes, fromCoord, COORD{X: toCoord.X, Y: fromCoord.Y})
456
-		if err != nil {
457
-			return nw, err
458
-		}
459
-		nw += n
460
-		// lines between
461
-		linesBetween := toCoord.Y - fromCoord.Y - 1
462
-		if linesBetween > 0 {
463
-			n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: toCoord.X, Y: toCoord.Y - 1})
464
-			if err != nil {
465
-				return nw, err
466
-			}
467
-			nw += n
468
-		}
469
-		// lines at end
470
-		n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord)
471
-		if err != nil {
472
-			return nw, err
473
-		}
474
-		nw += n
475
-	}
476
-	return nw, nil
477
-}
478
-
479
-// setConsoleCursorPosition sets the console cursor position
480
-// Note The X and Y are zero based
481
-// If relative is true then the new position is relative to current one
482
-func setConsoleCursorPosition(handle uintptr, isRelative bool, column int16, line int16) error {
483
-	screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
484
-	if err != nil {
485
-		return err
486
-	}
487
-	var position COORD
488
-	if isRelative {
489
-		position.X = screenBufferInfo.CursorPosition.X + SHORT(column)
490
-		position.Y = screenBufferInfo.CursorPosition.Y + SHORT(line)
491
-	} else {
492
-		position.X = SHORT(column)
493
-		position.Y = SHORT(line)
494
-	}
495
-	return getError(setConsoleCursorPositionProc.Call(handle, marshal(position), 0))
496
-}
497
-
498
-// http://msdn.microsoft.com/en-us/library/windows/desktop/ms683207(v=vs.85).aspx
499
-func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) {
500
-	var n DWORD
501
-	if err := getError(getNumberOfConsoleInputEventsProc.Call(handle, uintptr(unsafe.Pointer(&n)))); err != nil {
502
-		return 0, err
503
-	}
504
-	return uint16(n), nil
505
-}
506
-
507
-//http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx
508
-func readConsoleInputKey(handle uintptr, inputBuffer []INPUT_RECORD) (int, error) {
509
-	var nr DWORD
510
-	if err := getError(readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&inputBuffer[0])), uintptr(len(inputBuffer)), uintptr(unsafe.Pointer(&nr)))); err != nil {
511
-		return 0, err
512
-	}
513
-	return int(nr), nil
514
-}
515
-
516
-func getWindowsTextAttributeForAnsiValue(originalFlag WORD, defaultValue WORD, ansiValue int16) (WORD, error) {
517
-	flag := WORD(originalFlag)
518
-	if flag == 0 {
519
-		flag = defaultValue
520
-	}
521
-	switch ansiValue {
522
-	case ANSI_ATTR_RESET:
523
-		flag &^= COMMON_LVB_UNDERSCORE
524
-		flag &^= BACKGROUND_INTENSITY
525
-		flag = flag | FOREGROUND_INTENSITY
526
-	case ANSI_ATTR_INVISIBLE:
527
-		// TODO: how do you reset reverse?
528
-	case ANSI_ATTR_UNDERLINE:
529
-		flag = flag | COMMON_LVB_UNDERSCORE
530
-	case ANSI_ATTR_BLINK:
531
-		// seems like background intenisty is blink
532
-		flag = flag | BACKGROUND_INTENSITY
533
-	case ANSI_ATTR_UNDERLINE_OFF:
534
-		flag &^= COMMON_LVB_UNDERSCORE
535
-	case ANSI_ATTR_BLINK_OFF:
536
-		// seems like background intenisty is blink
537
-		flag &^= BACKGROUND_INTENSITY
538
-	case ANSI_ATTR_BOLD:
539
-		flag = flag | FOREGROUND_INTENSITY
540
-	case ANSI_ATTR_DIM:
541
-		flag &^= FOREGROUND_INTENSITY
542
-	case ANSI_ATTR_REVERSE, ANSI_ATTR_REVERSE_OFF:
543
-		// swap forground and background bits
544
-		foreground := flag & FOREGROUND_MASK_SET
545
-		background := flag & BACKGROUND_MASK_SET
546
-		flag = (flag & BACKGROUND_MASK_UNSET & FOREGROUND_MASK_UNSET) | (foreground << 4) | (background >> 4)
547
-
548
-	// FOREGROUND
549
-	case ANSI_FOREGROUND_DEFAULT:
550
-		flag = (flag & FOREGROUND_MASK_UNSET) | (defaultValue & FOREGROUND_MASK_SET)
551
-	case ANSI_FOREGROUND_BLACK:
552
-		flag = flag ^ (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
553
-	case ANSI_FOREGROUND_RED:
554
-		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED
555
-	case ANSI_FOREGROUND_GREEN:
556
-		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_GREEN
557
-	case ANSI_FOREGROUND_YELLOW:
558
-		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_GREEN
559
-	case ANSI_FOREGROUND_BLUE:
560
-		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_BLUE
561
-	case ANSI_FOREGROUND_MAGENTA:
562
-		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_BLUE
563
-	case ANSI_FOREGROUND_CYAN:
564
-		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_GREEN | FOREGROUND_BLUE
565
-	case ANSI_FOREGROUND_WHITE:
566
-		flag = (flag & FOREGROUND_MASK_UNSET) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
567
-
568
-	// Background
569
-	case ANSI_BACKGROUND_DEFAULT:
570
-		// Black with no intensity
571
-		flag = (flag & BACKGROUND_MASK_UNSET) | (defaultValue & BACKGROUND_MASK_SET)
572
-	case ANSI_BACKGROUND_BLACK:
573
-		flag = (flag & BACKGROUND_MASK_UNSET)
574
-	case ANSI_BACKGROUND_RED:
575
-		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED
576
-	case ANSI_BACKGROUND_GREEN:
577
-		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_GREEN
578
-	case ANSI_BACKGROUND_YELLOW:
579
-		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_GREEN
580
-	case ANSI_BACKGROUND_BLUE:
581
-		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_BLUE
582
-	case ANSI_BACKGROUND_MAGENTA:
583
-		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_BLUE
584
-	case ANSI_BACKGROUND_CYAN:
585
-		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_GREEN | BACKGROUND_BLUE
586
-	case ANSI_BACKGROUND_WHITE:
587
-		flag = (flag & BACKGROUND_MASK_UNSET) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
588
-	}
589
-	return flag, nil
590
-}
591
-
592
-// HandleOutputCommand interpretes the Ansi commands and then makes appropriate Win32 calls
593
-func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) (n int, err error) {
594
-	// always consider all the bytes in command, processed
595
-	n = len(command)
596
-
597
-	parsedCommand := parseAnsiCommand(command)
598
-	logrus.Debugf("[windows] HandleOutputCommand: %v", parsedCommand)
599
-
600
-	// console settings changes need to happen in atomic way
601
-	term.outMutex.Lock()
602
-	defer term.outMutex.Unlock()
603
-
604
-	switch parsedCommand.Command {
605
-	case "m":
606
-		// [Value;...;Valuem
607
-		// Set Graphics Mode:
608
-		// Calls the graphics functions specified by the following values.
609
-		// These specified functions remain active until the next occurrence of this escape sequence.
610
-		// Graphics mode changes the colors and attributes of text (such as bold and underline) displayed on the screen.
611
-		screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
612
-		if err != nil {
613
-			return n, err
614
-		}
615
-		flag := screenBufferInfo.Attributes
616
-		for _, e := range parsedCommand.Parameters {
617
-			value, _ := strconv.ParseInt(e, 10, 16) // base 10, 16 bit
618
-			if value == ANSI_ATTR_RESET {
619
-				flag = term.screenBufferInfo.Attributes // reset
620
-			} else {
621
-				flag, err = getWindowsTextAttributeForAnsiValue(flag, term.screenBufferInfo.Attributes, int16(value))
622
-				if err != nil {
623
-					return n, err
624
-				}
625
-			}
626
-		}
627
-		if err := setConsoleTextAttribute(handle, flag); err != nil {
628
-			return n, err
629
-		}
630
-	case "H", "f":
631
-		// [line;columnH
632
-		// [line;columnf
633
-		// Moves the cursor to the specified position (coordinates).
634
-		// If you do not specify a position, the cursor moves to the home position at the upper-left corner of the screen (line 0, column 0).
635
-		screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
636
-		if err != nil {
637
-			return n, err
638
-		}
639
-		line, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
640
-		if err != nil {
641
-			return n, err
642
-		}
643
-		if line > int16(screenBufferInfo.Window.Bottom) {
644
-			line = int16(screenBufferInfo.Window.Bottom) + 1
645
-		}
646
-		column, err := parseInt16OrDefault(parsedCommand.getParam(1), 1)
647
-		if err != nil {
648
-			return n, err
649
-		}
650
-		if column > int16(screenBufferInfo.Window.Right) {
651
-			column = int16(screenBufferInfo.Window.Right) + 1
652
-		}
653
-		// The numbers are not 0 based, but 1 based
654
-		logrus.Debugf("[windows] HandleOutputCommmand: Moving cursor to (%v,%v)", column-1, line-1)
655
-		if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil {
656
-			return n, err
657
-		}
658
-
659
-	case "A":
660
-		// [valueA
661
-		// Moves the cursor up by the specified number of lines without changing columns.
662
-		// If the cursor is already on the top line, ignores this sequence.
663
-		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
664
-		if err != nil {
665
-			return len(command), err
666
-		}
667
-		if err := setConsoleCursorPosition(handle, true, 0, -value); err != nil {
668
-			return n, err
669
-		}
670
-	case "B":
671
-		// [valueB
672
-		// Moves the cursor down by the specified number of lines without changing columns.
673
-		// If the cursor is already on the bottom line, ignores this sequence.
674
-		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
675
-		if err != nil {
676
-			return n, err
677
-		}
678
-		if err := setConsoleCursorPosition(handle, true, 0, value); err != nil {
679
-			return n, err
680
-		}
681
-	case "C":
682
-		// [valueC
683
-		// Moves the cursor forward by the specified number of columns without changing lines.
684
-		// If the cursor is already in the rightmost column, ignores this sequence.
685
-		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
686
-		if err != nil {
687
-			return n, err
688
-		}
689
-		if err := setConsoleCursorPosition(handle, true, value, 0); err != nil {
690
-			return n, err
691
-		}
692
-	case "D":
693
-		// [valueD
694
-		// Moves the cursor back by the specified number of columns without changing lines.
695
-		// If the cursor is already in the leftmost column, ignores this sequence.
696
-		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 1)
697
-		if err != nil {
698
-			return n, err
699
-		}
700
-		if err := setConsoleCursorPosition(handle, true, -value, 0); err != nil {
701
-			return n, err
702
-		}
703
-	case "J":
704
-		// [J   Erases from the cursor to the end of the screen, including the cursor position.
705
-		// [1J  Erases from the beginning of the screen to the cursor, including the cursor position.
706
-		// [2J  Erases the complete display. The cursor does not move.
707
-		// Clears the screen and moves the cursor to the home position (line 0, column 0).
708
-		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 0)
709
-		if err != nil {
710
-			return n, err
711
-		}
712
-		var start COORD
713
-		var cursor COORD
714
-		var end COORD
715
-		screenBufferInfo, err := GetConsoleScreenBufferInfo(handle)
716
-		if err != nil {
717
-			return n, err
718
-		}
719
-		switch value {
720
-		case 0:
721
-			start = screenBufferInfo.CursorPosition
722
-			// end of the buffer
723
-			end.X = screenBufferInfo.Size.X - 1
724
-			end.Y = screenBufferInfo.Size.Y - 1
725
-			// cursor
726
-			cursor = screenBufferInfo.CursorPosition
727
-		case 1:
728
-
729
-			// start of the screen
730
-			start.X = 0
731
-			start.Y = 0
732
-			// end of the screen
733
-			end = screenBufferInfo.CursorPosition
734
-			// cursor
735
-			cursor = screenBufferInfo.CursorPosition
736
-		case 2:
737
-			// start of the screen
738
-			start.X = 0
739
-			start.Y = 0
740
-			// end of the buffer
741
-			end.X = screenBufferInfo.Size.X - 1
742
-			end.Y = screenBufferInfo.Size.Y - 1
743
-			// cursor
744
-			cursor.X = 0
745
-			cursor.Y = 0
746
-		}
747
-		if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
748
-			return n, err
749
-		}
750
-		// remember the the cursor position is 1 based
751
-		if err := setConsoleCursorPosition(handle, false, int16(cursor.X), int16(cursor.Y)); err != nil {
752
-			return n, err
753
-		}
754
-
755
-	case "K":
756
-		// [K
757
-		// Clears all characters from the cursor position to the end of the line (including the character at the cursor position).
758
-		// [K  Erases from the cursor to the end of the line, including the cursor position.
759
-		// [1K  Erases from the beginning of the line to the cursor, including the cursor position.
760
-		// [2K  Erases the complete line.
761
-		value, err := parseInt16OrDefault(parsedCommand.getParam(0), 0)
762
-		var start COORD
763
-		var cursor COORD
764
-		var end COORD
765
-		screenBufferInfo, err := GetConsoleScreenBufferInfo(uintptr(handle))
766
-		if err != nil {
767
-			return n, err
768
-		}
769
-		switch value {
770
-		case 0:
771
-			// start is where cursor is
772
-			start = screenBufferInfo.CursorPosition
773
-			// end of line
774
-			end.X = screenBufferInfo.Size.X - 1
775
-			end.Y = screenBufferInfo.CursorPosition.Y
776
-			// cursor remains the same
777
-			cursor = screenBufferInfo.CursorPosition
778
-
779
-		case 1:
780
-			// beginning of line
781
-			start.X = 0
782
-			start.Y = screenBufferInfo.CursorPosition.Y
783
-			// until cursor
784
-			end = screenBufferInfo.CursorPosition
785
-			// cursor remains the same
786
-			cursor = screenBufferInfo.CursorPosition
787
-		case 2:
788
-			// start of the line
789
-			start.X = 0
790
-			start.Y = screenBufferInfo.CursorPosition.Y - 1
791
-			// end of the line
792
-			end.X = screenBufferInfo.Size.X - 1
793
-			end.Y = screenBufferInfo.CursorPosition.Y - 1
794
-			// cursor
795
-			cursor.X = 0
796
-			cursor.Y = screenBufferInfo.CursorPosition.Y - 1
797
-		}
798
-		if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil {
799
-			return n, err
800
-		}
801
-		// remember the the cursor position is 1 based
802
-		if err := setConsoleCursorPosition(uintptr(handle), false, int16(cursor.X), int16(cursor.Y)); err != nil {
803
-			return n, err
804
-		}
805
-
806
-	case "l":
807
-		for _, value := range parsedCommand.Parameters {
808
-			switch value {
809
-			case "?25", "25":
810
-				SetCursorVisible(uintptr(handle), BOOL(0))
811
-			case "?1049", "1049":
812
-				// TODO (azlinux):  Restore terminal
813
-			case "?1", "1":
814
-				// If the DECCKM function is reset, then the arrow keys send ANSI cursor sequences to the host.
815
-				term.inputEscapeSequence = []byte(KEY_ESC_CSI)
816
-			}
817
-		}
818
-	case "h":
819
-		for _, value := range parsedCommand.Parameters {
820
-			switch value {
821
-			case "?25", "25":
822
-				SetCursorVisible(uintptr(handle), BOOL(1))
823
-			case "?1049", "1049":
824
-				// TODO (azlinux): Save terminal
825
-			case "?1", "1":
826
-				// If the DECCKM function is set, then the arrow keys send application sequences to the host.
827
-				// DECCKM (default off): When set, the cursor keys send an ESC O prefix, rather than ESC [.
828
-				term.inputEscapeSequence = []byte(KEY_ESC_O)
829
-			}
830
-		}
831
-
832
-	case "]":
833
-		/*
834
-			TODO (azlinux):
835
-				Linux Console Private CSI Sequences
836
-
837
-			       The following sequences are neither ECMA-48 nor native VT102.  They are
838
-			       native  to the Linux console driver.  Colors are in SGR parameters: 0 =
839
-			       black, 1 = red, 2 = green, 3 = brown, 4 = blue, 5 = magenta, 6 =  cyan,
840
-			       7 = white.
841
-
842
-			       ESC [ 1 ; n ]       Set color n as the underline color
843
-			       ESC [ 2 ; n ]       Set color n as the dim color
844
-			       ESC [ 8 ]           Make the current color pair the default attributes.
845
-			       ESC [ 9 ; n ]       Set screen blank timeout to n minutes.
846
-			       ESC [ 10 ; n ]      Set bell frequency in Hz.
847
-			       ESC [ 11 ; n ]      Set bell duration in msec.
848
-			       ESC [ 12 ; n ]      Bring specified console to the front.
849
-			       ESC [ 13 ]          Unblank the screen.
850
-			       ESC [ 14 ; n ]      Set the VESA powerdown interval in minutes.
851
-
852
-		*/
853
-	}
854
-	return n, nil
855
-}
856
-
857
-// WriteChars writes the bytes to given writer.
858
-func (term *WindowsTerminal) WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error) {
859
-	if len(p) == 0 {
860
-		return 0, nil
861
-	}
862
-	return w.Write(p)
863
-}
864
-
865
-const (
866
-	CAPSLOCK_ON        = 0x0080 //The CAPS LOCK light is on.
867
-	ENHANCED_KEY       = 0x0100 //The key is enhanced.
868
-	LEFT_ALT_PRESSED   = 0x0002 //The left ALT key is pressed.
869
-	LEFT_CTRL_PRESSED  = 0x0008 //The left CTRL key is pressed.
870
-	NUMLOCK_ON         = 0x0020 //The NUM LOCK light is on.
871
-	RIGHT_ALT_PRESSED  = 0x0001 //The right ALT key is pressed.
872
-	RIGHT_CTRL_PRESSED = 0x0004 //The right CTRL key is pressed.
873
-	SCROLLLOCK_ON      = 0x0040 //The SCROLL LOCK light is on.
874
-	SHIFT_PRESSED      = 0x0010 // The SHIFT key is pressed.
875
-)
876
-
877
-const (
878
-	KEY_CONTROL_PARAM_2 = ";2"
879
-	KEY_CONTROL_PARAM_3 = ";3"
880
-	KEY_CONTROL_PARAM_4 = ";4"
881
-	KEY_CONTROL_PARAM_5 = ";5"
882
-	KEY_CONTROL_PARAM_6 = ";6"
883
-	KEY_CONTROL_PARAM_7 = ";7"
884
-	KEY_CONTROL_PARAM_8 = ";8"
885
-	KEY_ESC_CSI         = "\x1B["
886
-	KEY_ESC_N           = "\x1BN"
887
-	KEY_ESC_O           = "\x1BO"
888
-)
889
-
890
-var keyMapPrefix = map[WORD]string{
891
-	VK_UP:     "\x1B[%sA",
892
-	VK_DOWN:   "\x1B[%sB",
893
-	VK_RIGHT:  "\x1B[%sC",
894
-	VK_LEFT:   "\x1B[%sD",
895
-	VK_HOME:   "\x1B[1%s~", // showkey shows ^[[1
896
-	VK_END:    "\x1B[4%s~", // showkey shows ^[[4
897
-	VK_INSERT: "\x1B[2%s~",
898
-	VK_DELETE: "\x1B[3%s~",
899
-	VK_PRIOR:  "\x1B[5%s~",
900
-	VK_NEXT:   "\x1B[6%s~",
901
-	VK_F1:     "",
902
-	VK_F2:     "",
903
-	VK_F3:     "\x1B[13%s~",
904
-	VK_F4:     "\x1B[14%s~",
905
-	VK_F5:     "\x1B[15%s~",
906
-	VK_F6:     "\x1B[17%s~",
907
-	VK_F7:     "\x1B[18%s~",
908
-	VK_F8:     "\x1B[19%s~",
909
-	VK_F9:     "\x1B[20%s~",
910
-	VK_F10:    "\x1B[21%s~",
911
-	VK_F11:    "\x1B[23%s~",
912
-	VK_F12:    "\x1B[24%s~",
913
-}
914
-
915
-var arrowKeyMapPrefix = map[WORD]string{
916
-	VK_UP:    "%s%sA",
917
-	VK_DOWN:  "%s%sB",
918
-	VK_RIGHT: "%s%sC",
919
-	VK_LEFT:  "%s%sD",
920
-}
921
-
922
-func getControlStateParameter(shift, alt, control, meta bool) string {
923
-	if shift && alt && control {
924
-		return KEY_CONTROL_PARAM_8
925
-	}
926
-	if alt && control {
927
-		return KEY_CONTROL_PARAM_7
928
-	}
929
-	if shift && control {
930
-		return KEY_CONTROL_PARAM_6
931
-	}
932
-	if control {
933
-		return KEY_CONTROL_PARAM_5
934
-	}
935
-	if shift && alt {
936
-		return KEY_CONTROL_PARAM_4
937
-	}
938
-	if alt {
939
-		return KEY_CONTROL_PARAM_3
940
-	}
941
-	if shift {
942
-		return KEY_CONTROL_PARAM_2
943
-	}
944
-	return ""
945
-}
946
-
947
-func getControlKeys(controlState DWORD) (shift, alt, control bool) {
948
-	shift = 0 != (controlState & SHIFT_PRESSED)
949
-	alt = 0 != (controlState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
950
-	control = 0 != (controlState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
951
-	return shift, alt, control
952
-}
953
-
954
-func charSequenceForKeys(key WORD, controlState DWORD, escapeSequence []byte) string {
955
-	i, ok := arrowKeyMapPrefix[key]
956
-	if ok {
957
-		shift, alt, control := getControlKeys(controlState)
958
-		modifier := getControlStateParameter(shift, alt, control, false)
959
-		return fmt.Sprintf(i, escapeSequence, modifier)
960
-	}
961
-
962
-	i, ok = keyMapPrefix[key]
963
-	if ok {
964
-		shift, alt, control := getControlKeys(controlState)
965
-		modifier := getControlStateParameter(shift, alt, control, false)
966
-		return fmt.Sprintf(i, modifier)
967
-	}
968
-
969
-	return ""
970
-}
971
-
972
-// mapKeystokeToTerminalString maps the given input event record to string
973
-func mapKeystokeToTerminalString(keyEvent *KEY_EVENT_RECORD, escapeSequence []byte) string {
974
-	_, alt, control := getControlKeys(keyEvent.ControlKeyState)
975
-	if keyEvent.UnicodeChar == 0 {
976
-		return charSequenceForKeys(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
977
-	}
978
-	if control {
979
-		// TODO(azlinux): Implement following control sequences
980
-		// <Ctrl>-D  Signals the end of input from the keyboard; also exits current shell.
981
-		// <Ctrl>-H  Deletes the first character to the left of the cursor. Also called the ERASE key.
982
-		// <Ctrl>-Q  Restarts printing after it has been stopped with <Ctrl>-s.
983
-		// <Ctrl>-S  Suspends printing on the screen (does not stop the program).
984
-		// <Ctrl>-U  Deletes all characters on the current line. Also called the KILL key.
985
-		// <Ctrl>-E  Quits current command and creates a core
986
-
987
-	}
988
-	// <Alt>+Key generates ESC N Key
989
-	if !control && alt {
990
-		return KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
991
-	}
992
-	return string(keyEvent.UnicodeChar)
993
-}
994
-
995
-// getAvailableInputEvents polls the console for availble events
996
-// The function does not return until at least one input record has been read.
997
-func getAvailableInputEvents(handle uintptr, inputEvents []INPUT_RECORD) (n int, err error) {
998
-	// TODO(azlinux): Why is there a for loop? Seems to me, that `n` cannot be negative. - tibor
999
-	for {
1000
-		// Read number of console events available
1001
-		n, err = readConsoleInputKey(handle, inputEvents)
1002
-		if err != nil || n >= 0 {
1003
-			return n, err
1004
-		}
1005
-	}
1006
-}
1007
-
1008
-// getTranslatedKeyCodes converts the input events into the string of characters
1009
-// The ansi escape sequence are used to map key strokes to the strings
1010
-func getTranslatedKeyCodes(inputEvents []INPUT_RECORD, escapeSequence []byte) string {
1011
-	var buf bytes.Buffer
1012
-	for i := 0; i < len(inputEvents); i++ {
1013
-		input := inputEvents[i]
1014
-		if input.EventType == KEY_EVENT && input.KeyEvent.KeyDown != 0 {
1015
-			keyString := mapKeystokeToTerminalString(&input.KeyEvent, escapeSequence)
1016
-			buf.WriteString(keyString)
1017
-		}
1018
-	}
1019
-	return buf.String()
1020
-}
1021
-
1022
-// ReadChars reads the characters from the given reader
1023
-func (term *WindowsTerminal) ReadChars(fd uintptr, r io.Reader, p []byte) (n int, err error) {
1024
-	for term.inputSize == 0 {
1025
-		nr, err := getAvailableInputEvents(fd, term.inputEvents)
1026
-		if nr == 0 && nil != err {
1027
-			return n, err
1028
-		}
1029
-		if nr > 0 {
1030
-			keyCodes := getTranslatedKeyCodes(term.inputEvents[:nr], term.inputEscapeSequence)
1031
-			term.inputSize = copy(term.inputBuffer, keyCodes)
1032
-		}
1033
-	}
1034
-	n = copy(p, term.inputBuffer[:term.inputSize])
1035
-	term.inputSize -= n
1036
-	return n, nil
1037
-}
1038
-
1039
-// HandleInputSequence interprets the input sequence command
1040
-func (term *WindowsTerminal) HandleInputSequence(fd uintptr, command []byte) (n int, err error) {
1041
-	return 0, nil
1042
-}
1043
-
1044
-func marshal(c COORD) uintptr {
1045
-	return uintptr(*((*DWORD)(unsafe.Pointer(&c))))
1046
-}
1047
-
1048
-// IsConsole returns true if the given file descriptor is a terminal.
1049
-// -- The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
1050
-func IsConsole(fd uintptr) bool {
1051
-	_, e := GetConsoleMode(fd)
1052
-	return e == nil
1053
-}
1054 1
deleted file mode 100644
... ...
@@ -1,232 +0,0 @@
1
-// +build windows
2
-
3
-package winconsole
4
-
5
-import (
6
-	"fmt"
7
-	"testing"
8
-)
9
-
10
-func helpsTestParseInt16OrDefault(t *testing.T, expectedValue int16, shouldFail bool, input string, defaultValue int16, format string, args ...string) {
11
-	value, err := parseInt16OrDefault(input, defaultValue)
12
-	if nil != err && !shouldFail {
13
-		t.Errorf("Unexpected error returned %v", err)
14
-		t.Errorf(format, args)
15
-	}
16
-	if nil == err && shouldFail {
17
-		t.Errorf("Should have failed as expected\n\tReturned value = %d", value)
18
-		t.Errorf(format, args)
19
-	}
20
-	if expectedValue != value {
21
-		t.Errorf("The value returned does not match expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value)
22
-		t.Errorf(format, args)
23
-	}
24
-}
25
-
26
-func TestParseInt16OrDefault(t *testing.T) {
27
-	// empty string
28
-	helpsTestParseInt16OrDefault(t, 0, false, "", 0, "Empty string returns default")
29
-	helpsTestParseInt16OrDefault(t, 2, false, "", 2, "Empty string returns default")
30
-
31
-	// normal case
32
-	helpsTestParseInt16OrDefault(t, 0, false, "0", 0, "0 handled correctly")
33
-	helpsTestParseInt16OrDefault(t, 111, false, "111", 2, "Normal")
34
-	helpsTestParseInt16OrDefault(t, 111, false, "+111", 2, "+N")
35
-	helpsTestParseInt16OrDefault(t, -111, false, "-111", 2, "-N")
36
-	helpsTestParseInt16OrDefault(t, 0, false, "+0", 11, "+0")
37
-	helpsTestParseInt16OrDefault(t, 0, false, "-0", 12, "-0")
38
-
39
-	// ill formed strings
40
-	helpsTestParseInt16OrDefault(t, 0, true, "abc", 0, "Invalid string")
41
-	helpsTestParseInt16OrDefault(t, 42, true, "+= 23", 42, "Invalid string")
42
-	helpsTestParseInt16OrDefault(t, 42, true, "123.45", 42, "float like")
43
-
44
-}
45
-
46
-func helpsTestGetNumberOfChars(t *testing.T, expected uint32, fromCoord COORD, toCoord COORD, screenSize COORD, format string, args ...interface{}) {
47
-	actual := getNumberOfChars(fromCoord, toCoord, screenSize)
48
-	mesg := fmt.Sprintf(format, args)
49
-	assertTrue(t, expected == actual, fmt.Sprintf("%s Expected=%d, Actual=%d, Parameters = { fromCoord=%+v, toCoord=%+v, screenSize=%+v", mesg, expected, actual, fromCoord, toCoord, screenSize))
50
-}
51
-
52
-func TestGetNumberOfChars(t *testing.T) {
53
-	// Note: The columns and lines are 0 based
54
-	// Also that interval is "inclusive" means will have both start and end chars
55
-	// This test only tests the number opf characters being written
56
-
57
-	// all four corners
58
-	maxWindow := COORD{X: 80, Y: 50}
59
-	leftTop := COORD{X: 0, Y: 0}
60
-	rightTop := COORD{X: 79, Y: 0}
61
-	leftBottom := COORD{X: 0, Y: 49}
62
-	rightBottom := COORD{X: 79, Y: 49}
63
-
64
-	// same position
65
-	helpsTestGetNumberOfChars(t, 1, COORD{X: 1, Y: 14}, COORD{X: 1, Y: 14}, COORD{X: 80, Y: 50}, "Same position random line")
66
-
67
-	// four corners
68
-	helpsTestGetNumberOfChars(t, 1, leftTop, leftTop, maxWindow, "Same position- leftTop")
69
-	helpsTestGetNumberOfChars(t, 1, rightTop, rightTop, maxWindow, "Same position- rightTop")
70
-	helpsTestGetNumberOfChars(t, 1, leftBottom, leftBottom, maxWindow, "Same position- leftBottom")
71
-	helpsTestGetNumberOfChars(t, 1, rightBottom, rightBottom, maxWindow, "Same position- rightBottom")
72
-
73
-	// from this char to next char on same line
74
-	helpsTestGetNumberOfChars(t, 2, COORD{X: 0, Y: 0}, COORD{X: 1, Y: 0}, maxWindow, "Next position on same line")
75
-	helpsTestGetNumberOfChars(t, 2, COORD{X: 1, Y: 14}, COORD{X: 2, Y: 14}, maxWindow, "Next position on same line")
76
-
77
-	// from this char to next 10 chars on same line
78
-	helpsTestGetNumberOfChars(t, 11, COORD{X: 0, Y: 0}, COORD{X: 10, Y: 0}, maxWindow, "Next position on same line")
79
-	helpsTestGetNumberOfChars(t, 11, COORD{X: 1, Y: 14}, COORD{X: 11, Y: 14}, maxWindow, "Next position on same line")
80
-
81
-	helpsTestGetNumberOfChars(t, 5, COORD{X: 3, Y: 11}, COORD{X: 7, Y: 11}, maxWindow, "To and from on same line")
82
-
83
-	helpsTestGetNumberOfChars(t, 8, COORD{X: 0, Y: 34}, COORD{X: 7, Y: 34}, maxWindow, "Start of line to middle")
84
-	helpsTestGetNumberOfChars(t, 4, COORD{X: 76, Y: 34}, COORD{X: 79, Y: 34}, maxWindow, "Middle to end of line")
85
-
86
-	// multiple lines - 1
87
-	helpsTestGetNumberOfChars(t, 81, COORD{X: 0, Y: 0}, COORD{X: 0, Y: 1}, maxWindow, "one line below same X")
88
-	helpsTestGetNumberOfChars(t, 81, COORD{X: 10, Y: 10}, COORD{X: 10, Y: 11}, maxWindow, "one line below same X")
89
-
90
-	// multiple lines - 2
91
-	helpsTestGetNumberOfChars(t, 161, COORD{X: 0, Y: 0}, COORD{X: 0, Y: 2}, maxWindow, "one line below same X")
92
-	helpsTestGetNumberOfChars(t, 161, COORD{X: 10, Y: 10}, COORD{X: 10, Y: 12}, maxWindow, "one line below same X")
93
-
94
-	// multiple lines - 3
95
-	helpsTestGetNumberOfChars(t, 241, COORD{X: 0, Y: 0}, COORD{X: 0, Y: 3}, maxWindow, "one line below same X")
96
-	helpsTestGetNumberOfChars(t, 241, COORD{X: 10, Y: 10}, COORD{X: 10, Y: 13}, maxWindow, "one line below same X")
97
-
98
-	// full line
99
-	helpsTestGetNumberOfChars(t, 80, COORD{X: 0, Y: 0}, COORD{X: 79, Y: 0}, maxWindow, "Full line - first")
100
-	helpsTestGetNumberOfChars(t, 80, COORD{X: 0, Y: 23}, COORD{X: 79, Y: 23}, maxWindow, "Full line - random")
101
-	helpsTestGetNumberOfChars(t, 80, COORD{X: 0, Y: 49}, COORD{X: 79, Y: 49}, maxWindow, "Full line - last")
102
-
103
-	// full screen
104
-	helpsTestGetNumberOfChars(t, 80*50, leftTop, rightBottom, maxWindow, "full screen")
105
-
106
-	helpsTestGetNumberOfChars(t, 80*50-1, COORD{X: 1, Y: 0}, rightBottom, maxWindow, "dropping first char to, end of screen")
107
-	helpsTestGetNumberOfChars(t, 80*50-2, COORD{X: 2, Y: 0}, rightBottom, maxWindow, "dropping first two char to, end of screen")
108
-
109
-	helpsTestGetNumberOfChars(t, 80*50-1, leftTop, COORD{X: 78, Y: 49}, maxWindow, "from start of screen, till last char-1")
110
-	helpsTestGetNumberOfChars(t, 80*50-2, leftTop, COORD{X: 77, Y: 49}, maxWindow, "from start of screen, till last char-2")
111
-
112
-	helpsTestGetNumberOfChars(t, 80*50-5, COORD{X: 4, Y: 0}, COORD{X: 78, Y: 49}, COORD{X: 80, Y: 50}, "from start of screen+4, till last char-1")
113
-	helpsTestGetNumberOfChars(t, 80*50-6, COORD{X: 4, Y: 0}, COORD{X: 77, Y: 49}, COORD{X: 80, Y: 50}, "from start of screen+4, till last char-2")
114
-}
115
-
116
-var allForeground = []int16{
117
-	ANSI_FOREGROUND_BLACK,
118
-	ANSI_FOREGROUND_RED,
119
-	ANSI_FOREGROUND_GREEN,
120
-	ANSI_FOREGROUND_YELLOW,
121
-	ANSI_FOREGROUND_BLUE,
122
-	ANSI_FOREGROUND_MAGENTA,
123
-	ANSI_FOREGROUND_CYAN,
124
-	ANSI_FOREGROUND_WHITE,
125
-	ANSI_FOREGROUND_DEFAULT,
126
-}
127
-var allBackground = []int16{
128
-	ANSI_BACKGROUND_BLACK,
129
-	ANSI_BACKGROUND_RED,
130
-	ANSI_BACKGROUND_GREEN,
131
-	ANSI_BACKGROUND_YELLOW,
132
-	ANSI_BACKGROUND_BLUE,
133
-	ANSI_BACKGROUND_MAGENTA,
134
-	ANSI_BACKGROUND_CYAN,
135
-	ANSI_BACKGROUND_WHITE,
136
-	ANSI_BACKGROUND_DEFAULT,
137
-}
138
-
139
-func maskForeground(flag WORD) WORD {
140
-	return flag & FOREGROUND_MASK_UNSET
141
-}
142
-
143
-func onlyForeground(flag WORD) WORD {
144
-	return flag & FOREGROUND_MASK_SET
145
-}
146
-
147
-func maskBackground(flag WORD) WORD {
148
-	return flag & BACKGROUND_MASK_UNSET
149
-}
150
-
151
-func onlyBackground(flag WORD) WORD {
152
-	return flag & BACKGROUND_MASK_SET
153
-}
154
-
155
-func helpsTestGetWindowsTextAttributeForAnsiValue(t *testing.T, oldValue WORD /*, expected WORD*/, ansi int16, onlyMask WORD, restMask WORD) WORD {
156
-	actual, err := getWindowsTextAttributeForAnsiValue(oldValue, FOREGROUND_MASK_SET, ansi)
157
-	assertTrue(t, nil == err, "Should be no error")
158
-	// assert that other bits are not affected
159
-	if 0 != oldValue {
160
-		assertTrue(t, (actual&restMask) == (oldValue&restMask), "The operation should not have affected other bits actual=%X oldValue=%X ansi=%d", actual, oldValue, ansi)
161
-	}
162
-	return actual
163
-}
164
-
165
-func TestBackgroundForAnsiValue(t *testing.T) {
166
-	// Check that nothing else changes
167
-	// background changes
168
-	for _, state1 := range allBackground {
169
-		for _, state2 := range allBackground {
170
-			flag := WORD(0)
171
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
172
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
173
-		}
174
-	}
175
-	// cummulative bcakground changes
176
-	for _, state1 := range allBackground {
177
-		flag := WORD(0)
178
-		for _, state2 := range allBackground {
179
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
180
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
181
-		}
182
-	}
183
-	// change background after foreground
184
-	for _, state1 := range allForeground {
185
-		for _, state2 := range allBackground {
186
-			flag := WORD(0)
187
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
188
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
189
-		}
190
-	}
191
-	// change background after change cumulative
192
-	for _, state1 := range allForeground {
193
-		flag := WORD(0)
194
-		for _, state2 := range allBackground {
195
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
196
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
197
-		}
198
-	}
199
-}
200
-
201
-func TestForegroundForAnsiValue(t *testing.T) {
202
-	// Check that nothing else changes
203
-	for _, state1 := range allForeground {
204
-		for _, state2 := range allForeground {
205
-			flag := WORD(0)
206
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
207
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
208
-		}
209
-	}
210
-
211
-	for _, state1 := range allForeground {
212
-		flag := WORD(0)
213
-		for _, state2 := range allForeground {
214
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
215
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
216
-		}
217
-	}
218
-	for _, state1 := range allBackground {
219
-		for _, state2 := range allForeground {
220
-			flag := WORD(0)
221
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
222
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
223
-		}
224
-	}
225
-	for _, state1 := range allBackground {
226
-		flag := WORD(0)
227
-		for _, state2 := range allForeground {
228
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state1, BACKGROUND_MASK_SET, BACKGROUND_MASK_UNSET)
229
-			flag = helpsTestGetWindowsTextAttributeForAnsiValue(t, flag, state2, FOREGROUND_MASK_SET, FOREGROUND_MASK_UNSET)
230
-		}
231
-	}
232
-}
233 1
deleted file mode 100644
... ...
@@ -1,234 +0,0 @@
1
-package winconsole
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"strconv"
7
-	"strings"
8
-)
9
-
10
-// http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
11
-const (
12
-	ANSI_ESCAPE_PRIMARY   = 0x1B
13
-	ANSI_ESCAPE_SECONDARY = 0x5B
14
-	ANSI_COMMAND_FIRST    = 0x40
15
-	ANSI_COMMAND_LAST     = 0x7E
16
-	ANSI_PARAMETER_SEP    = ";"
17
-	ANSI_CMD_G0           = '('
18
-	ANSI_CMD_G1           = ')'
19
-	ANSI_CMD_G2           = '*'
20
-	ANSI_CMD_G3           = '+'
21
-	ANSI_CMD_DECPNM       = '>'
22
-	ANSI_CMD_DECPAM       = '='
23
-	ANSI_CMD_OSC          = ']'
24
-	ANSI_CMD_STR_TERM     = '\\'
25
-	ANSI_BEL              = 0x07
26
-	KEY_EVENT             = 1
27
-)
28
-
29
-// Interface that implements terminal handling
30
-type terminalEmulator interface {
31
-	HandleOutputCommand(fd uintptr, command []byte) (n int, err error)
32
-	HandleInputSequence(fd uintptr, command []byte) (n int, err error)
33
-	WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error)
34
-	ReadChars(fd uintptr, w io.Reader, p []byte) (n int, err error)
35
-}
36
-
37
-type terminalWriter struct {
38
-	wrappedWriter io.Writer
39
-	emulator      terminalEmulator
40
-	command       []byte
41
-	inSequence    bool
42
-	fd            uintptr
43
-}
44
-
45
-type terminalReader struct {
46
-	wrappedReader io.ReadCloser
47
-	emulator      terminalEmulator
48
-	command       []byte
49
-	inSequence    bool
50
-	fd            uintptr
51
-}
52
-
53
-// http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
54
-func isAnsiCommandChar(b byte) bool {
55
-	switch {
56
-	case ANSI_COMMAND_FIRST <= b && b <= ANSI_COMMAND_LAST && b != ANSI_ESCAPE_SECONDARY:
57
-		return true
58
-	case b == ANSI_CMD_G1 || b == ANSI_CMD_OSC || b == ANSI_CMD_DECPAM || b == ANSI_CMD_DECPNM:
59
-		// non-CSI escape sequence terminator
60
-		return true
61
-	case b == ANSI_CMD_STR_TERM || b == ANSI_BEL:
62
-		// String escape sequence terminator
63
-		return true
64
-	}
65
-	return false
66
-}
67
-
68
-func isCharacterSelectionCmdChar(b byte) bool {
69
-	return (b == ANSI_CMD_G0 || b == ANSI_CMD_G1 || b == ANSI_CMD_G2 || b == ANSI_CMD_G3)
70
-}
71
-
72
-func isXtermOscSequence(command []byte, current byte) bool {
73
-	return (len(command) >= 2 && command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_CMD_OSC && current != ANSI_BEL)
74
-}
75
-
76
-// Write writes len(p) bytes from p to the underlying data stream.
77
-// http://golang.org/pkg/io/#Writer
78
-func (tw *terminalWriter) Write(p []byte) (n int, err error) {
79
-	if len(p) == 0 {
80
-		return 0, nil
81
-	}
82
-	if tw.emulator == nil {
83
-		return tw.wrappedWriter.Write(p)
84
-	}
85
-	// Emulate terminal by extracting commands and executing them
86
-	totalWritten := 0
87
-	start := 0 // indicates start of the next chunk
88
-	end := len(p)
89
-	for current := 0; current < end; current++ {
90
-		if tw.inSequence {
91
-			// inside escape sequence
92
-			tw.command = append(tw.command, p[current])
93
-			if isAnsiCommandChar(p[current]) {
94
-				if !isXtermOscSequence(tw.command, p[current]) {
95
-					// found the last command character.
96
-					// Now we have a complete command.
97
-					nchar, err := tw.emulator.HandleOutputCommand(tw.fd, tw.command)
98
-					totalWritten += nchar
99
-					if err != nil {
100
-						return totalWritten, err
101
-					}
102
-
103
-					// clear the command
104
-					// don't include current character again
105
-					tw.command = tw.command[:0]
106
-					start = current + 1
107
-					tw.inSequence = false
108
-				}
109
-			}
110
-		} else {
111
-			if p[current] == ANSI_ESCAPE_PRIMARY {
112
-				// entering escape sequnce
113
-				tw.inSequence = true
114
-				// indicates end of "normal sequence", write whatever you have so far
115
-				if len(p[start:current]) > 0 {
116
-					nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:current])
117
-					totalWritten += nw
118
-					if err != nil {
119
-						return totalWritten, err
120
-					}
121
-				}
122
-				// include the current character as part of the next sequence
123
-				tw.command = append(tw.command, p[current])
124
-			}
125
-		}
126
-	}
127
-	// note that so far, start of the escape sequence triggers writing out of bytes to console.
128
-	// For the part _after_ the end of last escape sequence, it is not written out yet. So write it out
129
-	if !tw.inSequence {
130
-		// assumption is that we can't be inside sequence and therefore command should be empty
131
-		if len(p[start:]) > 0 {
132
-			nw, err := tw.emulator.WriteChars(tw.fd, tw.wrappedWriter, p[start:])
133
-			totalWritten += nw
134
-			if err != nil {
135
-				return totalWritten, err
136
-			}
137
-		}
138
-	}
139
-	return totalWritten, nil
140
-
141
-}
142
-
143
-// Read reads up to len(p) bytes into p.
144
-// http://golang.org/pkg/io/#Reader
145
-func (tr *terminalReader) Read(p []byte) (n int, err error) {
146
-	//Implementations of Read are discouraged from returning a zero byte count
147
-	// with a nil error, except when len(p) == 0.
148
-	if len(p) == 0 {
149
-		return 0, nil
150
-	}
151
-	if nil == tr.emulator {
152
-		return tr.readFromWrappedReader(p)
153
-	}
154
-	return tr.emulator.ReadChars(tr.fd, tr.wrappedReader, p)
155
-}
156
-
157
-// Close the underlying stream
158
-func (tr *terminalReader) Close() (err error) {
159
-	return tr.wrappedReader.Close()
160
-}
161
-
162
-func (tr *terminalReader) readFromWrappedReader(p []byte) (n int, err error) {
163
-	return tr.wrappedReader.Read(p)
164
-}
165
-
166
-type ansiCommand struct {
167
-	CommandBytes []byte
168
-	Command      string
169
-	Parameters   []string
170
-	IsSpecial    bool
171
-}
172
-
173
-func parseAnsiCommand(command []byte) *ansiCommand {
174
-	if isCharacterSelectionCmdChar(command[1]) {
175
-		// Is Character Set Selection commands
176
-		return &ansiCommand{
177
-			CommandBytes: command,
178
-			Command:      string(command),
179
-			IsSpecial:    true,
180
-		}
181
-	}
182
-	// last char is command character
183
-	lastCharIndex := len(command) - 1
184
-
185
-	retValue := &ansiCommand{
186
-		CommandBytes: command,
187
-		Command:      string(command[lastCharIndex]),
188
-		IsSpecial:    false,
189
-	}
190
-	// more than a single escape
191
-	if lastCharIndex != 0 {
192
-		start := 1
193
-		// skip if double char escape sequence
194
-		if command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_ESCAPE_SECONDARY {
195
-			start++
196
-		}
197
-		// convert this to GetNextParam method
198
-		retValue.Parameters = strings.Split(string(command[start:lastCharIndex]), ANSI_PARAMETER_SEP)
199
-	}
200
-	return retValue
201
-}
202
-
203
-func (c *ansiCommand) getParam(index int) string {
204
-	if len(c.Parameters) > index {
205
-		return c.Parameters[index]
206
-	}
207
-	return ""
208
-}
209
-
210
-func (ac *ansiCommand) String() string {
211
-	return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
212
-		bytesToHex(ac.CommandBytes),
213
-		ac.Command,
214
-		strings.Join(ac.Parameters, "\",\""))
215
-}
216
-
217
-func bytesToHex(b []byte) string {
218
-	hex := make([]string, len(b))
219
-	for i, ch := range b {
220
-		hex[i] = fmt.Sprintf("%X", ch)
221
-	}
222
-	return strings.Join(hex, "")
223
-}
224
-
225
-func parseInt16OrDefault(s string, defaultValue int16) (n int16, err error) {
226
-	if s == "" {
227
-		return defaultValue, nil
228
-	}
229
-	parsedValue, err := strconv.ParseInt(s, 10, 16)
230
-	if err != nil {
231
-		return defaultValue, err
232
-	}
233
-	return int16(parsedValue), nil
234
-}
235 1
deleted file mode 100644
... ...
@@ -1,388 +0,0 @@
1
-package winconsole
2
-
3
-import (
4
-	"bytes"
5
-	"fmt"
6
-	"io"
7
-	"io/ioutil"
8
-	"testing"
9
-)
10
-
11
-const (
12
-	WRITE_OPERATION   = iota
13
-	COMMAND_OPERATION = iota
14
-)
15
-
16
-var languages = []string{
17
-	"Български",
18
-	"Català",
19
-	"Čeština",
20
-	"Ελληνικά",
21
-	"Español",
22
-	"Esperanto",
23
-	"Euskara",
24
-	"Français",
25
-	"Galego",
26
-	"한국어",
27
-	"ქართული",
28
-	"Latviešu",
29
-	"Lietuvių",
30
-	"Magyar",
31
-	"Nederlands",
32
-	"日本語",
33
-	"Norsk bokmål",
34
-	"Norsk nynorsk",
35
-	"Polski",
36
-	"Português",
37
-	"Română",
38
-	"Русский",
39
-	"Slovenčina",
40
-	"Slovenščina",
41
-	"Српски",
42
-	"српскохрватски",
43
-	"Suomi",
44
-	"Svenska",
45
-	"ไทย",
46
-	"Tiếng Việt",
47
-	"Türkçe",
48
-	"Українська",
49
-	"中文",
50
-}
51
-
52
-// Mock terminal handler object
53
-type mockTerminal struct {
54
-	OutputCommandSequence []terminalOperation
55
-}
56
-
57
-// Used for recording the callback data
58
-type terminalOperation struct {
59
-	Operation int
60
-	Data      []byte
61
-	Str       string
62
-}
63
-
64
-func (mt *mockTerminal) record(operation int, data []byte) {
65
-	op := terminalOperation{
66
-		Operation: operation,
67
-		Data:      make([]byte, len(data)),
68
-	}
69
-	copy(op.Data, data)
70
-	op.Str = string(op.Data)
71
-	mt.OutputCommandSequence = append(mt.OutputCommandSequence, op)
72
-}
73
-
74
-func (mt *mockTerminal) HandleOutputCommand(fd uintptr, command []byte) (n int, err error) {
75
-	mt.record(COMMAND_OPERATION, command)
76
-	return len(command), nil
77
-}
78
-
79
-func (mt *mockTerminal) HandleInputSequence(fd uintptr, command []byte) (n int, err error) {
80
-	return 0, nil
81
-}
82
-
83
-func (mt *mockTerminal) WriteChars(fd uintptr, w io.Writer, p []byte) (n int, err error) {
84
-	mt.record(WRITE_OPERATION, p)
85
-	return len(p), nil
86
-}
87
-
88
-func (mt *mockTerminal) ReadChars(fd uintptr, w io.Reader, p []byte) (n int, err error) {
89
-	return len(p), nil
90
-}
91
-
92
-func assertTrue(t *testing.T, cond bool, format string, args ...interface{}) {
93
-	if !cond {
94
-		t.Errorf(format, args...)
95
-	}
96
-}
97
-
98
-// reflect.DeepEqual does not provide detailed information as to what excatly failed.
99
-func assertBytesEqual(t *testing.T, expected, actual []byte, format string, args ...interface{}) {
100
-	match := true
101
-	mismatchIndex := 0
102
-	if len(expected) == len(actual) {
103
-		for i := 0; i < len(expected); i++ {
104
-			if expected[i] != actual[i] {
105
-				match = false
106
-				mismatchIndex = i
107
-				break
108
-			}
109
-		}
110
-	} else {
111
-		match = false
112
-		t.Errorf("Lengths don't match Expected=%d Actual=%d", len(expected), len(actual))
113
-	}
114
-	if !match {
115
-		t.Errorf("Mismatch at index %d ", mismatchIndex)
116
-		t.Errorf("\tActual String   = %s", string(actual))
117
-		t.Errorf("\tExpected String = %s", string(expected))
118
-		t.Errorf("\tActual          = %v", actual)
119
-		t.Errorf("\tExpected        = %v", expected)
120
-		t.Errorf(format, args)
121
-	}
122
-}
123
-
124
-// Just to make sure :)
125
-func TestAssertEqualBytes(t *testing.T) {
126
-	data := []byte{9, 9, 1, 1, 1, 9, 9}
127
-	assertBytesEqual(t, data, data, "Self")
128
-	assertBytesEqual(t, data[1:4], data[1:4], "Self")
129
-	assertBytesEqual(t, []byte{1, 1}, []byte{1, 1}, "Simple match")
130
-	assertBytesEqual(t, []byte{1, 2, 3}, []byte{1, 2, 3}, "content mismatch")
131
-	assertBytesEqual(t, []byte{1, 1, 1}, data[2:5], "slice match")
132
-}
133
-
134
-/*
135
-func TestAssertEqualBytesNegative(t *testing.T) {
136
-	AssertBytesEqual(t, []byte{1, 1}, []byte{1}, "Length mismatch")
137
-	AssertBytesEqual(t, []byte{1, 1}, []byte{1}, "Length mismatch")
138
-	AssertBytesEqual(t, []byte{1, 2, 3}, []byte{1, 1, 1}, "content mismatch")
139
-}*/
140
-
141
-// Checks that the calls received
142
-func assertHandlerOutput(t *testing.T, mock *mockTerminal, plainText string, commands ...string) {
143
-	text := make([]byte, 0, 3*len(plainText))
144
-	cmdIndex := 0
145
-	for opIndex := 0; opIndex < len(mock.OutputCommandSequence); opIndex++ {
146
-		op := mock.OutputCommandSequence[opIndex]
147
-		if op.Operation == WRITE_OPERATION {
148
-			t.Logf("\nThe data is[%d] == %s", opIndex, string(op.Data))
149
-			text = append(text[:], op.Data...)
150
-		} else {
151
-			assertTrue(t, mock.OutputCommandSequence[opIndex].Operation == COMMAND_OPERATION, "Operation should be command : %s", fmt.Sprintf("%+v", mock))
152
-			assertBytesEqual(t, StringToBytes(commands[cmdIndex]), mock.OutputCommandSequence[opIndex].Data, "Command data should match")
153
-			cmdIndex++
154
-		}
155
-	}
156
-	assertBytesEqual(t, StringToBytes(plainText), text, "Command data should match %#v", mock)
157
-}
158
-
159
-func StringToBytes(str string) []byte {
160
-	bytes := make([]byte, len(str))
161
-	copy(bytes[:], str)
162
-	return bytes
163
-}
164
-
165
-func TestParseAnsiCommand(t *testing.T) {
166
-	// Note: if the parameter does not exist then the empty value is returned
167
-
168
-	c := parseAnsiCommand(StringToBytes("\x1Bm"))
169
-	assertTrue(t, c.Command == "m", "Command should be m")
170
-	assertTrue(t, "" == c.getParam(0), "should return empty string")
171
-	assertTrue(t, "" == c.getParam(1), "should return empty string")
172
-
173
-	// Escape sequence - ESC[
174
-	c = parseAnsiCommand(StringToBytes("\x1B[m"))
175
-	assertTrue(t, c.Command == "m", "Command should be m")
176
-	assertTrue(t, "" == c.getParam(0), "should return empty string")
177
-	assertTrue(t, "" == c.getParam(1), "should return empty string")
178
-
179
-	// Escape sequence With empty parameters- ESC[
180
-	c = parseAnsiCommand(StringToBytes("\x1B[;m"))
181
-	assertTrue(t, c.Command == "m", "Command should be m")
182
-	assertTrue(t, "" == c.getParam(0), "should return empty string")
183
-	assertTrue(t, "" == c.getParam(1), "should return empty string")
184
-	assertTrue(t, "" == c.getParam(2), "should return empty string")
185
-
186
-	// Escape sequence With empty muliple parameters- ESC[
187
-	c = parseAnsiCommand(StringToBytes("\x1B[;;m"))
188
-	assertTrue(t, c.Command == "m", "Command should be m")
189
-	assertTrue(t, "" == c.getParam(0), "")
190
-	assertTrue(t, "" == c.getParam(1), "")
191
-	assertTrue(t, "" == c.getParam(2), "")
192
-
193
-	// Escape sequence With muliple parameters- ESC[
194
-	c = parseAnsiCommand(StringToBytes("\x1B[1;2;3m"))
195
-	assertTrue(t, c.Command == "m", "Command should be m")
196
-	assertTrue(t, "1" == c.getParam(0), "")
197
-	assertTrue(t, "2" == c.getParam(1), "")
198
-	assertTrue(t, "3" == c.getParam(2), "")
199
-
200
-	// Escape sequence With muliple parameters- some missing
201
-	c = parseAnsiCommand(StringToBytes("\x1B[1;;3;;;6m"))
202
-	assertTrue(t, c.Command == "m", "Command should be m")
203
-	assertTrue(t, "1" == c.getParam(0), "")
204
-	assertTrue(t, "" == c.getParam(1), "")
205
-	assertTrue(t, "3" == c.getParam(2), "")
206
-	assertTrue(t, "" == c.getParam(3), "")
207
-	assertTrue(t, "" == c.getParam(4), "")
208
-	assertTrue(t, "6" == c.getParam(5), "")
209
-}
210
-
211
-func newBufferedMockTerm() (stdOut io.Writer, stdErr io.Writer, stdIn io.ReadCloser, mock *mockTerminal) {
212
-	var input bytes.Buffer
213
-	var output bytes.Buffer
214
-	var err bytes.Buffer
215
-
216
-	mock = &mockTerminal{
217
-		OutputCommandSequence: make([]terminalOperation, 0, 256),
218
-	}
219
-
220
-	stdOut = &terminalWriter{
221
-		wrappedWriter: &output,
222
-		emulator:      mock,
223
-		command:       make([]byte, 0, 256),
224
-	}
225
-	stdErr = &terminalWriter{
226
-		wrappedWriter: &err,
227
-		emulator:      mock,
228
-		command:       make([]byte, 0, 256),
229
-	}
230
-	stdIn = &terminalReader{
231
-		wrappedReader: ioutil.NopCloser(&input),
232
-		emulator:      mock,
233
-		command:       make([]byte, 0, 256),
234
-	}
235
-
236
-	return
237
-}
238
-
239
-func TestOutputSimple(t *testing.T) {
240
-	stdOut, _, _, mock := newBufferedMockTerm()
241
-
242
-	stdOut.Write(StringToBytes("Hello world"))
243
-	stdOut.Write(StringToBytes("\x1BmHello again"))
244
-
245
-	assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
246
-	assertBytesEqual(t, StringToBytes("Hello world"), mock.OutputCommandSequence[0].Data, "Write data should match")
247
-
248
-	assertTrue(t, mock.OutputCommandSequence[1].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
249
-	assertBytesEqual(t, StringToBytes("\x1Bm"), mock.OutputCommandSequence[1].Data, "Command data should match")
250
-
251
-	assertTrue(t, mock.OutputCommandSequence[2].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
252
-	assertBytesEqual(t, StringToBytes("Hello again"), mock.OutputCommandSequence[2].Data, "Write data should match")
253
-}
254
-
255
-func TestOutputSplitCommand(t *testing.T) {
256
-	stdOut, _, _, mock := newBufferedMockTerm()
257
-
258
-	stdOut.Write(StringToBytes("Hello world\x1B[1;2;3"))
259
-	stdOut.Write(StringToBytes("mHello again"))
260
-
261
-	assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
262
-	assertBytesEqual(t, StringToBytes("Hello world"), mock.OutputCommandSequence[0].Data, "Write data should match")
263
-
264
-	assertTrue(t, mock.OutputCommandSequence[1].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
265
-	assertBytesEqual(t, StringToBytes("\x1B[1;2;3m"), mock.OutputCommandSequence[1].Data, "Command data should match")
266
-
267
-	assertTrue(t, mock.OutputCommandSequence[2].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
268
-	assertBytesEqual(t, StringToBytes("Hello again"), mock.OutputCommandSequence[2].Data, "Write data should match")
269
-}
270
-
271
-func TestOutputMultipleCommands(t *testing.T) {
272
-	stdOut, _, _, mock := newBufferedMockTerm()
273
-
274
-	stdOut.Write(StringToBytes("Hello world"))
275
-	stdOut.Write(StringToBytes("\x1B[1;2;3m"))
276
-	stdOut.Write(StringToBytes("\x1B[J"))
277
-	stdOut.Write(StringToBytes("Hello again"))
278
-
279
-	assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
280
-	assertBytesEqual(t, StringToBytes("Hello world"), mock.OutputCommandSequence[0].Data, "Write data should match")
281
-
282
-	assertTrue(t, mock.OutputCommandSequence[1].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
283
-	assertBytesEqual(t, StringToBytes("\x1B[1;2;3m"), mock.OutputCommandSequence[1].Data, "Command data should match")
284
-
285
-	assertTrue(t, mock.OutputCommandSequence[2].Operation == COMMAND_OPERATION, "Operation should be command : %+v", mock)
286
-	assertBytesEqual(t, StringToBytes("\x1B[J"), mock.OutputCommandSequence[2].Data, "Command data should match")
287
-
288
-	assertTrue(t, mock.OutputCommandSequence[3].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
289
-	assertBytesEqual(t, StringToBytes("Hello again"), mock.OutputCommandSequence[3].Data, "Write data should match")
290
-}
291
-
292
-// Splits the given data in two chunks , makes two writes and checks the split data is parsed correctly
293
-// checks output write/command is passed to handler correctly
294
-func helpsTestOutputSplitChunksAtIndex(t *testing.T, i int, data []byte) {
295
-	t.Logf("\ni=%d", i)
296
-	stdOut, _, _, mock := newBufferedMockTerm()
297
-
298
-	t.Logf("\nWriting chunk[0] == %s", string(data[:i]))
299
-	t.Logf("\nWriting chunk[1] == %s", string(data[i:]))
300
-	stdOut.Write(data[:i])
301
-	stdOut.Write(data[i:])
302
-
303
-	assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
304
-	assertBytesEqual(t, data[:i], mock.OutputCommandSequence[0].Data, "Write data should match")
305
-
306
-	assertTrue(t, mock.OutputCommandSequence[1].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
307
-	assertBytesEqual(t, data[i:], mock.OutputCommandSequence[1].Data, "Write data should match")
308
-}
309
-
310
-// Splits the given data in three chunks , makes three writes and checks the split data is parsed correctly
311
-// checks output write/command is passed to handler correctly
312
-func helpsTestOutputSplitThreeChunksAtIndex(t *testing.T, data []byte, i int, j int) {
313
-	stdOut, _, _, mock := newBufferedMockTerm()
314
-
315
-	t.Logf("\nWriting chunk[0] == %s", string(data[:i]))
316
-	t.Logf("\nWriting chunk[1] == %s", string(data[i:j]))
317
-	t.Logf("\nWriting chunk[2] == %s", string(data[j:]))
318
-	stdOut.Write(data[:i])
319
-	stdOut.Write(data[i:j])
320
-	stdOut.Write(data[j:])
321
-
322
-	assertTrue(t, mock.OutputCommandSequence[0].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
323
-	assertBytesEqual(t, data[:i], mock.OutputCommandSequence[0].Data, "Write data should match")
324
-
325
-	assertTrue(t, mock.OutputCommandSequence[1].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
326
-	assertBytesEqual(t, data[i:j], mock.OutputCommandSequence[1].Data, "Write data should match")
327
-
328
-	assertTrue(t, mock.OutputCommandSequence[2].Operation == WRITE_OPERATION, "Operation should be Write : %#v", mock)
329
-	assertBytesEqual(t, data[j:], mock.OutputCommandSequence[2].Data, "Write data should match")
330
-}
331
-
332
-// Splits the output into two parts and tests all such possible pairs
333
-func helpsTestOutputSplitChunks(t *testing.T, data []byte) {
334
-	for i := 1; i < len(data)-1; i++ {
335
-		helpsTestOutputSplitChunksAtIndex(t, i, data)
336
-	}
337
-}
338
-
339
-// Splits the output in three parts and tests all such possible triples
340
-func helpsTestOutputSplitThreeChunks(t *testing.T, data []byte) {
341
-	for i := 1; i < len(data)-2; i++ {
342
-		for j := i + 1; j < len(data)-1; j++ {
343
-			helpsTestOutputSplitThreeChunksAtIndex(t, data, i, j)
344
-		}
345
-	}
346
-}
347
-
348
-func helpsTestOutputSplitCommandsAtIndex(t *testing.T, data []byte, i int, plainText string, commands ...string) {
349
-	t.Logf("\ni=%d", i)
350
-	stdOut, _, _, mock := newBufferedMockTerm()
351
-
352
-	stdOut.Write(data[:i])
353
-	stdOut.Write(data[i:])
354
-	assertHandlerOutput(t, mock, plainText, commands...)
355
-}
356
-
357
-func helpsTestOutputSplitCommands(t *testing.T, data []byte, plainText string, commands ...string) {
358
-	for i := 1; i < len(data)-1; i++ {
359
-		helpsTestOutputSplitCommandsAtIndex(t, data, i, plainText, commands...)
360
-	}
361
-}
362
-
363
-func injectCommandAt(data string, i int, command string) string {
364
-	retValue := make([]byte, len(data)+len(command)+4)
365
-	retValue = append(retValue, data[:i]...)
366
-	retValue = append(retValue, data[i:]...)
367
-	return string(retValue)
368
-}
369
-
370
-func TestOutputSplitChunks(t *testing.T) {
371
-	data := StringToBytes("qwertyuiopasdfghjklzxcvbnm")
372
-	helpsTestOutputSplitChunks(t, data)
373
-	helpsTestOutputSplitChunks(t, StringToBytes("BBBBB"))
374
-	helpsTestOutputSplitThreeChunks(t, StringToBytes("ABCDE"))
375
-}
376
-
377
-func TestOutputSplitChunksIncludingCommands(t *testing.T) {
378
-	helpsTestOutputSplitCommands(t, StringToBytes("Hello world.\x1B[mHello again."), "Hello world.Hello again.", "\x1B[m")
379
-	helpsTestOutputSplitCommandsAtIndex(t, StringToBytes("Hello world.\x1B[mHello again."), 2, "Hello world.Hello again.", "\x1B[m")
380
-}
381
-
382
-func TestSplitChunkUnicode(t *testing.T) {
383
-	for _, l := range languages {
384
-		data := StringToBytes(l)
385
-		helpsTestOutputSplitChunks(t, data)
386
-		helpsTestOutputSplitThreeChunks(t, data)
387
-	}
388
-}
389 1
new file mode 100644
... ...
@@ -0,0 +1,256 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"bytes"
6
+	"errors"
7
+	"fmt"
8
+	"os"
9
+	"strings"
10
+	"unsafe"
11
+
12
+	. "github.com/Azure/go-ansiterm"
13
+	. "github.com/Azure/go-ansiterm/winterm"
14
+)
15
+
16
+// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation.
17
+type ansiReader struct {
18
+	file     *os.File
19
+	fd       uintptr
20
+	buffer   []byte
21
+	cbBuffer int
22
+	command  []byte
23
+	// TODO(azlinux): Remove this and hard-code the string -- it is not going to change
24
+	escapeSequence []byte
25
+}
26
+
27
+func newAnsiReader(nFile int) *ansiReader {
28
+	file, fd := GetStdFile(nFile)
29
+	return &ansiReader{
30
+		file:           file,
31
+		fd:             fd,
32
+		command:        make([]byte, 0, ANSI_MAX_CMD_LENGTH),
33
+		escapeSequence: []byte(KEY_ESC_CSI),
34
+		buffer:         make([]byte, 0),
35
+	}
36
+}
37
+
38
+// Close closes the wrapped file.
39
+func (ar *ansiReader) Close() (err error) {
40
+	return ar.file.Close()
41
+}
42
+
43
+// Fd returns the file descriptor of the wrapped file.
44
+func (ar *ansiReader) Fd() uintptr {
45
+	return ar.fd
46
+}
47
+
48
+// Read reads up to len(p) bytes of translated input events into p.
49
+func (ar *ansiReader) Read(p []byte) (int, error) {
50
+	if len(p) == 0 {
51
+		return 0, nil
52
+	}
53
+
54
+	// Previously read bytes exist, read as much as we can and return
55
+	if len(ar.buffer) > 0 {
56
+		logger.Debugf("Reading previously cached bytes")
57
+
58
+		originalLength := len(ar.buffer)
59
+		copiedLength := copy(p, ar.buffer)
60
+
61
+		if copiedLength == originalLength {
62
+			ar.buffer = make([]byte, 0, len(p))
63
+		} else {
64
+			ar.buffer = ar.buffer[copiedLength:]
65
+		}
66
+
67
+		logger.Debugf("Read from cache p[%d]: % x", copiedLength, p)
68
+		return copiedLength, nil
69
+	}
70
+
71
+	// Read and translate key events
72
+	events, err := readInputEvents(ar.fd, len(p))
73
+	if err != nil {
74
+		return 0, err
75
+	} else if len(events) == 0 {
76
+		logger.Debug("No input events detected")
77
+		return 0, nil
78
+	}
79
+
80
+	keyBytes := translateKeyEvents(events, ar.escapeSequence)
81
+
82
+	// Save excess bytes and right-size keyBytes
83
+	if len(keyBytes) > len(p) {
84
+		logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p))
85
+		ar.buffer = keyBytes[len(p):]
86
+		keyBytes = keyBytes[:len(p)]
87
+	} else if len(keyBytes) == 0 {
88
+		logger.Debug("No key bytes returned from the translater")
89
+		return 0, nil
90
+	}
91
+
92
+	copiedLength := copy(p, keyBytes)
93
+	if copiedLength != len(keyBytes) {
94
+		return 0, errors.New("Unexpected copy length encountered.")
95
+	}
96
+
97
+	logger.Debugf("Read        p[%d]: % x", copiedLength, p)
98
+	logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes)
99
+	return copiedLength, nil
100
+}
101
+
102
+// readInputEvents polls until at least one event is available.
103
+func readInputEvents(fd uintptr, maxBytes int) ([]INPUT_RECORD, error) {
104
+	// Determine the maximum number of records to retrieve
105
+	// -- Cast around the type system to obtain the size of a single INPUT_RECORD.
106
+	//    unsafe.Sizeof requires an expression vs. a type-reference; the casting
107
+	//    tricks the type system into believing it has such an expression.
108
+	recordSize := int(unsafe.Sizeof(*((*INPUT_RECORD)(unsafe.Pointer(&maxBytes)))))
109
+	countRecords := maxBytes / recordSize
110
+	if countRecords > MAX_INPUT_EVENTS {
111
+		countRecords = MAX_INPUT_EVENTS
112
+	}
113
+	logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize)
114
+
115
+	// Wait for and read input events
116
+	events := make([]INPUT_RECORD, countRecords)
117
+	nEvents := uint32(0)
118
+	eventsExist, err := WaitForSingleObject(fd, WAIT_INFINITE)
119
+	if err != nil {
120
+		return nil, err
121
+	}
122
+
123
+	if eventsExist {
124
+		err = ReadConsoleInput(fd, events, &nEvents)
125
+		if err != nil {
126
+			return nil, err
127
+		}
128
+	}
129
+
130
+	// Return a slice restricted to the number of returned records
131
+	logger.Debugf("[windows] readInputEvents: Read %v events", nEvents)
132
+	return events[:nEvents], nil
133
+}
134
+
135
+// KeyEvent Translation Helpers
136
+
137
+var arrowKeyMapPrefix = map[WORD]string{
138
+	VK_UP:    "%s%sA",
139
+	VK_DOWN:  "%s%sB",
140
+	VK_RIGHT: "%s%sC",
141
+	VK_LEFT:  "%s%sD",
142
+}
143
+
144
+var keyMapPrefix = map[WORD]string{
145
+	VK_UP:     "\x1B[%sA",
146
+	VK_DOWN:   "\x1B[%sB",
147
+	VK_RIGHT:  "\x1B[%sC",
148
+	VK_LEFT:   "\x1B[%sD",
149
+	VK_HOME:   "\x1B[1%s~", // showkey shows ^[[1
150
+	VK_END:    "\x1B[4%s~", // showkey shows ^[[4
151
+	VK_INSERT: "\x1B[2%s~",
152
+	VK_DELETE: "\x1B[3%s~",
153
+	VK_PRIOR:  "\x1B[5%s~",
154
+	VK_NEXT:   "\x1B[6%s~",
155
+	VK_F1:     "",
156
+	VK_F2:     "",
157
+	VK_F3:     "\x1B[13%s~",
158
+	VK_F4:     "\x1B[14%s~",
159
+	VK_F5:     "\x1B[15%s~",
160
+	VK_F6:     "\x1B[17%s~",
161
+	VK_F7:     "\x1B[18%s~",
162
+	VK_F8:     "\x1B[19%s~",
163
+	VK_F9:     "\x1B[20%s~",
164
+	VK_F10:    "\x1B[21%s~",
165
+	VK_F11:    "\x1B[23%s~",
166
+	VK_F12:    "\x1B[24%s~",
167
+}
168
+
169
+// translateKeyEvents converts the input events into the appropriate ANSI string.
170
+func translateKeyEvents(events []INPUT_RECORD, escapeSequence []byte) []byte {
171
+	var buffer bytes.Buffer
172
+	for _, event := range events {
173
+		if event.EventType == KEY_EVENT && event.KeyEvent.KeyDown != 0 {
174
+			buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence))
175
+		}
176
+	}
177
+
178
+	return buffer.Bytes()
179
+}
180
+
181
+// keyToString maps the given input event record to the corresponding string.
182
+func keyToString(keyEvent *KEY_EVENT_RECORD, escapeSequence []byte) string {
183
+	if keyEvent.UnicodeChar == 0 {
184
+		return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence)
185
+	}
186
+
187
+	_, alt, control := getControlKeys(keyEvent.ControlKeyState)
188
+	if control {
189
+		// TODO(azlinux): Implement following control sequences
190
+		// <Ctrl>-D  Signals the end of input from the keyboard; also exits current shell.
191
+		// <Ctrl>-H  Deletes the first character to the left of the cursor. Also called the ERASE key.
192
+		// <Ctrl>-Q  Restarts printing after it has been stopped with <Ctrl>-s.
193
+		// <Ctrl>-S  Suspends printing on the screen (does not stop the program).
194
+		// <Ctrl>-U  Deletes all characters on the current line. Also called the KILL key.
195
+		// <Ctrl>-E  Quits current command and creates a core
196
+
197
+	}
198
+
199
+	// <Alt>+Key generates ESC N Key
200
+	if !control && alt {
201
+		return KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar))
202
+	}
203
+
204
+	return string(keyEvent.UnicodeChar)
205
+}
206
+
207
+// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string.
208
+func formatVirtualKey(key WORD, controlState DWORD, escapeSequence []byte) string {
209
+	shift, alt, control := getControlKeys(controlState)
210
+	modifier := getControlKeysModifier(shift, alt, control, false)
211
+
212
+	if format, ok := arrowKeyMapPrefix[key]; ok {
213
+		return fmt.Sprintf(format, escapeSequence, modifier)
214
+	}
215
+
216
+	if format, ok := keyMapPrefix[key]; ok {
217
+		return fmt.Sprintf(format, modifier)
218
+	}
219
+
220
+	return ""
221
+}
222
+
223
+// getControlKeys extracts the shift, alt, and ctrl key states.
224
+func getControlKeys(controlState DWORD) (shift, alt, control bool) {
225
+	shift = 0 != (controlState & SHIFT_PRESSED)
226
+	alt = 0 != (controlState & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED))
227
+	control = 0 != (controlState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED))
228
+	return shift, alt, control
229
+}
230
+
231
+// getControlKeysModifier returns the ANSI modifier for the given combination of control keys.
232
+func getControlKeysModifier(shift, alt, control, meta bool) string {
233
+	if shift && alt && control {
234
+		return KEY_CONTROL_PARAM_8
235
+	}
236
+	if alt && control {
237
+		return KEY_CONTROL_PARAM_7
238
+	}
239
+	if shift && control {
240
+		return KEY_CONTROL_PARAM_6
241
+	}
242
+	if control {
243
+		return KEY_CONTROL_PARAM_5
244
+	}
245
+	if shift && alt {
246
+		return KEY_CONTROL_PARAM_4
247
+	}
248
+	if alt {
249
+		return KEY_CONTROL_PARAM_3
250
+	}
251
+	if shift {
252
+		return KEY_CONTROL_PARAM_2
253
+	}
254
+	return ""
255
+}
0 256
new file mode 100644
... ...
@@ -0,0 +1,76 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"io/ioutil"
6
+	"os"
7
+
8
+	. "github.com/Azure/go-ansiterm"
9
+	. "github.com/Azure/go-ansiterm/winterm"
10
+	"github.com/Sirupsen/logrus"
11
+)
12
+
13
+var logger *logrus.Logger
14
+
15
+// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation.
16
+type ansiWriter struct {
17
+	file           *os.File
18
+	fd             uintptr
19
+	infoReset      *CONSOLE_SCREEN_BUFFER_INFO
20
+	command        []byte
21
+	escapeSequence []byte
22
+	inAnsiSequence bool
23
+	parser         *AnsiParser
24
+}
25
+
26
+func newAnsiWriter(nFile int) *ansiWriter {
27
+	logFile := ioutil.Discard
28
+
29
+	if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
30
+		logFile, _ = os.Create("ansiReaderWriter.log")
31
+	}
32
+
33
+	logger = &logrus.Logger{
34
+		Out:       logFile,
35
+		Formatter: new(logrus.TextFormatter),
36
+		Level:     logrus.DebugLevel,
37
+	}
38
+
39
+	file, fd := GetStdFile(nFile)
40
+	info, err := GetConsoleScreenBufferInfo(fd)
41
+	if err != nil {
42
+		return nil
43
+	}
44
+
45
+	parser := CreateParser("Ground", CreateWinEventHandler(fd, file))
46
+	logger.Infof("newAnsiWriter: parser %p", parser)
47
+
48
+	aw := &ansiWriter{
49
+		file:           file,
50
+		fd:             fd,
51
+		infoReset:      info,
52
+		command:        make([]byte, 0, ANSI_MAX_CMD_LENGTH),
53
+		escapeSequence: []byte(KEY_ESC_CSI),
54
+		parser:         parser,
55
+	}
56
+
57
+	logger.Infof("newAnsiWriter: aw.parser %p", aw.parser)
58
+	logger.Infof("newAnsiWriter: %v", aw)
59
+	return aw
60
+}
61
+
62
+func (aw *ansiWriter) Fd() uintptr {
63
+	return aw.fd
64
+}
65
+
66
+// Write writes len(p) bytes from p to the underlying data stream.
67
+func (aw *ansiWriter) Write(p []byte) (total int, err error) {
68
+	if len(p) == 0 {
69
+		return 0, nil
70
+	}
71
+
72
+	logger.Infof("Write: % x", p)
73
+	logger.Infof("Write: %s", string(p))
74
+	return aw.parser.Parse(p)
75
+}
0 76
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+// +build windows
1
+
2
+package windows
3
+
4
+import (
5
+	"io"
6
+	"os"
7
+	"syscall"
8
+
9
+	. "github.com/Azure/go-ansiterm/winterm"
10
+)
11
+
12
+// ConsoleStreams, for each standard stream referencing a console, returns a wrapped version
13
+// that handles ANSI character sequences.
14
+func ConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
15
+	if IsConsole(os.Stdin.Fd()) {
16
+		stdIn = newAnsiReader(syscall.STD_INPUT_HANDLE)
17
+	} else {
18
+		stdIn = os.Stdin
19
+	}
20
+
21
+	if IsConsole(os.Stdout.Fd()) {
22
+		stdOut = newAnsiWriter(syscall.STD_OUTPUT_HANDLE)
23
+	} else {
24
+		stdOut = os.Stdout
25
+	}
26
+
27
+	if IsConsole(os.Stderr.Fd()) {
28
+		stdErr = newAnsiWriter(syscall.STD_ERROR_HANDLE)
29
+	} else {
30
+		stdErr = os.Stderr
31
+	}
32
+
33
+	return stdIn, stdOut, stdErr
34
+}
35
+
36
+// GetHandleInfo returns file descriptor and bool indicating whether the file is a console.
37
+func GetHandleInfo(in interface{}) (uintptr, bool) {
38
+	switch t := in.(type) {
39
+	case *ansiReader:
40
+		return t.Fd(), true
41
+	case *ansiWriter:
42
+		return t.Fd(), true
43
+	}
44
+
45
+	var inFd uintptr
46
+	var isTerminal bool
47
+
48
+	if file, ok := in.(*os.File); ok {
49
+		inFd = file.Fd()
50
+		isTerminal = IsConsole(inFd)
51
+	}
52
+	return inFd, isTerminal
53
+}
54
+
55
+// IsConsole returns true if the given file descriptor is a Windows Console.
56
+// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console.
57
+func IsConsole(fd uintptr) bool {
58
+	_, e := GetConsoleMode(fd)
59
+	return e == nil
60
+}
0 61
new file mode 100644
... ...
@@ -0,0 +1,5 @@
0
+// These files implement ANSI-aware input and output streams for use by the Docker Windows client.
1
+// When asked for the set of standard streams (e.g., stdin, stdout, stderr), the code will create
2
+// and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls.
3
+
4
+package windows
0 5
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+// This file is necessary to pass the Docker tests.
1
+
2
+package windows
0 3
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+The MIT License (MIT)
1
+
2
+Copyright (c) 2015 Microsoft Corporation
3
+
4
+Permission is hereby granted, free of charge, to any person obtaining a copy
5
+of this software and associated documentation files (the "Software"), to deal
6
+in the Software without restriction, including without limitation the rights
7
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+copies of the Software, and to permit persons to whom the Software is
9
+furnished to do so, subject to the following conditions:
10
+
11
+The above copyright notice and this permission notice shall be included in
12
+all copies or substantial portions of the Software.
13
+
14
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+THE SOFTWARE.
0 21
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+# go-ansiterm
1
+
2
+This is a cross platform Ansi Terminal Emulation library.  It reads a stream of Ansi characters and produces the appropriate function calls.  The results of the function calls are platform dependent.
3
+
4
+For example the parser might receive "ESC, [, A" as a stream of three characters.  This is the code for Cursor Up (http://www.vt100.net/docs/vt510-rm/CUU).  The parser then calls the cursor up function (CUU()) on an event handler.  The event handler determines what platform specific work must be done to cause the cursor to move up one position.
5
+
6
+The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png).  There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
7
+
8
+See parser_test.go for examples exercising the state machine and generating appropriate function calls.
0 9
new file mode 100644
... ...
@@ -0,0 +1,184 @@
0
+package ansiterm
1
+
2
+const LogEnv = "DEBUG_TERMINAL"
3
+
4
+// ANSI constants
5
+// References:
6
+// -- http://www.ecma-international.org/publications/standards/Ecma-048.htm
7
+// -- http://man7.org/linux/man-pages/man4/console_codes.4.html
8
+// -- http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html
9
+// -- http://en.wikipedia.org/wiki/ANSI_escape_code
10
+// -- http://vt100.net/emu/dec_ansi_parser
11
+// -- http://vt100.net/emu/vt500_parser.svg
12
+// -- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
13
+// -- http://www.inwap.com/pdp10/ansicode.txt
14
+const (
15
+	// ECMA-48 Set Graphics Rendition
16
+	// Note:
17
+	// -- Constants leading with an underscore (e.g., _ANSI_xxx) are unsupported or reserved
18
+	// -- Fonts could possibly be supported via SetCurrentConsoleFontEx
19
+	// -- Windows does not expose the per-window cursor (i.e., caret) blink times
20
+	ANSI_SGR_RESET              = 0
21
+	ANSI_SGR_BOLD               = 1
22
+	ANSI_SGR_DIM                = 2
23
+	_ANSI_SGR_ITALIC            = 3
24
+	ANSI_SGR_UNDERLINE          = 4
25
+	_ANSI_SGR_BLINKSLOW         = 5
26
+	_ANSI_SGR_BLINKFAST         = 6
27
+	ANSI_SGR_REVERSE            = 7
28
+	_ANSI_SGR_INVISIBLE         = 8
29
+	_ANSI_SGR_LINETHROUGH       = 9
30
+	_ANSI_SGR_FONT_00           = 10
31
+	_ANSI_SGR_FONT_01           = 11
32
+	_ANSI_SGR_FONT_02           = 12
33
+	_ANSI_SGR_FONT_03           = 13
34
+	_ANSI_SGR_FONT_04           = 14
35
+	_ANSI_SGR_FONT_05           = 15
36
+	_ANSI_SGR_FONT_06           = 16
37
+	_ANSI_SGR_FONT_07           = 17
38
+	_ANSI_SGR_FONT_08           = 18
39
+	_ANSI_SGR_FONT_09           = 19
40
+	_ANSI_SGR_FONT_10           = 20
41
+	_ANSI_SGR_DOUBLEUNDERLINE   = 21
42
+	ANSI_SGR_BOLD_DIM_OFF       = 22
43
+	_ANSI_SGR_ITALIC_OFF        = 23
44
+	ANSI_SGR_UNDERLINE_OFF      = 24
45
+	_ANSI_SGR_BLINK_OFF         = 25
46
+	_ANSI_SGR_RESERVED_00       = 26
47
+	ANSI_SGR_REVERSE_OFF        = 27
48
+	_ANSI_SGR_INVISIBLE_OFF     = 28
49
+	_ANSI_SGR_LINETHROUGH_OFF   = 29
50
+	ANSI_SGR_FOREGROUND_BLACK   = 30
51
+	ANSI_SGR_FOREGROUND_RED     = 31
52
+	ANSI_SGR_FOREGROUND_GREEN   = 32
53
+	ANSI_SGR_FOREGROUND_YELLOW  = 33
54
+	ANSI_SGR_FOREGROUND_BLUE    = 34
55
+	ANSI_SGR_FOREGROUND_MAGENTA = 35
56
+	ANSI_SGR_FOREGROUND_CYAN    = 36
57
+	ANSI_SGR_FOREGROUND_WHITE   = 37
58
+	_ANSI_SGR_RESERVED_01       = 38
59
+	ANSI_SGR_FOREGROUND_DEFAULT = 39
60
+	ANSI_SGR_BACKGROUND_BLACK   = 40
61
+	ANSI_SGR_BACKGROUND_RED     = 41
62
+	ANSI_SGR_BACKGROUND_GREEN   = 42
63
+	ANSI_SGR_BACKGROUND_YELLOW  = 43
64
+	ANSI_SGR_BACKGROUND_BLUE    = 44
65
+	ANSI_SGR_BACKGROUND_MAGENTA = 45
66
+	ANSI_SGR_BACKGROUND_CYAN    = 46
67
+	ANSI_SGR_BACKGROUND_WHITE   = 47
68
+	_ANSI_SGR_RESERVED_02       = 48
69
+	ANSI_SGR_BACKGROUND_DEFAULT = 49
70
+	// 50 - 65: Unsupported
71
+
72
+	ANSI_MAX_CMD_LENGTH = 4096
73
+
74
+	MAX_INPUT_EVENTS = 128
75
+	DEFAULT_WIDTH    = 80
76
+	DEFAULT_HEIGHT   = 24
77
+
78
+	ANSI_BEL              = 0x07
79
+	ANSI_LINE_FEED        = 0x0A
80
+	ANSI_CARRIAGE_RETURN  = 0x0D
81
+	ANSI_ESCAPE_PRIMARY   = 0x1B
82
+	ANSI_ESCAPE_SECONDARY = 0x5B
83
+	ANSI_OSC_STRING_ENTRY = 0x5D
84
+	ANSI_COMMAND_FIRST    = 0x40
85
+	ANSI_COMMAND_LAST     = 0x7E
86
+	DCS_ENTRY             = 0x90
87
+	CSI_ENTRY             = 0x9B
88
+	OSC_STRING            = 0x9D
89
+	ANSI_PARAMETER_SEP    = ";"
90
+	ANSI_CMD_G0           = '('
91
+	ANSI_CMD_G1           = ')'
92
+	ANSI_CMD_G2           = '*'
93
+	ANSI_CMD_G3           = '+'
94
+	ANSI_CMD_DECPNM       = '>'
95
+	ANSI_CMD_DECPAM       = '='
96
+	ANSI_CMD_OSC          = ']'
97
+	ANSI_CMD_STR_TERM     = '\\'
98
+
99
+	KEY_CONTROL_PARAM_2 = ";2"
100
+	KEY_CONTROL_PARAM_3 = ";3"
101
+	KEY_CONTROL_PARAM_4 = ";4"
102
+	KEY_CONTROL_PARAM_5 = ";5"
103
+	KEY_CONTROL_PARAM_6 = ";6"
104
+	KEY_CONTROL_PARAM_7 = ";7"
105
+	KEY_CONTROL_PARAM_8 = ";8"
106
+	KEY_ESC_CSI         = "\x1B["
107
+	KEY_ESC_N           = "\x1BN"
108
+	KEY_ESC_O           = "\x1BO"
109
+
110
+	FILL_CHARACTER = ' '
111
+)
112
+
113
+func getByteRange(start byte, end byte) []byte {
114
+	bytes := make([]byte, 0, 32)
115
+	for i := start; i <= end; i++ {
116
+		bytes = append(bytes, byte(i))
117
+	}
118
+
119
+	return bytes
120
+}
121
+
122
+var ToGroundBytes = getToGroundBytes()
123
+var Executors = getExecuteBytes()
124
+
125
+// SPACE		  20+A0 hex  Always and everywhere a blank space
126
+// Intermediate	  20-2F hex   !"#$%&'()*+,-./
127
+var Intermeds = getByteRange(0x20, 0x2F)
128
+
129
+// Parameters	  30-3F hex  0123456789:;<=>?
130
+// CSI Parameters 30-39, 3B hex 0123456789;
131
+var CsiParams = getByteRange(0x30, 0x3F)
132
+
133
+var CsiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
134
+
135
+// Uppercase	  40-5F hex  @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
136
+var UpperCase = getByteRange(0x40, 0x5F)
137
+
138
+// Lowercase	  60-7E hex  `abcdefghijlkmnopqrstuvwxyz{|}~
139
+var LowerCase = getByteRange(0x60, 0x7E)
140
+
141
+// Alphabetics	  40-7E hex  (all of upper and lower case)
142
+var Alphabetics = append(UpperCase, LowerCase...)
143
+
144
+var Printables = getByteRange(0x20, 0x7F)
145
+
146
+var EscapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
147
+var EscapeToGroundBytes = getEscapeToGroundBytes()
148
+
149
+// See http://www.vt100.net/emu/vt500_parser.png for description of the complex
150
+// byte ranges below
151
+
152
+func getEscapeToGroundBytes() []byte {
153
+	escapeToGroundBytes := getByteRange(0x30, 0x4F)
154
+	escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
155
+	escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
156
+	escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
157
+	escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
158
+	escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
159
+	return escapeToGroundBytes
160
+}
161
+
162
+func getExecuteBytes() []byte {
163
+	executeBytes := getByteRange(0x00, 0x17)
164
+	executeBytes = append(executeBytes, 0x19)
165
+	executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
166
+	return executeBytes
167
+}
168
+
169
+func getToGroundBytes() []byte {
170
+	groundBytes := []byte{0x18}
171
+	groundBytes = append(groundBytes, 0x1A)
172
+	groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
173
+	groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
174
+	groundBytes = append(groundBytes, 0x99)
175
+	groundBytes = append(groundBytes, 0x9A)
176
+	groundBytes = append(groundBytes, 0x9C)
177
+	return groundBytes
178
+}
179
+
180
+// Delete		     7F hex  Always and everywhere ignored
181
+// C1 Control	  80-9F hex  32 additional control characters
182
+// G1 Displayable A1-FE hex  94 additional displayable characters
183
+// Special		  A0+FF hex  Same as SPACE and DELETE
0 184
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+package ansiterm
1
+
2
+type AnsiContext struct {
3
+	currentChar byte
4
+	paramBuffer []byte
5
+	interBuffer []byte
6
+}
0 7
new file mode 100644
... ...
@@ -0,0 +1,49 @@
0
+package ansiterm
1
+
2
+type CsiEntryState struct {
3
+	BaseState
4
+}
5
+
6
+func (csiState CsiEntryState) Handle(b byte) (s State, e error) {
7
+	logger.Infof("CsiEntry::Handle %#x", b)
8
+
9
+	nextState, err := csiState.BaseState.Handle(b)
10
+	if nextState != nil || err != nil {
11
+		return nextState, err
12
+	}
13
+
14
+	switch {
15
+	case sliceContains(Alphabetics, b):
16
+		return csiState.parser.Ground, nil
17
+	case sliceContains(CsiCollectables, b):
18
+		return csiState.parser.CsiParam, nil
19
+	case sliceContains(Executors, b):
20
+		return csiState, csiState.parser.execute()
21
+	}
22
+
23
+	return csiState, nil
24
+}
25
+
26
+func (csiState CsiEntryState) Transition(s State) error {
27
+	logger.Infof("CsiEntry::Transition %s --> %s", csiState.Name(), s.Name())
28
+	csiState.BaseState.Transition(s)
29
+
30
+	switch s {
31
+	case csiState.parser.Ground:
32
+		return csiState.parser.csiDispatch()
33
+	case csiState.parser.CsiParam:
34
+		switch {
35
+		case sliceContains(CsiParams, csiState.parser.context.currentChar):
36
+			csiState.parser.collectParam()
37
+		case sliceContains(Intermeds, csiState.parser.context.currentChar):
38
+			csiState.parser.collectInter()
39
+		}
40
+	}
41
+
42
+	return nil
43
+}
44
+
45
+func (csiState CsiEntryState) Enter() error {
46
+	csiState.parser.clear()
47
+	return nil
48
+}
0 49
new file mode 100644
... ...
@@ -0,0 +1,38 @@
0
+package ansiterm
1
+
2
+type CsiParamState struct {
3
+	BaseState
4
+}
5
+
6
+func (csiState CsiParamState) Handle(b byte) (s State, e error) {
7
+	logger.Infof("CsiParam::Handle %#x", b)
8
+
9
+	nextState, err := csiState.BaseState.Handle(b)
10
+	if nextState != nil || err != nil {
11
+		return nextState, err
12
+	}
13
+
14
+	switch {
15
+	case sliceContains(Alphabetics, b):
16
+		return csiState.parser.Ground, nil
17
+	case sliceContains(CsiCollectables, b):
18
+		csiState.parser.collectParam()
19
+		return csiState, nil
20
+	case sliceContains(Executors, b):
21
+		return csiState, csiState.parser.execute()
22
+	}
23
+
24
+	return csiState, nil
25
+}
26
+
27
+func (csiState CsiParamState) Transition(s State) error {
28
+	logger.Infof("CsiParam::Transition %s --> %s", csiState.Name(), s.Name())
29
+	csiState.BaseState.Transition(s)
30
+
31
+	switch s {
32
+	case csiState.parser.Ground:
33
+		return csiState.parser.csiDispatch()
34
+	}
35
+
36
+	return nil
37
+}
0 38
new file mode 100644
... ...
@@ -0,0 +1,36 @@
0
+package ansiterm
1
+
2
+type EscapeIntermediateState struct {
3
+	BaseState
4
+}
5
+
6
+func (escState EscapeIntermediateState) Handle(b byte) (s State, e error) {
7
+	logger.Infof("EscapeIntermediateState::Handle %#x", b)
8
+	nextState, err := escState.BaseState.Handle(b)
9
+	if nextState != nil || err != nil {
10
+		return nextState, err
11
+	}
12
+
13
+	switch {
14
+	case sliceContains(Intermeds, b):
15
+		return escState, escState.parser.collectInter()
16
+	case sliceContains(Executors, b):
17
+		return escState, escState.parser.execute()
18
+	case sliceContains(EscapeIntermediateToGroundBytes, b):
19
+		return escState.parser.Ground, nil
20
+	}
21
+
22
+	return escState, nil
23
+}
24
+
25
+func (escState EscapeIntermediateState) Transition(s State) error {
26
+	logger.Infof("EscapeIntermediateState::Transition %s --> %s", escState.Name(), s.Name())
27
+	escState.BaseState.Transition(s)
28
+
29
+	switch s {
30
+	case escState.parser.Ground:
31
+		return escState.parser.escDispatch()
32
+	}
33
+
34
+	return nil
35
+}
0 36
new file mode 100644
... ...
@@ -0,0 +1,47 @@
0
+package ansiterm
1
+
2
+type EscapeState struct {
3
+	BaseState
4
+}
5
+
6
+func (escState EscapeState) Handle(b byte) (s State, e error) {
7
+	logger.Infof("EscapeState::Handle %#x", b)
8
+	nextState, err := escState.BaseState.Handle(b)
9
+	if nextState != nil || err != nil {
10
+		return nextState, err
11
+	}
12
+
13
+	switch {
14
+	case b == ANSI_ESCAPE_SECONDARY:
15
+		return escState.parser.CsiEntry, nil
16
+	case b == ANSI_OSC_STRING_ENTRY:
17
+		return escState.parser.OscString, nil
18
+	case sliceContains(Executors, b):
19
+		return escState, escState.parser.execute()
20
+	case sliceContains(EscapeToGroundBytes, b):
21
+		return escState.parser.Ground, nil
22
+	case sliceContains(Intermeds, b):
23
+		return escState.parser.EscapeIntermediate, nil
24
+	}
25
+
26
+	return escState, nil
27
+}
28
+
29
+func (escState EscapeState) Transition(s State) error {
30
+	logger.Infof("Escape::Transition %s --> %s", escState.Name(), s.Name())
31
+	escState.BaseState.Transition(s)
32
+
33
+	switch s {
34
+	case escState.parser.Ground:
35
+		return escState.parser.escDispatch()
36
+	case escState.parser.EscapeIntermediate:
37
+		return escState.parser.collectInter()
38
+	}
39
+
40
+	return nil
41
+}
42
+
43
+func (escState EscapeState) Enter() error {
44
+	escState.parser.clear()
45
+	return nil
46
+}
0 47
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+package ansiterm
1
+
2
+type AnsiEventHandler interface {
3
+	// Print
4
+	Print(b byte) error
5
+
6
+	// Execute C0 commands
7
+	Execute(b byte) error
8
+
9
+	// CUrsor Up
10
+	CUU(int) error
11
+
12
+	// CUrsor Down
13
+	CUD(int) error
14
+
15
+	// CUrsor Forward
16
+	CUF(int) error
17
+
18
+	// CUrsor Backward
19
+	CUB(int) error
20
+
21
+	// Cursor to Next Line
22
+	CNL(int) error
23
+
24
+	// Cursor to Previous Line
25
+	CPL(int) error
26
+
27
+	// Cursor Horizontal position Absolute
28
+	CHA(int) error
29
+
30
+	// CUrsor Position
31
+	CUP(int, int) error
32
+
33
+	// Horizontal and Vertical Position (depends on PUM)
34
+	HVP(int, int) error
35
+
36
+	// Text Cursor Enable Mode
37
+	DECTCEM(bool) error
38
+
39
+	// Erase in Display
40
+	ED(int) error
41
+
42
+	// Erase in Line
43
+	EL(int) error
44
+
45
+	// Insert Line
46
+	IL(int) error
47
+
48
+	// Delete Line
49
+	DL(int) error
50
+
51
+	// Set Graphics Rendition
52
+	SGR([]int) error
53
+
54
+	// Pan Down
55
+	SU(int) error
56
+
57
+	// Pan Up
58
+	SD(int) error
59
+
60
+	// Device Attributes
61
+	DA([]string) error
62
+
63
+	// Set Top and Bottom Margins
64
+	DECSTBM(int, int) error
65
+
66
+	// Reverse Index
67
+	RI() error
68
+
69
+	// Flush updates from previous commands
70
+	Flush() error
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package ansiterm
1
+
2
+type GroundState struct {
3
+	BaseState
4
+}
5
+
6
+func (gs GroundState) Handle(b byte) (s State, e error) {
7
+	gs.parser.context.currentChar = b
8
+
9
+	nextState, err := gs.BaseState.Handle(b)
10
+	if nextState != nil || err != nil {
11
+		return nextState, err
12
+	}
13
+
14
+	switch {
15
+	case sliceContains(Printables, b):
16
+		return gs, gs.parser.print()
17
+
18
+	case sliceContains(Executors, b):
19
+		return gs, gs.parser.execute()
20
+	}
21
+
22
+	return gs, nil
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package ansiterm
1
+
2
+type OscStringState struct {
3
+	BaseState
4
+}
5
+
6
+func (oscState OscStringState) Handle(b byte) (s State, e error) {
7
+	logger.Infof("OscString::Handle %#x", b)
8
+	nextState, err := oscState.BaseState.Handle(b)
9
+	if nextState != nil || err != nil {
10
+		return nextState, err
11
+	}
12
+
13
+	switch {
14
+	case isOscStringTerminator(b):
15
+		return oscState.parser.Ground, nil
16
+	}
17
+
18
+	return oscState, nil
19
+}
20
+
21
+// See below for OSC string terminators for linux
22
+// http://man7.org/linux/man-pages/man4/console_codes.4.html
23
+func isOscStringTerminator(b byte) bool {
24
+
25
+	if b == ANSI_BEL || b == 0x5C {
26
+		return true
27
+	}
28
+
29
+	return false
30
+}
0 31
new file mode 100644
... ...
@@ -0,0 +1,137 @@
0
+package ansiterm
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"os"
7
+
8
+	"github.com/Sirupsen/logrus"
9
+)
10
+
11
+var logger *logrus.Logger
12
+
13
+type AnsiParser struct {
14
+	currState          State
15
+	eventHandler       AnsiEventHandler
16
+	context            *AnsiContext
17
+	CsiEntry           State
18
+	CsiParam           State
19
+	DcsEntry           State
20
+	Escape             State
21
+	EscapeIntermediate State
22
+	Error              State
23
+	Ground             State
24
+	OscString          State
25
+	stateMap           []State
26
+}
27
+
28
+func CreateParser(initialState string, evtHandler AnsiEventHandler) *AnsiParser {
29
+	logFile := ioutil.Discard
30
+
31
+	if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
32
+		logFile, _ = os.Create("ansiParser.log")
33
+	}
34
+
35
+	logger = &logrus.Logger{
36
+		Out:       logFile,
37
+		Formatter: new(logrus.TextFormatter),
38
+		Level:     logrus.InfoLevel,
39
+	}
40
+
41
+	parser := &AnsiParser{
42
+		eventHandler: evtHandler,
43
+		context:      &AnsiContext{},
44
+	}
45
+
46
+	parser.CsiEntry = CsiEntryState{BaseState{name: "CsiEntry", parser: parser}}
47
+	parser.CsiParam = CsiParamState{BaseState{name: "CsiParam", parser: parser}}
48
+	parser.DcsEntry = DcsEntryState{BaseState{name: "DcsEntry", parser: parser}}
49
+	parser.Escape = EscapeState{BaseState{name: "Escape", parser: parser}}
50
+	parser.EscapeIntermediate = EscapeIntermediateState{BaseState{name: "EscapeIntermediate", parser: parser}}
51
+	parser.Error = ErrorState{BaseState{name: "Error", parser: parser}}
52
+	parser.Ground = GroundState{BaseState{name: "Ground", parser: parser}}
53
+	parser.OscString = OscStringState{BaseState{name: "OscString", parser: parser}}
54
+
55
+	parser.stateMap = []State{
56
+		parser.CsiEntry,
57
+		parser.CsiParam,
58
+		parser.DcsEntry,
59
+		parser.Escape,
60
+		parser.EscapeIntermediate,
61
+		parser.Error,
62
+		parser.Ground,
63
+		parser.OscString,
64
+	}
65
+
66
+	parser.currState = getState(initialState, parser.stateMap)
67
+
68
+	logger.Infof("CreateParser: parser %p", parser)
69
+	return parser
70
+}
71
+
72
+func getState(name string, states []State) State {
73
+	for _, el := range states {
74
+		if el.Name() == name {
75
+			return el
76
+		}
77
+	}
78
+
79
+	return nil
80
+}
81
+
82
+func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
83
+	for i, b := range bytes {
84
+		if err := ap.handle(b); err != nil {
85
+			return i, err
86
+		}
87
+	}
88
+
89
+	return len(bytes), ap.eventHandler.Flush()
90
+}
91
+
92
+func (ap *AnsiParser) handle(b byte) error {
93
+	ap.context.currentChar = b
94
+	newState, err := ap.currState.Handle(b)
95
+	if err != nil {
96
+		return err
97
+	}
98
+
99
+	if newState == nil {
100
+		logger.Warning("newState is nil")
101
+		return errors.New(fmt.Sprintf("New state of 'nil' is invalid."))
102
+	}
103
+
104
+	if newState != ap.currState {
105
+		if err := ap.changeState(newState); err != nil {
106
+			return err
107
+		}
108
+	}
109
+
110
+	return nil
111
+}
112
+
113
+func (ap *AnsiParser) changeState(newState State) error {
114
+	logger.Infof("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
115
+
116
+	// Exit old state
117
+	if err := ap.currState.Exit(); err != nil {
118
+		logger.Infof("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
119
+		return err
120
+	}
121
+
122
+	// Perform transition action
123
+	if err := ap.currState.Transition(newState); err != nil {
124
+		logger.Infof("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
125
+		return err
126
+	}
127
+
128
+	// Enter new state
129
+	if err := newState.Enter(); err != nil {
130
+		logger.Infof("Enter state '%s' failed with: '%v'", newState.Name(), err)
131
+		return err
132
+	}
133
+
134
+	ap.currState = newState
135
+	return nil
136
+}
0 137
new file mode 100644
... ...
@@ -0,0 +1,91 @@
0
+package ansiterm
1
+
2
+import (
3
+	"strconv"
4
+)
5
+
6
+func parseParams(bytes []byte) ([]string, error) {
7
+	paramBuff := make([]byte, 0, 0)
8
+	params := []string{}
9
+
10
+	for _, v := range bytes {
11
+		if v == ';' {
12
+			if len(paramBuff) > 0 {
13
+				// Completed parameter, append it to the list
14
+				s := string(paramBuff)
15
+				params = append(params, s)
16
+				paramBuff = make([]byte, 0, 0)
17
+			}
18
+		} else {
19
+			paramBuff = append(paramBuff, v)
20
+		}
21
+	}
22
+
23
+	// Last parameter may not be terminated with ';'
24
+	if len(paramBuff) > 0 {
25
+		s := string(paramBuff)
26
+		params = append(params, s)
27
+	}
28
+
29
+	logger.Infof("Parsed params: %v with length: %d", params, len(params))
30
+	return params, nil
31
+}
32
+
33
+func parseCmd(context AnsiContext) (string, error) {
34
+	return string(context.currentChar), nil
35
+}
36
+
37
+func getInt(params []string, dflt int) int {
38
+	i := getInts(params, 1, dflt)[0]
39
+	logger.Infof("getInt: %v", i)
40
+	return i
41
+}
42
+
43
+func getInts(params []string, minCount int, dflt int) []int {
44
+	ints := []int{}
45
+
46
+	for _, v := range params {
47
+		i, _ := strconv.Atoi(v)
48
+		// Zero is mapped to the default value in VT100.
49
+		if i == 0 {
50
+			i = dflt
51
+		}
52
+		ints = append(ints, i)
53
+	}
54
+
55
+	if len(ints) < minCount {
56
+		remaining := minCount - len(ints)
57
+		for i := 0; i < remaining; i++ {
58
+			ints = append(ints, dflt)
59
+		}
60
+	}
61
+
62
+	logger.Infof("getInts: %v", ints)
63
+
64
+	return ints
65
+}
66
+
67
+func (ap *AnsiParser) hDispatch(params []string) error {
68
+	if len(params) == 1 && params[0] == "?25" {
69
+		return ap.eventHandler.DECTCEM(true)
70
+	}
71
+
72
+	return nil
73
+}
74
+
75
+func (ap *AnsiParser) lDispatch(params []string) error {
76
+	if len(params) == 1 && params[0] == "?25" {
77
+		return ap.eventHandler.DECTCEM(false)
78
+	}
79
+
80
+	return nil
81
+}
82
+
83
+func getEraseParam(params []string) int {
84
+	param := getInt(params, 0)
85
+	if param < 0 || 3 < param {
86
+		param = 0
87
+	}
88
+
89
+	return param
90
+}
0 91
new file mode 100644
... ...
@@ -0,0 +1,108 @@
0
+package ansiterm
1
+
2
+import (
3
+	"fmt"
4
+)
5
+
6
+func (ap *AnsiParser) collectParam() error {
7
+	currChar := ap.context.currentChar
8
+	logger.Infof("collectParam %#x", currChar)
9
+	ap.context.paramBuffer = append(ap.context.paramBuffer, currChar)
10
+	return nil
11
+}
12
+
13
+func (ap *AnsiParser) collectInter() error {
14
+	currChar := ap.context.currentChar
15
+	logger.Infof("collectInter %#x", currChar)
16
+	ap.context.paramBuffer = append(ap.context.interBuffer, currChar)
17
+	return nil
18
+}
19
+
20
+func (ap *AnsiParser) escDispatch() error {
21
+	cmd, _ := parseCmd(*ap.context)
22
+	intermeds := ap.context.interBuffer
23
+	logger.Infof("escDispatch currentChar: %#x", ap.context.currentChar)
24
+	logger.Infof("escDispatch: %v(%v)", cmd, intermeds)
25
+
26
+	switch cmd {
27
+	case "M":
28
+		return ap.eventHandler.RI()
29
+	}
30
+
31
+	return nil
32
+}
33
+
34
+func (ap *AnsiParser) csiDispatch() error {
35
+	cmd, _ := parseCmd(*ap.context)
36
+	params, _ := parseParams(ap.context.paramBuffer)
37
+
38
+	logger.Infof("csiDispatch: %v(%v)", cmd, params)
39
+
40
+	switch cmd {
41
+	case "A":
42
+		return ap.eventHandler.CUU(getInt(params, 1))
43
+	case "B":
44
+		return ap.eventHandler.CUD(getInt(params, 1))
45
+	case "C":
46
+		return ap.eventHandler.CUF(getInt(params, 1))
47
+	case "D":
48
+		return ap.eventHandler.CUB(getInt(params, 1))
49
+	case "E":
50
+		return ap.eventHandler.CNL(getInt(params, 1))
51
+	case "F":
52
+		return ap.eventHandler.CPL(getInt(params, 1))
53
+	case "G":
54
+		return ap.eventHandler.CHA(getInt(params, 1))
55
+	case "H":
56
+		ints := getInts(params, 2, 1)
57
+		x, y := ints[0], ints[1]
58
+		return ap.eventHandler.CUP(x, y)
59
+	case "J":
60
+		param := getEraseParam(params)
61
+		return ap.eventHandler.ED(param)
62
+	case "K":
63
+		param := getEraseParam(params)
64
+		return ap.eventHandler.EL(param)
65
+	case "L":
66
+		return ap.eventHandler.IL(getInt(params, 1))
67
+	case "M":
68
+		return ap.eventHandler.DL(getInt(params, 1))
69
+	case "S":
70
+		return ap.eventHandler.SU(getInt(params, 1))
71
+	case "T":
72
+		return ap.eventHandler.SD(getInt(params, 1))
73
+	case "c":
74
+		return ap.eventHandler.DA(params)
75
+	case "f":
76
+		ints := getInts(params, 2, 1)
77
+		x, y := ints[0], ints[1]
78
+		return ap.eventHandler.HVP(x, y)
79
+	case "h":
80
+		return ap.hDispatch(params)
81
+	case "l":
82
+		return ap.lDispatch(params)
83
+	case "m":
84
+		return ap.eventHandler.SGR(getInts(params, 1, 0))
85
+	case "r":
86
+		ints := getInts(params, 2, 1)
87
+		top, bottom := ints[0], ints[1]
88
+		return ap.eventHandler.DECSTBM(top, bottom)
89
+	default:
90
+		logger.Errorf(fmt.Sprintf("Unsupported CSI command: '%s', with full context:  %v", cmd, ap.context))
91
+		return nil
92
+	}
93
+
94
+}
95
+
96
+func (ap *AnsiParser) print() error {
97
+	return ap.eventHandler.Print(ap.context.currentChar)
98
+}
99
+
100
+func (ap *AnsiParser) clear() error {
101
+	ap.context = &AnsiContext{}
102
+	return nil
103
+}
104
+
105
+func (ap *AnsiParser) execute() error {
106
+	return ap.eventHandler.Execute(ap.context.currentChar)
107
+}
0 108
new file mode 100644
... ...
@@ -0,0 +1,114 @@
0
+package ansiterm
1
+
2
+import (
3
+	"fmt"
4
+	"testing"
5
+)
6
+
7
+func getStateNames() []string {
8
+	parser, _ := createTestParser("Ground")
9
+
10
+	stateNames := []string{}
11
+	for _, state := range parser.stateMap {
12
+		stateNames = append(stateNames, state.Name())
13
+	}
14
+
15
+	return stateNames
16
+}
17
+
18
+func stateTransitionHelper(t *testing.T, start string, end string, bytes []byte) {
19
+	for _, b := range bytes {
20
+		bytes := []byte{byte(b)}
21
+		parser, _ := createTestParser(start)
22
+		parser.Parse(bytes)
23
+		validateState(t, parser.currState, end)
24
+	}
25
+}
26
+
27
+func anyToXHelper(t *testing.T, bytes []byte, expectedState string) {
28
+	for _, s := range getStateNames() {
29
+		stateTransitionHelper(t, s, expectedState, bytes)
30
+	}
31
+}
32
+
33
+func funcCallParamHelper(t *testing.T, bytes []byte, start string, expected string, expectedCalls []string) {
34
+	parser, evtHandler := createTestParser(start)
35
+	parser.Parse(bytes)
36
+	validateState(t, parser.currState, expected)
37
+	validateFuncCalls(t, evtHandler.FunctionCalls, expectedCalls)
38
+}
39
+
40
+func parseParamsHelper(t *testing.T, bytes []byte, expectedParams []string) {
41
+	params, err := parseParams(bytes)
42
+
43
+	if err != nil {
44
+		t.Errorf("Parameter parse error: %v", err)
45
+		return
46
+	}
47
+
48
+	if len(params) != len(expectedParams) {
49
+		t.Errorf("Parsed   parameters: %v", params)
50
+		t.Errorf("Expected parameters: %v", expectedParams)
51
+		t.Errorf("Parameter length failure: %d != %d", len(params), len(expectedParams))
52
+		return
53
+	}
54
+
55
+	for i, v := range expectedParams {
56
+		if v != params[i] {
57
+			t.Errorf("Parsed   parameters: %v", params)
58
+			t.Errorf("Expected parameters: %v", expectedParams)
59
+			t.Errorf("Parameter parse failure: %s != %s at position %d", v, params[i], i)
60
+		}
61
+	}
62
+}
63
+
64
+func cursorSingleParamHelper(t *testing.T, command byte, funcName string) {
65
+	funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
66
+	funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
67
+	funcCallParamHelper(t, []byte{'2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
68
+	funcCallParamHelper(t, []byte{'2', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([23])", funcName)})
69
+	funcCallParamHelper(t, []byte{'2', ';', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
70
+	funcCallParamHelper(t, []byte{'2', ';', '3', ';', '4', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
71
+}
72
+
73
+func cursorTwoParamHelper(t *testing.T, command byte, funcName string) {
74
+	funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1 1])", funcName)})
75
+	funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1 1])", funcName)})
76
+	funcCallParamHelper(t, []byte{'2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2 1])", funcName)})
77
+	funcCallParamHelper(t, []byte{'2', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([23 1])", funcName)})
78
+	funcCallParamHelper(t, []byte{'2', ';', '3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2 3])", funcName)})
79
+	funcCallParamHelper(t, []byte{'2', ';', '3', ';', '4', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2 3])", funcName)})
80
+}
81
+
82
+func eraseHelper(t *testing.T, command byte, funcName string) {
83
+	funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([0])", funcName)})
84
+	funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([0])", funcName)})
85
+	funcCallParamHelper(t, []byte{'1', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
86
+	funcCallParamHelper(t, []byte{'2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([2])", funcName)})
87
+	funcCallParamHelper(t, []byte{'3', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([3])", funcName)})
88
+	funcCallParamHelper(t, []byte{'4', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([0])", funcName)})
89
+	funcCallParamHelper(t, []byte{'1', ';', '2', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
90
+}
91
+
92
+func scrollHelper(t *testing.T, command byte, funcName string) {
93
+	funcCallParamHelper(t, []byte{command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
94
+	funcCallParamHelper(t, []byte{'0', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
95
+	funcCallParamHelper(t, []byte{'1', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([1])", funcName)})
96
+	funcCallParamHelper(t, []byte{'5', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([5])", funcName)})
97
+	funcCallParamHelper(t, []byte{'4', ';', '6', command}, "CsiEntry", "Ground", []string{fmt.Sprintf("%s([4])", funcName)})
98
+}
99
+
100
+func clearOnStateChangeHelper(t *testing.T, start string, end string, bytes []byte) {
101
+	p, _ := createTestParser(start)
102
+	fillContext(p.context)
103
+	p.Parse(bytes)
104
+	validateState(t, p.currState, end)
105
+	validateEmptyContext(t, p.context)
106
+}
107
+
108
+func c0Helper(t *testing.T, bytes []byte, expectedState string, expectedCalls []string) {
109
+	parser, evtHandler := createTestParser("Ground")
110
+	parser.Parse(bytes)
111
+	validateState(t, parser.currState, expectedState)
112
+	validateFuncCalls(t, evtHandler.FunctionCalls, expectedCalls)
113
+}
0 114
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+package ansiterm
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func createTestParser(s string) (*AnsiParser, *TestAnsiEventHandler) {
7
+	evtHandler := CreateTestAnsiEventHandler()
8
+	parser := CreateParser(s, evtHandler)
9
+
10
+	return parser, evtHandler
11
+}
12
+
13
+func validateState(t *testing.T, actualState State, expectedStateName string) {
14
+	actualName := "Nil"
15
+
16
+	if actualState != nil {
17
+		actualName = actualState.Name()
18
+	}
19
+
20
+	if actualName != expectedStateName {
21
+		t.Errorf("Invalid State: '%s' != '%s'", actualName, expectedStateName)
22
+	}
23
+}
24
+
25
+func validateFuncCalls(t *testing.T, actualCalls []string, expectedCalls []string) {
26
+	actualCount := len(actualCalls)
27
+	expectedCount := len(expectedCalls)
28
+
29
+	if actualCount != expectedCount {
30
+		t.Errorf("Actual   calls: %v", actualCalls)
31
+		t.Errorf("Expected calls: %v", expectedCalls)
32
+		t.Errorf("Call count error: %d != %d", actualCount, expectedCount)
33
+		return
34
+	}
35
+
36
+	for i, v := range actualCalls {
37
+		if v != expectedCalls[i] {
38
+			t.Errorf("Actual   calls: %v", actualCalls)
39
+			t.Errorf("Expected calls: %v", expectedCalls)
40
+			t.Errorf("Mismatched calls: %s != %s with lengths %d and %d", v, expectedCalls[i], len(v), len(expectedCalls[i]))
41
+		}
42
+	}
43
+}
44
+
45
+func fillContext(context *AnsiContext) {
46
+	context.currentChar = 'A'
47
+	context.paramBuffer = []byte{'C', 'D', 'E'}
48
+	context.interBuffer = []byte{'F', 'G', 'H'}
49
+}
50
+
51
+func validateEmptyContext(t *testing.T, context *AnsiContext) {
52
+	var expectedCurrChar byte = 0x0
53
+	if context.currentChar != expectedCurrChar {
54
+		t.Errorf("Currentchar mismatch '%#x' != '%#x'", context.currentChar, expectedCurrChar)
55
+	}
56
+
57
+	if len(context.paramBuffer) != 0 {
58
+		t.Errorf("Non-empty parameter buffer: %v", context.paramBuffer)
59
+	}
60
+
61
+	if len(context.paramBuffer) != 0 {
62
+		t.Errorf("Non-empty intermediate buffer: %v", context.interBuffer)
63
+	}
64
+
65
+}
0 66
new file mode 100644
... ...
@@ -0,0 +1,71 @@
0
+package ansiterm
1
+
2
+type StateId int
3
+
4
+type State interface {
5
+	Enter() error
6
+	Exit() error
7
+	Handle(byte) (State, error)
8
+	Name() string
9
+	Transition(State) error
10
+}
11
+
12
+type BaseState struct {
13
+	name   string
14
+	parser *AnsiParser
15
+}
16
+
17
+func (base BaseState) Enter() error {
18
+	return nil
19
+}
20
+
21
+func (base BaseState) Exit() error {
22
+	return nil
23
+}
24
+
25
+func (base BaseState) Handle(b byte) (s State, e error) {
26
+
27
+	switch {
28
+	case b == CSI_ENTRY:
29
+		return base.parser.CsiEntry, nil
30
+	case b == DCS_ENTRY:
31
+		return base.parser.DcsEntry, nil
32
+	case b == ANSI_ESCAPE_PRIMARY:
33
+		return base.parser.Escape, nil
34
+	case b == OSC_STRING:
35
+		return base.parser.OscString, nil
36
+	case sliceContains(ToGroundBytes, b):
37
+		return base.parser.Ground, nil
38
+	}
39
+
40
+	return nil, nil
41
+}
42
+
43
+func (base BaseState) Name() string {
44
+	return base.name
45
+}
46
+
47
+func (base BaseState) Transition(s State) error {
48
+	if s == base.parser.Ground {
49
+		execBytes := []byte{0x18}
50
+		execBytes = append(execBytes, 0x1A)
51
+		execBytes = append(execBytes, getByteRange(0x80, 0x8F)...)
52
+		execBytes = append(execBytes, getByteRange(0x91, 0x97)...)
53
+		execBytes = append(execBytes, 0x99)
54
+		execBytes = append(execBytes, 0x9A)
55
+
56
+		if sliceContains(execBytes, base.parser.context.currentChar) {
57
+			return base.parser.execute()
58
+		}
59
+	}
60
+
61
+	return nil
62
+}
63
+
64
+type DcsEntryState struct {
65
+	BaseState
66
+}
67
+
68
+type ErrorState struct {
69
+	BaseState
70
+}
0 71
new file mode 100644
... ...
@@ -0,0 +1,143 @@
0
+package ansiterm
1
+
2
+import (
3
+	"fmt"
4
+	"strconv"
5
+)
6
+
7
+type TestAnsiEventHandler struct {
8
+	FunctionCalls []string
9
+}
10
+
11
+func CreateTestAnsiEventHandler() *TestAnsiEventHandler {
12
+	evtHandler := TestAnsiEventHandler{}
13
+	evtHandler.FunctionCalls = make([]string, 0)
14
+	return &evtHandler
15
+}
16
+
17
+func (h *TestAnsiEventHandler) recordCall(call string, params []string) {
18
+	s := fmt.Sprintf("%s(%v)", call, params)
19
+	h.FunctionCalls = append(h.FunctionCalls, s)
20
+}
21
+
22
+func (h *TestAnsiEventHandler) Print(b byte) error {
23
+	h.recordCall("Print", []string{string(b)})
24
+	return nil
25
+}
26
+
27
+func (h *TestAnsiEventHandler) Execute(b byte) error {
28
+	h.recordCall("Execute", []string{string(b)})
29
+	return nil
30
+}
31
+
32
+func (h *TestAnsiEventHandler) CUU(param int) error {
33
+	h.recordCall("CUU", []string{strconv.Itoa(param)})
34
+	return nil
35
+}
36
+
37
+func (h *TestAnsiEventHandler) CUD(param int) error {
38
+	h.recordCall("CUD", []string{strconv.Itoa(param)})
39
+	return nil
40
+}
41
+
42
+func (h *TestAnsiEventHandler) CUF(param int) error {
43
+	h.recordCall("CUF", []string{strconv.Itoa(param)})
44
+	return nil
45
+}
46
+
47
+func (h *TestAnsiEventHandler) CUB(param int) error {
48
+	h.recordCall("CUB", []string{strconv.Itoa(param)})
49
+	return nil
50
+}
51
+
52
+func (h *TestAnsiEventHandler) CNL(param int) error {
53
+	h.recordCall("CNL", []string{strconv.Itoa(param)})
54
+	return nil
55
+}
56
+
57
+func (h *TestAnsiEventHandler) CPL(param int) error {
58
+	h.recordCall("CPL", []string{strconv.Itoa(param)})
59
+	return nil
60
+}
61
+
62
+func (h *TestAnsiEventHandler) CHA(param int) error {
63
+	h.recordCall("CHA", []string{strconv.Itoa(param)})
64
+	return nil
65
+}
66
+
67
+func (h *TestAnsiEventHandler) CUP(x int, y int) error {
68
+	xS, yS := strconv.Itoa(x), strconv.Itoa(y)
69
+	h.recordCall("CUP", []string{xS, yS})
70
+	return nil
71
+}
72
+
73
+func (h *TestAnsiEventHandler) HVP(x int, y int) error {
74
+	xS, yS := strconv.Itoa(x), strconv.Itoa(y)
75
+	h.recordCall("HVP", []string{xS, yS})
76
+	return nil
77
+}
78
+
79
+func (h *TestAnsiEventHandler) DECTCEM(visible bool) error {
80
+	h.recordCall("DECTCEM", []string{strconv.FormatBool(visible)})
81
+	return nil
82
+}
83
+
84
+func (h *TestAnsiEventHandler) ED(param int) error {
85
+	h.recordCall("ED", []string{strconv.Itoa(param)})
86
+	return nil
87
+}
88
+
89
+func (h *TestAnsiEventHandler) EL(param int) error {
90
+	h.recordCall("EL", []string{strconv.Itoa(param)})
91
+	return nil
92
+}
93
+
94
+func (h *TestAnsiEventHandler) IL(param int) error {
95
+	h.recordCall("IL", []string{strconv.Itoa(param)})
96
+	return nil
97
+}
98
+
99
+func (h *TestAnsiEventHandler) DL(param int) error {
100
+	h.recordCall("DL", []string{strconv.Itoa(param)})
101
+	return nil
102
+}
103
+
104
+func (h *TestAnsiEventHandler) SGR(params []int) error {
105
+	strings := []string{}
106
+	for _, v := range params {
107
+		strings = append(strings, strconv.Itoa(v))
108
+	}
109
+
110
+	h.recordCall("SGR", strings)
111
+	return nil
112
+}
113
+
114
+func (h *TestAnsiEventHandler) SU(param int) error {
115
+	h.recordCall("SU", []string{strconv.Itoa(param)})
116
+	return nil
117
+}
118
+
119
+func (h *TestAnsiEventHandler) SD(param int) error {
120
+	h.recordCall("SD", []string{strconv.Itoa(param)})
121
+	return nil
122
+}
123
+
124
+func (h *TestAnsiEventHandler) DA(params []string) error {
125
+	h.recordCall("DA", params)
126
+	return nil
127
+}
128
+
129
+func (h *TestAnsiEventHandler) DECSTBM(top int, bottom int) error {
130
+	topS, bottomS := strconv.Itoa(top), strconv.Itoa(bottom)
131
+	h.recordCall("DECSTBM", []string{topS, bottomS})
132
+	return nil
133
+}
134
+
135
+func (h *TestAnsiEventHandler) RI() error {
136
+	h.recordCall("RI", nil)
137
+	return nil
138
+}
139
+
140
+func (h *TestAnsiEventHandler) Flush() error {
141
+	return nil
142
+}
0 143
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package ansiterm
1
+
2
+import (
3
+	"strconv"
4
+)
5
+
6
+func sliceContains(bytes []byte, b byte) bool {
7
+	for _, v := range bytes {
8
+		if v == b {
9
+			return true
10
+		}
11
+	}
12
+
13
+	return false
14
+}
15
+
16
+func convertBytesToInteger(bytes []byte) int {
17
+	s := string(bytes)
18
+	i, _ := strconv.Atoi(s)
19
+	return i
20
+}
0 21
new file mode 100644
... ...
@@ -0,0 +1,182 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+import (
5
+	"fmt"
6
+	"os"
7
+	"strconv"
8
+	"strings"
9
+	"syscall"
10
+
11
+	. "github.com/docker/docker/vendor/src/github.com/Azure/go-ansiterm"
12
+)
13
+
14
+// Windows keyboard constants
15
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx.
16
+const (
17
+	VK_PRIOR    = 0x21 // PAGE UP key
18
+	VK_NEXT     = 0x22 // PAGE DOWN key
19
+	VK_END      = 0x23 // END key
20
+	VK_HOME     = 0x24 // HOME key
21
+	VK_LEFT     = 0x25 // LEFT ARROW key
22
+	VK_UP       = 0x26 // UP ARROW key
23
+	VK_RIGHT    = 0x27 // RIGHT ARROW key
24
+	VK_DOWN     = 0x28 // DOWN ARROW key
25
+	VK_SELECT   = 0x29 // SELECT key
26
+	VK_PRINT    = 0x2A // PRINT key
27
+	VK_EXECUTE  = 0x2B // EXECUTE key
28
+	VK_SNAPSHOT = 0x2C // PRINT SCREEN key
29
+	VK_INSERT   = 0x2D // INS key
30
+	VK_DELETE   = 0x2E // DEL key
31
+	VK_HELP     = 0x2F // HELP key
32
+	VK_F1       = 0x70 // F1 key
33
+	VK_F2       = 0x71 // F2 key
34
+	VK_F3       = 0x72 // F3 key
35
+	VK_F4       = 0x73 // F4 key
36
+	VK_F5       = 0x74 // F5 key
37
+	VK_F6       = 0x75 // F6 key
38
+	VK_F7       = 0x76 // F7 key
39
+	VK_F8       = 0x77 // F8 key
40
+	VK_F9       = 0x78 // F9 key
41
+	VK_F10      = 0x79 // F10 key
42
+	VK_F11      = 0x7A // F11 key
43
+	VK_F12      = 0x7B // F12 key
44
+
45
+	RIGHT_ALT_PRESSED  = 0x0001
46
+	LEFT_ALT_PRESSED   = 0x0002
47
+	RIGHT_CTRL_PRESSED = 0x0004
48
+	LEFT_CTRL_PRESSED  = 0x0008
49
+	SHIFT_PRESSED      = 0x0010
50
+	NUMLOCK_ON         = 0x0020
51
+	SCROLLLOCK_ON      = 0x0040
52
+	CAPSLOCK_ON        = 0x0080
53
+	ENHANCED_KEY       = 0x0100
54
+)
55
+
56
+type ansiCommand struct {
57
+	CommandBytes []byte
58
+	Command      string
59
+	Parameters   []string
60
+	IsSpecial    bool
61
+}
62
+
63
+func newAnsiCommand(command []byte) *ansiCommand {
64
+
65
+	if isCharacterSelectionCmdChar(command[1]) {
66
+		// Is Character Set Selection commands
67
+		return &ansiCommand{
68
+			CommandBytes: command,
69
+			Command:      string(command),
70
+			IsSpecial:    true,
71
+		}
72
+	}
73
+
74
+	// last char is command character
75
+	lastCharIndex := len(command) - 1
76
+
77
+	ac := &ansiCommand{
78
+		CommandBytes: command,
79
+		Command:      string(command[lastCharIndex]),
80
+		IsSpecial:    false,
81
+	}
82
+
83
+	// more than a single escape
84
+	if lastCharIndex != 0 {
85
+		start := 1
86
+		// skip if double char escape sequence
87
+		if command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_ESCAPE_SECONDARY {
88
+			start++
89
+		}
90
+		// convert this to GetNextParam method
91
+		ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ANSI_PARAMETER_SEP)
92
+	}
93
+
94
+	return ac
95
+}
96
+
97
+func (ac *ansiCommand) paramAsSHORT(index int, defaultValue SHORT) SHORT {
98
+	if index < 0 || index >= len(ac.Parameters) {
99
+		return defaultValue
100
+	}
101
+
102
+	param, err := strconv.ParseInt(ac.Parameters[index], 10, 16)
103
+	if err != nil {
104
+		return defaultValue
105
+	}
106
+
107
+	return SHORT(param)
108
+}
109
+
110
+func (ac *ansiCommand) String() string {
111
+	return fmt.Sprintf("0x%v \"%v\" (\"%v\")",
112
+		bytesToHex(ac.CommandBytes),
113
+		ac.Command,
114
+		strings.Join(ac.Parameters, "\",\""))
115
+}
116
+
117
+// isAnsiCommandChar returns true if the passed byte falls within the range of ANSI commands.
118
+// See http://manpages.ubuntu.com/manpages/intrepid/man4/console_codes.4.html.
119
+func isAnsiCommandChar(b byte) bool {
120
+	switch {
121
+	case ANSI_COMMAND_FIRST <= b && b <= ANSI_COMMAND_LAST && b != ANSI_ESCAPE_SECONDARY:
122
+		return true
123
+	case b == ANSI_CMD_G1 || b == ANSI_CMD_OSC || b == ANSI_CMD_DECPAM || b == ANSI_CMD_DECPNM:
124
+		// non-CSI escape sequence terminator
125
+		return true
126
+	case b == ANSI_CMD_STR_TERM || b == ANSI_BEL:
127
+		// String escape sequence terminator
128
+		return true
129
+	}
130
+	return false
131
+}
132
+
133
+func isXtermOscSequence(command []byte, current byte) bool {
134
+	return (len(command) >= 2 && command[0] == ANSI_ESCAPE_PRIMARY && command[1] == ANSI_CMD_OSC && current != ANSI_BEL)
135
+}
136
+
137
+func isCharacterSelectionCmdChar(b byte) bool {
138
+	return (b == ANSI_CMD_G0 || b == ANSI_CMD_G1 || b == ANSI_CMD_G2 || b == ANSI_CMD_G3)
139
+}
140
+
141
+// bytesToHex converts a slice of bytes to a human-readable string.
142
+func bytesToHex(b []byte) string {
143
+	hex := make([]string, len(b))
144
+	for i, ch := range b {
145
+		hex[i] = fmt.Sprintf("%X", ch)
146
+	}
147
+	return strings.Join(hex, "")
148
+}
149
+
150
+// ensureInRange adjusts the passed value, if necessary, to ensure it is within
151
+// the passed min / max range.
152
+func ensureInRange(n SHORT, min SHORT, max SHORT) SHORT {
153
+	if n < min {
154
+		return min
155
+	} else if n > max {
156
+		return max
157
+	} else {
158
+		return n
159
+	}
160
+}
161
+
162
+func GetStdFile(nFile int) (*os.File, uintptr) {
163
+	var file *os.File
164
+	switch nFile {
165
+	case syscall.STD_INPUT_HANDLE:
166
+		file = os.Stdin
167
+	case syscall.STD_OUTPUT_HANDLE:
168
+		file = os.Stdout
169
+	case syscall.STD_ERROR_HANDLE:
170
+		file = os.Stderr
171
+	default:
172
+		panic(fmt.Errorf("Invalid standard handle identifier: %v", nFile))
173
+	}
174
+
175
+	fd, err := syscall.GetStdHandle(nFile)
176
+	if err != nil {
177
+		panic(fmt.Errorf("Invalid standard handle indentifier: %v -- %v", nFile, err))
178
+	}
179
+
180
+	return file, uintptr(fd)
181
+}
0 182
new file mode 100644
... ...
@@ -0,0 +1,329 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+import (
5
+	"fmt"
6
+	"syscall"
7
+	"unsafe"
8
+)
9
+
10
+//===========================================================================================================
11
+// IMPORTANT NOTE:
12
+//
13
+//	The methods below make extensive use of the "unsafe" package to obtain the required pointers.
14
+//	Beginning in Go 1.3, the garbage collector may release local variables (e.g., incoming arguments, stack
15
+//	variables) the pointers reference *before* the API completes.
16
+//
17
+//  As a result, in those cases, the code must hint that the variables remain in active by invoking the
18
+//	dummy method "use" (see below). Newer versions of Go are planned to change the mechanism to no longer
19
+//	require unsafe pointers.
20
+//
21
+//	If you add or modify methods, ENSURE protection of local variables through the "use" builtin to inform
22
+//	the garbage collector the variables remain in use if:
23
+//
24
+//	-- The value is not a pointer (e.g., int32, struct)
25
+//	-- The value is not referenced by the method after passing the pointer to Windows
26
+//
27
+//	See http://golang.org/doc/go1.3.
28
+//===========================================================================================================
29
+
30
+var (
31
+	kernel32DLL = syscall.NewLazyDLL("kernel32.dll")
32
+
33
+	getConsoleCursorInfoProc       = kernel32DLL.NewProc("GetConsoleCursorInfo")
34
+	setConsoleCursorInfoProc       = kernel32DLL.NewProc("SetConsoleCursorInfo")
35
+	setConsoleCursorPositionProc   = kernel32DLL.NewProc("SetConsoleCursorPosition")
36
+	setConsoleModeProc             = kernel32DLL.NewProc("SetConsoleMode")
37
+	getConsoleScreenBufferInfoProc = kernel32DLL.NewProc("GetConsoleScreenBufferInfo")
38
+	setConsoleScreenBufferSizeProc = kernel32DLL.NewProc("SetConsoleScreenBufferSize")
39
+	scrollConsoleScreenBufferProc  = kernel32DLL.NewProc("ScrollConsoleScreenBufferA")
40
+	setConsoleTextAttributeProc    = kernel32DLL.NewProc("SetConsoleTextAttribute")
41
+	setConsoleWindowInfoProc       = kernel32DLL.NewProc("SetConsoleWindowInfo")
42
+	writeConsoleOutputProc         = kernel32DLL.NewProc("WriteConsoleOutputW")
43
+	readConsoleInputProc           = kernel32DLL.NewProc("ReadConsoleInputW")
44
+	waitForSingleObjectProc        = kernel32DLL.NewProc("WaitForSingleObject")
45
+)
46
+
47
+// Windows Console constants
48
+const (
49
+	// Console modes
50
+	// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
51
+	ENABLE_PROCESSED_INPUT = 0x0001
52
+	ENABLE_LINE_INPUT      = 0x0002
53
+	ENABLE_ECHO_INPUT      = 0x0004
54
+	ENABLE_WINDOW_INPUT    = 0x0008
55
+	ENABLE_MOUSE_INPUT     = 0x0010
56
+	ENABLE_INSERT_MODE     = 0x0020
57
+	ENABLE_QUICK_EDIT_MODE = 0x0040
58
+	ENABLE_EXTENDED_FLAGS  = 0x0080
59
+
60
+	ENABLE_PROCESSED_OUTPUT   = 0x0001
61
+	ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002
62
+
63
+	// Character attributes
64
+	// Note:
65
+	// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
66
+	//    Clearing all foreground or background colors results in black; setting all creates white.
67
+	// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
68
+	FOREGROUND_BLUE      WORD = 0x0001
69
+	FOREGROUND_GREEN     WORD = 0x0002
70
+	FOREGROUND_RED       WORD = 0x0004
71
+	FOREGROUND_INTENSITY WORD = 0x0008
72
+	FOREGROUND_MASK      WORD = 0x000F
73
+
74
+	BACKGROUND_BLUE      WORD = 0x0010
75
+	BACKGROUND_GREEN     WORD = 0x0020
76
+	BACKGROUND_RED       WORD = 0x0040
77
+	BACKGROUND_INTENSITY WORD = 0x0080
78
+	BACKGROUND_MASK      WORD = 0x00F0
79
+
80
+	COMMON_LVB_MASK          WORD = 0xFF00
81
+	COMMON_LVB_REVERSE_VIDEO WORD = 0x4000
82
+	COMMON_LVB_UNDERSCORE    WORD = 0x8000
83
+
84
+	// Input event types
85
+	// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
86
+	KEY_EVENT                = 0x0001
87
+	MOUSE_EVENT              = 0x0002
88
+	WINDOW_BUFFER_SIZE_EVENT = 0x0004
89
+	MENU_EVENT               = 0x0008
90
+	FOCUS_EVENT              = 0x0010
91
+
92
+	// WaitForSingleObject return codes
93
+	WAIT_ABANDONED = 0x00000080
94
+	WAIT_FAILED    = 0xFFFFFFFF
95
+	WAIT_SIGNALED  = 0x0000000
96
+	WAIT_TIMEOUT   = 0x00000102
97
+
98
+	// WaitForSingleObject wait duration
99
+	WAIT_INFINITE       = 0xFFFFFFFF
100
+	WAIT_ONE_SECOND     = 1000
101
+	WAIT_HALF_SECOND    = 500
102
+	WAIT_QUARTER_SECOND = 250
103
+)
104
+
105
+// Windows API Console types
106
+// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx for core types (e.g., SHORT)
107
+// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
108
+// -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
109
+type (
110
+	SHORT int16
111
+	BOOL  int32
112
+	WORD  uint16
113
+	WCHAR uint16
114
+	DWORD uint32
115
+
116
+	CHAR_INFO struct {
117
+		UnicodeChar WCHAR
118
+		Attributes  WORD
119
+	}
120
+
121
+	CONSOLE_CURSOR_INFO struct {
122
+		Size    DWORD
123
+		Visible BOOL
124
+	}
125
+
126
+	CONSOLE_SCREEN_BUFFER_INFO struct {
127
+		Size              COORD
128
+		CursorPosition    COORD
129
+		Attributes        WORD
130
+		Window            SMALL_RECT
131
+		MaximumWindowSize COORD
132
+	}
133
+
134
+	COORD struct {
135
+		X SHORT
136
+		Y SHORT
137
+	}
138
+
139
+	SMALL_RECT struct {
140
+		Left   SHORT
141
+		Top    SHORT
142
+		Right  SHORT
143
+		Bottom SHORT
144
+	}
145
+
146
+	// INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
147
+	// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
148
+	INPUT_RECORD struct {
149
+		EventType WORD
150
+		KeyEvent  KEY_EVENT_RECORD
151
+	}
152
+
153
+	KEY_EVENT_RECORD struct {
154
+		KeyDown         BOOL
155
+		RepeatCount     WORD
156
+		VirtualKeyCode  WORD
157
+		VirtualScanCode WORD
158
+		UnicodeChar     WCHAR
159
+		ControlKeyState DWORD
160
+	}
161
+
162
+	WINDOW_BUFFER_SIZE struct {
163
+		Size COORD
164
+	}
165
+)
166
+
167
+// boolToBOOL converts a Go bool into a Windows BOOL.
168
+func boolToBOOL(f bool) BOOL {
169
+	if f {
170
+		return BOOL(1)
171
+	} else {
172
+		return BOOL(0)
173
+	}
174
+}
175
+
176
+// GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
177
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
178
+func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
179
+	r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
180
+	return checkError(r1, r2, err)
181
+}
182
+
183
+// SetConsoleCursorInfo sets the size and visiblity of the console cursor.
184
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
185
+func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
186
+	r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
187
+	return checkError(r1, r2, err)
188
+}
189
+
190
+// SetConsoleCursorPosition location of the console cursor.
191
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
192
+func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
193
+	r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
194
+	use(coord)
195
+	return checkError(r1, r2, err)
196
+}
197
+
198
+// GetConsoleMode gets the console mode for given file descriptor
199
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
200
+func GetConsoleMode(handle uintptr) (mode uint32, err error) {
201
+	err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
202
+	return mode, err
203
+}
204
+
205
+// SetConsoleMode sets the console mode for given file descriptor
206
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
207
+func SetConsoleMode(handle uintptr, mode uint32) error {
208
+	r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
209
+	use(mode)
210
+	return checkError(r1, r2, err)
211
+}
212
+
213
+// GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
214
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
215
+func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
216
+	info := CONSOLE_SCREEN_BUFFER_INFO{}
217
+	err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
218
+	if err != nil {
219
+		return nil, err
220
+	}
221
+	return &info, nil
222
+}
223
+
224
+func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
225
+	r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
226
+	use(scrollRect)
227
+	use(clipRect)
228
+	use(destOrigin)
229
+	use(char)
230
+	return checkError(r1, r2, err)
231
+}
232
+
233
+// SetConsoleScreenBufferSize sets the size of the console screen buffer.
234
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
235
+func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
236
+	r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
237
+	use(coord)
238
+	return checkError(r1, r2, err)
239
+}
240
+
241
+// SetConsoleTextAttribute sets the attributes of characters written to the
242
+// console screen buffer by the WriteFile or WriteConsole function.
243
+// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
244
+func SetConsoleTextAttribute(handle uintptr, attribute WORD) error {
245
+	r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
246
+	use(attribute)
247
+	return checkError(r1, r2, err)
248
+}
249
+
250
+// SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
251
+// Note that the size and location must be within and no larger than the backing console screen buffer.
252
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
253
+func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
254
+	r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
255
+	use(isAbsolute)
256
+	use(rect)
257
+	return checkError(r1, r2, err)
258
+}
259
+
260
+// WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
261
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
262
+func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
263
+	r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
264
+	use(buffer)
265
+	use(bufferSize)
266
+	use(bufferCoord)
267
+	return checkError(r1, r2, err)
268
+}
269
+
270
+// ReadConsoleInput reads (and removes) data from the console input buffer.
271
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
272
+func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
273
+	r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
274
+	use(buffer)
275
+	return checkError(r1, r2, err)
276
+}
277
+
278
+// WaitForSingleObject waits for the passed handle to be signaled.
279
+// It returns true if the handle was signaled; false otherwise.
280
+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
281
+func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
282
+	r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(DWORD(msWait)))
283
+	switch r1 {
284
+	case WAIT_ABANDONED, WAIT_TIMEOUT:
285
+		return false, nil
286
+	case WAIT_SIGNALED:
287
+		return true, nil
288
+	}
289
+	use(msWait)
290
+	return false, err
291
+}
292
+
293
+// String helpers
294
+func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
295
+	return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
296
+}
297
+
298
+func (coord COORD) String() string {
299
+	return fmt.Sprintf("%v,%v", coord.X, coord.Y)
300
+}
301
+
302
+func (rect SMALL_RECT) String() string {
303
+	return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
304
+}
305
+
306
+// checkError evaluates the results of a Windows API call and returns the error if it failed.
307
+func checkError(r1, r2 uintptr, err error) error {
308
+	// Windows APIs return non-zero to indicate success
309
+	if r1 != 0 {
310
+		return nil
311
+	}
312
+
313
+	// Return the error if provided, otherwise default to EINVAL
314
+	if err != nil {
315
+		return err
316
+	}
317
+	return syscall.EINVAL
318
+}
319
+
320
+// coordToPointer converts a COORD into a uintptr (by fooling the type system).
321
+func coordToPointer(c COORD) uintptr {
322
+	// Note: This code assumes the two SHORTs are correctly laid out; the "cast" to DWORD is just to get a pointer to pass.
323
+	return uintptr(*((*DWORD)(unsafe.Pointer(&c))))
324
+}
325
+
326
+// use is a no-op, but the compiler cannot see that it is.
327
+// Calling use(p) ensures that p is kept live until that point.
328
+func use(p interface{}) {}
0 329
new file mode 100644
... ...
@@ -0,0 +1,95 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+import (
5
+	. "github.com/Azure/go-ansiterm"
6
+)
7
+
8
+const (
9
+	FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
10
+	BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
11
+)
12
+
13
+// collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
14
+// request represented by the passed ANSI mode.
15
+func collectAnsiIntoWindowsAttributes(windowsMode WORD, baseMode WORD, ansiMode SHORT) WORD {
16
+	switch ansiMode {
17
+
18
+	// Mode styles
19
+	case ANSI_SGR_BOLD:
20
+		windowsMode = windowsMode | FOREGROUND_INTENSITY
21
+
22
+	case ANSI_SGR_DIM, ANSI_SGR_BOLD_DIM_OFF:
23
+		windowsMode &^= FOREGROUND_INTENSITY
24
+
25
+	case ANSI_SGR_UNDERLINE:
26
+		windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
27
+
28
+	case ANSI_SGR_REVERSE, ANSI_SGR_REVERSE_OFF:
29
+		// Note: Windows does not support a native reverse. Simply swap the foreground / background color / intensity.
30
+		windowsMode = (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
31
+
32
+	case ANSI_SGR_UNDERLINE_OFF:
33
+		windowsMode &^= COMMON_LVB_UNDERSCORE
34
+
35
+		// Foreground colors
36
+	case ANSI_SGR_FOREGROUND_DEFAULT:
37
+		windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
38
+
39
+	case ANSI_SGR_FOREGROUND_BLACK:
40
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
41
+
42
+	case ANSI_SGR_FOREGROUND_RED:
43
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
44
+
45
+	case ANSI_SGR_FOREGROUND_GREEN:
46
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
47
+
48
+	case ANSI_SGR_FOREGROUND_YELLOW:
49
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
50
+
51
+	case ANSI_SGR_FOREGROUND_BLUE:
52
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
53
+
54
+	case ANSI_SGR_FOREGROUND_MAGENTA:
55
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
56
+
57
+	case ANSI_SGR_FOREGROUND_CYAN:
58
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
59
+
60
+	case ANSI_SGR_FOREGROUND_WHITE:
61
+		windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
62
+
63
+		// Background colors
64
+	case ANSI_SGR_BACKGROUND_DEFAULT:
65
+		// Black with no intensity
66
+		windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
67
+
68
+	case ANSI_SGR_BACKGROUND_BLACK:
69
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
70
+
71
+	case ANSI_SGR_BACKGROUND_RED:
72
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
73
+
74
+	case ANSI_SGR_BACKGROUND_GREEN:
75
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
76
+
77
+	case ANSI_SGR_BACKGROUND_YELLOW:
78
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
79
+
80
+	case ANSI_SGR_BACKGROUND_BLUE:
81
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
82
+
83
+	case ANSI_SGR_BACKGROUND_MAGENTA:
84
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
85
+
86
+	case ANSI_SGR_BACKGROUND_CYAN:
87
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
88
+
89
+	case ANSI_SGR_BACKGROUND_WHITE:
90
+		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
91
+	}
92
+
93
+	return windowsMode
94
+}
0 95
new file mode 100644
... ...
@@ -0,0 +1,79 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+const (
5
+	Horizontal = iota
6
+	Vertical
7
+)
8
+
9
+// setCursorPosition sets the cursor to the specified position, bounded to the buffer size
10
+func (h *WindowsAnsiEventHandler) setCursorPosition(position COORD, sizeBuffer COORD) error {
11
+	position.X = ensureInRange(position.X, 0, sizeBuffer.X-1)
12
+	position.Y = ensureInRange(position.Y, 0, sizeBuffer.Y-1)
13
+	return SetConsoleCursorPosition(h.fd, position)
14
+}
15
+
16
+func (h *WindowsAnsiEventHandler) moveCursorVertical(param int) error {
17
+	return h.moveCursor(Vertical, param)
18
+}
19
+
20
+func (h *WindowsAnsiEventHandler) moveCursorHorizontal(param int) error {
21
+	return h.moveCursor(Horizontal, param)
22
+}
23
+
24
+func (h *WindowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
25
+	info, err := GetConsoleScreenBufferInfo(h.fd)
26
+	if err != nil {
27
+		return err
28
+	}
29
+
30
+	position := info.CursorPosition
31
+	switch moveMode {
32
+	case Horizontal:
33
+		position.X = AddInRange(position.X, SHORT(param), info.Window.Left, info.Window.Right)
34
+	case Vertical:
35
+		position.Y = AddInRange(position.Y, SHORT(param), info.Window.Top, info.Window.Bottom)
36
+	}
37
+
38
+	if err = h.setCursorPosition(position, info.Size); err != nil {
39
+		return err
40
+	}
41
+
42
+	logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y)
43
+
44
+	return nil
45
+}
46
+
47
+func (h *WindowsAnsiEventHandler) moveCursorLine(param int) error {
48
+	info, err := GetConsoleScreenBufferInfo(h.fd)
49
+	if err != nil {
50
+		return err
51
+	}
52
+
53
+	position := info.CursorPosition
54
+	position.X = 0
55
+	position.Y = AddInRange(position.Y, SHORT(param), info.Window.Top, info.Window.Bottom)
56
+
57
+	if err = h.setCursorPosition(position, info.Size); err != nil {
58
+		return err
59
+	}
60
+
61
+	return nil
62
+}
63
+
64
+func (h *WindowsAnsiEventHandler) moveCursorColumn(param int) error {
65
+	info, err := GetConsoleScreenBufferInfo(h.fd)
66
+	if err != nil {
67
+		return err
68
+	}
69
+
70
+	position := info.CursorPosition
71
+	position.X = AddInRange(SHORT(param), -1, info.Window.Left, info.Window.Right)
72
+
73
+	if err = h.setCursorPosition(position, info.Size); err != nil {
74
+		return err
75
+	}
76
+
77
+	return nil
78
+}
0 79
new file mode 100644
... ...
@@ -0,0 +1,86 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+import (
5
+	. "github.com/Azure/go-ansiterm"
6
+)
7
+
8
+func (h *WindowsAnsiEventHandler) clearRange(attributes WORD, fromCoord COORD, toCoord COORD) error {
9
+	// Ignore an invalid (negative area) request
10
+	if toCoord.Y < fromCoord.Y {
11
+		return nil
12
+	}
13
+
14
+	var err error
15
+
16
+	var coordStart = COORD{}
17
+	var coordEnd = COORD{}
18
+
19
+	xCurrent, yCurrent := fromCoord.X, fromCoord.Y
20
+	xEnd, yEnd := toCoord.X, toCoord.Y
21
+
22
+	// Clear any partial initial line
23
+	if xCurrent > 0 {
24
+		coordStart.X, coordStart.Y = xCurrent, yCurrent
25
+		coordEnd.X, coordEnd.Y = xEnd, yCurrent
26
+
27
+		err = h.clearRect(attributes, coordStart, coordEnd)
28
+		if err != nil {
29
+			return err
30
+		}
31
+
32
+		xCurrent = 0
33
+		yCurrent += 1
34
+	}
35
+
36
+	// Clear intervening rectangular section
37
+	if yCurrent < yEnd {
38
+		coordStart.X, coordStart.Y = xCurrent, yCurrent
39
+		coordEnd.X, coordEnd.Y = xEnd, yEnd-1
40
+
41
+		err = h.clearRect(attributes, coordStart, coordEnd)
42
+		if err != nil {
43
+			return err
44
+		}
45
+
46
+		xCurrent = 0
47
+		yCurrent = yEnd
48
+	}
49
+
50
+	// Clear remaining partial ending line
51
+	coordStart.X, coordStart.Y = xCurrent, yCurrent
52
+	coordEnd.X, coordEnd.Y = xEnd, yEnd
53
+
54
+	err = h.clearRect(attributes, coordStart, coordEnd)
55
+	if err != nil {
56
+		return err
57
+	}
58
+
59
+	return nil
60
+}
61
+
62
+func (h *WindowsAnsiEventHandler) clearRect(attributes WORD, fromCoord COORD, toCoord COORD) error {
63
+	region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
64
+	width := toCoord.X - fromCoord.X + 1
65
+	height := toCoord.Y - fromCoord.Y + 1
66
+	size := uint32(width) * uint32(height)
67
+
68
+	if size <= 0 {
69
+		return nil
70
+	}
71
+
72
+	buffer := make([]CHAR_INFO, size)
73
+
74
+	char := CHAR_INFO{WCHAR(FILL_CHARACTER), attributes}
75
+	for i := 0; i < int(size); i++ {
76
+		buffer[i] = char
77
+	}
78
+
79
+	err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &region)
80
+	if err != nil {
81
+		return err
82
+	}
83
+
84
+	return nil
85
+}
0 86
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+func (h *WindowsAnsiEventHandler) scrollPageUp() error {
5
+	return h.scrollPage(1)
6
+}
7
+
8
+func (h *WindowsAnsiEventHandler) scrollPageDown() error {
9
+	return h.scrollPage(-1)
10
+}
11
+
12
+func (h *WindowsAnsiEventHandler) scrollPage(param int) error {
13
+	info, err := GetConsoleScreenBufferInfo(h.fd)
14
+	if err != nil {
15
+		return err
16
+	}
17
+
18
+	tmpScrollTop := h.sr.top
19
+	tmpScrollBottom := h.sr.bottom
20
+
21
+	// Set scroll region to whole window
22
+	h.sr.top = 0
23
+	h.sr.bottom = int(info.Size.Y - 1)
24
+
25
+	err = h.scroll(param)
26
+
27
+	h.sr.top = tmpScrollTop
28
+	h.sr.bottom = tmpScrollBottom
29
+
30
+	return err
31
+}
32
+
33
+func (h *WindowsAnsiEventHandler) scrollUp(param int) error {
34
+	return h.scroll(param)
35
+}
36
+
37
+func (h *WindowsAnsiEventHandler) scrollDown(param int) error {
38
+	return h.scroll(-param)
39
+}
40
+
41
+func (h *WindowsAnsiEventHandler) scroll(param int) error {
42
+
43
+	info, err := GetConsoleScreenBufferInfo(h.fd)
44
+	if err != nil {
45
+		return err
46
+	}
47
+
48
+	logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", h.sr.top, h.sr.bottom)
49
+	logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
50
+
51
+	rect := info.Window
52
+
53
+	// Current scroll region in Windows backing buffer coordinates
54
+	top := rect.Top + SHORT(h.sr.top)
55
+	bottom := rect.Top + SHORT(h.sr.bottom)
56
+
57
+	// Area from backing buffer to be copied
58
+	scrollRect := SMALL_RECT{
59
+		Top:    top + SHORT(param),
60
+		Bottom: bottom + SHORT(param),
61
+		Left:   rect.Left,
62
+		Right:  rect.Right,
63
+	}
64
+
65
+	// Clipping region should be the original scroll region
66
+	clipRegion := SMALL_RECT{
67
+		Top:    top,
68
+		Bottom: bottom,
69
+		Left:   rect.Left,
70
+		Right:  rect.Right,
71
+	}
72
+
73
+	// Origin to which area should be copied
74
+	destOrigin := COORD{
75
+		X: rect.Left,
76
+		Y: top,
77
+	}
78
+
79
+	char := CHAR_INFO{
80
+		UnicodeChar: ' ',
81
+		Attributes:  0,
82
+	}
83
+
84
+	if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, clipRegion, destOrigin, char); err != nil {
85
+		return err
86
+	}
87
+
88
+	return nil
89
+}
0 90
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+// AddInRange increments a value by the passed quantity while ensuring the values
5
+// always remain within the supplied min / max range.
6
+func AddInRange(n SHORT, increment SHORT, min SHORT, max SHORT) SHORT {
7
+	return ensureInRange(n+increment, min, max)
8
+}
0 9
new file mode 100644
... ...
@@ -0,0 +1,430 @@
0
+// +build windows
1
+
2
+package winterm
3
+
4
+import (
5
+	"bytes"
6
+	"io/ioutil"
7
+	"os"
8
+	"strconv"
9
+
10
+	. "github.com/Azure/go-ansiterm"
11
+	"github.com/Sirupsen/logrus"
12
+)
13
+
14
+var logger *logrus.Logger
15
+
16
+type WindowsAnsiEventHandler struct {
17
+	fd        uintptr
18
+	file      *os.File
19
+	infoReset *CONSOLE_SCREEN_BUFFER_INFO
20
+	sr        scrollRegion
21
+	buffer    bytes.Buffer
22
+}
23
+
24
+func CreateWinEventHandler(fd uintptr, file *os.File) AnsiEventHandler {
25
+	logFile := ioutil.Discard
26
+
27
+	if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
28
+		logFile, _ = os.Create("winEventHandler.log")
29
+	}
30
+
31
+	logger = &logrus.Logger{
32
+		Out:       logFile,
33
+		Formatter: new(logrus.TextFormatter),
34
+		Level:     logrus.DebugLevel,
35
+	}
36
+
37
+	infoReset, err := GetConsoleScreenBufferInfo(fd)
38
+	if err != nil {
39
+		return nil
40
+	}
41
+
42
+	sr := scrollRegion{int(infoReset.Window.Top), int(infoReset.Window.Bottom)}
43
+
44
+	return &WindowsAnsiEventHandler{
45
+		fd:        fd,
46
+		file:      file,
47
+		infoReset: infoReset,
48
+		sr:        sr,
49
+	}
50
+}
51
+
52
+type scrollRegion struct {
53
+	top    int
54
+	bottom int
55
+}
56
+
57
+func (h *WindowsAnsiEventHandler) Print(b byte) error {
58
+	return h.buffer.WriteByte(b)
59
+}
60
+
61
+func (h *WindowsAnsiEventHandler) Execute(b byte) error {
62
+	if ANSI_LINE_FEED == b {
63
+		info, err := GetConsoleScreenBufferInfo(h.fd)
64
+		if err != nil {
65
+			return err
66
+		}
67
+
68
+		if int(info.CursorPosition.Y) == h.sr.bottom {
69
+			if err := h.Flush(); err != nil {
70
+				return err
71
+			}
72
+
73
+			logger.Infof("Scrolling due to LF at bottom of scroll region")
74
+
75
+			// Scroll up one row if we attempt to line feed at the bottom
76
+			// of the scroll region
77
+			if err := h.scrollUp(1); err != nil {
78
+				return err
79
+			}
80
+
81
+			// Clear line
82
+			// if err := h.CUD(1); err != nil {
83
+			// 	return err
84
+			// }
85
+			if err := h.EL(0); err != nil {
86
+				return err
87
+			}
88
+		}
89
+	}
90
+
91
+	if ANSI_BEL <= b && b <= ANSI_CARRIAGE_RETURN {
92
+		return h.buffer.WriteByte(b)
93
+	}
94
+
95
+	return nil
96
+}
97
+
98
+func (h *WindowsAnsiEventHandler) CUU(param int) error {
99
+	if err := h.Flush(); err != nil {
100
+		return err
101
+	}
102
+	logger.Infof("CUU: [%v]", []string{strconv.Itoa(param)})
103
+	return h.moveCursorVertical(-param)
104
+}
105
+
106
+func (h *WindowsAnsiEventHandler) CUD(param int) error {
107
+	if err := h.Flush(); err != nil {
108
+		return err
109
+	}
110
+	logger.Infof("CUD: [%v]", []string{strconv.Itoa(param)})
111
+	return h.moveCursorVertical(param)
112
+}
113
+
114
+func (h *WindowsAnsiEventHandler) CUF(param int) error {
115
+	if err := h.Flush(); err != nil {
116
+		return err
117
+	}
118
+	logger.Infof("CUF: [%v]", []string{strconv.Itoa(param)})
119
+	return h.moveCursorHorizontal(param)
120
+}
121
+
122
+func (h *WindowsAnsiEventHandler) CUB(param int) error {
123
+	if err := h.Flush(); err != nil {
124
+		return err
125
+	}
126
+	logger.Infof("CUB: [%v]", []string{strconv.Itoa(param)})
127
+	return h.moveCursorHorizontal(-param)
128
+}
129
+
130
+func (h *WindowsAnsiEventHandler) CNL(param int) error {
131
+	if err := h.Flush(); err != nil {
132
+		return err
133
+	}
134
+	logger.Infof("CNL: [%v]", []string{strconv.Itoa(param)})
135
+	return h.moveCursorLine(param)
136
+}
137
+
138
+func (h *WindowsAnsiEventHandler) CPL(param int) error {
139
+	if err := h.Flush(); err != nil {
140
+		return err
141
+	}
142
+	logger.Infof("CPL: [%v]", []string{strconv.Itoa(param)})
143
+	return h.moveCursorLine(-param)
144
+}
145
+
146
+func (h *WindowsAnsiEventHandler) CHA(param int) error {
147
+	if err := h.Flush(); err != nil {
148
+		return err
149
+	}
150
+	logger.Infof("CHA: [%v]", []string{strconv.Itoa(param)})
151
+	return h.moveCursorColumn(param)
152
+}
153
+
154
+func (h *WindowsAnsiEventHandler) CUP(row int, col int) error {
155
+	if err := h.Flush(); err != nil {
156
+		return err
157
+	}
158
+	rowStr, colStr := strconv.Itoa(row), strconv.Itoa(col)
159
+	logger.Infof("CUP: [%v]", []string{rowStr, colStr})
160
+	info, err := GetConsoleScreenBufferInfo(h.fd)
161
+	if err != nil {
162
+		return err
163
+	}
164
+
165
+	rect := info.Window
166
+	rowS := AddInRange(SHORT(row-1), rect.Top, rect.Top, rect.Bottom)
167
+	colS := AddInRange(SHORT(col-1), rect.Left, rect.Left, rect.Right)
168
+	position := COORD{colS, rowS}
169
+
170
+	return h.setCursorPosition(position, info.Size)
171
+}
172
+
173
+func (h *WindowsAnsiEventHandler) HVP(row int, col int) error {
174
+	if err := h.Flush(); err != nil {
175
+		return err
176
+	}
177
+	rowS, colS := strconv.Itoa(row), strconv.Itoa(row)
178
+	logger.Infof("HVP: [%v]", []string{rowS, colS})
179
+	return h.CUP(row, col)
180
+}
181
+
182
+func (h *WindowsAnsiEventHandler) DECTCEM(visible bool) error {
183
+	if err := h.Flush(); err != nil {
184
+		return err
185
+	}
186
+	logger.Infof("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
187
+
188
+	return nil
189
+}
190
+
191
+func (h *WindowsAnsiEventHandler) ED(param int) error {
192
+	if err := h.Flush(); err != nil {
193
+		return err
194
+	}
195
+	logger.Infof("ED: [%v]", []string{strconv.Itoa(param)})
196
+
197
+	// [J  -- Erases from the cursor to the end of the screen, including the cursor position.
198
+	// [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
199
+	// [2J -- Erases the complete display. The cursor does not move.
200
+	// [3J -- Erases the complete display and backing buffer, cursor moves to (0,0)
201
+	// Notes:
202
+	// -- ANSI.SYS always moved the cursor to (0,0) for both [2J and [3J
203
+	// -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
204
+
205
+	info, err := GetConsoleScreenBufferInfo(h.fd)
206
+	if err != nil {
207
+		return err
208
+	}
209
+
210
+	var start COORD
211
+	var end COORD
212
+
213
+	switch param {
214
+	case 0:
215
+		start = info.CursorPosition
216
+		end = COORD{info.Size.X - 1, info.Size.Y - 1}
217
+
218
+	case 1:
219
+		start = COORD{0, 0}
220
+		end = info.CursorPosition
221
+
222
+	case 2:
223
+		start = COORD{0, 0}
224
+		end = COORD{info.Size.X - 1, info.Size.Y - 1}
225
+
226
+	case 3:
227
+		start = COORD{0, 0}
228
+		end = COORD{info.Size.X - 1, info.Size.Y - 1}
229
+	}
230
+
231
+	err = h.clearRange(info.Attributes, start, end)
232
+	if err != nil {
233
+		return err
234
+	}
235
+
236
+	if param == 2 || param == 3 {
237
+		err = h.setCursorPosition(COORD{0, 0}, info.Size)
238
+		if err != nil {
239
+			return err
240
+		}
241
+	}
242
+
243
+	return nil
244
+}
245
+
246
+func (h *WindowsAnsiEventHandler) EL(param int) error {
247
+	if err := h.Flush(); err != nil {
248
+		return err
249
+	}
250
+	logger.Infof("EL: [%v]", strconv.Itoa(param))
251
+
252
+	// [K  -- Erases from the cursor to the end of the line, including the cursor position.
253
+	// [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
254
+	// [2K -- Erases the complete line.
255
+
256
+	info, err := GetConsoleScreenBufferInfo(h.fd)
257
+	if err != nil {
258
+		return err
259
+	}
260
+
261
+	var start COORD
262
+	var end COORD
263
+
264
+	switch param {
265
+	case 0:
266
+		start = info.CursorPosition
267
+		end = COORD{info.Size.X, info.CursorPosition.Y}
268
+
269
+	case 1:
270
+		start = COORD{0, info.CursorPosition.Y}
271
+		end = info.CursorPosition
272
+
273
+	case 2:
274
+		start = COORD{0, info.CursorPosition.Y}
275
+		end = COORD{info.Size.X, info.CursorPosition.Y}
276
+	}
277
+
278
+	err = h.clearRange(info.Attributes, start, end)
279
+	if err != nil {
280
+		return err
281
+	}
282
+
283
+	return nil
284
+}
285
+
286
+func (h *WindowsAnsiEventHandler) IL(param int) error {
287
+	if err := h.Flush(); err != nil {
288
+		return err
289
+	}
290
+	logger.Infof("IL: [%v]", strconv.Itoa(param))
291
+	if err := h.scrollDown(param); err != nil {
292
+		return err
293
+	}
294
+
295
+	return h.EL(2)
296
+}
297
+
298
+func (h *WindowsAnsiEventHandler) DL(param int) error {
299
+	if err := h.Flush(); err != nil {
300
+		return err
301
+	}
302
+	logger.Infof("DL: [%v]", strconv.Itoa(param))
303
+	return h.scrollUp(param)
304
+}
305
+
306
+func (h *WindowsAnsiEventHandler) SGR(params []int) error {
307
+	if err := h.Flush(); err != nil {
308
+		return err
309
+	}
310
+	strings := []string{}
311
+	for _, v := range params {
312
+		logger.Infof("SGR: [%v]", strings)
313
+		strings = append(strings, strconv.Itoa(v))
314
+	}
315
+
316
+	logger.Infof("SGR: [%v]", strings)
317
+
318
+	info, err := GetConsoleScreenBufferInfo(h.fd)
319
+	if err != nil {
320
+		return err
321
+	}
322
+
323
+	attributes := info.Attributes
324
+	if len(params) <= 0 {
325
+		attributes = h.infoReset.Attributes
326
+	} else {
327
+		for _, attr := range params {
328
+
329
+			if attr == ANSI_SGR_RESET {
330
+				attributes = h.infoReset.Attributes
331
+				continue
332
+			}
333
+
334
+			attributes = collectAnsiIntoWindowsAttributes(attributes, h.infoReset.Attributes, SHORT(attr))
335
+		}
336
+	}
337
+
338
+	err = SetConsoleTextAttribute(h.fd, attributes)
339
+	if err != nil {
340
+		return err
341
+	}
342
+
343
+	return nil
344
+}
345
+
346
+func (h *WindowsAnsiEventHandler) SU(param int) error {
347
+	if err := h.Flush(); err != nil {
348
+		return err
349
+	}
350
+	logger.Infof("SU: [%v]", []string{strconv.Itoa(param)})
351
+	return h.scrollPageUp()
352
+}
353
+
354
+func (h *WindowsAnsiEventHandler) SD(param int) error {
355
+	if err := h.Flush(); err != nil {
356
+		return err
357
+	}
358
+	logger.Infof("SD: [%v]", []string{strconv.Itoa(param)})
359
+	return h.scrollPageDown()
360
+}
361
+
362
+func (h *WindowsAnsiEventHandler) DA(params []string) error {
363
+	logger.Infof("DA: [%v]", params)
364
+
365
+	// See the site below for details of the device attributes command
366
+	// http://vt100.net/docs/vt220-rm/chapter4.html
367
+
368
+	// First character of first parameter string is '>'
369
+	if params[0][0] == '>' {
370
+		// Secondary device attribute request:
371
+		// Respond with:
372
+		// "I am a VT220 version 1.0, no options.
373
+		//                    CSI     >     1     ;     1     0     ;     0     c    CR    LF
374
+		h.buffer.Write([]byte{CSI_ENTRY, 0x3E, 0x31, 0x3B, 0x31, 0x30, 0x3B, 0x30, 0x63, 0x0D, 0x0A})
375
+
376
+	} else {
377
+		// Primary device attribute request:
378
+		// Respond with:
379
+		// "I am a service class 2 terminal (62) with 132 columns (1),
380
+		// printer port (2), selective erase (6), DRCS (7), UDK (8),
381
+		// and I support 7-bit national replacement character sets (9)."
382
+		//                    CSI     ?     6     2     ;     1     ;     2     ;     6     ;     7     ;     8     ;     9     c    CR    LF
383
+		h.buffer.Write([]byte{CSI_ENTRY, 0x3F, 0x36, 0x32, 0x3B, 0x31, 0x3B, 0x32, 0x3B, 0x36, 0x3B, 0x37, 0x3B, 0x38, 0x3B, 0x39, 0x63, 0x0D, 0x0A})
384
+	}
385
+
386
+	return nil
387
+}
388
+
389
+func (h *WindowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
390
+	logger.Infof("DECSTBM: [%d, %d]", top, bottom)
391
+
392
+	// Windows is 0 indexed, Linux is 1 indexed
393
+	h.sr.top = top - 1
394
+	h.sr.bottom = bottom - 1
395
+
396
+	return nil
397
+}
398
+
399
+func (h *WindowsAnsiEventHandler) RI() error {
400
+	if err := h.Flush(); err != nil {
401
+		return err
402
+	}
403
+	logger.Info("RI: []")
404
+
405
+	info, err := GetConsoleScreenBufferInfo(h.fd)
406
+	if err != nil {
407
+		return err
408
+	}
409
+
410
+	if info.Window.Top == info.CursorPosition.Y {
411
+		if err := h.scrollPageDown(); err != nil {
412
+			return err
413
+		}
414
+
415
+		return h.EL(2)
416
+	} else {
417
+		return h.CUU(1)
418
+	}
419
+}
420
+
421
+func (h *WindowsAnsiEventHandler) Flush() error {
422
+	if h.buffer.Len() > 0 {
423
+		logger.Infof("Flush: [%s]", h.buffer.Bytes())
424
+		if _, err := h.buffer.WriteTo(h.file); err != nil {
425
+			return err
426
+		}
427
+	}
428
+	return nil
429
+}