Fixed terminal module so that it doesn't raise an EOS exception when stdout is redirected.

This commit is contained in:
Dominik Picheta 2013-06-22 19:57:12 +01:00
commit 9540a93d95
2 changed files with 334 additions and 334 deletions

View file

@ -1,312 +1,310 @@
# #
# #
# Nimrod's Runtime Library # Nimrod's Runtime Library
# (c) Copyright 2012 Andreas Rumpf # (c) Copyright 2012 Andreas Rumpf
# #
# See the file "copying.txt", included in this # See the file "copying.txt", included in this
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
## This module contains a few procedures to control the *terminal* ## This module contains a few procedures to control the *terminal*
## (also called *console*). On UNIX, the implementation simply uses ANSI escape ## (also called *console*). On UNIX, the implementation simply uses ANSI escape
## sequences and does not depend on any other module, on Windows it uses the ## sequences and does not depend on any other module, on Windows it uses the
## Windows API. ## Windows API.
## Changing the style is permanent even after program termination! Use the ## Changing the style is permanent even after program termination! Use the
## code ``system.addQuitProc(resetAttributes)`` to restore the defaults. ## code ``system.addQuitProc(resetAttributes)`` to restore the defaults.
import macros import macros
when defined(windows): when defined(windows):
import windows, os import windows, os
var var
conHandle: THandle conHandle: THandle
# = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0) # = createFile("CONOUT$", GENERIC_WRITE, 0, nil, OPEN_ALWAYS, 0, 0)
block: block:
var hTemp = GetStdHandle(STD_OUTPUT_HANDLE) var hTemp = GetStdHandle(STD_OUTPUT_HANDLE)
if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(), if DuplicateHandle(GetCurrentProcess(), hTemp, GetCurrentProcess(),
addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0: addr(conHandle), 0, 1, DUPLICATE_SAME_ACCESS) == 0:
OSError() OSError()
proc getCursorPos(): tuple [x,y: int] = proc getCursorPos(): tuple [x,y: int] =
var c: TCONSOLE_SCREEN_BUFFER_INFO var c: TCONSOLE_SCREEN_BUFFER_INFO
if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError() if GetConsoleScreenBufferInfo(conHandle, addr(c)) == 0: OSError()
return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y)) return (int(c.dwCursorPosition.x), int(c.dwCursorPosition.y))
proc getAttributes(): int16 = proc getAttributes(): int16 =
var c: TCONSOLE_SCREEN_BUFFER_INFO var c: TCONSOLE_SCREEN_BUFFER_INFO
# workaround Windows bugs: try several times # workaround Windows bugs: try several times
if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0: if GetConsoleScreenBufferInfo(conHandle, addr(c)) != 0:
return c.wAttributes return c.wAttributes
else: return 0x70'i16 # ERROR: return white background, black text
OSError()
return 0x70'i16 # ERROR: return white background, black text var
oldAttr = getAttributes()
var
oldAttr = getAttributes() proc setCursorPos*(x, y: int) =
## sets the terminal's cursor to the (x,y) position. (0,0) is the
proc setCursorPos*(x, y: int) = ## upper left of the screen.
## sets the terminal's cursor to the (x,y) position. (0,0) is the when defined(windows):
## upper left of the screen. var c: TCoord
when defined(windows): c.x = int16(x)
var c: TCoord c.y = int16(y)
c.x = int16(x) if SetConsoleCursorPosition(conHandle, c) == 0: OSError()
c.y = int16(y) else:
if SetConsoleCursorPosition(conHandle, c) == 0: OSError() stdout.write("\e[" & $y & ';' & $x & 'f')
else:
stdout.write("\e[" & $y & ';' & $x & 'f') proc setCursorXPos*(x: int) =
## sets the terminal's cursor to the x position. The y position is
proc setCursorXPos*(x: int) = ## not changed.
## sets the terminal's cursor to the x position. The y position is when defined(windows):
## not changed. var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
when defined(windows): var hStdout = conHandle
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var hStdout = conHandle var origin = scrbuf.dwCursorPosition
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() origin.x = int16(x)
var origin = scrbuf.dwCursorPosition if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
origin.x = int16(x) else:
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() stdout.write("\e[" & $x & 'G')
else:
stdout.write("\e[" & $x & 'G') when defined(windows):
proc setCursorYPos*(y: int) =
when defined(windows): ## sets the terminal's cursor to the y position. The x position is
proc setCursorYPos*(y: int) = ## not changed. **Warning**: This is not supported on UNIX!
## sets the terminal's cursor to the y position. The x position is when defined(windows):
## not changed. **Warning**: This is not supported on UNIX! var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
when defined(windows): var hStdout = conHandle
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var hStdout = conHandle var origin = scrbuf.dwCursorPosition
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() origin.y = int16(y)
var origin = scrbuf.dwCursorPosition if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
origin.y = int16(y) else:
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() nil
else:
nil proc CursorUp*(count=1) =
## Moves the cursor up by `count` rows.
proc CursorUp*(count=1) = when defined(windows):
## Moves the cursor up by `count` rows. var p = getCursorPos()
when defined(windows): dec(p.y, count)
var p = getCursorPos() setCursorPos(p.x, p.y)
dec(p.y, count) else:
setCursorPos(p.x, p.y) stdout.write("\e[" & $count & 'A')
else:
stdout.write("\e[" & $count & 'A') proc CursorDown*(count=1) =
## Moves the cursor down by `count` rows.
proc CursorDown*(count=1) = when defined(windows):
## Moves the cursor down by `count` rows. var p = getCursorPos()
when defined(windows): inc(p.y, count)
var p = getCursorPos() setCursorPos(p.x, p.y)
inc(p.y, count) else:
setCursorPos(p.x, p.y) stdout.write("\e[" & $count & 'B')
else:
stdout.write("\e[" & $count & 'B') proc CursorForward*(count=1) =
## Moves the cursor forward by `count` columns.
proc CursorForward*(count=1) = when defined(windows):
## Moves the cursor forward by `count` columns. var p = getCursorPos()
when defined(windows): inc(p.x, count)
var p = getCursorPos() setCursorPos(p.x, p.y)
inc(p.x, count) else:
setCursorPos(p.x, p.y) stdout.write("\e[" & $count & 'C')
else:
stdout.write("\e[" & $count & 'C') proc CursorBackward*(count=1) =
## Moves the cursor backward by `count` columns.
proc CursorBackward*(count=1) = when defined(windows):
## Moves the cursor backward by `count` columns. var p = getCursorPos()
when defined(windows): dec(p.x, count)
var p = getCursorPos() setCursorPos(p.x, p.y)
dec(p.x, count) else:
setCursorPos(p.x, p.y) stdout.write("\e[" & $count & 'D')
else:
stdout.write("\e[" & $count & 'D') when true:
nil
when true: else:
nil proc EraseLineEnd* =
else: ## Erases from the current cursor position to the end of the current line.
proc EraseLineEnd* = when defined(windows):
## Erases from the current cursor position to the end of the current line. nil
when defined(windows): else:
nil stdout.write("\e[K")
else:
stdout.write("\e[K") proc EraseLineStart* =
## Erases from the current cursor position to the start of the current line.
proc EraseLineStart* = when defined(windows):
## Erases from the current cursor position to the start of the current line. nil
when defined(windows): else:
nil stdout.write("\e[1K")
else:
stdout.write("\e[1K") proc EraseDown* =
## Erases the screen from the current line down to the bottom of the screen.
proc EraseDown* = when defined(windows):
## Erases the screen from the current line down to the bottom of the screen. nil
when defined(windows): else:
nil stdout.write("\e[J")
else:
stdout.write("\e[J") proc EraseUp* =
## Erases the screen from the current line up to the top of the screen.
proc EraseUp* = when defined(windows):
## Erases the screen from the current line up to the top of the screen. nil
when defined(windows): else:
nil stdout.write("\e[1J")
else:
stdout.write("\e[1J") proc EraseLine* =
## Erases the entire current line.
proc EraseLine* = when defined(windows):
## Erases the entire current line. var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
when defined(windows): var numwrote: DWORD
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO var hStdout = conHandle
var numwrote: DWORD if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var hStdout = conHandle var origin = scrbuf.dwCursorPosition
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() origin.x = 0'i16
var origin = scrbuf.dwCursorPosition if SetConsoleCursorPosition(conHandle, origin) == 0: OSError()
origin.x = 0'i16 var ht = scrbuf.dwSize.Y - origin.Y
if SetConsoleCursorPosition(conHandle, origin) == 0: OSError() var wt = scrbuf.dwSize.X - origin.X
var ht = scrbuf.dwSize.Y - origin.Y if FillConsoleOutputCharacter(hStdout,' ', ht*wt,
var wt = scrbuf.dwSize.X - origin.X origin, addr(numwrote)) == 0:
if FillConsoleOutputCharacter(hStdout,' ', ht*wt, OSError()
origin, addr(numwrote)) == 0: if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt,
OSError() scrbuf.dwCursorPosition, addr(numwrote)) == 0:
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, ht * wt, OSError()
scrbuf.dwCursorPosition, addr(numwrote)) == 0: else:
OSError() stdout.write("\e[2K")
else: setCursorXPos(0)
stdout.write("\e[2K")
setCursorXPos(0) proc EraseScreen* =
## Erases the screen with the background colour and moves the cursor to home.
proc EraseScreen* = when defined(windows):
## Erases the screen with the background colour and moves the cursor to home. var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO
when defined(windows): var numwrote: DWORD
var scrbuf: TCONSOLE_SCREEN_BUFFER_INFO var origin: TCoord # is inititalized to 0, 0
var numwrote: DWORD var hStdout = conHandle
var origin: TCoord # is inititalized to 0, 0 if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError()
var hStdout = conHandle if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y,
if GetConsoleScreenBufferInfo(hStdout, addr(scrbuf)) == 0: OSError() origin, addr(numwrote)) == 0:
if FillConsoleOutputCharacter(hStdout, ' ', scrbuf.dwSize.X*scrbuf.dwSize.Y, OSError()
origin, addr(numwrote)) == 0: if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes,
OSError() scrbuf.dwSize.X * scrbuf.dwSize.Y,
if FillConsoleOutputAttribute(hStdout, scrbuf.wAttributes, origin, addr(numwrote)) == 0:
scrbuf.dwSize.X * scrbuf.dwSize.Y, OSError()
origin, addr(numwrote)) == 0: setCursorXPos(0)
OSError() else:
setCursorXPos(0) stdout.write("\e[2J")
else:
stdout.write("\e[2J") proc ResetAttributes* {.noconv.} =
## resets all attributes; it is advisable to register this as a quit proc
proc ResetAttributes* {.noconv.} = ## with ``system.addQuitProc(resetAttributes)``.
## resets all attributes; it is advisable to register this as a quit proc when defined(windows):
## with ``system.addQuitProc(resetAttributes)``. discard SetConsoleTextAttribute(conHandle, oldAttr)
when defined(windows): else:
discard SetConsoleTextAttribute(conHandle, oldAttr) stdout.write("\e[0m")
else:
stdout.write("\e[0m") type
TStyle* = enum ## different styles for text output
type styleBright = 1, ## bright text
TStyle* = enum ## different styles for text output styleDim, ## dim text
styleBright = 1, ## bright text styleUnknown, ## unknown
styleDim, ## dim text styleUnderscore = 4, ## underscored text
styleUnknown, ## unknown styleBlink, ## blinking/bold text
styleUnderscore = 4, ## underscored text styleReverse = 7, ## unknown
styleBlink, ## blinking/bold text styleHidden ## hidden text
styleReverse = 7, ## unknown
styleHidden ## hidden text when not defined(windows):
var
when not defined(windows): # XXX: These better be thread-local
var gFG = 0
# XXX: These better be thread-local gBG = 0
gFG = 0
gBG = 0 proc setStyle*(style: set[TStyle]) =
## sets the terminal style
proc setStyle*(style: set[TStyle]) = when defined(windows):
## sets the terminal style var a = 0'i16
when defined(windows): if styleBright in style: a = a or int16(FOREGROUND_INTENSITY)
var a = 0'i16 if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY)
if styleBright in style: a = a or int16(FOREGROUND_INTENSITY) if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO
if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY) if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE
if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO discard SetConsoleTextAttribute(conHandle, a)
if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE else:
discard SetConsoleTextAttribute(conHandle, a) for s in items(style):
else: stdout.write("\e[" & $ord(s) & 'm')
for s in items(style):
stdout.write("\e[" & $ord(s) & 'm') proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) =
## writes the text `txt` in a given `style`.
proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) = when defined(windows):
## writes the text `txt` in a given `style`. var old = getAttributes()
when defined(windows): setStyle(style)
var old = getAttributes() stdout.write(txt)
setStyle(style) discard SetConsoleTextAttribute(conHandle, old)
stdout.write(txt) else:
discard SetConsoleTextAttribute(conHandle, old) setStyle(style)
else: stdout.write(txt)
setStyle(style) resetAttributes()
stdout.write(txt) if gFG != 0:
resetAttributes() stdout.write("\e[" & $ord(gFG) & 'm')
if gFG != 0: if gBG != 0:
stdout.write("\e[" & $ord(gFG) & 'm') stdout.write("\e[" & $ord(gBG) & 'm')
if gBG != 0:
stdout.write("\e[" & $ord(gBG) & 'm') type
TForegroundColor* = enum ## terminal's foreground colors
type fgBlack = 30, ## black
TForegroundColor* = enum ## terminal's foreground colors fgRed, ## red
fgBlack = 30, ## black fgGreen, ## green
fgRed, ## red fgYellow, ## yellow
fgGreen, ## green fgBlue, ## blue
fgYellow, ## yellow fgMagenta, ## magenta
fgBlue, ## blue fgCyan, ## cyan
fgMagenta, ## magenta fgWhite ## white
fgCyan, ## cyan
fgWhite ## white TBackgroundColor* = enum ## terminal's background colors
bgBlack = 40, ## black
TBackgroundColor* = enum ## terminal's background colors bgRed, ## red
bgBlack = 40, ## black bgGreen, ## green
bgRed, ## red bgYellow, ## yellow
bgGreen, ## green bgBlue, ## blue
bgYellow, ## yellow bgMagenta, ## magenta
bgBlue, ## blue bgCyan, ## cyan
bgMagenta, ## magenta bgWhite ## white
bgCyan, ## cyan
bgWhite ## white proc setForegroundColor*(fg: TForegroundColor, bright=false) =
## sets the terminal's foreground color
proc setForegroundColor*(fg: TForegroundColor, bright=false) = when defined(windows):
## sets the terminal's foreground color var old = getAttributes() and not 0x0007
when defined(windows): if bright:
var old = getAttributes() and not 0x0007 old = old or FOREGROUND_INTENSITY
if bright: const lookup: array [TForegroundColor, int] = [
old = old or FOREGROUND_INTENSITY 0,
const lookup: array [TForegroundColor, int] = [ (FOREGROUND_RED),
0, (FOREGROUND_GREEN),
(FOREGROUND_RED), (FOREGROUND_RED or FOREGROUND_GREEN),
(FOREGROUND_GREEN), (FOREGROUND_BLUE),
(FOREGROUND_RED or FOREGROUND_GREEN), (FOREGROUND_RED or FOREGROUND_BLUE),
(FOREGROUND_BLUE), (FOREGROUND_BLUE or FOREGROUND_GREEN),
(FOREGROUND_RED or FOREGROUND_BLUE), (FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)]
(FOREGROUND_BLUE or FOREGROUND_GREEN), discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg]))
(FOREGROUND_BLUE or FOREGROUND_GREEN or FOREGROUND_RED)] else:
discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[fg])) gFG = ord(fg)
else: if bright: inc(gFG, 60)
gFG = ord(fg) stdout.write("\e[" & $gFG & 'm')
if bright: inc(gFG, 60)
stdout.write("\e[" & $gFG & 'm') proc setBackgroundColor*(bg: TBackgroundColor, bright=false) =
## sets the terminal's background color
proc setBackgroundColor*(bg: TBackgroundColor, bright=false) = when defined(windows):
## sets the terminal's background color var old = getAttributes() and not 0x0070
when defined(windows): if bright:
var old = getAttributes() and not 0x0070 old = old or BACKGROUND_INTENSITY
if bright: const lookup: array [TBackgroundColor, int] = [
old = old or BACKGROUND_INTENSITY 0,
const lookup: array [TBackgroundColor, int] = [ (BACKGROUND_RED),
0, (BACKGROUND_GREEN),
(BACKGROUND_RED), (BACKGROUND_RED or BACKGROUND_GREEN),
(BACKGROUND_GREEN), (BACKGROUND_BLUE),
(BACKGROUND_RED or BACKGROUND_GREEN), (BACKGROUND_RED or BACKGROUND_BLUE),
(BACKGROUND_BLUE), (BACKGROUND_BLUE or BACKGROUND_GREEN),
(BACKGROUND_RED or BACKGROUND_BLUE), (BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)]
(BACKGROUND_BLUE or BACKGROUND_GREEN), discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg]))
(BACKGROUND_BLUE or BACKGROUND_GREEN or BACKGROUND_RED)] else:
discard SetConsoleTextAttribute(conHandle, toU16(old or lookup[bg])) gBG = ord(bg)
else: if bright: inc(gBG, 60)
gBG = ord(bg) stdout.write("\e[" & $gBG & 'm')
if bright: inc(gBG, 60)
stdout.write("\e[" & $gBG & 'm')
proc isatty*(f: TFile): bool = proc isatty*(f: TFile): bool =
## returns true if `f` is associated with a terminal device. ## returns true if `f` is associated with a terminal device.
@ -318,33 +316,33 @@ proc isatty*(f: TFile): bool =
importc: "_isatty", header: "<io.h>".} importc: "_isatty", header: "<io.h>".}
result = isatty(fileHandle(f)) != 0'i32 result = isatty(fileHandle(f)) != 0'i32
proc styledEchoProcessArg(s: string) = write stdout, s proc styledEchoProcessArg(s: string) = write stdout, s
proc styledEchoProcessArg(style: TStyle) = setStyle({style}) proc styledEchoProcessArg(style: TStyle) = setStyle({style})
proc styledEchoProcessArg(style: set[TStyle]) = setStyle style proc styledEchoProcessArg(style: set[TStyle]) = setStyle style
proc styledEchoProcessArg(color: TForegroundColor) = setForeGroundColor color proc styledEchoProcessArg(color: TForegroundColor) = setForeGroundColor color
proc styledEchoProcessArg(color: TBackgroundColor) = setBackGroundColor color proc styledEchoProcessArg(color: TBackgroundColor) = setBackGroundColor color
macro styledEcho*(m: varargs[expr]): stmt = macro styledEcho*(m: varargs[expr]): stmt =
## to be documented. ## to be documented.
let m = callsite() let m = callsite()
result = newNimNode(nnkStmtList) result = newNimNode(nnkStmtList)
for i in countup(1, m.len - 1): for i in countup(1, m.len - 1):
result.add(newCall(bindSym"styledEchoProcessArg", m[i])) result.add(newCall(bindSym"styledEchoProcessArg", m[i]))
result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n"))) result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n")))
result.add(newCall(bindSym"resetAttributes")) result.add(newCall(bindSym"resetAttributes"))
when isMainModule: when isMainModule:
system.addQuitProc(resetAttributes) system.addQuitProc(resetAttributes)
write(stdout, "never mind") write(stdout, "never mind")
eraseLine() eraseLine()
#setCursorPos(2, 2) #setCursorPos(2, 2)
writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore}) writeStyled("styled text ", {styleBright, styleBlink, styleUnderscore})
setBackGroundColor(bgCyan, true) setBackGroundColor(bgCyan, true)
setForeGroundColor(fgBlue) setForeGroundColor(fgBlue)
writeln(stdout, "ordinary text") writeln(stdout, "ordinary text")
styledEcho("styled text ", {styleBright, styleBlink, styleUnderscore}) styledEcho("styled text ", {styleBright, styleBlink, styleUnderscore})

View file

@ -32,6 +32,7 @@ Options:
--ignoreCase, -i be case insensitive --ignoreCase, -i be case insensitive
--ignoreStyle, -y be style insensitive --ignoreStyle, -y be style insensitive
--ext:EX1|EX2|... only search the files with the given extension(s) --ext:EX1|EX2|... only search the files with the given extension(s)
--nocolor output will be given without any colours.
--verbose be verbose: list every processed file --verbose be verbose: list every processed file
--help, -h shows this help --help, -h shows this help
--version, -v shows the version --version, -v shows the version
@ -291,6 +292,7 @@ for kind, key, val in getopt():
of "ignorecase", "i": incl(options, optIgnoreCase) of "ignorecase", "i": incl(options, optIgnoreCase)
of "ignorestyle", "y": incl(options, optIgnoreStyle) of "ignorestyle", "y": incl(options, optIgnoreStyle)
of "ext": extensions = val.split('|') of "ext": extensions = val.split('|')
of "nocolor": useWriteStyled = false
of "verbose": incl(options, optVerbose) of "verbose": incl(options, optVerbose)
of "help", "h": writeHelp() of "help", "h": writeHelp()
of "version", "v": writeVersion() of "version", "v": writeVersion()