diff options
author | Christian Kamm <christian.d.kamm@nokia.com> | 2010-07-07 11:45:18 +0200 |
---|---|---|
committer | Christian Kamm <christian.d.kamm@nokia.com> | 2010-08-10 14:27:08 +0200 |
commit | 822de6c17ab52002ae59a94c3e231bf0d5e3e438 (patch) | |
tree | a683fcd312b3f5840cd326ae2106f377fcd36505 | |
parent | f6232260c2c5ba93e85d725e05c2d2c9b86e293c (diff) | |
download | qt-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.pri | 9 | ||||
-rw-r--r-- | src/libs/qmljs/qmljscodeformatter.cpp | 910 | ||||
-rw-r--r-- | src/libs/qmljs/qmljscodeformatter.h | 309 | ||||
-rw-r--r-- | src/plugins/qmljseditor/qmljseditor.cpp | 24 | ||||
-rw-r--r-- | src/plugins/qmljseditor/qmljseditor.pro | 6 | ||||
-rw-r--r-- | src/plugins/qmljseditor/qmljseditorcodeformatter.cpp | 358 | ||||
-rw-r--r-- | src/plugins/qmljseditor/qmljseditorcodeformatter.h | 69 | ||||
-rw-r--r-- | tests/auto/qml/qmleditor/codeformatter/codeformatter.pro | 24 | ||||
-rw-r--r-- | tests/auto/qml/qmleditor/codeformatter/tst_codeformatter.cpp | 888 | ||||
-rw-r--r-- | tests/auto/qml/qmleditor/qmleditor.pro | 2 |
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 ¤tToken() 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 |