summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Kamm <christian.d.kamm@nokia.com>2010-07-07 11:45:18 +0200
committerChristian Kamm <christian.d.kamm@nokia.com>2010-08-10 14:27:08 +0200
commit822de6c17ab52002ae59a94c3e231bf0d5e3e438 (patch)
treea683fcd312b3f5840cd326ae2106f377fcd36505
parentf6232260c2c5ba93e85d725e05c2d2c9b86e293c (diff)
downloadqt-creator-822de6c17ab52002ae59a94c3e231bf0d5e3e438.tar.gz
QmlJS: Introduce a new indenter that works similarly to the new C++ one.
Done-with: Thomas Hartmann
-rw-r--r--src/libs/qmljs/qmljs-lib.pri9
-rw-r--r--src/libs/qmljs/qmljscodeformatter.cpp910
-rw-r--r--src/libs/qmljs/qmljscodeformatter.h309
-rw-r--r--src/plugins/qmljseditor/qmljseditor.cpp24
-rw-r--r--src/plugins/qmljseditor/qmljseditor.pro6
-rw-r--r--src/plugins/qmljseditor/qmljseditorcodeformatter.cpp358
-rw-r--r--src/plugins/qmljseditor/qmljseditorcodeformatter.h69
-rw-r--r--tests/auto/qml/qmleditor/codeformatter/codeformatter.pro24
-rw-r--r--tests/auto/qml/qmleditor/codeformatter/tst_codeformatter.cpp888
-rw-r--r--tests/auto/qml/qmleditor/qmleditor.pro2
10 files changed, 2587 insertions, 12 deletions
diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri
index 0c4cb8e17a..fa9484182b 100644
--- a/src/libs/qmljs/qmljs-lib.pri
+++ b/src/libs/qmljs/qmljs-lib.pri
@@ -56,6 +56,11 @@ OTHER_FILES += \
$$PWD/parser/qmljs.g
contains(QT, gui) {
- SOURCES += $$PWD/qmljsindenter.cpp
- HEADERS += $$PWD/qmljsindenter.h
+ SOURCES += \
+ $$PWD/qmljsindenter.cpp \
+ $$PWD/qmljscodeformatter.cpp
+
+ HEADERS += \
+ $$PWD/qmljsindenter.h \
+ $$PWD/qmljscodeformatter.h
}
diff --git a/src/libs/qmljs/qmljscodeformatter.cpp b/src/libs/qmljs/qmljscodeformatter.cpp
new file mode 100644
index 0000000000..468d112cbc
--- /dev/null
+++ b/src/libs/qmljs/qmljscodeformatter.cpp
@@ -0,0 +1,910 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "qmljscodeformatter.h"
+
+#include <QtCore/QDebug>
+#include <QtCore/QMetaEnum>
+#include <QtGui/QTextDocument>
+#include <QtGui/QTextCursor>
+#include <QtGui/QTextBlock>
+
+using namespace QmlJS;
+
+CodeFormatter::BlockData::BlockData()
+ : m_blockRevision(-1)
+{
+}
+
+CodeFormatter::CodeFormatter()
+ : m_indentDepth(0)
+ , m_tabSize(4)
+{
+}
+
+CodeFormatter::~CodeFormatter()
+{
+}
+
+void CodeFormatter::setTabSize(int tabSize)
+{
+ m_tabSize = tabSize;
+}
+
+void CodeFormatter::recalculateStateAfter(const QTextBlock &block)
+{
+ restoreCurrentState(block.previous());
+
+ const int lexerState = tokenizeBlock(block);
+ m_tokenIndex = 0;
+ m_newStates.clear();
+
+ //qDebug() << "Starting to look at " << block.text() << block.blockNumber() + 1;
+
+ for (; m_tokenIndex < m_tokens.size(); ) {
+ m_currentToken = tokenAt(m_tokenIndex);
+ const int kind = extendedTokenKind(m_currentToken);
+ //dump();
+ //qDebug() << "Token" << m_currentLine.mid(m_currentToken.begin(), m_currentToken.length) << m_tokenIndex << "in line" << block.blockNumber() + 1;
+
+ if (kind == Comment
+ && state().type != multiline_comment_cont
+ && state().type != multiline_comment_start) {
+ m_tokenIndex += 1;
+ continue;
+ }
+
+ switch (m_currentState.top().type) {
+ case topmost_intro:
+ switch (kind) {
+ case Identifier: enter(objectdefinition_or_js); continue;
+ case Import: enter(top_qml); continue;
+ default: enter(top_js); continue;
+ } break;
+
+ case top_qml:
+ switch (kind) {
+ case Import: enter(import_start); break;
+ case Identifier: enter(binding_or_objectdefinition); break;
+ } break;
+
+ case top_js:
+ tryStatement();
+ break;
+
+ case objectdefinition_or_js:
+ switch (kind) {
+ case Dot: break;
+ case Identifier:
+ if (!m_currentLine.at(m_currentToken.begin()).isUpper()) {
+ turnInto(top_js);
+ continue;
+ }
+ break;
+ case LeftBrace: turnInto(binding_or_objectdefinition); continue;
+ default: turnInto(top_js); continue;
+ } break;
+
+ case import_start:
+ enter(import_maybe_dot_or_version_or_as);
+ break;
+
+ case import_maybe_dot_or_version_or_as:
+ switch (kind) {
+ case Dot: turnInto(import_dot); break;
+ case As: turnInto(import_as); break;
+ case Number: turnInto(import_maybe_as); break;
+ default: leave(); leave(); continue;
+ } break;
+
+ case import_maybe_as:
+ switch (kind) {
+ case As: turnInto(import_as); break;
+ default: leave(); leave(); continue;
+ } break;
+
+ case import_dot:
+ switch (kind) {
+ case Identifier: turnInto(import_maybe_dot_or_version_or_as); break;
+ default: leave(); leave(); continue;
+ } break;
+
+ case import_as:
+ switch (kind) {
+ case Identifier: leave(); leave(); break;
+ } break;
+
+ case binding_or_objectdefinition:
+ switch (kind) {
+ case Colon: enter(binding_assignment); break;
+ case LeftBrace: enter(objectdefinition_open); break;
+ } break;
+
+ case binding_assignment:
+ switch (kind) {
+ case Semicolon: leave(true); break;
+ case If: enter(if_statement); break;
+ case LeftBrace: enter(jsblock_open); break;
+ case On:
+ case As:
+ case List:
+ case Import:
+ case Signal:
+ case Property:
+ case Identifier: enter(expression_or_objectdefinition); break;
+ default: enter(expression); continue;
+ } break;
+
+ case objectdefinition_open:
+ switch (kind) {
+ case RightBrace: leave(true); break;
+ case Default: enter(default_property_start); break;
+ case Property: enter(property_start); break;
+ case Function: enter(function_start); break;
+ case Signal: enter(signal_start); break;
+ case On:
+ case As:
+ case List:
+ case Import:
+ case Identifier: enter(binding_or_objectdefinition); break;
+ } break;
+
+ case default_property_start:
+ if (kind != Property)
+ leave(true);
+ else
+ turnInto(property_start);
+ break;
+
+ case property_start:
+ switch (kind) {
+ case Colon: enter(binding_assignment); break; // oops, was a binding
+ case Var:
+ case Identifier: enter(property_type); break;
+ case List: enter(property_list_open); break;
+ default: leave(true); continue;
+ } break;
+
+ case property_type:
+ turnInto(property_maybe_initializer);
+ break;
+
+ case property_list_open:
+ if (m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length) == QLatin1String(">"))
+ turnInto(property_maybe_initializer);
+ break;
+
+ case property_maybe_initializer:
+ switch (kind) {
+ case Colon: enter(binding_assignment); break;
+ default: leave(true); continue;
+ } break;
+
+ case signal_start:
+ switch (kind) {
+ case Colon: enter(binding_assignment); break; // oops, was a binding
+ default: enter(signal_maybe_arglist); break;
+ } break;
+
+ case signal_maybe_arglist:
+ switch (kind) {
+ case LeftParenthesis: turnInto(signal_arglist_open); break;
+ default: leave(true); continue;
+ } break;
+
+ case signal_arglist_open:
+ switch (kind) {
+ case RightParenthesis: leave(true); break;
+ } break;
+
+ case function_start:
+ switch (kind) {
+ case LeftParenthesis: enter(function_arglist_open); break;
+ } break;
+
+ case function_arglist_open:
+ switch (kind) {
+ case RightParenthesis: turnInto(function_arglist_closed); break;
+ } break;
+
+ case function_arglist_closed:
+ switch (kind) {
+ case LeftBrace: turnInto(jsblock_open); break;
+ default: leave(true); continue; // error recovery
+ } break;
+
+ case expression_or_objectdefinition:
+ switch (kind) {
+ case LeftBrace: turnInto(objectdefinition_open); break;
+ default: enter(expression); continue; // really? first token already gone!
+ } break;
+
+ case expression:
+ if (tryInsideExpression())
+ break;
+ switch (kind) {
+ case Comma:
+ case Delimiter: enter(expression_continuation); break;
+ case RightBracket:
+ case RightParenthesis: leave(); continue;
+ case RightBrace: leave(true); continue;
+ case Semicolon: leave(true); break;
+ } break;
+
+ case expression_continuation:
+ leave();
+ continue;
+
+ case expression_maybe_continuation:
+ switch (kind) {
+ case Question:
+ case Delimiter:
+ case LeftBracket:
+ case LeftParenthesis: leave(); continue;
+ default: leave(true); continue;
+ } break;
+
+ case paren_open:
+ if (tryInsideExpression())
+ break;
+ switch (kind) {
+ case RightParenthesis: leave(); break;
+ } break;
+
+ case bracket_open:
+ if (tryInsideExpression())
+ break;
+ switch (kind) {
+ case Comma: enter(bracket_element_start); break;
+ case RightBracket: leave(); break;
+ } break;
+
+ case bracket_element_start:
+ switch (kind) {
+ case Identifier: turnInto(bracket_element_maybe_objectdefinition); break;
+ default: leave(); continue;
+ } break;
+
+ case bracket_element_maybe_objectdefinition:
+ switch (kind) {
+ case LeftBrace: turnInto(objectdefinition_open); break;
+ default: leave(); continue;
+ } break;
+
+ case ternary_op:
+ if (tryInsideExpression())
+ break;
+ switch (kind) {
+ case RightParenthesis:
+ case RightBracket:
+ case RightBrace:
+ case Comma:
+ case Semicolon: leave(); continue;
+ case Colon: enter(expression); break; // entering expression makes maybe_continuation work
+ } break;
+
+ case jsblock_open:
+ case substatement_open:
+ if (tryStatement())
+ break;
+ switch (kind) {
+ case RightBrace: leave(true); break;
+ } break;
+
+ case substatement:
+ // prefer substatement_open over block_open
+ if (kind != LeftBrace) {
+ if (tryStatement())
+ break;
+ }
+ switch (kind) {
+ case LeftBrace: turnInto(substatement_open); break;
+ } break;
+
+ case if_statement:
+ switch (kind) {
+ case LeftParenthesis: enter(condition_open); break;
+ default: leave(true); break; // error recovery
+ } break;
+
+ case maybe_else:
+ if (kind == Else) {
+ turnInto(else_clause);
+ enter(substatement);
+ break;
+ } else {
+ leave(true);
+ continue;
+ }
+
+ case else_clause:
+ // ### shouldn't happen
+ dump();
+ Q_ASSERT(false);
+ leave(true);
+ break;
+
+ case condition_open:
+ switch (kind) {
+ case RightParenthesis: turnInto(substatement); break;
+ case LeftParenthesis: enter(condition_paren_open); break;
+ } break;
+
+ // paren nesting
+ case condition_paren_open:
+ switch (kind) {
+ case RightParenthesis: leave(); break;
+ case LeftParenthesis: enter(condition_paren_open); break;
+ } break;
+
+ case switch_statement:
+ case statement_with_condition:
+ switch (kind) {
+ case LeftParenthesis: enter(statement_with_condition_paren_open); break;
+ default: leave(true);
+ } break;
+
+ case statement_with_condition_paren_open:
+ if (tryInsideExpression())
+ break;
+ switch (kind) {
+ case RightParenthesis: turnInto(substatement); break;
+ } break;
+
+ case statement_with_block:
+ switch (kind) {
+ case LeftBrace: enter(jsblock_open); break;
+ default: leave(true); break;
+ } break;
+
+ case do_statement:
+ switch (kind) {
+ case While: break;
+ case LeftParenthesis: enter(do_statement_while_paren_open); break;
+ default: leave(true); break;
+ } break;
+
+ case do_statement_while_paren_open:
+ if (tryInsideExpression())
+ break;
+ switch (kind) {
+ case RightParenthesis: leave(); leave(true); break;
+ } break;
+
+ break;
+
+ case case_start:
+ switch (kind) {
+ case Colon: turnInto(case_cont); break;
+ } break;
+
+ case case_cont:
+ if (kind != Case && kind != Default && tryStatement())
+ break;
+ switch (kind) {
+ case RightBrace: leave(); continue;
+ case Default:
+ case Case: leave(); continue;
+ } break;
+
+ case multiline_comment_start:
+ case multiline_comment_cont:
+ if (kind != Comment) {
+ leave();
+ continue;
+ } else if (m_tokenIndex == m_tokens.size() - 1
+ && lexerState == Scanner::Normal) {
+ leave();
+ } else if (m_tokenIndex == 0) {
+ // to allow enter/leave to update the indentDepth
+ turnInto(multiline_comment_cont);
+ }
+ break;
+
+ default:
+ qWarning() << "Unhandled state" << m_currentState.top().type;
+ break;
+ } // end of state switch
+
+ ++m_tokenIndex;
+ }
+
+ int topState = m_currentState.top().type;
+
+ if (topState == expression
+ || topState == expression_or_objectdefinition) {
+ enter(expression_maybe_continuation);
+ }
+ if (topState != multiline_comment_start
+ && topState != multiline_comment_cont
+ && lexerState == Scanner::MultiLineComment) {
+ enter(multiline_comment_start);
+ }
+
+ saveCurrentState(block);
+}
+
+int CodeFormatter::indentFor(const QTextBlock &block)
+{
+// qDebug() << "indenting for" << block.blockNumber() + 1;
+
+ restoreCurrentState(block.previous());
+ correctIndentation(block);
+ return m_indentDepth;
+}
+
+void CodeFormatter::updateStateUntil(const QTextBlock &endBlock)
+{
+ QStack<State> previousState = initialState();
+ QTextBlock it = endBlock.document()->firstBlock();
+
+ // find the first block that needs recalculation
+ for (; it.isValid() && it != endBlock; it = it.next()) {
+ BlockData blockData;
+ if (!loadBlockData(it, &blockData))
+ break;
+ if (blockData.m_blockRevision != it.revision())
+ break;
+ if (previousState != blockData.m_beginState)
+ break;
+ if (loadLexerState(it) == -1)
+ break;
+
+ previousState = blockData.m_endState;
+ }
+
+ if (it == endBlock)
+ return;
+
+ // update everthing until endBlock
+ for (; it.isValid() && it != endBlock; it = it.next()) {
+ recalculateStateAfter(it);
+ }
+
+ // invalidate everything below by marking the state in endBlock as invalid
+ if (it.isValid()) {
+ BlockData invalidBlockData;
+ saveBlockData(&it, invalidBlockData);
+ }
+}
+
+void CodeFormatter::updateLineStateChange(const QTextBlock &block)
+{
+ if (!block.isValid())
+ return;
+
+ BlockData blockData;
+ if (loadBlockData(block, &blockData) && blockData.m_blockRevision == block.revision())
+ return;
+
+ recalculateStateAfter(block);
+
+ // invalidate everything below by marking the next block's state as invalid
+ QTextBlock next = block.next();
+ if (!next.isValid())
+ return;
+
+ saveBlockData(&next, BlockData());
+}
+
+CodeFormatter::State CodeFormatter::state(int belowTop) const
+{
+ if (belowTop < m_currentState.size())
+ return m_currentState.at(m_currentState.size() - 1 - belowTop);
+ else
+ return State();
+}
+
+const QVector<CodeFormatter::State> &CodeFormatter::newStatesThisLine() const
+{
+ return m_newStates;
+}
+
+int CodeFormatter::tokenIndex() const
+{
+ return m_tokenIndex;
+}
+
+int CodeFormatter::tokenCount() const
+{
+ return m_tokens.size();
+}
+
+const Token &CodeFormatter::currentToken() const
+{
+ return m_currentToken;
+}
+
+void CodeFormatter::invalidateCache(QTextDocument *document)
+{
+ if (!document)
+ return;
+
+ BlockData invalidBlockData;
+ QTextBlock it = document->firstBlock();
+ for (; it.isValid(); it = it.next()) {
+ saveBlockData(&it, invalidBlockData);
+ }
+}
+
+void CodeFormatter::enter(int newState)
+{
+ int savedIndentDepth = m_indentDepth;
+ onEnter(newState, &m_indentDepth, &savedIndentDepth);
+ State s(newState, savedIndentDepth);
+ m_currentState.push(s);
+ m_newStates.push(s);
+
+ if (newState == bracket_open)
+ enter(bracket_element_start);
+}
+
+void CodeFormatter::leave(bool statementDone)
+{
+ Q_ASSERT(m_currentState.size() > 1);
+ if (m_currentState.top().type == topmost_intro)
+ return;
+
+ if (m_newStates.size() > 0)
+ m_newStates.pop();
+
+ // restore indent depth
+ State poppedState = m_currentState.pop();
+ m_indentDepth = poppedState.savedIndentDepth;
+
+ int topState = m_currentState.top().type;
+
+ // if statement is done, may need to leave recursively
+ if (statementDone) {
+ if (!isExpressionEndState(topState))
+ leave(true);
+ if (topState == if_statement) {
+ if (poppedState.type != maybe_else)
+ enter(maybe_else);
+ else
+ leave(true);
+ } else if (topState == else_clause) {
+ // leave the else *and* the surrounding if, to prevent another else
+ leave();
+ leave(true);
+ }
+ }
+}
+
+void CodeFormatter::correctIndentation(const QTextBlock &block)
+{
+ const int lexerState = tokenizeBlock(block);
+ Q_ASSERT(m_currentState.size() >= 1);
+
+ adjustIndent(m_tokens, lexerState, &m_indentDepth);
+}
+
+bool CodeFormatter::tryInsideExpression(bool alsoExpression)
+{
+ int newState = -1;
+ const int kind = extendedTokenKind(m_currentToken);
+ switch (kind) {
+ case LeftParenthesis: newState = paren_open; break;
+ case LeftBracket: newState = bracket_open; break;
+ case Function: newState = function_start; break;
+ case Question: newState = ternary_op; break;
+ }
+
+ if (newState != -1) {
+ if (alsoExpression)
+ enter(expression);
+ enter(newState);
+ return true;
+ }
+
+ return false;
+}
+
+bool CodeFormatter::tryStatement()
+{
+ const int kind = extendedTokenKind(m_currentToken);
+ switch (kind) {
+ case Semicolon:
+ enter(empty_statement);
+ leave(true);
+ return true;
+ case Return:
+ enter(return_statement);
+ enter(expression);
+ return true;
+ case While:
+ case For:
+ case Catch:
+ enter(statement_with_condition);
+ return true;
+ case Switch:
+ enter(switch_statement);
+ return true;
+ case If:
+ enter(if_statement);
+ return true;
+ case Do:
+ enter(do_statement);
+ enter(substatement);
+ return true;
+ case Case:
+ case Default:
+ enter(case_start);
+ return true;
+ case Try:
+ case Finally:
+ enter(statement_with_block);
+ return true;
+ case LeftBrace:
+ enter(jsblock_open);
+ return true;
+ case Identifier:
+ case Delimiter:
+ case PlusPlus:
+ case MinusMinus:
+ case Import:
+ case Signal:
+ case On:
+ case As:
+ case List:
+ case Property:
+ case Function:
+ enter(expression);
+ // look at the token again
+ m_tokenIndex -= 1;
+ return true;
+ }
+ return false;
+}
+
+bool CodeFormatter::isBracelessState(int type) const
+{
+ return
+ type == if_statement ||
+ type == else_clause ||
+ type == substatement ||
+ type == binding_assignment ||
+ type == binding_or_objectdefinition;
+}
+
+bool CodeFormatter::isExpressionEndState(int type) const
+{
+ return
+ type == topmost_intro ||
+ type == top_js ||
+ type == objectdefinition_open ||
+ type == if_statement ||
+ type == else_clause ||
+ type == do_statement ||
+ type == jsblock_open ||
+ type == substatement_open ||
+ type == bracket_open ||
+ type == paren_open ||
+ type == case_cont;
+}
+
+const Token &CodeFormatter::tokenAt(int idx) const
+{
+ static const Token empty;
+ if (idx < 0 || idx >= m_tokens.size())
+ return empty;
+ else
+ return m_tokens.at(idx);
+}
+
+int CodeFormatter::column(int index) const
+{
+ int col = 0;
+ if (index > m_currentLine.length())
+ index = m_currentLine.length();
+
+ const QChar tab = QLatin1Char('\t');
+
+ for (int i = 0; i < index; i++) {
+ if (m_currentLine[i] == tab) {
+ col = ((col / m_tabSize) + 1) * m_tabSize;
+ } else {
+ col++;
+ }
+ }
+ return col;
+}
+
+QStringRef CodeFormatter::currentTokenText() const
+{
+ return m_currentLine.midRef(m_currentToken.begin(), m_currentToken.length);
+}
+
+void CodeFormatter::turnInto(int newState)
+{
+ leave(false);
+ enter(newState);
+}
+
+void CodeFormatter::saveCurrentState(const QTextBlock &block)
+{
+ if (!block.isValid())
+ return;
+
+ BlockData blockData;
+ blockData.m_blockRevision = block.revision();
+ blockData.m_beginState = m_beginState;
+ blockData.m_endState = m_currentState;
+ blockData.m_indentDepth = m_indentDepth;
+
+ QTextBlock saveableBlock(block);
+ saveBlockData(&saveableBlock, blockData);
+}
+
+void CodeFormatter::restoreCurrentState(const QTextBlock &block)
+{
+ if (block.isValid()) {
+ BlockData blockData;
+ if (loadBlockData(block, &blockData)) {
+ m_indentDepth = blockData.m_indentDepth;
+ m_currentState = blockData.m_endState;
+ m_beginState = m_currentState;
+ return;
+ }
+ }
+
+ m_currentState = initialState();
+ m_beginState = m_currentState;
+ m_indentDepth = 0;
+}
+
+QStack<CodeFormatter::State> CodeFormatter::initialState()
+{
+ static QStack<CodeFormatter::State> initialState;
+ if (initialState.isEmpty())
+ initialState.push(State(topmost_intro, 0));
+ return initialState;
+}
+
+int CodeFormatter::tokenizeBlock(const QTextBlock &block)
+{
+ int startState = loadLexerState(block.previous());
+ if (block.blockNumber() == 0)
+ startState = 0;
+ Q_ASSERT(startState != -1);
+
+ Scanner tokenize;
+ tokenize.setScanComments(true);
+
+ m_currentLine = block.text();
+ // to determine whether a line was joined, Tokenizer needs a
+ // newline character at the end
+ m_currentLine.append(QLatin1Char('\n'));
+ m_tokens = tokenize(m_currentLine, startState);
+
+ const int lexerState = tokenize.state();
+ QTextBlock saveableBlock(block);
+ saveLexerState(&saveableBlock, lexerState);
+ return lexerState;
+}
+
+CodeFormatter::TokenKind CodeFormatter::extendedTokenKind(const QmlJS::Token &token) const
+{
+ const int kind = token.kind;
+ QStringRef text = m_currentLine.midRef(token.begin(), token.length);
+
+ if (kind == Identifier) {
+ if (text == "as")
+ return As;
+ if (text == "import")
+ return Import;
+ if (text == "signal")
+ return Signal;
+ if (text == "property")
+ return Property;
+ if (text == "on")
+ return On;
+ if (text == "list")
+ return On;
+ } else if (kind == Keyword) {
+ const QChar char1 = text.at(0);
+ const QChar char2 = text.at(1);
+ const QChar char3 = (text.size() > 2 ? text.at(2) : QChar());
+ switch (char1.toLatin1()) {
+ case 'v':
+ return Var;
+ case 'i':
+ if (char2 == 'f')
+ return If;
+ else if (char3 == 's')
+ return Instanceof;
+ else
+ return In;
+ case 'f':
+ if (char2 == 'o')
+ return For;
+ else if (char2 == 'u')
+ return Function;
+ else
+ return Finally;
+ case 'e':
+ return Else;
+ case 'n':
+ return New;
+ case 'r':
+ return Return;
+ case 's':
+ return Switch;
+ case 'w':
+ if (char2 == 'h')
+ return While;
+ return With;
+ case 'c':
+ if (char3 == 's')
+ return Case;
+ if (char3 == 't')
+ return Catch;
+ return Continue;
+ case 'd':
+ if (char3 == 'l')
+ return Delete;
+ if (char3 == 'f')
+ return Default;
+ if (char3 == 'b')
+ return Debugger;
+ return Do;
+ case 't':
+ if (char3 == 'i')
+ return This;
+ if (char3 == 'y')
+ return Try;
+ if (char3 == 'r')
+ return Throw;
+ return Typeof;
+ case 'b':
+ return Break;
+ }
+ } else if (kind == Delimiter) {
+ if (text == "?")
+ return Question;
+ else if (text == "++")
+ return PlusPlus;
+ else if (text == "--")
+ return MinusMinus;
+ }
+
+ return static_cast<TokenKind>(kind);
+}
+
+void CodeFormatter::dump() const
+{
+ QMetaEnum metaEnum = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("StateType"));
+
+ qDebug() << "Current token index" << m_tokenIndex;
+ qDebug() << "Current state:";
+ foreach (State s, m_currentState) {
+ qDebug() << metaEnum.valueToKey(s.type) << s.savedIndentDepth;
+ }
+ qDebug() << "Current indent depth:" << m_indentDepth;
+}
diff --git a/src/libs/qmljs/qmljscodeformatter.h b/src/libs/qmljs/qmljscodeformatter.h
new file mode 100644
index 0000000000..40954647f5
--- /dev/null
+++ b/src/libs/qmljs/qmljscodeformatter.h
@@ -0,0 +1,309 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef QMLJSCODEFORMATTER_H
+#define QMLJSCODEFORMATTER_H
+
+#include "qmljs_global.h"
+
+#include "qmljsscanner.h"
+
+#include <QtCore/QChar>
+#include <QtCore/QStack>
+#include <QtCore/QList>
+#include <QtCore/QVector>
+#include <QtCore/QPointer>
+
+QT_BEGIN_NAMESPACE
+class QTextDocument;
+class QTextBlock;
+QT_END_NAMESPACE
+
+namespace QmlJS {
+
+class QMLJS_EXPORT CodeFormatter
+{
+ Q_GADGET
+public:
+ CodeFormatter();
+ virtual ~CodeFormatter();
+
+ // updates all states up until block if necessary
+ // it is safe to call indentFor on block afterwards
+ void updateStateUntil(const QTextBlock &block);
+
+ // calculates the state change introduced by changing a single line
+ void updateLineStateChange(const QTextBlock &block);
+
+ int indentFor(const QTextBlock &block);
+
+ void setTabSize(int tabSize);
+
+ void invalidateCache(QTextDocument *document);
+
+protected:
+ virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const = 0;
+ virtual void adjustIndent(const QList<Token> &tokens, int lexerState, int *indentDepth) const = 0;
+
+ class State;
+ class BlockData
+ {
+ public:
+ BlockData();
+
+ QStack<State> m_beginState;
+ QStack<State> m_endState;
+ int m_indentDepth;
+ int m_blockRevision;
+ };
+
+ virtual void saveBlockData(QTextBlock *block, const BlockData &data) const = 0;
+ virtual bool loadBlockData(const QTextBlock &block, BlockData *data) const = 0;
+
+ virtual void saveLexerState(QTextBlock *block, int state) const = 0;
+ virtual int loadLexerState(const QTextBlock &block) const = 0;
+
+public: // must be public to make Q_GADGET introspection work
+ enum StateType {
+ invalid = 0,
+
+ topmost_intro, // The first line in a "topmost" definition.
+
+ top_qml, // root state for qml
+ top_js, // root for js
+ objectdefinition_or_js, // file starts with identifier
+
+ multiline_comment_start,
+ multiline_comment_cont,
+
+ import_start, // after 'import'
+ import_maybe_dot_or_version_or_as, // after string or identifier
+ import_dot, // after .
+ import_maybe_as, // after version
+ import_as,
+
+ property_start, // after 'property'
+ default_property_start, // after 'default'
+ property_type, // after first identifier
+ property_list_open, // after 'list' as a type
+ property_maybe_initializer, // after
+
+ signal_start, // after 'signal'
+ signal_maybe_arglist, // after identifier
+ signal_arglist_open, // after '('
+
+ function_start, // after 'function'
+ function_arglist_open, // after '(' starting function argument list
+ function_arglist_closed, // after ')' in argument list, expecting '{'
+
+ binding_or_objectdefinition, // after an identifier
+
+ binding_assignment, // after :
+ objectdefinition_open, // after {
+
+ expression,
+ expression_continuation, // at the end of the line, when the next line definitely is a continuation
+ expression_maybe_continuation, // at the end of the line, when the next line may be an expression
+ expression_or_objectdefinition, // after a binding starting with an identifier ("x: foo")
+
+ paren_open, // opening ( in expression
+ bracket_open, // opening [ in expression
+
+ bracket_element_start, // after starting bracket_open or after ',' in bracket_open
+ bracket_element_maybe_objectdefinition, // after an identifier in bracket_element_start
+
+ ternary_op, // The ? : operator
+
+ jsblock_open,
+
+ empty_statement, // for a ';', will never linger
+
+ if_statement, // After 'if'
+ maybe_else, // after the first substatement in an if
+ else_clause, // The else line of an if-else construct.
+
+ condition_open, // Start of a condition in 'if', 'while', entered after opening paren
+ condition_paren_open, // After an lparen in a condition
+
+ substatement, // The first line after a conditional or loop construct.
+ substatement_open, // The brace that opens a substatement block.
+
+ return_statement, // After 'return'
+
+ statement_with_condition, // After the 'for', 'while', 'catch', ... token
+ statement_with_condition_paren_open, // While inside the (...)
+
+ statement_with_block, // try, finally
+
+ do_statement, // after 'do'
+ do_statement_while_paren_open, // after '(' in while clause
+
+ switch_statement, // After 'switch' token
+ case_start, // after a 'case' or 'default' token
+ case_cont, // after the colon in a case/default
+ };
+ Q_ENUMS(StateType)
+
+protected:
+ // extends Token::Kind from qmljsscanner.h
+ // the entries until EndOfExistingTokenKinds must match
+ enum TokenKind {
+ EndOfFile,
+ Keyword,
+ Identifier,
+ String,
+ Comment,
+ Number,
+ LeftParenthesis,
+ RightParenthesis,
+ LeftBrace,
+ RightBrace,
+ LeftBracket,
+ RightBracket,
+ Semicolon,
+ Colon,
+ Comma,
+ Dot,
+ Delimiter,
+
+ EndOfExistingTokenKinds,
+
+ Break,
+ Case,
+ Catch,
+ Continue,
+ Debugger,
+ Default,
+ Delete,
+ Do,
+ Else,
+ Finally,
+ For,
+ Function,
+ If,
+ In,
+ Instanceof,
+ New,
+ Return,
+ Switch,
+ This,
+ Throw,
+ Try,
+ Typeof,
+ Var,
+ Void,
+ While,
+ With,
+
+ Import,
+ Signal,
+ On,
+ As,
+ List,
+ Property,
+
+ Question,
+ PlusPlus,
+ MinusMinus,
+ };
+
+ TokenKind extendedTokenKind(const QmlJS::Token &token) const;
+
+ struct State {
+ State()
+ : savedIndentDepth(0)
+ , type(0)
+ {}
+
+ State(quint8 ty, quint16 savedDepth)
+ : savedIndentDepth(savedDepth)
+ , type(ty)
+ {}
+
+ quint16 savedIndentDepth;
+ quint8 type;
+
+ bool operator==(const State &other) const {
+ return type == other.type
+ && savedIndentDepth == other.savedIndentDepth;
+ }
+ };
+
+ State state(int belowTop = 0) const;
+ const QVector<State> &newStatesThisLine() const;
+ int tokenIndex() const;
+ int tokenCount() const;
+ const Token &currentToken() const;
+ const Token &tokenAt(int idx) const;
+ int column(int position) const;
+
+ bool isBracelessState(int type) const;
+ bool isExpressionEndState(int type) const;
+
+ void dump() const;
+
+private:
+ void recalculateStateAfter(const QTextBlock &block);
+ void saveCurrentState(const QTextBlock &block);
+ void restoreCurrentState(const QTextBlock &block);
+
+ QStringRef currentTokenText() const;
+
+ int tokenizeBlock(const QTextBlock &block);
+
+ void turnInto(int newState);
+
+ bool tryInsideExpression(bool alsoExpression = false);
+ bool tryStatement();
+
+ void enter(int newState);
+ void leave(bool statementDone = false);
+ void correctIndentation(const QTextBlock &block);
+
+private:
+ static QStack<State> initialState();
+
+ QStack<State> m_beginState;
+ QStack<State> m_currentState;
+ QStack<State> m_newStates;
+
+ QList<Token> m_tokens;
+ QString m_currentLine;
+ Token m_currentToken;
+ int m_tokenIndex;
+
+ // should store indent level and padding instead
+ int m_indentDepth;
+
+ int m_tabSize;
+};
+
+} // namespace QmlJS
+
+#endif // QMLJSCODEFORMATTER_H
diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp
index bf01cd0381..6119e11999 100644
--- a/src/plugins/qmljseditor/qmljseditor.cpp
+++ b/src/plugins/qmljseditor/qmljseditor.cpp
@@ -33,8 +33,8 @@
#include "qmljseditorplugin.h"
#include "qmljsmodelmanager.h"
#include "qmloutlinemodel.h"
+#include "qmljseditorcodeformatter.h"
-#include <qmljs/qmljsindenter.h>
#include <qmljs/qmljsbind.h>
#include <qmljs/qmljscheck.h>
#include <qmljs/qmljsdocument.h>
@@ -1276,15 +1276,25 @@ bool QmlJSTextEditor::isClosingBrace(const QList<Token> &tokens) const
return false;
}
+static QmlJSEditor::QtStyleCodeFormatter setupCodeFormatter(const TextEditor::TabSettings &ts)
+{
+ QmlJSEditor::QtStyleCodeFormatter codeFormatter;
+ codeFormatter.setIndentSize(ts.m_indentSize);
+ codeFormatter.setTabSize(ts.m_tabSize);
+ return codeFormatter;
+}
+
void QmlJSTextEditor::indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar)
{
- TextEditor::TabSettings ts = tabSettings();
- QmlJSIndenter indenter;
- indenter.setTabSize(ts.m_tabSize);
- indenter.setIndentSize(ts.m_indentSize);
+ Q_UNUSED(doc)
+ Q_UNUSED(typedChar)
+
+ const TextEditor::TabSettings &ts = tabSettings();
+ QmlJSEditor::QtStyleCodeFormatter codeFormatter = setupCodeFormatter(ts);
- const int indent = indenter.indentForBottomLine(doc->begin(), block.next(), typedChar);
- ts.indentLine(block, indent);
+ codeFormatter.updateStateUntil(block);
+ const int depth = codeFormatter.indentFor(block);
+ ts.indentLine(block, depth);
}
TextEditor::BaseTextEditorEditable *QmlJSTextEditor::createEditableInterface()
diff --git a/src/plugins/qmljseditor/qmljseditor.pro b/src/plugins/qmljseditor/qmljseditor.pro
index 6bcd71a22b..72fa465762 100644
--- a/src/plugins/qmljseditor/qmljseditor.pro
+++ b/src/plugins/qmljseditor/qmljseditor.pro
@@ -26,7 +26,8 @@ HEADERS += \
qmljscomponentfromobjectdef.h \
qmljsoutline.h \
qmloutlinemodel.h \
- qmltaskmanager.h
+ qmltaskmanager.h \
+ qmljseditorcodeformatter.h
SOURCES += \
qmljscodecompletion.cpp \
@@ -46,7 +47,8 @@ SOURCES += \
qmljsoutline.cpp \
qmloutlinemodel.cpp \
qmltaskmanager.cpp \
- qmljsquickfixes.cpp
+ qmljsquickfixes.cpp \
+ qmljseditorcodeformatter.cpp
RESOURCES += qmljseditor.qrc
OTHER_FILES += QmlJSEditor.pluginspec QmlJSEditor.mimetypes.xml
diff --git a/src/plugins/qmljseditor/qmljseditorcodeformatter.cpp b/src/plugins/qmljseditor/qmljseditorcodeformatter.cpp
new file mode 100644
index 0000000000..175e384a59
--- /dev/null
+++ b/src/plugins/qmljseditor/qmljseditorcodeformatter.cpp
@@ -0,0 +1,358 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "qmljseditorcodeformatter.h"
+
+#include <QtCore/QDebug>
+
+using namespace QmlJS;
+using namespace QmlJSEditor;
+using namespace TextEditor;
+
+QtStyleCodeFormatter::QtStyleCodeFormatter()
+ : m_indentSize(4)
+{
+}
+
+void QtStyleCodeFormatter::setIndentSize(int size)
+{
+ m_indentSize = size;
+}
+
+void QtStyleCodeFormatter::saveBlockData(QTextBlock *block, const BlockData &data) const
+{
+ TextBlockUserData *userData = BaseTextDocumentLayout::userData(*block);
+ QmlJSCodeFormatterData *cppData = static_cast<QmlJSCodeFormatterData *>(userData->codeFormatterData());
+ if (!cppData) {
+ cppData = new QmlJSCodeFormatterData;
+ userData->setCodeFormatterData(cppData);
+ }
+ cppData->m_data = data;
+}
+
+bool QtStyleCodeFormatter::loadBlockData(const QTextBlock &block, BlockData *data) const
+{
+ TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(block);
+ if (!userData)
+ return false;
+ QmlJSCodeFormatterData *cppData = static_cast<QmlJSCodeFormatterData *>(userData->codeFormatterData());
+ if (!cppData)
+ return false;
+
+ *data = cppData->m_data;
+ return true;
+}
+
+void QtStyleCodeFormatter::saveLexerState(QTextBlock *block, int state) const
+{
+ BaseTextDocumentLayout::setLexerState(*block, state);
+}
+
+int QtStyleCodeFormatter::loadLexerState(const QTextBlock &block) const
+{
+ return BaseTextDocumentLayout::lexerState(block);
+}
+
+void QtStyleCodeFormatter::onEnter(int newState, int *indentDepth, int *savedIndentDepth) const
+{
+ const State &parentState = state();
+ const Token &tk = currentToken();
+ const int tokenPosition = column(tk.begin());
+ const bool firstToken = (tokenIndex() == 0);
+ const bool lastToken = (tokenIndex() == tokenCount() - 1);
+
+ switch (newState) {
+ case objectdefinition_open: {
+ // special case for things like "gradient: Gradient {"
+ if (parentState.type == binding_assignment)
+ *savedIndentDepth = state(1).savedIndentDepth;
+
+ bool followedByData = (!lastToken && !tokenAt(tokenIndex() + 1).kind == Token::Comment);
+ if (firstToken || followedByData)
+ *savedIndentDepth = tokenPosition;
+
+ *indentDepth = *savedIndentDepth;
+
+ if (followedByData) {
+ *indentDepth = column(tokenAt(tokenIndex() + 1).begin());
+ } else {
+ *indentDepth += m_indentSize;
+ }
+ break;
+ }
+
+ case binding_or_objectdefinition:
+ if (firstToken)
+ *indentDepth = *savedIndentDepth = tokenPosition;
+ break;
+
+ case binding_assignment:
+ if (lastToken)
+ *indentDepth = *savedIndentDepth + 4;
+ else
+ *indentDepth = column(tokenAt(tokenIndex() + 1).begin());
+ break;
+
+ case expression_or_objectdefinition:
+ *indentDepth = tokenPosition;
+ break;
+
+ case expression:
+ // expression_or_objectdefinition has already consumed the first token
+ // ternary already adjusts indents nicely
+ if (parentState.type != expression_or_objectdefinition
+ && parentState.type != binding_assignment
+ && parentState.type != ternary_op) {
+ *indentDepth += 2 * m_indentSize;
+ }
+ if (!firstToken && parentState.type != expression_or_objectdefinition) {
+ *indentDepth = tokenPosition;
+ }
+ break;
+
+ case expression_maybe_continuation:
+ // set indent depth to indent we'd get if the expression ended here
+ for (int i = 1; state(i).type != topmost_intro; ++i) {
+ const int type = state(i).type;
+ if (isExpressionEndState(type) && !isBracelessState(type)) {
+ *indentDepth = state(i - 1).savedIndentDepth;
+ break;
+ }
+ }
+ break;
+
+ case bracket_open:
+ if (parentState.type == expression && state(1).type == binding_assignment) {
+ *savedIndentDepth = state(2).savedIndentDepth;
+ *indentDepth = *savedIndentDepth + m_indentSize;
+ } else if (!lastToken) {
+ *indentDepth = tokenPosition + 1;
+ } else {
+ *indentDepth = *savedIndentDepth + m_indentSize;
+ }
+ break;
+
+ case function_start:
+ if (parentState.type == expression) {
+ // undo the continuation indent of the expression
+ *indentDepth = parentState.savedIndentDepth;
+ *savedIndentDepth = *indentDepth;
+ }
+ break;
+
+ case do_statement_while_paren_open:
+ case statement_with_condition_paren_open:
+ case signal_arglist_open:
+ case function_arglist_open:
+ case paren_open:
+ case condition_paren_open:
+ if (!lastToken)
+ *indentDepth = tokenPosition + 1;
+ else
+ *indentDepth += m_indentSize;
+ break;
+
+ case ternary_op:
+ if (!lastToken)
+ *indentDepth = tokenPosition + tk.length + 1;
+ else
+ *indentDepth += m_indentSize;
+ break;
+
+ case jsblock_open:
+ // closing brace should be aligned to case
+ if (parentState.type == case_cont) {
+ *savedIndentDepth = parentState.savedIndentDepth;
+ break;
+ }
+ // fallthrough
+ case substatement_open:
+ // special case for foo: {
+ if (parentState.type == binding_assignment && state(1).type == binding_or_objectdefinition)
+ *savedIndentDepth = state(1).savedIndentDepth;
+ *indentDepth = *savedIndentDepth + m_indentSize;
+ break;
+
+ case statement_with_condition:
+ case statement_with_block:
+ case if_statement:
+ case do_statement:
+ case switch_statement:
+ if (firstToken || parentState.type == binding_assignment)
+ *savedIndentDepth = tokenPosition;
+ // ### continuation
+ *indentDepth = *savedIndentDepth; // + 2*m_indentSize;
+ break;
+
+ case maybe_else: {
+ // set indent to outermost braceless savedIndent
+ int outermostBraceless = 0;
+ while (isBracelessState(state(outermostBraceless + 1).type))
+ ++outermostBraceless;
+ *indentDepth = state(outermostBraceless).savedIndentDepth;
+ // this is where the else should go, if one appears - aligned to if_statement
+ *savedIndentDepth = state().savedIndentDepth;
+ break;
+ }
+
+ case condition_open:
+ // fixed extra indent when continuing 'if (', but not for 'else if ('
+ if (tokenPosition <= *indentDepth + m_indentSize)
+ *indentDepth += 2*m_indentSize;
+ else
+ *indentDepth = tokenPosition + 1;
+ break;
+
+ case case_start:
+ *savedIndentDepth = tokenPosition;
+ break;
+
+ case case_cont:
+ *indentDepth += m_indentSize;
+ break;
+
+ case multiline_comment_start:
+ *indentDepth = tokenPosition + 2;
+ break;
+
+ case multiline_comment_cont:
+ *indentDepth = tokenPosition;
+ break;
+ }
+}
+
+void QtStyleCodeFormatter::adjustIndent(const QList<Token> &tokens, int lexerState, int *indentDepth) const
+{
+ Q_UNUSED(lexerState)
+
+ State topState = state();
+ State previousState = state(1);
+
+ // adjusting the indentDepth here instead of in enter() gives 'else if' the correct indentation
+ // ### could be moved?
+ if (topState.type == substatement)
+ *indentDepth += m_indentSize;
+
+ // keep user-adjusted indent in multiline comments
+ if (topState.type == multiline_comment_start
+ || topState.type == multiline_comment_cont) {
+ if (!tokens.isEmpty()) {
+ *indentDepth = column(tokens.at(0).begin());
+ return;
+ }
+ }
+
+ const int kind = extendedTokenKind(tokenAt(0));
+ switch (kind) {
+ case LeftBrace:
+ if (topState.type == substatement
+ || topState.type == binding_assignment
+ || topState.type == case_cont) {
+ *indentDepth = topState.savedIndentDepth;
+ }
+ break;
+ case RightBrace: {
+ if (topState.type == jsblock_open && previousState.type == case_cont) {
+ *indentDepth = previousState.savedIndentDepth;
+ break;
+ }
+ for (int i = 0; state(i).type != topmost_intro; ++i) {
+ const int type = state(i).type;
+ if (type == objectdefinition_open
+ || type == jsblock_open
+ || type == substatement_open) {
+ *indentDepth = state(i).savedIndentDepth;
+ break;
+ }
+ }
+ break;
+ }
+ case RightBracket:
+ for (int i = 0; state(i).type != topmost_intro; ++i) {
+ const int type = state(i).type;
+ if (type == bracket_open) {
+ *indentDepth = state(i).savedIndentDepth;
+ break;
+ }
+ }
+ break;
+
+ case LeftBracket:
+ case LeftParenthesis:
+ case Delimiter:
+ if (topState.type == expression_maybe_continuation)
+ *indentDepth = topState.savedIndentDepth;
+ break;
+
+ case Else:
+ if (topState.type == maybe_else) {
+ *indentDepth = topState.savedIndentDepth;
+ } else if (topState.type == expression_maybe_continuation) {
+ bool hasElse = false;
+ for (int i = 1; state(i).type != topmost_intro; ++i) {
+ const int type = state(i).type;
+ if (type == else_clause)
+ hasElse = true;
+ if (type == if_statement) {
+ if (hasElse) {
+ hasElse = false;
+ } else {
+ *indentDepth = state(i).savedIndentDepth;
+ break;
+ }
+ }
+ }
+ }
+ break;
+
+ case Colon:
+ if (topState.type == ternary_op) {
+ *indentDepth -= 2;
+ }
+ break;
+
+ case Question:
+ if (topState.type == expression_maybe_continuation)
+ *indentDepth = topState.savedIndentDepth;
+ break;
+
+ case Default:
+ case Case:
+ for (int i = 0; state(i).type != topmost_intro; ++i) {
+ const int type = state(i).type;
+ if (type == switch_statement || type == case_cont) {
+ *indentDepth = state(i).savedIndentDepth;
+ break;
+ } else if (type == topmost_intro) {
+ break;
+ }
+ }
+ break;
+ }
+}
diff --git a/src/plugins/qmljseditor/qmljseditorcodeformatter.h b/src/plugins/qmljseditor/qmljseditorcodeformatter.h
new file mode 100644
index 0000000000..a9aed6d05d
--- /dev/null
+++ b/src/plugins/qmljseditor/qmljseditorcodeformatter.h
@@ -0,0 +1,69 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef QMLJSEDITORCODEFORMATTER_H
+#define QMLJSEDITORCODEFORMATTER_H
+
+#include "qmljseditor_global.h"
+
+#include <texteditor/basetextdocumentlayout.h>
+#include <qmljs/qmljscodeformatter.h>
+
+namespace QmlJSEditor {
+
+class QMLJSEDITOR_EXPORT QtStyleCodeFormatter : public QmlJS::CodeFormatter
+{
+public:
+ QtStyleCodeFormatter();
+
+ void setIndentSize(int size);
+
+protected:
+ virtual void onEnter(int newState, int *indentDepth, int *savedIndentDepth) const;
+ virtual void adjustIndent(const QList<QmlJS::Token> &tokens, int lexerState, int *indentDepth) const;
+
+ virtual void saveBlockData(QTextBlock *block, const BlockData &data) const;
+ virtual bool loadBlockData(const QTextBlock &block, BlockData *data) const;
+
+ virtual void saveLexerState(QTextBlock *block, int state) const;
+ virtual int loadLexerState(const QTextBlock &block) const;
+
+private:
+ int m_indentSize;
+
+ class QmlJSCodeFormatterData: public TextEditor::CodeFormatterData
+ {
+ public:
+ QmlJS::CodeFormatter::BlockData m_data;
+ };
+};
+
+} // namespace QmlJSEditor
+
+#endif // QMLJSEDITORCODEFORMATTER_H
diff --git a/tests/auto/qml/qmleditor/codeformatter/codeformatter.pro b/tests/auto/qml/qmleditor/codeformatter/codeformatter.pro
new file mode 100644
index 0000000000..ee95456908
--- /dev/null
+++ b/tests/auto/qml/qmleditor/codeformatter/codeformatter.pro
@@ -0,0 +1,24 @@
+TEMPLATE = app
+CONFIG += qt warn_on console depend_includepath
+QT += testlib network
+
+SRCDIR = ../../../../../src
+
+#DEFINES += QML_BUILD_STATIC_LIB
+#include($$SRCDIR/../qtcreator.pri)
+include($$SRCDIR/libs/qmljs/qmljs-lib.pri)
+include($$SRCDIR/libs/utils/utils-lib.pri)
+#LIBS += -L$$IDE_LIBRARY_PATH
+
+SOURCES += \
+ tst_codeformatter.cpp \
+ $$SRCDIR/plugins/qmljseditor/qmljseditorcodeformatter.cpp \
+ $$SRCDIR/plugins/texteditor/basetextdocumentlayout.cpp
+
+HEADERS += \
+ $$SRCDIR/plugins/qmljseditor/qmljseditorcodeformatter.h \
+ $$SRCDIR/plugins/texteditor/basetextdocumentlayout.h \
+
+INCLUDEPATH += $$SRCDIR/plugins $$SRCDIR/libs
+
+TARGET=tst_$$TARGET
diff --git a/tests/auto/qml/qmleditor/codeformatter/tst_codeformatter.cpp b/tests/auto/qml/qmleditor/codeformatter/tst_codeformatter.cpp
new file mode 100644
index 0000000000..336e642eaf
--- /dev/null
+++ b/tests/auto/qml/qmleditor/codeformatter/tst_codeformatter.cpp
@@ -0,0 +1,888 @@
+#include <QtTest>
+#include <QObject>
+#include <QList>
+#include <QTextDocument>
+#include <QTextBlock>
+
+#include <qmljseditor/qmljseditorcodeformatter.h>
+
+using namespace QmlJSEditor;
+
+class tst_CodeFormatter: public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void objectDefinitions1();
+ void objectDefinitions2();
+ void expressionEndSimple();
+ void expressionEnd();
+ void expressionEndBracket();
+ void expressionEndParen();
+ void objectBinding();
+ void arrayBinding();
+ void functionDeclaration();
+ void functionExpression();
+ void propertyDeclarations();
+ void signalDeclarations();
+ void ifBinding1();
+ void ifBinding2();
+ void ifStatementWithoutBraces1();
+ void ifStatementWithoutBraces2();
+ void ifStatementWithBraces1();
+ void ifStatementWithBraces2();
+ void ifStatementMixed();
+ void ifStatementAndComments();
+ void ifStatementLongCondition();
+ void moreIfThenElse();
+ void strayElse();
+ void oneLineIf();
+ void forStatement();
+ void whileStatement();
+ void tryStatement();
+ void doWhile();
+ void cStyleComments();
+ void cppStyleComments();
+ void qmlKeywords();
+ void ternary();
+ void switch1();
+// void gnuStyle();
+// void whitesmithsStyle();
+ void expressionContinuation();
+};
+
+struct Line {
+ Line(QString l)
+ : line(l)
+ {
+ for (int i = 0; i < l.size(); ++i) {
+ if (!l.at(i).isSpace()) {
+ expectedIndent = i;
+ return;
+ }
+ }
+ expectedIndent = l.size();
+ }
+
+ Line(QString l, int expect)
+ : line(l), expectedIndent(expect)
+ {}
+
+ QString line;
+ int expectedIndent;
+};
+
+QString concatLines(QList<Line> lines)
+{
+ QString result;
+ foreach (const Line &l, lines) {
+ result += l.line;
+ result += "\n";
+ }
+ return result;
+}
+
+void checkIndent(QList<Line> data, int style = 0)
+{
+ Q_UNUSED(style)
+
+ QString text = concatLines(data);
+ QTextDocument document(text);
+ QtStyleCodeFormatter formatter;
+
+ int i = 0;
+ foreach (const Line &l, data) {
+ QTextBlock b = document.findBlockByLineNumber(i);
+ if (l.expectedIndent != -1) {
+ int actualIndent = formatter.indentFor(b);
+ if (actualIndent != l.expectedIndent) {
+ QFAIL(QString("Wrong indent in line %1 with text '%2', expected indent %3, got %4").arg(
+ QString::number(i+1), l.line, QString::number(l.expectedIndent), QString::number(actualIndent)).toLatin1().constData());
+ }
+ }
+ formatter.updateLineStateChange(b);
+ ++i;
+ }
+}
+
+void tst_CodeFormatter::objectDefinitions1()
+{
+ QList<Line> data;
+ data << Line("import Qt 4.7")
+ << Line("")
+ << Line("Rectangle {")
+ << Line(" foo: bar;")
+ << Line(" Item {")
+ << Line(" x: 42;")
+ << Line(" y: x;")
+ << Line(" }")
+ << Line(" Component.onCompleted: foo;")
+ << Line(" ")
+ << Line(" Foo.Bar {")
+ << Line(" width: 12 + 54;")
+ << Line(" anchors.fill: parent;")
+ << Line(" }")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::objectDefinitions2()
+{
+ QList<Line> data;
+ data << Line("import Qt 4.7")
+ << Line("")
+ << Line("Rectangle {")
+ << Line(" foo: bar;")
+ << Line(" Image { source: \"a+b+c\"; x: 42; y: 12 }")
+ << Line(" Component.onCompleted: foo;")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::expressionEndSimple()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar +")
+ << Line(" foo(4, 5) +")
+ << Line(" 7")
+ << Line(" x: 42")
+ << Line(" y: 43")
+ << Line(" width: 10")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::expressionEnd()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar +")
+ << Line(" foo(4")
+ << Line(" + 5)")
+ << Line(" + 7")
+ << Line(" x: 42")
+ << Line(" + 43")
+ << Line(" + 10")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::expressionEndParen()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar")
+ << Line(" (foo(4")
+ << Line(" + 5)")
+ << Line(" + 7,")
+ << Line(" abc)")
+ << Line(" x: a + b(fpp, ba + 12) + foo(")
+ << Line(" bar,")
+ << Line(" 10)")
+ << Line(" + 10")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::expressionEndBracket()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar")
+ << Line(" [foo[4")
+ << Line(" + 5]")
+ << Line(" + 7,")
+ << Line(" abc]")
+ << Line(" x: a + b[fpp, ba + 12] + foo[")
+ << Line(" bar,")
+ << Line(" 10]")
+ << Line(" + 10")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::objectBinding()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar")
+ << Line(" x: 3")
+ << Line(" foo: Gradient {")
+ << Line(" x: 12")
+ << Line(" y: x")
+ << Line(" }")
+ << Line(" Item {")
+ << Line(" states: State {}")
+ << Line(" }")
+ << Line(" x: 1")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::arrayBinding()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar")
+ << Line(" x: 3")
+ << Line(" foo: [")
+ << Line(" State {")
+ << Line(" y: x")
+ << Line(" },")
+ << Line(" State")
+ << Line(" {")
+ << Line(" }")
+ << Line(" ]")
+ << Line(" foo: [")
+ << Line(" 1 +")
+ << Line(" 2")
+ << Line(" + 345 * foo(")
+ << Line(" bar, car,")
+ << Line(" dar),")
+ << Line(" x, y,")
+ << Line(" z,")
+ << Line(" ]")
+ << Line(" x: 1")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+
+
+void tst_CodeFormatter::moreIfThenElse()
+{
+ QList<Line> data;
+ data << Line("Image {")
+ << Line(" source: {")
+ << Line(" if(type == 1) {")
+ << Line(" \"pics/blueStone.png\";")
+ << Line(" } else if (type == 2) {")
+ << Line(" \"pics/head.png\";")
+ << Line(" } else {")
+ << Line(" \"pics/redStone.png\";")
+ << Line(" }")
+ << Line(" }")
+ << Line("}");
+ checkIndent(data);
+}
+
+
+void tst_CodeFormatter::functionDeclaration()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar")
+ << Line(" function foo(a, b, c) {")
+ << Line(" if (a)")
+ << Line(" b;")
+ << Line(" }")
+ << Line(" property alias boo :")
+ << Line(" foo")
+ << Line(" Item {")
+ << Line(" property variant g : Gradient {")
+ << Line(" v: 12")
+ << Line(" }")
+ << Line(" function bar(")
+ << Line(" a, b,")
+ << Line(" c)")
+ << Line(" {")
+ << Line(" var b")
+ << Line(" }")
+ << Line(" function bar(a,")
+ << Line(" a, b,")
+ << Line(" c)")
+ << Line(" {")
+ << Line(" var b")
+ << Line(" }")
+ << Line(" }")
+ << Line(" x: 1")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::functionExpression()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line("onFoo: {", 4)
+ << Line(" function foo(a, b, c) {")
+ << Line(" if (a)")
+ << Line(" b;")
+ << Line(" }")
+ << Line(" return function(a, b) { return a + b; }")
+ << Line(" return function foo(a, b) {")
+ << Line(" return a")
+ << Line(" }")
+ << Line("}")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::propertyDeclarations()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar")
+ << Line(" property int foo : 2 +")
+ << Line(" x")
+ << Line(" property list<Foo> bar")
+ << Line(" property alias boo :")
+ << Line(" foo")
+ << Line(" Item {")
+ << Line(" property variant g : Gradient {")
+ << Line(" v: 12")
+ << Line(" }")
+ << Line(" default property Item g")
+ << Line(" default property Item g : parent.foo")
+ << Line(" }")
+ << Line(" x: 1")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::signalDeclarations()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" foo: bar")
+ << Line(" signal foo")
+ << Line(" x: bar")
+ << Line(" signal bar(a, int b)")
+ << Line(" signal bar2()")
+ << Line(" Item {")
+ << Line(" signal property")
+ << Line(" signal import(a, b);")
+ << Line(" }")
+ << Line(" x: 1")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifBinding1()
+{
+ QList<Line> data;
+ data << Line("A.Rectangle {")
+ << Line(" foo: bar")
+ << Line(" x: if (a) b")
+ << Line(" x: if (a)")
+ << Line(" b")
+ << Line(" x: if (a) b;")
+ << Line(" x: if (a)")
+ << Line(" b;")
+ << Line(" x: if (a) b; else c")
+ << Line(" x: if (a) b")
+ << Line(" else c")
+ << Line(" x: if (a) b;")
+ << Line(" else c")
+ << Line(" x: if (a) b;")
+ << Line(" else")
+ << Line(" c")
+ << Line(" x: if (a)")
+ << Line(" b")
+ << Line(" else")
+ << Line(" c")
+ << Line(" x: if (a) b; else c;")
+ << Line(" x: 1")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifBinding2()
+{
+ QList<Line> data;
+ data << Line("A.Rectangle {")
+ << Line(" foo: bar")
+ << Line(" x: if (a) b +")
+ << Line(" 5 +")
+ << Line(" 5 * foo(")
+ << Line(" 1, 2)")
+ << Line(" else a =")
+ << Line(" foo(15,")
+ << Line(" bar(")
+ << Line(" 1),")
+ << Line(" bar)")
+ << Line(" x: if (a) b")
+ << Line(" + 5")
+ << Line(" + 5")
+ << Line(" x: if (a)")
+ << Line(" b")
+ << Line(" + 5")
+ << Line(" + 5")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifStatementWithoutBraces1()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" x: if (a)")
+ << Line(" if (b)")
+ << Line(" foo")
+ << Line(" else if (c)")
+ << Line(" foo")
+ << Line(" else")
+ << Line(" if (d)")
+ << Line(" foo;")
+ << Line(" else")
+ << Line(" a + b + ")
+ << Line(" c")
+ << Line(" else")
+ << Line(" foo;")
+ << Line(" y: 2")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifStatementWithoutBraces2()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" x: {")
+ << Line(" if (a)")
+ << Line(" if (b)")
+ << Line(" foo;")
+ << Line(" if (a) b();")
+ << Line(" if (a) b(); else")
+ << Line(" foo;")
+ << Line(" if (a)")
+ << Line(" if (b)")
+ << Line(" foo;")
+ << Line(" else if (c)")
+ << Line(" foo;")
+ << Line(" else")
+ << Line(" if (d)")
+ << Line(" foo;")
+ << Line(" else")
+ << Line(" e")
+ << Line(" else")
+ << Line(" foo;")
+ << Line(" }")
+ << Line(" foo: bar")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifStatementWithBraces1()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line("onClicked: {", 4)
+ << Line(" if (a) {")
+ << Line(" if (b) {")
+ << Line(" foo;")
+ << Line(" } else if (c) {")
+ << Line(" foo")
+ << Line(" } else {")
+ << Line(" if (d) {")
+ << Line(" foo")
+ << Line(" } else {")
+ << Line(" foo;")
+ << Line(" }")
+ << Line(" }")
+ << Line(" } else {")
+ << Line(" foo;")
+ << Line(" }")
+ << Line("}")
+ << Line("}");
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifStatementWithBraces2()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line("onClicked:", 4)
+ << Line(" if (a)")
+ << Line(" {")
+ << Line(" if (b)")
+ << Line(" {")
+ << Line(" foo")
+ << Line(" }")
+ << Line(" else if (c)")
+ << Line(" {")
+ << Line(" foo;")
+ << Line(" }")
+ << Line(" else")
+ << Line(" {")
+ << Line(" if (d)")
+ << Line(" {")
+ << Line(" foo;")
+ << Line(" }")
+ << Line(" else")
+ << Line(" {")
+ << Line(" foo")
+ << Line(" }")
+ << Line(" }")
+ << Line(" }")
+ << Line(" else")
+ << Line(" {")
+ << Line(" foo")
+ << Line(" }")
+ << Line("}");
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifStatementMixed()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line("onClicked:", 4)
+ << Line(" if (foo)")
+ << Line(" if (bar)")
+ << Line(" {")
+ << Line(" foo;")
+ << Line(" }")
+ << Line(" else")
+ << Line(" if (car)")
+ << Line(" {}")
+ << Line(" else doo")
+ << Line(" else abc")
+ << Line("}");
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifStatementAndComments()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line("onClicked: {", 4)
+ << Line(" if (foo)")
+ << Line(" ; // bla")
+ << Line(" else if (bar)")
+ << Line(" ;")
+ << Line(" if (foo)")
+ << Line(" ; /*bla")
+ << Line(" bla */")
+ << Line(" else if (bar)")
+ << Line(" // foobar")
+ << Line(" ;")
+ << Line(" else if (bar)")
+ << Line(" /* bla")
+ << Line(" bla */")
+ << Line(" ;")
+ << Line("}")
+ << Line("}");
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ifStatementLongCondition()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line("onClicked: {", 4)
+ << Line(" if (foo &&")
+ << Line(" bar")
+ << Line(" || (a + b > 4")
+ << Line(" && foo(bar)")
+ << Line(" )")
+ << Line(" ) {")
+ << Line(" foo;")
+ << Line(" }")
+ << Line("}");
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::strayElse()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line("onClicked: {", 4)
+ << Line(" while( true ) {}")
+ << Line(" else", -1)
+ << Line(" else {", -1)
+ << Line(" }", -1)
+ << Line("}");
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::oneLineIf()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" onClicked: { if (showIt) show(); }")
+ << Line(" x: 2")
+ << Line("};")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::forStatement()
+{
+ QList<Line> data;
+ data << Line("for (var i = 0; i < 20; ++i) {")
+ << Line(" print(i);")
+ << Line("}")
+ << Line("for (var x in [a, b, c, d])")
+ << Line(" x += 5")
+ << Line("var z")
+ << Line("for (var x in [a, b, c, d])")
+ << Line(" for (;;)")
+ << Line(" {")
+ << Line(" for (a(); b(); c())")
+ << Line(" for (a();")
+ << Line(" b(); c())")
+ << Line(" for (a(); b(); c())")
+ << Line(" print(3*d)")
+ << Line(" }")
+ << Line("z = 2")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::whileStatement()
+{
+ QList<Line> data;
+ data << Line("while (i < 20) {")
+ << Line(" print(i);")
+ << Line("}")
+ << Line("while (x in [a, b, c, d])")
+ << Line(" x += 5")
+ << Line("var z")
+ << Line("while (a + b > 0")
+ << Line(" && b + c > 0)")
+ << Line(" for (;;)")
+ << Line(" {")
+ << Line(" for (a(); b(); c())")
+ << Line(" while (a())")
+ << Line(" for (a(); b(); c())")
+ << Line(" print(3*d)")
+ << Line(" }")
+ << Line("z = 2")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::tryStatement()
+{
+ QList<Line> data;
+ data << Line("try {")
+ << Line(" print(i);")
+ << Line("} catch (foo) {")
+ << Line(" print(foo)")
+ << Line("} finally {")
+ << Line(" var z")
+ << Line(" while (a + b > 0")
+ << Line(" && b + c > 0)")
+ << Line(" ;")
+ << Line("}")
+ << Line("z = 2")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::doWhile()
+{
+ QList<Line> data;
+ data << Line("function foo() {")
+ << Line(" do { if (c) foo; } while(a);")
+ << Line(" do {")
+ << Line(" if(a);")
+ << Line(" } while(a);")
+ << Line(" do")
+ << Line(" foo;")
+ << Line(" while(a);")
+ << Line(" do foo; while(a);")
+ << Line("};")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::cStyleComments()
+{
+ QList<Line> data;
+ data << Line("/*")
+ << Line(" ")
+ << Line(" foo")
+ << Line(" ")
+ << Line(" foo")
+ << Line(" ")
+ << Line("*/")
+ << Line("Rectangle {")
+ << Line(" /*")
+ << Line(" ")
+ << Line(" foo")
+ << Line(" ")
+ << Line(" */")
+ << Line(" /* bar */")
+ << Line("}")
+ << Line("Item {")
+ << Line(" /* foo */")
+ << Line(" /*")
+ << Line(" ")
+ << Line(" foo")
+ << Line(" ")
+ << Line(" */")
+ << Line(" /* bar */")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::cppStyleComments()
+{
+ QList<Line> data;
+ data << Line("// abc")
+ << Line("Item { ")
+ << Line(" // ghij")
+ << Line(" // def")
+ << Line(" // ghij")
+ << Line(" x: 4 // hik")
+ << Line(" // doo")
+ << Line("} // ba")
+ << Line("// ba")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::ternary()
+{
+ QList<Line> data;
+ data << Line("function foo() {")
+ << Line(" var i = a ? b : c;")
+ << Line(" foo += a_bigger_condition ?")
+ << Line(" b")
+ << Line(" : c;")
+ << Line(" foo += a_bigger_condition")
+ << Line(" ? b")
+ << Line(" : c;")
+ << Line(" var i = a ?")
+ << Line(" b : c;")
+ << Line(" var i = aooo ? b")
+ << Line(" : c +")
+ << Line(" 2;")
+ << Line(" var i = (a ? b : c) + (foo")
+ << Line(" bar);")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::switch1()
+{
+ QList<Line> data;
+ data << Line("function foo() {")
+ << Line(" switch (a) {")
+ << Line(" case 1:")
+ << Line(" foo;")
+ << Line(" if (a);")
+ << Line(" case 2:")
+ << Line(" case 3: {")
+ << Line(" foo")
+ << Line(" }")
+ << Line(" case 4:")
+ << Line(" {")
+ << Line(" foo;")
+ << Line(" }")
+ << Line(" case bar:")
+ << Line(" break")
+ << Line(" }")
+ << Line(" case 4:")
+ << Line(" {")
+ << Line(" if (a) {")
+ << Line(" }")
+ << Line(" }")
+ << Line("}")
+ ;
+ checkIndent(data);
+}
+
+//void tst_CodeFormatter::gnuStyle()
+//{
+// QList<Line> data;
+// data << Line("struct S")
+// << Line("{")
+// << Line(" void foo()")
+// << Line(" {")
+// << Line(" if (a)")
+// << Line(" {")
+// << Line(" fpp;")
+// << Line(" }")
+// << Line(" else if (b)")
+// << Line(" {")
+// << Line(" fpp;")
+// << Line(" }")
+// << Line(" else")
+// << Line(" {")
+// << Line(" }")
+// << Line(" if (b) {")
+// << Line(" fpp;")
+// << Line(" }")
+// << Line(" }")
+// << Line("};")
+// ;
+// checkIndent(data, 1);
+//}
+
+//void tst_CodeFormatter::whitesmithsStyle()
+//{
+// QList<Line> data;
+// data << Line("struct S")
+// << Line(" {")
+// << Line(" void foo()")
+// << Line(" {")
+// << Line(" if (a)")
+// << Line(" {")
+// << Line(" fpp;")
+// << Line(" }")
+// << Line(" if (b) {")
+// << Line(" fpp;")
+// << Line(" }")
+// << Line(" }")
+// << Line(" };")
+// ;
+// checkIndent(data, 2);
+//}
+
+void tst_CodeFormatter::qmlKeywords()
+{
+ QList<Line> data;
+ data << Line("Rectangle {")
+ << Line(" on: 2")
+ << Line(" property: 2")
+ << Line(" signal: 2")
+ << Line(" list: 2")
+ << Line(" as: 2")
+ << Line(" import: 2")
+ << Line(" Item {")
+ << Line(" }")
+ << Line(" x: 2")
+ << Line("};")
+ ;
+ checkIndent(data);
+}
+
+void tst_CodeFormatter::expressionContinuation()
+{
+ QList<Line> data;
+ data << Line("var x = 1 ? 2")
+ << Line(" + 3 : 4")
+ << Line("++x")
+ << Line("++y--")
+ << Line("x +=")
+ << Line(" y++")
+ << Line("var z")
+ ;
+ checkIndent(data);
+}
+
+QTEST_APPLESS_MAIN(tst_CodeFormatter)
+#include "tst_codeformatter.moc"
+
+
diff --git a/tests/auto/qml/qmleditor/qmleditor.pro b/tests/auto/qml/qmleditor/qmleditor.pro
index 089574dbe7..7c5fddb9a7 100644
--- a/tests/auto/qml/qmleditor/qmleditor.pro
+++ b/tests/auto/qml/qmleditor/qmleditor.pro
@@ -1,3 +1,3 @@
TEMPLATE = subdirs
-SUBDIRS += lookup
+SUBDIRS += lookup codeformatter