@@ -106,28 +106,115 @@ string[string] errorFormatMap()
106
106
static string [string ] ret;
107
107
if (ret is null )
108
108
ret = [
109
- " github" : " ::{type2} file={filepath},line={line},endLine={endLine},col={column},endColumn={endColumn},title={Type2} ({name})::{message}"
109
+ " github" : " ::{type2} file={filepath},line={line},endLine={endLine},col={column},endColumn={endColumn},title={Type2} ({name})::{message}" ,
110
+ " pretty" : " \x1B [1m{filepath}({line}:{column}): {Type2}: \x1B [0m{message} \x1B [2m({name})\x1B [0m{context}{supplemental}"
110
111
];
111
112
return ret;
112
113
}
113
114
114
- void messageFunctionFormat (string format, Message message, bool isError )
115
+ private string formatBase (string format, Message.Diagnostic diagnostic, scope const ( ubyte )[] code, bool color )
115
116
{
116
117
auto s = format;
118
+ s = s.replace(" {filepath}" , diagnostic.fileName);
119
+ s = s.replace(" {line}" , to! string (diagnostic.startLine));
120
+ s = s.replace(" {column}" , to! string (diagnostic.startColumn));
121
+ s = s.replace(" {endLine}" , to! string (diagnostic.endLine));
122
+ s = s.replace(" {endColumn}" , to! string (diagnostic.endColumn));
123
+ s = s.replace(" {message}" , diagnostic.message);
124
+ s = s.replace(" {context}" , diagnostic.formatContext(cast (const (char )[]) code, color));
125
+ return s;
126
+ }
127
+
128
+ private string formatContext (Message.Diagnostic diagnostic, scope const (char )[] code, bool color)
129
+ {
130
+ import std.string : indexOf, lastIndexOf;
131
+
132
+ if (diagnostic.startIndex >= diagnostic.endIndex || diagnostic.endIndex > code.length
133
+ || diagnostic.startColumn >= diagnostic.endColumn || diagnostic.endColumn == 0 )
134
+ return null ;
135
+
136
+ auto lineStart = code.lastIndexOf(' \n ' , diagnostic.startIndex) + 1 ;
137
+ auto lineEnd = code.indexOf(' \n ' , diagnostic.endIndex);
138
+ if (lineEnd == - 1 )
139
+ lineEnd = code.length;
140
+
141
+ auto ret = appender! string ;
142
+ ret.reserve ((lineEnd - lineStart) + diagnostic.endColumn + (color ? 30 : 10 ));
143
+ ret ~= ' \n ' ;
144
+ if (color)
145
+ ret ~= " \x1B [m" ; // reset
146
+ ret ~= code[lineStart .. lineEnd].replace(' \t ' , ' ' );
147
+ ret ~= ' \n ' ;
148
+ if (color)
149
+ ret ~= " \x1B [0;33m" ; // reset, yellow
150
+ foreach (_; 0 .. diagnostic.startColumn - 1 )
151
+ ret ~= ' ' ;
152
+ foreach (_; 0 .. diagnostic.endColumn - diagnostic.startColumn)
153
+ ret ~= ' ^' ;
154
+ if (color)
155
+ ret ~= " \x1B [m" ; // reset
156
+ return ret.data;
157
+ }
158
+
159
+ version (Windows )
160
+ void enableColoredOutput ()
161
+ {
162
+ import core.sys.windows.windows ;
163
+
164
+ // Set output mode to handle virtual terminal sequences
165
+ HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE );
166
+ if (hOut == INVALID_HANDLE_VALUE )
167
+ return ;
168
+
169
+ DWORD dwMode;
170
+ if (! GetConsoleMode(hOut, &dwMode))
171
+ return ;
172
+
173
+ dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING ;
174
+ if (! SetConsoleMode(hOut, dwMode))
175
+ return ;
176
+ }
117
177
118
- s = s.replace(" {filepath}" , message.fileName);
119
- s = s.replace(" {line}" , to! string (message.startLine));
120
- s = s.replace(" {column}" , to! string (message.startColumn));
121
- s = s.replace(" {endLine}" , to! string (message.endLine));
122
- s = s.replace(" {endColumn}" , to! string (message.endColumn));
123
- s = s.replace(" {type}" , isError ? " error" : " warn" );
124
- s = s.replace(" {Type}" , isError ? " Error" : " Warn" );
125
- s = s.replace(" {TYPE}" , isError ? " ERROR" : " WARN" );
126
- s = s.replace(" {type2}" , isError ? " error" : " warning" );
127
- s = s.replace(" {Type2}" , isError ? " Error" : " Warning" );
128
- s = s.replace(" {TYPE2}" , isError ? " ERROR" : " WARNING" );
129
- s = s.replace(" {message}" , message.message);
130
- s = s.replace(" {name}" , message.checkName);
178
+ void messageFunctionFormat (string format, Message message, bool isError, scope const (ubyte )[] code = null )
179
+ {
180
+ bool color = format.canFind(" \x1B [" );
181
+ if (color)
182
+ {
183
+ version (Windows )
184
+ enableColoredOutput();
185
+ }
186
+
187
+ auto s = format.formatBase(message.diagnostic, code, color);
188
+
189
+ string formatType (string s, string type, string colorCode)
190
+ {
191
+ import std.ascii : toUpper;
192
+ import std.string : representation;
193
+
194
+ string upperFirst (string s) { return s[0 ].toUpper ~ s[1 .. $]; }
195
+ string upper (string s) { return s.representation.map! (a => toUpper(cast (char ) a)).array; }
196
+
197
+ string type2 = type;
198
+ if (type2 == " warn" )
199
+ type2 = " warning" ;
200
+
201
+ s = s.replace(" {type}" , color ? (colorCode ~ type ~ " \x1B [m" ) : type);
202
+ s = s.replace(" {Type}" , color ? (colorCode ~ upperFirst(type) ~ " \x1B [m" ) : upperFirst(type));
203
+ s = s.replace(" {TYPE}" , color ? (colorCode ~ upper(type) ~ " \x1B [m" ) : upper(type));
204
+ s = s.replace(" {type2}" , color ? (colorCode ~ type2 ~ " \x1B [m" ) : type2);
205
+ s = s.replace(" {Type2}" , color ? (colorCode ~ upperFirst(type2) ~ " \x1B [m" ) : upperFirst(type2));
206
+ s = s.replace(" {TYPE2}" , color ? (colorCode ~ upper(type2) ~ " \x1B [m" ) : upper(type2));
207
+
208
+ return s;
209
+ }
210
+
211
+ s = formatType(s, isError ? " error" : " warn" , isError ? " \x1B [31m" : " \x1B [33m" );
212
+ s = s.replace(" {name}" , message.checkName);
213
+ s = s.replace(" {supplemental}" , message.supplemental.map! (a => " \n\t "
214
+ ~ formatType(format.formatBase(a, code, color), " hint" , " \x1B [35m" )
215
+ .replace(" {name}" , " " ).replace(" {supplemental}" , " " )
216
+ .replace(" \n " , " \n\t " ))
217
+ .join());
131
218
132
219
writefln(" %s" , s);
133
220
}
@@ -303,7 +390,7 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, string error
303
390
foreach (result; results[])
304
391
{
305
392
hasErrors = true ;
306
- messageFunctionFormat(errorFormat, result, false );
393
+ messageFunctionFormat(errorFormat, result, false , code );
307
394
}
308
395
}
309
396
return hasErrors;
@@ -334,7 +421,7 @@ const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
334
421
// TODO: proper index and column ranges
335
422
return messageFunctionFormat (errorFormat,
336
423
Message(Message.Diagnostic.from(fileName, [0 , 0 ], line, [column, column], message), " dscanner.syntax" ),
337
- isError);
424
+ isError, code );
338
425
};
339
426
340
427
return parseModule (fileName, code, p, cache, tokens,
0 commit comments