mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-19 09:28:33 +00:00
Fix terminal.nim to be GCSAFE. (#8296)
* Also fix deprecated callsite().
This commit is contained in:
committed by
Andreas Rumpf
parent
ba1f036a56
commit
3fec2ba5e5
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user