blob: e6c363b3732b2c94be7dd4cc10e6fd5accd82fe3 [file] [log] [blame]
Alexander Kornienko70ce7882013-04-15 14:28:00 +00001//===--- WhitespaceManager.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 This file implements WhitespaceManager class.
12///
13//===----------------------------------------------------------------------===//
14
15#include "WhitespaceManager.h"
16#include "llvm/ADT/STLExtras.h"
17
18namespace clang {
19namespace format {
20
Manuel Klimeke573c3f2013-05-22 12:51:29 +000021bool
22WhitespaceManager::Change::IsBeforeInFile::operator()(const Change &C1,
23 const Change &C2) const {
24 return SourceMgr.isBeforeInTranslationUnit(
25 C1.OriginalWhitespaceRange.getBegin(),
26 C2.OriginalWhitespaceRange.getBegin());
27}
Daniel Jasper2972d042013-04-25 08:56:26 +000028
Manuel Klimeke573c3f2013-05-22 12:51:29 +000029WhitespaceManager::Change::Change(
30 bool CreateReplacement, const SourceRange &OriginalWhitespaceRange,
31 unsigned Spaces, unsigned StartOfTokenColumn, unsigned NewlinesBefore,
32 StringRef PreviousLinePostfix, StringRef CurrentLinePrefix,
33 tok::TokenKind Kind, bool ContinuesPPDirective)
34 : CreateReplacement(CreateReplacement),
35 OriginalWhitespaceRange(OriginalWhitespaceRange),
36 StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
37 PreviousLinePostfix(PreviousLinePostfix),
38 CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
39 ContinuesPPDirective(ContinuesPPDirective), Spaces(Spaces) {}
40
41void WhitespaceManager::replaceWhitespace(const AnnotatedToken &Tok,
42 unsigned Newlines, unsigned Spaces,
43 unsigned StartOfTokenColumn,
44 bool InPPDirective) {
45 Changes.push_back(Change(
Manuel Klimekad3094b2013-05-23 10:56:37 +000046 true, Tok.FormatTok.WhitespaceRange,
Manuel Klimeke573c3f2013-05-22 12:51:29 +000047 Spaces, StartOfTokenColumn, Newlines, "", "", Tok.FormatTok.Tok.getKind(),
48 InPPDirective && !Tok.FormatTok.IsFirst));
Alexander Kornienko70ce7882013-04-15 14:28:00 +000049
Alexander Kornienko70ce7882013-04-15 14:28:00 +000050 // Align line comments if they are trailing or if they continue other
51 // trailing comments.
Manuel Klimeke573c3f2013-05-22 12:51:29 +000052 // FIXME: Pull this out and generalize so it works the same way in broken
53 // comments and unbroken comments with trailing whitespace.
Alexander Kornienko70ce7882013-04-15 14:28:00 +000054 if (Tok.isTrailingComment()) {
Alexander Kornienko919398b2013-04-17 17:34:05 +000055 SourceLocation TokenEndLoc = Tok.FormatTok.getStartOfNonWhitespace()
56 .getLocWithOffset(Tok.FormatTok.TokenLength);
Alexander Kornienko70ce7882013-04-15 14:28:00 +000057 // Remove the comment's trailing whitespace.
Alexander Kornienko919398b2013-04-17 17:34:05 +000058 if (Tok.FormatTok.TrailingWhiteSpaceLength != 0)
Alexander Kornienko70ce7882013-04-15 14:28:00 +000059 Replaces.insert(tooling::Replacement(
Alexander Kornienko919398b2013-04-17 17:34:05 +000060 SourceMgr, TokenEndLoc, Tok.FormatTok.TrailingWhiteSpaceLength, ""));
Alexander Kornienko70ce7882013-04-15 14:28:00 +000061 }
Alexander Kornienko70ce7882013-04-15 14:28:00 +000062}
63
Manuel Klimeke573c3f2013-05-22 12:51:29 +000064void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
65 bool InPPDirective) {
Manuel Klimekad3094b2013-05-23 10:56:37 +000066 Changes.push_back(
67 Change(false, Tok.WhitespaceRange, /*Spaces=*/0,
68 SourceMgr.getSpellingColumnNumber(Tok.Tok.getLocation()) - 1,
69 Tok.NewlinesBefore, "", "", Tok.Tok.getKind(),
70 InPPDirective && !Tok.IsFirst));
Alexander Kornienko70ce7882013-04-15 14:28:00 +000071}
72
73void WhitespaceManager::breakToken(const FormatToken &Tok, unsigned Offset,
Manuel Klimeke573c3f2013-05-22 12:51:29 +000074 unsigned ReplaceChars,
75 StringRef PreviousPostfix,
76 StringRef CurrentPrefix, bool InPPDirective,
77 unsigned Spaces) {
78 Changes.push_back(Change(
79 true, SourceRange(Tok.getStartOfNonWhitespace().getLocWithOffset(Offset),
80 Tok.getStartOfNonWhitespace().getLocWithOffset(
81 Offset + ReplaceChars)),
82 Spaces, Spaces, 1, PreviousPostfix, CurrentPrefix,
83 // FIXME: Unify token adjustment, so we don't split it between
84 // BreakableToken and the WhitespaceManager. That would also allow us to
85 // correctly store a tok::TokenKind instead of rolling our own enum.
86 tok::unknown, InPPDirective && !Tok.IsFirst));
Alexander Kornienko70ce7882013-04-15 14:28:00 +000087}
88
89void WhitespaceManager::addReplacement(const SourceLocation &SourceLoc,
90 unsigned ReplaceChars, StringRef Text) {
91 Replaces.insert(
92 tooling::Replacement(SourceMgr, SourceLoc, ReplaceChars, Text));
93}
94
Manuel Klimeke573c3f2013-05-22 12:51:29 +000095const tooling::Replacements &WhitespaceManager::generateReplacements() {
96 if (Changes.empty())
97 return Replaces;
98
99 std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
100 calculateLineBreakInformation();
101 alignTrailingComments();
102 alignEscapedNewlines();
103 generateChanges();
104
105 return Replaces;
106}
107
108void WhitespaceManager::calculateLineBreakInformation() {
109 Changes[0].PreviousEndOfTokenColumn = 0;
110 for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
111 unsigned OriginalWhitespaceStart =
112 SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
113 unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
114 Changes[i - 1].OriginalWhitespaceRange.getEnd());
115 Changes[i - 1].TokenLength =
116 OriginalWhitespaceStart - PreviousOriginalWhitespaceEnd +
117 Changes[i].PreviousLinePostfix.size() +
118 Changes[i - 1].CurrentLinePrefix.size();
119
120 Changes[i].PreviousEndOfTokenColumn =
121 Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
122
123 Changes[i - 1].IsTrailingComment =
124 (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
125 Changes[i - 1].Kind == tok::comment;
126 }
Manuel Klimek0cd57b52013-05-22 14:01:08 +0000127 // FIXME: The last token is currently not always an eof token; in those
128 // cases, setting TokenLength of the last token to 0 is wrong.
129 Changes.back().TokenLength = 0;
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000130 Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
131}
132
133void WhitespaceManager::alignTrailingComments() {
134 unsigned MinColumn = 0;
135 unsigned MaxColumn = UINT_MAX;
136 unsigned StartOfSequence = 0;
137 bool BreakBeforeNext = false;
138 unsigned Newlines = 0;
139 for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
140 unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
141 // FIXME: Correctly handle ChangeMaxColumn in PP directives.
142 unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
143 Newlines += Changes[i].NewlinesBefore;
144 if (Changes[i].IsTrailingComment) {
Manuel Klimekebfb88c2013-05-23 11:42:52 +0000145 bool WasAlignedWithStartOfNextLine =
146 // A comment on its own line.
147 Changes[i].NewlinesBefore == 1 &&
148 // Not the last line.
149 i + 1 != e &&
150 // The start of the next token was previously aligned with
151 // the start of this comment.
152 (SourceMgr.getSpellingColumnNumber(
153 Changes[i].OriginalWhitespaceRange.getEnd()) ==
154 SourceMgr.getSpellingColumnNumber(
155 Changes[i + 1].OriginalWhitespaceRange.getEnd())) &&
156 // Which is not a comment itself.
157 Changes[i + 1].Kind != tok::comment;
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000158 if (BreakBeforeNext || Newlines > 1 ||
159 (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
160 // Break the comment sequence if the previous line did not end
161 // in a trailing comment.
162 (Changes[i].NewlinesBefore == 1 && i > 0 &&
Manuel Klimekebfb88c2013-05-23 11:42:52 +0000163 !Changes[i - 1].IsTrailingComment) ||
164 WasAlignedWithStartOfNextLine) {
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000165 alignTrailingComments(StartOfSequence, i, MinColumn);
166 MinColumn = ChangeMinColumn;
167 MaxColumn = ChangeMaxColumn;
168 StartOfSequence = i;
169 } else {
170 MinColumn = std::max(MinColumn, ChangeMinColumn);
171 MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
172 }
Manuel Klimek23ad3392013-05-23 19:54:43 +0000173 BreakBeforeNext = (i == 0) || (Changes[i].NewlinesBefore > 1) ||
174 (Changes[i].NewlinesBefore == 1 &&
175 !Changes[i - 1].IsTrailingComment) ||
176 WasAlignedWithStartOfNextLine;
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000177 Newlines = 0;
178 }
179 }
180 alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
181}
182
183void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
184 unsigned Column) {
185 for (unsigned i = Start; i != End; ++i) {
186 if (Changes[i].IsTrailingComment) {
187 assert(Column >= Changes[i].StartOfTokenColumn);
188 Changes[i].Spaces += Column - Changes[i].StartOfTokenColumn;
189 Changes[i].StartOfTokenColumn = Column;
190 }
191 }
192}
193
194void WhitespaceManager::alignEscapedNewlines() {
195 unsigned MaxEndOfLine = 0;
196 unsigned StartOfMacro = 0;
197 for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
198 Change &C = Changes[i];
199 if (C.NewlinesBefore > 0) {
200 if (C.ContinuesPPDirective) {
201 if (Style.AlignEscapedNewlinesLeft)
202 MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
203 else
204 MaxEndOfLine = Style.ColumnLimit;
205 } else {
206 alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
207 MaxEndOfLine = 0;
208 StartOfMacro = i;
209 }
210 }
211 }
212 alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
213}
214
215void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
216 unsigned Column) {
217 for (unsigned i = Start; i < End; ++i) {
218 Change &C = Changes[i];
219 if (C.NewlinesBefore > 0) {
220 assert(C.ContinuesPPDirective);
221 if (C.PreviousEndOfTokenColumn + 1 > Column)
222 C.EscapedNewlineColumn = 0;
223 else
224 C.EscapedNewlineColumn = Column;
225 }
226 }
227}
228
229void WhitespaceManager::generateChanges() {
230 for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
231 const Change &C = Changes[i];
232 if (C.CreateReplacement) {
233 std::string ReplacementText =
234 C.PreviousLinePostfix +
235 (C.ContinuesPPDirective
236 ? getNewLineText(C.NewlinesBefore, C.Spaces,
237 C.PreviousEndOfTokenColumn,
238 C.EscapedNewlineColumn)
239 : getNewLineText(C.NewlinesBefore, C.Spaces)) +
240 C.CurrentLinePrefix;
241 storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
242 }
243 }
244}
245
246void WhitespaceManager::storeReplacement(const SourceRange &Range,
247 StringRef Text) {
248 unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
249 SourceMgr.getFileOffset(Range.getBegin());
250 // Don't create a replacement, if it does not change anything.
251 if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
252 WhitespaceLength) ==
253 Text)
254 return;
255 Replaces.insert(tooling::Replacement(
256 SourceMgr, CharSourceRange::getCharRange(Range), Text));
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000257}
258
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000259std::string WhitespaceManager::getNewLineText(unsigned NewLines,
260 unsigned Spaces) {
Manuel Klimek7c9a93e2013-05-13 09:22:11 +0000261 return std::string(NewLines, '\n') + getIndentText(Spaces);
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000262}
263
264std::string WhitespaceManager::getNewLineText(unsigned NewLines,
265 unsigned Spaces,
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000266 unsigned PreviousEndOfTokenColumn,
Daniel Jasper2972d042013-04-25 08:56:26 +0000267 unsigned EscapedNewlineColumn) {
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000268 std::string NewLineText;
269 if (NewLines > 0) {
270 unsigned Offset =
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000271 std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000272 for (unsigned i = 0; i < NewLines; ++i) {
Daniel Jasper2972d042013-04-25 08:56:26 +0000273 NewLineText += std::string(EscapedNewlineColumn - Offset - 1, ' ');
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000274 NewLineText += "\\\n";
275 Offset = 0;
276 }
277 }
Manuel Klimek7c9a93e2013-05-13 09:22:11 +0000278 return NewLineText + getIndentText(Spaces);
279}
280
281std::string WhitespaceManager::getIndentText(unsigned Spaces) {
Manuel Klimek967d9e92013-05-13 12:53:04 +0000282 if (!Style.UseTab)
Manuel Klimek7c9a93e2013-05-13 09:22:11 +0000283 return std::string(Spaces, ' ');
Manuel Klimek967d9e92013-05-13 12:53:04 +0000284
Manuel Klimek7c9a93e2013-05-13 09:22:11 +0000285 return std::string(Spaces / Style.IndentWidth, '\t') +
286 std::string(Spaces % Style.IndentWidth, ' ');
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000287}
288
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000289} // namespace format
290} // namespace clang