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>
This commit is contained in:
root 2015-08-07 03:38:56 +00:00 committed by John Starks
parent b9094633f3
commit ecf528fcb6
11 changed files with 627 additions and 197 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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, &region); 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
}