123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744 |
- // +build windows
- package winterm
- import (
- "bytes"
- "log"
- "os"
- "strconv"
- "github.com/Azure/go-ansiterm"
- )
- type windowsAnsiEventHandler struct {
- fd uintptr
- file *os.File
- infoReset *CONSOLE_SCREEN_BUFFER_INFO
- sr scrollRegion
- buffer bytes.Buffer
- attributes uint16
- inverted bool
- wrapNext bool
- drewMarginByte bool
- originMode bool
- marginByte byte
- curInfo *CONSOLE_SCREEN_BUFFER_INFO
- curPos COORD
- logf func(string, ...interface{})
- }
- type Option func(*windowsAnsiEventHandler)
- func WithLogf(f func(string, ...interface{})) Option {
- return func(w *windowsAnsiEventHandler) {
- w.logf = f
- }
- }
- func CreateWinEventHandler(fd uintptr, file *os.File, opts ...Option) ansiterm.AnsiEventHandler {
- infoReset, err := GetConsoleScreenBufferInfo(fd)
- if err != nil {
- return nil
- }
- h := &windowsAnsiEventHandler{
- fd: fd,
- file: file,
- infoReset: infoReset,
- attributes: infoReset.Attributes,
- }
- for _, o := range opts {
- o(h)
- }
- if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" {
- logFile, _ := os.Create("winEventHandler.log")
- logger := log.New(logFile, "", log.LstdFlags)
- if h.logf != nil {
- l := h.logf
- h.logf = func(s string, v ...interface{}) {
- l(s, v...)
- logger.Printf(s, v...)
- }
- } else {
- h.logf = logger.Printf
- }
- }
- if h.logf == nil {
- h.logf = func(string, ...interface{}) {}
- }
- return h
- }
- type scrollRegion struct {
- top int16
- bottom int16
- }
- // 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
- }
- // A custom scroll region is active. Scroll the window manually to simulate
- // the LF.
- if err := h.Flush(); err != nil {
- return false, err
- }
- h.logf("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.
- h.logf("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
- }
- }
- // 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
- }
- h.buffer.WriteByte(ansiterm.ANSI_LINE_FEED)
- if pos.X != 0 {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("Resetting cursor position for LF without CR")
- if err := SetConsoleCursorPosition(h.fd, pos); err != nil {
- return err
- }
- }
- }
- 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 ansiterm.ANSI_TAB:
- h.logf("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 ansiterm.ANSI_BEL:
- h.buffer.WriteByte(ansiterm.ANSI_BEL)
- return nil
- case ansiterm.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(ansiterm.ANSI_BACKSPACE)
- }
- return nil
- case ansiterm.ANSI_VERTICAL_TAB, ansiterm.ANSI_FORM_FEED:
- // Treat as true LF.
- return h.executeLF()
- case ansiterm.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(ansiterm.ANSI_LINE_FEED)
- case ansiterm.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(ansiterm.ANSI_CARRIAGE_RETURN)
- }
- return nil
- default:
- return nil
- }
- }
- func (h *windowsAnsiEventHandler) CUU(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("CUU: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.moveCursorVertical(-param)
- }
- func (h *windowsAnsiEventHandler) CUD(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("CUD: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.moveCursorVertical(param)
- }
- func (h *windowsAnsiEventHandler) CUF(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("CUF: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.moveCursorHorizontal(param)
- }
- func (h *windowsAnsiEventHandler) CUB(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("CUB: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.moveCursorHorizontal(-param)
- }
- func (h *windowsAnsiEventHandler) CNL(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("CNL: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.moveCursorLine(param)
- }
- func (h *windowsAnsiEventHandler) CPL(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("CPL: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.moveCursorLine(-param)
- }
- func (h *windowsAnsiEventHandler) CHA(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("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
- }
- h.logf("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 + int16(param) - 1
- return h.setCursorPosition(position, window)
- }
- func (h *windowsAnsiEventHandler) CUP(row int, col int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("CUP: [[%d %d]]", row, col)
- h.clearWrap()
- info, err := GetConsoleScreenBufferInfo(h.fd)
- if err != nil {
- return err
- }
- window := h.getCursorWindow(info)
- position := COORD{window.Left + int16(col) - 1, window.Top + int16(row) - 1}
- return h.setCursorPosition(position, window)
- }
- func (h *windowsAnsiEventHandler) HVP(row int, col int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("HVP: [[%d %d]]", row, col)
- h.clearWrap()
- return h.CUP(row, col)
- }
- func (h *windowsAnsiEventHandler) DECTCEM(visible bool) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("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
- }
- h.logf("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
- }
- h.logf("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 := int16(80)
- if use132 {
- targetWidth = 132
- }
- if info.Size.X < targetWidth {
- if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
- h.logf("set buffer failed: %v", err)
- return err
- }
- }
- window := info.Window
- window.Left = 0
- window.Right = targetWidth - 1
- if err := SetConsoleWindowInfo(h.fd, true, window); err != nil {
- h.logf("set window failed: %v", err)
- return err
- }
- if info.Size.X > targetWidth {
- if err := SetConsoleScreenBufferSize(h.fd, COORD{targetWidth, info.Size.Y}); err != nil {
- h.logf("set buffer failed: %v", 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
- }
- h.logf("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.
- // Notes:
- // -- Clearing the entire buffer, versus just the Window, works best for Windows Consoles
- info, err := GetConsoleScreenBufferInfo(h.fd)
- if err != nil {
- return err
- }
- var start COORD
- var end COORD
- switch param {
- case 0:
- start = info.CursorPosition
- end = COORD{info.Size.X - 1, info.Size.Y - 1}
- case 1:
- start = COORD{0, 0}
- end = info.CursorPosition
- case 2:
- start = COORD{0, 0}
- end = COORD{info.Size.X - 1, info.Size.Y - 1}
- }
- err = h.clearRange(h.attributes, start, end)
- if err != nil {
- return err
- }
- // 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
- }
- }
- return nil
- }
- func (h *windowsAnsiEventHandler) EL(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("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.
- // [2K -- Erases the complete line.
- info, err := GetConsoleScreenBufferInfo(h.fd)
- if err != nil {
- return err
- }
- var start COORD
- var end COORD
- switch param {
- case 0:
- start = info.CursorPosition
- end = COORD{info.Size.X, info.CursorPosition.Y}
- case 1:
- start = COORD{0, info.CursorPosition.Y}
- end = info.CursorPosition
- case 2:
- start = COORD{0, info.CursorPosition.Y}
- end = COORD{info.Size.X, info.CursorPosition.Y}
- }
- err = h.clearRange(h.attributes, start, end)
- if err != nil {
- return err
- }
- return nil
- }
- func (h *windowsAnsiEventHandler) IL(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("IL: [%v]", strconv.Itoa(param))
- h.clearWrap()
- return h.insertLines(param)
- }
- func (h *windowsAnsiEventHandler) DL(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("DL: [%v]", strconv.Itoa(param))
- h.clearWrap()
- return h.deleteLines(param)
- }
- func (h *windowsAnsiEventHandler) ICH(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("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
- }
- h.logf("DCH: [%v]", strconv.Itoa(param))
- h.clearWrap()
- return h.deleteCharacters(param)
- }
- func (h *windowsAnsiEventHandler) SGR(params []int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- strings := []string{}
- for _, v := range params {
- strings = append(strings, strconv.Itoa(v))
- }
- h.logf("SGR: [%v]", strings)
- if len(params) <= 0 {
- h.attributes = h.infoReset.Attributes
- h.inverted = false
- } else {
- for _, attr := range params {
- if attr == ansiterm.ANSI_SGR_RESET {
- h.attributes = h.infoReset.Attributes
- h.inverted = false
- continue
- }
- h.attributes, h.inverted = collectAnsiIntoWindowsAttributes(h.attributes, h.inverted, h.infoReset.Attributes, int16(attr))
- }
- }
- attributes := h.attributes
- if h.inverted {
- attributes = invertAttributes(attributes)
- }
- err := SetConsoleTextAttribute(h.fd, attributes)
- if err != nil {
- return err
- }
- return nil
- }
- func (h *windowsAnsiEventHandler) SU(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("SU: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.scrollUp(param)
- }
- func (h *windowsAnsiEventHandler) SD(param int) error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("SD: [%v]", []string{strconv.Itoa(param)})
- h.clearWrap()
- return h.scrollDown(param)
- }
- func (h *windowsAnsiEventHandler) DA(params []string) error {
- h.logf("DA: [%v]", params)
- // 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
- }
- h.logf("DECSTBM: [%d, %d]", top, bottom)
- // Windows is 0 indexed, Linux is 1 indexed
- h.sr.top = int16(top - 1)
- h.sr.bottom = int16(bottom - 1)
- // This command also moves the cursor to the origin.
- h.clearWrap()
- return h.CUP(1, 1)
- }
- func (h *windowsAnsiEventHandler) RI() error {
- if err := h.Flush(); err != nil {
- return err
- }
- h.logf("RI: []")
- h.clearWrap()
- info, err := GetConsoleScreenBufferInfo(h.fd)
- if err != nil {
- return err
- }
- sr := h.effectiveSr(info.Window)
- if info.CursorPosition.Y == sr.top {
- return h.scrollDown(1)
- }
- return h.moveCursorVertical(-1)
- }
- func (h *windowsAnsiEventHandler) IND() error {
- h.logf("IND: []")
- return h.executeLF()
- }
- func (h *windowsAnsiEventHandler) Flush() error {
- h.curInfo = nil
- if h.buffer.Len() > 0 {
- h.logf("Flush: [%s]", h.buffer.Bytes())
- if _, err := h.buffer.WriteTo(h.file); err != nil {
- return err
- }
- }
- if h.wrapNext && !h.drewMarginByte {
- h.logf("Flush: drawing margin byte '%c'", h.marginByte)
- info, err := GetConsoleScreenBufferInfo(h.fd)
- if err != nil {
- return err
- }
- charInfo := []CHAR_INFO{{UnicodeChar: uint16(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
- }
|