11 "github.com/Azure/go-ansiterm"
12 "github.com/Sirupsen/logrus"
15 var logger *logrus.Logger
17 type windowsAnsiEventHandler struct {
20 infoReset *CONSOLE_SCREEN_BUFFER_INFO
29 curInfo *CONSOLE_SCREEN_BUFFER_INFO
33 func CreateWinEventHandler(fd uintptr, file *os.File) ansiterm.AnsiEventHandler {
34 logFile := ioutil.Discard
36 if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
37 logFile, _ = os.Create("winEventHandler.log")
40 logger = &logrus.Logger{
42 Formatter: new(logrus.TextFormatter),
43 Level: logrus.DebugLevel,
46 infoReset, err := GetConsoleScreenBufferInfo(fd)
51 return &windowsAnsiEventHandler{
55 attributes: infoReset.Attributes,
59 type scrollRegion struct {
64 // simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the
65 // current cursor position and scroll region settings, in which case it returns
66 // true. If no special handling is necessary, then it does nothing and returns
69 // In the false case, the caller should ensure that a carriage return
70 // and line feed are inserted or that the text is otherwise wrapped.
71 func (h *windowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) {
73 if err := h.Flush(); err != nil {
78 pos, info, err := h.getCurrentInfo()
82 sr := h.effectiveSr(info.Window)
83 if pos.Y == sr.bottom {
84 // Scrolling is necessary. Let Windows automatically scroll if the scrolling region
85 // is the full window.
86 if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom {
94 // A custom scroll region is active. Scroll the window manually to simulate
96 if err := h.Flush(); err != nil {
99 logger.Info("Simulating LF inside scroll region")
100 if err := h.scrollUp(1); err != nil {
105 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
111 } else if pos.Y < info.Window.Bottom {
112 // Let Windows handle the LF.
120 // The cursor is at the bottom of the screen but outside the scroll
121 // region. Skip the LF.
122 logger.Info("Simulating LF outside scroll region")
124 if err := h.Flush(); err != nil {
128 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
136 // executeLF executes a LF without a CR.
137 func (h *windowsAnsiEventHandler) executeLF() error {
138 handled, err := h.simulateLF(false)
143 // Windows LF will reset the cursor column position. Write the LF
144 // and restore the cursor position.
145 pos, _, err := h.getCurrentInfo()
149 h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
151 if err := h.Flush(); err != nil {
154 logger.Info("Resetting cursor position for LF without CR")
155 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
163 func (h *windowsAnsiEventHandler) Print(b byte) error {
165 h.buffer.WriteByte(h.marginByte)
167 if _, err := h.simulateLF(true); err != nil {
171 pos, info, err := h.getCurrentInfo()
175 if pos.X == info.Size.X-1 {
181 h.buffer.WriteByte(b)
186 func (h *windowsAnsiEventHandler) Execute(b byte) error {
188 case ansiterm.ANSI_TAB:
189 logger.Info("Execute(TAB)")
190 // Move to the next tab stop, but preserve auto-wrap if already set.
192 pos, info, err := h.getCurrentInfo()
196 pos.X = (pos.X + 8) - pos.X%8
197 if pos.X >= info.Size.X {
198 pos.X = info.Size.X - 1
200 if err := h.Flush(); err != nil {
203 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
209 case ansiterm.ANSI_BEL:
210 h.buffer.WriteByte(ansiterm.ANSI_BEL)
213 case ansiterm.ANSI_BACKSPACE:
215 if err := h.Flush(); err != nil {
220 pos, _, err := h.getCurrentInfo()
227 h.buffer.WriteByte(ansiterm.ANSI_BACKSPACE)
231 case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
235 case ansiterm.ANSI_LINE_FEED:
236 // Simulate a CR and LF for now since there is no way in go-ansiterm
237 // to tell if the LF should include CR (and more things break when it's
238 // missing than when it's incorrectly added).
239 handled, err := h.simulateLF(true)
240 if handled || err != nil {
243 return h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
245 case ansiterm.ANSI_CARRIAGE_RETURN:
247 if err := h.Flush(); err != nil {
252 pos, _, err := h.getCurrentInfo()
259 h.buffer.WriteByte(ansiterm.ANSI_CARRIAGE_RETURN)
268 func (h *windowsAnsiEventHandler) CUU(param int) error {
269 if err := h.Flush(); err != nil {
272 logger.Infof("CUU: [%v]", []string{strconv.Itoa(param)})
274 return h.moveCursorVertical(-param)
277 func (h *windowsAnsiEventHandler) CUD(param int) error {
278 if err := h.Flush(); err != nil {
281 logger.Infof("CUD: [%v]", []string{strconv.Itoa(param)})
283 return h.moveCursorVertical(param)
286 func (h *windowsAnsiEventHandler) CUF(param int) error {
287 if err := h.Flush(); err != nil {
290 logger.Infof("CUF: [%v]", []string{strconv.Itoa(param)})
292 return h.moveCursorHorizontal(param)
295 func (h *windowsAnsiEventHandler) CUB(param int) error {
296 if err := h.Flush(); err != nil {
299 logger.Infof("CUB: [%v]", []string{strconv.Itoa(param)})
301 return h.moveCursorHorizontal(-param)
304 func (h *windowsAnsiEventHandler) CNL(param int) error {
305 if err := h.Flush(); err != nil {
308 logger.Infof("CNL: [%v]", []string{strconv.Itoa(param)})
310 return h.moveCursorLine(param)
313 func (h *windowsAnsiEventHandler) CPL(param int) error {
314 if err := h.Flush(); err != nil {
317 logger.Infof("CPL: [%v]", []string{strconv.Itoa(param)})
319 return h.moveCursorLine(-param)
322 func (h *windowsAnsiEventHandler) CHA(param int) error {
323 if err := h.Flush(); err != nil {
326 logger.Infof("CHA: [%v]", []string{strconv.Itoa(param)})
328 return h.moveCursorColumn(param)
331 func (h *windowsAnsiEventHandler) VPA(param int) error {
332 if err := h.Flush(); err != nil {
335 logger.Infof("VPA: [[%d]]", param)
337 info, err := GetConsoleScreenBufferInfo(h.fd)
341 window := h.getCursorWindow(info)
342 position := info.CursorPosition
343 position.Y = window.Top + int16(param) - 1
344 return h.setCursorPosition(position, window)
347 func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
348 if err := h.Flush(); err != nil {
351 logger.Infof("CUP: [[%d %d]]", row, col)
353 info, err := GetConsoleScreenBufferInfo(h.fd)
358 window := h.getCursorWindow(info)
359 position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
360 return h.setCursorPosition(position, window)
363 func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
364 if err := h.Flush(); err != nil {
367 logger.Infof("HVP: [[%d %d]]", row, col)
369 return h.CUP(row, col)
372 func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
373 if err := h.Flush(); err != nil {
376 logger.Infof("DECTCEM: [%v]", []string{strconv.FormatBool(visible)})
381 func (h *windowsAnsiEventHandler) DECOM(enable bool) error {
382 if err := h.Flush(); err != nil {
385 logger.Infof("DECOM: [%v]", []string{strconv.FormatBool(enable)})
387 h.originMode = enable
391 func (h *windowsAnsiEventHandler) DECCOLM(use132 bool) error {
392 if err := h.Flush(); err != nil {
395 logger.Infof("DECCOLM: [%v]", []string{strconv.FormatBool(use132)})
397 if err := h.ED(2); err != nil {
400 info, err := GetConsoleScreenBufferInfo(h.fd)
404 targetWidth := int16(80)
408 if info.Size.X < targetWidth {
409 if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
410 logger.Info("set buffer failed:", err)
414 window := info.Window
416 window.Right = targetWidth - 1
417 if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
418 logger.Info("set window failed:", err)
421 if info.Size.X > targetWidth {
422 if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
423 logger.Info("set buffer failed:", err)
427 return SetConsoleCursorPosition(h.fd, COORD{0, 0})
430 func (h *windowsAnsiEventHandler) ED(param int) error {
431 if err := h.Flush(); err != nil {
434 logger.Infof("ED: [%v]", []string{strconv.Itoa(param)})
437 // [J -- Erases from the cursor to the end of the screen, including the cursor position.
438 // [1J -- Erases from the beginning of the screen to the cursor, including the cursor position.
439 // [2J -- Erases the complete display. The cursor does not move.
441 // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
443 info, err := GetConsoleScreenBufferInfo(h.fd)
453 start = info.CursorPosition
454 end = COORD{info.Size.X - 1, info.Size.Y - 1}
458 end = info.CursorPosition
462 end = COORD{info.Size.X - 1, info.Size.Y - 1}
465 err = h.clearRange(h.attributes, start, end)
470 // If the whole buffer was cleared, move the window to the top while preserving
471 // the window-relative cursor position.
473 pos := info.CursorPosition
474 window := info.Window
476 window.Bottom -= window.Top
478 if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
481 if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
489 func (h *windowsAnsiEventHandler) EL(param int) error {
490 if err := h.Flush(); err != nil {
493 logger.Infof("EL: [%v]", strconv.Itoa(param))
496 // [K -- Erases from the cursor to the end of the line, including the cursor position.
497 // [1K -- Erases from the beginning of the line to the cursor, including the cursor position.
498 // [2K -- Erases the complete line.
500 info, err := GetConsoleScreenBufferInfo(h.fd)
510 start = info.CursorPosition
511 end = COORD{info.Size.X, info.CursorPosition.Y}
514 start = COORD{0, info.CursorPosition.Y}
515 end = info.CursorPosition
518 start = COORD{0, info.CursorPosition.Y}
519 end = COORD{info.Size.X, info.CursorPosition.Y}
522 err = h.clearRange(h.attributes, start, end)
530 func (h *windowsAnsiEventHandler) IL(param int) error {
531 if err := h.Flush(); err != nil {
534 logger.Infof("IL: [%v]", strconv.Itoa(param))
536 return h.insertLines(param)
539 func (h *windowsAnsiEventHandler) DL(param int) error {
540 if err := h.Flush(); err != nil {
543 logger.Infof("DL: [%v]", strconv.Itoa(param))
545 return h.deleteLines(param)
548 func (h *windowsAnsiEventHandler) ICH(param int) error {
549 if err := h.Flush(); err != nil {
552 logger.Infof("ICH: [%v]", strconv.Itoa(param))
554 return h.insertCharacters(param)
557 func (h *windowsAnsiEventHandler) DCH(param int) error {
558 if err := h.Flush(); err != nil {
561 logger.Infof("DCH: [%v]", strconv.Itoa(param))
563 return h.deleteCharacters(param)
566 func (h *windowsAnsiEventHandler) SGR(params []int) error {
567 if err := h.Flush(); err != nil {
570 strings := []string{}
571 for _, v := range params {
572 strings = append(strings, strconv.Itoa(v))
575 logger.Infof("SGR: [%v]", strings)
577 if len(params) <= 0 {
578 h.attributes = h.infoReset.Attributes
581 for _, attr := range params {
583 if attr == ansiterm.ANSI_SGR_RESET {
584 h.attributes = h.infoReset.Attributes
589 h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
593 attributes := h.attributes
595 attributes = invertAttributes(attributes)
597 err := SetConsoleTextAttribute(h.fd, attributes)
605 func (h *windowsAnsiEventHandler) SU(param int) error {
606 if err := h.Flush(); err != nil {
609 logger.Infof("SU: [%v]", []string{strconv.Itoa(param)})
611 return h.scrollUp(param)
614 func (h *windowsAnsiEventHandler) SD(param int) error {
615 if err := h.Flush(); err != nil {
618 logger.Infof("SD: [%v]", []string{strconv.Itoa(param)})
620 return h.scrollDown(param)
623 func (h *windowsAnsiEventHandler) DA(params []string) error {
624 logger.Infof("DA: [%v]", params)
625 // DA cannot be implemented because it must send data on the VT100 input stream,
626 // which is not available to go-ansiterm.
630 func (h *windowsAnsiEventHandler) DECSTBM(top int, bottom int) error {
631 if err := h.Flush(); err != nil {
634 logger.Infof("DECSTBM: [%d, %d]", top, bottom)
636 // Windows is 0 indexed, Linux is 1 indexed
637 h.sr.top = int16(top - 1)
638 h.sr.bottom = int16(bottom - 1)
640 // This command also moves the cursor to the origin.
645 func (h *windowsAnsiEventHandler) RI() error {
646 if err := h.Flush(); err != nil {
649 logger.Info("RI: []")
652 info, err := GetConsoleScreenBufferInfo(h.fd)
657 sr := h.effectiveSr(info.Window)
658 if info.CursorPosition.Y == sr.top {
659 return h.scrollDown(1)
662 return h.moveCursorVertical(-1)
665 func (h *windowsAnsiEventHandler) IND() error {
666 logger.Info("IND: []")
670 func (h *windowsAnsiEventHandler) Flush() error {
672 if h.buffer.Len() > 0 {
673 logger.Infof("Flush: [%s]", h.buffer.Bytes())
674 if _, err := h.buffer.WriteTo(h.file); err != nil {
679 if h.wrapNext && !h.drewMarginByte {
680 logger.Infof("Flush: drawing margin byte '%c'", h.marginByte)
682 info, err := GetConsoleScreenBufferInfo(h.fd)
687 charInfo := []CHAR_INFO{{UnicodeChar: uint16(h.marginByte), Attributes: info.Attributes}}
689 position := COORD{0, 0}
690 region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y}
691 if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil {
694 h.drewMarginByte = true
699 // cacheConsoleInfo ensures that the current console screen information has been queried
700 // since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos.
701 func (h *windowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) {
702 if h.curInfo == nil {
703 info, err := GetConsoleScreenBufferInfo(h.fd)
705 return COORD{}, nil, err
708 h.curPos = info.CursorPosition
710 return h.curPos, h.curInfo, nil
713 func (h *windowsAnsiEventHandler) updatePos(pos COORD) {
714 if h.curInfo == nil {
715 panic("failed to call getCurrentInfo before calling updatePos")
720 // clearWrap clears the state where the cursor is in the margin
721 // waiting for the next character before wrapping the line. This must
722 // be done before most operations that act on the cursor.
723 func (h *windowsAnsiEventHandler) clearWrap() {
725 h.drewMarginByte = false