/// \brief When the source code line we want to print is too long for /// the terminal, select the "interesting" region. static void selectInterestingSourceRegion(std::string &SourceLine, std::string &CaretLine, std::string &FixItInsertionLine, unsigned Columns, const SourceColumnMap &map) { unsigned MaxColumns = std::max<unsigned>(map.columns(), std::max(CaretLine.size(), FixItInsertionLine.size())); // if the number of columns is less than the desired number we're done if (MaxColumns <= Columns) return; // no special characters allowed in CaretLine or FixItInsertionLine assert(CaretLine.end() == std::find_if(CaretLine.begin(), CaretLine.end(), char_out_of_range(' ','~'))); assert(FixItInsertionLine.end() == std::find_if(FixItInsertionLine.begin(), FixItInsertionLine.end(), char_out_of_range(' ','~'))); // Find the slice that we need to display the full caret line // correctly. unsigned CaretStart = 0, CaretEnd = CaretLine.size(); for (; CaretStart != CaretEnd; ++CaretStart) if (!isspace(static_cast<unsigned char>(CaretLine[CaretStart]))) break; for (; CaretEnd != CaretStart; --CaretEnd) if (!isspace(static_cast<unsigned char>(CaretLine[CaretEnd - 1]))) break; // caret has already been inserted into CaretLine so the above whitespace // check is guaranteed to include the caret // If we have a fix-it line, make sure the slice includes all of the // fix-it information. if (!FixItInsertionLine.empty()) { unsigned FixItStart = 0, FixItEnd = FixItInsertionLine.size(); for (; FixItStart != FixItEnd; ++FixItStart) if (!isspace(static_cast<unsigned char>(FixItInsertionLine[FixItStart]))) break; for (; FixItEnd != FixItStart; --FixItEnd) if (!isspace(static_cast<unsigned char>(FixItInsertionLine[FixItEnd - 1]))) break; CaretStart = std::min(FixItStart, CaretStart); CaretEnd = std::max(FixItEnd, CaretEnd); } // CaretEnd may have been set at the middle of a character // If it's not at a character's first column then advance it past the current // character. while (static_cast<int>(CaretEnd) < map.columns() && -1 == map.columnToByte(CaretEnd)) ++CaretEnd; assert((static_cast<int>(CaretStart) > map.columns() || -1!=map.columnToByte(CaretStart)) && "CaretStart must not point to a column in the middle of a source" " line character"); assert((static_cast<int>(CaretEnd) > map.columns() || -1!=map.columnToByte(CaretEnd)) && "CaretEnd must not point to a column in the middle of a source line" " character"); // CaretLine[CaretStart, CaretEnd) contains all of the interesting // parts of the caret line. While this slice is smaller than the // number of columns we have, try to grow the slice to encompass // more context. unsigned SourceStart = map.columnToByte(std::min<unsigned>(CaretStart, map.columns())); unsigned SourceEnd = map.columnToByte(std::min<unsigned>(CaretEnd, map.columns())); unsigned CaretColumnsOutsideSource = CaretEnd-CaretStart - (map.byteToColumn(SourceEnd)-map.byteToColumn(SourceStart)); char const *front_ellipse = " ..."; char const *front_space = " "; char const *back_ellipse = "..."; unsigned ellipses_space = strlen(front_ellipse) + strlen(back_ellipse); unsigned TargetColumns = Columns; // Give us extra room for the ellipses // and any of the caret line that extends past the source if (TargetColumns > ellipses_space+CaretColumnsOutsideSource) TargetColumns -= ellipses_space+CaretColumnsOutsideSource; while (SourceStart>0 || SourceEnd<SourceLine.size()) { bool ExpandedRegion = false; if (SourceStart>0) { unsigned NewStart = map.startOfPreviousColumn(SourceStart); // Skip over any whitespace we see here; we're looking for // another bit of interesting text. // FIXME: Detect non-ASCII whitespace characters too. while (NewStart && isspace(static_cast<unsigned char>(SourceLine[NewStart]))) NewStart = map.startOfPreviousColumn(NewStart); // Skip over this bit of "interesting" text. while (NewStart) { unsigned Prev = map.startOfPreviousColumn(NewStart); if (isspace(static_cast<unsigned char>(SourceLine[Prev]))) break; NewStart = Prev; } assert(map.byteToColumn(NewStart) != -1); unsigned NewColumns = map.byteToColumn(SourceEnd) - map.byteToColumn(NewStart); if (NewColumns <= TargetColumns) { SourceStart = NewStart; ExpandedRegion = true; } } if (SourceEnd<SourceLine.size()) { unsigned NewEnd = map.startOfNextColumn(SourceEnd); // Skip over any whitespace we see here; we're looking for // another bit of interesting text. // FIXME: Detect non-ASCII whitespace characters too. while (NewEnd < SourceLine.size() && isspace(static_cast<unsigned char>(SourceLine[NewEnd]))) NewEnd = map.startOfNextColumn(NewEnd); // Skip over this bit of "interesting" text. while (NewEnd < SourceLine.size() && !isspace(static_cast<unsigned char>(SourceLine[NewEnd]))) NewEnd = map.startOfNextColumn(NewEnd); assert(map.byteToColumn(NewEnd) != -1); unsigned NewColumns = map.byteToColumn(NewEnd) - map.byteToColumn(SourceStart); if (NewColumns <= TargetColumns) { SourceEnd = NewEnd; ExpandedRegion = true; } } if (!ExpandedRegion) break; } CaretStart = map.byteToColumn(SourceStart); CaretEnd = map.byteToColumn(SourceEnd) + CaretColumnsOutsideSource; // [CaretStart, CaretEnd) is the slice we want. Update the various // output lines to show only this slice, with two-space padding // before the lines so that it looks nicer. assert(CaretStart!=(unsigned)-1 && CaretEnd!=(unsigned)-1 && SourceStart!=(unsigned)-1 && SourceEnd!=(unsigned)-1); assert(SourceStart <= SourceEnd); assert(CaretStart <= CaretEnd); unsigned BackColumnsRemoved = map.byteToColumn(SourceLine.size())-map.byteToColumn(SourceEnd); unsigned FrontColumnsRemoved = CaretStart; unsigned ColumnsKept = CaretEnd-CaretStart; // We checked up front that the line needed truncation assert(FrontColumnsRemoved+ColumnsKept+BackColumnsRemoved > Columns); // The line needs some trunctiona, and we'd prefer to keep the front // if possible, so remove the back if (BackColumnsRemoved > strlen(back_ellipse)) SourceLine.replace(SourceEnd, std::string::npos, back_ellipse); // If that's enough then we're done if (FrontColumnsRemoved+ColumnsKept <= Columns) return; // Otherwise remove the front as well if (FrontColumnsRemoved > strlen(front_ellipse)) { SourceLine.replace(0, SourceStart, front_ellipse); CaretLine.replace(0, CaretStart, front_space); if (!FixItInsertionLine.empty()) FixItInsertionLine.replace(0, CaretStart, front_space); } }
/// \brief Emit a code snippet and caret line. /// /// This routine emits a single line's code snippet and caret line.. /// /// \param Loc The location for the caret. /// \param Ranges The underlined ranges for this code snippet. /// \param Hints The FixIt hints active for this diagnostic. void TextDiagnostic::emitSnippetAndCaret( SourceLocation Loc, DiagnosticsEngine::Level Level, SmallVectorImpl<CharSourceRange>& Ranges, ArrayRef<FixItHint> Hints, const SourceManager &SM) { assert(Loc.isValid() && "must have a valid source location here"); assert(Loc.isFileID() && "must have a file location here"); // If caret diagnostics are enabled and we have location, we want to // emit the caret. However, we only do this if the location moved // from the last diagnostic, if the last diagnostic was a note that // was part of a different warning or error diagnostic, or if the // diagnostic has ranges. We don't want to emit the same caret // multiple times if one loc has multiple diagnostics. if (!DiagOpts->ShowCarets) return; if (Loc == LastLoc && Ranges.empty() && Hints.empty() && (LastLevel != DiagnosticsEngine::Note || Level == LastLevel)) return; // Decompose the location into a FID/Offset pair. std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc); FileID FID = LocInfo.first; unsigned FileOffset = LocInfo.second; // Get information about the buffer it points into. bool Invalid = false; StringRef BufData = SM.getBufferData(FID, &Invalid); if (Invalid) return; const char *BufStart = BufData.data(); const char *BufEnd = BufStart + BufData.size(); unsigned LineNo = SM.getLineNumber(FID, FileOffset); unsigned ColNo = SM.getColumnNumber(FID, FileOffset); // Arbitrarily stop showing snippets when the line is too long. static const size_t MaxLineLengthToPrint = 4096; if (ColNo > MaxLineLengthToPrint) return; // Rewind from the current position to the start of the line. const char *TokPtr = BufStart+FileOffset; const char *LineStart = TokPtr-ColNo+1; // Column # is 1-based. // Compute the line end. Scan forward from the error position to the end of // the line. const char *LineEnd = TokPtr; while (*LineEnd != '\n' && *LineEnd != '\r' && LineEnd != BufEnd) ++LineEnd; // Arbitrarily stop showing snippets when the line is too long. if (size_t(LineEnd - LineStart) > MaxLineLengthToPrint) return; // Trim trailing null-bytes. StringRef Line(LineStart, LineEnd - LineStart); while (Line.size() > ColNo && Line.back() == '\0') Line = Line.drop_back(); // Copy the line of code into an std::string for ease of manipulation. std::string SourceLine(Line.begin(), Line.end()); // Build the byte to column map. const SourceColumnMap sourceColMap(SourceLine, DiagOpts->TabStop); // Create a line for the caret that is filled with spaces that is the same // number of columns as the line of source code. std::string CaretLine(sourceColMap.columns(), ' '); // Highlight all of the characters covered by Ranges with ~ characters. for (SmallVectorImpl<CharSourceRange>::iterator I = Ranges.begin(), E = Ranges.end(); I != E; ++I) highlightRange(*I, LineNo, FID, sourceColMap, CaretLine, SM, LangOpts); // Next, insert the caret itself. ColNo = sourceColMap.byteToContainingColumn(ColNo-1); if (CaretLine.size()<ColNo+1) CaretLine.resize(ColNo+1, ' '); CaretLine[ColNo] = '^'; std::string FixItInsertionLine = buildFixItInsertionLine(LineNo, sourceColMap, Hints, SM, DiagOpts.get()); // If the source line is too long for our terminal, select only the // "interesting" source region within that line. unsigned Columns = DiagOpts->MessageLength; if (Columns) selectInterestingSourceRegion(SourceLine, CaretLine, FixItInsertionLine, Columns, sourceColMap); // If we are in -fdiagnostics-print-source-range-info mode, we are trying // to produce easily machine parsable output. Add a space before the // source line and the caret to make it trivial to tell the main diagnostic // line from what the user is intended to see. if (DiagOpts->ShowSourceRanges) { SourceLine = ' ' + SourceLine; CaretLine = ' ' + CaretLine; } // Finally, remove any blank spaces from the end of CaretLine. while (CaretLine[CaretLine.size()-1] == ' ') CaretLine.erase(CaretLine.end()-1); // Emit what we have computed. emitSnippet(SourceLine); if (DiagOpts->ShowColors) OS.changeColor(caretColor, true); OS << CaretLine << '\n'; if (DiagOpts->ShowColors) OS.resetColor(); if (!FixItInsertionLine.empty()) { if (DiagOpts->ShowColors) // Print fixit line in color OS.changeColor(fixitColor, false); if (DiagOpts->ShowSourceRanges) OS << ' '; OS << FixItInsertionLine << '\n'; if (DiagOpts->ShowColors) OS.resetColor(); } // Print out any parseable fixit information requested by the options. emitParseableFixits(Hints, SM); }