Fix terminal.nim to be GCSAFE. (#8296)

* Also fix deprecated callsite().
This commit is contained in:
Eugene Kabanov
2018-07-12 19:06:15 +03:00
committed by Andreas Rumpf
parent ba1f036a56
commit 3fec2ba5e5

View File

@@ -19,22 +19,30 @@
import macros
import strformat
from strutils import toLowerAscii
import colors
import colors, tables
const
hasThreadSupport = compileOption("threads")
when defined(windows):
import winlean
when not hasThreadSupport:
import tables
var
colorsFGCache = initTable[Color, string]()
colorsBGCache = initTable[Color, string]()
styleCache = initTable[int, string]()
type
PTerminal = ref object
trueColorIsSupported: bool
trueColorIsEnabled: bool
fgSetColor: bool
when defined(windows):
hStdout: Handle
hStderr: Handle
oldStdoutAttr: int16
oldStderrAttr: int16
var
trueColorIsSupported: bool
trueColorIsEnabled: bool
fgSetColor: bool
var gTerm {.threadvar.}: PTerminal
proc newTerminal(): PTerminal
proc getTerminal(): PTerminal {.inline.} =
if isNil(gTerm):
gTerm = newTerminal()
result = gTerm
const
fgPrefix = "\x1b[38;2;"
@@ -156,23 +164,6 @@ when defined(windows):
proc setConsoleMode(hConsoleHandle: Handle, dwMode: DWORD): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "SetConsoleMode".}
var
hStdout: Handle # = createFile("CONOUT$", GENERIC_WRITE, 0, nil,
# OPEN_ALWAYS, 0, 0)
hStderr: Handle
block:
var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE)
if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(),
addr(hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
when defined(consoleapp):
raiseOSError(osLastError())
var hStderrTemp = getStdHandle(STD_ERROR_HANDLE)
if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(),
addr(hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
when defined(consoleapp):
raiseOSError(osLastError())
proc getCursorPos(h: Handle): tuple [x,y: int] =
var c: CONSOLESCREENBUFFERINFO
if getConsoleScreenBufferInfo(h, addr(c)) == 0:
@@ -193,12 +184,23 @@ when defined(windows):
return c.wAttributes
return 0x70'i16 # ERROR: return white background, black text
var
oldStdoutAttr = getAttributes(hStdout)
oldStderrAttr = getAttributes(hStderr)
proc initTerminal(term: PTerminal) =
var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE)
if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(),
addr(term.hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
when defined(consoleapp):
raiseOSError(osLastError())
var hStderrTemp = getStdHandle(STD_ERROR_HANDLE)
if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(),
addr(term.hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
when defined(consoleapp):
raiseOSError(osLastError())
term.oldStdoutAttr = getAttributes(term.hStdout)
term.oldStderrAttr = getAttributes(term.hStderr)
template conHandle(f: File): Handle =
if f == stderr: hStderr else: hStdout
let term = getTerminal()
if f == stderr: term.hStderr else: term.hStdout
else:
import termios, posix, os, parseutils
@@ -459,10 +461,11 @@ proc eraseScreen*(f: File) =
proc resetAttributes*(f: File) =
## Resets all attributes.
when defined(windows):
let term = getTerminal()
if f == stderr:
discard setConsoleTextAttribute(hStderr, oldStderrAttr)
discard setConsoleTextAttribute(term.hStderr, term.oldStderrAttr)
else:
discard setConsoleTextAttribute(hStdout, oldStdoutAttr)
discard setConsoleTextAttribute(term.hStdout, term.oldStdoutAttr)
else:
f.write(ansiResetCode)
@@ -487,14 +490,7 @@ when not defined(windows):
gBG {.threadvar.}: int
proc ansiStyleCode*(style: int): string =
when hasThreadSupport:
result = fmt"{stylePrefix}{style}m"
else:
if styleCache.hasKey(style):
result = styleCache[style]
else:
result = fmt"{stylePrefix}{style}m"
styleCache[style] = result
result = fmt"{stylePrefix}{style}m"
template ansiStyleCode*(style: Style): string =
ansiStyleCode(style.int)
@@ -521,10 +517,11 @@ proc setStyle*(f: File, style: set[Style]) =
proc writeStyled*(txt: string, style: set[Style] = {styleBright}) =
## Writes the text `txt` in a given `style` to stdout.
when defined(windows):
var old = getAttributes(hStdout)
let term = getTerminal()
var old = getAttributes(term.hStdout)
stdout.setStyle(style)
stdout.write(txt)
discard setConsoleTextAttribute(hStdout, old)
discard setConsoleTextAttribute(term.hStdout, old)
else:
stdout.setStyle(style)
stdout.write(txt)
@@ -633,32 +630,16 @@ template ansiForegroundColorCode*(fg: static[ForegroundColor],
ansiStyleCode(fg.int + bright.int * 60)
proc ansiForegroundColorCode*(color: Color): string =
when hasThreadSupport:
let rgb = extractRGB(color)
result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
else:
if colorsFGCache.hasKey(color):
result = colorsFGCache[color]
else:
let rgb = extractRGB(color)
result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
colorsFGCache[color] = result
let rgb = extractRGB(color)
result = fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
template ansiForegroundColorCode*(color: static[Color]): string =
const rgb = extractRGB(color)
(static(fmt"{fgPrefix}{rgb.r};{rgb.g};{rgb.b}m"))
proc ansiBackgroundColorCode*(color: Color): string =
when hasThreadSupport:
let rgb = extractRGB(color)
result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
else:
if colorsBGCache.hasKey(color):
result = colorsBGCache[color]
else:
let rgb = extractRGB(color)
result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
colorsFGCache[color] = result
let rgb = extractRGB(color)
result = fmt"{bgPrefix}{rgb.r};{rgb.g};{rgb.b}m"
template ansiBackgroundColorCode*(color: static[Color]): string =
const rgb = extractRGB(color)
@@ -666,16 +647,17 @@ template ansiBackgroundColorCode*(color: static[Color]): string =
proc setForegroundColor*(f: File, color: Color) =
## Sets the terminal's foreground true color.
if trueColorIsEnabled:
if getTerminal().trueColorIsEnabled:
f.write(ansiForegroundColorCode(color))
proc setBackgroundColor*(f: File, color: Color) =
## Sets the terminal's background true color.
if trueColorIsEnabled:
if getTerminal().trueColorIsEnabled:
f.write(ansiBackgroundColorCode(color))
proc setTrueColor(f: File, color: Color) =
if fgSetColor:
let term = getTerminal()
if term.fgSetColor:
setForegroundColor(f, color)
else:
setBackgroundColor(f, color)
@@ -727,11 +709,10 @@ macro styledWrite*(f: File, m: varargs[typed]): untyped =
## stdout.styledWrite(fgRed, "red text ")
## stdout.styledWrite(fgGreen, "green text")
##
let m = callsite()
var reset = false
result = newNimNode(nnkStmtList)
for i in countup(2, m.len - 1):
for i in countup(0, m.len - 1):
let item = m[i]
case item.kind
of nnkStrLit..nnkTripleStrLit:
@@ -872,54 +853,61 @@ proc resetAttributes*() {.noconv.} =
proc isTrueColorSupported*(): bool =
## Returns true if a terminal supports true color.
return trueColorIsSupported
return getTerminal().trueColorIsSupported
when defined(windows):
import os
proc enableTrueColors*() =
## Enable true color.
var term = getTerminal()
when defined(windows):
var
ver: OSVERSIONINFO
ver.dwOSVersionInfoSize = sizeof(ver).DWORD
let res = getVersionExW(addr ver)
if res == 0:
trueColorIsSupported = false
term.trueColorIsSupported = false
else:
trueColorIsSupported = ver.dwMajorVersion > 10 or
term.trueColorIsSupported = ver.dwMajorVersion > 10 or
(ver.dwMajorVersion == 10 and (ver.dwMinorVersion > 0 or
(ver.dwMinorVersion == 0 and ver.dwBuildNumber >= 10586)))
if not trueColorIsSupported:
trueColorIsSupported = getEnv("ANSICON_DEF").len > 0
if not term.trueColorIsSupported:
term.trueColorIsSupported = getEnv("ANSICON_DEF").len > 0
if trueColorIsSupported:
if term.trueColorIsSupported:
if getEnv("ANSICON_DEF").len == 0:
var mode: DWORD = 0
if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0:
mode = mode or ENABLE_VIRTUAL_TERMINAL_PROCESSING
if setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode) != 0:
trueColorIsEnabled = true
term.trueColorIsEnabled = true
else:
trueColorIsEnabled = false
term.trueColorIsEnabled = false
else:
trueColorIsEnabled = true
term.trueColorIsEnabled = true
else:
trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"]
trueColorIsEnabled = trueColorIsSupported
term.trueColorIsSupported = string(getEnv("COLORTERM")).toLowerAscii() in ["truecolor", "24bit"]
term.trueColorIsEnabled = term.trueColorIsSupported
proc disableTrueColors*() =
## Disable true color.
var term = getTerminal()
when defined(windows):
if trueColorIsSupported:
if term.trueColorIsSupported:
if getEnv("ANSICON_DEF").len == 0:
var mode: DWORD = 0
if getConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), addr(mode)) != 0:
mode = mode and not ENABLE_VIRTUAL_TERMINAL_PROCESSING
discard setConsoleMode(getStdHandle(STD_OUTPUT_HANDLE), mode)
trueColorIsEnabled = false
term.trueColorIsEnabled = false
else:
trueColorIsEnabled = false
term.trueColorIsEnabled = false
proc newTerminal(): PTerminal =
new result
when defined(windows):
initTerminal(result)
when not defined(testing) and isMainModule:
assert ansiStyleCode(styleBright) == "\e[1m"