Browse code

Update vendored go-ansiterm for Windows console

This update fixes Windows client console bugs and increases VT100
compatibility. With this change, nano and emacs become usable, and bash
works better.

Signed-off-by: John Starks <jostarks@microsoft.com>

root authored on 2015/08/07 12:38:56
Showing 11 changed files
... ...
@@ -6,7 +6,7 @@ rm -rf vendor/
6 6
 source 'hack/.vendor-helpers.sh'
7 7
 
8 8
 # the following lines are in sorted order, FYI
9
-clone git github.com/Azure/go-ansiterm 0a9ca7117fc3e5629da85238ede560cb5e749783
9
+clone git github.com/Azure/go-ansiterm 70b2c90b260171e829f1ebd7c17f600c11858dbe
10 10
 clone git github.com/Sirupsen/logrus v0.8.2 # logrus is a common dependency among multiple deps
11 11
 clone git github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
12 12
 clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673
... ...
@@ -77,7 +77,11 @@ const (
77 77
 	DEFAULT_HEIGHT   = 24
78 78
 
79 79
 	ANSI_BEL              = 0x07
80
+	ANSI_BACKSPACE        = 0x08
81
+	ANSI_TAB              = 0x09
80 82
 	ANSI_LINE_FEED        = 0x0A
83
+	ANSI_VERTICAL_TAB     = 0x0B
84
+	ANSI_FORM_FEED        = 0x0C
81 85
 	ANSI_CARRIAGE_RETURN  = 0x0D
82 86
 	ANSI_ESCAPE_PRIMARY   = 0x1B
83 87
 	ANSI_ESCAPE_SECONDARY = 0x5B
... ...
@@ -28,6 +28,9 @@ type AnsiEventHandler interface {
28 28
 	// Cursor Horizontal position Absolute
29 29
 	CHA(int) error
30 30
 
31
+	// Vertical line Position Absolute
32
+	VPA(int) error
33
+
31 34
 	// CUrsor Position
32 35
 	CUP(int, int) error
33 36
 
... ...
@@ -37,6 +40,12 @@ type AnsiEventHandler interface {
37 37
 	// Text Cursor Enable Mode
38 38
 	DECTCEM(bool) error
39 39
 
40
+	// Origin Mode
41
+	DECOM(bool) error
42
+
43
+	// 132 Column Mode
44
+	DECCOLM(bool) error
45
+
40 46
 	// Erase in Display
41 47
 	ED(int) error
42 48
 
... ...
@@ -49,6 +58,12 @@ type AnsiEventHandler interface {
49 49
 	// Delete Line
50 50
 	DL(int) error
51 51
 
52
+	// Insert Character
53
+	ICH(int) error
54
+
55
+	// Delete Character
56
+	DCH(int) error
57
+
52 58
 	// Set Graphics Rendition
53 59
 	SGR([]int) error
54 60
 
... ...
@@ -64,6 +79,9 @@ type AnsiEventHandler interface {
64 64
 	// Set Top and Bottom Margins
65 65
 	DECSTBM(int, int) error
66 66
 
67
+	// Index
68
+	IND() error
69
+
67 70
 	// Reverse Index
68 71
 	RI() error
69 72
 
... ...
@@ -65,17 +65,29 @@ func getInts(params []string, minCount int, dflt int) []int {
65 65
 	return ints
66 66
 }
67 67
 
68
+func (ap *AnsiParser) modeDispatch(param string, set bool) error {
69
+	switch param {
70
+	case "?3":
71
+		return ap.eventHandler.DECCOLM(set)
72
+	case "?6":
73
+		return ap.eventHandler.DECOM(set)
74
+	case "?25":
75
+		return ap.eventHandler.DECTCEM(set)
76
+	}
77
+	return nil
78
+}
79
+
68 80
 func (ap *AnsiParser) hDispatch(params []string) error {
69
-	if len(params) == 1 && params[0] == "?25" {
70
-		return ap.eventHandler.DECTCEM(true)
81
+	if len(params) == 1 {
82
+		return ap.modeDispatch(params[0], true)
71 83
 	}
72 84
 
73 85
 	return nil
74 86
 }
75 87
 
76 88
 func (ap *AnsiParser) lDispatch(params []string) error {
77
-	if len(params) == 1 && params[0] == "?25" {
78
-		return ap.eventHandler.DECTCEM(false)
89
+	if len(params) == 1 {
90
+		return ap.modeDispatch(params[0], false)
79 91
 	}
80 92
 
81 93
 	return nil
... ...
@@ -25,7 +25,15 @@ func (ap *AnsiParser) escDispatch() error {
25 25
 	logger.Infof("escDispatch: %v(%v)", cmd, intermeds)
26 26
 
27 27
 	switch cmd {
28
-	case "M":
28
+	case "D": // IND
29
+		return ap.eventHandler.IND()
30
+	case "E": // NEL, equivalent to CRLF
31
+		err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN)
32
+		if err == nil {
33
+			err = ap.eventHandler.Execute(ANSI_LINE_FEED)
34
+		}
35
+		return err
36
+	case "M": // RI
29 37
 		return ap.eventHandler.RI()
30 38
 	}
31 39
 
... ...
@@ -39,6 +47,8 @@ func (ap *AnsiParser) csiDispatch() error {
39 39
 	logger.Infof("csiDispatch: %v(%v)", cmd, params)
40 40
 
41 41
 	switch cmd {
42
+	case "@":
43
+		return ap.eventHandler.ICH(getInt(params, 1))
42 44
 	case "A":
43 45
 		return ap.eventHandler.CUU(getInt(params, 1))
44 46
 	case "B":
... ...
@@ -67,12 +77,16 @@ func (ap *AnsiParser) csiDispatch() error {
67 67
 		return ap.eventHandler.IL(getInt(params, 1))
68 68
 	case "M":
69 69
 		return ap.eventHandler.DL(getInt(params, 1))
70
+	case "P":
71
+		return ap.eventHandler.DCH(getInt(params, 1))
70 72
 	case "S":
71 73
 		return ap.eventHandler.SU(getInt(params, 1))
72 74
 	case "T":
73 75
 		return ap.eventHandler.SD(getInt(params, 1))
74 76
 	case "c":
75 77
 		return ap.eventHandler.DA(params)
78
+	case "d":
79
+		return ap.eventHandler.VPA(getInt(params, 1))
76 80
 	case "f":
77 81
 		ints := getInts(params, 2, 1)
78 82
 		x, y := ints[0], ints[1]
... ...
@@ -65,6 +65,11 @@ func (h *TestAnsiEventHandler) CHA(param int) error {
65 65
 	return nil
66 66
 }
67 67
 
68
+func (h *TestAnsiEventHandler) VPA(param int) error {
69
+	h.recordCall("VPA", []string{strconv.Itoa(param)})
70
+	return nil
71
+}
72
+
68 73
 func (h *TestAnsiEventHandler) CUP(x int, y int) error {
69 74
 	xS, yS := strconv.Itoa(x), strconv.Itoa(y)
70 75
 	h.recordCall("CUP", []string{xS, yS})
... ...
@@ -82,6 +87,16 @@ func (h *TestAnsiEventHandler) DECTCEM(visible bool) error {
82 82
 	return nil
83 83
 }
84 84
 
85
+func (h *TestAnsiEventHandler) DECOM(visible bool) error {
86
+	h.recordCall("DECOM", []string{strconv.FormatBool(visible)})
87
+	return nil
88
+}
89
+
90
+func (h *TestAnsiEventHandler) DECCOLM(use132 bool) error {
91
+	h.recordCall("DECOLM", []string{strconv.FormatBool(use132)})
92
+	return nil
93
+}
94
+
85 95
 func (h *TestAnsiEventHandler) ED(param int) error {
86 96
 	h.recordCall("ED", []string{strconv.Itoa(param)})
87 97
 	return nil
... ...
@@ -102,6 +117,16 @@ func (h *TestAnsiEventHandler) DL(param int) error {
102 102
 	return nil
103 103
 }
104 104
 
105
+func (h *TestAnsiEventHandler) ICH(param int) error {
106
+	h.recordCall("ICH", []string{strconv.Itoa(param)})
107
+	return nil
108
+}
109
+
110
+func (h *TestAnsiEventHandler) DCH(param int) error {
111
+	h.recordCall("DCH", []string{strconv.Itoa(param)})
112
+	return nil
113
+}
114
+
105 115
 func (h *TestAnsiEventHandler) SGR(params []int) error {
106 116
 	strings := []string{}
107 117
 	for _, v := range params {
... ...
@@ -138,6 +163,11 @@ func (h *TestAnsiEventHandler) RI() error {
138 138
 	return nil
139 139
 }
140 140
 
141
+func (h *TestAnsiEventHandler) IND() error {
142
+	h.recordCall("IND", nil)
143
+	return nil
144
+}
145
+
141 146
 func (h *TestAnsiEventHandler) Flush() error {
142 147
 	return nil
143 148
 }
... ...
@@ -9,7 +9,7 @@ import (
9 9
 	"strings"
10 10
 	"syscall"
11 11
 
12
-	. "github.com/docker/docker/vendor/src/github.com/Azure/go-ansiterm"
12
+	. "github.com/Azure/go-ansiterm"
13 13
 )
14 14
 
15 15
 // Windows keyboard constants
... ...
@@ -13,7 +13,7 @@ const (
13 13
 
14 14
 // collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the
15 15
 // request represented by the passed ANSI mode.
16
-func collectAnsiIntoWindowsAttributes(windowsMode WORD, baseMode WORD, ansiMode SHORT) WORD {
16
+func collectAnsiIntoWindowsAttributes(windowsMode WORD, inverted bool, baseMode WORD, ansiMode SHORT) (WORD, bool) {
17 17
 	switch ansiMode {
18 18
 
19 19
 	// Mode styles
... ...
@@ -26,9 +26,11 @@ func collectAnsiIntoWindowsAttributes(windowsMode WORD, baseMode WORD, ansiMode
26 26
 	case ANSI_SGR_UNDERLINE:
27 27
 		windowsMode = windowsMode | COMMON_LVB_UNDERSCORE
28 28
 
29
-	case ANSI_SGR_REVERSE, ANSI_SGR_REVERSE_OFF:
30
-		// Note: Windows does not support a native reverse. Simply swap the foreground / background color / intensity.
31
-		windowsMode = (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
29
+	case ANSI_SGR_REVERSE:
30
+		inverted = true
31
+
32
+	case ANSI_SGR_REVERSE_OFF:
33
+		inverted = false
32 34
 
33 35
 	case ANSI_SGR_UNDERLINE_OFF:
34 36
 		windowsMode &^= COMMON_LVB_UNDERSCORE
... ...
@@ -91,5 +93,10 @@ func collectAnsiIntoWindowsAttributes(windowsMode WORD, baseMode WORD, ansiMode
91 91
 		windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE
92 92
 	}
93 93
 
94
-	return windowsMode
94
+	return windowsMode, inverted
95
+}
96
+
97
+// invertAttributes inverts the foreground and background colors of a Windows attributes value
98
+func invertAttributes(windowsMode WORD) WORD {
99
+	return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4)
95 100
 }
... ...
@@ -7,11 +7,35 @@ const (
7 7
 	Vertical
8 8
 )
9 9
 
10
-// setCursorPosition sets the cursor to the specified position, bounded to the buffer size
11
-func (h *WindowsAnsiEventHandler) setCursorPosition(position COORD, sizeBuffer COORD) error {
12
-	position.X = ensureInRange(position.X, 0, sizeBuffer.X-1)
13
-	position.Y = ensureInRange(position.Y, 0, sizeBuffer.Y-1)
14
-	return SetConsoleCursorPosition(h.fd, position)
10
+func (h *WindowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT {
11
+	if h.originMode {
12
+		sr := h.effectiveSr(info.Window)
13
+		return SMALL_RECT{
14
+			Top:    sr.top,
15
+			Bottom: sr.bottom,
16
+			Left:   0,
17
+			Right:  info.Size.X - 1,
18
+		}
19
+	} else {
20
+		return SMALL_RECT{
21
+			Top:    info.Window.Top,
22
+			Bottom: info.Window.Bottom,
23
+			Left:   0,
24
+			Right:  info.Size.X - 1,
25
+		}
26
+	}
27
+}
28
+
29
+// setCursorPosition sets the cursor to the specified position, bounded to the screen size
30
+func (h *WindowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error {
31
+	position.X = ensureInRange(position.X, window.Left, window.Right)
32
+	position.Y = ensureInRange(position.Y, window.Top, window.Bottom)
33
+	err := SetConsoleCursorPosition(h.fd, position)
34
+	if err != nil {
35
+		return err
36
+	}
37
+	logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y)
38
+	return err
15 39
 }
16 40
 
17 41
 func (h *WindowsAnsiEventHandler) moveCursorVertical(param int) error {
... ...
@@ -31,17 +55,15 @@ func (h *WindowsAnsiEventHandler) moveCursor(moveMode int, param int) error {
31 31
 	position := info.CursorPosition
32 32
 	switch moveMode {
33 33
 	case Horizontal:
34
-		position.X = AddInRange(position.X, SHORT(param), info.Window.Left, info.Window.Right)
34
+		position.X += SHORT(param)
35 35
 	case Vertical:
36
-		position.Y = AddInRange(position.Y, SHORT(param), info.Window.Top, info.Window.Bottom)
36
+		position.Y += SHORT(param)
37 37
 	}
38 38
 
39
-	if err = h.setCursorPosition(position, info.Size); err != nil {
39
+	if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
40 40
 		return err
41 41
 	}
42 42
 
43
-	logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y)
44
-
45 43
 	return nil
46 44
 }
47 45
 
... ...
@@ -53,9 +75,9 @@ func (h *WindowsAnsiEventHandler) moveCursorLine(param int) error {
53 53
 
54 54
 	position := info.CursorPosition
55 55
 	position.X = 0
56
-	position.Y = AddInRange(position.Y, SHORT(param), info.Window.Top, info.Window.Bottom)
56
+	position.Y += SHORT(param)
57 57
 
58
-	if err = h.setCursorPosition(position, info.Size); err != nil {
58
+	if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
59 59
 		return err
60 60
 	}
61 61
 
... ...
@@ -69,9 +91,9 @@ func (h *WindowsAnsiEventHandler) moveCursorColumn(param int) error {
69 69
 	}
70 70
 
71 71
 	position := info.CursorPosition
72
-	position.X = AddInRange(SHORT(param), -1, info.Window.Left, info.Window.Right)
72
+	position.X = SHORT(param) - 1
73 73
 
74
-	if err = h.setCursorPosition(position, info.Size); err != nil {
74
+	if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil {
75 75
 		return err
76 76
 	}
77 77
 
... ...
@@ -2,89 +2,117 @@
2 2
 
3 3
 package winterm
4 4
 
5
-func (h *WindowsAnsiEventHandler) scrollPageUp() error {
6
-	return h.scrollPage(1)
5
+// effectiveSr gets the current effective scroll region in buffer coordinates
6
+func (h *WindowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion {
7
+	top := AddInRange(window.Top, h.sr.top, window.Top, window.Bottom)
8
+	bottom := AddInRange(window.Top, h.sr.bottom, window.Top, window.Bottom)
9
+	if top >= bottom {
10
+		top = window.Top
11
+		bottom = window.Bottom
12
+	}
13
+	return scrollRegion{top: top, bottom: bottom}
14
+}
15
+
16
+func (h *WindowsAnsiEventHandler) scrollUp(param int) error {
17
+	info, err := GetConsoleScreenBufferInfo(h.fd)
18
+	if err != nil {
19
+		return err
20
+	}
21
+
22
+	sr := h.effectiveSr(info.Window)
23
+	return h.scroll(param, sr, info)
7 24
 }
8 25
 
9
-func (h *WindowsAnsiEventHandler) scrollPageDown() error {
10
-	return h.scrollPage(-1)
26
+func (h *WindowsAnsiEventHandler) scrollDown(param int) error {
27
+	return h.scrollUp(-param)
11 28
 }
12 29
 
13
-func (h *WindowsAnsiEventHandler) scrollPage(param int) error {
30
+func (h *WindowsAnsiEventHandler) deleteLines(param int) error {
14 31
 	info, err := GetConsoleScreenBufferInfo(h.fd)
15 32
 	if err != nil {
16 33
 		return err
17 34
 	}
18 35
 
19
-	tmpScrollTop := h.sr.top
20
-	tmpScrollBottom := h.sr.bottom
36
+	start := info.CursorPosition.Y
37
+	sr := h.effectiveSr(info.Window)
38
+	// Lines cannot be inserted or deleted outside the scrolling region.
39
+	if start >= sr.top && start <= sr.bottom {
40
+		sr.top = start
41
+		return h.scroll(param, sr, info)
42
+	} else {
43
+		return nil
44
+	}
45
+}
21 46
 
22
-	// Set scroll region to whole window
23
-	h.sr.top = 0
24
-	h.sr.bottom = int(info.Size.Y - 1)
47
+func (h *WindowsAnsiEventHandler) insertLines(param int) error {
48
+	return h.deleteLines(-param)
49
+}
25 50
 
26
-	err = h.scroll(param)
51
+// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates.
52
+func (h *WindowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error {
53
+	logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom)
54
+	logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
27 55
 
28
-	h.sr.top = tmpScrollTop
29
-	h.sr.bottom = tmpScrollBottom
56
+	// Copy from and clip to the scroll region (full buffer width)
57
+	scrollRect := SMALL_RECT{
58
+		Top:    sr.top,
59
+		Bottom: sr.bottom,
60
+		Left:   0,
61
+		Right:  info.Size.X - 1,
62
+	}
30 63
 
31
-	return err
32
-}
64
+	// Origin to which area should be copied
65
+	destOrigin := COORD{
66
+		X: 0,
67
+		Y: sr.top - SHORT(param),
68
+	}
33 69
 
34
-func (h *WindowsAnsiEventHandler) scrollUp(param int) error {
35
-	return h.scroll(param)
36
-}
70
+	char := CHAR_INFO{
71
+		UnicodeChar: ' ',
72
+		Attributes:  h.attributes,
73
+	}
37 74
 
38
-func (h *WindowsAnsiEventHandler) scrollDown(param int) error {
39
-	return h.scroll(-param)
75
+	if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
76
+		return err
77
+	}
78
+	return nil
40 79
 }
41 80
 
42
-func (h *WindowsAnsiEventHandler) scroll(param int) error {
43
-
81
+func (h *WindowsAnsiEventHandler) deleteCharacters(param int) error {
44 82
 	info, err := GetConsoleScreenBufferInfo(h.fd)
45 83
 	if err != nil {
46 84
 		return err
47 85
 	}
86
+	return h.scrollLine(param, info.CursorPosition, info)
87
+}
48 88
 
49
-	logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", h.sr.top, h.sr.bottom)
50
-	logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom)
51
-
52
-	rect := info.Window
53
-
54
-	// Current scroll region in Windows backing buffer coordinates
55
-	top := rect.Top + SHORT(h.sr.top)
56
-	bottom := rect.Top + SHORT(h.sr.bottom)
89
+func (h *WindowsAnsiEventHandler) insertCharacters(param int) error {
90
+	return h.deleteCharacters(-param)
91
+}
57 92
 
58
-	// Area from backing buffer to be copied
93
+// scrollLine scrolls a line horizontally starting at the provided position by a number of columns.
94
+func (h *WindowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error {
95
+	// Copy from and clip to the scroll region (full buffer width)
59 96
 	scrollRect := SMALL_RECT{
60
-		Top:    top + SHORT(param),
61
-		Bottom: bottom + SHORT(param),
62
-		Left:   rect.Left,
63
-		Right:  rect.Right,
64
-	}
65
-
66
-	// Clipping region should be the original scroll region
67
-	clipRegion := SMALL_RECT{
68
-		Top:    top,
69
-		Bottom: bottom,
70
-		Left:   rect.Left,
71
-		Right:  rect.Right,
97
+		Top:    position.Y,
98
+		Bottom: position.Y,
99
+		Left:   position.X,
100
+		Right:  info.Size.X - 1,
72 101
 	}
73 102
 
74 103
 	// Origin to which area should be copied
75 104
 	destOrigin := COORD{
76
-		X: rect.Left,
77
-		Y: top,
105
+		X: position.X - SHORT(columns),
106
+		Y: position.Y,
78 107
 	}
79 108
 
80 109
 	char := CHAR_INFO{
81 110
 		UnicodeChar: ' ',
82
-		Attributes:  0,
111
+		Attributes:  h.attributes,
83 112
 	}
84 113
 
85
-	if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, clipRegion, destOrigin, char); err != nil {
114
+	if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil {
86 115
 		return err
87 116
 	}
88
-
89 117
 	return nil
90 118
 }
... ...
@@ -15,11 +15,19 @@ import (
15 15
 var logger *logrus.Logger
16 16
 
17 17
 type WindowsAnsiEventHandler struct {
18
-	fd        uintptr
19
-	file      *os.File
20
-	infoReset *CONSOLE_SCREEN_BUFFER_INFO
21
-	sr        scrollRegion
22
-	buffer    bytes.Buffer
18
+	fd             uintptr
19
+	file           *os.File
20
+	infoReset      *CONSOLE_SCREEN_BUFFER_INFO
21
+	sr             scrollRegion
22
+	buffer         bytes.Buffer
23
+	attributes     WORD
24
+	inverted       bool
25
+	wrapNext       bool
26
+	drewMarginByte bool
27
+	originMode     bool
28
+	marginByte     byte
29
+	curInfo        *CONSOLE_SCREEN_BUFFER_INFO
30
+	curPos         COORD
23 31
 }
24 32
 
25 33
 func CreateWinEventHandler(fd uintptr, file *os.File) AnsiEventHandler {
... ...
@@ -40,60 +48,220 @@ func CreateWinEventHandler(fd uintptr, file *os.File) AnsiEventHandler {
40 40
 		return nil
41 41
 	}
42 42
 
43
-	sr := scrollRegion{int(infoReset.Window.Top), int(infoReset.Window.Bottom)}
44
-
45 43
 	return &WindowsAnsiEventHandler{
46
-		fd:        fd,
47
-		file:      file,
48
-		infoReset: infoReset,
49
-		sr:        sr,
44
+		fd:         fd,
45
+		file:       file,
46
+		infoReset:  infoReset,
47
+		attributes: infoReset.Attributes,
50 48
 	}
51 49
 }
52 50
 
53 51
 type scrollRegion struct {
54
-	top    int
55
-	bottom int
52
+	top    SHORT
53
+	bottom SHORT
56 54
 }
57 55
 
58
-func (h *WindowsAnsiEventHandler) Print(b byte) error {
59
-	return h.buffer.WriteByte(b)
56
+// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
57
+// current cursor position and scroll region settings, in which case it returns
58
+// true. If no special handling is necessary, then it does nothing and returns
59
+// false.
60
+//
61
+// In the false case, the caller should ensure that a carriage return
62
+// and line feed are inserted or that the text is otherwise wrapped.
63
+func (h *WindowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
64
+	if h.wrapNext {
65
+		if err := h.Flush(); err != nil {
66
+			return false, err
67
+		}
68
+		h.clearWrap()
69
+	}
70
+	pos, info, err := h.getCurrentInfo()
71
+	if err != nil {
72
+		return false, err
73
+	}
74
+	sr := h.effectiveSr(info.Window)
75
+	if pos.Y == sr.bottom {
76
+		// Scrolling is necessary. Let Windows automatically scroll if the scrolling region
77
+		// is the full window.
78
+		if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
79
+			if includeCR {
80
+				pos.X = 0
81
+				h.updatePos(pos)
82
+			}
83
+			return false, nil
84
+		} else {
85
+			// A custom scroll region is active. Scroll the window manually to simulate
86
+			// the LF.
87
+			if err := h.Flush(); err != nil {
88
+				return false, err
89
+			}
90
+			logger.Info("Simulating LF inside scroll region")
91
+			if err := h.scrollUp(1); err != nil {
92
+				return false, err
93
+			}
94
+			if includeCR {
95
+				pos.X = 0
96
+				if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
97
+					return false, err
98
+				}
99
+			}
100
+			return true, nil
101
+		}
102
+	} else if pos.Y < info.Window.Bottom {
103
+		// Let Windows handle the LF.
104
+		pos.Y++
105
+		if includeCR {
106
+			pos.X = 0
107
+		}
108
+		h.updatePos(pos)
109
+		return false, nil
110
+	} else {
111
+		// The cursor is at the bottom of the screen but outside the scroll
112
+		// region. Skip the LF.
113
+		logger.Info("Simulating LF outside scroll region")
114
+		if includeCR {
115
+			if err := h.Flush(); err != nil {
116
+				return false, err
117
+			}
118
+			pos.X = 0
119
+			if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
120
+				return false, err
121
+			}
122
+		}
123
+		return true, nil
124
+	}
60 125
 }
61 126
 
62
-func (h *WindowsAnsiEventHandler) Execute(b byte) error {
63
-	if ANSI_LINE_FEED == b {
64
-		info, err := GetConsoleScreenBufferInfo(h.fd)
127
+// executeLF executes a LF without a CR.
128
+func (h *WindowsAnsiEventHandler) executeLF() error {
129
+	handled, err := h.simulateLF(false)
130
+	if err != nil {
131
+		return err
132
+	}
133
+	if !handled {
134
+		// Windows LF will reset the cursor column position. Write the LF
135
+		// and restore the cursor position.
136
+		pos, _, err := h.getCurrentInfo()
65 137
 		if err != nil {
66 138
 			return err
67 139
 		}
140
+		h.buffer.WriteByte(ANSI_LINE_FEED)
141
+		if pos.X != 0 {
142
+			if err := h.Flush(); err != nil {
143
+				return err
144
+			}
145
+			logger.Info("Resetting cursor position for LF without CR")
146
+			if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
147
+				return err
148
+			}
149
+		}
150
+	}
151
+	return nil
152
+}
68 153
 
69
-		if int(info.CursorPosition.Y) == h.sr.bottom {
154
+func (h *WindowsAnsiEventHandler) Print(b byte) error {
155
+	if h.wrapNext {
156
+		h.buffer.WriteByte(h.marginByte)
157
+		h.clearWrap()
158
+		if _, err := h.simulateLF(true); err != nil {
159
+			return err
160
+		}
161
+	}
162
+	pos, info, err := h.getCurrentInfo()
163
+	if err != nil {
164
+		return err
165
+	}
166
+	if pos.X == info.Size.X-1 {
167
+		h.wrapNext = true
168
+		h.marginByte = b
169
+	} else {
170
+		pos.X++
171
+		h.updatePos(pos)
172
+		h.buffer.WriteByte(b)
173
+	}
174
+	return nil
175
+}
176
+
177
+func (h *WindowsAnsiEventHandler) Execute(b byte) error {
178
+	switch b {
179
+	case ANSI_TAB:
180
+		logger.Info("Execute(TAB)")
181
+		// Move to the next tab stop, but preserve auto-wrap if already set.
182
+		if !h.wrapNext {
183
+			pos, info, err := h.getCurrentInfo()
184
+			if err != nil {
185
+				return err
186
+			}
187
+			pos.X = (pos.X + 8) - pos.X%8
188
+			if pos.X >= info.Size.X {
189
+				pos.X = info.Size.X - 1
190
+			}
70 191
 			if err := h.Flush(); err != nil {
71 192
 				return err
72 193
 			}
194
+			if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
195
+				return err
196
+			}
197
+		}
198
+		return nil
73 199
 
74
-			logger.Infof("Scrolling due to LF at bottom of scroll region")
200
+	case ANSI_BEL:
201
+		h.buffer.WriteByte(ANSI_BEL)
202
+		return nil
75 203
 
76
-			// Scroll up one row if we attempt to line feed at the bottom
77
-			// of the scroll region
78
-			if err := h.scrollUp(1); err != nil {
204
+	case ANSI_BACKSPACE:
205
+		if h.wrapNext {
206
+			if err := h.Flush(); err != nil {
79 207
 				return err
80 208
 			}
209
+			h.clearWrap()
210
+		}
211
+		pos, _, err := h.getCurrentInfo()
212
+		if err != nil {
213
+			return err
214
+		}
215
+		if pos.X > 0 {
216
+			pos.X--
217
+			h.updatePos(pos)
218
+			h.buffer.WriteByte(ANSI_BACKSPACE)
219
+		}
220
+		return nil
221
+
222
+	case ANSI_VERTICAL_TAB, ANSI_FORM_FEED:
223
+		// Treat as true LF.
224
+		return h.executeLF()
225
+
226
+	case ANSI_LINE_FEED:
227
+		// Simulate a CR and LF for now since there is no way in go-ansiterm
228
+		// to tell if the LF should include CR (and more things break when it's
229
+		// missing than when it's incorrectly added).
230
+		handled, err := h.simulateLF(true)
231
+		if handled || err != nil {
232
+			return err
233
+		}
234
+		return h.buffer.WriteByte(ANSI_LINE_FEED)
81 235
 
82
-			// Clear line
83
-			// if err := h.CUD(1); err != nil {
84
-			// 	return err
85
-			// }
86
-			if err := h.EL(0); err != nil {
236
+	case ANSI_CARRIAGE_RETURN:
237
+		if h.wrapNext {
238
+			if err := h.Flush(); err != nil {
87 239
 				return err
88 240
 			}
241
+			h.clearWrap()
89 242
 		}
90
-	}
243
+		pos, _, err := h.getCurrentInfo()
244
+		if err != nil {
245
+			return err
246
+		}
247
+		if pos.X != 0 {
248
+			pos.X = 0
249
+			h.updatePos(pos)
250
+			h.buffer.WriteByte(ANSI_CARRIAGE_RETURN)
251
+		}
252
+		return nil
91 253
 
92
-	if ANSI_BEL <= b && b <= ANSI_CARRIAGE_RETURN {
93
-		return h.buffer.WriteByte(b)
254
+	default:
255
+		return nil
94 256
 	}
95
-
96
-	return nil
97 257
 }
98 258
 
99 259
 func (h *WindowsAnsiEventHandler) CUU(param int) error {
... ...
@@ -101,6 +269,7 @@ func (h *WindowsAnsiEventHandler) CUU(param int) error {
101 101
 		return err
102 102
 	}
103 103
 	logger.Infof("CUU: [%v]", []string{strconv.Itoa(param)})
104
+	h.clearWrap()
104 105
 	return h.moveCursorVertical(-param)
105 106
 }
106 107
 
... ...
@@ -109,6 +278,7 @@ func (h *WindowsAnsiEventHandler) CUD(param int) error {
109 109
 		return err
110 110
 	}
111 111
 	logger.Infof("CUD: [%v]", []string{strconv.Itoa(param)})
112
+	h.clearWrap()
112 113
 	return h.moveCursorVertical(param)
113 114
 }
114 115
 
... ...
@@ -117,6 +287,7 @@ func (h *WindowsAnsiEventHandler) CUF(param int) error {
117 117
 		return err
118 118
 	}
119 119
 	logger.Infof("CUF: [%v]", []string{strconv.Itoa(param)})
120
+	h.clearWrap()
120 121
 	return h.moveCursorHorizontal(param)
121 122
 }
122 123
 
... ...
@@ -125,6 +296,7 @@ func (h *WindowsAnsiEventHandler) CUB(param int) error {
125 125
 		return err
126 126
 	}
127 127
 	logger.Infof("CUB: [%v]", []string{strconv.Itoa(param)})
128
+	h.clearWrap()
128 129
 	return h.moveCursorHorizontal(-param)
129 130
 }
130 131
 
... ...
@@ -133,6 +305,7 @@ func (h *WindowsAnsiEventHandler) CNL(param int) error {
133 133
 		return err
134 134
 	}
135 135
 	logger.Infof("CNL: [%v]", []string{strconv.Itoa(param)})
136
+	h.clearWrap()
136 137
 	return h.moveCursorLine(param)
137 138
 }
138 139
 
... ...
@@ -141,6 +314,7 @@ func (h *WindowsAnsiEventHandler) CPL(param int) error {
141 141
 		return err
142 142
 	}
143 143
 	logger.Infof("CPL: [%v]", []string{strconv.Itoa(param)})
144
+	h.clearWrap()
144 145
 	return h.moveCursorLine(-param)
145 146
 }
146 147
 
... ...
@@ -149,34 +323,48 @@ func (h *WindowsAnsiEventHandler) CHA(param int) error {
149 149
 		return err
150 150
 	}
151 151
 	logger.Infof("CHA: [%v]", []string{strconv.Itoa(param)})
152
+	h.clearWrap()
152 153
 	return h.moveCursorColumn(param)
153 154
 }
154 155
 
155
-func (h *WindowsAnsiEventHandler) CUP(row int, col int) error {
156
+func (h *WindowsAnsiEventHandler) VPA(param int) error {
156 157
 	if err := h.Flush(); err != nil {
157 158
 		return err
158 159
 	}
159
-	rowStr, colStr := strconv.Itoa(row), strconv.Itoa(col)
160
-	logger.Infof("CUP: [%v]", []string{rowStr, colStr})
160
+	logger.Infof("VPA: [[%d]]", param)
161
+	h.clearWrap()
161 162
 	info, err := GetConsoleScreenBufferInfo(h.fd)
162 163
 	if err != nil {
163 164
 		return err
164 165
 	}
166
+	window := h.getCursorWindow(info)
167
+	position := info.CursorPosition
168
+	position.Y = window.Top + SHORT(param) - 1
169
+	return h.setCursorPosition(position, window)
170
+}
165 171
 
166
-	rect := info.Window
167
-	rowS := AddInRange(SHORT(row-1), rect.Top, rect.Top, rect.Bottom)
168
-	colS := AddInRange(SHORT(col-1), rect.Left, rect.Left, rect.Right)
169
-	position := COORD{colS, rowS}
172
+func (h *WindowsAnsiEventHandler) CUP(row int, col int) error {
173
+	if err := h.Flush(); err != nil {
174
+		return err
175
+	}
176
+	logger.Infof("CUP: [[%d %d]]", row, col)
177
+	h.clearWrap()
178
+	info, err := GetConsoleScreenBufferInfo(h.fd)
179
+	if err != nil {
180
+		return err
181
+	}
170 182
 
171
-	return h.setCursorPosition(position, info.Size)
183
+	window := h.getCursorWindow(info)
184
+	position := COORD{window.Left + SHORT(col) - 1, window.Top + SHORT(row) - 1}
185
+	return h.setCursorPosition(position, window)
172 186
 }
173 187
 
174 188
 func (h *WindowsAnsiEventHandler) HVP(row int, col int) error {
175 189
 	if err := h.Flush(); err != nil {
176 190
 		return err
177 191
 	}
178
-	rowS, colS := strconv.Itoa(row), strconv.Itoa(row)
179
-	logger.Infof("HVP: [%v]", []string{rowS, colS})
192
+	logger.Infof("HVP: [[%d %d]]", row, col)
193
+	h.clearWrap()
180 194
 	return h.CUP(row, col)
181 195
 }
182 196
 
... ...
@@ -185,22 +373,70 @@ func (h *WindowsAnsiEventHandler) DECTCEM(visible bool) error {
185 185
 		return err
186 186
 	}
187 187
 	logger.Infof("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
188
-
188
+	h.clearWrap()
189 189
 	return nil
190 190
 }
191 191
 
192
+func (h *WindowsAnsiEventHandler) DECOM(enable bool) error {
193
+	if err := h.Flush(); err != nil {
194
+		return err
195
+	}
196
+	logger.Infof("DECOM: [%v]", []string{strconv.FormatBool(enable)})
197
+	h.clearWrap()
198
+	h.originMode = enable
199
+	return h.CUP(1, 1)
200
+}
201
+
202
+func (h *WindowsAnsiEventHandler) DECCOLM(use132 bool) error {
203
+	if err := h.Flush(); err != nil {
204
+		return err
205
+	}
206
+	logger.Infof("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
207
+	h.clearWrap()
208
+	if err := h.ED(2); err != nil {
209
+		return err
210
+	}
211
+	info, err := GetConsoleScreenBufferInfo(h.fd)
212
+	if err != nil {
213
+		return err
214
+	}
215
+	targetWidth := SHORT(80)
216
+	if use132 {
217
+		targetWidth = 132
218
+	}
219
+	if info.Size.X < targetWidth {
220
+		if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
221
+			logger.Info("set buffer failed:", err)
222
+			return err
223
+		}
224
+	}
225
+	window := info.Window
226
+	window.Left = 0
227
+	window.Right = targetWidth - 1
228
+	if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
229
+		logger.Info("set window failed:", err)
230
+		return err
231
+	}
232
+	if info.Size.X > targetWidth {
233
+		if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
234
+			logger.Info("set buffer failed:", err)
235
+			return err
236
+		}
237
+	}
238
+	return SetConsoleCursorPosition(h.fd, COORD{0, 0})
239
+}
240
+
192 241
 func (h *WindowsAnsiEventHandler) ED(param int) error {
193 242
 	if err := h.Flush(); err != nil {
194 243
 		return err
195 244
 	}
196 245
 	logger.Infof("ED: [%v]", []string{strconv.Itoa(param)})
246
+	h.clearWrap()
197 247
 
198 248
 	// [J  -- Erases from the cursor to the end of the screen, including the cursor position.
199 249
 	// [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
200 250
 	// [2J -- Erases the complete display. The cursor does not move.
201
-	// [3J -- Erases the complete display and backing buffer, cursor moves to (0,0)
202 251
 	// Notes:
203
-	// -- ANSI.SYS always moved the cursor to (0,0) for both [2J and [3J
204 252
 	// -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
205 253
 
206 254
 	info, err := GetConsoleScreenBufferInfo(h.fd)
... ...
@@ -223,20 +459,25 @@ func (h *WindowsAnsiEventHandler) ED(param int) error {
223 223
 	case 2:
224 224
 		start = COORD{0, 0}
225 225
 		end = COORD{info.Size.X - 1, info.Size.Y - 1}
226
-
227
-	case 3:
228
-		start = COORD{0, 0}
229
-		end = COORD{info.Size.X - 1, info.Size.Y - 1}
230 226
 	}
231 227
 
232
-	err = h.clearRange(info.Attributes, start, end)
228
+	err = h.clearRange(h.attributes, start, end)
233 229
 	if err != nil {
234 230
 		return err
235 231
 	}
236 232
 
237
-	if param == 2 || param == 3 {
238
-		err = h.setCursorPosition(COORD{0, 0}, info.Size)
239
-		if err != nil {
233
+	// If the whole buffer was cleared, move the window to the top while preserving
234
+	// the window-relative cursor position.
235
+	if param == 2 {
236
+		pos := info.CursorPosition
237
+		window := info.Window
238
+		pos.Y -= window.Top
239
+		window.Bottom -= window.Top
240
+		window.Top = 0
241
+		if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
242
+			return err
243
+		}
244
+		if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
240 245
 			return err
241 246
 		}
242 247
 	}
... ...
@@ -249,6 +490,7 @@ func (h *WindowsAnsiEventHandler) EL(param int) error {
249 249
 		return err
250 250
 	}
251 251
 	logger.Infof("EL: [%v]", strconv.Itoa(param))
252
+	h.clearWrap()
252 253
 
253 254
 	// [K  -- Erases from the cursor to the end of the line, including the cursor position.
254 255
 	// [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
... ...
@@ -276,7 +518,7 @@ func (h *WindowsAnsiEventHandler) EL(param int) error {
276 276
 		end = COORD{info.Size.X, info.CursorPosition.Y}
277 277
 	}
278 278
 
279
-	err = h.clearRange(info.Attributes, start, end)
279
+	err = h.clearRange(h.attributes, start, end)
280 280
 	if err != nil {
281 281
 		return err
282 282
 	}
... ...
@@ -289,19 +531,35 @@ func (h *WindowsAnsiEventHandler) IL(param int) error {
289 289
 		return err
290 290
 	}
291 291
 	logger.Infof("IL: [%v]", strconv.Itoa(param))
292
-	if err := h.scrollDown(param); err != nil {
292
+	h.clearWrap()
293
+	return h.insertLines(param)
294
+}
295
+
296
+func (h *WindowsAnsiEventHandler) DL(param int) error {
297
+	if err := h.Flush(); err != nil {
293 298
 		return err
294 299
 	}
300
+	logger.Infof("DL: [%v]", strconv.Itoa(param))
301
+	h.clearWrap()
302
+	return h.deleteLines(param)
303
+}
295 304
 
296
-	return h.EL(2)
305
+func (h *WindowsAnsiEventHandler) ICH(param int) error {
306
+	if err := h.Flush(); err != nil {
307
+		return err
308
+	}
309
+	logger.Infof("ICH: [%v]", strconv.Itoa(param))
310
+	h.clearWrap()
311
+	return h.insertCharacters(param)
297 312
 }
298 313
 
299
-func (h *WindowsAnsiEventHandler) DL(param int) error {
314
+func (h *WindowsAnsiEventHandler) DCH(param int) error {
300 315
 	if err := h.Flush(); err != nil {
301 316
 		return err
302 317
 	}
303
-	logger.Infof("DL: [%v]", strconv.Itoa(param))
304
-	return h.scrollUp(param)
318
+	logger.Infof("DCH: [%v]", strconv.Itoa(param))
319
+	h.clearWrap()
320
+	return h.deleteCharacters(param)
305 321
 }
306 322
 
307 323
 func (h *WindowsAnsiEventHandler) SGR(params []int) error {
... ...
@@ -310,33 +568,32 @@ func (h *WindowsAnsiEventHandler) SGR(params []int) error {
310 310
 	}
311 311
 	strings := []string{}
312 312
 	for _, v := range params {
313
-		logger.Infof("SGR: [%v]", strings)
314 313
 		strings = append(strings, strconv.Itoa(v))
315 314
 	}
316 315
 
317 316
 	logger.Infof("SGR: [%v]", strings)
318 317
 
319
-	info, err := GetConsoleScreenBufferInfo(h.fd)
320
-	if err != nil {
321
-		return err
322
-	}
323
-
324
-	attributes := info.Attributes
325 318
 	if len(params) <= 0 {
326
-		attributes = h.infoReset.Attributes
319
+		h.attributes = h.infoReset.Attributes
320
+		h.inverted = false
327 321
 	} else {
328 322
 		for _, attr := range params {
329 323
 
330 324
 			if attr == ANSI_SGR_RESET {
331
-				attributes = h.infoReset.Attributes
325
+				h.attributes = h.infoReset.Attributes
326
+				h.inverted = false
332 327
 				continue
333 328
 			}
334 329
 
335
-			attributes = collectAnsiIntoWindowsAttributes(attributes, h.infoReset.Attributes, SHORT(attr))
330
+			h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, SHORT(attr))
336 331
 		}
337 332
 	}
338 333
 
339
-	err = SetConsoleTextAttribute(h.fd, attributes)
334
+	attributes := h.attributes
335
+	if h.inverted {
336
+		attributes = invertAttributes(attributes)
337
+	}
338
+	err := SetConsoleTextAttribute(h.fd, attributes)
340 339
 	if err != nil {
341 340
 		return err
342 341
 	}
... ...
@@ -349,7 +606,8 @@ func (h *WindowsAnsiEventHandler) SU(param int) error {
349 349
 		return err
350 350
 	}
351 351
 	logger.Infof("SU: [%v]", []string{strconv.Itoa(param)})
352
-	return h.scrollPageUp()
352
+	h.clearWrap()
353
+	return h.scrollUp(param)
353 354
 }
354 355
 
355 356
 func (h *WindowsAnsiEventHandler) SD(param int) error {
... ...
@@ -357,44 +615,30 @@ func (h *WindowsAnsiEventHandler) SD(param int) error {
357 357
 		return err
358 358
 	}
359 359
 	logger.Infof("SD: [%v]", []string{strconv.Itoa(param)})
360
-	return h.scrollPageDown()
360
+	h.clearWrap()
361
+	return h.scrollDown(param)
361 362
 }
362 363
 
363 364
 func (h *WindowsAnsiEventHandler) DA(params []string) error {
364 365
 	logger.Infof("DA: [%v]", params)
365
-
366
-	// See the site below for details of the device attributes command
367
-	// http://vt100.net/docs/vt220-rm/chapter4.html
368
-
369
-	// First character of first parameter string is '>'
370
-	if params[0][0] == '>' {
371
-		// Secondary device attribute request:
372
-		// Respond with:
373
-		// "I am a VT220 version 1.0, no options.
374
-		//                    CSI     >     1     ;     1     0     ;     0     c    CR    LF
375
-		h.buffer.Write([]byte{CSI_ENTRY, 0x3E, 0x31, 0x3B, 0x31, 0x30, 0x3B, 0x30, 0x63, 0x0D, 0x0A})
376
-
377
-	} else {
378
-		// Primary device attribute request:
379
-		// Respond with:
380
-		// "I am a service class 2 terminal (62) with 132 columns (1),
381
-		// printer port (2), selective erase (6), DRCS (7), UDK (8),
382
-		// and I support 7-bit national replacement character sets (9)."
383
-		//                    CSI     ?     6     2     ;     1     ;     2     ;     6     ;     7     ;     8     ;     9     c    CR    LF
384
-		h.buffer.Write([]byte{CSI_ENTRY, 0x3F, 0x36, 0x32, 0x3B, 0x31, 0x3B, 0x32, 0x3B, 0x36, 0x3B, 0x37, 0x3B, 0x38, 0x3B, 0x39, 0x63, 0x0D, 0x0A})
385
-	}
386
-
366
+	// DA cannot be implemented because it must send data on the VT100 input stream,
367
+	// which is not available to go-ansiterm.
387 368
 	return nil
388 369
 }
389 370
 
390 371
 func (h *WindowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
372
+	if err := h.Flush(); err != nil {
373
+		return err
374
+	}
391 375
 	logger.Infof("DECSTBM: [%d, %d]", top, bottom)
392 376
 
393 377
 	// Windows is 0 indexed, Linux is 1 indexed
394
-	h.sr.top = top - 1
395
-	h.sr.bottom = bottom - 1
378
+	h.sr.top = SHORT(top - 1)
379
+	h.sr.bottom = SHORT(bottom - 1)
396 380
 
397
-	return nil
381
+	// This command also moves the cursor to the origin.
382
+	h.clearWrap()
383
+	return h.CUP(1, 1)
398 384
 }
399 385
 
400 386
 func (h *WindowsAnsiEventHandler) RI() error {
... ...
@@ -402,29 +646,80 @@ func (h *WindowsAnsiEventHandler) RI() error {
402 402
 		return err
403 403
 	}
404 404
 	logger.Info("RI: []")
405
+	h.clearWrap()
405 406
 
406 407
 	info, err := GetConsoleScreenBufferInfo(h.fd)
407 408
 	if err != nil {
408 409
 		return err
409 410
 	}
410 411
 
411
-	if info.Window.Top == info.CursorPosition.Y {
412
-		if err := h.scrollPageDown(); err != nil {
413
-			return err
414
-		}
415
-
416
-		return h.EL(2)
412
+	sr := h.effectiveSr(info.Window)
413
+	if info.CursorPosition.Y == sr.top {
414
+		return h.scrollDown(1)
417 415
 	} else {
418
-		return h.CUU(1)
416
+		return h.moveCursorVertical(-1)
419 417
 	}
420 418
 }
421 419
 
420
+func (h *WindowsAnsiEventHandler) IND() error {
421
+	logger.Info("IND: []")
422
+	return h.executeLF()
423
+}
424
+
422 425
 func (h *WindowsAnsiEventHandler) Flush() error {
426
+	h.curInfo = nil
423 427
 	if h.buffer.Len() > 0 {
424 428
 		logger.Infof("Flush: [%s]", h.buffer.Bytes())
425 429
 		if _, err := h.buffer.WriteTo(h.file); err != nil {
426 430
 			return err
427 431
 		}
428 432
 	}
433
+
434
+	if h.wrapNext && !h.drewMarginByte {
435
+		logger.Infof("Flush: drawing margin byte '%c'", h.marginByte)
436
+
437
+		info, err := GetConsoleScreenBufferInfo(h.fd)
438
+		if err != nil {
439
+			return err
440
+		}
441
+
442
+		charInfo := []CHAR_INFO{{UnicodeChar: WCHAR(h.marginByte), Attributes: info.Attributes}}
443
+		size := COORD{1, 1}
444
+		position := COORD{0, 0}
445
+		region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
446
+		if err := WriteConsoleOutput(h.fd, charInfo, size, position, &region); err != nil {
447
+			return err
448
+		}
449
+		h.drewMarginByte = true
450
+	}
429 451
 	return nil
430 452
 }
453
+
454
+// cacheConsoleInfo ensures that the current console screen information has been queried
455
+// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
456
+func (h *WindowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
457
+	if h.curInfo == nil {
458
+		info, err := GetConsoleScreenBufferInfo(h.fd)
459
+		if err != nil {
460
+			return COORD{}, nil, err
461
+		}
462
+		h.curInfo = info
463
+		h.curPos = info.CursorPosition
464
+	}
465
+	return h.curPos, h.curInfo, nil
466
+}
467
+
468
+func (h *WindowsAnsiEventHandler) updatePos(pos COORD) {
469
+	if h.curInfo == nil {
470
+		panic("failed to call getCurrentInfo before calling updatePos")
471
+	}
472
+	h.curPos = pos
473
+}
474
+
475
+// clearWrap clears the state where the cursor is in the margin
476
+// waiting for the next character before wrapping the line. This must
477
+// be done before most operations that act on the cursor.
478
+func (h *WindowsAnsiEventHandler) clearWrap() {
479
+	h.wrapNext = false
480
+	h.drewMarginByte = false
481
+}