diff --git a/hack/vendor.sh b/hack/vendor.sh index 0726c22021..f63ceed9a8 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -6,7 +6,7 @@ rm -rf vendor/ source 'hack/.vendor-helpers.sh' # the following lines are in sorted order, FYI -clone git github.com/Azure/go-ansiterm 0a9ca7117fc3e5629da85238ede560cb5e749783 +clone git github.com/Azure/go-ansiterm 70b2c90b260171e829f1ebd7c17f600c11858dbe clone git github.com/Sirupsen/logrus v0.8.2 # logrus is a common dependency among multiple deps clone git github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673 diff --git a/vendor/src/github.com/Azure/go-ansiterm/constants.go b/vendor/src/github.com/Azure/go-ansiterm/constants.go index 906b1bd1df..ebfce8a8d3 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/constants.go +++ b/vendor/src/github.com/Azure/go-ansiterm/constants.go @@ -77,7 +77,11 @@ const ( DEFAULT_HEIGHT = 24 ANSI_BEL = 0x07 + ANSI_BACKSPACE = 0x08 + ANSI_TAB = 0x09 ANSI_LINE_FEED = 0x0A + ANSI_VERTICAL_TAB = 0x0B + ANSI_FORM_FEED = 0x0C ANSI_CARRIAGE_RETURN = 0x0D ANSI_ESCAPE_PRIMARY = 0x1B ANSI_ESCAPE_SECONDARY = 0x5B diff --git a/vendor/src/github.com/Azure/go-ansiterm/event_handler.go b/vendor/src/github.com/Azure/go-ansiterm/event_handler.go index 5e6b00ccb6..98087b38c2 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/event_handler.go +++ b/vendor/src/github.com/Azure/go-ansiterm/event_handler.go @@ -28,6 +28,9 @@ type AnsiEventHandler interface { // Cursor Horizontal position Absolute CHA(int) error + // Vertical line Position Absolute + VPA(int) error + // CUrsor Position CUP(int, int) error @@ -37,6 +40,12 @@ type AnsiEventHandler interface { // Text Cursor Enable Mode DECTCEM(bool) error + // Origin Mode + DECOM(bool) error + + // 132 Column Mode + DECCOLM(bool) error + // Erase in Display ED(int) error @@ -49,6 +58,12 @@ type AnsiEventHandler interface { // Delete Line DL(int) error + // Insert Character + ICH(int) error + + // Delete Character + DCH(int) error + // Set Graphics Rendition SGR([]int) error @@ -64,6 +79,9 @@ type AnsiEventHandler interface { // Set Top and Bottom Margins DECSTBM(int, int) error + // Index + IND() error + // Reverse Index RI() error diff --git a/vendor/src/github.com/Azure/go-ansiterm/parser_action_helpers.go b/vendor/src/github.com/Azure/go-ansiterm/parser_action_helpers.go index b300b43806..438802097d 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/parser_action_helpers.go +++ b/vendor/src/github.com/Azure/go-ansiterm/parser_action_helpers.go @@ -65,17 +65,29 @@ func getInts(params []string, minCount int, dflt int) []int { return ints } +func (ap *AnsiParser) modeDispatch(param string, set bool) error { + switch param { + case "?3": + return ap.eventHandler.DECCOLM(set) + case "?6": + return ap.eventHandler.DECOM(set) + case "?25": + return ap.eventHandler.DECTCEM(set) + } + return nil +} + func (ap *AnsiParser) hDispatch(params []string) error { - if len(params) == 1 && params[0] == "?25" { - return ap.eventHandler.DECTCEM(true) + if len(params) == 1 { + return ap.modeDispatch(params[0], true) } return nil } func (ap *AnsiParser) lDispatch(params []string) error { - if len(params) == 1 && params[0] == "?25" { - return ap.eventHandler.DECTCEM(false) + if len(params) == 1 { + return ap.modeDispatch(params[0], false) } return nil diff --git a/vendor/src/github.com/Azure/go-ansiterm/parser_actions.go b/vendor/src/github.com/Azure/go-ansiterm/parser_actions.go index 328392e109..260e6aae3c 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/parser_actions.go +++ b/vendor/src/github.com/Azure/go-ansiterm/parser_actions.go @@ -25,7 +25,15 @@ func (ap *AnsiParser) escDispatch() error { logger.Infof("escDispatch: %v(%v)", cmd, intermeds) switch cmd { - case "M": + case "D": // IND + return ap.eventHandler.IND() + case "E": // NEL, equivalent to CRLF + err := ap.eventHandler.Execute(ANSI_CARRIAGE_RETURN) + if err == nil { + err = ap.eventHandler.Execute(ANSI_LINE_FEED) + } + return err + case "M": // RI return ap.eventHandler.RI() } @@ -39,6 +47,8 @@ func (ap *AnsiParser) csiDispatch() error { logger.Infof("csiDispatch: %v(%v)", cmd, params) switch cmd { + case "@": + return ap.eventHandler.ICH(getInt(params, 1)) case "A": return ap.eventHandler.CUU(getInt(params, 1)) case "B": @@ -67,12 +77,16 @@ func (ap *AnsiParser) csiDispatch() error { return ap.eventHandler.IL(getInt(params, 1)) case "M": return ap.eventHandler.DL(getInt(params, 1)) + case "P": + return ap.eventHandler.DCH(getInt(params, 1)) case "S": return ap.eventHandler.SU(getInt(params, 1)) case "T": return ap.eventHandler.SD(getInt(params, 1)) case "c": return ap.eventHandler.DA(params) + case "d": + return ap.eventHandler.VPA(getInt(params, 1)) case "f": ints := getInts(params, 2, 1) x, y := ints[0], ints[1] diff --git a/vendor/src/github.com/Azure/go-ansiterm/test_event_handler.go b/vendor/src/github.com/Azure/go-ansiterm/test_event_handler.go index 4ac1299643..60f9f30b98 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/test_event_handler.go +++ b/vendor/src/github.com/Azure/go-ansiterm/test_event_handler.go @@ -65,6 +65,11 @@ func (h *TestAnsiEventHandler) CHA(param int) error { return nil } +func (h *TestAnsiEventHandler) VPA(param int) error { + h.recordCall("VPA", []string{strconv.Itoa(param)}) + return nil +} + func (h *TestAnsiEventHandler) CUP(x int, y int) error { xS, yS := strconv.Itoa(x), strconv.Itoa(y) h.recordCall("CUP", []string{xS, yS}) @@ -82,6 +87,16 @@ func (h *TestAnsiEventHandler) DECTCEM(visible bool) error { return nil } +func (h *TestAnsiEventHandler) DECOM(visible bool) error { + h.recordCall("DECOM", []string{strconv.FormatBool(visible)}) + return nil +} + +func (h *TestAnsiEventHandler) DECCOLM(use132 bool) error { + h.recordCall("DECOLM", []string{strconv.FormatBool(use132)}) + return nil +} + func (h *TestAnsiEventHandler) ED(param int) error { h.recordCall("ED", []string{strconv.Itoa(param)}) return nil @@ -102,6 +117,16 @@ func (h *TestAnsiEventHandler) DL(param int) error { return nil } +func (h *TestAnsiEventHandler) ICH(param int) error { + h.recordCall("ICH", []string{strconv.Itoa(param)}) + return nil +} + +func (h *TestAnsiEventHandler) DCH(param int) error { + h.recordCall("DCH", []string{strconv.Itoa(param)}) + return nil +} + func (h *TestAnsiEventHandler) SGR(params []int) error { strings := []string{} for _, v := range params { @@ -138,6 +163,11 @@ func (h *TestAnsiEventHandler) RI() error { return nil } +func (h *TestAnsiEventHandler) IND() error { + h.recordCall("IND", nil) + return nil +} + func (h *TestAnsiEventHandler) Flush() error { return nil } diff --git a/vendor/src/github.com/Azure/go-ansiterm/winterm/ansi.go b/vendor/src/github.com/Azure/go-ansiterm/winterm/ansi.go index 68f4305508..78fe92fe65 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/winterm/ansi.go +++ b/vendor/src/github.com/Azure/go-ansiterm/winterm/ansi.go @@ -9,7 +9,7 @@ import ( "strings" "syscall" - . "github.com/docker/docker/vendor/src/github.com/Azure/go-ansiterm" + . "github.com/Azure/go-ansiterm" ) // Windows keyboard constants diff --git a/vendor/src/github.com/Azure/go-ansiterm/winterm/attr_translation.go b/vendor/src/github.com/Azure/go-ansiterm/winterm/attr_translation.go index be047b71c6..94665db6fb 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/winterm/attr_translation.go +++ b/vendor/src/github.com/Azure/go-ansiterm/winterm/attr_translation.go @@ -13,7 +13,7 @@ const ( // collectAnsiIntoWindowsAttributes modifies the passed Windows text mode flags to reflect the // request represented by the passed ANSI mode. -func collectAnsiIntoWindowsAttributes(windowsMode WORD, baseMode WORD, ansiMode SHORT) WORD { +func collectAnsiIntoWindowsAttributes(windowsMode WORD, inverted bool, baseMode WORD, ansiMode SHORT) (WORD, bool) { switch ansiMode { // Mode styles @@ -26,9 +26,11 @@ func collectAnsiIntoWindowsAttributes(windowsMode WORD, baseMode WORD, ansiMode case ANSI_SGR_UNDERLINE: windowsMode = windowsMode | COMMON_LVB_UNDERSCORE - case ANSI_SGR_REVERSE, ANSI_SGR_REVERSE_OFF: - // Note: Windows does not support a native reverse. Simply swap the foreground / background color / intensity. - windowsMode = (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4) + case ANSI_SGR_REVERSE: + inverted = true + + case ANSI_SGR_REVERSE_OFF: + inverted = false case ANSI_SGR_UNDERLINE_OFF: windowsMode &^= COMMON_LVB_UNDERSCORE @@ -91,5 +93,10 @@ func collectAnsiIntoWindowsAttributes(windowsMode WORD, baseMode WORD, ansiMode windowsMode = (windowsMode &^ BACKGROUND_COLOR_MASK) | BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE } - return windowsMode + return windowsMode, inverted +} + +// invertAttributes inverts the foreground and background colors of a Windows attributes value +func invertAttributes(windowsMode WORD) WORD { + return (COMMON_LVB_MASK & windowsMode) | ((FOREGROUND_MASK & windowsMode) << 4) | ((BACKGROUND_MASK & windowsMode) >> 4) } diff --git a/vendor/src/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go b/vendor/src/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go index d73cd90758..e4b1c255a4 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go +++ b/vendor/src/github.com/Azure/go-ansiterm/winterm/cursor_helpers.go @@ -7,11 +7,35 @@ const ( Vertical ) -// setCursorPosition sets the cursor to the specified position, bounded to the buffer size -func (h *WindowsAnsiEventHandler) setCursorPosition(position COORD, sizeBuffer COORD) error { - position.X = ensureInRange(position.X, 0, sizeBuffer.X-1) - position.Y = ensureInRange(position.Y, 0, sizeBuffer.Y-1) - return SetConsoleCursorPosition(h.fd, position) +func (h *WindowsAnsiEventHandler) getCursorWindow(info *CONSOLE_SCREEN_BUFFER_INFO) SMALL_RECT { + if h.originMode { + sr := h.effectiveSr(info.Window) + return SMALL_RECT{ + Top: sr.top, + Bottom: sr.bottom, + Left: 0, + Right: info.Size.X - 1, + } + } else { + return SMALL_RECT{ + Top: info.Window.Top, + Bottom: info.Window.Bottom, + Left: 0, + Right: info.Size.X - 1, + } + } +} + +// setCursorPosition sets the cursor to the specified position, bounded to the screen size +func (h *WindowsAnsiEventHandler) setCursorPosition(position COORD, window SMALL_RECT) error { + position.X = ensureInRange(position.X, window.Left, window.Right) + position.Y = ensureInRange(position.Y, window.Top, window.Bottom) + err := SetConsoleCursorPosition(h.fd, position) + if err != nil { + return err + } + logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y) + return err } func (h *WindowsAnsiEventHandler) moveCursorVertical(param int) error { @@ -31,17 +55,15 @@ func (h *WindowsAnsiEventHandler) moveCursor(moveMode int, param int) error { position := info.CursorPosition switch moveMode { case Horizontal: - position.X = AddInRange(position.X, SHORT(param), info.Window.Left, info.Window.Right) + position.X += SHORT(param) case Vertical: - position.Y = AddInRange(position.Y, SHORT(param), info.Window.Top, info.Window.Bottom) + position.Y += SHORT(param) } - if err = h.setCursorPosition(position, info.Size); err != nil { + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { return err } - logger.Infof("Cursor position set: (%d, %d)", position.X, position.Y) - return nil } @@ -53,9 +75,9 @@ func (h *WindowsAnsiEventHandler) moveCursorLine(param int) error { position := info.CursorPosition position.X = 0 - position.Y = AddInRange(position.Y, SHORT(param), info.Window.Top, info.Window.Bottom) + position.Y += SHORT(param) - if err = h.setCursorPosition(position, info.Size); err != nil { + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { return err } @@ -69,9 +91,9 @@ func (h *WindowsAnsiEventHandler) moveCursorColumn(param int) error { } position := info.CursorPosition - position.X = AddInRange(SHORT(param), -1, info.Window.Left, info.Window.Right) + position.X = SHORT(param) - 1 - if err = h.setCursorPosition(position, info.Size); err != nil { + if err = h.setCursorPosition(position, h.getCursorWindow(info)); err != nil { return err } diff --git a/vendor/src/github.com/Azure/go-ansiterm/winterm/scroll_helper.go b/vendor/src/github.com/Azure/go-ansiterm/winterm/scroll_helper.go index b4ae7d9c16..ed1998245c 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/winterm/scroll_helper.go +++ b/vendor/src/github.com/Azure/go-ansiterm/winterm/scroll_helper.go @@ -2,89 +2,117 @@ package winterm -func (h *WindowsAnsiEventHandler) scrollPageUp() error { - return h.scrollPage(1) -} - -func (h *WindowsAnsiEventHandler) scrollPageDown() error { - return h.scrollPage(-1) -} - -func (h *WindowsAnsiEventHandler) scrollPage(param int) error { - info, err := GetConsoleScreenBufferInfo(h.fd) - if err != nil { - return err +// effectiveSr gets the current effective scroll region in buffer coordinates +func (h *WindowsAnsiEventHandler) effectiveSr(window SMALL_RECT) scrollRegion { + top := AddInRange(window.Top, h.sr.top, window.Top, window.Bottom) + bottom := AddInRange(window.Top, h.sr.bottom, window.Top, window.Bottom) + if top >= bottom { + top = window.Top + bottom = window.Bottom } - - tmpScrollTop := h.sr.top - tmpScrollBottom := h.sr.bottom - - // Set scroll region to whole window - h.sr.top = 0 - h.sr.bottom = int(info.Size.Y - 1) - - err = h.scroll(param) - - h.sr.top = tmpScrollTop - h.sr.bottom = tmpScrollBottom - - return err + return scrollRegion{top: top, bottom: bottom} } func (h *WindowsAnsiEventHandler) scrollUp(param int) error { - return h.scroll(param) -} - -func (h *WindowsAnsiEventHandler) scrollDown(param int) error { - return h.scroll(-param) -} - -func (h *WindowsAnsiEventHandler) scroll(param int) error { - info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } - logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", h.sr.top, h.sr.bottom) - logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom) + sr := h.effectiveSr(info.Window) + return h.scroll(param, sr, info) +} - rect := info.Window +func (h *WindowsAnsiEventHandler) scrollDown(param int) error { + return h.scrollUp(-param) +} - // Current scroll region in Windows backing buffer coordinates - top := rect.Top + SHORT(h.sr.top) - bottom := rect.Top + SHORT(h.sr.bottom) - - // Area from backing buffer to be copied - scrollRect := SMALL_RECT{ - Top: top + SHORT(param), - Bottom: bottom + SHORT(param), - Left: rect.Left, - Right: rect.Right, +func (h *WindowsAnsiEventHandler) deleteLines(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err } - // Clipping region should be the original scroll region - clipRegion := SMALL_RECT{ - Top: top, - Bottom: bottom, - Left: rect.Left, - Right: rect.Right, + start := info.CursorPosition.Y + sr := h.effectiveSr(info.Window) + // Lines cannot be inserted or deleted outside the scrolling region. + if start >= sr.top && start <= sr.bottom { + sr.top = start + return h.scroll(param, sr, info) + } else { + return nil + } +} + +func (h *WindowsAnsiEventHandler) insertLines(param int) error { + return h.deleteLines(-param) +} + +// scroll scrolls the provided scroll region by param lines. The scroll region is in buffer coordinates. +func (h *WindowsAnsiEventHandler) scroll(param int, sr scrollRegion, info *CONSOLE_SCREEN_BUFFER_INFO) error { + logger.Infof("scroll: scrollTop: %d, scrollBottom: %d", sr.top, sr.bottom) + logger.Infof("scroll: windowTop: %d, windowBottom: %d", info.Window.Top, info.Window.Bottom) + + // Copy from and clip to the scroll region (full buffer width) + scrollRect := SMALL_RECT{ + Top: sr.top, + Bottom: sr.bottom, + Left: 0, + Right: info.Size.X - 1, } // Origin to which area should be copied destOrigin := COORD{ - X: rect.Left, - Y: top, + X: 0, + Y: sr.top - SHORT(param), } char := CHAR_INFO{ UnicodeChar: ' ', - Attributes: 0, + Attributes: h.attributes, } - if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, clipRegion, destOrigin, char); err != nil { + if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { + return err + } + return nil +} + +func (h *WindowsAnsiEventHandler) deleteCharacters(param int) error { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + return h.scrollLine(param, info.CursorPosition, info) +} + +func (h *WindowsAnsiEventHandler) insertCharacters(param int) error { + return h.deleteCharacters(-param) +} + +// scrollLine scrolls a line horizontally starting at the provided position by a number of columns. +func (h *WindowsAnsiEventHandler) scrollLine(columns int, position COORD, info *CONSOLE_SCREEN_BUFFER_INFO) error { + // Copy from and clip to the scroll region (full buffer width) + scrollRect := SMALL_RECT{ + Top: position.Y, + Bottom: position.Y, + Left: position.X, + Right: info.Size.X - 1, + } + + // Origin to which area should be copied + destOrigin := COORD{ + X: position.X - SHORT(columns), + Y: position.Y, + } + + char := CHAR_INFO{ + UnicodeChar: ' ', + Attributes: h.attributes, + } + + if err := ScrollConsoleScreenBuffer(h.fd, scrollRect, scrollRect, destOrigin, char); err != nil { return err } - return nil } diff --git a/vendor/src/github.com/Azure/go-ansiterm/winterm/win_event_handler.go b/vendor/src/github.com/Azure/go-ansiterm/winterm/win_event_handler.go index 249943784d..2d492b32e4 100644 --- a/vendor/src/github.com/Azure/go-ansiterm/winterm/win_event_handler.go +++ b/vendor/src/github.com/Azure/go-ansiterm/winterm/win_event_handler.go @@ -15,11 +15,19 @@ import ( var logger *logrus.Logger type WindowsAnsiEventHandler struct { - fd uintptr - file *os.File - infoReset *CONSOLE_SCREEN_BUFFER_INFO - sr scrollRegion - buffer bytes.Buffer + fd uintptr + file *os.File + infoReset *CONSOLE_SCREEN_BUFFER_INFO + sr scrollRegion + buffer bytes.Buffer + attributes WORD + inverted bool + wrapNext bool + drewMarginByte bool + originMode bool + marginByte byte + curInfo *CONSOLE_SCREEN_BUFFER_INFO + curPos COORD } func CreateWinEventHandler(fd uintptr, file *os.File) AnsiEventHandler { @@ -40,67 +48,228 @@ func CreateWinEventHandler(fd uintptr, file *os.File) AnsiEventHandler { return nil } - sr := scrollRegion{int(infoReset.Window.Top), int(infoReset.Window.Bottom)} - return &WindowsAnsiEventHandler{ - fd: fd, - file: file, - infoReset: infoReset, - sr: sr, + fd: fd, + file: file, + infoReset: infoReset, + attributes: infoReset.Attributes, } } type scrollRegion struct { - top int - bottom int + top SHORT + bottom SHORT } -func (h *WindowsAnsiEventHandler) Print(b byte) error { - return h.buffer.WriteByte(b) +// simulateLF simulates a LF or CR+LF by scrolling if necessary to handle the +// current cursor position and scroll region settings, in which case it returns +// true. If no special handling is necessary, then it does nothing and returns +// false. +// +// In the false case, the caller should ensure that a carriage return +// and line feed are inserted or that the text is otherwise wrapped. +func (h *WindowsAnsiEventHandler) simulateLF(includeCR bool) (bool, error) { + if h.wrapNext { + if err := h.Flush(); err != nil { + return false, err + } + h.clearWrap() + } + pos, info, err := h.getCurrentInfo() + if err != nil { + return false, err + } + sr := h.effectiveSr(info.Window) + if pos.Y == sr.bottom { + // Scrolling is necessary. Let Windows automatically scroll if the scrolling region + // is the full window. + if sr.top == info.Window.Top && sr.bottom == info.Window.Bottom { + if includeCR { + pos.X = 0 + h.updatePos(pos) + } + return false, nil + } else { + // A custom scroll region is active. Scroll the window manually to simulate + // the LF. + if err := h.Flush(); err != nil { + return false, err + } + logger.Info("Simulating LF inside scroll region") + if err := h.scrollUp(1); err != nil { + return false, err + } + if includeCR { + pos.X = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return false, err + } + } + return true, nil + } + } else if pos.Y < info.Window.Bottom { + // Let Windows handle the LF. + pos.Y++ + if includeCR { + pos.X = 0 + } + h.updatePos(pos) + return false, nil + } else { + // The cursor is at the bottom of the screen but outside the scroll + // region. Skip the LF. + logger.Info("Simulating LF outside scroll region") + if includeCR { + if err := h.Flush(); err != nil { + return false, err + } + pos.X = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return false, err + } + } + return true, nil + } } -func (h *WindowsAnsiEventHandler) Execute(b byte) error { - if ANSI_LINE_FEED == b { - info, err := GetConsoleScreenBufferInfo(h.fd) +// executeLF executes a LF without a CR. +func (h *WindowsAnsiEventHandler) executeLF() error { + handled, err := h.simulateLF(false) + if err != nil { + return err + } + if !handled { + // Windows LF will reset the cursor column position. Write the LF + // and restore the cursor position. + pos, _, err := h.getCurrentInfo() if err != nil { return err } - - if int(info.CursorPosition.Y) == h.sr.bottom { + h.buffer.WriteByte(ANSI_LINE_FEED) + if pos.X != 0 { if err := h.Flush(); err != nil { return err } - - logger.Infof("Scrolling due to LF at bottom of scroll region") - - // Scroll up one row if we attempt to line feed at the bottom - // of the scroll region - if err := h.scrollUp(1); err != nil { - return err - } - - // Clear line - // if err := h.CUD(1); err != nil { - // return err - // } - if err := h.EL(0); err != nil { + logger.Info("Resetting cursor position for LF without CR") + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { return err } } } - - if ANSI_BEL <= b && b <= ANSI_CARRIAGE_RETURN { - return h.buffer.WriteByte(b) - } - return nil } +func (h *WindowsAnsiEventHandler) Print(b byte) error { + if h.wrapNext { + h.buffer.WriteByte(h.marginByte) + h.clearWrap() + if _, err := h.simulateLF(true); err != nil { + return err + } + } + pos, info, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X == info.Size.X-1 { + h.wrapNext = true + h.marginByte = b + } else { + pos.X++ + h.updatePos(pos) + h.buffer.WriteByte(b) + } + return nil +} + +func (h *WindowsAnsiEventHandler) Execute(b byte) error { + switch b { + case ANSI_TAB: + logger.Info("Execute(TAB)") + // Move to the next tab stop, but preserve auto-wrap if already set. + if !h.wrapNext { + pos, info, err := h.getCurrentInfo() + if err != nil { + return err + } + pos.X = (pos.X + 8) - pos.X%8 + if pos.X >= info.Size.X { + pos.X = info.Size.X - 1 + } + if err := h.Flush(); err != nil { + return err + } + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + } + return nil + + case ANSI_BEL: + h.buffer.WriteByte(ANSI_BEL) + return nil + + case ANSI_BACKSPACE: + if h.wrapNext { + if err := h.Flush(); err != nil { + return err + } + h.clearWrap() + } + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X > 0 { + pos.X-- + h.updatePos(pos) + h.buffer.WriteByte(ANSI_BACKSPACE) + } + return nil + + case ANSI_VERTICAL_TAB, ANSI_FORM_FEED: + // Treat as true LF. + return h.executeLF() + + case ANSI_LINE_FEED: + // Simulate a CR and LF for now since there is no way in go-ansiterm + // to tell if the LF should include CR (and more things break when it's + // missing than when it's incorrectly added). + handled, err := h.simulateLF(true) + if handled || err != nil { + return err + } + return h.buffer.WriteByte(ANSI_LINE_FEED) + + case ANSI_CARRIAGE_RETURN: + if h.wrapNext { + if err := h.Flush(); err != nil { + return err + } + h.clearWrap() + } + pos, _, err := h.getCurrentInfo() + if err != nil { + return err + } + if pos.X != 0 { + pos.X = 0 + h.updatePos(pos) + h.buffer.WriteByte(ANSI_CARRIAGE_RETURN) + } + return nil + + default: + return nil + } +} + func (h *WindowsAnsiEventHandler) CUU(param int) error { if err := h.Flush(); err != nil { return err } logger.Infof("CUU: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() return h.moveCursorVertical(-param) } @@ -109,6 +278,7 @@ func (h *WindowsAnsiEventHandler) CUD(param int) error { return err } logger.Infof("CUD: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() return h.moveCursorVertical(param) } @@ -117,6 +287,7 @@ func (h *WindowsAnsiEventHandler) CUF(param int) error { return err } logger.Infof("CUF: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() return h.moveCursorHorizontal(param) } @@ -125,6 +296,7 @@ func (h *WindowsAnsiEventHandler) CUB(param int) error { return err } logger.Infof("CUB: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() return h.moveCursorHorizontal(-param) } @@ -133,6 +305,7 @@ func (h *WindowsAnsiEventHandler) CNL(param int) error { return err } logger.Infof("CNL: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() return h.moveCursorLine(param) } @@ -141,6 +314,7 @@ func (h *WindowsAnsiEventHandler) CPL(param int) error { return err } logger.Infof("CPL: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() return h.moveCursorLine(-param) } @@ -149,34 +323,48 @@ func (h *WindowsAnsiEventHandler) CHA(param int) error { return err } logger.Infof("CHA: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() return h.moveCursorColumn(param) } +func (h *WindowsAnsiEventHandler) VPA(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("VPA: [[%d]]", param) + h.clearWrap() + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + window := h.getCursorWindow(info) + position := info.CursorPosition + position.Y = window.Top + SHORT(param) - 1 + return h.setCursorPosition(position, window) +} + func (h *WindowsAnsiEventHandler) CUP(row int, col int) error { if err := h.Flush(); err != nil { return err } - rowStr, colStr := strconv.Itoa(row), strconv.Itoa(col) - logger.Infof("CUP: [%v]", []string{rowStr, colStr}) + logger.Infof("CUP: [[%d %d]]", row, col) + h.clearWrap() info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } - rect := info.Window - rowS := AddInRange(SHORT(row-1), rect.Top, rect.Top, rect.Bottom) - colS := AddInRange(SHORT(col-1), rect.Left, rect.Left, rect.Right) - position := COORD{colS, rowS} - - return h.setCursorPosition(position, info.Size) + window := h.getCursorWindow(info) + position := COORD{window.Left + SHORT(col) - 1, window.Top + SHORT(row) - 1} + return h.setCursorPosition(position, window) } func (h *WindowsAnsiEventHandler) HVP(row int, col int) error { if err := h.Flush(); err != nil { return err } - rowS, colS := strconv.Itoa(row), strconv.Itoa(row) - logger.Infof("HVP: [%v]", []string{rowS, colS}) + logger.Infof("HVP: [[%d %d]]", row, col) + h.clearWrap() return h.CUP(row, col) } @@ -185,22 +373,70 @@ func (h *WindowsAnsiEventHandler) DECTCEM(visible bool) error { return err } logger.Infof("DECTCEM: [%v]", []string{strconv.FormatBool(visible)}) - + h.clearWrap() return nil } +func (h *WindowsAnsiEventHandler) DECOM(enable bool) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DECOM: [%v]", []string{strconv.FormatBool(enable)}) + h.clearWrap() + h.originMode = enable + return h.CUP(1, 1) +} + +func (h *WindowsAnsiEventHandler) DECCOLM(use132 bool) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DECCOLM: [%v]", []string{strconv.FormatBool(use132)}) + h.clearWrap() + if err := h.ED(2); err != nil { + return err + } + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + targetWidth := SHORT(80) + if use132 { + targetWidth = 132 + } + if info.Size.X < targetWidth { + if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { + logger.Info("set buffer failed:", err) + return err + } + } + window := info.Window + window.Left = 0 + window.Right = targetWidth - 1 + if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { + logger.Info("set window failed:", err) + return err + } + if info.Size.X > targetWidth { + if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil { + logger.Info("set buffer failed:", err) + return err + } + } + return SetConsoleCursorPosition(h.fd, COORD{0, 0}) +} + func (h *WindowsAnsiEventHandler) ED(param int) error { if err := h.Flush(); err != nil { return err } logger.Infof("ED: [%v]", []string{strconv.Itoa(param)}) + h.clearWrap() // [J -- Erases from the cursor to the end of the screen, including the cursor position. // [1J -- Erases from the beginning of the screen to the cursor, including the cursor position. // [2J -- Erases the complete display. The cursor does not move. - // [3J -- Erases the complete display and backing buffer, cursor moves to (0,0) // Notes: - // -- ANSI.SYS always moved the cursor to (0,0) for both [2J and [3J // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles info, err := GetConsoleScreenBufferInfo(h.fd) @@ -223,20 +459,25 @@ func (h *WindowsAnsiEventHandler) ED(param int) error { case 2: start = COORD{0, 0} end = COORD{info.Size.X - 1, info.Size.Y - 1} - - case 3: - start = COORD{0, 0} - end = COORD{info.Size.X - 1, info.Size.Y - 1} } - err = h.clearRange(info.Attributes, start, end) + err = h.clearRange(h.attributes, start, end) if err != nil { return err } - if param == 2 || param == 3 { - err = h.setCursorPosition(COORD{0, 0}, info.Size) - if err != nil { + // If the whole buffer was cleared, move the window to the top while preserving + // the window-relative cursor position. + if param == 2 { + pos := info.CursorPosition + window := info.Window + pos.Y -= window.Top + window.Bottom -= window.Top + window.Top = 0 + if err := SetConsoleCursorPosition(h.fd, pos); err != nil { + return err + } + if err := SetConsoleWindowInfo(h.fd, true, window); err != nil { return err } } @@ -249,6 +490,7 @@ func (h *WindowsAnsiEventHandler) EL(param int) error { return err } logger.Infof("EL: [%v]", strconv.Itoa(param)) + h.clearWrap() // [K -- Erases from the cursor to the end of the line, including the cursor position. // [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 { end = COORD{info.Size.X, info.CursorPosition.Y} } - err = h.clearRange(info.Attributes, start, end) + err = h.clearRange(h.attributes, start, end) if err != nil { return err } @@ -289,11 +531,8 @@ func (h *WindowsAnsiEventHandler) IL(param int) error { return err } logger.Infof("IL: [%v]", strconv.Itoa(param)) - if err := h.scrollDown(param); err != nil { - return err - } - - return h.EL(2) + h.clearWrap() + return h.insertLines(param) } func (h *WindowsAnsiEventHandler) DL(param int) error { @@ -301,7 +540,26 @@ func (h *WindowsAnsiEventHandler) DL(param int) error { return err } logger.Infof("DL: [%v]", strconv.Itoa(param)) - return h.scrollUp(param) + h.clearWrap() + return h.deleteLines(param) +} + +func (h *WindowsAnsiEventHandler) ICH(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("ICH: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.insertCharacters(param) +} + +func (h *WindowsAnsiEventHandler) DCH(param int) error { + if err := h.Flush(); err != nil { + return err + } + logger.Infof("DCH: [%v]", strconv.Itoa(param)) + h.clearWrap() + return h.deleteCharacters(param) } func (h *WindowsAnsiEventHandler) SGR(params []int) error { @@ -310,33 +568,32 @@ func (h *WindowsAnsiEventHandler) SGR(params []int) error { } strings := []string{} for _, v := range params { - logger.Infof("SGR: [%v]", strings) strings = append(strings, strconv.Itoa(v)) } logger.Infof("SGR: [%v]", strings) - info, err := GetConsoleScreenBufferInfo(h.fd) - if err != nil { - return err - } - - attributes := info.Attributes if len(params) <= 0 { - attributes = h.infoReset.Attributes + h.attributes = h.infoReset.Attributes + h.inverted = false } else { for _, attr := range params { if attr == ANSI_SGR_RESET { - attributes = h.infoReset.Attributes + h.attributes = h.infoReset.Attributes + h.inverted = false continue } - attributes = collectAnsiIntoWindowsAttributes(attributes, h.infoReset.Attributes, SHORT(attr)) + h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, SHORT(attr)) } } - err = SetConsoleTextAttribute(h.fd, attributes) + attributes := h.attributes + if h.inverted { + attributes = invertAttributes(attributes) + } + err := SetConsoleTextAttribute(h.fd, attributes) if err != nil { return err } @@ -349,7 +606,8 @@ func (h *WindowsAnsiEventHandler) SU(param int) error { return err } logger.Infof("SU: [%v]", []string{strconv.Itoa(param)}) - return h.scrollPageUp() + h.clearWrap() + return h.scrollUp(param) } func (h *WindowsAnsiEventHandler) SD(param int) error { @@ -357,44 +615,30 @@ func (h *WindowsAnsiEventHandler) SD(param int) error { return err } logger.Infof("SD: [%v]", []string{strconv.Itoa(param)}) - return h.scrollPageDown() + h.clearWrap() + return h.scrollDown(param) } func (h *WindowsAnsiEventHandler) DA(params []string) error { logger.Infof("DA: [%v]", params) - - // See the site below for details of the device attributes command - // http://vt100.net/docs/vt220-rm/chapter4.html - - // First character of first parameter string is '>' - if params[0][0] == '>' { - // Secondary device attribute request: - // Respond with: - // "I am a VT220 version 1.0, no options. - // CSI > 1 ; 1 0 ; 0 c CR LF - h.buffer.Write([]byte{CSI_ENTRY, 0x3E, 0x31, 0x3B, 0x31, 0x30, 0x3B, 0x30, 0x63, 0x0D, 0x0A}) - - } else { - // Primary device attribute request: - // Respond with: - // "I am a service class 2 terminal (62) with 132 columns (1), - // printer port (2), selective erase (6), DRCS (7), UDK (8), - // and I support 7-bit national replacement character sets (9)." - // CSI ? 6 2 ; 1 ; 2 ; 6 ; 7 ; 8 ; 9 c CR LF - h.buffer.Write([]byte{CSI_ENTRY, 0x3F, 0x36, 0x32, 0x3B, 0x31, 0x3B, 0x32, 0x3B, 0x36, 0x3B, 0x37, 0x3B, 0x38, 0x3B, 0x39, 0x63, 0x0D, 0x0A}) - } - + // DA cannot be implemented because it must send data on the VT100 input stream, + // which is not available to go-ansiterm. return nil } func (h *WindowsAnsiEventHandler) DECSTBM(top int, bottom int) error { + if err := h.Flush(); err != nil { + return err + } logger.Infof("DECSTBM: [%d, %d]", top, bottom) // Windows is 0 indexed, Linux is 1 indexed - h.sr.top = top - 1 - h.sr.bottom = bottom - 1 + h.sr.top = SHORT(top - 1) + h.sr.bottom = SHORT(bottom - 1) - return nil + // This command also moves the cursor to the origin. + h.clearWrap() + return h.CUP(1, 1) } func (h *WindowsAnsiEventHandler) RI() error { @@ -402,29 +646,80 @@ func (h *WindowsAnsiEventHandler) RI() error { return err } logger.Info("RI: []") + h.clearWrap() info, err := GetConsoleScreenBufferInfo(h.fd) if err != nil { return err } - if info.Window.Top == info.CursorPosition.Y { - if err := h.scrollPageDown(); err != nil { - return err - } - - return h.EL(2) + sr := h.effectiveSr(info.Window) + if info.CursorPosition.Y == sr.top { + return h.scrollDown(1) } else { - return h.CUU(1) + return h.moveCursorVertical(-1) } } +func (h *WindowsAnsiEventHandler) IND() error { + logger.Info("IND: []") + return h.executeLF() +} + func (h *WindowsAnsiEventHandler) Flush() error { + h.curInfo = nil if h.buffer.Len() > 0 { logger.Infof("Flush: [%s]", h.buffer.Bytes()) if _, err := h.buffer.WriteTo(h.file); err != nil { return err } } + + if h.wrapNext && !h.drewMarginByte { + logger.Infof("Flush: drawing margin byte '%c'", h.marginByte) + + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return err + } + + charInfo := []CHAR_INFO{{UnicodeChar: WCHAR(h.marginByte), Attributes: info.Attributes}} + size := COORD{1, 1} + position := COORD{0, 0} + region := SMALL_RECT{Left: info.CursorPosition.X, Top: info.CursorPosition.Y, Right: info.CursorPosition.X, Bottom: info.CursorPosition.Y} + if err := WriteConsoleOutput(h.fd, charInfo, size, position, ®ion); err != nil { + return err + } + h.drewMarginByte = true + } return nil } + +// cacheConsoleInfo ensures that the current console screen information has been queried +// since the last call to Flush(). It must be called before accessing h.curInfo or h.curPos. +func (h *WindowsAnsiEventHandler) getCurrentInfo() (COORD, *CONSOLE_SCREEN_BUFFER_INFO, error) { + if h.curInfo == nil { + info, err := GetConsoleScreenBufferInfo(h.fd) + if err != nil { + return COORD{}, nil, err + } + h.curInfo = info + h.curPos = info.CursorPosition + } + return h.curPos, h.curInfo, nil +} + +func (h *WindowsAnsiEventHandler) updatePos(pos COORD) { + if h.curInfo == nil { + panic("failed to call getCurrentInfo before calling updatePos") + } + h.curPos = pos +} + +// clearWrap clears the state where the cursor is in the margin +// waiting for the next character before wrapping the line. This must +// be done before most operations that act on the cursor. +func (h *WindowsAnsiEventHandler) clearWrap() { + h.wrapNext = false + h.drewMarginByte = false +}