summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Kamm <christian.d.kamm@nokia.com>2011-11-07 15:14:43 +0100
committerChristian Kamm <christian.d.kamm@nokia.com>2011-11-15 11:31:40 +0100
commita5b26a32b3ba5d63fcd0c62fcca6ac676f1482f0 (patch)
treee8c2fcf2a6cc65234fe8185ad8252ef0cc372675 /src
parentfa7c7c4ecaa829ab4ab6d6f2b5aec454d3b4b180 (diff)
downloadqt-creator-a5b26a32b3ba5d63fcd0c62fcca6ac676f1482f0.tar.gz
QmlJS: Add 'reformat' action which regenerates the whole file.
Change-Id: I0aed6c6e197e122200d720eb9291a083095a6299 Reviewed-by: Roberto Raggi <roberto.raggi@nokia.com>
Diffstat (limited to 'src')
-rw-r--r--src/libs/qmljs/qmljs-lib.pri6
-rw-r--r--src/libs/qmljs/qmljsreformatter.cpp1265
-rw-r--r--src/libs/qmljs/qmljsreformatter.h44
-rw-r--r--src/plugins/qmljseditor/qmljseditor.cpp2
-rw-r--r--src/plugins/qmljseditor/qmljseditor.h1
-rw-r--r--src/plugins/qmljseditor/qmljseditorconstants.h1
-rw-r--r--src/plugins/qmljseditor/qmljseditorplugin.cpp53
-rw-r--r--src/plugins/qmljseditor/qmljseditorplugin.h13
8 files changed, 1369 insertions, 16 deletions
diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri
index e83410e6f0..1fa8e57b1d 100644
--- a/src/libs/qmljs/qmljs-lib.pri
+++ b/src/libs/qmljs/qmljs-lib.pri
@@ -67,9 +67,11 @@ OTHER_FILES += \
contains(QT, gui) {
SOURCES += \
$$PWD/qmljsindenter.cpp \
- $$PWD/qmljscodeformatter.cpp
+ $$PWD/qmljscodeformatter.cpp \
+ $$PWD/qmljsreformatter.cpp
HEADERS += \
$$PWD/qmljsindenter.h \
- $$PWD/qmljscodeformatter.h
+ $$PWD/qmljscodeformatter.h \
+ $$PWD/qmljsreformatter.h
}
diff --git a/src/libs/qmljs/qmljsreformatter.cpp b/src/libs/qmljs/qmljsreformatter.cpp
new file mode 100644
index 0000000000..884922c698
--- /dev/null
+++ b/src/libs/qmljs/qmljsreformatter.cpp
@@ -0,0 +1,1265 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at info@qt.nokia.com.
+**
+**************************************************************************/
+
+#include "qmljsreformatter.h"
+
+#include "qmljscodeformatter.h"
+#include "qmljsscanner.h"
+#include "parser/qmljsast_p.h"
+#include "parser/qmljsastvisitor_p.h"
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QString>
+#include <QtCore/QFile>
+#include <QtCore/QTextStream>
+#include <QtGui/QTextBlock>
+#include <QtGui/QTextDocument>
+#include <QtGui/QTextCursor>
+
+#include <limits>
+
+using namespace QmlJS;
+using namespace QmlJS::AST;
+
+namespace {
+
+class SimpleFormatter : public QtStyleCodeFormatter
+{
+protected:
+ class FormatterData : public QTextBlockUserData
+ {
+ public:
+ FormatterData(const BlockData &data) : data(data) {}
+ BlockData data;
+ };
+
+ virtual void saveBlockData(QTextBlock *block, const BlockData &data) const
+ {
+ block->setUserData(new FormatterData(data));
+ }
+
+ virtual bool loadBlockData(const QTextBlock &block, BlockData *data) const
+ {
+ if (!block.userData())
+ return false;
+
+ *data = static_cast<FormatterData *>(block.userData())->data;
+ return true;
+ }
+
+ virtual void saveLexerState(QTextBlock *block, int state) const
+ {
+ block->setUserState(state);
+ }
+
+ virtual int loadLexerState(const QTextBlock &block) const
+ {
+ return block.userState();
+ }
+};
+
+class Rewriter : protected Visitor
+{
+ Document::Ptr _doc;
+ QString _result;
+ QString _line;
+ class Split {
+ public:
+ int offset;
+ qreal badness;
+ };
+ QList<Split> _possibleSplits;
+ QTextDocument _resultDocument;
+ SimpleFormatter _formatter;
+ int _indent;
+ int _nextComment;
+ int _lastNewlineOffset;
+ bool _hadEmptyLine;
+ int _binaryExpDepth;
+
+public:
+ Rewriter(Document::Ptr doc)
+ : _doc(doc)
+ {
+ }
+
+ QString operator()(Node *node)
+ {
+ Q_ASSERT(node == _doc->ast()); // comment handling fails otherwise
+
+ _result.reserve(_doc->source().size());
+ _line.clear();
+ _possibleSplits.clear();
+ _indent = 0;
+ _nextComment = 0;
+ _lastNewlineOffset = -1;
+ _hadEmptyLine = false;
+ _binaryExpDepth = 0;
+
+ accept(node);
+
+ // emit the final comments
+ const QList<SourceLocation> &comments = _doc->engine()->comments();
+ for (; _nextComment < comments.size(); ++_nextComment) {
+ outComment(comments.at(_nextComment));
+ }
+
+ // ensure good ending
+ if (!_result.endsWith("\n\n") || !_line.isEmpty())
+ newLine();
+
+ return _result;
+ }
+
+protected:
+ void accept(Node *node)
+ {
+ Node::accept(node, this);
+ }
+
+ void acceptIndented(Node *node)
+ {
+ accept(node);
+ }
+
+ void lnAcceptIndented(Node *node)
+ {
+ newLine();
+ accept(node);
+ }
+
+ void out(const char *str, const SourceLocation &lastLoc = SourceLocation())
+ {
+ out(QString::fromLatin1(str), lastLoc);
+ }
+
+ void outComment(const SourceLocation &commentLoc)
+ {
+ SourceLocation fixedLoc = commentLoc;
+ fixCommentLocation(fixedLoc);
+ if (precededByEmptyLine(fixedLoc))
+ newLine();
+ out(toString(fixedLoc)); // don't use the sourceloc overload here
+ if (followedByNewLine(fixedLoc)) {
+ newLine();
+ } else {
+ out(" ");
+ }
+ }
+
+ void out(const QString &str, const SourceLocation &lastLoc = SourceLocation())
+ {
+ if (lastLoc.isValid()) {
+ QList<SourceLocation> comments = _doc->engine()->comments();
+ for (; _nextComment < comments.size(); ++_nextComment) {
+ SourceLocation commentLoc = comments.at(_nextComment);
+ if (commentLoc.end() > lastLoc.end())
+ break;
+
+ outComment(commentLoc);
+ }
+ }
+
+ QStringList lines = str.split(QLatin1Char('\n'));
+ for (int i = 0; i < lines.size(); ++i) {
+ _line += lines.at(i);
+ if (i != lines.size() - 1)
+ newLine();
+ }
+ _hadEmptyLine = false;
+ }
+
+ QString toString(const SourceLocation &loc)
+ {
+ return _doc->source().mid(loc.offset, loc.length);
+ }
+
+ void out(const SourceLocation &loc)
+ {
+ if (!loc.isValid())
+ return;
+ out(toString(loc), loc);
+ }
+
+ int tryIndent(const QString &line)
+ {
+ // append the line to the text document
+ QTextCursor cursor(&_resultDocument);
+ cursor.movePosition(QTextCursor::End);
+ int cursorStartLinePos = cursor.position();
+ cursor.insertText(line);
+
+ // get the expected indentation
+ QTextBlock last = _resultDocument.lastBlock();
+ _formatter.updateStateUntil(last);
+ int indent = _formatter.indentFor(last);
+
+ // remove the line again
+ cursor.setPosition(cursorStartLinePos);
+ cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
+ cursor.removeSelectedText();
+
+ return indent;
+ }
+
+ void finishLine()
+ {
+ // remove trailing spaces
+ int len = _line.size();
+ while (len > 0 && _line.at(len - 1).isSpace())
+ --len;
+ _line.resize(len);
+
+ _line += QLatin1Char('\n');
+
+ _result += _line;
+ QTextCursor cursor(&_resultDocument);
+ cursor.movePosition(QTextCursor::End);
+ cursor.insertText(_line);
+
+ _line = QString(_indent, QLatin1Char(' '));
+ }
+
+ class BestSplit {
+ public:
+ QStringList lines;
+ qreal badnessFromSplits;
+
+ qreal badness()
+ {
+ const int maxLineLength = 80;
+ const int strongMaxLineLength = 100;
+ const int minContentLength = 10;
+
+ qreal result = badnessFromSplits;
+ foreach (const QString &line, lines) {
+ // really long lines should be avoided at all cost
+ if (line.size() > strongMaxLineLength) {
+ result += 50 + (line.size() - strongMaxLineLength);
+ }
+ // having long lines is bad
+ else if (line.size() > maxLineLength) {
+ result += 3 + (line.size() - maxLineLength);
+ }
+ // and even ok-sized lines should have a cost
+ else {
+ result += 1;
+ }
+
+ // having lines with little content is bad
+ const int contentSize = line.trimmed().size();
+ if (contentSize < minContentLength)
+ result += 0.5;
+ }
+ return result;
+ }
+ };
+
+ void adjustIndent(QString *line, QList<Split> *splits, int indent)
+ {
+ int startSpaces = 0;
+ while (startSpaces < line->size() && line->at(startSpaces).isSpace())
+ ++startSpaces;
+
+ line->replace(0, startSpaces, QString(indent, ' '));
+ for (int i = 0; i < splits->size(); ++i) {
+ (*splits)[i].offset = splits->at(i).offset - startSpaces + indent;
+ }
+ }
+
+ BestSplit computeBestSplits(QStringList context, QString line, QList<Split> possibleSplits)
+ {
+ BestSplit result;
+
+ result.badnessFromSplits = 0;
+ result.lines = QStringList(line);
+
+ //qDebug() << "trying to split" << line << possibleSplits << context;
+
+ // try to split at every possible position
+ for (int i = possibleSplits.size() - 1; i >= 0; --i) {
+ const int splitPos = possibleSplits.at(i).offset;
+ const QString newContextLine = line.left(splitPos);
+ QStringList newContext = context;
+ newContext += newContextLine;
+ const QString restLine = line.mid(splitPos);
+ if (restLine.trimmed().isEmpty())
+ continue;
+
+ // the extra space is to avoid // comments sticking to the 0 offset
+ QString indentLine = newContext.join("\n") + QLatin1String("\n ") + restLine;
+ int indent = tryIndent(indentLine);
+
+ QList<Split> newSplits = possibleSplits.mid(i + 1);
+ QString newSplitLine = restLine;
+ adjustIndent(&newSplitLine, &newSplits, indent);
+
+ for (int j = 0; j < newSplits.size(); ++j)
+ newSplits[j].offset = newSplits.at(j).offset - splitPos;
+
+ BestSplit nested = computeBestSplits(newContext, newSplitLine, newSplits);
+
+ nested.lines.prepend(newContextLine);
+ nested.badnessFromSplits += possibleSplits.at(i).badness;
+ if (nested.badness() < result.badness())
+ result = nested;
+ }
+
+ return result;
+ }
+
+ void newLine()
+ {
+ // if preceded by a newline, it's an empty line!
+ _hadEmptyLine = _line.trimmed().isEmpty();
+
+ // if the preceding line wasn't empty, reindent etc.
+ if (!_hadEmptyLine) {
+ int indentStart = 0;
+ while (indentStart < _line.size() && _line.at(indentStart).isSpace())
+ ++indentStart;
+
+ _indent = tryIndent(_line);
+ adjustIndent(&_line, &_possibleSplits, _indent);
+
+ // maybe make multi-line?
+ BestSplit split = computeBestSplits(QStringList(), _line, _possibleSplits);
+ if (!split.lines.isEmpty() && split.lines.size() > 1) {
+ for (int i = 0; i < split.lines.size(); ++i) {
+ _line = split.lines.at(i);
+ if (i != split.lines.size() - 1)
+ finishLine();
+ }
+ }
+ }
+
+ finishLine();
+ _possibleSplits.clear();
+ }
+
+ void requireEmptyLine()
+ {
+ while (!_hadEmptyLine)
+ newLine();
+ }
+
+ bool acceptBlockOrIndented(Node *ast, bool finishWithSpaceOrNewline = false)
+ {
+ if (cast<Block *>(ast)) {
+ out(" ");
+ accept(ast);
+ if (finishWithSpaceOrNewline)
+ out(" ");
+ return true;
+ } else {
+ lnAcceptIndented(ast);
+ if (finishWithSpaceOrNewline)
+ newLine();
+ return false;
+ }
+ }
+
+ bool followedByNewLine(const SourceLocation &loc)
+ {
+ const QString &source = _doc->source();
+ int i = loc.end();
+ for (; i < source.size() && source.at(i).isSpace(); ++i) {
+ if (source.at(i) == QLatin1Char('\n'))
+ return true;
+ }
+ return false;
+ }
+
+ bool precededByEmptyLine(const SourceLocation &loc)
+ {
+ const QString &source = _doc->source();
+ int i = loc.offset;
+
+ // expect spaces and \n, twice
+ bool first = true;
+ for (--i;
+ i >= 0 && source.at(i).isSpace();
+ --i) {
+
+ if (source.at(i) == QLatin1Char('\n')) {
+ if (first)
+ first = false;
+ else
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool firstOnLine()
+ {
+ foreach (const QChar &c, _line) {
+ if (!c.isSpace())
+ return false;
+ }
+ return true;
+ }
+
+ void addPossibleSplit(qreal badness, int offset = 0)
+ {
+ Split s;
+ s.badness = badness;
+ s.offset = _line.size() + offset;
+ _possibleSplits += s;
+ }
+
+ void fixCommentLocation(SourceLocation &loc)
+ {
+ loc.offset -= 2;
+ loc.startColumn -= 2;
+ loc.length += 2;
+ if (_doc->source().mid(loc.offset, 2) == QLatin1String("/*"))
+ loc.length += 2;
+ }
+
+ virtual bool preVisit(Node *ast)
+ {
+ SourceLocation firstLoc;
+ if (ExpressionNode *expr = ast->expressionCast()) {
+ firstLoc = expr->firstSourceLocation();
+ } else if (Statement *stmt = ast->statementCast()) {
+ firstLoc = stmt->firstSourceLocation();
+ } else if (UiObjectMember *mem = ast->uiObjectMemberCast()) {
+ firstLoc = mem->firstSourceLocation();
+ } else if (UiImport *import = cast<UiImport *>(ast)) {
+ firstLoc = import->firstSourceLocation();
+ }
+
+ if (firstLoc.isValid() && int(firstLoc.offset) != _lastNewlineOffset) {
+ _lastNewlineOffset = firstLoc.offset;
+
+ if (precededByEmptyLine(firstLoc) && !_result.endsWith(QLatin1String("\n\n"))) {
+ newLine();
+ }
+ }
+
+ return true;
+ }
+
+ virtual void postVisit(Node *ast)
+ {
+ SourceLocation lastLoc;
+ if (ExpressionNode *expr = ast->expressionCast()) {
+ lastLoc = expr->lastSourceLocation();
+ } else if (Statement *stmt = ast->statementCast()) {
+ lastLoc = stmt->lastSourceLocation();
+ } else if (UiObjectMember *mem = ast->uiObjectMemberCast()) {
+ lastLoc = mem->lastSourceLocation();
+ } else if (UiImport *import = cast<UiImport *>(ast)) {
+ lastLoc = import->lastSourceLocation();
+ }
+
+ if (lastLoc.isValid()) {
+ const QList<SourceLocation> &comments = _doc->engine()->comments();
+
+ // preserve trailing comments
+ for (; _nextComment < comments.size(); ++_nextComment) {
+ SourceLocation nextCommentLoc = comments.at(_nextComment);
+ if (nextCommentLoc.startLine != lastLoc.startLine)
+ break;
+ fixCommentLocation(nextCommentLoc);
+
+ // there must only be whitespace between lastLoc and the comment
+ bool commentFollows = true;
+ for (quint32 i = lastLoc.end(); i < nextCommentLoc.begin(); ++i) {
+ if (!_doc->source().at(i).isSpace()) {
+ commentFollows = false;
+ break;
+ }
+ }
+ if (!commentFollows)
+ break;
+
+ out(" ");
+ out(toString(nextCommentLoc));
+ }
+ }
+ }
+
+ virtual bool visit(UiImport *ast)
+ {
+ out("import ", ast->importToken);
+ if (!ast->fileName.isNull()) {
+ out(QString("\"%1\"").arg(ast->fileName.toString()));
+ } else {
+ accept(ast->importUri);
+ }
+ if (ast->versionToken.isValid()) {
+ out(" ");
+ out(ast->versionToken);
+ }
+ if (!ast->importId.isNull()) {
+ out(" as ", ast->asToken);
+ out(ast->importIdToken);
+ }
+ return false;
+ }
+
+ virtual bool visit(UiObjectDefinition *ast)
+ {
+ accept(ast->qualifiedTypeNameId);
+ out(" ");
+ accept(ast->initializer);
+ return false;
+ }
+
+ virtual bool visit(UiObjectInitializer *ast)
+ {
+ out(ast->lbraceToken);
+ if (ast->members)
+ lnAcceptIndented(ast->members);
+ newLine();
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ virtual bool visit(UiPublicMember *ast)
+ {
+ if (ast->type == UiPublicMember::Property) {
+ if (ast->isDefaultMember)
+ out("default ", ast->defaultToken);
+ out("property ", ast->propertyToken);
+ if (!ast->typeModifier.isNull()) {
+ out(ast->typeModifierToken);
+ out("<");
+ out(ast->typeToken);
+ out(">");
+ } else {
+ out(ast->typeToken);
+ }
+ out(" ");
+ out(ast->identifierToken);
+ if (ast->statement) {
+ out(": ", ast->colonToken);
+ accept(ast->statement);
+ } else if (ast->binding) {
+ out(": ", ast->colonToken);
+ accept(ast->binding);
+ }
+ } else { // signal
+ out("signal ");
+ out(ast->identifierToken);
+ accept(ast->parameters);
+ }
+ return false;
+ }
+
+ virtual bool visit(UiObjectBinding *ast)
+ {
+ if (ast->hasOnToken) {
+ accept(ast->qualifiedTypeNameId);
+ out(" on ");
+ accept(ast->qualifiedId);
+ } else {
+ accept(ast->qualifiedId);
+ out(": ", ast->colonToken);
+ accept(ast->qualifiedTypeNameId);
+ }
+ out(" ");
+ accept(ast->initializer);
+ return false;
+ }
+
+ virtual bool visit(UiScriptBinding *ast)
+ {
+ accept(ast->qualifiedId);
+ out(": ", ast->colonToken);
+ accept(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(UiArrayBinding *ast)
+ {
+ accept(ast->qualifiedId);
+ out(ast->colonToken);
+ out(" ");
+ out(ast->lbracketToken);
+ lnAcceptIndented(ast->members);
+ newLine();
+ out(ast->rbracketToken);
+ return false;
+ }
+
+ virtual bool visit(ThisExpression *ast) { out(ast->thisToken); return true; }
+ virtual bool visit(NullExpression *ast) { out(ast->nullToken); return true; }
+ virtual bool visit(TrueLiteral *ast) { out(ast->trueToken); return true; }
+ virtual bool visit(FalseLiteral *ast) { out(ast->falseToken); return true; }
+ virtual bool visit(IdentifierExpression *ast) { out(ast->identifierToken); return true; }
+ virtual bool visit(StringLiteral *ast) { out(ast->literalToken); return true; }
+ virtual bool visit(NumericLiteral *ast) { out(ast->literalToken); return true; }
+ virtual bool visit(RegExpLiteral *ast) { out(ast->literalToken); return true; }
+
+ virtual bool visit(ArrayLiteral *ast)
+ {
+ out(ast->lbracketToken);
+ if (ast->elements)
+ accept(ast->elements);
+ if (ast->elements && ast->elision)
+ out(", ", ast->commaToken);
+ if (ast->elision)
+ accept(ast->elision);
+ out(ast->rbracketToken);
+ return false;
+ }
+
+ virtual bool visit(ObjectLiteral *ast)
+ {
+ out(ast->lbraceToken);
+ lnAcceptIndented(ast->properties);
+ newLine();
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ virtual bool visit(ElementList *ast)
+ {
+ for (ElementList *it = ast; it; it = it->next) {
+ if (it->elision)
+ accept(it->elision);
+ if (it->elision && it->expression)
+ out(", ");
+ if (it->expression)
+ accept(it->expression);
+ if (it->next)
+ out(", ", ast->commaToken);
+ }
+ return false;
+ }
+
+ virtual bool visit(PropertyNameAndValueList *ast)
+ {
+ for (PropertyNameAndValueList *it = ast; it; it = it->next) {
+ accept(it->name);
+ out(": ", ast->colonToken);
+ accept(it->value);
+ if (it->next) {
+ out(ast->commaToken);
+ newLine();
+ }
+ }
+ return false;
+ }
+
+ virtual bool visit(NestedExpression *ast)
+ {
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ virtual bool visit(IdentifierPropertyName *ast) { out(ast->id.toString()); return true; }
+ virtual bool visit(StringLiteralPropertyName *ast) { out(ast->id.toString()); return true; }
+ virtual bool visit(NumericLiteralPropertyName *ast) { out(QString::number(ast->id)); return true; }
+
+ virtual bool visit(ArrayMemberExpression *ast)
+ {
+ accept(ast->base);
+ out(ast->lbracketToken);
+ accept(ast->expression);
+ out(ast->rbracketToken);
+ return false;
+ }
+
+ virtual bool visit(FieldMemberExpression *ast)
+ {
+ accept(ast->base);
+ out(ast->dotToken);
+ out(ast->identifierToken);
+ return false;
+ }
+
+ virtual bool visit(NewMemberExpression *ast)
+ {
+ out("new ", ast->newToken);
+ accept(ast->base);
+ out(ast->lparenToken);
+ accept(ast->arguments);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ virtual bool visit(NewExpression *ast)
+ {
+ out("new ", ast->newToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(CallExpression *ast)
+ {
+ accept(ast->base);
+ out(ast->lparenToken);
+ addPossibleSplit(0);
+ accept(ast->arguments);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ virtual bool visit(PostIncrementExpression *ast)
+ {
+ accept(ast->base);
+ out(ast->incrementToken);
+ return false;
+ }
+
+ virtual bool visit(PostDecrementExpression *ast)
+ {
+ accept(ast->base);
+ out(ast->decrementToken);
+ return false;
+ }
+
+ virtual bool visit(PreIncrementExpression *ast)
+ {
+ out(ast->incrementToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(PreDecrementExpression *ast)
+ {
+ out(ast->decrementToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(DeleteExpression *ast)
+ {
+ out("delete ", ast->deleteToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(VoidExpression *ast)
+ {
+ out("void ", ast->voidToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(TypeOfExpression *ast)
+ {
+ out("typeof ", ast->typeofToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(UnaryPlusExpression *ast)
+ {
+ out(ast->plusToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(UnaryMinusExpression *ast)
+ {
+ out(ast->minusToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(TildeExpression *ast)
+ {
+ out(ast->tildeToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(NotExpression *ast)
+ {
+ out(ast->notToken);
+ accept(ast->expression);
+ return false;
+ }
+
+ virtual bool visit(BinaryExpression *ast)
+ {
+ ++_binaryExpDepth;
+ accept(ast->left);
+
+ // in general, avoid splitting at the operator
+ // but && and || are ok
+ qreal splitBadness = 30;
+ if (ast->op == QSOperator::And
+ || ast->op == QSOperator::Or)
+ splitBadness = 0;
+ addPossibleSplit(splitBadness);
+
+ out(" ");
+ out(ast->operatorToken);
+ out(" ");
+ accept(ast->right);
+ --_binaryExpDepth;
+ return false;
+ }
+
+ virtual bool visit(ConditionalExpression *ast)
+ {
+ accept(ast->expression);
+ out(" ? ", ast->questionToken);
+ accept(ast->ok);
+ out(" : ", ast->colonToken);
+ accept(ast->ko);
+ return false;
+ }
+
+ virtual bool visit(Block *ast)
+ {
+ out(ast->lbraceToken);
+ lnAcceptIndented(ast->statements);
+ newLine();
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ virtual bool visit(VariableStatement *ast)
+ {
+ out("var ", ast->declarationKindToken);
+ accept(ast->declarations);
+ return false;
+ }
+
+ virtual bool visit(VariableDeclaration *ast)
+ {
+ out(ast->identifierToken);
+ if (ast->expression) {
+ out(" = ");
+ accept(ast->expression);
+ }
+ return false;
+ }
+
+ virtual bool visit(EmptyStatement *ast)
+ {
+ out(ast->semicolonToken);
+ return false;
+ }
+
+ virtual bool visit(IfStatement *ast)
+ {
+ out(ast->ifToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->ok, ast->ko);
+ if (ast->ko) {
+ out(ast->elseToken);
+ if (cast<Block *>(ast->ko) || cast<IfStatement *>(ast->ko)) {
+ out(" ");
+ accept(ast->ko);
+ } else {
+ lnAcceptIndented(ast->ko);
+ }
+ }
+ return false;
+ }
+
+ virtual bool visit(DoWhileStatement *ast)
+ {
+ out(ast->doToken);
+ acceptBlockOrIndented(ast->statement, true);
+ out(ast->whileToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ return false;
+ }
+
+ virtual bool visit(WhileStatement *ast)
+ {
+ out(ast->whileToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(ForStatement *ast)
+ {
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->initialiser);
+ out("; ", ast->firstSemicolonToken);
+ accept(ast->condition);
+ out("; ", ast->secondSemicolonToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(LocalForStatement *ast)
+ {
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ out(ast->varToken);
+ out(" ");
+ accept(ast->declarations);
+ out("; ", ast->firstSemicolonToken);
+ accept(ast->condition);
+ out("; ", ast->secondSemicolonToken);
+ accept(ast->expression);
+ out(")", ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(ForEachStatement *ast)
+ {
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->initialiser);
+ out(" in ", ast->inToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(LocalForEachStatement *ast)
+ {
+ out(ast->forToken);
+ out(" ");
+ out(ast->lparenToken);
+ out(ast->varToken);
+ out(" ");
+ accept(ast->declaration);
+ out(" in ", ast->inToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(ContinueStatement *ast)
+ {
+ out(ast->continueToken);
+ if (!ast->label.isNull()) {
+ out(" ");
+ out(ast->identifierToken);
+ }
+ return false;
+ }
+
+ virtual bool visit(BreakStatement *ast)
+ {
+ out(ast->breakToken);
+ if (!ast->label.isNull()) {
+ out(" ");
+ out(ast->identifierToken);
+ }
+ return false;
+ }
+
+ virtual bool visit(ReturnStatement *ast)
+ {
+ out(ast->returnToken);
+ if (ast->expression) {
+ out(" ");
+ accept(ast->expression);
+ }
+ return false;
+ }
+
+ virtual bool visit(ThrowStatement *ast)
+ {
+ out(ast->throwToken);
+ if (ast->expression) {
+ out(" ");
+ accept(ast->expression);
+ }
+ return false;
+ }
+
+ virtual bool visit(WithStatement *ast)
+ {
+ out(ast->withToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ acceptBlockOrIndented(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(SwitchStatement *ast)
+ {
+ out(ast->switchToken);
+ out(" ");
+ out(ast->lparenToken);
+ accept(ast->expression);
+ out(ast->rparenToken);
+ out(" ");
+ accept(ast->block);
+ return false;
+ }
+
+ virtual bool visit(CaseBlock *ast)
+ {
+ out(ast->lbraceToken);
+ newLine();
+ accept(ast->clauses);
+ if (ast->clauses && ast->defaultClause)
+ newLine();
+ accept(ast->defaultClause);
+ if (ast->moreClauses)
+ newLine();
+ accept(ast->moreClauses);
+ newLine();
+ out(ast->rbraceToken);
+ return false;
+ }
+
+ virtual bool visit(CaseClause *ast)
+ {
+ out("case ", ast->caseToken);
+ accept(ast->expression);
+ out(ast->colonToken);
+ lnAcceptIndented(ast->statements);
+ return false;
+ }
+
+ virtual bool visit(DefaultClause *ast)
+ {
+ out(ast->defaultToken);
+ out(ast->colonToken);
+ lnAcceptIndented(ast->statements);
+ return false;
+ }
+
+ virtual bool visit(LabelledStatement *ast)
+ {
+ out(ast->identifierToken);
+ out(": ", ast->colonToken);
+ accept(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(TryStatement *ast)
+ {
+ out("try ", ast->tryToken);
+ accept(ast->statement);
+ if (ast->catchExpression) {
+ out(" ");
+ accept(ast->catchExpression);
+ }
+ if (ast->finallyExpression) {
+ out(" ");
+ accept(ast->finallyExpression);
+ }
+ return false;
+ }
+
+ virtual bool visit(Catch *ast)
+ {
+ out(ast->catchToken);
+ out(" ");
+ out(ast->lparenToken);
+ out(ast->identifierToken);
+ out(") ", ast->rparenToken);
+ accept(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(Finally *ast)
+ {
+ out("finally ", ast->finallyToken);
+ accept(ast->statement);
+ return false;
+ }
+
+ virtual bool visit(FunctionDeclaration *ast)
+ {
+ return visit(static_cast<FunctionExpression *>(ast));
+ }
+
+ virtual bool visit(FunctionExpression *ast)
+ {
+ if (!firstOnLine())
+ newLine();
+ out("function ", ast->functionToken);
+ if (!ast->name.isNull())
+ out(ast->identifierToken);
+ out(ast->lparenToken);
+ accept(ast->formals);
+ out(ast->rparenToken);
+ out(" ");
+ out(ast->lbraceToken);
+ if (ast->body) {
+ lnAcceptIndented(ast->body);
+ newLine();
+ }
+ out(ast->rbraceToken);
+ return false;
+ }
+
+
+ virtual bool visit(UiImportList *ast)
+ {
+ for (UiImportList *it = ast; it; it = it->next) {
+ accept(it->import);
+ newLine();
+ }
+ requireEmptyLine();
+ return false;
+ }
+
+ virtual bool visit(UiObjectMemberList *ast)
+ {
+ for (UiObjectMemberList *it = ast; it; it = it->next) {
+ accept(it->member);
+ if (it->next)
+ newLine();
+ }
+ return false;
+ }
+
+ virtual bool visit(UiArrayMemberList *ast)
+ {
+ for (UiArrayMemberList *it = ast; it; it = it->next) {
+ accept(it->member);
+ if (it->next) {
+ out(",", ast->commaToken); // ### comma token seems to be generally invalid
+ newLine();
+ }
+ }
+ return false;
+ }
+
+ virtual bool visit(UiQualifiedId *ast)
+ {
+ for (UiQualifiedId *it = ast; it; it = it->next) {
+ out(it->identifierToken);
+ if (it->next)
+ out(".");
+ }
+ return false;
+ }
+
+ virtual bool visit(Elision *ast)
+ {
+ for (Elision *it = ast; it; it = it->next) {
+ if (it->next)
+ out(", ", ast->commaToken);
+ }
+ return false;
+ }
+
+ virtual bool visit(ArgumentList *ast)
+ {
+ for (ArgumentList *it = ast; it; it = it->next) {
+ accept(it->expression);
+ if (it->next) {
+ out(", ", it->commaToken);
+ addPossibleSplit(-1);
+ }
+ }
+ return false;
+ }
+
+ virtual bool visit(StatementList *ast)
+ {
+ for (StatementList *it = ast; it; it = it->next) {
+ // ### work around parser bug: skip empty statements with wrong tokens
+ if (EmptyStatement *emptyStatement = cast<EmptyStatement *>(it->statement)) {
+ if (toString(emptyStatement->semicolonToken) != QLatin1String(";"))
+ continue;
+ }
+
+ accept(it->statement);
+ if (it->next)
+ newLine();
+ }
+ return false;
+ }
+
+ virtual bool visit(SourceElements *ast)
+ {
+ for (SourceElements *it = ast; it; it = it->next) {
+ accept(it->element);
+ if (it->next)
+ newLine();
+ }
+ return false;
+ }
+
+ virtual bool visit(VariableDeclarationList *ast)
+ {
+ for (VariableDeclarationList *it = ast; it; it = it->next) {
+ accept(it->declaration);
+ if (it->next)
+ out(", ", it->commaToken);
+ }
+ return false;
+ }
+
+ virtual bool visit(CaseClauses *ast)
+ {
+ for (CaseClauses *it = ast; it; it = it->next) {
+ accept(it->clause);
+ if (it->next)
+ newLine();
+ }
+ return false;
+ }
+
+ virtual bool visit(FormalParameterList *ast)
+ {
+ for (FormalParameterList *it = ast; it; it = it->next) {
+ if (it->commaToken.isValid())
+ out(", ", it->commaToken);
+ out(it->identifierToken);
+ }
+ return false;
+ }
+};
+
+} // anonymous namespace
+
+QString QmlJS::reformat(const Document::Ptr &doc)
+{
+ Rewriter rewriter(doc);
+ return rewriter(doc->ast());
+}
diff --git a/src/libs/qmljs/qmljsreformatter.h b/src/libs/qmljs/qmljsreformatter.h
new file mode 100644
index 0000000000..277f8d1477
--- /dev/null
+++ b/src/libs/qmljs/qmljsreformatter.h
@@ -0,0 +1,44 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (info@qt.nokia.com)
+**
+**
+** GNU Lesser General Public License Usage
+**
+** 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.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** Other Usage
+**
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at info@qt.nokia.com.
+**
+**************************************************************************/
+
+#ifndef QMLJSREFORMATTER_H
+#define QMLJSREFORMATTER_H
+
+#include "qmljs_global.h"
+
+#include "qmljsdocument.h"
+
+namespace QmlJS {
+QMLJS_EXPORT QString reformat(const Document::Ptr &doc);
+}
+
+#endif // QMLJSREFORMATTER_H
diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp
index 35338571ed..ba5f3653c2 100644
--- a/src/plugins/qmljseditor/qmljseditor.cpp
+++ b/src/plugins/qmljseditor/qmljseditor.cpp
@@ -1588,6 +1588,8 @@ void QmlJSTextEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo)
Core::EditorManager *editorManager = Core::EditorManager::instance();
if (editorManager->currentEditor() == editor())
m_semanticHighlighter->rerun(m_semanticInfo.scopeChain());
+
+ emit semanticInfoUpdated();
}
void QmlJSTextEditorWidget::onRefactorMarkerClicked(const TextEditor::RefactorMarker &marker)
diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h
index 3355fb1e4f..35f80f1138 100644
--- a/src/plugins/qmljseditor/qmljseditor.h
+++ b/src/plugins/qmljseditor/qmljseditor.h
@@ -182,6 +182,7 @@ public slots:
signals:
void outlineModelIndexChanged(const QModelIndex &index);
void selectedElementsChanged(QList<int> offsets, const QString &wordAtCursor);
+ void semanticInfoUpdated();
private slots:
void onDocumentUpdated(QmlJS::Document::Ptr doc);
diff --git a/src/plugins/qmljseditor/qmljseditorconstants.h b/src/plugins/qmljseditor/qmljseditorconstants.h
index 526bf13655..5a8c5f416f 100644
--- a/src/plugins/qmljseditor/qmljseditorconstants.h
+++ b/src/plugins/qmljseditor/qmljseditorconstants.h
@@ -55,6 +55,7 @@ const char FOLLOW_SYMBOL_UNDER_CURSOR[] = "QmlJSEditor.FollowSymbolUnderCursor";
const char FIND_USAGES[] = "QmlJSEditor.FindUsages";
const char RENAME_USAGES[] = "QmlJSEditor.RenameUsages";
const char RUN_SEMANTIC_SCAN[] = "QmlJSEditor.RunSemanticScan";
+const char REFORMAT_FILE[] = "QmlJSEditor.ReformatFile";
const char SHOW_QT_QUICK_HELPER[] = "QmlJSEditor.ShowQtQuickHelper";
const char TASK_CATEGORY_QML[] = "Task.Category.Qml";
diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp
index cd486a4286..a596098c2b 100644
--- a/src/plugins/qmljseditor/qmljseditorplugin.cpp
+++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp
@@ -49,6 +49,7 @@
#include <qmljs/qmljsicons.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
+#include <qmljs/qmljsreformatter.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <qmldesigner/qmldesignerconstants.h>
@@ -93,10 +94,11 @@ QmlJSEditorPlugin *QmlJSEditorPlugin::m_instance = 0;
QmlJSEditorPlugin::QmlJSEditorPlugin() :
m_modelManager(0),
- m_wizard(0),
m_editor(0),
m_actionHandler(0),
- m_quickFixAssistProvider(0)
+ m_quickFixAssistProvider(0),
+ m_reformatFileAction(0),
+ m_currentEditor(0)
{
m_instance = this;
}
@@ -205,6 +207,11 @@ bool QmlJSEditorPlugin::initialize(const QStringList & /*arguments*/, QString *e
connect(semanticScan, SIGNAL(triggered()), this, SLOT(runSemanticScan()));
qmlToolsMenu->addAction(cmd);
+ m_reformatFileAction = new QAction(tr("Reformat File"), this);
+ cmd = am->registerAction(m_reformatFileAction, Core::Id(Constants::REFORMAT_FILE), globalContext);
+ connect(m_reformatFileAction, SIGNAL(triggered()), this, SLOT(reformatFile()));
+ qmlToolsMenu->addAction(cmd);
+
QAction *showQuickToolbar = new QAction(tr("Show Qt Quick Toolbar"), this);
cmd = am->registerAction(showQuickToolbar, Constants::SHOW_QT_QUICK_HELPER, context);
#ifdef Q_WS_MACX
@@ -300,6 +307,20 @@ void QmlJSEditorPlugin::renameUsages()
editor->renameUsages();
}
+void QmlJSEditorPlugin::reformatFile()
+{
+ Core::EditorManager *em = Core::EditorManager::instance();
+ if (QmlJSTextEditorWidget *editor = qobject_cast<QmlJSTextEditorWidget*>(em->currentEditor()->widget())) {
+ QTC_ASSERT(!editor->isOutdated(), return);
+
+ const QString &newText = QmlJS::reformat(editor->semanticInfo().document);
+ QTextCursor tc(editor->textCursor());
+ tc.movePosition(QTextCursor::Start);
+ tc.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
+ tc.insertText(newText);
+ }
+}
+
void QmlJSEditorPlugin::showContextPane()
{
Core::EditorManager *em = Core::EditorManager::instance();
@@ -327,11 +348,23 @@ QmlJSQuickFixAssistProvider *QmlJSEditorPlugin::quickFixAssistProvider() const
void QmlJSEditorPlugin::currentEditorChanged(Core::IEditor *editor)
{
- if (! editor)
- return;
-
- else if (QmlJSTextEditorWidget *textEditor = qobject_cast<QmlJSTextEditorWidget *>(editor->widget())) {
- textEditor->forceReparse();
+ QmlJSTextEditorWidget *newTextEditor = 0;
+ if (editor)
+ newTextEditor = qobject_cast<QmlJSTextEditorWidget *>(editor->widget());
+
+ if (m_currentEditor) {
+ disconnect(m_currentEditor.data(), SIGNAL(contentsChanged()),
+ this, SLOT(checkCurrentEditorSemanticInfoUpToDate()));
+ disconnect(m_currentEditor.data(), SIGNAL(semanticInfoUpdated()),
+ this, SLOT(checkCurrentEditorSemanticInfoUpToDate()));
+ }
+ m_currentEditor = newTextEditor;
+ if (newTextEditor) {
+ connect(newTextEditor, SIGNAL(contentsChanged()),
+ this, SLOT(checkCurrentEditorSemanticInfoUpToDate()));
+ connect(newTextEditor, SIGNAL(semanticInfoUpdated()),
+ this, SLOT(checkCurrentEditorSemanticInfoUpToDate()));
+ newTextEditor->forceReparse();
}
}
@@ -343,4 +376,10 @@ void QmlJSEditorPlugin::runSemanticScan()
hub->popup(false);
}
+void QmlJSEditorPlugin::checkCurrentEditorSemanticInfoUpToDate()
+{
+ const bool semanticInfoUpToDate = m_currentEditor && !m_currentEditor->isOutdated();
+ m_reformatFileAction->setEnabled(semanticInfoUpToDate);
+}
+
Q_EXPORT_PLUGIN(QmlJSEditorPlugin)
diff --git a/src/plugins/qmljseditor/qmljseditorplugin.h b/src/plugins/qmljseditor/qmljseditorplugin.h
index 2d79b3804c..e07276fbb2 100644
--- a/src/plugins/qmljseditor/qmljseditorplugin.h
+++ b/src/plugins/qmljseditor/qmljseditorplugin.h
@@ -95,11 +95,13 @@ public Q_SLOTS:
void followSymbolUnderCursor();
void findUsages();
void renameUsages();
+ void reformatFile();
void showContextPane();
private Q_SLOTS:
void currentEditorChanged(Core::IEditor *editor);
void runSemanticScan();
+ void checkCurrentEditorSemanticInfoUpToDate();
private:
Core::Command *addToolAction(QAction *a, Core::ActionManager *am, Core::Context &context, const Core::Id &id,
@@ -107,18 +109,15 @@ private:
static QmlJSEditorPlugin *m_instance;
- QAction *m_actionPreview;
- QmlJSPreviewRunner *m_previewRunner;
-
QmlJS::ModelManagerInterface *m_modelManager;
- QmlFileWizard *m_wizard;
QmlJSEditorFactory *m_editor;
TextEditor::TextEditorActionHandler *m_actionHandler;
-
QmlJSQuickFixAssistProvider *m_quickFixAssistProvider;
-
- QPointer<TextEditor::ITextEditor> m_currentTextEditable;
QmlTaskManager *m_qmlTaskManager;
+
+ QAction *m_reformatFileAction;
+
+ QPointer<QmlJSTextEditorWidget> m_currentEditor;
};
} // namespace Internal