private void RenderSourceLine(SourceLine sourceLine) { var xOffset = buffer.CursorX; // Print the source line with a default color buffer.ForegroundColor = ConsoleColor.White; var line = sourceLine.Source.Line(sourceLine.Line); var lineCur = new LineCursor { TabSize = TabSize }; foreach (var ch in line) { if (ch == '\r' || ch == '\n') { break; } if (lineCur.Append(ch, out var advance)) { buffer.CursorX += advance; } else { buffer.Write(ch); } } // Do syntax highlighting, if needed var tokenInfo = SyntaxHighlighter.GetHighlightingForLine(sourceLine.Source, sourceLine.Line).ToList(); if (tokenInfo.Count > 0) { // There are tokens to highlight var charIdx = 0; lineCur.Column = 0; var tokenInfoList = tokenInfo.OrderBy(ti => ti.StartIndex).ToList(); foreach (var token in tokenInfoList) { // Skip until the next token for (; charIdx < token.StartIndex; ++charIdx) { lineCur.Append(line[charIdx], out var advance); } // Go through the token var tokenStart = lineCur.Column; for (; charIdx < token.StartIndex + token.Length; ++charIdx) { lineCur.Append(line[charIdx], out var advance); } var tokenEnd = lineCur.Column; // Recolor the token buffer.ForegroundColor = TokenKindToColor(token.Kind); buffer.Recolor(xOffset + tokenStart, buffer.CursorY, tokenEnd - tokenStart, 1); } } buffer.ResetColor(); }
private void RenderAnnotationLines(AnnotationLine annotationLine, string prefix) { var sourceFile = annotationLine.Annotations.First().Span.Source; Debug.Assert(sourceFile != null); var line = sourceFile.Line(annotationLine.AnnotatedLine).TrimEnd(); // Order annotations by starting position var annotationsOrdered = annotationLine.Annotations.OrderBy(si => si.Span.Start).ToList(); // Now we draw the arrows to their correct places under the annotated line // Also collect physical column positions to extend the arrows var arrowHeadColumns = new List <(int Column, SpannedDiagnosticInfo Info)>(); buffer.Write(prefix); var lineCur = new LineCursor { TabSize = TabSize }; var charIdx = 0; foreach (var annot in annotationsOrdered) { // From the last character index until the start of this annotation we need to fill with spaces for (; charIdx < annot.Span.Start.Column; ++charIdx) { if (charIdx < line.Length) { // Still in range of the line lineCur.Append(line[charIdx], out var advance); buffer.CursorX += advance; } else { // After the line buffer.CursorX += 1; } } // Now we are inside the span var arrowHead = annot.Severity != null ? '^' : '-'; var startColumn = buffer.CursorX; arrowHeadColumns.Add((startColumn, annot)); if (annot.Severity != null) { buffer.ForegroundColor = annot.Severity.Color; } for (; charIdx < annot.Span.End.Column; ++charIdx) { if (charIdx < line.Length) { // Still in range of the line lineCur.Append(line[charIdx], out var advance); for (int i = 0; i < advance; ++i) { buffer.Write(arrowHead); } } else { // After the line buffer.Write(arrowHead); } } var endColumn = buffer.CursorX; if (annot.Severity != null) { // Recolor the source line too buffer.Recolor(startColumn, buffer.CursorY - 1, endColumn - startColumn, 1); buffer.ResetColor(); } } // Now we are done with arrows in the line, it's time to do the arrow bodies downwards // The first one will have N, the last 0 length bodies, decreasing by one // The last one just has the message inline { var lastAnnot = annotationsOrdered.Last(); if (lastAnnot.Message != null) { buffer.Write($" {lastAnnot.Message}"); } buffer.WriteLine(); } // From now on all previous ones will be one longer than the ones later int arrowBaseLine = buffer.CursorY; int arrowBodyLength = 0; // We only consider annotations with messages foreach (var(col, annot) in arrowHeadColumns.SkipLast(1).Reverse().Where(a => a.Info.Message != null)) { if (annot.Severity != null) { buffer.ForegroundColor = annot.Severity.Color; } // Draw the arrow buffer.Fill(col, arrowBaseLine, 1, arrowBodyLength, '│'); buffer.Plot(col, arrowBaseLine + arrowBodyLength, '└'); arrowBodyLength += 1; // Append the message buffer.Write($" {annot.Message}"); if (annot.Severity != null) { buffer.ResetColor(); } } // Fill the in between lines with the prefix for (int i = 0; i < arrowBodyLength; ++i) { buffer.WriteAt(0, arrowBaseLine + i, prefix); } // Reset cursor position buffer.CursorX = 0; buffer.CursorY = arrowBaseLine + arrowBodyLength; }