1
2
3 package winterm
4
5 import (
6 "bytes"
7 "log"
8 "os"
9 "strconv"
10
11 "github.com/Azure/go-ansiterm"
12 )
13
14 type windowsAnsiEventHandler struct {
15 fd uintptr
16 file *os.File
17 infoReset *CONSOLE_SCREEN_BUFFER_INFO
18 sr scrollRegion
19 buffer bytes.Buffer
20 attributes uint16
21 inverted bool
22 wrapNext bool
23 drewMarginByte bool
24 originMode bool
25 marginByte byte
26 curInfo *CONSOLE_SCREEN_BUFFER_INFO
27 curPos COORD
28 logf func(string, ...interface{})
29 }
30
31 type Option func(*windowsAnsiEventHandler)
32
33 func WithLogf(f func(string, ...interface{})) Option {
34 return func(w *windowsAnsiEventHandler) {
35 w.logf = f
36 }
37 }
38
39 func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler {
40 infoReset, err := GetConsoleScreenBufferInfo(fd)
41 if err != nil {
42 return nil
43 }
44
45 h := &windowsAnsiEventHandler{
46 fd: fd,
47 file: file,
48 infoReset: infoReset,
49 attributes: infoReset.Attributes,
50 }
51 for _, o := range opts {
52 o(h)
53 }
54
55 if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
56 logFile, _ := os.Create("winEventHandler.log")
57 logger := log.New(logFile, "", log.LstdFlags)
58 if h.logf != nil {
59 l := h.logf
60 h.logf = func(s string, v ...interface{}) {
61 l(s, v...)
62 logger.Printf(s, v...)
63 }
64 } else {
65 h.logf = logger.Printf
66 }
67 }
68
69 if h.logf == nil {
70 h.logf = func(string, ...interface{}) {}
71 }
72
73 return h
74 }
75
76 type scrollRegion struct {
77 top int16
78 bottom int16
79 }
80
81
82
83
84
85
86
87
88 func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
89 if h.wrapNext {
90 if err := h.Flush(); err != nil {
91 return false, err
92 }
93 h.clearWrap()
94 }
95 pos, info, err := h.getCurrentInfo()
96 if err != nil {
97 return false, err
98 }
99 sr := h.effectiveSr(info.Window)
100 if pos.Y == sr.bottom {
101
102
103 if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
104 if includeCR {
105 pos.X = 0
106 h.updatePos(pos)
107 }
108 return false, nil
109 }
110
111
112
113 if err := h.Flush(); err != nil {
114 return false, err
115 }
116 h.logf("Simulating LF inside scroll region")
117 if err := h.scrollUp(1); err != nil {
118 return false, err
119 }
120 if includeCR {
121 pos.X = 0
122 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
123 return false, err
124 }
125 }
126 return true, nil
127
128 } else if pos.Y < info.Window.Bottom {
129
130 pos.Y++
131 if includeCR {
132 pos.X = 0
133 }
134 h.updatePos(pos)
135 return false, nil
136 } else {
137
138
139 h.logf("Simulating LF outside scroll region")
140 if includeCR {
141 if err := h.Flush(); err != nil {
142 return false, err
143 }
144 pos.X = 0
145 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
146 return false, err
147 }
148 }
149 return true, nil
150 }
151 }
152
153
154 func (h *windowsAnsiEventHandler) executeLF() error {
155 handled, err := h.simulateLF(false)
156 if err != nil {
157 return err
158 }
159 if !handled {
160
161
162 pos, _, err := h.getCurrentInfo()
163 if err != nil {
164 return err
165 }
166 h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
167 if pos.X != 0 {
168 if err := h.Flush(); err != nil {
169 return err
170 }
171 h.logf("Resetting cursor position for LF without CR")
172 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
173 return err
174 }
175 }
176 }
177 return nil
178 }
179
180 func (h *windowsAnsiEventHandler) Print(b byte) error {
181 if h.wrapNext {
182 h.buffer.WriteByte(h.marginByte)
183 h.clearWrap()
184 if _, err := h.simulateLF(true); err != nil {
185 return err
186 }
187 }
188 pos, info, err := h.getCurrentInfo()
189 if err != nil {
190 return err
191 }
192 if pos.X == info.Size.X-1 {
193 h.wrapNext = true
194 h.marginByte = b
195 } else {
196 pos.X++
197 h.updatePos(pos)
198 h.buffer.WriteByte(b)
199 }
200 return nil
201 }
202
203 func (h *windowsAnsiEventHandler) Execute(b byte) error {
204 switch b {
205 case ansiterm.ANSI_TAB:
206 h.logf("Execute(TAB)")
207
208 if !h.wrapNext {
209 pos, info, err := h.getCurrentInfo()
210 if err != nil {
211 return err
212 }
213 pos.X = (pos.X + 8) - pos.X%8
214 if pos.X >= info.Size.X {
215 pos.X = info.Size.X - 1
216 }
217 if err := h.Flush(); err != nil {
218 return err
219 }
220 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
221 return err
222 }
223 }
224 return nil
225
226 case ansiterm.ANSI_BEL:
227 h.buffer.WriteByte(ansiterm.ANSI_BEL)
228 return nil
229
230 case ansiterm.ANSI_BACKSPACE:
231 if h.wrapNext {
232 if err := h.Flush(); err != nil {
233 return err
234 }
235 h.clearWrap()
236 }
237 pos, _, err := h.getCurrentInfo()
238 if err != nil {
239 return err
240 }
241 if pos.X > 0 {
242 pos.X--
243 h.updatePos(pos)
244 h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
245 }
246 return nil
247
248 case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
249
250 return h.executeLF()
251
252 case ansiterm.ANSI_LINE_FEED:
253
254
255
256 handled, err := h.simulateLF(true)
257 if handled || err != nil {
258 return err
259 }
260 return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
261
262 case ansiterm.ANSI_CARRIAGE_RETURN:
263 if h.wrapNext {
264 if err := h.Flush(); err != nil {
265 return err
266 }
267 h.clearWrap()
268 }
269 pos, _, err := h.getCurrentInfo()
270 if err != nil {
271 return err
272 }
273 if pos.X != 0 {
274 pos.X = 0
275 h.updatePos(pos)
276 h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
277 }
278 return nil
279
280 default:
281 return nil
282 }
283 }
284
285 func (h *windowsAnsiEventHandler) CUU(param int) error {
286 if err := h.Flush(); err != nil {
287 return err
288 }
289 h.logf("CUU: [%v]", []string{strconv.Itoa(param)})
290 h.clearWrap()
291 return h.moveCursorVertical(-param)
292 }
293
294 func (h *windowsAnsiEventHandler) CUD(param int) error {
295 if err := h.Flush(); err != nil {
296 return err
297 }
298 h.logf("CUD: [%v]", []string{strconv.Itoa(param)})
299 h.clearWrap()
300 return h.moveCursorVertical(param)
301 }
302
303 func (h *windowsAnsiEventHandler) CUF(param int) error {
304 if err := h.Flush(); err != nil {
305 return err
306 }
307 h.logf("CUF: [%v]", []string{strconv.Itoa(param)})
308 h.clearWrap()
309 return h.moveCursorHorizontal(param)
310 }
311
312 func (h *windowsAnsiEventHandler) CUB(param int) error {
313 if err := h.Flush(); err != nil {
314 return err
315 }
316 h.logf("CUB: [%v]", []string{strconv.Itoa(param)})
317 h.clearWrap()
318 return h.moveCursorHorizontal(-param)
319 }
320
321 func (h *windowsAnsiEventHandler) CNL(param int) error {
322 if err := h.Flush(); err != nil {
323 return err
324 }
325 h.logf("CNL: [%v]", []string{strconv.Itoa(param)})
326 h.clearWrap()
327 return h.moveCursorLine(param)
328 }
329
330 func (h *windowsAnsiEventHandler) CPL(param int) error {
331 if err := h.Flush(); err != nil {
332 return err
333 }
334 h.logf("CPL: [%v]", []string{strconv.Itoa(param)})
335 h.clearWrap()
336 return h.moveCursorLine(-param)
337 }
338
339 func (h *windowsAnsiEventHandler) CHA(param int) error {
340 if err := h.Flush(); err != nil {
341 return err
342 }
343 h.logf("CHA: [%v]", []string{strconv.Itoa(param)})
344 h.clearWrap()
345 return h.moveCursorColumn(param)
346 }
347
348 func (h *windowsAnsiEventHandler) VPA(param int) error {
349 if err := h.Flush(); err != nil {
350 return err
351 }
352 h.logf("VPA: [[%d]]", param)
353 h.clearWrap()
354 info, err := GetConsoleScreenBufferInfo(h.fd)
355 if err != nil {
356 return err
357 }
358 window := h.getCursorWindow(info)
359 position := info.CursorPosition
360 position.Y = window.Top + int16(param) - 1
361 return h.setCursorPosition(position, window)
362 }
363
364 func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
365 if err := h.Flush(); err != nil {
366 return err
367 }
368 h.logf("CUP: [[%d %d]]", row, col)
369 h.clearWrap()
370 info, err := GetConsoleScreenBufferInfo(h.fd)
371 if err != nil {
372 return err
373 }
374
375 window := h.getCursorWindow(info)
376 position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
377 return h.setCursorPosition(position, window)
378 }
379
380 func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
381 if err := h.Flush(); err != nil {
382 return err
383 }
384 h.logf("HVP: [[%d %d]]", row, col)
385 h.clearWrap()
386 return h.CUP(row, col)
387 }
388
389 func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
390 if err := h.Flush(); err != nil {
391 return err
392 }
393 h.logf("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
394 h.clearWrap()
395 return nil
396 }
397
398 func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
399 if err := h.Flush(); err != nil {
400 return err
401 }
402 h.logf("DECOM: [%v]", []string{strconv.FormatBool(enable)})
403 h.clearWrap()
404 h.originMode = enable
405 return h.CUP(1, 1)
406 }
407
408 func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
409 if err := h.Flush(); err != nil {
410 return err
411 }
412 h.logf("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
413 h.clearWrap()
414 if err := h.ED(2); err != nil {
415 return err
416 }
417 info, err := GetConsoleScreenBufferInfo(h.fd)
418 if err != nil {
419 return err
420 }
421 targetWidth := int16(80)
422 if use132 {
423 targetWidth = 132
424 }
425 if info.Size.X < targetWidth {
426 if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
427 h.logf("set buffer failed: %v", err)
428 return err
429 }
430 }
431 window := info.Window
432 window.Left = 0
433 window.Right = targetWidth - 1
434 if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
435 h.logf("set window failed: %v", err)
436 return err
437 }
438 if info.Size.X > targetWidth {
439 if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
440 h.logf("set buffer failed: %v", err)
441 return err
442 }
443 }
444 return SetConsoleCursorPosition(h.fd, COORD{0, 0})
445 }
446
447 func (h *windowsAnsiEventHandler) ED(param int) error {
448 if err := h.Flush(); err != nil {
449 return err
450 }
451 h.logf("ED: [%v]", []string{strconv.Itoa(param)})
452 h.clearWrap()
453
454
455
456
457
458
459
460 info, err := GetConsoleScreenBufferInfo(h.fd)
461 if err != nil {
462 return err
463 }
464
465 var start COORD
466 var end COORD
467
468 switch param {
469 case 0:
470 start = info.CursorPosition
471 end = COORD{info.Size.X - 1, info.Size.Y - 1}
472
473 case 1:
474 start = COORD{0, 0}
475 end = info.CursorPosition
476
477 case 2:
478 start = COORD{0, 0}
479 end = COORD{info.Size.X - 1, info.Size.Y - 1}
480 }
481
482 err = h.clearRange(h.attributes, start, end)
483 if err != nil {
484 return err
485 }
486
487
488
489 if param == 2 {
490 pos := info.CursorPosition
491 window := info.Window
492 pos.Y -= window.Top
493 window.Bottom -= window.Top
494 window.Top = 0
495 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
496 return err
497 }
498 if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
499 return err
500 }
501 }
502
503 return nil
504 }
505
506 func (h *windowsAnsiEventHandler) EL(param int) error {
507 if err := h.Flush(); err != nil {
508 return err
509 }
510 h.logf("EL: [%v]", strconv.Itoa(param))
511 h.clearWrap()
512
513
514
515
516
517 info, err := GetConsoleScreenBufferInfo(h.fd)
518 if err != nil {
519 return err
520 }
521
522 var start COORD
523 var end COORD
524
525 switch param {
526 case 0:
527 start = info.CursorPosition
528 end = COORD{info.Size.X, info.CursorPosition.Y}
529
530 case 1:
531 start = COORD{0, info.CursorPosition.Y}
532 end = info.CursorPosition
533
534 case 2:
535 start = COORD{0, info.CursorPosition.Y}
536 end = COORD{info.Size.X, info.CursorPosition.Y}
537 }
538
539 err = h.clearRange(h.attributes, start, end)
540 if err != nil {
541 return err
542 }
543
544 return nil
545 }
546
547 func (h *windowsAnsiEventHandler) IL(param int) error {
548 if err := h.Flush(); err != nil {
549 return err
550 }
551 h.logf("IL: [%v]", strconv.Itoa(param))
552 h.clearWrap()
553 return h.insertLines(param)
554 }
555
556 func (h *windowsAnsiEventHandler) DL(param int) error {
557 if err := h.Flush(); err != nil {
558 return err
559 }
560 h.logf("DL: [%v]", strconv.Itoa(param))
561 h.clearWrap()
562 return h.deleteLines(param)
563 }
564
565 func (h *windowsAnsiEventHandler) ICH(param int) error {
566 if err := h.Flush(); err != nil {
567 return err
568 }
569 h.logf("ICH: [%v]", strconv.Itoa(param))
570 h.clearWrap()
571 return h.insertCharacters(param)
572 }
573
574 func (h *windowsAnsiEventHandler) DCH(param int) error {
575 if err := h.Flush(); err != nil {
576 return err
577 }
578 h.logf("DCH: [%v]", strconv.Itoa(param))
579 h.clearWrap()
580 return h.deleteCharacters(param)
581 }
582
583 func (h *windowsAnsiEventHandler) SGR(params []int) error {
584 if err := h.Flush(); err != nil {
585 return err
586 }
587 strings := []string{}
588 for _, v := range params {
589 strings = append(strings, strconv.Itoa(v))
590 }
591
592 h.logf("SGR: [%v]", strings)
593
594 if len(params) <= 0 {
595 h.attributes = h.infoReset.Attributes
596 h.inverted = false
597 } else {
598 for _, attr := range params {
599
600 if attr == ansiterm.ANSI_SGR_RESET {
601 h.attributes = h.infoReset.Attributes
602 h.inverted = false
603 continue
604 }
605
606 h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
607 }
608 }
609
610 attributes := h.attributes
611 if h.inverted {
612 attributes = invertAttributes(attributes)
613 }
614 err := SetConsoleTextAttribute(h.fd, attributes)
615 if err != nil {
616 return err
617 }
618
619 return nil
620 }
621
622 func (h *windowsAnsiEventHandler) SU(param int) error {
623 if err := h.Flush(); err != nil {
624 return err
625 }
626 h.logf("SU: [%v]", []string{strconv.Itoa(param)})
627 h.clearWrap()
628 return h.scrollUp(param)
629 }
630
631 func (h *windowsAnsiEventHandler) SD(param int) error {
632 if err := h.Flush(); err != nil {
633 return err
634 }
635 h.logf("SD: [%v]", []string{strconv.Itoa(param)})
636 h.clearWrap()
637 return h.scrollDown(param)
638 }
639
640 func (h *windowsAnsiEventHandler) DA(params []string) error {
641 h.logf("DA: [%v]", params)
642
643
644 return nil
645 }
646
647 func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
648 if err := h.Flush(); err != nil {
649 return err
650 }
651 h.logf("DECSTBM: [%d, %d]", top, bottom)
652
653
654 h.sr.top = int16(top - 1)
655 h.sr.bottom = int16(bottom - 1)
656
657
658 h.clearWrap()
659 return h.CUP(1, 1)
660 }
661
662 func (h *windowsAnsiEventHandler) RI() error {
663 if err := h.Flush(); err != nil {
664 return err
665 }
666 h.logf("RI: []")
667 h.clearWrap()
668
669 info, err := GetConsoleScreenBufferInfo(h.fd)
670 if err != nil {
671 return err
672 }
673
674 sr := h.effectiveSr(info.Window)
675 if info.CursorPosition.Y == sr.top {
676 return h.scrollDown(1)
677 }
678
679 return h.moveCursorVertical(-1)
680 }
681
682 func (h *windowsAnsiEventHandler) IND() error {
683 h.logf("IND: []")
684 return h.executeLF()
685 }
686
687 func (h *windowsAnsiEventHandler) Flush() error {
688 h.curInfo = nil
689 if h.buffer.Len() > 0 {
690 h.logf("Flush: [%s]", h.buffer.Bytes())
691 if _, err := h.buffer.WriteTo(h.file); err != nil {
692 return err
693 }
694 }
695
696 if h.wrapNext && !h.drewMarginByte {
697 h.logf("Flush: drawing margin byte '%c'", h.marginByte)
698
699 info, err := GetConsoleScreenBufferInfo(h.fd)
700 if err != nil {
701 return err
702 }
703
704 charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
705 size := COORD{1, 1}
706 position := COORD{0, 0}
707 region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
708 if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil {
709 return err
710 }
711 h.drewMarginByte = true
712 }
713 return nil
714 }
715
716
717
718 func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
719 if h.curInfo == nil {
720 info, err := GetConsoleScreenBufferInfo(h.fd)
721 if err != nil {
722 return COORD{}, nil, err
723 }
724 h.curInfo = info
725 h.curPos = info.CursorPosition
726 }
727 return h.curPos, h.curInfo, nil
728 }
729
730 func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
731 if h.curInfo == nil {
732 panic("failed to call getCurrentInfo before calling updatePos")
733 }
734 h.curPos = pos
735 }
736
737
738
739
740 func (h *windowsAnsiEventHandler) clearWrap() {
741 h.wrapNext = false
742 h.drewMarginByte = false
743 }
744
View as plain text