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