@@ -241,6 +241,101 @@ namespace ts {
241
241
return output ;
242
242
}
243
243
244
+ const redForegroundEscapeSequence = "\u001b[91m" ;
245
+ const yellowForegroundEscapeSequence = "\u001b[93m" ;
246
+ const blueForegroundEscapeSequence = "\u001b[93m" ;
247
+ const gutterStyleSequence = "\u001b[100;30m" ;
248
+ const gutterSeparator = " " ;
249
+ const resetEscapeSequence = "\u001b[0m" ;
250
+ const ellipsis = "..." ;
251
+ function getCategoryFormat ( category : DiagnosticCategory ) : string {
252
+ switch ( category ) {
253
+ case DiagnosticCategory . Warning : return yellowForegroundEscapeSequence ;
254
+ case DiagnosticCategory . Error : return redForegroundEscapeSequence ;
255
+ case DiagnosticCategory . Message : return blueForegroundEscapeSequence ;
256
+ }
257
+ }
258
+
259
+ function formatAndReset ( text : string , formatStyle : string ) {
260
+ return formatStyle + text + resetEscapeSequence ;
261
+ }
262
+
263
+ function padLeft ( s : string , length : number ) {
264
+ while ( s . length < length ) {
265
+ s = " " + s ;
266
+ }
267
+ return s ;
268
+ }
269
+
270
+ export function formatDiagnosticsWithColorAndContext ( diagnostics : Diagnostic [ ] , host : FormatDiagnosticsHost ) : string {
271
+ let output = "" ;
272
+ for ( const diagnostic of diagnostics ) {
273
+ if ( diagnostic . file ) {
274
+ const { start, length, file } = diagnostic ;
275
+ const { line : firstLine , character : firstLineChar } = getLineAndCharacterOfPosition ( file , start ) ;
276
+ const { line : lastLine , character : lastLineChar } = getLineAndCharacterOfPosition ( file , start + length ) ;
277
+ const lastLineInFile = getLineAndCharacterOfPosition ( file , file . text . length ) . line ;
278
+ const relativeFileName = host ? convertToRelativePath ( file . fileName , host . getCurrentDirectory ( ) , fileName => host . getCanonicalFileName ( fileName ) ) : file . fileName ;
279
+
280
+ const hasMoreThanFiveLines = ( lastLine - firstLine ) >= 4 ;
281
+ let gutterWidth = ( lastLine + 1 + "" ) . length ;
282
+ if ( hasMoreThanFiveLines ) {
283
+ gutterWidth = Math . max ( ellipsis . length , gutterWidth ) ;
284
+ }
285
+
286
+ output += sys . newLine ;
287
+ for ( let i = firstLine ; i <= lastLine ; i ++ ) {
288
+ // If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
289
+ // so we'll skip ahead to the second-to-last line.
290
+ if ( hasMoreThanFiveLines && firstLine + 1 < i && i < lastLine - 1 ) {
291
+ output += formatAndReset ( padLeft ( ellipsis , gutterWidth ) , gutterStyleSequence ) + gutterSeparator + sys . newLine ;
292
+ i = lastLine - 1 ;
293
+ }
294
+
295
+ const lineStart = getPositionOfLineAndCharacter ( file , i , 0 ) ;
296
+ const lineEnd = i < lastLineInFile ? getPositionOfLineAndCharacter ( file , i + 1 , 0 ) : file . text . length ;
297
+ let lineContent = file . text . slice ( lineStart , lineEnd ) ;
298
+ lineContent = lineContent . replace ( / \s + $ / g, "" ) ; // trim from end
299
+ lineContent = lineContent . replace ( "\t" , " " ) ; // convert tabs to single spaces
300
+
301
+ // Output the gutter and the actual contents of the line.
302
+ output += formatAndReset ( padLeft ( i + 1 + "" , gutterWidth ) , gutterStyleSequence ) + gutterSeparator ;
303
+ output += lineContent + sys . newLine ;
304
+
305
+ // Output the gutter and the error span for the line using tildes.
306
+ output += formatAndReset ( padLeft ( "" , gutterWidth ) , gutterStyleSequence ) + gutterSeparator ;
307
+ output += redForegroundEscapeSequence ;
308
+ if ( i === firstLine ) {
309
+ // If we're on the last line, then limit it to the last character of the last line.
310
+ // Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
311
+ const lastCharForLine = i === lastLine ? lastLineChar : undefined ;
312
+
313
+ output += lineContent . slice ( 0 , firstLineChar ) . replace ( / \S / g, " " ) ;
314
+ output += lineContent . slice ( firstLineChar , lastCharForLine ) . replace ( / ./ g, "~" ) ;
315
+ }
316
+ else if ( i === lastLine ) {
317
+ output += lineContent . slice ( 0 , lastLineChar ) . replace ( / ./ g, "~" ) ;
318
+ }
319
+ else {
320
+ // Squiggle the entire line.
321
+ output += lineContent . replace ( / ./ g, "~" ) ;
322
+ }
323
+ output += resetEscapeSequence ;
324
+
325
+ output += sys . newLine ;
326
+ }
327
+
328
+ output += sys . newLine ;
329
+ output += `${ relativeFileName } (${ firstLine + 1 } ,${ firstLineChar + 1 } ): ` ;
330
+ }
331
+
332
+ const categoryColor = getCategoryFormat ( diagnostic . category ) ;
333
+ const category = DiagnosticCategory [ diagnostic . category ] . toLowerCase ( ) ;
334
+ output += `${ formatAndReset ( category , categoryColor ) } TS${ diagnostic . code } : ${ flattenDiagnosticMessageText ( diagnostic . messageText , sys . newLine ) } ` ;
335
+ }
336
+ return output ;
337
+ }
338
+
244
339
export function flattenDiagnosticMessageText ( messageText : string | DiagnosticMessageChain , newLine : string ) : string {
245
340
if ( typeof messageText === "string" ) {
246
341
return messageText ;
0 commit comments