Skip to content
This repository was archived by the owner on Apr 19, 2024. It is now read-only.

Commit 459523e

Browse files
authored
Add terminal.Cursor error handling on Windows (#414)
1 parent 6cbb195 commit 459523e

File tree

4 files changed

+121
-71
lines changed

4 files changed

+121
-71
lines changed

terminal/cursor.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//go:build !windows
12
// +build !windows
23

34
package terminal
@@ -78,8 +79,8 @@ func (c *Cursor) Hide() error {
7879
return err
7980
}
8081

81-
// Move moves the cursor to a specific x,y location.
82-
func (c *Cursor) Move(x int, y int) error {
82+
// move moves the cursor to a specific x,y location.
83+
func (c *Cursor) move(x int, y int) error {
8384
_, err := fmt.Fprintf(c.Out, "\x1b[%d;%df", x, y)
8485
return err
8586
}
@@ -194,7 +195,7 @@ func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
194195
defer c.Restore()
195196

196197
// move the cursor to the very bottom of the terminal
197-
c.Move(999, 999)
198+
c.move(999, 999)
198199

199200
// ask for the current location
200201
bottom, err := c.Location(buf)

terminal/cursor_windows.go

Lines changed: 62 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,37 @@ type Cursor struct {
1616
Out FileWriter
1717
}
1818

19-
func (c *Cursor) Up(n int) {
20-
c.cursorMove(0, n)
19+
func (c *Cursor) Up(n int) error {
20+
return c.cursorMove(0, n)
2121
}
2222

23-
func (c *Cursor) Down(n int) {
24-
c.cursorMove(0, -1*n)
23+
func (c *Cursor) Down(n int) error {
24+
return c.cursorMove(0, -1*n)
2525
}
2626

27-
func (c *Cursor) Forward(n int) {
28-
c.cursorMove(n, 0)
27+
func (c *Cursor) Forward(n int) error {
28+
return c.cursorMove(n, 0)
2929
}
3030

31-
func (c *Cursor) Back(n int) {
32-
c.cursorMove(-1*n, 0)
31+
func (c *Cursor) Back(n int) error {
32+
return c.cursorMove(-1*n, 0)
3333
}
3434

3535
// save the cursor location
36-
func (c *Cursor) Save() {
37-
cursorLoc, _ = c.Location(nil)
36+
func (c *Cursor) Save() error {
37+
loc, err := c.Location(nil)
38+
if err != nil {
39+
return err
40+
}
41+
cursorLoc = *loc
42+
return nil
3843
}
3944

40-
func (c *Cursor) Restore() {
45+
func (c *Cursor) Restore() error {
4146
handle := syscall.Handle(c.Out.Fd())
4247
// restore it to the original position
43-
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursorLoc))))
48+
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursorLoc))))
49+
return normalizeError(err)
4450
}
4551

4652
func (cur Coord) CursorIsAtLineEnd(size *Coord) bool {
@@ -51,40 +57,49 @@ func (cur Coord) CursorIsAtLineBegin() bool {
5157
return cur.X == 0
5258
}
5359

54-
func (c *Cursor) cursorMove(x int, y int) {
60+
func (c *Cursor) cursorMove(x int, y int) error {
5561
handle := syscall.Handle(c.Out.Fd())
5662

5763
var csbi consoleScreenBufferInfo
58-
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
64+
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
65+
return err
66+
}
5967

6068
var cursor Coord
6169
cursor.X = csbi.cursorPosition.X + Short(x)
6270
cursor.Y = csbi.cursorPosition.Y + Short(y)
6371

64-
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
72+
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
73+
return normalizeError(err)
6574
}
6675

67-
func (c *Cursor) NextLine(n int) {
68-
c.Up(n)
69-
c.HorizontalAbsolute(0)
76+
func (c *Cursor) NextLine(n int) error {
77+
if err := c.Up(n); err != nil {
78+
return err
79+
}
80+
return c.HorizontalAbsolute(0)
7081
}
7182

72-
func (c *Cursor) PreviousLine(n int) {
73-
c.Down(n)
74-
c.HorizontalAbsolute(0)
83+
func (c *Cursor) PreviousLine(n int) error {
84+
if err := c.Down(n); err != nil {
85+
return err
86+
}
87+
return c.HorizontalAbsolute(0)
7588
}
7689

7790
// for comparability purposes between windows
7891
// in windows we don't have to print out a new line
79-
func (c *Cursor) MoveNextLine(cur Coord, terminalSize *Coord) {
80-
c.NextLine(1)
92+
func (c *Cursor) MoveNextLine(cur *Coord, terminalSize *Coord) error {
93+
return c.NextLine(1)
8194
}
8295

83-
func (c *Cursor) HorizontalAbsolute(x int) {
96+
func (c *Cursor) HorizontalAbsolute(x int) error {
8497
handle := syscall.Handle(c.Out.Fd())
8598

8699
var csbi consoleScreenBufferInfo
87-
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
100+
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
101+
return err
102+
}
88103

89104
var cursor Coord
90105
cursor.X = Short(x)
@@ -94,43 +109,54 @@ func (c *Cursor) HorizontalAbsolute(x int) {
94109
cursor.X = csbi.size.X
95110
}
96111

97-
procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
112+
_, _, err := procSetConsoleCursorPosition.Call(uintptr(handle), uintptr(*(*int32)(unsafe.Pointer(&cursor))))
113+
return normalizeError(err)
98114
}
99115

100-
func (c *Cursor) Show() {
116+
func (c *Cursor) Show() error {
101117
handle := syscall.Handle(c.Out.Fd())
102118

103119
var cci consoleCursorInfo
104-
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
120+
if _, _, err := procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))); normalizeError(err) != nil {
121+
return err
122+
}
105123
cci.visible = 1
106124

107-
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
125+
_, _, err := procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
126+
return normalizeError(err)
108127
}
109128

110-
func (c *Cursor) Hide() {
129+
func (c *Cursor) Hide() error {
111130
handle := syscall.Handle(c.Out.Fd())
112131

113132
var cci consoleCursorInfo
114-
procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
133+
if _, _, err := procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci))); normalizeError(err) != nil {
134+
return err
135+
}
115136
cci.visible = 0
116137

117-
procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
138+
_, _, err := procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&cci)))
139+
return normalizeError(err)
118140
}
119141

120-
func (c *Cursor) Location(buf *bytes.Buffer) (Coord, error) {
142+
func (c *Cursor) Location(buf *bytes.Buffer) (*Coord, error) {
121143
handle := syscall.Handle(c.Out.Fd())
122144

123145
var csbi consoleScreenBufferInfo
124-
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
146+
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
147+
return nil, err
148+
}
125149

126-
return csbi.cursorPosition, nil
150+
return &csbi.cursorPosition, nil
127151
}
128152

129153
func (c *Cursor) Size(buf *bytes.Buffer) (*Coord, error) {
130154
handle := syscall.Handle(c.Out.Fd())
131155

132156
var csbi consoleScreenBufferInfo
133-
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
157+
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
158+
return nil, err
159+
}
134160
// windows' coordinate system begins at (0, 0)
135161
csbi.size.X--
136162
csbi.size.Y--

terminal/display_windows.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import (
55
"unsafe"
66
)
77

8-
func EraseLine(out FileWriter, mode EraseLineMode) {
8+
func EraseLine(out FileWriter, mode EraseLineMode) error {
99
handle := syscall.Handle(out.Fd())
1010

1111
var csbi consoleScreenBufferInfo
12-
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
12+
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
13+
return err
14+
}
1315

1416
var w uint32
1517
var x Short
@@ -23,5 +25,7 @@ func EraseLine(out FileWriter, mode EraseLineMode) {
2325
cursor.X = 0
2426
x = csbi.size.X
2527
}
26-
procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))
28+
29+
_, _, err := procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(x), uintptr(*(*int32)(unsafe.Pointer(&cursor))), uintptr(unsafe.Pointer(&w)))
30+
return normalizeError(err)
2731
}

terminal/output_windows.go

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,6 @@ import (
1212
"github.com/mattn/go-isatty"
1313
)
1414

15-
var (
16-
cursorFunctions = map[rune]func(c *Cursor) func(int){
17-
'A': func(c *Cursor) func(int) { return c.Up },
18-
'B': func(c *Cursor) func(int) { return c.Down },
19-
'C': func(c *Cursor) func(int) { return c.Forward },
20-
'D': func(c *Cursor) func(int) { return c.Back },
21-
'E': func(c *Cursor) func(int) { return c.NextLine },
22-
'F': func(c *Cursor) func(int) { return c.PreviousLine },
23-
'G': func(c *Cursor) func(int) { return c.HorizontalAbsolute },
24-
}
25-
)
26-
2715
const (
2816
foregroundBlue = 0x1
2917
foregroundGreen = 0x2
@@ -98,9 +86,14 @@ func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
9886
buf := make([]byte, 0, 10)
9987
buf = append(buf, "\x1b"...)
10088

89+
var ch rune
90+
var size int
10191
// Check '[' continues after \x1b
102-
ch, size, err := r.ReadRune()
92+
ch, size, err = r.ReadRune()
10393
if err != nil {
94+
if err == io.EOF {
95+
err = nil
96+
}
10497
fmt.Fprint(w.out, string(buf))
10598
return
10699
}
@@ -116,6 +109,9 @@ func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
116109
for {
117110
ch, size, err = r.ReadRune()
118111
if err != nil {
112+
if err == io.EOF {
113+
err = nil
114+
}
119115
fmt.Fprint(w.out, string(buf))
120116
return
121117
}
@@ -127,47 +123,62 @@ func (w *Writer) handleEscape(r *bytes.Reader) (n int, err error) {
127123
argBuf = append(argBuf, string(ch)...)
128124
}
129125

130-
w.applyEscapeCode(buf, string(argBuf), code)
126+
err = w.applyEscapeCode(buf, string(argBuf), code)
131127
return
132128
}
133129

134-
func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) {
130+
func (w *Writer) applyEscapeCode(buf []byte, arg string, code rune) error {
135131
c := &Cursor{Out: w.out}
136132

137133
switch arg + string(code) {
138134
case "?25h":
139-
c.Show()
140-
return
135+
return c.Show()
141136
case "?25l":
142-
c.Hide()
143-
return
137+
return c.Hide()
144138
}
145139

146-
if f, ok := cursorFunctions[code]; ok {
140+
if code >= 'A' && code <= 'G' {
147141
if n, err := strconv.Atoi(arg); err == nil {
148-
f(c)(n)
149-
return
142+
switch code {
143+
case 'A':
144+
return c.Up(n)
145+
case 'B':
146+
return c.Down(n)
147+
case 'C':
148+
return c.Forward(n)
149+
case 'D':
150+
return c.Back(n)
151+
case 'E':
152+
return c.NextLine(n)
153+
case 'F':
154+
return c.PreviousLine(n)
155+
case 'G':
156+
return c.HorizontalAbsolute(n)
157+
}
150158
}
151159
}
152160

153161
switch code {
154162
case 'm':
155-
w.applySelectGraphicRendition(arg)
163+
return w.applySelectGraphicRendition(arg)
156164
default:
157165
buf = append(buf, string(code)...)
158-
fmt.Fprint(w.out, string(buf))
166+
_, err := fmt.Fprint(w.out, string(buf))
167+
return err
159168
}
160169
}
161170

162171
// Original implementation: https://github.com/mattn/go-colorable
163-
func (w *Writer) applySelectGraphicRendition(arg string) {
172+
func (w *Writer) applySelectGraphicRendition(arg string) error {
164173
if arg == "" {
165-
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr))
166-
return
174+
_, _, err := procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.orgAttr))
175+
return normalizeError(err)
167176
}
168177

169178
var csbi consoleScreenBufferInfo
170-
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
179+
if _, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))); normalizeError(err) != nil {
180+
return err
181+
}
171182
attr := csbi.attributes
172183

173184
for _, param := range strings.Split(arg, ";") {
@@ -230,5 +241,13 @@ func (w *Writer) applySelectGraphicRendition(arg string) {
230241
}
231242
}
232243

233-
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
244+
_, _, err := procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
245+
return normalizeError(err)
246+
}
247+
248+
func normalizeError(err error) error {
249+
if syserr, ok := err.(syscall.Errno); ok && syserr == 0 {
250+
return nil
251+
}
252+
return err
234253
}

0 commit comments

Comments
 (0)