1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
35 This file is a self-contained interactive indenter for Qt Script.
37 The general problem of indenting a program is ill posed. On
38 the one hand, an indenter has to analyze programs written in a
39 free-form formal language that is best described in terms of
40 tokens, not characters, not lines. On the other hand, indentation
41 applies to lines and white space characters matter, and otherwise
42 the programs to indent are formally invalid in general, as they
45 The approach taken here works line by line. We receive a program
46 consisting of N lines or more, and we want to compute the
47 indentation appropriate for the Nth line. Lines beyond the Nth
48 lines are of no concern to us, so for simplicity we pretend the
49 program has exactly N lines and we call the Nth line the "bottom
50 line". Typically, we have to indent the bottom line when it's
51 still empty, so we concentrate our analysis on the N - 1 lines
54 By inspecting the (N - 1)-th line, the (N - 2)-th line, ...
55 backwards, we determine the kind of the bottom line and indent it
58 * The bottom line is a comment line. See
59 bottomLineStartsInCComment() and
60 indentWhenBottomLineStartsInCComment().
61 * The bottom line is a continuation line. See isContinuationLine()
62 and indentForContinuationLine().
63 * The bottom line is a standalone line. See
64 indentForStandaloneLine().
66 Certain tokens that influence the indentation, notably braces,
67 are looked for in the lines. This is done by simple string
68 comparison, without a real tokenizer. Confusing constructs such
69 as comments and string literals are removed beforehand.
72 #include <qmljs/qmljsindenter.h>
73 #include <qmljs/qmljsscanner.h>
77 using namespace QmlJS;
80 Saves and restores the state of the global linizer. This enables
83 Identical to the defines in qmljslineinfo.cpp
85 #define YY_SAVE() LinizerState savedState = yyLinizerState
86 #define YY_RESTORE() yyLinizerState = savedState
89 QmlJSIndenter::QmlJSIndenter()
90 : caseOrDefault(QRegExp(QLatin1String(
99 The indenter supports a few parameters:
101 * ppHardwareTabSize is the size of a '\t' in your favorite editor.
102 * ppIndentSize is the size of an indentation, or software tab
104 * ppContinuationIndentSize is the extra indent for a continuation
105 line, when there is nothing to align against on the previous
107 * ppCommentOffset is the indentation within a C-style comment,
108 when it cannot be picked up.
111 ppHardwareTabSize = 8;
113 ppContinuationIndentSize = 8;
117 QmlJSIndenter::~QmlJSIndenter()
121 void QmlJSIndenter::setTabSize(int size)
123 ppHardwareTabSize = size;
126 void QmlJSIndenter::setIndentSize(int size)
129 ppContinuationIndentSize = 2 * size;
133 Returns true if string t is made only of white space; otherwise
136 bool QmlJSIndenter::isOnlyWhiteSpace(const QString &t) const
138 return firstNonWhiteSpace(t).isNull();
142 Assuming string t is a line, returns the column number of a given
143 index. Column numbers and index are identical for strings that don't
146 int QmlJSIndenter::columnForIndex(const QString &t, int index) const
149 if (index > t.length())
152 for (int i = 0; i < index; i++) {
153 if (t.at(i) == QLatin1Char('\t')) {
154 col = ((col / ppHardwareTabSize) + 1) * ppHardwareTabSize;
163 Returns the indentation size of string t.
165 int QmlJSIndenter::indentOfLine(const QString &t) const
167 return columnForIndex(t, t.indexOf(firstNonWhiteSpace(t)));
171 Replaces t[k] by ch, unless t[k] is '\t'. Tab characters are better
172 left alone since they break the "index equals column" rule. No
173 provisions are taken against '\n' or '\r', which shouldn't occur in
176 void QmlJSIndenter::eraseChar(QString &t, int k, QChar ch) const
178 if (t.at(k) != QLatin1Char('\t'))
183 Returns '(' if the last parenthesis is opening, ')' if it is
184 closing, and QChar() if there are no parentheses in t.
186 QChar QmlJSIndenter::lastParen() const
188 for (int index = yyLinizerState.tokens.size() - 1; index != -1; --index) {
189 const Token &token = yyLinizerState.tokens.at(index);
191 if (token.is(Token::LeftParenthesis))
194 else if (token.is(Token::RightParenthesis))
202 Returns true if typedIn the same as okayCh or is null; otherwise
205 bool QmlJSIndenter::okay(QChar typedIn, QChar okayCh) const
207 return typedIn == QChar() || typedIn == okayCh;
211 Returns the recommended indent for the bottom line of yyProgram
212 assuming that it starts in a C-style comment, a condition that is
215 Essentially, we're trying to align against some text on the
218 int QmlJSIndenter::indentWhenBottomLineStartsInMultiLineComment()
220 QTextBlock block = yyProgram.lastBlock().previous();
223 for (; block.isValid(); block = block.previous()) {
224 blockText = block.text();
226 if (! isOnlyWhiteSpace(blockText))
230 return indentOfLine(blockText);
234 Returns the recommended indent for the bottom line of yyProgram,
235 assuming it's a continuation line.
237 We're trying to align the continuation line against some parenthesis
238 or other bracked left opened on a previous line, or some interesting
239 operator such as '='.
241 int QmlJSIndenter::indentForContinuationLine()
246 bool leftBraceFollowed = *yyLeftBraceFollows;
248 for (int i = 0; i < SmallRoof; i++) {
251 int j = yyLine->length();
252 while (j > 0 && hook < 0) {
254 QChar ch = yyLine->at(j);
256 switch (ch.unicode()) {
269 An unclosed delimiter is a good place to align at,
270 at least for some styles (including Qt's).
272 if (delimDepth == -1)
279 An unclosed delimiter is a good place to align at,
280 at least for some styles (including Qt's).
282 if (braceDepth == -1)
288 A left brace followed by other stuff on the same
289 line is typically for an enum or an initializer.
290 Such a brace must be treated just like the other
293 if (braceDepth == -1) {
294 if (j < yyLine->length() - 1) {
297 return 0; // shouldn't happen
303 An equal sign is a very natural alignment hook
304 because it's usually the operator with the lowest
305 precedence in statements it appears in. Case in
311 However, we have to beware of constructs such as
312 default arguments and explicit enum constant
323 These constructs are caracterized by a ',' at the
324 end of the unfinished lines or by unbalanced
327 Q_ASSERT(j - 1 >= 0);
329 if (QString::fromLatin1("!=<>").indexOf(yyLine->at(j - 1)) == -1 &&
330 j + 1 < yyLine->length() && yyLine->at(j + 1) != '=') {
331 if (braceDepth == 0 && delimDepth == 0 &&
332 j < yyLine->length() - 1 &&
333 !yyLine->endsWith(QLatin1Char(',')) &&
334 (yyLine->contains(QLatin1Char('(')) == yyLine->contains(QLatin1Char(')'))))
342 Yes, we have a delimiter or an operator to align
343 against! We don't really align against it, but rather
344 against the following token, if any. In this example,
345 the following token is "11":
350 If there is no such token, we use a continuation indent:
352 static QRegExp foo(QString(
353 "foo foo foo foo foo foo foo foo foo"));
356 while (hook < yyLine->length()) {
357 if (!yyLine->at(hook).isSpace())
358 return columnForIndex(*yyLine, hook);
361 return indentOfLine(*yyLine) + ppContinuationIndentSize;
368 The line's delimiters are balanced. It looks like a
369 continuation line or something.
371 if (delimDepth == 0) {
372 if (leftBraceFollowed) {
385 The "{" should be flush left.
387 if (!isContinuationLine())
388 return indentOfLine(*yyLine);
389 } else if (isContinuationLine() || yyLine->endsWith(QLatin1String(","))) {
403 The "c;" should fall right under the "b +", and the
404 "4, 5, 6" right under the "1, 2, 3,".
406 return indentOfLine(*yyLine);
414 We could, but we don't, try to analyze which
415 operator has precedence over which and so on, to
416 obtain the excellent result
421 We do have a special trick above for the assignment
422 operator above, though.
424 return indentOfLine(*yyLine) + ppContinuationIndentSize;
435 Returns the recommended indent for the bottom line of yyProgram if
436 that line is standalone (or should be indented likewise).
438 Indenting a standalone line is tricky, mostly because of braceless
439 control statements. Grossly, we are looking backwards for a special
440 line, a "hook line", that we can use as a starting point to indent,
441 and then modify the indentation level according to the braces met
442 along the way to that hook.
444 Let's consider a few examples. In all cases, we want to indent the
452 The hook line is "x = 1;". We met 0 opening braces and 0 closing
453 braces. Therefore, "y = 2;" inherits the indent of "x = 1;".
460 The hook line is "if (x) {". No matter what precedes it, "y;" has
461 to be indented one level deeper than the hook line, since we met one
462 opening brace along the way.
472 To indent "d;" correctly, we have to go as far as the "if (a)".
481 Still, we're striving to go back as little as possible to
482 accommodate people with irregular indentation schemes. A hook line
483 near at hand is much more reliable than a remote one.
485 int QmlJSIndenter::indentForStandaloneLine()
487 for (int i = 0; i < SmallRoof; i++) {
488 if (!*yyLeftBraceFollows) {
491 if (matchBracelessControlStatement()) {
493 The situation is this, and we want to indent "z;":
499 yyLine is "if (x &&".
501 return indentOfLine(*yyLine) + ppIndentSize;
506 if (yyLine->endsWith(QLatin1Char(';')) || yyLine->contains(QLatin1Char('{'))) {
508 The situation is possibly this, and we want to indent
515 We return the indent of "while (x)". In place of "y;",
516 any arbitrarily complex compound statement can appear.
519 if (*yyBraceDepth > 0) {
523 } while (*yyBraceDepth > 0);
526 LinizerState hookState;
528 while (isContinuationLine())
530 hookState = yyLinizerState;
533 if (*yyBraceDepth <= 0) {
535 if (!matchBracelessControlStatement())
537 hookState = yyLinizerState;
538 } while (readLine());
541 yyLinizerState = hookState;
543 while (isContinuationLine())
546 int indentChange = - *yyBraceDepth;
547 if (caseOrDefault.exactMatch(*yyLine))
551 Never trust lines containing only '{' or '}', as some
552 people (Richard M. Stallman) format them weirdly.
554 if (yyLine->trimmed().length() > 1)
555 return indentOfLine(*yyLine) + indentChange * ppIndentSize;
559 return -*yyBraceDepth * ppIndentSize;
565 Returns the recommended indent for the bottom line of program.
566 Unless null, typedIn stores the character of yyProgram that
567 triggered reindentation.
569 This function works better if typedIn is set properly; it is
570 slightly more conservative if typedIn is completely wild, and
571 slighly more liberal if typedIn is always null. The user might be
572 annoyed by the liberal behavior.
574 int QmlJSIndenter::indentForBottomLine(QTextBlock begin, QTextBlock end, QChar typedIn)
579 const QTextBlock last = end.previous();
581 initialize(begin, last);
583 QString bottomLine = last.text();
584 QChar firstCh = firstNonWhiteSpace(bottomLine);
587 if (bottomLineStartsInMultilineComment()) {
589 The bottom line starts in a C-style comment. Indent it
590 smartly, unless the user has already played around with it,
591 in which case it's better to leave her stuff alone.
593 if (isOnlyWhiteSpace(bottomLine)) {
594 indent = indentWhenBottomLineStartsInMultiLineComment();
596 indent = indentOfLine(bottomLine);
599 if (isUnfinishedLine()) {
600 indent = indentForContinuationLine();
602 indent = indentForStandaloneLine();
605 if ((okay(typedIn, QLatin1Char('}')) && firstCh == QLatin1Char('}'))
606 || (okay(typedIn, QLatin1Char(']')) && firstCh == QLatin1Char(']'))) {
608 A closing brace is one level more to the left than the
611 indent -= ppIndentSize;
612 } else if (okay(typedIn, QLatin1Char(':'))) {
613 if (caseOrDefault.exactMatch(bottomLine)) {
615 Move a case label (or the ':' in front of a
616 constructor initialization list) one level to the
617 left, but only if the user did not play around with
618 it yet. Some users have exotic tastes in the
619 matter, and most users probably are not patient
620 enough to wait for the final ':' to format their
623 We don't attempt the same for goto labels, as the
624 user is probably the middle of "foo::bar". (Who
627 if (indentOfLine(bottomLine) <= indent)
628 indent -= ppIndentSize;
630 indent = indentOfLine(bottomLine);
635 return qMax(0, indent);