blob: 802145e72d852b26db03bff845d2a11d87e77b23 [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,
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +000031 unsigned IndentLevel, unsigned Spaces, unsigned StartOfTokenColumn,
32 unsigned NewlinesBefore, StringRef PreviousLinePostfix,
33 StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective)
Manuel Klimeke573c3f2013-05-22 12:51:29 +000034 : CreateReplacement(CreateReplacement),
35 OriginalWhitespaceRange(OriginalWhitespaceRange),
36 StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
37 PreviousLinePostfix(PreviousLinePostfix),
38 CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +000039 ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel),
40 Spaces(Spaces) {}
Manuel Klimeke573c3f2013-05-22 12:51:29 +000041
Manuel Klimekb3987012013-05-29 14:47:47 +000042void WhitespaceManager::replaceWhitespace(const FormatToken &Tok,
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +000043 unsigned Newlines,
44 unsigned IndentLevel, unsigned Spaces,
Manuel Klimeke573c3f2013-05-22 12:51:29 +000045 unsigned StartOfTokenColumn,
46 bool InPPDirective) {
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +000047 Changes.push_back(Change(true, Tok.WhitespaceRange, IndentLevel, Spaces,
Daniel Jasper2a409b62013-07-08 14:34:09 +000048 StartOfTokenColumn, Newlines, "", "",
49 Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst));
Alexander Kornienko70ce7882013-04-15 14:28:00 +000050}
51
Manuel Klimeke573c3f2013-05-22 12:51:29 +000052void WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
53 bool InPPDirective) {
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +000054 Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0,
55 /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore,
56 "", "", Tok.Tok.getKind(),
57 InPPDirective && !Tok.IsFirst));
Alexander Kornienko70ce7882013-04-15 14:28:00 +000058}
59
Alexander Kornienko2b2faa52013-06-11 16:01:49 +000060void WhitespaceManager::replaceWhitespaceInToken(
61 const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
62 StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +000063 unsigned Newlines, unsigned IndentLevel, unsigned Spaces) {
Manuel Klimeke573c3f2013-05-22 12:51:29 +000064 Changes.push_back(Change(
65 true, SourceRange(Tok.getStartOfNonWhitespace().getLocWithOffset(Offset),
66 Tok.getStartOfNonWhitespace().getLocWithOffset(
67 Offset + ReplaceChars)),
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +000068 IndentLevel, Spaces, Spaces, Newlines, PreviousPostfix, CurrentPrefix,
Alexander Kornienko22d0e292013-06-17 12:59:44 +000069 // If we don't add a newline this change doesn't start a comment. Thus,
70 // when we align line comments, we don't need to treat this change as one.
71 // FIXME: We still need to take this change in account to properly
72 // calculate the new length of the comment and to calculate the changes
73 // for which to do the alignment when aligning comments.
74 Tok.Type == TT_LineComment && Newlines > 0 ? tok::comment : tok::unknown,
75 InPPDirective && !Tok.IsFirst));
Alexander Kornienko70ce7882013-04-15 14:28:00 +000076}
77
Manuel Klimeke573c3f2013-05-22 12:51:29 +000078const tooling::Replacements &WhitespaceManager::generateReplacements() {
79 if (Changes.empty())
80 return Replaces;
81
82 std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
83 calculateLineBreakInformation();
84 alignTrailingComments();
85 alignEscapedNewlines();
86 generateChanges();
87
88 return Replaces;
89}
90
91void WhitespaceManager::calculateLineBreakInformation() {
92 Changes[0].PreviousEndOfTokenColumn = 0;
93 for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
94 unsigned OriginalWhitespaceStart =
95 SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
96 unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
97 Changes[i - 1].OriginalWhitespaceRange.getEnd());
Daniel Jasper2a409b62013-07-08 14:34:09 +000098 Changes[i - 1].TokenLength = OriginalWhitespaceStart -
99 PreviousOriginalWhitespaceEnd +
100 Changes[i].PreviousLinePostfix.size() +
101 Changes[i - 1].CurrentLinePrefix.size();
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000102
103 Changes[i].PreviousEndOfTokenColumn =
104 Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
105
106 Changes[i - 1].IsTrailingComment =
107 (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
108 Changes[i - 1].Kind == tok::comment;
109 }
Manuel Klimek0cd57b52013-05-22 14:01:08 +0000110 // FIXME: The last token is currently not always an eof token; in those
111 // cases, setting TokenLength of the last token to 0 is wrong.
112 Changes.back().TokenLength = 0;
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000113 Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
114}
115
116void WhitespaceManager::alignTrailingComments() {
117 unsigned MinColumn = 0;
118 unsigned MaxColumn = UINT_MAX;
119 unsigned StartOfSequence = 0;
120 bool BreakBeforeNext = false;
121 unsigned Newlines = 0;
122 for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
123 unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
124 // FIXME: Correctly handle ChangeMaxColumn in PP directives.
125 unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
126 Newlines += Changes[i].NewlinesBefore;
127 if (Changes[i].IsTrailingComment) {
Daniel Jasper893ea8d2013-07-31 23:55:15 +0000128 // If this comment follows an } in column 0, it probably documents the
129 // closing of a namespace and we don't want to align it.
Daniel Jaspercbe86cc2013-07-01 11:22:57 +0000130 bool FollowsRBraceInColumn0 = i > 0 && Changes[i].NewlinesBefore == 0 &&
131 Changes[i - 1].Kind == tok::r_brace &&
132 Changes[i - 1].StartOfTokenColumn == 0;
Manuel Klimekebfb88c2013-05-23 11:42:52 +0000133 bool WasAlignedWithStartOfNextLine =
134 // A comment on its own line.
135 Changes[i].NewlinesBefore == 1 &&
136 // Not the last line.
137 i + 1 != e &&
138 // The start of the next token was previously aligned with
139 // the start of this comment.
140 (SourceMgr.getSpellingColumnNumber(
141 Changes[i].OriginalWhitespaceRange.getEnd()) ==
142 SourceMgr.getSpellingColumnNumber(
143 Changes[i + 1].OriginalWhitespaceRange.getEnd())) &&
144 // Which is not a comment itself.
145 Changes[i + 1].Kind != tok::comment;
Daniel Jasper893ea8d2013-07-31 23:55:15 +0000146 if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
Daniel Jaspercbe86cc2013-07-01 11:22:57 +0000147 alignTrailingComments(StartOfSequence, i, MinColumn);
148 MinColumn = ChangeMinColumn;
149 MaxColumn = ChangeMinColumn;
150 StartOfSequence = i;
151 } else if (BreakBeforeNext || Newlines > 1 ||
152 (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
153 // Break the comment sequence if the previous line did not end
154 // in a trailing comment.
155 (Changes[i].NewlinesBefore == 1 && i > 0 &&
156 !Changes[i - 1].IsTrailingComment) ||
157 WasAlignedWithStartOfNextLine) {
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000158 alignTrailingComments(StartOfSequence, i, MinColumn);
159 MinColumn = ChangeMinColumn;
160 MaxColumn = ChangeMaxColumn;
161 StartOfSequence = i;
162 } else {
163 MinColumn = std::max(MinColumn, ChangeMinColumn);
164 MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
165 }
Manuel Klimek854ca792013-05-23 20:46:07 +0000166 BreakBeforeNext =
167 (i == 0) || (Changes[i].NewlinesBefore > 1) ||
168 // Never start a sequence with a comment at the beginning of
169 // the line.
170 (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000171 Newlines = 0;
172 }
173 }
174 alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
175}
176
177void WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
178 unsigned Column) {
179 for (unsigned i = Start; i != End; ++i) {
180 if (Changes[i].IsTrailingComment) {
181 assert(Column >= Changes[i].StartOfTokenColumn);
182 Changes[i].Spaces += Column - Changes[i].StartOfTokenColumn;
183 Changes[i].StartOfTokenColumn = Column;
184 }
185 }
186}
187
188void WhitespaceManager::alignEscapedNewlines() {
Daniel Jasperc9346c92013-08-28 09:07:32 +0000189 unsigned MaxEndOfLine =
190 Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000191 unsigned StartOfMacro = 0;
192 for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
193 Change &C = Changes[i];
194 if (C.NewlinesBefore > 0) {
195 if (C.ContinuesPPDirective) {
Daniel Jasperc9346c92013-08-28 09:07:32 +0000196 MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000197 } else {
198 alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
Daniel Jasperc9346c92013-08-28 09:07:32 +0000199 MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000200 StartOfMacro = i;
201 }
202 }
203 }
204 alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
205}
206
207void WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
208 unsigned Column) {
209 for (unsigned i = Start; i < End; ++i) {
210 Change &C = Changes[i];
211 if (C.NewlinesBefore > 0) {
212 assert(C.ContinuesPPDirective);
213 if (C.PreviousEndOfTokenColumn + 1 > Column)
214 C.EscapedNewlineColumn = 0;
215 else
216 C.EscapedNewlineColumn = Column;
217 }
218 }
219}
220
221void WhitespaceManager::generateChanges() {
222 for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
223 const Change &C = Changes[i];
224 if (C.CreateReplacement) {
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000225 std::string ReplacementText = C.PreviousLinePostfix;
226 if (C.ContinuesPPDirective)
227 appendNewlineText(ReplacementText, C.NewlinesBefore,
228 C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn);
229 else
230 appendNewlineText(ReplacementText, C.NewlinesBefore);
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +0000231 appendIndentText(ReplacementText, C.IndentLevel, C.Spaces,
232 C.StartOfTokenColumn - C.Spaces);
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000233 ReplacementText.append(C.CurrentLinePrefix);
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000234 storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
235 }
236 }
237}
238
239void WhitespaceManager::storeReplacement(const SourceRange &Range,
240 StringRef Text) {
241 unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
242 SourceMgr.getFileOffset(Range.getBegin());
243 // Don't create a replacement, if it does not change anything.
244 if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
Daniel Jasper2a409b62013-07-08 14:34:09 +0000245 WhitespaceLength) == Text)
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000246 return;
247 Replaces.insert(tooling::Replacement(
248 SourceMgr, CharSourceRange::getCharRange(Range), Text));
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000249}
250
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000251void WhitespaceManager::appendNewlineText(std::string &Text,
252 unsigned Newlines) {
253 for (unsigned i = 0; i < Newlines; ++i)
254 Text.append(UseCRLF ? "\r\n" : "\n");
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000255}
256
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000257void WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
258 unsigned PreviousEndOfTokenColumn,
259 unsigned EscapedNewlineColumn) {
Alexander Kornienko2b2faa52013-06-11 16:01:49 +0000260 if (Newlines > 0) {
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000261 unsigned Offset =
Manuel Klimeke573c3f2013-05-22 12:51:29 +0000262 std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
Alexander Kornienko2b2faa52013-06-11 16:01:49 +0000263 for (unsigned i = 0; i < Newlines; ++i) {
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000264 Text.append(std::string(EscapedNewlineColumn - Offset - 1, ' '));
265 Text.append(UseCRLF ? "\\\r\n" : "\\\n");
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000266 Offset = 0;
267 }
268 }
Manuel Klimek7c9a93e2013-05-13 09:22:11 +0000269}
270
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +0000271void WhitespaceManager::appendIndentText(std::string &Text,
272 unsigned IndentLevel, unsigned Spaces,
Alexander Kornienkoacf8e902013-09-27 09:45:40 +0000273 unsigned WhitespaceStartColumn) {
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +0000274 switch (Style.UseTab) {
275 case FormatStyle::UT_Never:
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000276 Text.append(std::string(Spaces, ' '));
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +0000277 break;
278 case FormatStyle::UT_Always: {
Alexander Kornienkoacf8e902013-09-27 09:45:40 +0000279 unsigned FirstTabWidth =
280 Style.TabWidth - WhitespaceStartColumn % Style.TabWidth;
281 // Indent with tabs only when there's at least one full tab.
282 if (FirstTabWidth + Style.TabWidth <= Spaces) {
283 Spaces -= FirstTabWidth;
284 Text.append("\t");
285 }
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000286 Text.append(std::string(Spaces / Style.TabWidth, '\t'));
287 Text.append(std::string(Spaces % Style.TabWidth, ' '));
Alexander Kornienko3d9ffcf2013-09-27 16:14:22 +0000288 break;
289 }
290 case FormatStyle::UT_ForIndentation:
291 if (WhitespaceStartColumn == 0) {
292 unsigned Indentation = IndentLevel * Style.IndentWidth;
293 if (Indentation > Spaces)
294 Indentation = Spaces;
295 unsigned Tabs = Indentation / Style.TabWidth;
296 Text.append(std::string(Tabs, '\t'));
297 Spaces -= Tabs * Style.TabWidth;
298 }
299 Text.append(std::string(Spaces, ' '));
300 break;
Alexander Kornienko73d845c2013-09-11 12:25:57 +0000301 }
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000302}
303
Alexander Kornienko70ce7882013-04-15 14:28:00 +0000304} // namespace format
305} // namespace clang