/// \brief Highlight a SourceRange (with ~'s) for any characters on LineNo. static void highlightRange(const CharSourceRange &R, unsigned LineNo, FileID FID, const SourceColumnMap &map, std::string &CaretLine, const SourceManager &SM, const LangOptions &LangOpts) { if (!R.isValid()) return; SourceLocation Begin = R.getBegin(); SourceLocation End = R.getEnd(); unsigned StartLineNo = SM.getExpansionLineNumber(Begin); if (StartLineNo > LineNo || SM.getFileID(Begin) != FID) return; // No intersection. unsigned EndLineNo = SM.getExpansionLineNumber(End); if (EndLineNo < LineNo || SM.getFileID(End) != FID) return; // No intersection. // Compute the column number of the start. unsigned StartColNo = 0; if (StartLineNo == LineNo) { StartColNo = SM.getExpansionColumnNumber(Begin); if (StartColNo) --StartColNo; // Zero base the col #. } // Compute the column number of the end. unsigned EndColNo = map.getSourceLine().size(); if (EndLineNo == LineNo) { EndColNo = SM.getExpansionColumnNumber(End); if (EndColNo) { --EndColNo; // Zero base the col #. // Add in the length of the token, so that we cover multi-char tokens if // this is a token range. if (R.isTokenRange()) EndColNo += Lexer::MeasureTokenLength(End, SM, LangOpts); } else { EndColNo = CaretLine.size(); } } assert(StartColNo <= EndColNo && "Invalid range!"); // Check that a token range does not highlight only whitespace. if (R.isTokenRange()) { // Pick the first non-whitespace column. while (StartColNo < map.getSourceLine().size() && (map.getSourceLine()[StartColNo] == ' ' || map.getSourceLine()[StartColNo] == '\t')) StartColNo = map.startOfNextColumn(StartColNo); // Pick the last non-whitespace column. if (EndColNo > map.getSourceLine().size()) EndColNo = map.getSourceLine().size(); while (EndColNo-1 && (map.getSourceLine()[EndColNo-1] == ' ' || map.getSourceLine()[EndColNo-1] == '\t')) EndColNo = map.startOfPreviousColumn(EndColNo); // If the start/end passed each other, then we are trying to highlight a // range that just exists in whitespace, which must be some sort of other // bug. assert(StartColNo <= EndColNo && "Trying to highlight whitespace??"); } assert(StartColNo <= map.getSourceLine().size() && "Invalid range!"); assert(EndColNo <= map.getSourceLine().size() && "Invalid range!"); // Fill the range with ~'s. StartColNo = map.byteToContainingColumn(StartColNo); EndColNo = map.byteToContainingColumn(EndColNo); assert(StartColNo <= EndColNo && "Invalid range!"); if (CaretLine.size() < EndColNo) CaretLine.resize(EndColNo,' '); std::fill(CaretLine.begin()+StartColNo,CaretLine.begin()+EndColNo,'~'); }
/// \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); } }