1251609Sdim//===--- WhitespaceManager.cpp - Format C++ code --------------------------===//
2251609Sdim//
3251609Sdim//                     The LLVM Compiler Infrastructure
4251609Sdim//
5251609Sdim// This file is distributed under the University of Illinois Open Source
6251609Sdim// License. See LICENSE.TXT for details.
7251609Sdim//
8251609Sdim//===----------------------------------------------------------------------===//
9251609Sdim///
10251609Sdim/// \file
11251609Sdim/// \brief This file implements WhitespaceManager class.
12251609Sdim///
13251609Sdim//===----------------------------------------------------------------------===//
14251609Sdim
15251609Sdim#include "WhitespaceManager.h"
16251609Sdim#include "llvm/ADT/STLExtras.h"
17251609Sdim
18251609Sdimnamespace clang {
19251609Sdimnamespace format {
20251609Sdim
21263508Sdimbool
22263508SdimWhitespaceManager::Change::IsBeforeInFile::operator()(const Change &C1,
23263508Sdim                                                      const Change &C2) const {
24263508Sdim  return SourceMgr.isBeforeInTranslationUnit(
25263508Sdim      C1.OriginalWhitespaceRange.getBegin(),
26263508Sdim      C2.OriginalWhitespaceRange.getBegin());
27263508Sdim}
28251609Sdim
29263508SdimWhitespaceManager::Change::Change(
30263508Sdim    bool CreateReplacement, const SourceRange &OriginalWhitespaceRange,
31263508Sdim    unsigned IndentLevel, unsigned Spaces, unsigned StartOfTokenColumn,
32263508Sdim    unsigned NewlinesBefore, StringRef PreviousLinePostfix,
33263508Sdim    StringRef CurrentLinePrefix, tok::TokenKind Kind, bool ContinuesPPDirective)
34263508Sdim    : CreateReplacement(CreateReplacement),
35263508Sdim      OriginalWhitespaceRange(OriginalWhitespaceRange),
36263508Sdim      StartOfTokenColumn(StartOfTokenColumn), NewlinesBefore(NewlinesBefore),
37263508Sdim      PreviousLinePostfix(PreviousLinePostfix),
38263508Sdim      CurrentLinePrefix(CurrentLinePrefix), Kind(Kind),
39263508Sdim      ContinuesPPDirective(ContinuesPPDirective), IndentLevel(IndentLevel),
40263508Sdim      Spaces(Spaces) {}
41251609Sdim
42263508Sdimvoid WhitespaceManager::reset() {
43263508Sdim  Changes.clear();
44263508Sdim  Replaces.clear();
45263508Sdim}
46251609Sdim
47263508Sdimvoid WhitespaceManager::replaceWhitespace(FormatToken &Tok, unsigned Newlines,
48263508Sdim                                          unsigned IndentLevel, unsigned Spaces,
49263508Sdim                                          unsigned StartOfTokenColumn,
50263508Sdim                                          bool InPPDirective) {
51263508Sdim  if (Tok.Finalized)
52263508Sdim    return;
53263508Sdim  Tok.Decision = (Newlines > 0) ? FD_Break : FD_Continue;
54263508Sdim  Changes.push_back(Change(true, Tok.WhitespaceRange, IndentLevel, Spaces,
55263508Sdim                           StartOfTokenColumn, Newlines, "", "",
56263508Sdim                           Tok.Tok.getKind(), InPPDirective && !Tok.IsFirst));
57251609Sdim}
58251609Sdim
59263508Sdimvoid WhitespaceManager::addUntouchableToken(const FormatToken &Tok,
60263508Sdim                                            bool InPPDirective) {
61263508Sdim  if (Tok.Finalized)
62263508Sdim    return;
63263508Sdim  Changes.push_back(Change(false, Tok.WhitespaceRange, /*IndentLevel=*/0,
64263508Sdim                           /*Spaces=*/0, Tok.OriginalColumn, Tok.NewlinesBefore,
65263508Sdim                           "", "", Tok.Tok.getKind(),
66263508Sdim                           InPPDirective && !Tok.IsFirst));
67251609Sdim}
68251609Sdim
69263508Sdimvoid WhitespaceManager::replaceWhitespaceInToken(
70263508Sdim    const FormatToken &Tok, unsigned Offset, unsigned ReplaceChars,
71263508Sdim    StringRef PreviousPostfix, StringRef CurrentPrefix, bool InPPDirective,
72263508Sdim    unsigned Newlines, unsigned IndentLevel, unsigned Spaces) {
73263508Sdim  if (Tok.Finalized)
74263508Sdim    return;
75263508Sdim  Changes.push_back(Change(
76263508Sdim      true, SourceRange(Tok.getStartOfNonWhitespace().getLocWithOffset(Offset),
77263508Sdim                        Tok.getStartOfNonWhitespace().getLocWithOffset(
78263508Sdim                            Offset + ReplaceChars)),
79263508Sdim      IndentLevel, Spaces, Spaces, Newlines, PreviousPostfix, CurrentPrefix,
80263508Sdim      // If we don't add a newline this change doesn't start a comment. Thus,
81263508Sdim      // when we align line comments, we don't need to treat this change as one.
82263508Sdim      // FIXME: We still need to take this change in account to properly
83263508Sdim      // calculate the new length of the comment and to calculate the changes
84263508Sdim      // for which to do the alignment when aligning comments.
85263508Sdim      Tok.Type == TT_LineComment && Newlines > 0 ? tok::comment : tok::unknown,
86263508Sdim      InPPDirective && !Tok.IsFirst));
87251609Sdim}
88251609Sdim
89251609Sdimconst tooling::Replacements &WhitespaceManager::generateReplacements() {
90263508Sdim  if (Changes.empty())
91263508Sdim    return Replaces;
92263508Sdim
93263508Sdim  std::sort(Changes.begin(), Changes.end(), Change::IsBeforeInFile(SourceMgr));
94263508Sdim  calculateLineBreakInformation();
95263508Sdim  alignTrailingComments();
96251609Sdim  alignEscapedNewlines();
97263508Sdim  generateChanges();
98263508Sdim
99251609Sdim  return Replaces;
100251609Sdim}
101251609Sdim
102263508Sdimvoid WhitespaceManager::calculateLineBreakInformation() {
103263508Sdim  Changes[0].PreviousEndOfTokenColumn = 0;
104263508Sdim  for (unsigned i = 1, e = Changes.size(); i != e; ++i) {
105263508Sdim    unsigned OriginalWhitespaceStart =
106263508Sdim        SourceMgr.getFileOffset(Changes[i].OriginalWhitespaceRange.getBegin());
107263508Sdim    unsigned PreviousOriginalWhitespaceEnd = SourceMgr.getFileOffset(
108263508Sdim        Changes[i - 1].OriginalWhitespaceRange.getEnd());
109263508Sdim    Changes[i - 1].TokenLength = OriginalWhitespaceStart -
110263508Sdim                                 PreviousOriginalWhitespaceEnd +
111263508Sdim                                 Changes[i].PreviousLinePostfix.size() +
112263508Sdim                                 Changes[i - 1].CurrentLinePrefix.size();
113251609Sdim
114263508Sdim    Changes[i].PreviousEndOfTokenColumn =
115263508Sdim        Changes[i - 1].StartOfTokenColumn + Changes[i - 1].TokenLength;
116251609Sdim
117263508Sdim    Changes[i - 1].IsTrailingComment =
118263508Sdim        (Changes[i].NewlinesBefore > 0 || Changes[i].Kind == tok::eof) &&
119263508Sdim        Changes[i - 1].Kind == tok::comment;
120251609Sdim  }
121263508Sdim  // FIXME: The last token is currently not always an eof token; in those
122263508Sdim  // cases, setting TokenLength of the last token to 0 is wrong.
123263508Sdim  Changes.back().TokenLength = 0;
124263508Sdim  Changes.back().IsTrailingComment = Changes.back().Kind == tok::comment;
125251609Sdim}
126251609Sdim
127263508Sdimvoid WhitespaceManager::alignTrailingComments() {
128251609Sdim  unsigned MinColumn = 0;
129251609Sdim  unsigned MaxColumn = UINT_MAX;
130263508Sdim  unsigned StartOfSequence = 0;
131263508Sdim  bool BreakBeforeNext = false;
132263508Sdim  unsigned Newlines = 0;
133263508Sdim  for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
134263508Sdim    unsigned ChangeMinColumn = Changes[i].StartOfTokenColumn;
135263508Sdim    // FIXME: Correctly handle ChangeMaxColumn in PP directives.
136263508Sdim    unsigned ChangeMaxColumn = Style.ColumnLimit - Changes[i].TokenLength;
137263508Sdim    Newlines += Changes[i].NewlinesBefore;
138263508Sdim    if (Changes[i].IsTrailingComment) {
139263508Sdim      // If this comment follows an } in column 0, it probably documents the
140263508Sdim      // closing of a namespace and we don't want to align it.
141263508Sdim      bool FollowsRBraceInColumn0 = i > 0 && Changes[i].NewlinesBefore == 0 &&
142263508Sdim                                    Changes[i - 1].Kind == tok::r_brace &&
143263508Sdim                                    Changes[i - 1].StartOfTokenColumn == 0;
144263508Sdim      bool WasAlignedWithStartOfNextLine = false;
145263508Sdim      if (Changes[i].NewlinesBefore == 1) { // A comment on its own line.
146263508Sdim        for (unsigned j = i + 1; j != e; ++j) {
147263508Sdim          if (Changes[j].Kind != tok::comment) { // Skip over comments.
148263508Sdim            // The start of the next token was previously aligned with the
149263508Sdim            // start of this comment.
150263508Sdim            WasAlignedWithStartOfNextLine =
151263508Sdim                (SourceMgr.getSpellingColumnNumber(
152263508Sdim                     Changes[i].OriginalWhitespaceRange.getEnd()) ==
153263508Sdim                 SourceMgr.getSpellingColumnNumber(
154263508Sdim                     Changes[j].OriginalWhitespaceRange.getEnd()));
155263508Sdim            break;
156263508Sdim          }
157263508Sdim        }
158263508Sdim      }
159263508Sdim      if (!Style.AlignTrailingComments || FollowsRBraceInColumn0) {
160263508Sdim        alignTrailingComments(StartOfSequence, i, MinColumn);
161263508Sdim        MinColumn = ChangeMinColumn;
162263508Sdim        MaxColumn = ChangeMinColumn;
163263508Sdim        StartOfSequence = i;
164263508Sdim      } else if (BreakBeforeNext || Newlines > 1 ||
165263508Sdim                 (ChangeMinColumn > MaxColumn || ChangeMaxColumn < MinColumn) ||
166263508Sdim                 // Break the comment sequence if the previous line did not end
167263508Sdim                 // in a trailing comment.
168263508Sdim                 (Changes[i].NewlinesBefore == 1 && i > 0 &&
169263508Sdim                  !Changes[i - 1].IsTrailingComment) ||
170263508Sdim                 WasAlignedWithStartOfNextLine) {
171263508Sdim        alignTrailingComments(StartOfSequence, i, MinColumn);
172263508Sdim        MinColumn = ChangeMinColumn;
173263508Sdim        MaxColumn = ChangeMaxColumn;
174263508Sdim        StartOfSequence = i;
175263508Sdim      } else {
176263508Sdim        MinColumn = std::max(MinColumn, ChangeMinColumn);
177263508Sdim        MaxColumn = std::min(MaxColumn, ChangeMaxColumn);
178263508Sdim      }
179263508Sdim      BreakBeforeNext =
180263508Sdim          (i == 0) || (Changes[i].NewlinesBefore > 1) ||
181263508Sdim          // Never start a sequence with a comment at the beginning of
182263508Sdim          // the line.
183263508Sdim          (Changes[i].NewlinesBefore == 1 && StartOfSequence == i);
184263508Sdim      Newlines = 0;
185251609Sdim    }
186251609Sdim  }
187263508Sdim  alignTrailingComments(StartOfSequence, Changes.size(), MinColumn);
188251609Sdim}
189251609Sdim
190263508Sdimvoid WhitespaceManager::alignTrailingComments(unsigned Start, unsigned End,
191263508Sdim                                              unsigned Column) {
192263508Sdim  for (unsigned i = Start; i != End; ++i) {
193263508Sdim    if (Changes[i].IsTrailingComment) {
194263508Sdim      assert(Column >= Changes[i].StartOfTokenColumn);
195263508Sdim      Changes[i].Spaces += Column - Changes[i].StartOfTokenColumn;
196263508Sdim      Changes[i].StartOfTokenColumn = Column;
197251609Sdim    }
198251609Sdim  }
199251609Sdim}
200251609Sdim
201251609Sdimvoid WhitespaceManager::alignEscapedNewlines() {
202263508Sdim  unsigned MaxEndOfLine =
203263508Sdim      Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
204263508Sdim  unsigned StartOfMacro = 0;
205263508Sdim  for (unsigned i = 1, e = Changes.size(); i < e; ++i) {
206263508Sdim    Change &C = Changes[i];
207263508Sdim    if (C.NewlinesBefore > 0) {
208263508Sdim      if (C.ContinuesPPDirective) {
209263508Sdim        MaxEndOfLine = std::max(C.PreviousEndOfTokenColumn + 2, MaxEndOfLine);
210263508Sdim      } else {
211263508Sdim        alignEscapedNewlines(StartOfMacro + 1, i, MaxEndOfLine);
212263508Sdim        MaxEndOfLine = Style.AlignEscapedNewlinesLeft ? 0 : Style.ColumnLimit;
213263508Sdim        StartOfMacro = i;
214263508Sdim      }
215251609Sdim    }
216251609Sdim  }
217263508Sdim  alignEscapedNewlines(StartOfMacro + 1, Changes.size(), MaxEndOfLine);
218263508Sdim}
219251609Sdim
220263508Sdimvoid WhitespaceManager::alignEscapedNewlines(unsigned Start, unsigned End,
221263508Sdim                                             unsigned Column) {
222263508Sdim  for (unsigned i = Start; i < End; ++i) {
223263508Sdim    Change &C = Changes[i];
224263508Sdim    if (C.NewlinesBefore > 0) {
225263508Sdim      assert(C.ContinuesPPDirective);
226263508Sdim      if (C.PreviousEndOfTokenColumn + 1 > Column)
227263508Sdim        C.EscapedNewlineColumn = 0;
228263508Sdim      else
229263508Sdim        C.EscapedNewlineColumn = Column;
230263508Sdim    }
231263508Sdim  }
232263508Sdim}
233251609Sdim
234263508Sdimvoid WhitespaceManager::generateChanges() {
235263508Sdim  for (unsigned i = 0, e = Changes.size(); i != e; ++i) {
236263508Sdim    const Change &C = Changes[i];
237263508Sdim    if (C.CreateReplacement) {
238263508Sdim      std::string ReplacementText = C.PreviousLinePostfix;
239263508Sdim      if (C.ContinuesPPDirective)
240263508Sdim        appendNewlineText(ReplacementText, C.NewlinesBefore,
241263508Sdim                          C.PreviousEndOfTokenColumn, C.EscapedNewlineColumn);
242263508Sdim      else
243263508Sdim        appendNewlineText(ReplacementText, C.NewlinesBefore);
244263508Sdim      appendIndentText(ReplacementText, C.IndentLevel, C.Spaces,
245263508Sdim                       C.StartOfTokenColumn - C.Spaces);
246263508Sdim      ReplacementText.append(C.CurrentLinePrefix);
247263508Sdim      storeReplacement(C.OriginalWhitespaceRange, ReplacementText);
248263508Sdim    }
249251609Sdim  }
250251609Sdim}
251251609Sdim
252263508Sdimvoid WhitespaceManager::storeReplacement(const SourceRange &Range,
253263508Sdim                                         StringRef Text) {
254263508Sdim  unsigned WhitespaceLength = SourceMgr.getFileOffset(Range.getEnd()) -
255263508Sdim                              SourceMgr.getFileOffset(Range.getBegin());
256251609Sdim  // Don't create a replacement, if it does not change anything.
257263508Sdim  if (StringRef(SourceMgr.getCharacterData(Range.getBegin()),
258263508Sdim                WhitespaceLength) == Text)
259251609Sdim    return;
260263508Sdim  Replaces.insert(tooling::Replacement(
261263508Sdim      SourceMgr, CharSourceRange::getCharRange(Range), Text));
262251609Sdim}
263251609Sdim
264263508Sdimvoid WhitespaceManager::appendNewlineText(std::string &Text,
265263508Sdim                                          unsigned Newlines) {
266263508Sdim  for (unsigned i = 0; i < Newlines; ++i)
267263508Sdim    Text.append(UseCRLF ? "\r\n" : "\n");
268263508Sdim}
269263508Sdim
270263508Sdimvoid WhitespaceManager::appendNewlineText(std::string &Text, unsigned Newlines,
271263508Sdim                                          unsigned PreviousEndOfTokenColumn,
272263508Sdim                                          unsigned EscapedNewlineColumn) {
273263508Sdim  if (Newlines > 0) {
274263508Sdim    unsigned Offset =
275263508Sdim        std::min<int>(EscapedNewlineColumn - 1, PreviousEndOfTokenColumn);
276263508Sdim    for (unsigned i = 0; i < Newlines; ++i) {
277263508Sdim      Text.append(std::string(EscapedNewlineColumn - Offset - 1, ' '));
278263508Sdim      Text.append(UseCRLF ? "\\\r\n" : "\\\n");
279263508Sdim      Offset = 0;
280263508Sdim    }
281263508Sdim  }
282263508Sdim}
283263508Sdim
284263508Sdimvoid WhitespaceManager::appendIndentText(std::string &Text,
285263508Sdim                                         unsigned IndentLevel, unsigned Spaces,
286263508Sdim                                         unsigned WhitespaceStartColumn) {
287263508Sdim  switch (Style.UseTab) {
288263508Sdim  case FormatStyle::UT_Never:
289263508Sdim    Text.append(std::string(Spaces, ' '));
290263508Sdim    break;
291263508Sdim  case FormatStyle::UT_Always: {
292263508Sdim    unsigned FirstTabWidth =
293263508Sdim        Style.TabWidth - WhitespaceStartColumn % Style.TabWidth;
294263508Sdim    // Indent with tabs only when there's at least one full tab.
295263508Sdim    if (FirstTabWidth + Style.TabWidth <= Spaces) {
296263508Sdim      Spaces -= FirstTabWidth;
297263508Sdim      Text.append("\t");
298263508Sdim    }
299263508Sdim    Text.append(std::string(Spaces / Style.TabWidth, '\t'));
300263508Sdim    Text.append(std::string(Spaces % Style.TabWidth, ' '));
301263508Sdim    break;
302263508Sdim  }
303263508Sdim  case FormatStyle::UT_ForIndentation:
304263508Sdim    if (WhitespaceStartColumn == 0) {
305263508Sdim      unsigned Indentation = IndentLevel * Style.IndentWidth;
306263508Sdim      // This happens, e.g. when a line in a block comment is indented less than
307263508Sdim      // the first one.
308263508Sdim      if (Indentation > Spaces)
309263508Sdim        Indentation = Spaces;
310263508Sdim      unsigned Tabs = Indentation / Style.TabWidth;
311263508Sdim      Text.append(std::string(Tabs, '\t'));
312263508Sdim      Spaces -= Tabs * Style.TabWidth;
313263508Sdim    }
314263508Sdim    Text.append(std::string(Spaces, ' '));
315263508Sdim    break;
316263508Sdim  }
317263508Sdim}
318263508Sdim
319251609Sdim} // namespace format
320251609Sdim} // namespace clang
321