Codebase list golang-github-azure-go-ansiterm / 7d5377c
Import Upstream version 0.0~git20160622.0.fa152c5 Tim Potter 7 years ago
28 changed file(s) with 3132 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
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 # 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.
9
10 -----
11 This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
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_BACKSPACE = 0x08
80 ANSI_TAB = 0x09
81 ANSI_LINE_FEED = 0x0A
82 ANSI_VERTICAL_TAB = 0x0B
83 ANSI_FORM_FEED = 0x0C
84 ANSI_CARRIAGE_RETURN = 0x0D
85 ANSI_ESCAPE_PRIMARY = 0x1B
86 ANSI_ESCAPE_SECONDARY = 0x5B
87 ANSI_OSC_STRING_ENTRY = 0x5D
88 ANSI_COMMAND_FIRST = 0x40
89 ANSI_COMMAND_LAST = 0x7E
90 DCS_ENTRY = 0x90
91 CSI_ENTRY = 0x9B
92 OSC_STRING = 0x9D
93 ANSI_PARAMETER_SEP = ";"
94 ANSI_CMD_G0 = '('
95 ANSI_CMD_G1 = ')'
96 ANSI_CMD_G2 = '*'
97 ANSI_CMD_G3 = '+'
98 ANSI_CMD_DECPNM = '>'
99 ANSI_CMD_DECPAM = '='
100 ANSI_CMD_OSC = ']'
101 ANSI_CMD_STR_TERM = '\\'
102
103 KEY_CONTROL_PARAM_2 = ";2"
104 KEY_CONTROL_PARAM_3 = ";3"
105 KEY_CONTROL_PARAM_4 = ";4"
106 KEY_CONTROL_PARAM_5 = ";5"
107 KEY_CONTROL_PARAM_6 = ";6"
108 KEY_CONTROL_PARAM_7 = ";7"
109 KEY_CONTROL_PARAM_8 = ";8"
110 KEY_ESC_CSI = "\x1B["
111 KEY_ESC_N = "\x1BN"
112 KEY_ESC_O = "\x1BO"
113
114 FILL_CHARACTER = ' '
115 )
116
117 func getByteRange(start byte, end byte) []byte {
118 bytes := make([]byte, 0, 32)
119 for i := start; i <= end; i++ {
120 bytes = append(bytes, byte(i))
121 }
122
123 return bytes
124 }
125
126 var toGroundBytes = getToGroundBytes()
127 var executors = getExecuteBytes()
128
129 // SPACE 20+A0 hex Always and everywhere a blank space
130 // Intermediate 20-2F hex !"#$%&'()*+,-./
131 var intermeds = getByteRange(0x20, 0x2F)
132
133 // Parameters 30-3F hex 0123456789:;<=>?
134 // CSI Parameters 30-39, 3B hex 0123456789;
135 var csiParams = getByteRange(0x30, 0x3F)
136
137 var csiCollectables = append(getByteRange(0x30, 0x39), getByteRange(0x3B, 0x3F)...)
138
139 // Uppercase 40-5F hex @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_
140 var upperCase = getByteRange(0x40, 0x5F)
141
142 // Lowercase 60-7E hex `abcdefghijlkmnopqrstuvwxyz{|}~
143 var lowerCase = getByteRange(0x60, 0x7E)
144
145 // Alphabetics 40-7E hex (all of upper and lower case)
146 var alphabetics = append(upperCase, lowerCase...)
147
148 var printables = getByteRange(0x20, 0x7F)
149
150 var escapeIntermediateToGroundBytes = getByteRange(0x30, 0x7E)
151 var escapeToGroundBytes = getEscapeToGroundBytes()
152
153 // See http://www.vt100.net/emu/vt500_parser.png for description of the complex
154 // byte ranges below
155
156 func getEscapeToGroundBytes() []byte {
157 escapeToGroundBytes := getByteRange(0x30, 0x4F)
158 escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x51, 0x57)...)
159 escapeToGroundBytes = append(escapeToGroundBytes, 0x59)
160 escapeToGroundBytes = append(escapeToGroundBytes, 0x5A)
161 escapeToGroundBytes = append(escapeToGroundBytes, 0x5C)
162 escapeToGroundBytes = append(escapeToGroundBytes, getByteRange(0x60, 0x7E)...)
163 return escapeToGroundBytes
164 }
165
166 func getExecuteBytes() []byte {
167 executeBytes := getByteRange(0x00, 0x17)
168 executeBytes = append(executeBytes, 0x19)
169 executeBytes = append(executeBytes, getByteRange(0x1C, 0x1F)...)
170 return executeBytes
171 }
172
173 func getToGroundBytes() []byte {
174 groundBytes := []byte{0x18}
175 groundBytes = append(groundBytes, 0x1A)
176 groundBytes = append(groundBytes, getByteRange(0x80, 0x8F)...)
177 groundBytes = append(groundBytes, getByteRange(0x91, 0x97)...)
178 groundBytes = append(groundBytes, 0x99)
179 groundBytes = append(groundBytes, 0x9A)
180 groundBytes = append(groundBytes, 0x9C)
181 return groundBytes
182 }
183
184 // Delete 7F hex Always and everywhere ignored
185 // C1 Control 80-9F hex 32 additional control characters
186 // G1 Displayable A1-FE hex 94 additional displayable characters
187 // Special A0+FF hex Same as SPACE and DELETE
0 package ansiterm
1
2 type ansiContext struct {
3 currentChar byte
4 paramBuffer []byte
5 interBuffer []byte
6 }
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 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 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 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 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 // Vertical line Position Absolute
31 VPA(int) error
32
33 // CUrsor Position
34 CUP(int, int) error
35
36 // Horizontal and Vertical Position (depends on PUM)
37 HVP(int, int) error
38
39 // Text Cursor Enable Mode
40 DECTCEM(bool) error
41
42 // Origin Mode
43 DECOM(bool) error
44
45 // 132 Column Mode
46 DECCOLM(bool) error
47
48 // Erase in Display
49 ED(int) error
50
51 // Erase in Line
52 EL(int) error
53
54 // Insert Line
55 IL(int) error
56
57 // Delete Line
58 DL(int) error
59
60 // Insert Character
61 ICH(int) error
62
63 // Delete Character
64 DCH(int) error
65
66 // Set Graphics Rendition
67 SGR([]int) error
68
69 // Pan Down
70 SU(int) error
71
72 // Pan Up
73 SD(int) error
74
75 // Device Attributes
76 DA([]string) error
77
78 // Set Top and Bottom Margins
79 DECSTBM(int, int) error
80
81 // Index
82 IND() error
83
84 // Reverse Index
85 RI() error
86
87 // Flush updates from previous commands
88 Flush() error
89 }
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 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 package ansiterm
1
2 import (
3 "errors"
4 "io/ioutil"
5 "os"
6
7 "github.com/Sirupsen/logrus"
8 )
9
10 var logger *logrus.Logger
11
12 type AnsiParser struct {
13 currState state
14 eventHandler AnsiEventHandler
15 context *ansiContext
16 csiEntry state
17 csiParam state
18 dcsEntry state
19 escape state
20 escapeIntermediate state
21 error state
22 ground state
23 oscString state
24 stateMap []state
25 }
26
27 func CreateParser(initialState string, evtHandler AnsiEventHandler) *AnsiParser {
28 logFile := ioutil.Discard
29
30 if isDebugEnv := os.Getenv(LogEnv); isDebugEnv == "1" {
31 logFile, _ = os.Create("ansiParser.log")
32 }
33
34 logger = &logrus.Logger{
35 Out: logFile,
36 Formatter: new(logrus.TextFormatter),
37 Level: logrus.InfoLevel,
38 }
39
40 parser := &AnsiParser{
41 eventHandler: evtHandler,
42 context: &ansiContext{},
43 }
44
45 parser.csiEntry = csiEntryState{baseState{name: "CsiEntry", parser: parser}}
46 parser.csiParam = csiParamState{baseState{name: "CsiParam", parser: parser}}
47 parser.dcsEntry = dcsEntryState{baseState{name: "DcsEntry", parser: parser}}
48 parser.escape = escapeState{baseState{name: "Escape", parser: parser}}
49 parser.escapeIntermediate = escapeIntermediateState{baseState{name: "EscapeIntermediate", parser: parser}}
50 parser.error = errorState{baseState{name: "Error", parser: parser}}
51 parser.ground = groundState{baseState{name: "Ground", parser: parser}}
52 parser.oscString = oscStringState{baseState{name: "OscString", parser: parser}}
53
54 parser.stateMap = []state{
55 parser.csiEntry,
56 parser.csiParam,
57 parser.dcsEntry,
58 parser.escape,
59 parser.escapeIntermediate,
60 parser.error,
61 parser.ground,
62 parser.oscString,
63 }
64
65 parser.currState = getState(initialState, parser.stateMap)
66
67 logger.Infof("CreateParser: parser %p", parser)
68 return parser
69 }
70
71 func getState(name string, states []state) state {
72 for _, el := range states {
73 if el.Name() == name {
74 return el
75 }
76 }
77
78 return nil
79 }
80
81 func (ap *AnsiParser) Parse(bytes []byte) (int, error) {
82 for i, b := range bytes {
83 if err := ap.handle(b); err != nil {
84 return i, err
85 }
86 }
87
88 return len(bytes), ap.eventHandler.Flush()
89 }
90
91 func (ap *AnsiParser) handle(b byte) error {
92 ap.context.currentChar = b
93 newState, err := ap.currState.Handle(b)
94 if err != nil {
95 return err
96 }
97
98 if newState == nil {
99 logger.Warning("newState is nil")
100 return errors.New("New state of 'nil' is invalid.")
101 }
102
103 if newState != ap.currState {
104 if err := ap.changeState(newState); err != nil {
105 return err
106 }
107 }
108
109 return nil
110 }
111
112 func (ap *AnsiParser) changeState(newState state) error {
113 logger.Infof("ChangeState %s --> %s", ap.currState.Name(), newState.Name())
114
115 // Exit old state
116 if err := ap.currState.Exit(); err != nil {
117 logger.Infof("Exit state '%s' failed with : '%v'", ap.currState.Name(), err)
118 return err
119 }
120
121 // Perform transition action
122 if err := ap.currState.Transition(newState); err != nil {
123 logger.Infof("Transition from '%s' to '%s' failed with: '%v'", ap.currState.Name(), newState.Name, err)
124 return err
125 }
126
127 // Enter new state
128 if err := newState.Enter(); err != nil {
129 logger.Infof("Enter state '%s' failed with: '%v'", newState.Name(), err)
130 return err
131 }
132
133 ap.currState = newState
134 return nil
135 }
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) modeDispatch(param string, set bool) error {
68 switch param {
69 case "?3":
70 return ap.eventHandler.DECCOLM(set)
71 case "?6":
72 return ap.eventHandler.DECOM(set)
73 case "?25":
74 return ap.eventHandler.DECTCEM(set)
75 }
76 return nil
77 }
78
79 func (ap *AnsiParser) hDispatch(params []string) error {
80 if len(params) == 1 {
81 return ap.modeDispatch(params[0], true)
82 }
83
84 return nil
85 }
86
87 func (ap *AnsiParser) lDispatch(params []string) error {
88 if len(params) == 1 {
89 return ap.modeDispatch(params[0], false)
90 }
91
92 return nil
93 }
94
95 func getEraseParam(params []string) int {
96 param := getInt(params, 0)
97 if param < 0 || 3 < param {
98 param = 0
99 }
100
101 return param
102 }
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 "D": // IND
28 return ap.eventHandler.IND()
29 case "E": // NEL, equivalent to CRLF
30 err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
31 if err == nil {
32 err = ap.eventHandler.Execute(ANSI_LINE_FEED)
33 }
34 return err
35 case "M": // RI
36 return ap.eventHandler.RI()
37 }
38
39 return nil
40 }
41
42 func (ap *AnsiParser) csiDispatch() error {
43 cmd, _ := parseCmd(*ap.context)
44 params, _ := parseParams(ap.context.paramBuffer)
45
46 logger.Infof("csiDispatch: %v(%v)", cmd, params)
47
48 switch cmd {
49 case "@":
50 return ap.eventHandler.ICH(getInt(params, 1))
51 case "A":
52 return ap.eventHandler.CUU(getInt(params, 1))
53 case "B":
54 return ap.eventHandler.CUD(getInt(params, 1))
55 case "C":
56 return ap.eventHandler.CUF(getInt(params, 1))
57 case "D":
58 return ap.eventHandler.CUB(getInt(params, 1))
59 case "E":
60 return ap.eventHandler.CNL(getInt(params, 1))
61 case "F":
62 return ap.eventHandler.CPL(getInt(params, 1))
63 case "G":
64 return ap.eventHandler.CHA(getInt(params, 1))
65 case "H":
66 ints := getInts(params, 2, 1)
67 x, y := ints[0], ints[1]
68 return ap.eventHandler.CUP(x, y)
69 case "J":
70 param := getEraseParam(params)
71 return ap.eventHandler.ED(param)
72 case "K":
73 param := getEraseParam(params)
74 return ap.eventHandler.EL(param)
75 case "L":
76 return ap.eventHandler.IL(getInt(params, 1))
77 case "M":
78 return ap.eventHandler.DL(getInt(params, 1))
79 case "P":
80 return ap.eventHandler.DCH(getInt(params, 1))
81 case "S":
82 return ap.eventHandler.SU(getInt(params, 1))
83 case "T":
84 return ap.eventHandler.SD(getInt(params, 1))
85 case "c":
86 return ap.eventHandler.DA(params)
87 case "d":
88 return ap.eventHandler.VPA(getInt(params, 1))
89 case "f":
90 ints := getInts(params, 2, 1)
91 x, y := ints[0], ints[1]
92 return ap.eventHandler.HVP(x, y)
93 case "h":
94 return ap.hDispatch(params)
95 case "l":
96 return ap.lDispatch(params)
97 case "m":
98 return ap.eventHandler.SGR(getInts(params, 1, 0))
99 case "r":
100 ints := getInts(params, 2, 1)
101 top, bottom := ints[0], ints[1]
102 return ap.eventHandler.DECSTBM(top, bottom)
103 default:
104 logger.Errorf(fmt.Sprintf("Unsupported CSI command: '%s', with full context: %v", cmd, ap.context))
105 return nil
106 }
107
108 }
109
110 func (ap *AnsiParser) print() error {
111 return ap.eventHandler.Print(ap.context.currentChar)
112 }
113
114 func (ap *AnsiParser) clear() error {
115 ap.context = &ansiContext{}
116 return nil
117 }
118
119 func (ap *AnsiParser) execute() error {
120 return ap.eventHandler.Execute(ap.context.currentChar)
121 }
0 package ansiterm
1
2 import (
3 "fmt"
4 "testing"
5 )
6
7 func TestStateTransitions(t *testing.T) {
8 stateTransitionHelper(t, "CsiEntry", "Ground", alphabetics)
9 stateTransitionHelper(t, "CsiEntry", "CsiParam", csiCollectables)
10 stateTransitionHelper(t, "Escape", "CsiEntry", []byte{ANSI_ESCAPE_SECONDARY})
11 stateTransitionHelper(t, "Escape", "OscString", []byte{0x5D})
12 stateTransitionHelper(t, "Escape", "Ground", escapeToGroundBytes)
13 stateTransitionHelper(t, "Escape", "EscapeIntermediate", intermeds)
14 stateTransitionHelper(t, "EscapeIntermediate", "EscapeIntermediate", intermeds)
15 stateTransitionHelper(t, "EscapeIntermediate", "EscapeIntermediate", executors)
16 stateTransitionHelper(t, "EscapeIntermediate", "Ground", escapeIntermediateToGroundBytes)
17 stateTransitionHelper(t, "OscString", "Ground", []byte{ANSI_BEL})
18 stateTransitionHelper(t, "OscString", "Ground", []byte{0x5C})
19 stateTransitionHelper(t, "Ground", "Ground", executors)
20 }
21
22 func TestAnyToX(t *testing.T) {
23 anyToXHelper(t, []byte{ANSI_ESCAPE_PRIMARY}, "Escape")
24 anyToXHelper(t, []byte{DCS_ENTRY}, "DcsEntry")
25 anyToXHelper(t, []byte{OSC_STRING}, "OscString")
26 anyToXHelper(t, []byte{CSI_ENTRY}, "CsiEntry")
27 anyToXHelper(t, toGroundBytes, "Ground")
28 }
29
30 func TestCollectCsiParams(t *testing.T) {
31 parser, _ := createTestParser("CsiEntry")
32 parser.Parse(csiCollectables)
33
34 buffer := parser.context.paramBuffer
35 bufferCount := len(buffer)
36
37 if bufferCount != len(csiCollectables) {
38 t.Errorf("Buffer: %v", buffer)
39 t.Errorf("CsiParams: %v", csiCollectables)
40 t.Errorf("Buffer count failure: %d != %d", bufferCount, len(csiParams))
41 return
42 }
43
44 for i, v := range csiCollectables {
45 if v != buffer[i] {
46 t.Errorf("Buffer: %v", buffer)
47 t.Errorf("CsiParams: %v", csiParams)
48 t.Errorf("Mismatch at buffer[%d] = %d", i, buffer[i])
49 }
50 }
51 }
52
53 func TestParseParams(t *testing.T) {
54 parseParamsHelper(t, []byte{}, []string{})
55 parseParamsHelper(t, []byte{';'}, []string{})
56 parseParamsHelper(t, []byte{';', ';'}, []string{})
57 parseParamsHelper(t, []byte{'7'}, []string{"7"})
58 parseParamsHelper(t, []byte{'7', ';'}, []string{"7"})
59 parseParamsHelper(t, []byte{'7', ';', ';'}, []string{"7"})
60 parseParamsHelper(t, []byte{'7', ';', ';', '8'}, []string{"7", "8"})
61 parseParamsHelper(t, []byte{'7', ';', '8', ';'}, []string{"7", "8"})
62 parseParamsHelper(t, []byte{'7', ';', ';', '8', ';', ';'}, []string{"7", "8"})
63 parseParamsHelper(t, []byte{'7', '8'}, []string{"78"})
64 parseParamsHelper(t, []byte{'7', '8', ';'}, []string{"78"})
65 parseParamsHelper(t, []byte{'7', '8', ';', '9', '0'}, []string{"78", "90"})
66 parseParamsHelper(t, []byte{'7', '8', ';', ';', '9', '0'}, []string{"78", "90"})
67 parseParamsHelper(t, []byte{'7', '8', ';', '9', '0', ';'}, []string{"78", "90"})
68 parseParamsHelper(t, []byte{'7', '8', ';', '9', '0', ';', ';'}, []string{"78", "90"})
69 }
70
71 func TestCursor(t *testing.T) {
72 cursorSingleParamHelper(t, 'A', "CUU")
73 cursorSingleParamHelper(t, 'B', "CUD")
74 cursorSingleParamHelper(t, 'C', "CUF")
75 cursorSingleParamHelper(t, 'D', "CUB")
76 cursorSingleParamHelper(t, 'E', "CNL")
77 cursorSingleParamHelper(t, 'F', "CPL")
78 cursorSingleParamHelper(t, 'G', "CHA")
79 cursorTwoParamHelper(t, 'H', "CUP")
80 cursorTwoParamHelper(t, 'f', "HVP")
81 funcCallParamHelper(t, []byte{'?', '2', '5', 'h'}, "CsiEntry", "Ground", []string{"DECTCEM([true])"})
82 funcCallParamHelper(t, []byte{'?', '2', '5', 'l'}, "CsiEntry", "Ground", []string{"DECTCEM([false])"})
83 }
84
85 func TestErase(t *testing.T) {
86 // Erase in Display
87 eraseHelper(t, 'J', "ED")
88
89 // Erase in Line
90 eraseHelper(t, 'K', "EL")
91 }
92
93 func TestSelectGraphicRendition(t *testing.T) {
94 funcCallParamHelper(t, []byte{'m'}, "CsiEntry", "Ground", []string{"SGR([0])"})
95 funcCallParamHelper(t, []byte{'0', 'm'}, "CsiEntry", "Ground", []string{"SGR([0])"})
96 funcCallParamHelper(t, []byte{'0', ';', '1', 'm'}, "CsiEntry", "Ground", []string{"SGR([0 1])"})
97 funcCallParamHelper(t, []byte{'0', ';', '1', ';', '2', 'm'}, "CsiEntry", "Ground", []string{"SGR([0 1 2])"})
98 }
99
100 func TestScroll(t *testing.T) {
101 scrollHelper(t, 'S', "SU")
102 scrollHelper(t, 'T', "SD")
103 }
104
105 func TestPrint(t *testing.T) {
106 parser, evtHandler := createTestParser("Ground")
107 parser.Parse(printables)
108 validateState(t, parser.currState, "Ground")
109
110 for i, v := range printables {
111 expectedCall := fmt.Sprintf("Print([%s])", string(v))
112 actualCall := evtHandler.FunctionCalls[i]
113 if actualCall != expectedCall {
114 t.Errorf("Actual != Expected: %v != %v at %d", actualCall, expectedCall, i)
115 }
116 }
117 }
118
119 func TestClear(t *testing.T) {
120 p, _ := createTestParser("Ground")
121 fillContext(p.context)
122 p.clear()
123 validateEmptyContext(t, p.context)
124 }
125
126 func TestClearOnStateChange(t *testing.T) {
127 clearOnStateChangeHelper(t, "Ground", "Escape", []byte{ANSI_ESCAPE_PRIMARY})
128 clearOnStateChangeHelper(t, "Ground", "CsiEntry", []byte{CSI_ENTRY})
129 }
130
131 func TestC0(t *testing.T) {
132 expectedCall := "Execute([" + string(ANSI_LINE_FEED) + "])"
133 c0Helper(t, []byte{ANSI_LINE_FEED}, "Ground", []string{expectedCall})
134 expectedCall = "Execute([" + string(ANSI_CARRIAGE_RETURN) + "])"
135 c0Helper(t, []byte{ANSI_CARRIAGE_RETURN}, "Ground", []string{expectedCall})
136 }
137
138 func TestEscDispatch(t *testing.T) {
139 funcCallParamHelper(t, []byte{'M'}, "Escape", "Ground", []string{"RI([])"})
140 }
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 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 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 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) VPA(param int) error {
68 h.recordCall("VPA", []string{strconv.Itoa(param)})
69 return nil
70 }
71
72 func (h *TestAnsiEventHandler) CUP(x int, y int) error {
73 xS, yS := strconv.Itoa(x), strconv.Itoa(y)
74 h.recordCall("CUP", []string{xS, yS})
75 return nil
76 }
77
78 func (h *TestAnsiEventHandler) HVP(x int, y int) error {
79 xS, yS := strconv.Itoa(x), strconv.Itoa(y)
80 h.recordCall("HVP", []string{xS, yS})
81 return nil
82 }
83
84 func (h *TestAnsiEventHandler) DECTCEM(visible bool) error {
85 h.recordCall("DECTCEM", []string{strconv.FormatBool(visible)})
86 return nil
87 }
88
89 func (h *TestAnsiEventHandler) DECOM(visible bool) error {
90 h.recordCall("DECOM", []string{strconv.FormatBool(visible)})
91 return nil
92 }
93
94 func (h *TestAnsiEventHandler) DECCOLM(use132 bool) error {
95 h.recordCall("DECOLM", []string{strconv.FormatBool(use132)})
96 return nil
97 }
98
99 func (h *TestAnsiEventHandler) ED(param int) error {
100 h.recordCall("ED", []string{strconv.Itoa(param)})
101 return nil
102 }
103
104 func (h *TestAnsiEventHandler) EL(param int) error {
105 h.recordCall("EL", []string{strconv.Itoa(param)})
106 return nil
107 }
108
109 func (h *TestAnsiEventHandler) IL(param int) error {
110 h.recordCall("IL", []string{strconv.Itoa(param)})
111 return nil
112 }
113
114 func (h *TestAnsiEventHandler) DL(param int) error {
115 h.recordCall("DL", []string{strconv.Itoa(param)})
116 return nil
117 }
118
119 func (h *TestAnsiEventHandler) ICH(param int) error {
120 h.recordCall("ICH", []string{strconv.Itoa(param)})
121 return nil
122 }
123
124 func (h *TestAnsiEventHandler) DCH(param int) error {
125 h.recordCall("DCH", []string{strconv.Itoa(param)})
126 return nil
127 }
128
129 func (h *TestAnsiEventHandler) SGR(params []int) error {
130 strings := []string{}
131 for _, v := range params {
132 strings = append(strings, strconv.Itoa(v))
133 }
134
135 h.recordCall("SGR", strings)
136 return nil
137 }
138
139 func (h *TestAnsiEventHandler) SU(param int) error {
140 h.recordCall("SU", []string{strconv.Itoa(param)})
141 return nil
142 }
143
144 func (h *TestAnsiEventHandler) SD(param int) error {
145 h.recordCall("SD", []string{strconv.Itoa(param)})
146 return nil
147 }
148
149 func (h *TestAnsiEventHandler) DA(params []string) error {
150 h.recordCall("DA", params)
151 return nil
152 }
153
154 func (h *TestAnsiEventHandler) DECSTBM(top int, bottom int) error {
155 topS, bottomS := strconv.Itoa(top), strconv.Itoa(bottom)
156 h.recordCall("DECSTBM", []string{topS, bottomS})
157 return nil
158 }
159
160 func (h *TestAnsiEventHandler) RI() error {
161 h.recordCall("RI", nil)
162 return nil
163 }
164
165 func (h *TestAnsiEventHandler) IND() error {
166 h.recordCall("IND", nil)
167 return nil
168 }
169
170 func (h *TestAnsiEventHandler) Flush() error {
171 return nil
172 }
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 // +build windows
1
2 package winterm
3
4 import (
5 "fmt"
6 "os"
7 "strconv"
8 "strings"
9 "syscall"
10
11 "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] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_ESCAPE_SECONDARY {
88 start++
89 }
90 // convert this to GetNextParam method
91 ac.Parameters = strings.Split(string(command[start:lastCharIndex]), ansiterm.ANSI_PARAMETER_SEP)
92 }
93
94 return ac
95 }
96
97 func (ac *ansiCommand) paramAsSHORT(index int, defaultValue int16) int16 {
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 int16(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 ansiterm.ANSI_COMMAND_FIRST <= b && b <= ansiterm.ANSI_COMMAND_LAST && b != ansiterm.ANSI_ESCAPE_SECONDARY:
122 return true
123 case b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_OSC || b == ansiterm.ANSI_CMD_DECPAM || b == ansiterm.ANSI_CMD_DECPNM:
124 // non-CSI escape sequence terminator
125 return true
126 case b == ansiterm.ANSI_CMD_STR_TERM || b == ansiterm.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] == ansiterm.ANSI_ESCAPE_PRIMARY && command[1] == ansiterm.ANSI_CMD_OSC && current != ansiterm.ANSI_BEL)
135 }
136
137 func isCharacterSelectionCmdChar(b byte) bool {
138 return (b == ansiterm.ANSI_CMD_G0 || b == ansiterm.ANSI_CMD_G1 || b == ansiterm.ANSI_CMD_G2 || b == ansiterm.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 int16, min int16, max int16) int16 {
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 // +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 uint16 = 0x0001
69 FOREGROUND_GREEN uint16 = 0x0002
70 FOREGROUND_RED uint16 = 0x0004
71 FOREGROUND_INTENSITY uint16 = 0x0008
72 FOREGROUND_MASK uint16 = 0x000F
73
74 BACKGROUND_BLUE uint16 = 0x0010
75 BACKGROUND_GREEN uint16 = 0x0020
76 BACKGROUND_RED uint16 = 0x0040
77 BACKGROUND_INTENSITY uint16 = 0x0080
78 BACKGROUND_MASK uint16 = 0x00F0
79
80 COMMON_LVB_MASK uint16 = 0xFF00
81 COMMON_LVB_REVERSE_VIDEO uint16 = 0x4000
82 COMMON_LVB_UNDERSCORE uint16 = 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/ms682101(v=vs.85).aspx for Console specific types (e.g., COORD)
107 // -- See https://msdn.microsoft.com/en-us/library/aa296569(v=vs.60).aspx for comments on alignment
108 type (
109 CHAR_INFO struct {
110 UnicodeChar uint16
111 Attributes uint16
112 }
113
114 CONSOLE_CURSOR_INFO struct {
115 Size uint32
116 Visible int32
117 }
118
119 CONSOLE_SCREEN_BUFFER_INFO struct {
120 Size COORD
121 CursorPosition COORD
122 Attributes uint16
123 Window SMALL_RECT
124 MaximumWindowSize COORD
125 }
126
127 COORD struct {
128 X int16
129 Y int16
130 }
131
132 SMALL_RECT struct {
133 Left int16
134 Top int16
135 Right int16
136 Bottom int16
137 }
138
139 // INPUT_RECORD is a C/C++ union of which KEY_EVENT_RECORD is one case, it is also the largest
140 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx.
141 INPUT_RECORD struct {
142 EventType uint16
143 KeyEvent KEY_EVENT_RECORD
144 }
145
146 KEY_EVENT_RECORD struct {
147 KeyDown int32
148 RepeatCount uint16
149 VirtualKeyCode uint16
150 VirtualScanCode uint16
151 UnicodeChar uint16
152 ControlKeyState uint32
153 }
154
155 WINDOW_BUFFER_SIZE struct {
156 Size COORD
157 }
158 )
159
160 // boolToBOOL converts a Go bool into a Windows int32.
161 func boolToBOOL(f bool) int32 {
162 if f {
163 return int32(1)
164 } else {
165 return int32(0)
166 }
167 }
168
169 // GetConsoleCursorInfo retrieves information about the size and visiblity of the console cursor.
170 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683163(v=vs.85).aspx.
171 func GetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
172 r1, r2, err := getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
173 return checkError(r1, r2, err)
174 }
175
176 // SetConsoleCursorInfo sets the size and visiblity of the console cursor.
177 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx.
178 func SetConsoleCursorInfo(handle uintptr, cursorInfo *CONSOLE_CURSOR_INFO) error {
179 r1, r2, err := setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)
180 return checkError(r1, r2, err)
181 }
182
183 // SetConsoleCursorPosition location of the console cursor.
184 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx.
185 func SetConsoleCursorPosition(handle uintptr, coord COORD) error {
186 r1, r2, err := setConsoleCursorPositionProc.Call(handle, coordToPointer(coord))
187 use(coord)
188 return checkError(r1, r2, err)
189 }
190
191 // GetConsoleMode gets the console mode for given file descriptor
192 // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx.
193 func GetConsoleMode(handle uintptr) (mode uint32, err error) {
194 err = syscall.GetConsoleMode(syscall.Handle(handle), &mode)
195 return mode, err
196 }
197
198 // SetConsoleMode sets the console mode for given file descriptor
199 // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx.
200 func SetConsoleMode(handle uintptr, mode uint32) error {
201 r1, r2, err := setConsoleModeProc.Call(handle, uintptr(mode), 0)
202 use(mode)
203 return checkError(r1, r2, err)
204 }
205
206 // GetConsoleScreenBufferInfo retrieves information about the specified console screen buffer.
207 // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683171(v=vs.85).aspx.
208 func GetConsoleScreenBufferInfo(handle uintptr) (*CONSOLE_SCREEN_BUFFER_INFO, error) {
209 info := CONSOLE_SCREEN_BUFFER_INFO{}
210 err := checkError(getConsoleScreenBufferInfoProc.Call(handle, uintptr(unsafe.Pointer(&info)), 0))
211 if err != nil {
212 return nil, err
213 }
214 return &info, nil
215 }
216
217 func ScrollConsoleScreenBuffer(handle uintptr, scrollRect SMALL_RECT, clipRect SMALL_RECT, destOrigin COORD, char CHAR_INFO) error {
218 r1, r2, err := scrollConsoleScreenBufferProc.Call(handle, uintptr(unsafe.Pointer(&scrollRect)), uintptr(unsafe.Pointer(&clipRect)), coordToPointer(destOrigin), uintptr(unsafe.Pointer(&char)))
219 use(scrollRect)
220 use(clipRect)
221 use(destOrigin)
222 use(char)
223 return checkError(r1, r2, err)
224 }
225
226 // SetConsoleScreenBufferSize sets the size of the console screen buffer.
227 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686044(v=vs.85).aspx.
228 func SetConsoleScreenBufferSize(handle uintptr, coord COORD) error {
229 r1, r2, err := setConsoleScreenBufferSizeProc.Call(handle, coordToPointer(coord))
230 use(coord)
231 return checkError(r1, r2, err)
232 }
233
234 // SetConsoleTextAttribute sets the attributes of characters written to the
235 // console screen buffer by the WriteFile or WriteConsole function.
236 // See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
237 func SetConsoleTextAttribute(handle uintptr, attribute uint16) error {
238 r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
239 use(attribute)
240 return checkError(r1, r2, err)
241 }
242
243 // SetConsoleWindowInfo sets the size and position of the console screen buffer's window.
244 // Note that the size and location must be within and no larger than the backing console screen buffer.
245 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686125(v=vs.85).aspx.
246 func SetConsoleWindowInfo(handle uintptr, isAbsolute bool, rect SMALL_RECT) error {
247 r1, r2, err := setConsoleWindowInfoProc.Call(handle, uintptr(boolToBOOL(isAbsolute)), uintptr(unsafe.Pointer(&rect)))
248 use(isAbsolute)
249 use(rect)
250 return checkError(r1, r2, err)
251 }
252
253 // WriteConsoleOutput writes the CHAR_INFOs from the provided buffer to the active console buffer.
254 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687404(v=vs.85).aspx.
255 func WriteConsoleOutput(handle uintptr, buffer []CHAR_INFO, bufferSize COORD, bufferCoord COORD, writeRegion *SMALL_RECT) error {
256 r1, r2, err := writeConsoleOutputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), coordToPointer(bufferSize), coordToPointer(bufferCoord), uintptr(unsafe.Pointer(writeRegion)))
257 use(buffer)
258 use(bufferSize)
259 use(bufferCoord)
260 return checkError(r1, r2, err)
261 }
262
263 // ReadConsoleInput reads (and removes) data from the console input buffer.
264 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx.
265 func ReadConsoleInput(handle uintptr, buffer []INPUT_RECORD, count *uint32) error {
266 r1, r2, err := readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&buffer[0])), uintptr(len(buffer)), uintptr(unsafe.Pointer(count)))
267 use(buffer)
268 return checkError(r1, r2, err)
269 }
270
271 // WaitForSingleObject waits for the passed handle to be signaled.
272 // It returns true if the handle was signaled; false otherwise.
273 // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx.
274 func WaitForSingleObject(handle uintptr, msWait uint32) (bool, error) {
275 r1, _, err := waitForSingleObjectProc.Call(handle, uintptr(uint32(msWait)))
276 switch r1 {
277 case WAIT_ABANDONED, WAIT_TIMEOUT:
278 return false, nil
279 case WAIT_SIGNALED:
280 return true, nil
281 }
282 use(msWait)
283 return false, err
284 }
285
286 // String helpers
287 func (info CONSOLE_SCREEN_BUFFER_INFO) String() string {
288 return fmt.Sprintf("Size(%v) Cursor(%v) Window(%v) Max(%v)", info.Size, info.CursorPosition, info.Window, info.MaximumWindowSize)
289 }
290
291 func (coord COORD) String() string {
292 return fmt.Sprintf("%v,%v", coord.X, coord.Y)
293 }
294
295 func (rect SMALL_RECT) String() string {
296 return fmt.Sprintf("(%v,%v),(%v,%v)", rect.Left, rect.Top, rect.Right, rect.Bottom)
297 }
298
299 // checkError evaluates the results of a Windows API call and returns the error if it failed.
300 func checkError(r1, r2 uintptr, err error) error {
301 // Windows APIs return non-zero to indicate success
302 if r1 != 0 {
303 return nil
304 }
305
306 // Return the error if provided, otherwise default to EINVAL
307 if err != nil {
308 return err
309 }
310 return syscall.EINVAL
311 }
312
313 // coordToPointer converts a COORD into a uintptr (by fooling the type system).
314 func coordToPointer(c COORD) uintptr {
315 // Note: This code assumes the two SHORTs are correctly laid out; the "cast" to uint32 is just to get a pointer to pass.
316 return uintptr(*((*uint32)(unsafe.Pointer(&c))))
317 }
318
319 // use is a no-op, but the compiler cannot see that it is.
320 // Calling use(p) ensures that p is kept live until that point.
321 func use(p interface{}) {}
0 // +build windows
1
2 package winterm
3
4 import "github.com/Azure/go-ansiterm"
5
6 const (
7 FOREGROUND_COLOR_MASK = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
8 BACKGROUND_COLOR_MASK = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
9 )
10
11 // collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
12 // request represented by the passed ANSI mode.
13 func collectAnsiIntoWindowsAttributes(windowsMode uint16, inverted bool, baseMode uint16, ansiMode int16) (uint16, bool) {
14 switch ansiMode {
15
16 // Mode styles
17 case ansiterm.ANSI_SGR_BOLD:
18 windowsMode = windowsMode | FOREGROUND_INTENSITY
19
20 case ansiterm.ANSI_SGR_DIM, ansiterm.ANSI_SGR_BOLD_DIM_OFF:
21 windowsMode &^= FOREGROUND_INTENSITY
22
23 case ansiterm.ANSI_SGR_UNDERLINE:
24 windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
25
26 case ansiterm.ANSI_SGR_REVERSE:
27 inverted = true
28
29 case ansiterm.ANSI_SGR_REVERSE_OFF:
30 inverted = false
31
32 case ansiterm.ANSI_SGR_UNDERLINE_OFF:
33 windowsMode &^= COMMON_LVB_UNDERSCORE
34
35 // Foreground colors
36 case ansiterm.ANSI_SGR_FOREGROUND_DEFAULT:
37 windowsMode = (windowsMode &^ FOREGROUND_MASK) | (baseMode & FOREGROUND_MASK)
38
39 case ansiterm.ANSI_SGR_FOREGROUND_BLACK:
40 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK)
41
42 case ansiterm.ANSI_SGR_FOREGROUND_RED:
43 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED
44
45 case ansiterm.ANSI_SGR_FOREGROUND_GREEN:
46 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN
47
48 case ansiterm.ANSI_SGR_FOREGROUND_YELLOW:
49 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN
50
51 case ansiterm.ANSI_SGR_FOREGROUND_BLUE:
52 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_BLUE
53
54 case ansiterm.ANSI_SGR_FOREGROUND_MAGENTA:
55 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_BLUE
56
57 case ansiterm.ANSI_SGR_FOREGROUND_CYAN:
58 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_GREEN | FOREGROUND_BLUE
59
60 case ansiterm.ANSI_SGR_FOREGROUND_WHITE:
61 windowsMode = (windowsMode &^ FOREGROUND_COLOR_MASK) | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE
62
63 // Background colors
64 case ansiterm.ANSI_SGR_BACKGROUND_DEFAULT:
65 // Black with no intensity
66 windowsMode = (windowsMode &^ BACKGROUND_MASK) | (baseMode & BACKGROUND_MASK)
67
68 case ansiterm.ANSI_SGR_BACKGROUND_BLACK:
69 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK)
70
71 case ansiterm.ANSI_SGR_BACKGROUND_RED:
72 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED
73
74 case ansiterm.ANSI_SGR_BACKGROUND_GREEN:
75 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN
76
77 case ansiterm.ANSI_SGR_BACKGROUND_YELLOW:
78 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN
79
80 case ansiterm.ANSI_SGR_BACKGROUND_BLUE:
81 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_BLUE
82
83 case ansiterm.ANSI_SGR_BACKGROUND_MAGENTA:
84 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_BLUE
85
86 case ansiterm.ANSI_SGR_BACKGROUND_CYAN:
87 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_GREEN | BACKGROUND_BLUE
88
89 case ansiterm.ANSI_SGR_BACKGROUND_WHITE:
90 windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
91 }
92
93 return windowsMode, inverted
94 }
95
96 // invertAttributes inverts the foreground and background colors of a Windows attributes value
97 func invertAttributes(windowsMode uint16) uint16 {
98 return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
99 }
0 // +build windows
1
2 package winterm
3
4 const (
5 horizontal = iota
6 vertical
7 )
8
9 func (h *windowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
10 if h.originMode {
11 sr := h.effectiveSr(info.Window)
12 return SMALL_RECT{
13 Top: sr.top,
14 Bottom: sr.bottom,
15 Left: 0,
16 Right: info.Size.X - 1,
17 }
18 } else {
19 return SMALL_RECT{
20 Top: info.Window.Top,
21 Bottom: info.Window.Bottom,
22 Left: 0,
23 Right: info.Size.X - 1,
24 }
25 }
26 }
27
28 // setCursorPosition sets the cursor to the specified position, bounded to the screen size
29 func (h *windowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
30 position.X = ensureInRange(position.X, window.Left, window.Right)
31 position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
32 err := SetConsoleCursorPosition(h.fd, position)
33 if err != nil {
34 return err
35 }
36 logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y)
37 return err
38 }
39
40 func (h *windowsAnsiEventHandler) moveCursorVertical(param int) error {
41 return h.moveCursor(vertical, param)
42 }
43
44 func (h *windowsAnsiEventHandler) moveCursorHorizontal(param int) error {
45 return h.moveCursor(horizontal, param)
46 }
47
48 func (h *windowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
49 info, err := GetConsoleScreenBufferInfo(h.fd)
50 if err != nil {
51 return err
52 }
53
54 position := info.CursorPosition
55 switch moveMode {
56 case horizontal:
57 position.X += int16(param)
58 case vertical:
59 position.Y += int16(param)
60 }
61
62 if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
63 return err
64 }
65
66 return nil
67 }
68
69 func (h *windowsAnsiEventHandler) moveCursorLine(param int) error {
70 info, err := GetConsoleScreenBufferInfo(h.fd)
71 if err != nil {
72 return err
73 }
74
75 position := info.CursorPosition
76 position.X = 0
77 position.Y += int16(param)
78
79 if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
80 return err
81 }
82
83 return nil
84 }
85
86 func (h *windowsAnsiEventHandler) moveCursorColumn(param int) error {
87 info, err := GetConsoleScreenBufferInfo(h.fd)
88 if err != nil {
89 return err
90 }
91
92 position := info.CursorPosition
93 position.X = int16(param) - 1
94
95 if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
96 return err
97 }
98
99 return nil
100 }
0 // +build windows
1
2 package winterm
3
4 import "github.com/Azure/go-ansiterm"
5
6 func (h *windowsAnsiEventHandler) clearRange(attributes uint16, fromCoord COORD, toCoord COORD) error {
7 // Ignore an invalid (negative area) request
8 if toCoord.Y < fromCoord.Y {
9 return nil
10 }
11
12 var err error
13
14 var coordStart = COORD{}
15 var coordEnd = COORD{}
16
17 xCurrent, yCurrent := fromCoord.X, fromCoord.Y
18 xEnd, yEnd := toCoord.X, toCoord.Y
19
20 // Clear any partial initial line
21 if xCurrent > 0 {
22 coordStart.X, coordStart.Y = xCurrent, yCurrent
23 coordEnd.X, coordEnd.Y = xEnd, yCurrent
24
25 err = h.clearRect(attributes, coordStart, coordEnd)
26 if err != nil {
27 return err
28 }
29
30 xCurrent = 0
31 yCurrent += 1
32 }
33
34 // Clear intervening rectangular section
35 if yCurrent < yEnd {
36 coordStart.X, coordStart.Y = xCurrent, yCurrent
37 coordEnd.X, coordEnd.Y = xEnd, yEnd-1
38
39 err = h.clearRect(attributes, coordStart, coordEnd)
40 if err != nil {
41 return err
42 }
43
44 xCurrent = 0
45 yCurrent = yEnd
46 }
47
48 // Clear remaining partial ending line
49 coordStart.X, coordStart.Y = xCurrent, yCurrent
50 coordEnd.X, coordEnd.Y = xEnd, yEnd
51
52 err = h.clearRect(attributes, coordStart, coordEnd)
53 if err != nil {
54 return err
55 }
56
57 return nil
58 }
59
60 func (h *windowsAnsiEventHandler) clearRect(attributes uint16, fromCoord COORD, toCoord COORD) error {
61 region := SMALL_RECT{Top: fromCoord.Y, Left: fromCoord.X, Bottom: toCoord.Y, Right: toCoord.X}
62 width := toCoord.X - fromCoord.X + 1
63 height := toCoord.Y - fromCoord.Y + 1
64 size := uint32(width) * uint32(height)
65
66 if size <= 0 {
67 return nil
68 }
69
70 buffer := make([]CHAR_INFO, size)
71
72 char := CHAR_INFO{ansiterm.FILL_CHARACTER, attributes}
73 for i := 0; i < int(size); i++ {
74 buffer[i] = char
75 }
76
77 err := WriteConsoleOutput(h.fd, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &region)
78 if err != nil {
79 return err
80 }
81
82 return nil
83 }
0 // +build windows
1
2 package winterm
3
4 // effectiveSr gets the current effective scroll region in buffer coordinates
5 func (h *windowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
6 top := addInRange(window.Top, h.sr.top, window.Top, window.Bottom)
7 bottom := addInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
8 if top >= bottom {
9 top = window.Top
10 bottom = window.Bottom
11 }
12 return scrollRegion{top: top, bottom: bottom}
13 }
14
15 func (h *windowsAnsiEventHandler) scrollUp(param int) error {
16 info, err := GetConsoleScreenBufferInfo(h.fd)
17 if err != nil {
18 return err
19 }
20
21 sr := h.effectiveSr(info.Window)
22 return h.scroll(param, sr, info)
23 }
24
25 func (h *windowsAnsiEventHandler) scrollDown(param int) error {
26 return h.scrollUp(-param)
27 }
28
29 func (h *windowsAnsiEventHandler) deleteLines(param int) error {
30 info, err := GetConsoleScreenBufferInfo(h.fd)
31 if err != nil {
32 return err
33 }
34
35 start := info.CursorPosition.Y
36 sr := h.effectiveSr(info.Window)
37 // Lines cannot be inserted or deleted outside the scrolling region.
38 if start >= sr.top && start <= sr.bottom {
39 sr.top = start
40 return h.scroll(param, sr, info)
41 } else {
42 return nil
43 }
44 }
45
46 func (h *windowsAnsiEventHandler) insertLines(param int) error {
47 return h.deleteLines(-param)
48 }
49
50 // scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
51 func (h *windowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
52 logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
53 logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
54
55 // Copy from and clip to the scroll region (full buffer width)
56 scrollRect := SMALL_RECT{
57 Top: sr.top,
58 Bottom: sr.bottom,
59 Left: 0,
60 Right: info.Size.X - 1,
61 }
62
63 // Origin to which area should be copied
64 destOrigin := COORD{
65 X: 0,
66 Y: sr.top - int16(param),
67 }
68
69 char := CHAR_INFO{
70 UnicodeChar: ' ',
71 Attributes: h.attributes,
72 }
73
74 if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
75 return err
76 }
77 return nil
78 }
79
80 func (h *windowsAnsiEventHandler) deleteCharacters(param int) error {
81 info, err := GetConsoleScreenBufferInfo(h.fd)
82 if err != nil {
83 return err
84 }
85 return h.scrollLine(param, info.CursorPosition, info)
86 }
87
88 func (h *windowsAnsiEventHandler) insertCharacters(param int) error {
89 return h.deleteCharacters(-param)
90 }
91
92 // scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
93 func (h *windowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
94 // Copy from and clip to the scroll region (full buffer width)
95 scrollRect := SMALL_RECT{
96 Top: position.Y,
97 Bottom: position.Y,
98 Left: position.X,
99 Right: info.Size.X - 1,
100 }
101
102 // Origin to which area should be copied
103 destOrigin := COORD{
104 X: position.X - int16(columns),
105 Y: position.Y,
106 }
107
108 char := CHAR_INFO{
109 UnicodeChar: ' ',
110 Attributes: h.attributes,
111 }
112
113 if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
114 return err
115 }
116 return nil
117 }
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 int16, increment int16, min int16, max int16) int16 {
7 return ensureInRange(n+increment, min, max)
8 }
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 attributes uint16
23 inverted bool
24 wrapNext bool
25 drewMarginByte bool
26 originMode bool
27 marginByte byte
28 curInfo *CONSOLE_SCREEN_BUFFER_INFO
29 curPos COORD
30 }
31
32 func CreateWinEventHandler(fd uintptr, file *os.File) ansiterm.AnsiEventHandler {
33 logFile := ioutil.Discard
34
35 if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
36 logFile, _ = os.Create("winEventHandler.log")
37 }
38
39 logger = &logrus.Logger{
40 Out: logFile,
41 Formatter: new(logrus.TextFormatter),
42 Level: logrus.DebugLevel,
43 }
44
45 infoReset, err := GetConsoleScreenBufferInfo(fd)
46 if err != nil {
47 return nil
48 }
49
50 return &windowsAnsiEventHandler{
51 fd: fd,
52 file: file,
53 infoReset: infoReset,
54 attributes: infoReset.Attributes,
55 }
56 }
57
58 type scrollRegion struct {
59 top int16
60 bottom int16
61 }
62
63 // simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
64 // current cursor position and scroll region settings, in which case it returns
65 // true. If no special handling is necessary, then it does nothing and returns
66 // false.
67 //
68 // In the false case, the caller should ensure that a carriage return
69 // and line feed are inserted or that the text is otherwise wrapped.
70 func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
71 if h.wrapNext {
72 if err := h.Flush(); err != nil {
73 return false, err
74 }
75 h.clearWrap()
76 }
77 pos, info, err := h.getCurrentInfo()
78 if err != nil {
79 return false, err
80 }
81 sr := h.effectiveSr(info.Window)
82 if pos.Y == sr.bottom {
83 // Scrolling is necessary. Let Windows automatically scroll if the scrolling region
84 // is the full window.
85 if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
86 if includeCR {
87 pos.X = 0
88 h.updatePos(pos)
89 }
90 return false, nil
91 }
92
93 // A custom scroll region is active. Scroll the window manually to simulate
94 // the LF.
95 if err := h.Flush(); err != nil {
96 return false, err
97 }
98 logger.Info("Simulating LF inside scroll region")
99 if err := h.scrollUp(1); err != nil {
100 return false, err
101 }
102 if includeCR {
103 pos.X = 0
104 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
105 return false, err
106 }
107 }
108 return true, nil
109
110 } else if pos.Y < info.Window.Bottom {
111 // Let Windows handle the LF.
112 pos.Y++
113 if includeCR {
114 pos.X = 0
115 }
116 h.updatePos(pos)
117 return false, nil
118 } else {
119 // The cursor is at the bottom of the screen but outside the scroll
120 // region. Skip the LF.
121 logger.Info("Simulating LF outside scroll region")
122 if includeCR {
123 if err := h.Flush(); err != nil {
124 return false, err
125 }
126 pos.X = 0
127 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
128 return false, err
129 }
130 }
131 return true, nil
132 }
133 }
134
135 // executeLF executes a LF without a CR.
136 func (h *windowsAnsiEventHandler) executeLF() error {
137 handled, err := h.simulateLF(false)
138 if err != nil {
139 return err
140 }
141 if !handled {
142 // Windows LF will reset the cursor column position. Write the LF
143 // and restore the cursor position.
144 pos, _, err := h.getCurrentInfo()
145 if err != nil {
146 return err
147 }
148 h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
149 if pos.X != 0 {
150 if err := h.Flush(); err != nil {
151 return err
152 }
153 logger.Info("Resetting cursor position for LF without CR")
154 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
155 return err
156 }
157 }
158 }
159 return nil
160 }
161
162 func (h *windowsAnsiEventHandler) Print(b byte) error {
163 if h.wrapNext {
164 h.buffer.WriteByte(h.marginByte)
165 h.clearWrap()
166 if _, err := h.simulateLF(true); err != nil {
167 return err
168 }
169 }
170 pos, info, err := h.getCurrentInfo()
171 if err != nil {
172 return err
173 }
174 if pos.X == info.Size.X-1 {
175 h.wrapNext = true
176 h.marginByte = b
177 } else {
178 pos.X++
179 h.updatePos(pos)
180 h.buffer.WriteByte(b)
181 }
182 return nil
183 }
184
185 func (h *windowsAnsiEventHandler) Execute(b byte) error {
186 switch b {
187 case ansiterm.ANSI_TAB:
188 logger.Info("Execute(TAB)")
189 // Move to the next tab stop, but preserve auto-wrap if already set.
190 if !h.wrapNext {
191 pos, info, err := h.getCurrentInfo()
192 if err != nil {
193 return err
194 }
195 pos.X = (pos.X + 8) - pos.X%8
196 if pos.X >= info.Size.X {
197 pos.X = info.Size.X - 1
198 }
199 if err := h.Flush(); err != nil {
200 return err
201 }
202 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
203 return err
204 }
205 }
206 return nil
207
208 case ansiterm.ANSI_BEL:
209 h.buffer.WriteByte(ansiterm.ANSI_BEL)
210 return nil
211
212 case ansiterm.ANSI_BACKSPACE:
213 if h.wrapNext {
214 if err := h.Flush(); err != nil {
215 return err
216 }
217 h.clearWrap()
218 }
219 pos, _, err := h.getCurrentInfo()
220 if err != nil {
221 return err
222 }
223 if pos.X > 0 {
224 pos.X--
225 h.updatePos(pos)
226 h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
227 }
228 return nil
229
230 case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
231 // Treat as true LF.
232 return h.executeLF()
233
234 case ansiterm.ANSI_LINE_FEED:
235 // Simulate a CR and LF for now since there is no way in go-ansiterm
236 // to tell if the LF should include CR (and more things break when it's
237 // missing than when it's incorrectly added).
238 handled, err := h.simulateLF(true)
239 if handled || err != nil {
240 return err
241 }
242 return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
243
244 case ansiterm.ANSI_CARRIAGE_RETURN:
245 if h.wrapNext {
246 if err := h.Flush(); err != nil {
247 return err
248 }
249 h.clearWrap()
250 }
251 pos, _, err := h.getCurrentInfo()
252 if err != nil {
253 return err
254 }
255 if pos.X != 0 {
256 pos.X = 0
257 h.updatePos(pos)
258 h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
259 }
260 return nil
261
262 default:
263 return nil
264 }
265 }
266
267 func (h *windowsAnsiEventHandler) CUU(param int) error {
268 if err := h.Flush(); err != nil {
269 return err
270 }
271 logger.Infof("CUU: [%v]", []string{strconv.Itoa(param)})
272 h.clearWrap()
273 return h.moveCursorVertical(-param)
274 }
275
276 func (h *windowsAnsiEventHandler) CUD(param int) error {
277 if err := h.Flush(); err != nil {
278 return err
279 }
280 logger.Infof("CUD: [%v]", []string{strconv.Itoa(param)})
281 h.clearWrap()
282 return h.moveCursorVertical(param)
283 }
284
285 func (h *windowsAnsiEventHandler) CUF(param int) error {
286 if err := h.Flush(); err != nil {
287 return err
288 }
289 logger.Infof("CUF: [%v]", []string{strconv.Itoa(param)})
290 h.clearWrap()
291 return h.moveCursorHorizontal(param)
292 }
293
294 func (h *windowsAnsiEventHandler) CUB(param int) error {
295 if err := h.Flush(); err != nil {
296 return err
297 }
298 logger.Infof("CUB: [%v]", []string{strconv.Itoa(param)})
299 h.clearWrap()
300 return h.moveCursorHorizontal(-param)
301 }
302
303 func (h *windowsAnsiEventHandler) CNL(param int) error {
304 if err := h.Flush(); err != nil {
305 return err
306 }
307 logger.Infof("CNL: [%v]", []string{strconv.Itoa(param)})
308 h.clearWrap()
309 return h.moveCursorLine(param)
310 }
311
312 func (h *windowsAnsiEventHandler) CPL(param int) error {
313 if err := h.Flush(); err != nil {
314 return err
315 }
316 logger.Infof("CPL: [%v]", []string{strconv.Itoa(param)})
317 h.clearWrap()
318 return h.moveCursorLine(-param)
319 }
320
321 func (h *windowsAnsiEventHandler) CHA(param int) error {
322 if err := h.Flush(); err != nil {
323 return err
324 }
325 logger.Infof("CHA: [%v]", []string{strconv.Itoa(param)})
326 h.clearWrap()
327 return h.moveCursorColumn(param)
328 }
329
330 func (h *windowsAnsiEventHandler) VPA(param int) error {
331 if err := h.Flush(); err != nil {
332 return err
333 }
334 logger.Infof("VPA: [[%d]]", param)
335 h.clearWrap()
336 info, err := GetConsoleScreenBufferInfo(h.fd)
337 if err != nil {
338 return err
339 }
340 window := h.getCursorWindow(info)
341 position := info.CursorPosition
342 position.Y = window.Top + int16(param) - 1
343 return h.setCursorPosition(position, window)
344 }
345
346 func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
347 if err := h.Flush(); err != nil {
348 return err
349 }
350 logger.Infof("CUP: [[%d %d]]", row, col)
351 h.clearWrap()
352 info, err := GetConsoleScreenBufferInfo(h.fd)
353 if err != nil {
354 return err
355 }
356
357 window := h.getCursorWindow(info)
358 position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
359 return h.setCursorPosition(position, window)
360 }
361
362 func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
363 if err := h.Flush(); err != nil {
364 return err
365 }
366 logger.Infof("HVP: [[%d %d]]", row, col)
367 h.clearWrap()
368 return h.CUP(row, col)
369 }
370
371 func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
372 if err := h.Flush(); err != nil {
373 return err
374 }
375 logger.Infof("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
376 h.clearWrap()
377 return nil
378 }
379
380 func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
381 if err := h.Flush(); err != nil {
382 return err
383 }
384 logger.Infof("DECOM: [%v]", []string{strconv.FormatBool(enable)})
385 h.clearWrap()
386 h.originMode = enable
387 return h.CUP(1, 1)
388 }
389
390 func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
391 if err := h.Flush(); err != nil {
392 return err
393 }
394 logger.Infof("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
395 h.clearWrap()
396 if err := h.ED(2); err != nil {
397 return err
398 }
399 info, err := GetConsoleScreenBufferInfo(h.fd)
400 if err != nil {
401 return err
402 }
403 targetWidth := int16(80)
404 if use132 {
405 targetWidth = 132
406 }
407 if info.Size.X < targetWidth {
408 if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
409 logger.Info("set buffer failed:", err)
410 return err
411 }
412 }
413 window := info.Window
414 window.Left = 0
415 window.Right = targetWidth - 1
416 if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
417 logger.Info("set window failed:", err)
418 return err
419 }
420 if info.Size.X > targetWidth {
421 if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
422 logger.Info("set buffer failed:", err)
423 return err
424 }
425 }
426 return SetConsoleCursorPosition(h.fd, COORD{0, 0})
427 }
428
429 func (h *windowsAnsiEventHandler) ED(param int) error {
430 if err := h.Flush(); err != nil {
431 return err
432 }
433 logger.Infof("ED: [%v]", []string{strconv.Itoa(param)})
434 h.clearWrap()
435
436 // [J -- Erases from the cursor to the end of the screen, including the cursor position.
437 // [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
438 // [2J -- Erases the complete display. The cursor does not move.
439 // Notes:
440 // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
441
442 info, err := GetConsoleScreenBufferInfo(h.fd)
443 if err != nil {
444 return err
445 }
446
447 var start COORD
448 var end COORD
449
450 switch param {
451 case 0:
452 start = info.CursorPosition
453 end = COORD{info.Size.X - 1, info.Size.Y - 1}
454
455 case 1:
456 start = COORD{0, 0}
457 end = info.CursorPosition
458
459 case 2:
460 start = COORD{0, 0}
461 end = COORD{info.Size.X - 1, info.Size.Y - 1}
462 }
463
464 err = h.clearRange(h.attributes, start, end)
465 if err != nil {
466 return err
467 }
468
469 // If the whole buffer was cleared, move the window to the top while preserving
470 // the window-relative cursor position.
471 if param == 2 {
472 pos := info.CursorPosition
473 window := info.Window
474 pos.Y -= window.Top
475 window.Bottom -= window.Top
476 window.Top = 0
477 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
478 return err
479 }
480 if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
481 return err
482 }
483 }
484
485 return nil
486 }
487
488 func (h *windowsAnsiEventHandler) EL(param int) error {
489 if err := h.Flush(); err != nil {
490 return err
491 }
492 logger.Infof("EL: [%v]", strconv.Itoa(param))
493 h.clearWrap()
494
495 // [K -- Erases from the cursor to the end of the line, including the cursor position.
496 // [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
497 // [2K -- Erases the complete line.
498
499 info, err := GetConsoleScreenBufferInfo(h.fd)
500 if err != nil {
501 return err
502 }
503
504 var start COORD
505 var end COORD
506
507 switch param {
508 case 0:
509 start = info.CursorPosition
510 end = COORD{info.Size.X, info.CursorPosition.Y}
511
512 case 1:
513 start = COORD{0, info.CursorPosition.Y}
514 end = info.CursorPosition
515
516 case 2:
517 start = COORD{0, info.CursorPosition.Y}
518 end = COORD{info.Size.X, info.CursorPosition.Y}
519 }
520
521 err = h.clearRange(h.attributes, start, end)
522 if err != nil {
523 return err
524 }
525
526 return nil
527 }
528
529 func (h *windowsAnsiEventHandler) IL(param int) error {
530 if err := h.Flush(); err != nil {
531 return err
532 }
533 logger.Infof("IL: [%v]", strconv.Itoa(param))
534 h.clearWrap()
535 return h.insertLines(param)
536 }
537
538 func (h *windowsAnsiEventHandler) DL(param int) error {
539 if err := h.Flush(); err != nil {
540 return err
541 }
542 logger.Infof("DL: [%v]", strconv.Itoa(param))
543 h.clearWrap()
544 return h.deleteLines(param)
545 }
546
547 func (h *windowsAnsiEventHandler) ICH(param int) error {
548 if err := h.Flush(); err != nil {
549 return err
550 }
551 logger.Infof("ICH: [%v]", strconv.Itoa(param))
552 h.clearWrap()
553 return h.insertCharacters(param)
554 }
555
556 func (h *windowsAnsiEventHandler) DCH(param int) error {
557 if err := h.Flush(); err != nil {
558 return err
559 }
560 logger.Infof("DCH: [%v]", strconv.Itoa(param))
561 h.clearWrap()
562 return h.deleteCharacters(param)
563 }
564
565 func (h *windowsAnsiEventHandler) SGR(params []int) error {
566 if err := h.Flush(); err != nil {
567 return err
568 }
569 strings := []string{}
570 for _, v := range params {
571 strings = append(strings, strconv.Itoa(v))
572 }
573
574 logger.Infof("SGR: [%v]", strings)
575
576 if len(params) <= 0 {
577 h.attributes = h.infoReset.Attributes
578 h.inverted = false
579 } else {
580 for _, attr := range params {
581
582 if attr == ansiterm.ANSI_SGR_RESET {
583 h.attributes = h.infoReset.Attributes
584 h.inverted = false
585 continue
586 }
587
588 h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
589 }
590 }
591
592 attributes := h.attributes
593 if h.inverted {
594 attributes = invertAttributes(attributes)
595 }
596 err := SetConsoleTextAttribute(h.fd, attributes)
597 if err != nil {
598 return err
599 }
600
601 return nil
602 }
603
604 func (h *windowsAnsiEventHandler) SU(param int) error {
605 if err := h.Flush(); err != nil {
606 return err
607 }
608 logger.Infof("SU: [%v]", []string{strconv.Itoa(param)})
609 h.clearWrap()
610 return h.scrollUp(param)
611 }
612
613 func (h *windowsAnsiEventHandler) SD(param int) error {
614 if err := h.Flush(); err != nil {
615 return err
616 }
617 logger.Infof("SD: [%v]", []string{strconv.Itoa(param)})
618 h.clearWrap()
619 return h.scrollDown(param)
620 }
621
622 func (h *windowsAnsiEventHandler) DA(params []string) error {
623 logger.Infof("DA: [%v]", params)
624 // DA cannot be implemented because it must send data on the VT100 input stream,
625 // which is not available to go-ansiterm.
626 return nil
627 }
628
629 func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
630 if err := h.Flush(); err != nil {
631 return err
632 }
633 logger.Infof("DECSTBM: [%d, %d]", top, bottom)
634
635 // Windows is 0 indexed, Linux is 1 indexed
636 h.sr.top = int16(top - 1)
637 h.sr.bottom = int16(bottom - 1)
638
639 // This command also moves the cursor to the origin.
640 h.clearWrap()
641 return h.CUP(1, 1)
642 }
643
644 func (h *windowsAnsiEventHandler) RI() error {
645 if err := h.Flush(); err != nil {
646 return err
647 }
648 logger.Info("RI: []")
649 h.clearWrap()
650
651 info, err := GetConsoleScreenBufferInfo(h.fd)
652 if err != nil {
653 return err
654 }
655
656 sr := h.effectiveSr(info.Window)
657 if info.CursorPosition.Y == sr.top {
658 return h.scrollDown(1)
659 }
660
661 return h.moveCursorVertical(-1)
662 }
663
664 func (h *windowsAnsiEventHandler) IND() error {
665 logger.Info("IND: []")
666 return h.executeLF()
667 }
668
669 func (h *windowsAnsiEventHandler) Flush() error {
670 h.curInfo = nil
671 if h.buffer.Len() > 0 {
672 logger.Infof("Flush: [%s]", h.buffer.Bytes())
673 if _, err := h.buffer.WriteTo(h.file); err != nil {
674 return err
675 }
676 }
677
678 if h.wrapNext && !h.drewMarginByte {
679 logger.Infof("Flush: drawing margin byte '%c'", h.marginByte)
680
681 info, err := GetConsoleScreenBufferInfo(h.fd)
682 if err != nil {
683 return err
684 }
685
686 charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
687 size := COORD{1, 1}
688 position := COORD{0, 0}
689 region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
690 if err := WriteConsoleOutput(h.fd, charInfo, size, position, &region); err != nil {
691 return err
692 }
693 h.drewMarginByte = true
694 }
695 return nil
696 }
697
698 // cacheConsoleInfo ensures that the current console screen information has been queried
699 // since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
700 func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
701 if h.curInfo == nil {
702 info, err := GetConsoleScreenBufferInfo(h.fd)
703 if err != nil {
704 return COORD{}, nil, err
705 }
706 h.curInfo = info
707 h.curPos = info.CursorPosition
708 }
709 return h.curPos, h.curInfo, nil
710 }
711
712 func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
713 if h.curInfo == nil {
714 panic("failed to call getCurrentInfo before calling updatePos")
715 }
716 h.curPos = pos
717 }
718
719 // clearWrap clears the state where the cursor is in the margin
720 // waiting for the next character before wrapping the line. This must
721 // be done before most operations that act on the cursor.
722 func (h *windowsAnsiEventHandler) clearWrap() {
723 h.wrapNext = false
724 h.drewMarginByte = false
725 }