terminal: Support both styled stdout and stderr
This is important if we want to write styled diagnostics to stderr, eg. some tool outputting results to stdout, but writing styled error messages to stderr. Previously this module was assuming we are writing only to stdout. Now all module procs take file handle as first argument. Wrappers assuming stdout are provided for backwards compatibility. The new terminal.styledWriteLine(f, args) is provided and documented as counterpart for unstyled plain writeLine(f, args).
This commit is contained in:
parent
9ef50717fa
commit
2bc6acc808
1 changed files with 188 additions and 117 deletions
|
|
@ -79,33 +79,49 @@ when defined(windows):
|
||||||
stdcall, dynlib: "kernel32", importc: "SetConsoleTextAttribute".}
|
stdcall, dynlib: "kernel32", importc: "SetConsoleTextAttribute".}
|
||||||
|
|
||||||
var
|
var
|
||||||
hStdout: Handle
|
hStdout: Handle # = createFile("CONOUT$", GENERIC_WRITE, 0, nil,
|
||||||
# = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0)
|
# OPEN_ALWAYS, 0, 0)
|
||||||
|
hStderr: Handle
|
||||||
|
|
||||||
block:
|
block:
|
||||||
var hTemp = getStdHandle(STD_OUTPUT_HANDLE)
|
var hStdoutTemp = getStdHandle(STD_OUTPUT_HANDLE)
|
||||||
if duplicateHandle(getCurrentProcess(), hTemp, getCurrentProcess(),
|
if duplicateHandle(getCurrentProcess(), hStdoutTemp, getCurrentProcess(),
|
||||||
addr(hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
|
addr(hStdout), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
|
var hStderrTemp = getStdHandle(STD_ERROR_HANDLE)
|
||||||
|
if duplicateHandle(getCurrentProcess(), hStderrTemp, getCurrentProcess(),
|
||||||
|
addr(hStderr), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
|
||||||
|
raiseOSError(osLastError())
|
||||||
|
|
||||||
proc getCursorPos(): tuple [x,y: int] =
|
proc getCursorPos(h: Handle): tuple [x,y: int] =
|
||||||
var c: CONSOLESCREENBUFFERINFO
|
var c: CONSOLESCREENBUFFERINFO
|
||||||
if getConsoleScreenBufferInfo(hStdout, addr(c)) == 0:
|
if getConsoleScreenBufferInfo(h, addr(c)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
return (int(c.dwCursorPosition.X), int(c.dwCursorPosition.Y))
|
return (int(c.dwCursorPosition.X), int(c.dwCursorPosition.Y))
|
||||||
|
|
||||||
proc getAttributes(): int16 =
|
proc setCursorPos(h: Handle, x, y: int) =
|
||||||
|
var c: COORD
|
||||||
|
c.X = int16(x)
|
||||||
|
c.Y = int16(y)
|
||||||
|
if setConsoleCursorPosition(h, c) == 0:
|
||||||
|
raiseOSError(osLastError())
|
||||||
|
|
||||||
|
proc getAttributes(h: Handle): int16 =
|
||||||
var c: CONSOLESCREENBUFFERINFO
|
var c: CONSOLESCREENBUFFERINFO
|
||||||
# workaround Windows bugs: try several times
|
# workaround Windows bugs: try several times
|
||||||
if getConsoleScreenBufferInfo(hStdout, addr(c)) != 0:
|
if getConsoleScreenBufferInfo(h, addr(c)) != 0:
|
||||||
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
|
var
|
||||||
oldAttr = getAttributes()
|
oldStdoutAttr = getAttributes(hStdout)
|
||||||
|
oldStderrAttr = getAttributes(hStderr)
|
||||||
|
|
||||||
|
template conHandle(f: File): Handle =
|
||||||
|
if f == stderr: hStderr else: hStdout
|
||||||
|
|
||||||
else:
|
else:
|
||||||
import termios, unsigned
|
import termios
|
||||||
|
|
||||||
proc setRaw(fd: FileHandle, time: cint = TCSAFLUSH) =
|
proc setRaw(fd: FileHandle, time: cint = TCSAFLUSH) =
|
||||||
var mode: Termios
|
var mode: Termios
|
||||||
|
|
@ -119,164 +135,173 @@ else:
|
||||||
mode.c_cc[VTIME] = 0.cuchar
|
mode.c_cc[VTIME] = 0.cuchar
|
||||||
discard fd.tcsetattr(time, addr mode)
|
discard fd.tcsetattr(time, addr mode)
|
||||||
|
|
||||||
proc setCursorPos*(x, y: int) =
|
proc setCursorPos*(f: File, x, y: int) =
|
||||||
## sets the terminal's cursor to the (x,y) position. (0,0) is the
|
## Sets the terminal's cursor to the (x,y) position.
|
||||||
## upper left of the screen.
|
## (0,0) is the upper left of the screen.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var c: COORD
|
let h = conHandle(f)
|
||||||
c.X = int16(x)
|
setCursorPos(h, x, y)
|
||||||
c.Y = int16(y)
|
|
||||||
if setConsoleCursorPosition(hStdout, c) == 0: raiseOSError(osLastError())
|
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[" & $y & ';' & $x & 'f')
|
f.write("\e[" & $y & ';' & $x & 'f')
|
||||||
|
|
||||||
proc setCursorXPos*(x: int) =
|
proc setCursorXPos*(f: File, x: int) =
|
||||||
## sets the terminal's cursor to the x position. The y position is
|
## Sets the terminal's cursor to the x position.
|
||||||
## not changed.
|
## The y position is not changed.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
|
let h = conHandle(f)
|
||||||
var scrbuf: CONSOLESCREENBUFFERINFO
|
var scrbuf: CONSOLESCREENBUFFERINFO
|
||||||
if getConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0:
|
if getConsoleScreenBufferInfo(h, addr(scrbuf)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
var origin = scrbuf.dwCursorPosition
|
var origin = scrbuf.dwCursorPosition
|
||||||
origin.X = int16(x)
|
origin.X = int16(x)
|
||||||
if setConsoleCursorPosition(hStdout, origin) == 0:
|
if setConsoleCursorPosition(h, origin) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[" & $x & 'G')
|
f.write("\e[" & $x & 'G')
|
||||||
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
proc setCursorYPos*(y: int) =
|
proc setCursorYPos*(f: File, y: int) =
|
||||||
## sets the terminal's cursor to the y position. The x position is
|
## Sets the terminal's cursor to the y position.
|
||||||
## not changed. **Warning**: This is not supported on UNIX!
|
## The x position is not changed.
|
||||||
|
## **Warning**: This is not supported on UNIX!
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
|
let h = conHandle(f)
|
||||||
var scrbuf: CONSOLESCREENBUFFERINFO
|
var scrbuf: CONSOLESCREENBUFFERINFO
|
||||||
if getConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0:
|
if getConsoleScreenBufferInfo(h, addr(scrbuf)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
var origin = scrbuf.dwCursorPosition
|
var origin = scrbuf.dwCursorPosition
|
||||||
origin.Y = int16(y)
|
origin.Y = int16(y)
|
||||||
if setConsoleCursorPosition(hStdout, origin) == 0:
|
if setConsoleCursorPosition(h, origin) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc cursorUp*(count=1) =
|
proc cursorUp*(f: File, count=1) =
|
||||||
## Moves the cursor up by `count` rows.
|
## Moves the cursor up by `count` rows.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var p = getCursorPos()
|
let h = conHandle(f)
|
||||||
|
var p = getCursorPos(h)
|
||||||
dec(p.y, count)
|
dec(p.y, count)
|
||||||
setCursorPos(p.x, p.y)
|
setCursorPos(h, p.x, p.y)
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[" & $count & 'A')
|
f.write("\e[" & $count & 'A')
|
||||||
|
|
||||||
proc cursorDown*(count=1) =
|
proc cursorDown*(f: File, count=1) =
|
||||||
## Moves the cursor down by `count` rows.
|
## Moves the cursor down by `count` rows.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var p = getCursorPos()
|
let h = conHandle(f)
|
||||||
|
var p = getCursorPos(h)
|
||||||
inc(p.y, count)
|
inc(p.y, count)
|
||||||
setCursorPos(p.x, p.y)
|
setCursorPos(h, p.x, p.y)
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[" & $count & 'B')
|
f.write("\e[" & $count & 'B')
|
||||||
|
|
||||||
proc cursorForward*(count=1) =
|
proc cursorForward*(f: File, count=1) =
|
||||||
## Moves the cursor forward by `count` columns.
|
## Moves the cursor forward by `count` columns.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var p = getCursorPos()
|
let h = conHandle(f)
|
||||||
|
var p = getCursorPos(h)
|
||||||
inc(p.x, count)
|
inc(p.x, count)
|
||||||
setCursorPos(p.x, p.y)
|
setCursorPos(h, p.x, p.y)
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[" & $count & 'C')
|
f.write("\e[" & $count & 'C')
|
||||||
|
|
||||||
proc cursorBackward*(count=1) =
|
proc cursorBackward*(f: File, count=1) =
|
||||||
## Moves the cursor backward by `count` columns.
|
## Moves the cursor backward by `count` columns.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var p = getCursorPos()
|
let h = conHandle(f)
|
||||||
|
var p = getCursorPos(h)
|
||||||
dec(p.x, count)
|
dec(p.x, count)
|
||||||
setCursorPos(p.x, p.y)
|
setCursorPos(h, p.x, p.y)
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[" & $count & 'D')
|
f.write("\e[" & $count & 'D')
|
||||||
|
|
||||||
when true:
|
when true:
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
proc eraseLineEnd* =
|
proc eraseLineEnd*(f: File) =
|
||||||
## Erases from the current cursor position to the end of the current line.
|
## Erases from the current cursor position to the end of the current line.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[K")
|
f.write("\e[K")
|
||||||
|
|
||||||
proc eraseLineStart* =
|
proc eraseLineStart*(f: File) =
|
||||||
## Erases from the current cursor position to the start of the current line.
|
## Erases from the current cursor position to the start of the current line.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[1K")
|
f.write("\e[1K")
|
||||||
|
|
||||||
proc eraseDown* =
|
proc eraseDown*(f: File) =
|
||||||
## Erases the screen from the current line down to the bottom of the screen.
|
## Erases the screen from the current line down to the bottom of the screen.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[J")
|
f.write("\e[J")
|
||||||
|
|
||||||
proc eraseUp* =
|
proc eraseUp*(f: File) =
|
||||||
## Erases the screen from the current line up to the top of the screen.
|
## Erases the screen from the current line up to the top of the screen.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[1J")
|
f.write("\e[1J")
|
||||||
|
|
||||||
proc eraseLine* =
|
proc eraseLine*(f: File) =
|
||||||
## Erases the entire current line.
|
## Erases the entire current line.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
|
let h = conHandle(f)
|
||||||
var scrbuf: CONSOLESCREENBUFFERINFO
|
var scrbuf: CONSOLESCREENBUFFERINFO
|
||||||
var numwrote: DWORD
|
var numwrote: DWORD
|
||||||
if getConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0:
|
if getConsoleScreenBufferInfo(h, addr(scrbuf)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
var origin = scrbuf.dwCursorPosition
|
var origin = scrbuf.dwCursorPosition
|
||||||
origin.X = 0'i16
|
origin.X = 0'i16
|
||||||
if setConsoleCursorPosition(hStdout, origin) == 0:
|
if setConsoleCursorPosition(h, origin) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
var ht = scrbuf.dwSize.Y - origin.Y
|
var ht = scrbuf.dwSize.Y - origin.Y
|
||||||
var wt = scrbuf.dwSize.X - origin.X
|
var wt = scrbuf.dwSize.X - origin.X
|
||||||
if fillConsoleOutputCharacter(hStdout,' ', ht*wt,
|
if fillConsoleOutputCharacter(h, ' ', ht*wt,
|
||||||
origin, addr(numwrote)) == 0:
|
origin, addr(numwrote)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
if fillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt,
|
if fillConsoleOutputAttribute(h, scrbuf.wAttributes, ht * wt,
|
||||||
scrbuf.dwCursorPosition, addr(numwrote)) == 0:
|
scrbuf.dwCursorPosition, addr(numwrote)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[2K")
|
f.write("\e[2K")
|
||||||
setCursorXPos(0)
|
setCursorXPos(f, 0)
|
||||||
|
|
||||||
proc eraseScreen* =
|
proc eraseScreen*(f: File) =
|
||||||
## Erases the screen with the background colour and moves the cursor to home.
|
## Erases the screen with the background colour and moves the cursor to home.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
|
let h = conHandle(f)
|
||||||
var scrbuf: CONSOLESCREENBUFFERINFO
|
var scrbuf: CONSOLESCREENBUFFERINFO
|
||||||
var numwrote: DWORD
|
var numwrote: DWORD
|
||||||
var origin: COORD # is inititalized to 0, 0
|
var origin: COORD # is inititalized to 0, 0
|
||||||
|
|
||||||
if getConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0:
|
if getConsoleScreenBufferInfo(h, addr(scrbuf)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
let numChars = int32(scrbuf.dwSize.X)*int32(scrbuf.dwSize.Y)
|
let numChars = int32(scrbuf.dwSize.X)*int32(scrbuf.dwSize.Y)
|
||||||
|
|
||||||
if fillConsoleOutputCharacter(hStdout, ' ', numChars,
|
if fillConsoleOutputCharacter(h, ' ', numChars,
|
||||||
origin, addr(numwrote)) == 0:
|
origin, addr(numwrote)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
if fillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, numChars,
|
if fillConsoleOutputAttribute(h, scrbuf.wAttributes, numChars,
|
||||||
origin, addr(numwrote)) == 0:
|
origin, addr(numwrote)) == 0:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
setCursorXPos(0)
|
setCursorXPos(f, 0)
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[2J")
|
f.write("\e[2J")
|
||||||
|
|
||||||
proc resetAttributes* {.noconv.} =
|
proc resetAttributes*(f: File) =
|
||||||
## resets all attributes; it is advisable to register this as a quit proc
|
## Resets all attributes.
|
||||||
## with ``system.addQuitProc(resetAttributes)``.
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
discard setConsoleTextAttribute(hStdout, oldAttr)
|
if f == stderr:
|
||||||
|
discard setConsoleTextAttribute(hStderr, oldStderrAttr)
|
||||||
|
else:
|
||||||
|
discard setConsoleTextAttribute(hStdout, oldStdoutAttr)
|
||||||
else:
|
else:
|
||||||
stdout.write("\e[0m")
|
f.write("\e[0m")
|
||||||
|
|
||||||
type
|
type
|
||||||
Style* = enum ## different styles for text output
|
Style* = enum ## different styles for text output
|
||||||
|
|
@ -296,30 +321,31 @@ when not defined(windows):
|
||||||
gFG = 0
|
gFG = 0
|
||||||
gBG = 0
|
gBG = 0
|
||||||
|
|
||||||
proc setStyle*(style: set[Style]) =
|
proc setStyle*(f: File, style: set[Style]) =
|
||||||
## sets the terminal style
|
## Sets the terminal style.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
|
let h = conHandle(f)
|
||||||
var a = 0'i16
|
var a = 0'i16
|
||||||
if styleBright in style: a = a or int16(FOREGROUND_INTENSITY)
|
if styleBright in style: a = a or int16(FOREGROUND_INTENSITY)
|
||||||
if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY)
|
if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY)
|
||||||
if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO
|
if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO
|
||||||
if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE
|
if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE
|
||||||
discard setConsoleTextAttribute(hStdout, a)
|
discard setConsoleTextAttribute(h, a)
|
||||||
else:
|
else:
|
||||||
for s in items(style):
|
for s in items(style):
|
||||||
stdout.write("\e[" & $ord(s) & 'm')
|
f.write("\e[" & $ord(s) & 'm')
|
||||||
|
|
||||||
proc writeStyled*(txt: string, style: set[Style] = {styleBright}) =
|
proc writeStyled*(txt: string, style: set[Style] = {styleBright}) =
|
||||||
## writes the text `txt` in a given `style`.
|
## Writes the text `txt` in a given `style` to stdout.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var old = getAttributes()
|
var old = getAttributes(hStdout)
|
||||||
setStyle(style)
|
stdout.setStyle(style)
|
||||||
stdout.write(txt)
|
stdout.write(txt)
|
||||||
discard setConsoleTextAttribute(hStdout, old)
|
discard setConsoleTextAttribute(hStdout, old)
|
||||||
else:
|
else:
|
||||||
setStyle(style)
|
stdout.setStyle(style)
|
||||||
stdout.write(txt)
|
stdout.write(txt)
|
||||||
resetAttributes()
|
stdout.resetAttributes()
|
||||||
if gFG != 0:
|
if gFG != 0:
|
||||||
stdout.write("\e[" & $ord(gFG) & 'm')
|
stdout.write("\e[" & $ord(gFG) & 'm')
|
||||||
if gBG != 0:
|
if gBG != 0:
|
||||||
|
|
@ -349,10 +375,11 @@ type
|
||||||
{.deprecated: [TForegroundColor: ForegroundColor,
|
{.deprecated: [TForegroundColor: ForegroundColor,
|
||||||
TBackgroundColor: BackgroundColor].}
|
TBackgroundColor: BackgroundColor].}
|
||||||
|
|
||||||
proc setForegroundColor*(fg: ForegroundColor, bright=false) =
|
proc setForegroundColor*(f: File, fg: ForegroundColor, bright=false) =
|
||||||
## sets the terminal's foreground color
|
## Sets the terminal's foreground color.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var old = getAttributes() and not 0x0007
|
let h = conHandle(f)
|
||||||
|
var old = getAttributes(h) and not 0x0007
|
||||||
if bright:
|
if bright:
|
||||||
old = old or FOREGROUND_INTENSITY
|
old = old or FOREGROUND_INTENSITY
|
||||||
const lookup: array [ForegroundColor, int] = [
|
const lookup: array [ForegroundColor, int] = [
|
||||||
|
|
@ -364,16 +391,17 @@ proc setForegroundColor*(fg: ForegroundColor, bright=false) =
|
||||||
(FOREGROUND_RED or FOREGROUND_BLUE),
|
(FOREGROUND_RED or FOREGROUND_BLUE),
|
||||||
(FOREGROUND_BLUE or FOREGROUND_GREEN),
|
(FOREGROUND_BLUE or FOREGROUND_GREEN),
|
||||||
(FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)]
|
(FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)]
|
||||||
discard setConsoleTextAttribute(hStdout, toU16(old or lookup[fg]))
|
discard setConsoleTextAttribute(h, toU16(old or lookup[fg]))
|
||||||
else:
|
else:
|
||||||
gFG = ord(fg)
|
gFG = ord(fg)
|
||||||
if bright: inc(gFG, 60)
|
if bright: inc(gFG, 60)
|
||||||
stdout.write("\e[" & $gFG & 'm')
|
f.write("\e[" & $gFG & 'm')
|
||||||
|
|
||||||
proc setBackgroundColor*(bg: BackgroundColor, bright=false) =
|
proc setBackgroundColor*(f: File, bg: BackgroundColor, bright=false) =
|
||||||
## sets the terminal's background color
|
## Sets the terminal's background color.
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
var old = getAttributes() and not 0x0070
|
let h = conHandle(f)
|
||||||
|
var old = getAttributes(h) and not 0x0070
|
||||||
if bright:
|
if bright:
|
||||||
old = old or BACKGROUND_INTENSITY
|
old = old or BACKGROUND_INTENSITY
|
||||||
const lookup: array [BackgroundColor, int] = [
|
const lookup: array [BackgroundColor, int] = [
|
||||||
|
|
@ -385,14 +413,14 @@ proc setBackgroundColor*(bg: BackgroundColor, bright=false) =
|
||||||
(BACKGROUND_RED or BACKGROUND_BLUE),
|
(BACKGROUND_RED or BACKGROUND_BLUE),
|
||||||
(BACKGROUND_BLUE or BACKGROUND_GREEN),
|
(BACKGROUND_BLUE or BACKGROUND_GREEN),
|
||||||
(BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)]
|
(BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)]
|
||||||
discard setConsoleTextAttribute(hStdout, toU16(old or lookup[bg]))
|
discard setConsoleTextAttribute(h, toU16(old or lookup[bg]))
|
||||||
else:
|
else:
|
||||||
gBG = ord(bg)
|
gBG = ord(bg)
|
||||||
if bright: inc(gBG, 60)
|
if bright: inc(gBG, 60)
|
||||||
stdout.write("\e[" & $gBG & 'm')
|
f.write("\e[" & $gBG & 'm')
|
||||||
|
|
||||||
proc isatty*(f: File): bool =
|
proc isatty*(f: File): bool =
|
||||||
## returns true if `f` is associated with a terminal device.
|
## Returns true if `f` is associated with a terminal device.
|
||||||
when defined(posix):
|
when defined(posix):
|
||||||
proc isatty(fildes: FileHandle): cint {.
|
proc isatty(fildes: FileHandle): cint {.
|
||||||
importc: "isatty", header: "<unistd.h>".}
|
importc: "isatty", header: "<unistd.h>".}
|
||||||
|
|
@ -406,39 +434,62 @@ type
|
||||||
TerminalCmd* = enum ## commands that can be expressed as arguments
|
TerminalCmd* = enum ## commands that can be expressed as arguments
|
||||||
resetStyle ## reset attributes
|
resetStyle ## reset attributes
|
||||||
|
|
||||||
template styledEchoProcessArg(s: string) = write stdout, s
|
template styledEchoProcessArg(f: File, s: string) = write f, s
|
||||||
template styledEchoProcessArg(style: Style) = setStyle({style})
|
template styledEchoProcessArg(f: File, style: Style) = setStyle(f, {style})
|
||||||
template styledEchoProcessArg(style: set[Style]) = setStyle style
|
template styledEchoProcessArg(f: File, style: set[Style]) = setStyle f, style
|
||||||
template styledEchoProcessArg(color: ForegroundColor) = setForegroundColor color
|
template styledEchoProcessArg(f: File, color: ForegroundColor) =
|
||||||
template styledEchoProcessArg(color: BackgroundColor) = setBackgroundColor color
|
setForegroundColor f, color
|
||||||
template styledEchoProcessArg(cmd: TerminalCmd) =
|
template styledEchoProcessArg(f: File, color: BackgroundColor) =
|
||||||
|
setBackgroundColor f, color
|
||||||
|
template styledEchoProcessArg(f: File, cmd: TerminalCmd) =
|
||||||
when cmd == resetStyle:
|
when cmd == resetStyle:
|
||||||
resetAttributes()
|
resetAttributes(f)
|
||||||
|
|
||||||
macro styledEcho*(m: varargs[expr]): stmt =
|
macro styledWriteLine*(f: File, m: varargs[expr]): stmt =
|
||||||
## to be documented.
|
## Similar to ``writeLine``, but treating terminal style arguments specially.
|
||||||
|
## When some argument is ``Style``, ``set[Style]``, ``ForegroundColor``,
|
||||||
|
## ``BackgroundColor`` or ``TerminalCmd`` then it is not sent directly to
|
||||||
|
## ``f``, but instead corresponding terminal style proc is called.
|
||||||
|
##
|
||||||
|
## Example:
|
||||||
|
##
|
||||||
|
## .. code-block:: nim
|
||||||
|
##
|
||||||
|
## proc error(msg: string) =
|
||||||
|
## styleWriteLine(stderr, fgRed, "Error: ", resetStyle, msg)
|
||||||
|
##
|
||||||
let m = callsite()
|
let m = callsite()
|
||||||
var reset = false
|
var reset = false
|
||||||
result = newNimNode(nnkStmtList)
|
result = newNimNode(nnkStmtList)
|
||||||
|
|
||||||
for i in countup(1, m.len - 1):
|
for i in countup(2, m.len - 1):
|
||||||
let item = m[i]
|
let item = m[i]
|
||||||
case item.kind
|
case item.kind
|
||||||
of nnkStrLit..nnkTripleStrLit:
|
of nnkStrLit..nnkTripleStrLit:
|
||||||
if i == m.len - 1:
|
if i == m.len - 1:
|
||||||
# optimize if string literal is last, just call writeLine
|
# optimize if string literal is last, just call writeLine
|
||||||
result.add(newCall(bindSym"writeLine", bindSym"stdout", item))
|
result.add(newCall(bindSym"writeLine", f, item))
|
||||||
if reset: result.add(newCall(bindSym"resetAttributes"))
|
if reset: result.add(newCall(bindSym"resetAttributes", f))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
# if it is string literal just call write, do not enable reset
|
# if it is string literal just call write, do not enable reset
|
||||||
result.add(newCall(bindSym"write", bindSym"stdout", item))
|
result.add(newCall(bindSym"write", f, item))
|
||||||
else:
|
else:
|
||||||
result.add(newCall(bindSym"styledEchoProcessArg", item))
|
result.add(newCall(bindSym"styledEchoProcessArg", f, item))
|
||||||
reset = true
|
reset = true
|
||||||
|
|
||||||
result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n")))
|
result.add(newCall(bindSym"write", f, newStrLitNode("\n")))
|
||||||
if reset: result.add(newCall(bindSym"resetAttributes"))
|
if reset: result.add(newCall(bindSym"resetAttributes", f))
|
||||||
|
|
||||||
|
macro callStyledEcho(args: varargs[expr]): stmt =
|
||||||
|
result = newCall(bindSym"styledWriteLine")
|
||||||
|
result.add(bindSym"stdout")
|
||||||
|
for arg in children(args[0][1]):
|
||||||
|
result.add(arg)
|
||||||
|
|
||||||
|
template styledEcho*(args: varargs[expr]): expr =
|
||||||
|
## Echoes styles arguments to stdout using ``styledWriteLine``.
|
||||||
|
callStyledEcho(args)
|
||||||
|
|
||||||
when defined(nimdoc):
|
when defined(nimdoc):
|
||||||
proc getch*(): char =
|
proc getch*(): char =
|
||||||
|
|
@ -458,15 +509,35 @@ elif not defined(windows):
|
||||||
result = stdin.readChar()
|
result = stdin.readChar()
|
||||||
discard fd.tcsetattr(TCSADRAIN, addr oldMode)
|
discard fd.tcsetattr(TCSADRAIN, addr oldMode)
|
||||||
|
|
||||||
|
# Wrappers assuming output to stdout:
|
||||||
|
template setCursorPos*(x, y: int) = setCursorPos(stdout, x, y)
|
||||||
|
template setCursorXPos*(x: int) = setCursorXPos(stdout, x)
|
||||||
|
when defined(windows):
|
||||||
|
template setCursorYPos(x: int) = setCursorYPos(stdout, x)
|
||||||
|
template cursorUp*(count=1) = cursorUp(stdout, f)
|
||||||
|
template cursorDown*(count=1) = cursorDown(stdout, f)
|
||||||
|
template cursorForward*(count=1) = cursorForward(stdout, f)
|
||||||
|
template cursorBackward*(count=1) = cursorBackward(stdout, f)
|
||||||
|
template eraseLine*() = eraseLine(stdout)
|
||||||
|
template eraseScreen*() = eraseScreen(stdout)
|
||||||
|
template setStyle*(style: set[Style]) =
|
||||||
|
setStyle(stdout, style)
|
||||||
|
template setForegroundColor*(fg: ForegroundColor, bright=false) =
|
||||||
|
setForegroundColor(stdout, fg, bright)
|
||||||
|
template setBackgroundColor*(bg: BackgroundColor, bright=false) =
|
||||||
|
setBackgroundColor(stdout, bg, bright)
|
||||||
|
proc resetAttributes*() {.noconv.} =
|
||||||
|
## Resets all attributes on stdout.
|
||||||
|
## It is advisable to register this as a quit proc with
|
||||||
|
## ``system.addQuitProc(resetAttributes)``.
|
||||||
|
resetAttributes(stdout)
|
||||||
|
|
||||||
when not defined(testing) and isMainModule:
|
when not defined(testing) and isMainModule:
|
||||||
system.addQuitProc(resetAttributes)
|
#system.addQuitProc(resetAttributes)
|
||||||
write(stdout, "never mind")
|
write(stdout, "never mind")
|
||||||
eraseLine()
|
stdout.eraseLine()
|
||||||
#setCursorPos(2, 2)
|
stdout.styledWriteLine("styled text ", {styleBright, styleBlink, styleUnderscore})
|
||||||
writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore})
|
stdout.setBackGroundColor(bgCyan, true)
|
||||||
setBackGroundColor(bgCyan, true)
|
stdout.setForeGroundColor(fgBlue)
|
||||||
setForeGroundColor(fgBlue)
|
stdout.writeLine("ordinary text")
|
||||||
writeLine(stdout, "ordinary text")
|
stdout.resetAttributes()
|
||||||
|
|
||||||
styledEcho("styled text ", {styleBright, styleBlink, styleUnderscore})
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue