Files
swift-mirror/lib/Markup/LineList.cpp
Ashley Garland edd7fdb73f Fix bug in LineList end locations
LineList Line end locations were getting cut off too early because they were
based on the untrimmed (w.r.t. comment markers) locations, instead of the
trimmed one.
2020-02-17 16:21:01 -08:00

189 lines
5.9 KiB
C++

//===--- LineList.cpp - Data structures for Markup parsing ----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "swift/AST/RawComment.h"
#include "swift/Basic/PrimitiveParsing.h"
#include "swift/Markup/LineList.h"
#include "swift/Markup/Markup.h"
using namespace swift;
using namespace markup;
std::string LineList::str() const {
std::string Result;
llvm::raw_string_ostream Stream(Result);
if (Lines.empty())
return "";
auto FirstLine = Lines.begin();
while (FirstLine != Lines.end() && FirstLine->Text.empty())
++FirstLine;
if (FirstLine == Lines.end())
return "";
auto InitialIndentation = measureIndentation(FirstLine->Text);
for (auto Line = FirstLine; Line != Lines.end(); ++Line) {
auto Drop = std::min(InitialIndentation, Line->FirstNonspaceOffset);
Stream << Line->Text.drop_front(Drop) << "\n";
}
Stream.flush();
return Result;
}
size_t swift::markup::measureIndentation(StringRef Text) {
size_t Col = 0;
for (size_t i = 0, e = Text.size(); i != e; ++i) {
if (Text[i] == ' ' || Text[i] == '\v' || Text[i] == '\f') {
Col++;
continue;
}
if (Text[i] == '\t') {
Col += ((i + 8) / 8) * 8;
continue;
}
return i;
}
return Text.size();
}
void LineListBuilder::addLine(llvm::StringRef Text, swift::SourceRange Range) {
Lines.push_back({Text, Range});
}
LineList LineListBuilder::takeLineList() const {
return LineList(Context.allocateCopy(ArrayRef<Line>(Lines)));
}
static unsigned measureASCIIArt(StringRef S, unsigned NumLeadingSpaces) {
StringRef Spaces = S.substr(0, NumLeadingSpaces);
if (Spaces.size() != NumLeadingSpaces)
return 0;
if (Spaces.find_first_not_of(' ') != StringRef::npos)
return 0;
S = S.drop_front(NumLeadingSpaces);
if (S.startswith(" * "))
return NumLeadingSpaces + 3;
if (S.startswith(" *\n") || S.startswith(" *\n\r"))
return NumLeadingSpaces + 2;
return 0;
}
LineList MarkupContext::getLineList(swift::RawComment RC) {
LineListBuilder Builder(*this);
for (const auto &C : RC.Comments) {
if (C.isLine()) {
// Skip comment marker.
unsigned CommentMarkerBytes = 2 + (C.isOrdinary() ? 0 : 1);
StringRef Cleaned = C.RawText.drop_front(CommentMarkerBytes);
// Drop trailing newline.
Cleaned = Cleaned.rtrim("\n\r");
auto CleanedStartLoc =
C.Range.getStart().getAdvancedLocOrInvalid(CommentMarkerBytes);
auto CleanedEndLoc =
CleanedStartLoc.getAdvancedLocOrInvalid(Cleaned.size());
Builder.addLine(Cleaned, { CleanedStartLoc, CleanedEndLoc });
} else {
// Skip comment markers at the beginning and at the end.
unsigned CommentMarkerBytes = 2 + (C.isOrdinary() ? 0 : 1);
StringRef Cleaned = C.RawText.drop_front(CommentMarkerBytes);
if (Cleaned.endswith("*/"))
Cleaned = Cleaned.drop_back(2);
else if (Cleaned.endswith("/"))
Cleaned = Cleaned.drop_back(1);
swift::SourceLoc CleanedStartLoc =
C.Range.getStart().getAdvancedLocOrInvalid(CommentMarkerBytes);
// Determine if we have leading decorations in this block comment.
bool HasASCIIArt = false;
if (swift::startsWithNewline(Cleaned)) {
unsigned NewlineBytes = swift::measureNewline(Cleaned);
Cleaned = Cleaned.drop_front(NewlineBytes);
CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(NewlineBytes);
HasASCIIArt = measureASCIIArt(Cleaned, C.StartColumn - 1) != 0;
}
while (!Cleaned.empty()) {
size_t Pos = Cleaned.find_first_of("\n\r");
if (Pos == StringRef::npos)
Pos = Cleaned.size();
// Skip over ASCII art, if present.
if (HasASCIIArt)
if (unsigned ASCIIArtBytes =
measureASCIIArt(Cleaned, C.StartColumn - 1)) {
Cleaned = Cleaned.drop_front(ASCIIArtBytes);
CleanedStartLoc =
CleanedStartLoc.getAdvancedLocOrInvalid(ASCIIArtBytes);
Pos -= ASCIIArtBytes;
}
StringRef Line = Cleaned.substr(0, Pos);
auto CleanedEndLoc = CleanedStartLoc.getAdvancedLocOrInvalid(Pos);
Builder.addLine(Line, { CleanedStartLoc, CleanedEndLoc });
Cleaned = Cleaned.drop_front(Pos);
unsigned NewlineBytes = swift::measureNewline(Cleaned);
Cleaned = Cleaned.drop_front(NewlineBytes);
Pos += NewlineBytes;
CleanedStartLoc = CleanedStartLoc.getAdvancedLocOrInvalid(Pos);
}
}
}
return Builder.takeLineList();
}
LineList LineList::subListWithRange(MarkupContext &MC, size_t StartLine,
size_t EndLine, size_t StartColumn,
size_t EndColumn) const {
auto TrimmedLines = ArrayRef<Line>(Lines.begin() + StartLine,
Lines.begin() + EndLine);
LineListBuilder Builder(MC);
if (TrimmedLines.empty())
return LineList();
auto FirstLine = TrimmedLines.begin();
auto End = TrimmedLines.end();
auto LastLine = End - 1;
for (auto Line = FirstLine; Line != End; ++Line) {
auto T = Line->Text;
auto RangeStart = Line->Range.Start;
auto RangeEnd = Line->Range.End;
if (Line == LastLine) {
T = T.drop_back(T.size() - EndColumn);
RangeEnd = RangeStart.getAdvancedLocOrInvalid(EndColumn);
}
if (Line == FirstLine) {
T = T.drop_front(StartColumn);
RangeStart = RangeStart.getAdvancedLocOrInvalid(StartColumn);
}
Builder.addLine(T, {RangeStart, RangeEnd});
}
return Builder.takeLineList();
}