Commit a5b26a32 authored by Christian Kamm's avatar Christian Kamm

QmlJS: Add 'reformat' action which regenerates the whole file.

Change-Id: I0aed6c6e197e122200d720eb9291a083095a6299
Reviewed-by: default avatarRoberto Raggi <roberto.raggi@nokia.com>
parent fa7c7c4e
......@@ -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
}
This diff is collapsed.
/**************************************************************************
**
** 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
......@@ -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)
......
......@@ -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);
......
......@@ -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";
......
......@@ -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)
......@@ -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
......
......@@ -3,4 +3,5 @@ TEMPLATE = subdirs
SUBDIRS += qmldesigner \
qmleditor \
qmlprojectmanager \
codemodel
codemodel \
reformatter
var x
var y = 12
function foo(a, b) {
x = 15
x += 4
}
var foo =
function (a, b) {}
while (true) {
for (var a = 1; a < 5; ++a) {
switch (a) {
case 1:
++a
break
case 2:
a += 2
foo()
break
default:
break
case 3:
continue
}
}
for (var x in a) {
print(a[x])
}
do {
a = x
x *= a
} while (a < x)
try {
Math.sqrt(a)
} catch (e) {
Math.sqrt(a)
} finally {
Math.sqrt(a)
}
try {
Math.sqrt(a)
} finally {
Math.sqrt(a)
}
try {
Math.sqrt(a)
} catch (e) {
Math.sqrt(a)
}
}
// imports
import QtQuick 2.0
import Qt.labs.particles 1.0 as Part
import "/usr" as Foo
import "." 1.0
Text {
// properties
property int foo
property alias bar: x
property list<QtObject> pro
default property int def
// script binding
x: x + y
// object bindings
Rectangle on font.family {
x: x
}
anchors.bottom: AnchorAnimation {
running: true
}
// array binding
states: [
State {
name: x
},
State {
name: y
}
]
// nested with qualified id
Part.ParticleMotion {
id: foo
}
// functions
function foo(a, b) {}
function foo(a, b) {
x = a + 12 * b
}
}
include(../../qttest.pri)
DEFINES+=QTCREATORDIR=\\\"$$IDE_SOURCE_TREE\\\"
DEFINES+=TESTSRCDIR=\\\"$$PWD\\\"
include($$IDE_SOURCE_TREE/src/libs/utils/utils.pri)
include($$IDE_SOURCE_TREE/src/libs/qmljs/qmljs.pri)
TARGET = tst_reformatter
SOURCES += \
tst_reformatter.cpp
/**************************************************************************
**
** 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 <QScopedPointer>
#include <QLatin1String>
#include <QGraphicsObject>
#include <QApplication>
#include <QSettings>
#include <QFileInfo>
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsreformatter.h>
#include <qmljs/parser/qmljsast_p.h>
#include <QtTest>
#include <algorithm>
using namespace QmlJS;
using namespace QmlJS::AST;
class tst_Reformatter : public QObject
{
Q_OBJECT
public:
tst_Reformatter();
private slots:
void test();
void test_data();
};
tst_Reformatter::tst_Reformatter()
{
}
#define QCOMPARE_NOEXIT(actual, expected) \
QTest::qCompare(actual, expected, #actual, #expected, __FILE__, __LINE__)
void tst_Reformatter::test_data()
{
QTest::addColumn<QString>("path");
QDirIterator it(TESTSRCDIR, QStringList() << QLatin1String("*.qml") << QLatin1String("*.js"), QDir::Files);
while (it.hasNext()) {
const QString fileName = it.next();
QTest::newRow(fileName.toLatin1()) << it.filePath();
}
}
void tst_Reformatter::test()
{
QFETCH(QString, path);
Document::Ptr doc = Document::create(path, Document::guessLanguageFromSuffix(path));
QFile file(doc->fileName());
file.open(QFile::ReadOnly | QFile::Text);
QString source = QString::fromUtf8(file.readAll());
doc->setSource(source);
file.close();
doc->parse();
QVERIFY(!doc->source().isEmpty());
QVERIFY(doc->diagnosticMessages().isEmpty());
QString rewritten = reformat(doc);
QStringList sourceLines = source.split(QLatin1Char('\n'));
QStringList newLines = rewritten.split(QLatin1Char('\n'));
// compare line by line
int commonLines = qMin(newLines.size(), sourceLines.size());
for (int i = 0; i < commonLines; ++i) {
// names intentional to make 'Actual (sourceLine): ...\nExpected (newLinee): ...' line up
const QString &sourceLine = sourceLines.at(i);
const QString &newLinee = newLines.at(i);
if (sourceLine.trimmed().isEmpty() && newLinee.trimmed().isEmpty())
continue;
bool fail = !QCOMPARE_NOEXIT(sourceLine, newLinee);
if (fail) {
qDebug() << "in line" << (i + 1);
return;
}
}
QCOMPARE(sourceLines.size(), newLines.size());
}
QTEST_MAIN(tst_Reformatter);
#include "tst_reformatter.moc"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment