mirror of https://github.com/beego/bee.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
427 lines
12 KiB
427 lines
12 KiB
// Copyright 2013 bee authors |
|
// |
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may |
|
// not use this file except in compliance with the License. You may obtain |
|
// a copy of the License at |
|
// |
|
// http://www.apache.org/licenses/LICENSE-2.0 |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
|
// License for the specific language governing permissions and limitations |
|
// under the License. |
|
|
|
// +build windows |
|
|
|
package colors |
|
|
|
import ( |
|
"bytes" |
|
"io" |
|
"strings" |
|
"syscall" |
|
"unsafe" |
|
) |
|
|
|
type csiState int |
|
|
|
const ( |
|
outsideCsiCode csiState = iota |
|
firstCsiCode |
|
secondCsiCode |
|
) |
|
|
|
type parseResult int |
|
|
|
const ( |
|
noConsole parseResult = iota |
|
changedColor |
|
unknown |
|
) |
|
|
|
type colorWriter struct { |
|
w io.Writer |
|
mode outputMode |
|
state csiState |
|
paramStartBuf bytes.Buffer |
|
paramBuf bytes.Buffer |
|
} |
|
|
|
const ( |
|
firstCsiChar byte = '\x1b' |
|
secondeCsiChar byte = '[' |
|
separatorChar byte = ';' |
|
sgrCode byte = 'm' |
|
) |
|
|
|
const ( |
|
foregroundBlue = uint16(0x0001) |
|
foregroundGreen = uint16(0x0002) |
|
foregroundRed = uint16(0x0004) |
|
foregroundIntensity = uint16(0x0008) |
|
backgroundBlue = uint16(0x0010) |
|
backgroundGreen = uint16(0x0020) |
|
backgroundRed = uint16(0x0040) |
|
backgroundIntensity = uint16(0x0080) |
|
underscore = uint16(0x8000) |
|
|
|
foregroundMask = foregroundBlue | foregroundGreen | foregroundRed | foregroundIntensity |
|
backgroundMask = backgroundBlue | backgroundGreen | backgroundRed | backgroundIntensity |
|
) |
|
|
|
const ( |
|
ansiReset = "0" |
|
ansiIntensityOn = "1" |
|
ansiIntensityOff = "21" |
|
ansiUnderlineOn = "4" |
|
ansiUnderlineOff = "24" |
|
ansiBlinkOn = "5" |
|
ansiBlinkOff = "25" |
|
|
|
ansiForegroundBlack = "30" |
|
ansiForegroundRed = "31" |
|
ansiForegroundGreen = "32" |
|
ansiForegroundYellow = "33" |
|
ansiForegroundBlue = "34" |
|
ansiForegroundMagenta = "35" |
|
ansiForegroundCyan = "36" |
|
ansiForegroundWhite = "37" |
|
ansiForegroundDefault = "39" |
|
|
|
ansiBackgroundBlack = "40" |
|
ansiBackgroundRed = "41" |
|
ansiBackgroundGreen = "42" |
|
ansiBackgroundYellow = "43" |
|
ansiBackgroundBlue = "44" |
|
ansiBackgroundMagenta = "45" |
|
ansiBackgroundCyan = "46" |
|
ansiBackgroundWhite = "47" |
|
ansiBackgroundDefault = "49" |
|
|
|
ansiLightForegroundGray = "90" |
|
ansiLightForegroundRed = "91" |
|
ansiLightForegroundGreen = "92" |
|
ansiLightForegroundYellow = "93" |
|
ansiLightForegroundBlue = "94" |
|
ansiLightForegroundMagenta = "95" |
|
ansiLightForegroundCyan = "96" |
|
ansiLightForegroundWhite = "97" |
|
|
|
ansiLightBackgroundGray = "100" |
|
ansiLightBackgroundRed = "101" |
|
ansiLightBackgroundGreen = "102" |
|
ansiLightBackgroundYellow = "103" |
|
ansiLightBackgroundBlue = "104" |
|
ansiLightBackgroundMagenta = "105" |
|
ansiLightBackgroundCyan = "106" |
|
ansiLightBackgroundWhite = "107" |
|
) |
|
|
|
type drawType int |
|
|
|
const ( |
|
foreground drawType = iota |
|
background |
|
) |
|
|
|
type winColor struct { |
|
code uint16 |
|
drawType drawType |
|
} |
|
|
|
var colorMap = map[string]winColor{ |
|
ansiForegroundBlack: {0, foreground}, |
|
ansiForegroundRed: {foregroundRed, foreground}, |
|
ansiForegroundGreen: {foregroundGreen, foreground}, |
|
ansiForegroundYellow: {foregroundRed | foregroundGreen, foreground}, |
|
ansiForegroundBlue: {foregroundBlue, foreground}, |
|
ansiForegroundMagenta: {foregroundRed | foregroundBlue, foreground}, |
|
ansiForegroundCyan: {foregroundGreen | foregroundBlue, foreground}, |
|
ansiForegroundWhite: {foregroundRed | foregroundGreen | foregroundBlue, foreground}, |
|
ansiForegroundDefault: {foregroundRed | foregroundGreen | foregroundBlue, foreground}, |
|
|
|
ansiBackgroundBlack: {0, background}, |
|
ansiBackgroundRed: {backgroundRed, background}, |
|
ansiBackgroundGreen: {backgroundGreen, background}, |
|
ansiBackgroundYellow: {backgroundRed | backgroundGreen, background}, |
|
ansiBackgroundBlue: {backgroundBlue, background}, |
|
ansiBackgroundMagenta: {backgroundRed | backgroundBlue, background}, |
|
ansiBackgroundCyan: {backgroundGreen | backgroundBlue, background}, |
|
ansiBackgroundWhite: {backgroundRed | backgroundGreen | backgroundBlue, background}, |
|
ansiBackgroundDefault: {0, background}, |
|
|
|
ansiLightForegroundGray: {foregroundIntensity, foreground}, |
|
ansiLightForegroundRed: {foregroundIntensity | foregroundRed, foreground}, |
|
ansiLightForegroundGreen: {foregroundIntensity | foregroundGreen, foreground}, |
|
ansiLightForegroundYellow: {foregroundIntensity | foregroundRed | foregroundGreen, foreground}, |
|
ansiLightForegroundBlue: {foregroundIntensity | foregroundBlue, foreground}, |
|
ansiLightForegroundMagenta: {foregroundIntensity | foregroundRed | foregroundBlue, foreground}, |
|
ansiLightForegroundCyan: {foregroundIntensity | foregroundGreen | foregroundBlue, foreground}, |
|
ansiLightForegroundWhite: {foregroundIntensity | foregroundRed | foregroundGreen | foregroundBlue, foreground}, |
|
|
|
ansiLightBackgroundGray: {backgroundIntensity, background}, |
|
ansiLightBackgroundRed: {backgroundIntensity | backgroundRed, background}, |
|
ansiLightBackgroundGreen: {backgroundIntensity | backgroundGreen, background}, |
|
ansiLightBackgroundYellow: {backgroundIntensity | backgroundRed | backgroundGreen, background}, |
|
ansiLightBackgroundBlue: {backgroundIntensity | backgroundBlue, background}, |
|
ansiLightBackgroundMagenta: {backgroundIntensity | backgroundRed | backgroundBlue, background}, |
|
ansiLightBackgroundCyan: {backgroundIntensity | backgroundGreen | backgroundBlue, background}, |
|
ansiLightBackgroundWhite: {backgroundIntensity | backgroundRed | backgroundGreen | backgroundBlue, background}, |
|
} |
|
|
|
var ( |
|
kernel32 = syscall.NewLazyDLL("kernel32.dll") |
|
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") |
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") |
|
defaultAttr *textAttributes |
|
) |
|
|
|
func init() { |
|
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout)) |
|
if screenInfo != nil { |
|
colorMap[ansiForegroundDefault] = winColor{ |
|
screenInfo.WAttributes & (foregroundRed | foregroundGreen | foregroundBlue), |
|
foreground, |
|
} |
|
colorMap[ansiBackgroundDefault] = winColor{ |
|
screenInfo.WAttributes & (backgroundRed | backgroundGreen | backgroundBlue), |
|
background, |
|
} |
|
defaultAttr = convertTextAttr(screenInfo.WAttributes) |
|
} |
|
} |
|
|
|
type coord struct { |
|
X, Y int16 |
|
} |
|
|
|
type smallRect struct { |
|
Left, Top, Right, Bottom int16 |
|
} |
|
|
|
type consoleScreenBufferInfo struct { |
|
DwSize coord |
|
DwCursorPosition coord |
|
WAttributes uint16 |
|
SrWindow smallRect |
|
DwMaximumWindowSize coord |
|
} |
|
|
|
func getConsoleScreenBufferInfo(hConsoleOutput uintptr) *consoleScreenBufferInfo { |
|
var csbi consoleScreenBufferInfo |
|
ret, _, _ := procGetConsoleScreenBufferInfo.Call( |
|
hConsoleOutput, |
|
uintptr(unsafe.Pointer(&csbi))) |
|
if ret == 0 { |
|
return nil |
|
} |
|
return &csbi |
|
} |
|
|
|
func setConsoleTextAttribute(hConsoleOutput uintptr, wAttributes uint16) bool { |
|
ret, _, _ := procSetConsoleTextAttribute.Call( |
|
hConsoleOutput, |
|
uintptr(wAttributes)) |
|
return ret != 0 |
|
} |
|
|
|
type textAttributes struct { |
|
foregroundColor uint16 |
|
backgroundColor uint16 |
|
foregroundIntensity uint16 |
|
backgroundIntensity uint16 |
|
underscore uint16 |
|
otherAttributes uint16 |
|
} |
|
|
|
func convertTextAttr(winAttr uint16) *textAttributes { |
|
fgColor := winAttr & (foregroundRed | foregroundGreen | foregroundBlue) |
|
bgColor := winAttr & (backgroundRed | backgroundGreen | backgroundBlue) |
|
fgIntensity := winAttr & foregroundIntensity |
|
bgIntensity := winAttr & backgroundIntensity |
|
underline := winAttr & underscore |
|
otherAttributes := winAttr &^ (foregroundMask | backgroundMask | underscore) |
|
return &textAttributes{fgColor, bgColor, fgIntensity, bgIntensity, underline, otherAttributes} |
|
} |
|
|
|
func convertWinAttr(textAttr *textAttributes) uint16 { |
|
var winAttr uint16 |
|
winAttr |= textAttr.foregroundColor |
|
winAttr |= textAttr.backgroundColor |
|
winAttr |= textAttr.foregroundIntensity |
|
winAttr |= textAttr.backgroundIntensity |
|
winAttr |= textAttr.underscore |
|
winAttr |= textAttr.otherAttributes |
|
return winAttr |
|
} |
|
|
|
func changeColor(param []byte) parseResult { |
|
screenInfo := getConsoleScreenBufferInfo(uintptr(syscall.Stdout)) |
|
if screenInfo == nil { |
|
return noConsole |
|
} |
|
|
|
winAttr := convertTextAttr(screenInfo.WAttributes) |
|
strParam := string(param) |
|
if len(strParam) <= 0 { |
|
strParam = "0" |
|
} |
|
csiParam := strings.Split(strParam, string(separatorChar)) |
|
for _, p := range csiParam { |
|
c, ok := colorMap[p] |
|
switch { |
|
case !ok: |
|
switch p { |
|
case ansiReset: |
|
winAttr.foregroundColor = defaultAttr.foregroundColor |
|
winAttr.backgroundColor = defaultAttr.backgroundColor |
|
winAttr.foregroundIntensity = defaultAttr.foregroundIntensity |
|
winAttr.backgroundIntensity = defaultAttr.backgroundIntensity |
|
winAttr.underscore = 0 |
|
winAttr.otherAttributes = 0 |
|
case ansiIntensityOn: |
|
winAttr.foregroundIntensity = foregroundIntensity |
|
case ansiIntensityOff: |
|
winAttr.foregroundIntensity = 0 |
|
case ansiUnderlineOn: |
|
winAttr.underscore = underscore |
|
case ansiUnderlineOff: |
|
winAttr.underscore = 0 |
|
case ansiBlinkOn: |
|
winAttr.backgroundIntensity = backgroundIntensity |
|
case ansiBlinkOff: |
|
winAttr.backgroundIntensity = 0 |
|
default: |
|
// unknown code |
|
} |
|
case c.drawType == foreground: |
|
winAttr.foregroundColor = c.code |
|
case c.drawType == background: |
|
winAttr.backgroundColor = c.code |
|
} |
|
} |
|
winTextAttribute := convertWinAttr(winAttr) |
|
setConsoleTextAttribute(uintptr(syscall.Stdout), winTextAttribute) |
|
|
|
return changedColor |
|
} |
|
|
|
func parseEscapeSequence(command byte, param []byte) parseResult { |
|
if defaultAttr == nil { |
|
return noConsole |
|
} |
|
|
|
switch command { |
|
case sgrCode: |
|
return changeColor(param) |
|
default: |
|
return unknown |
|
} |
|
} |
|
|
|
func (cw *colorWriter) flushBuffer() (int, error) { |
|
return cw.flushTo(cw.w) |
|
} |
|
|
|
func (cw *colorWriter) resetBuffer() (int, error) { |
|
return cw.flushTo(nil) |
|
} |
|
|
|
func (cw *colorWriter) flushTo(w io.Writer) (int, error) { |
|
var n1, n2 int |
|
var err error |
|
|
|
startBytes := cw.paramStartBuf.Bytes() |
|
cw.paramStartBuf.Reset() |
|
if w != nil { |
|
n1, err = cw.w.Write(startBytes) |
|
if err != nil { |
|
return n1, err |
|
} |
|
} else { |
|
n1 = len(startBytes) |
|
} |
|
paramBytes := cw.paramBuf.Bytes() |
|
cw.paramBuf.Reset() |
|
if w != nil { |
|
n2, err = cw.w.Write(paramBytes) |
|
if err != nil { |
|
return n1 + n2, err |
|
} |
|
} else { |
|
n2 = len(paramBytes) |
|
} |
|
return n1 + n2, nil |
|
} |
|
|
|
func isParameterChar(b byte) bool { |
|
return ('0' <= b && b <= '9') || b == separatorChar |
|
} |
|
|
|
func (cw *colorWriter) Write(p []byte) (int, error) { |
|
var r, nw, first, last int |
|
if cw.mode != DiscardNonColorEscSeq { |
|
cw.state = outsideCsiCode |
|
cw.resetBuffer() |
|
} |
|
|
|
var err error |
|
for i, ch := range p { |
|
switch cw.state { |
|
case outsideCsiCode: |
|
if ch == firstCsiChar { |
|
cw.paramStartBuf.WriteByte(ch) |
|
cw.state = firstCsiCode |
|
} |
|
case firstCsiCode: |
|
switch ch { |
|
case firstCsiChar: |
|
cw.paramStartBuf.WriteByte(ch) |
|
break |
|
case secondeCsiChar: |
|
cw.paramStartBuf.WriteByte(ch) |
|
cw.state = secondCsiCode |
|
last = i - 1 |
|
default: |
|
cw.resetBuffer() |
|
cw.state = outsideCsiCode |
|
} |
|
case secondCsiCode: |
|
if isParameterChar(ch) { |
|
cw.paramBuf.WriteByte(ch) |
|
} else { |
|
nw, err = cw.w.Write(p[first:last]) |
|
r += nw |
|
if err != nil { |
|
return r, err |
|
} |
|
first = i + 1 |
|
result := parseEscapeSequence(ch, cw.paramBuf.Bytes()) |
|
if result == noConsole || (cw.mode == OutputNonColorEscSeq && result == unknown) { |
|
cw.paramBuf.WriteByte(ch) |
|
nw, err := cw.flushBuffer() |
|
if err != nil { |
|
return r, err |
|
} |
|
r += nw |
|
} else { |
|
n, _ := cw.resetBuffer() |
|
// Add one more to the size of the buffer for the last ch |
|
r += n + 1 |
|
} |
|
|
|
cw.state = outsideCsiCode |
|
} |
|
default: |
|
cw.state = outsideCsiCode |
|
} |
|
} |
|
|
|
if cw.mode != DiscardNonColorEscSeq || cw.state == outsideCsiCode { |
|
nw, err = cw.w.Write(p[first:]) |
|
r += nw |
|
} |
|
|
|
return r, err |
|
}
|
|
|