blob: 320913c2d46f096c02c9ac107d5746844d710a6f [file] [log] [blame]
Alexander Kornienko70ce7882013-04-15 14:28:00 +00001//===--- BreakableToken.cpp - Format C++ code -----------------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9///
10/// \file
11/// \brief Contains implementation of BreakableToken class and classes derived
12/// from it.
13///
14//===----------------------------------------------------------------------===//
15
16#include "BreakableToken.h"
Alexander Kornienko919398b2013-04-17 17:34:05 +000017#include "llvm/ADT/STLExtras.h"
Alexander Kornienko70ce7882013-04-15 14:28:00 +000018#include <algorithm>
19
20namespace clang {
21namespace format {
22
Alexander Kornienko919398b2013-04-17 17:34:05 +000023BreakableToken::Split BreakableComment::getSplit(unsigned LineIndex,
24 unsigned TailOffset,
25 unsigned ColumnLimit) const {
26 StringRef Text = getLine(LineIndex).substr(TailOffset);
27 unsigned ContentStartColumn = getContentStartColumn(LineIndex, TailOffset);
28 if (ColumnLimit <= ContentStartColumn + 1)
29 return Split(StringRef::npos, 0);
30
31 unsigned MaxSplit = ColumnLimit - ContentStartColumn + 1;
32 StringRef::size_type SpaceOffset = Text.rfind(' ', MaxSplit);
33 if (SpaceOffset == StringRef::npos ||
34 Text.find_last_not_of(' ', SpaceOffset) == StringRef::npos) {
35 SpaceOffset = Text.find(' ', MaxSplit);
36 }
37 if (SpaceOffset != StringRef::npos && SpaceOffset != 0) {
38 StringRef BeforeCut = Text.substr(0, SpaceOffset).rtrim();
39 StringRef AfterCut = Text.substr(SpaceOffset).ltrim();
40 return BreakableToken::Split(BeforeCut.size(),
41 AfterCut.begin() - BeforeCut.end());
42 }
43 return BreakableToken::Split(StringRef::npos, 0);
44}
45
46void BreakableComment::insertBreak(unsigned LineIndex, unsigned TailOffset,
47 Split Split, bool InPPDirective,
48 WhitespaceManager &Whitespaces) {
49 StringRef Text = getLine(LineIndex).substr(TailOffset);
50 StringRef AdditionalPrefix = Decoration;
51 if (Text.size() == Split.first + Split.second) {
52 // For all but the last line handle trailing space in trimLine.
53 if (LineIndex < Lines.size() - 1)
54 return;
55 // For the last line we need to break before "*/", but not to add "* ".
56 AdditionalPrefix = "";
57 }
58
Alexander Kornienko919398b2013-04-17 17:34:05 +000059 unsigned BreakOffset = Text.data() - TokenText.data() + Split.first;
60 unsigned CharsToRemove = Split.second;
61 Whitespaces.breakToken(Tok, BreakOffset, CharsToRemove, "", AdditionalPrefix,
Manuel Klimeke573c3f2013-05-22 12:51:29 +000062 InPPDirective, IndentAtLineBreak);
Alexander Kornienko919398b2013-04-17 17:34:05 +000063}
64
Alexander Kornienko70ce7882013-04-15 14:28:00 +000065BreakableBlockComment::BreakableBlockComment(const SourceManager &SourceMgr,
66 const AnnotatedToken &Token,
67 unsigned StartColumn)
Alexander Kornienko919398b2013-04-17 17:34:05 +000068 : BreakableComment(SourceMgr, Token.FormatTok, StartColumn + 2) {
Alexander Kornienko70ce7882013-04-15 14:28:00 +000069 assert(TokenText.startswith("/*") && TokenText.endswith("*/"));
70
Alexander Kornienko919398b2013-04-17 17:34:05 +000071 OriginalStartColumn =
72 SourceMgr.getSpellingColumnNumber(Tok.getStartOfNonWhitespace()) - 1;
Alexander Kornienko70ce7882013-04-15 14:28:00 +000073
74 TokenText.substr(2, TokenText.size() - 4).split(Lines, "\n");
75
Alexander Kornienko919398b2013-04-17 17:34:05 +000076 bool NeedsStar = true;
Alexander Kornienko70ce7882013-04-15 14:28:00 +000077 CommonPrefixLength = UINT_MAX;
78 if (Lines.size() == 1) {
79 if (Token.Parent == 0) {
80 // Standalone block comments will be aligned and prefixed with *s.
81 CommonPrefixLength = OriginalStartColumn + 1;
82 } else {
83 // Trailing comments can start on arbitrary column, and available
84 // horizontal space can be too small to align consecutive lines with
85 // the first one. We could, probably, align them to current
86 // indentation level, but now we just wrap them without indentation
87 // and stars.
88 CommonPrefixLength = 0;
89 NeedsStar = false;
90 }
91 } else {
92 for (size_t i = 1; i < Lines.size(); ++i) {
93 size_t FirstNonWhitespace = Lines[i].find_first_not_of(" ");
94 if (FirstNonWhitespace != StringRef::npos) {
95 NeedsStar = NeedsStar && (Lines[i][FirstNonWhitespace] == '*');
96 CommonPrefixLength =
97 std::min<unsigned>(CommonPrefixLength, FirstNonWhitespace);
98 }
99 }
100 }
101 if (CommonPrefixLength == UINT_MAX)
102 CommonPrefixLength = 0;
103
Alexander Kornienko919398b2013-04-17 17:34:05 +0000104 Decoration = NeedsStar ? "* " : "";
105
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000106 IndentAtLineBreak =
107 std::max<int>(StartColumn - OriginalStartColumn + CommonPrefixLength, 0);
108}
109
110void BreakableBlockComment::alignLines(WhitespaceManager &Whitespaces) {
Alexander Kornienko919398b2013-04-17 17:34:05 +0000111 SourceLocation TokenLoc = Tok.getStartOfNonWhitespace();
112 int IndentDelta = (StartColumn - 2) - OriginalStartColumn;
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000113 if (IndentDelta > 0) {
114 std::string WhiteSpace(IndentDelta, ' ');
115 for (size_t i = 1; i < Lines.size(); ++i) {
116 Whitespaces.addReplacement(
117 TokenLoc.getLocWithOffset(Lines[i].data() - TokenText.data()), 0,
118 WhiteSpace);
119 }
120 } else if (IndentDelta < 0) {
121 std::string WhiteSpace(-IndentDelta, ' ');
122 // Check that the line is indented enough.
123 for (size_t i = 1; i < Lines.size(); ++i) {
124 if (!Lines[i].startswith(WhiteSpace))
125 return;
126 }
127 for (size_t i = 1; i < Lines.size(); ++i) {
128 Whitespaces.addReplacement(
129 TokenLoc.getLocWithOffset(Lines[i].data() - TokenText.data()),
130 -IndentDelta, "");
131 }
132 }
133
134 for (unsigned i = 1; i < Lines.size(); ++i)
Alexander Kornienko919398b2013-04-17 17:34:05 +0000135 Lines[i] = Lines[i].substr(CommonPrefixLength + Decoration.size());
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000136}
137
138void BreakableBlockComment::trimLine(unsigned LineIndex, unsigned TailOffset,
139 unsigned InPPDirective,
140 WhitespaceManager &Whitespaces) {
141 if (LineIndex == Lines.size() - 1)
142 return;
143 StringRef Text = Lines[LineIndex].substr(TailOffset);
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000144
145 // FIXME: The algorithm for trimming a line should naturally yield a
146 // non-change if there is nothing to trim; removing this line breaks the
147 // algorithm; investigate the root cause, and make sure to either document
148 // why exactly this is needed for remove it.
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000149 if (!Text.endswith(" ") && !InPPDirective)
150 return;
151
152 StringRef TrimmedLine = Text.rtrim();
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000153 unsigned BreakOffset = TrimmedLine.end() - TokenText.data();
154 unsigned CharsToRemove = Text.size() - TrimmedLine.size() + 1;
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000155 // FIXME: It seems like we're misusing the call to breakToken to remove
156 // whitespace instead of breaking a token. We should make this an explicit
157 // call option to the WhitespaceManager, or handle trimming and alignment
158 // of comments completely within in the WhitespaceManger. Passing '0' here
159 // and relying on this not breaking assumptions of the WhitespaceManager seems
160 // like a bad idea.
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000161 Whitespaces.breakToken(Tok, BreakOffset, CharsToRemove, "", "", InPPDirective,
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000162 0);
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000163}
164
Alexander Kornienko919398b2013-04-17 17:34:05 +0000165BreakableLineComment::BreakableLineComment(const SourceManager &SourceMgr,
166 const AnnotatedToken &Token,
167 unsigned StartColumn)
168 : BreakableComment(SourceMgr, Token.FormatTok, StartColumn) {
169 assert(TokenText.startswith("//"));
170 Decoration = getLineCommentPrefix(TokenText);
171 Lines.push_back(TokenText.substr(Decoration.size()));
172 IndentAtLineBreak = StartColumn;
173 this->StartColumn += Decoration.size(); // Start column of the contents.
174}
175
176StringRef BreakableLineComment::getLineCommentPrefix(StringRef Comment) {
177 const char *KnownPrefixes[] = { "/// ", "///", "// ", "//" };
178 for (size_t i = 0; i < llvm::array_lengthof(KnownPrefixes); ++i)
179 if (Comment.startswith(KnownPrefixes[i]))
180 return KnownPrefixes[i];
181 return "";
182}
183
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000184} // namespace format
185} // namespace clang