Merge pull request #2906 from nanoant/patch/more-about-colors

More flexible msg colors
This commit is contained in:
Andreas Rumpf 2015-06-15 14:51:20 +02:00
commit 0272da0188
2 changed files with 185 additions and 108 deletions

View file

@ -8,7 +8,7 @@
# #
import import
options, strutils, os, tables, ropes, platform, terminal options, strutils, os, tables, ropes, platform, terminal, macros
type type
TMsgKind* = enum TMsgKind* = enum
@ -367,54 +367,54 @@ const
"of the generic paramers can be inferred from the expected signature.", "of the generic paramers can be inferred from the expected signature.",
errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target", errCompilerDoesntSupportTarget: "The current compiler \'$1\' doesn't support the requested compilation target",
errUser: "$1", errUser: "$1",
warnCannotOpenFile: "cannot open \'$1\' [CannotOpenFile]", warnCannotOpenFile: "cannot open \'$1\'",
warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored [OctalEscape]", warnOctalEscape: "octal escape sequences do not exist; leading zero is ignored",
warnXIsNeverRead: "\'$1\' is never read [XIsNeverRead]", warnXIsNeverRead: "\'$1\' is never read",
warnXmightNotBeenInit: "\'$1\' might not have been initialized [XmightNotBeenInit]", warnXmightNotBeenInit: "\'$1\' might not have been initialized",
warnDeprecated: "$1 is deprecated [Deprecated]", warnDeprecated: "$1 is deprecated",
warnConfigDeprecated: "config file '$1' is deprecated [ConfigDeprecated]", warnConfigDeprecated: "config file '$1' is deprecated",
warnSmallLshouldNotBeUsed: "\'l\' should not be used as an identifier; may look like \'1\' (one) [SmallLshouldNotBeUsed]", warnSmallLshouldNotBeUsed: "\'l\' should not be used as an identifier; may look like \'1\' (one)",
warnUnknownMagic: "unknown magic \'$1\' might crash the compiler [UnknownMagic]", warnUnknownMagic: "unknown magic \'$1\' might crash the compiler",
warnRedefinitionOfLabel: "redefinition of label \'$1\' [RedefinitionOfLabel]", warnRedefinitionOfLabel: "redefinition of label \'$1\'",
warnUnknownSubstitutionX: "unknown substitution \'$1\' [UnknownSubstitutionX]", warnUnknownSubstitutionX: "unknown substitution \'$1\'",
warnLanguageXNotSupported: "language \'$1\' not supported [LanguageXNotSupported]", warnLanguageXNotSupported: "language \'$1\' not supported",
warnFieldXNotSupported: "field \'$1\' not supported [FieldXNotSupported]", warnFieldXNotSupported: "field \'$1\' not supported",
warnCommentXIgnored: "comment \'$1\' ignored [CommentXIgnored]", warnCommentXIgnored: "comment \'$1\' ignored",
warnNilStatement: "'nil' statement is deprecated; use an empty 'discard' statement instead [NilStmt]", warnNilStatement: "'nil' statement is deprecated; use an empty 'discard' statement instead",
warnTypelessParam: "'$1' has no type. Typeless parameters are deprecated; only allowed for 'template' [TypelessParam]", warnTypelessParam: "'$1' has no type. Typeless parameters are deprecated; only allowed for 'template'",
warnDifferentHeaps: "possible inconsistency of thread local heaps [DifferentHeaps]", warnDifferentHeaps: "possible inconsistency of thread local heaps",
warnWriteToForeignHeap: "write to foreign heap [WriteToForeignHeap]", warnWriteToForeignHeap: "write to foreign heap",
warnUnsafeCode: "unsafe code: '$1' [UnsafeCode]", warnUnsafeCode: "unsafe code: '$1'",
warnEachIdentIsTuple: "each identifier is a tuple [EachIdentIsTuple]", warnEachIdentIsTuple: "each identifier is a tuple",
warnShadowIdent: "shadowed identifier: '$1' [ShadowIdent]", warnShadowIdent: "shadowed identifier: '$1'",
warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future. [ProveInit]", warnProveInit: "Cannot prove that '$1' is initialized. This will become a compile time error in the future.",
warnProveField: "cannot prove that field '$1' is accessible [ProveField]", warnProveField: "cannot prove that field '$1' is accessible",
warnProveIndex: "cannot prove index '$1' is valid [ProveIndex]", warnProveIndex: "cannot prove index '$1' is valid",
warnGcUnsafe: "not GC-safe: '$1' [GcUnsafe]", warnGcUnsafe: "not GC-safe: '$1'",
warnGcUnsafe2: "$1", warnGcUnsafe2: "$1",
warnUninit: "'$1' might not have been initialized [Uninit]", warnUninit: "'$1' might not have been initialized",
warnGcMem: "'$1' uses GC'ed memory [GcMem]", warnGcMem: "'$1' uses GC'ed memory",
warnDestructor: "usage of a type with a destructor in a non destructible context. This will become a compile time error in the future. [Destructor]", warnDestructor: "usage of a type with a destructor in a non destructible context. This will become a compile time error in the future.",
warnLockLevel: "$1 [LockLevel]", warnLockLevel: "$1",
warnResultShadowed: "Special variable 'result' is shadowed. [ResultShadowed]", warnResultShadowed: "Special variable 'result' is shadowed.",
warnUser: "$1 [User]", warnUser: "$1",
hintSuccess: "operation successful [Success]", hintSuccess: "operation successful",
hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#; $#) [SuccessX]", hintSuccessX: "operation successful ($# lines compiled; $# sec total; $#; $#)",
hintLineTooLong: "line too long [LineTooLong]", hintLineTooLong: "line too long",
hintXDeclaredButNotUsed: "\'$1\' is declared but not used [XDeclaredButNotUsed]", hintXDeclaredButNotUsed: "\'$1\' is declared but not used",
hintConvToBaseNotNeeded: "conversion to base object is not needed [ConvToBaseNotNeeded]", hintConvToBaseNotNeeded: "conversion to base object is not needed",
hintConvFromXtoItselfNotNeeded: "conversion from $1 to itself is pointless [ConvFromXtoItselfNotNeeded]", hintConvFromXtoItselfNotNeeded: "conversion from $1 to itself is pointless",
hintExprAlwaysX: "expression evaluates always to \'$1\' [ExprAlwaysX]", hintExprAlwaysX: "expression evaluates always to \'$1\'",
hintQuitCalled: "quit() called [QuitCalled]", hintQuitCalled: "quit() called",
hintProcessing: "$1 [Processing]", hintProcessing: "$1",
hintCodeBegin: "generated code listing: [CodeBegin]", hintCodeBegin: "generated code listing:",
hintCodeEnd: "end of listing [CodeEnd]", hintCodeEnd: "end of listing",
hintConf: "used config file \'$1\' [Conf]", hintConf: "used config file \'$1\'",
hintPath: "added path: '$1' [Path]", hintPath: "added path: '$1'",
hintConditionAlwaysTrue: "condition is always true: '$1' [CondTrue]", hintConditionAlwaysTrue: "condition is always true: '$1'",
hintName: "name should be: '$1' [Name]", hintName: "name should be: '$1'",
hintPattern: "$1 [Pattern]", hintPattern: "$1",
hintUser: "$1 [User]"] hintUser: "$1"]
const const
WarningsToStr*: array[0..30, string] = ["CannotOpenFile", "OctalEscape", WarningsToStr*: array[0..30, string] = ["CannotOpenFile", "OctalEscape",
@ -605,13 +605,17 @@ proc suggestQuit*() =
# this format is understood by many text editors: it is the same that # this format is understood by many text editors: it is the same that
# Borland and Freepascal use # Borland and Freepascal use
const const
PosErrorFormat* = "$1($2, $3) Error: " PosFormat = "$1($2, $3) "
PosWarningFormat* = "$1($2, $3) Warning: " KindFormat = " [$1]"
PosHintFormat* = "$1($2, $3) Hint: " KindColor = fgCyan
PosContextFormat = "$1($2, $3) Info: " ErrorTitle = "Error: "
RawError* = "Error: " ErrorColor = fgRed
RawWarning* = "Warning: " WarningTitle = "Warning: "
RawHint* = "Hint: " WarningColor = fgYellow
HintTitle = "Hint: "
HintColor = fgGreen
InfoTitle = "Info: "
InfoColor = fgCyan
proc getInfoContextLen*(): int = return msgContext.len proc getInfoContextLen*(): int = return msgContext.len
proc setInfoContextLen*(L: int) = setLen(msgContext, L) proc setInfoContextLen*(L: int) = setLen(msgContext, L)
@ -686,27 +690,58 @@ proc outWriteln*(s: string) =
## Writes to stdout. Always. ## Writes to stdout. Always.
if eStdOut in errorOutputs: writeln(stdout, s) if eStdOut in errorOutputs: writeln(stdout, s)
proc msgWriteln*(s: string, color: ForegroundColor = fgWhite, coloredText: string = "") = proc msgWriteln*(s: string) =
## Writes to stdout. If --stderr option is given, writes to stderr instead. ## Writes to stdout. If --stdout option is given, writes to stderr instead.
#if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return #if gCmd == cmdIdeTools and optCDebug notin gGlobalOptions: return
var hasColor = optUseColors in gGlobalOptions
if not isNil(writelnHook): if not isNil(writelnHook):
writelnHook(coloredText & s) writelnHook(s)
elif optStdout in gGlobalOptions:
if eStdErr in errorOutputs: writeln(stderr, s)
else: else:
if optStdout in gGlobalOptions: if eStdOut in errorOutputs: writeln(stdout, s)
if eStdErr in errorOutputs:
if hasColor: setForegroundColor(color) macro callIgnoringStyle(theProc: typed, first: typed,
write(stderr, coloredText) args: varargs[expr]): stmt =
if hasColor: resetAttributes() let typForegroundColor = bindSym"ForegroundColor".getType
writeln(stderr, s) let typBackgroundColor = bindSym"BackgroundColor".getType
else: let typStyle = bindSym"Style".getType
if eStdOut in errorOutputs: let typTerminalCmd = bindSym"TerminalCmd".getType
if hasColor: setForegroundColor(color) result = newCall(theProc)
write(stdout, coloredText) if first.kind != nnkNilLit: result.add(first)
if hasColor: resetAttributes() for arg in children(args[0][1]):
writeln(stdout, s) if arg.kind == nnkNilLit: continue
let typ = arg.getType
if typ.kind != nnkEnumTy or
typ != typForegroundColor and
typ != typBackgroundColor and
typ != typStyle and
typ != typTerminalCmd:
result.add(arg)
macro callStyledEcho(args: varargs[expr]): stmt =
result = newCall(bindSym"styledEcho")
for arg in children(args[0][1]):
result.add(arg)
template callWritelnHook(args: varargs[string, `$`]) =
var s = ""
for arg in args:
s.add arg
writelnHook s
template styledMsgWriteln*(args: varargs[expr]) =
if not isNil(writelnHook):
callIgnoringStyle(callWritelnHook, nil, args)
elif optStdout in gGlobalOptions:
if eStdErr in errorOutputs: callIgnoringStyle(writeln, stderr, args)
else:
if eStdOut in errorOutputs:
if optUseColors in gGlobalOptions:
callStyledEcho(args)
else:
callIgnoringStyle(writeln, stdout, args)
proc coordToStr(coord: int): string = proc coordToStr(coord: int): string =
if coord == -1: result = "???" if coord == -1: result = "???"
@ -728,7 +763,7 @@ proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) =
if stackTraceAvailable() and isNil(writelnHook): if stackTraceAvailable() and isNil(writelnHook):
writeStackTrace() writeStackTrace()
else: else:
msgWriteln("", fgRed, "No stack traceback available\nTo create a stacktrace, rerun compilation with ./koch temp " & options.command & " <file>") styledMsgWriteln(fgRed, "No stack traceback available\nTo create a stacktrace, rerun compilation with ./koch temp " & options.command & " <file>")
quit 1 quit 1
if msg >= fatalMin and msg <= fatalMax: if msg >= fatalMin and msg <= fatalMax:
@ -750,10 +785,12 @@ proc writeContext(lastinfo: TLineInfo) =
var info = lastinfo var info = lastinfo
for i in countup(0, len(msgContext) - 1): for i in countup(0, len(msgContext) - 1):
if msgContext[i] != lastinfo and msgContext[i] != info: if msgContext[i] != lastinfo and msgContext[i] != info:
msgWriteln(PosContextFormat % [toMsgFilename(msgContext[i]), styledMsgWriteln(styleBright,
coordToStr(msgContext[i].line), PosFormat % [toMsgFilename(msgContext[i]),
coordToStr(msgContext[i].col+1), coordToStr(msgContext[i].line),
getMessageStr(errInstantiationFrom, "")]) coordToStr(msgContext[i].col+1)],
styleDim,
getMessageStr(errInstantiationFrom, ""))
info = msgContext[i] info = msgContext[i]
proc ignoreMsgBecauseOfIdeTools(msg: TMsgKind): bool = proc ignoreMsgBecauseOfIdeTools(msg: TMsgKind): bool =
@ -761,29 +798,36 @@ proc ignoreMsgBecauseOfIdeTools(msg: TMsgKind): bool =
proc rawMessage*(msg: TMsgKind, args: openArray[string]) = proc rawMessage*(msg: TMsgKind, args: openArray[string]) =
var var
frmt: string title: string
color: ForegroundColor color: ForegroundColor
kind: string
case msg case msg
of errMin..errMax: of errMin..errMax:
writeContext(unknownLineInfo()) writeContext(unknownLineInfo())
frmt = RawError title = ErrorTitle
color = fgRed color = ErrorColor
of warnMin..warnMax: of warnMin..warnMax:
if optWarns notin gOptions: return if optWarns notin gOptions: return
if msg notin gNotes: return if msg notin gNotes: return
writeContext(unknownLineInfo()) writeContext(unknownLineInfo())
frmt = RawWarning title = WarningTitle
color = WarningColor
kind = WarningsToStr[ord(msg) - ord(warnMin)]
inc(gWarnCounter) inc(gWarnCounter)
color = fgYellow
of hintMin..hintMax: of hintMin..hintMax:
if optHints notin gOptions: return if optHints notin gOptions: return
if msg notin gNotes: return if msg notin gNotes: return
frmt = RawHint title = HintTitle
color = HintColor
kind = HintsToStr[ord(msg) - ord(hintMin)]
inc(gHintCounter) inc(gHintCounter)
color = fgGreen
let s = `%`(msgKindToString(msg), args) let s = `%`(msgKindToString(msg), args)
if not ignoreMsgBecauseOfIdeTools(msg): if not ignoreMsgBecauseOfIdeTools(msg):
msgWriteln(s, color, frmt) if kind != nil:
styledMsgWriteln(color, title, resetStyle, s,
KindColor, `%`(KindFormat, kind))
else:
styledMsgWriteln(color, title, resetStyle, s)
handleError(msg, doAbort, s) handleError(msg, doAbort, s)
proc rawMessage*(msg: TMsgKind, arg: string) = proc rawMessage*(msg: TMsgKind, arg: string) =
@ -795,47 +839,56 @@ proc writeSurroundingSrc(info: TLineInfo) =
msgWriteln(indent & spaces(info.col) & '^') msgWriteln(indent & spaces(info.col) & '^')
proc formatMsg*(info: TLineInfo, msg: TMsgKind, arg: string): string = proc formatMsg*(info: TLineInfo, msg: TMsgKind, arg: string): string =
let frmt = case msg let title = case msg
of warnMin..warnMax: PosWarningFormat of warnMin..warnMax: WarningTitle
of hintMin..hintMax: PosHintFormat of hintMin..hintMax: HintTitle
else: PosErrorFormat else: ErrorTitle
result = frmt % [toMsgFilename(info), coordToStr(info.line), result = PosFormat % [toMsgFilename(info), coordToStr(info.line),
coordToStr(info.col+1), getMessageStr(msg, arg)] coordToStr(info.col+1)] &
title &
getMessageStr(msg, arg)
proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string, proc liMessage(info: TLineInfo, msg: TMsgKind, arg: string,
eh: TErrorHandling) = eh: TErrorHandling) =
var var
frmt: string title: string
ignoreMsg = false
color: ForegroundColor color: ForegroundColor
kind: string
ignoreMsg = false
case msg case msg
of errMin..errMax: of errMin..errMax:
writeContext(info) writeContext(info)
frmt = PosErrorFormat title = ErrorTitle
color = ErrorColor
# we try to filter error messages so that not two error message # we try to filter error messages so that not two error message
# in the same file and line are produced: # in the same file and line are produced:
#ignoreMsg = lastError == info and eh != doAbort #ignoreMsg = lastError == info and eh != doAbort
lastError = info lastError = info
color = fgRed
of warnMin..warnMax: of warnMin..warnMax:
ignoreMsg = optWarns notin gOptions or msg notin gNotes ignoreMsg = optWarns notin gOptions or msg notin gNotes
if not ignoreMsg: writeContext(info) if not ignoreMsg: writeContext(info)
frmt = PosWarningFormat title = WarningTitle
color = WarningColor
kind = WarningsToStr[ord(msg) - ord(warnMin)]
inc(gWarnCounter) inc(gWarnCounter)
color = fgYellow
of hintMin..hintMax: of hintMin..hintMax:
ignoreMsg = optHints notin gOptions or msg notin gNotes ignoreMsg = optHints notin gOptions or msg notin gNotes
frmt = PosHintFormat title = HintTitle
color = HintColor
kind = HintsToStr[ord(msg) - ord(hintMin)]
inc(gHintCounter) inc(gHintCounter)
color = fgGreen
# NOTE: currently line info line numbers start with 1, # NOTE: currently line info line numbers start with 1,
# but column numbers start with 0, however most editors expect # but column numbers start with 0, however most editors expect
# first column to be 1, so we need to +1 here # first column to be 1, so we need to +1 here
let x = frmt % [toMsgFilename(info), coordToStr(info.line), let x = PosFormat % [toMsgFilename(info), coordToStr(info.line),
coordToStr(info.col+1)] coordToStr(info.col+1)]
let s = getMessageStr(msg, arg) let s = getMessageStr(msg, arg)
if not ignoreMsg and not ignoreMsgBecauseOfIdeTools(msg): if not ignoreMsg and not ignoreMsgBecauseOfIdeTools(msg):
msgWriteln(s, color, x) if kind != nil:
styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s,
KindColor, `%`(KindFormat, kind))
else:
styledMsgWriteln(styleBright, x, resetStyle, color, title, resetStyle, s)
if optPrintSurroundingSrc and msg in errMin..errMax: if optPrintSurroundingSrc and msg in errMin..errMax:
info.writeSurroundingSrc info.writeSurroundingSrc
handleError(msg, eh, s) handleError(msg, eh, s)
@ -907,3 +960,6 @@ ropes.errorHandler = proc (err: RopesError, msg: string, useWarning: bool) =
of rCannotOpenFile: of rCannotOpenFile:
rawMessage(if useWarning: warnCannotOpenFile else: errCannotOpenFile, msg) rawMessage(if useWarning: warnCannotOpenFile else: errCannotOpenFile, msg)
# enable colors by default on terminals
if terminal.isatty(stdout):
incl(gGlobalOptions, optUseColors)

View file

@ -406,22 +406,43 @@ proc isatty*(f: File): bool =
result = isatty(getFileHandle(f)) != 0'i32 result = isatty(getFileHandle(f)) != 0'i32
proc styledEchoProcessArg(s: string) = write stdout, s type
proc styledEchoProcessArg(style: Style) = setStyle({style}) TerminalCmd* = enum ## commands that can be expressed as arguments
proc styledEchoProcessArg(style: set[Style]) = setStyle style resetStyle ## reset attributes
proc styledEchoProcessArg(color: ForegroundColor) = setForegroundColor color
proc styledEchoProcessArg(color: BackgroundColor) = setBackgroundColor color template styledEchoProcessArg(s: string) = write stdout, s
template styledEchoProcessArg(style: Style) = setStyle({style})
template styledEchoProcessArg(style: set[Style]) = setStyle style
template styledEchoProcessArg(color: ForegroundColor) = setForegroundColor color
template styledEchoProcessArg(color: BackgroundColor) = setBackgroundColor color
template styledEchoProcessArg(cmd: TerminalCmd) =
when cmd == resetStyle:
resetAttributes()
macro styledEcho*(m: varargs[expr]): stmt = macro styledEcho*(m: varargs[expr]): stmt =
## to be documented. ## to be documented.
let m = callsite() let m = callsite()
var reset = false
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])) let item = m[i]
case item.kind
of nnkStrLit..nnkTripleStrLit:
if i == m.len - 1:
# optimize if string literal is last, just call writeln
result.add(newCall(bindSym"writeln", bindSym"stdout", item))
if reset: result.add(newCall(bindSym"resetAttributes"))
return
else:
# if it is string literal just call write, do not enable reset
result.add(newCall(bindSym"write", bindSym"stdout", item))
else:
result.add(newCall(bindSym"styledEchoProcessArg", item))
reset = true
result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n"))) result.add(newCall(bindSym"write", bindSym"stdout", newStrLitNode("\n")))
result.add(newCall(bindSym"resetAttributes")) if reset: result.add(newCall(bindSym"resetAttributes"))
when defined(nimdoc): when defined(nimdoc):
proc getch*(): char = proc getch*(): char =