Commit 942326ae authored by Fawzi Mohamed's avatar Fawzi Mohamed

qmljs: add infrastructure handling qml dialects better

QmlBundles enables us to treat the different qml
dialects differently.

Add imports completion.

Change-log: [Qml/JS Support] Corrected handling of QtQuick2 only features.
Change-log: [Qml/JS Support] Added import completion in editor.
Task-number: QTCREATORBUG-8750
Task-number: QTCREATORBUG-8624
Task-number: QTCREATORBUG-8584
Task-number: QTCREATORBUG-8583
Task-number: QTCREATORBUG-8429

Change-Id: I1384b1b23136a85b4d077895ea86f92960da9e71
Reviewed-by: default avatarKai Koehne <kai.koehne@digia.com>
parent 6d3c271d
{
"name": "qbs",
"searchPaths": [
"$(QBS_IMPORT_PATH)"],
"installPaths": [
"$(QBS_IMPORT_PATH)"],
"implicitImports": [
"__javascriptQt5__"],
"supportedImports": [
"qbs.base 1.0",
"qbs 1.0",
"qbs.fileinfo 1.0",
"qbs.probe 1.0"]
}
{
"name": "qmlproject",
"searchPaths": [
""],
"installPaths": [
""],
"implicitImports": [
""],
"supportedImports": [
"QmlProject 1.0",
"QmlProject 1.1"]
}
{
"name":"qmltypes",
"searchPaths":[],
"installPaths":[],
"implicitImports":[],
"supportedImports":[
"QtQuick.tooling 1.1",
"QtQuick.tooling 1.0"]
}
{
"name": "QtQuick1",
"searchPaths": [
"$(QT_INSTALL_IMPORTS)"],
"installPaths": [
"$(QT_INSTALL_IMPORTS)"],
"implicitImports": [
"__javascriptQt4__"],
"supportedImports": [
"QtQuick 1.0",
"QtQuick 1.1",
"Qt.labs.gestures 1.0",
"Qt.labs.particles 1.0",
"Qt.labs.shaders 1.0",
"Qt.labs.folderlistmodel 1.0",
"QtWebKit 1.0"]
}
{
"name": "QtQuick1",
"searchPaths": [
"$(QT_INSTALL_IMPORTS)"],
"installPaths": [
"$(QT_INSTALL_IMPORTS)"],
"implicitImports": [
"__javascriptQt5__"],
"supportedImports": [
"QtQuick 1.1",
"Qt.labs.gestures 1.0",
"Qt.labs.particles 1.0",
"Qt.labs.shaders 1.0",
"Qt.labs.folderlistmodel 1.0",
"QtWebKit 1.0"]
}
{
"name": "QtQuick2",
"searchPaths": [
"$(QT_INSTALL_QML)"],
"installPaths": [
"$(QT_INSTALL_QML)"],
"implicitImports": [
"__javascriptQt5__"],
"supportedImports": [
"Qt.labs.folderlistmodel 2.0",
"QtAudioEngine 1.0",
"QtMultimedia 5.0",
"QtQuick.LocalStorage 2.0",
"QtQuick.Particles 2.0",
"QtQuick.Window 2.0",
"QtQuick.XmlListModel 2.0",
"QtQuick 2.0",
"QtTest 1.0",
"QtWebKit 3.0"]
}
......@@ -261,6 +261,54 @@ QStringList TrieNode::stringList(const TrieNode::Ptr &trie)
return a.res;
}
bool TrieNode::isSame(const TrieNode::Ptr &trie1, const TrieNode::Ptr &trie2)
{
if (trie1.data() == trie2.data())
return true;
if (trie1.isNull() || trie2.isNull())
return false; // assume never to generate non null empty tries
if (trie1->prefix != trie2->prefix)
return false; // assume no excess splitting
QList<TrieNode::Ptr> t1 = trie1->postfixes, t2 =trie2->postfixes;
int nEl = t1.size();
if (nEl != t2.size()) return false;
// different order = different trie
for (int i = 0; i < nEl; ++i) {
if (!isSame(t1.value(i), t2.value(i)))
return false;
}
return true;
}
namespace {
class ReplaceInTrie
{
public:
TrieNode::Ptr trie;
QHash<QString, QString> replacements;
ReplaceInTrie() { }
void operator()(QString s)
{
QHashIterator<QString, QString> i(replacements);
QString res = s;
while (i.hasNext()) {
i.next();
res.replace(i.key(), i.value());
}
trie = TrieNode::insertF(trie,res);
}
};
}
TrieNode::Ptr TrieNode::replaceF(const TrieNode::Ptr &trie, const QHash<QString, QString> &replacements)
{
// inefficient...
ReplaceInTrie rep;
rep.replacements = replacements;
enumerateTrieNode<ReplaceInTrie>(trie, rep, QString());
return rep.trie;
}
std::pair<TrieNode::Ptr,int> TrieNode::intersectF(
const TrieNode::Ptr &v1, const TrieNode::Ptr &v2, int index1)
{
......@@ -553,6 +601,26 @@ void Trie::merge(const Trie &v)
trie = TrieNode::mergeF(trie, v.trie).first;
}
void Trie::replace(const QHash<QString, QString> &replacements)
{
trie = TrieNode::replaceF(trie, replacements);
}
bool Trie::isEmpty() const
{
return trie.isNull(); // assuming to never generate an empty non null trie
}
bool Trie::operator==(const Trie &o)
{
return TrieNode::isSame(trie,o.trie);
}
bool Trie::operator!=(const Trie &o)
{
return !TrieNode::isSame(trie,o.trie);
}
Trie Trie::insertF(const QString &value) const
{
return Trie(TrieNode::insertF(trie, value));
......@@ -568,13 +636,19 @@ Trie Trie::mergeF(const Trie &v) const
return Trie(TrieNode::mergeF(trie, v.trie).first);
}
Trie Trie::replaceF(const QHash<QString, QString> &replacements) const
{
return Trie(TrieNode::replaceF(trie, replacements));
}
/*!
\fn int matchStrength(const QString &searchStr, const QString &str)
Returns a number defining how well the serachStr matches str.
Quite simplistic, looks only at the first match, and prefers contiguos
matches, or matches to ca capitalized or separated word.
matches, or matches to capitalized or separated words.
Match to the last char is also preferred.
*/
int matchStrength(const QString &searchStr, const QString &str)
{
......@@ -601,6 +675,8 @@ int matchStrength(const QString &searchStr, const QString &str)
}
if (i != iEnd)
return iEnd - i;
if (j == jEnd)
++res;
return res;
}
......
......@@ -32,6 +32,7 @@
#include <qmljs/qmljs_global.h>
#include <QHash>
#include <QList>
#include <QSharedPointer>
......@@ -67,7 +68,9 @@ public:
const QString &base = QString(), LookupFlags flags = LookupFlags(CaseInsensitive|Partial));
static bool contains(const Ptr &trie, const QString &value, LookupFlags flags = LookupFlags(0));
static QStringList stringList(const Ptr &trie);
static bool isSame(const Ptr &trie1, const Ptr &trie2);
static Ptr replaceF(const Ptr &trie, const QHash<QString, QString> &replacements);
static Ptr insertF(const Ptr &trie, const QString &value);
static std::pair<Ptr,int> intersectF(const Ptr &v1, const Ptr &v2, int index1=0);
static std::pair<Ptr,int> mergeF(const Ptr &v1, const Ptr &v2);
......@@ -91,10 +94,16 @@ public:
Trie insertF(const QString &value) const;
Trie intersectF(const Trie &v) const;
Trie mergeF(const Trie &v) const;
Trie replaceF(const QHash<QString, QString> &replacements) const;
void insert(const QString &value);
void intersect(const Trie &v);
void merge(const Trie &v);
void replace(const QHash<QString, QString> &replacements);
bool isEmpty() const;
bool operator==(const Trie &o);
bool operator!=(const Trie &o);
friend QMLJS_EXPORT QDebug &operator<<(QDebug &dbg, const TrieNode::Ptr &trie);
friend QMLJS_EXPORT QDebug &operator<<(QDebug &dbg, const Trie &trie);
......
......@@ -11,6 +11,7 @@ INCLUDEPATH += $$PWD/..
HEADERS += \
$$PWD/qmljs_global.h \
$$PWD/qmljsbind.h \
$$PWD/qmljsbundle.h \
$$PWD/qmljsevaluate.h \
$$PWD/qmljsdocument.h \
$$PWD/qmljsscanner.h \
......@@ -42,6 +43,7 @@ HEADERS += \
SOURCES += \
$$PWD/qmljsbind.cpp \
$$PWD/qmljsbundle.cpp \
$$PWD/qmljsevaluate.cpp \
$$PWD/qmljsdocument.cpp \
$$PWD/qmljsscanner.cpp \
......
......@@ -23,6 +23,8 @@ QtcLibrary {
"qmljs_global.h",
"qmljsbind.cpp",
"qmljsbind.h",
"qmljsbundle.cpp",
"qmljsbundle.h",
"qmljscheck.cpp",
"qmljscheck.h",
"qmljscodeformatter.cpp",
......
......@@ -31,6 +31,7 @@
#include "qmljsbind.h"
#include "qmljsutils.h"
#include "qmljsdocument.h"
#include "qmljsmodelmanagerinterface.h"
#include <languageutils/componentversion.h>
......@@ -204,15 +205,27 @@ bool Bind::visit(UiImport *ast)
_diagnosticMessages->append(
errorMessage(ast, tr("package import requires a version number")));
}
_imports += ImportInfo::moduleImport(toString(ast->importUri), version,
ast->importId.toString(), ast);
ImportInfo import = ImportInfo::moduleImport(toString(ast->importUri), version,
ast->importId.toString(), ast);
if (_doc->language() == Document::QmlLanguage) {
QString importStr = import.name() + ast->importId.toString();
QmlLanguageBundles langBundles = ModelManagerInterface::instance()->extendedBundles();
QmlBundle qq1 = langBundles.bundleForLanguage(Document::QmlQtQuick1Language);
QmlBundle qq2 = langBundles.bundleForLanguage(Document::QmlQtQuick2Language);
bool isQQ1 = qq1.supportedImports().contains(importStr);
bool isQQ2 = qq2.supportedImports().contains(importStr);
if (isQQ1 && ! isQQ2)
_doc->setLanguage(Document::QmlQtQuick1Language);
if (isQQ2 && ! isQQ1)
_doc->setLanguage(Document::QmlQtQuick2Language);
}
_imports += import;
} else if (!ast->fileName.isEmpty()) {
_imports += ImportInfo::pathImport(_doc->path(), ast->fileName.toString(),
version, ast->importId.toString(), ast);
} else {
_imports += ImportInfo::invalidImport(ast);
}
return false;
}
......
This diff is collapsed.
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** 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.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/
#ifndef QMLJSBUNDLE_H
#define QMLJSBUNDLE_H
#include <qmljs/qmljs_global.h>
#include <qmljs/persistenttrie.h>
#include <qmljs/qmljsdocument.h>
#include <QString>
#include <QCoreApplication>
#include <QHash>
QT_FORWARD_DECLARE_CLASS(QTextStream)
namespace Utils {
class JsonObjectValue;
}
namespace QmlJS {
/* !
\class QmlJS::QmlBundle
A Qmlbundle represents a set of qml libraries, with a list of their exports
Note that searchPaths, installPaths and implicitImports are PersistentTries
and not QStringLists.
This makes merging easier, and the order is not important for our use case.
*/
class QMLJS_EXPORT QmlBundle
{
Q_DECLARE_TR_FUNCTIONS(QmlJS::QmlBundle)
typedef PersistentTrie::Trie Trie;
public:
QmlBundle(const QmlBundle &o);
QmlBundle();
QmlBundle(const QString &name,
const Trie &searchPaths,
const Trie &installPaths,
const Trie &supportedImports,
const Trie &implicitImports);
QString name() const;
Trie installPaths() const;
Trie searchPaths() const;
Trie implicitImports() const;
Trie supportedImports() const;
void merge(const QmlBundle &o);
void intersect(const QmlBundle &o);
QmlBundle mergeF(const QmlBundle &o) const;
QmlBundle intersectF(const QmlBundle &o) const;
bool isEmpty() const;
void replaceVars(const QHash<QString, QString> &replacements);
QmlBundle replaceVarsF(const QHash<QString, QString> &replacements) const;
bool writeTo(const QString &path) const;
bool writeTo(QTextStream &stream, const QString &indent = QString()) const;
QString toString(const QString &indent = QString());
bool readFrom(QString path, QStringList *errors);
bool operator==(const QmlBundle &o) const;
bool operator!=(const QmlBundle &o) const;
private:
static void printEscaped(QTextStream &s, const QString &str);
static void writeTrie(QTextStream &stream, const Trie &t, const QString &indent);
QStringList maybeReadTrie(Trie &trie, Utils::JsonObjectValue *config, const QString &path,
const QString &propertyName, bool required = false);
QString m_name;
Trie m_searchPaths;
Trie m_installPaths;
Trie m_supportedImports;
Trie m_implicitImports;
};
class QMLJS_EXPORT QmlLanguageBundles
{
public:
QmlBundle bundleForLanguage(Document::Language l) const;
void mergeBundleForLanguage(Document::Language l, const QmlBundle &bundle);
QList<Document::Language> languages() const;
void mergeLanguageBundles(const QmlLanguageBundles &);
private:
QHash<Document::Language,QmlBundle> m_bundles;
};
} // namespace QmlJS
#endif // QMLJSBUNDLE_H
......@@ -31,6 +31,7 @@
#include <QDebug>
#include <QTextDocument>
#include <QStringList>
using namespace QmlJS;
......@@ -204,6 +205,8 @@ void CompletionContextFinder::checkImport()
//qDebug() << "Start line:" << *yyLine << m_startTokenIndex;
QStringList libVersionImport;
int isInLibVersionImport;
int i = m_startTokenIndex;
bool stop = false;
enum State {
......@@ -216,6 +219,7 @@ void CompletionContextFinder::checkImport()
ExpectAs = 1 << 5
};
State state = Unknown;
isInLibVersionImport = -1;
while (!stop) {
if (i < 0) {
......@@ -233,17 +237,27 @@ void CompletionContextFinder::checkImport()
case Token::Identifier: {
const QStringRef tokenString = yyLine->midRef(token.begin(), token.length);
if (tokenString == QLatin1String("as")) {
isInLibVersionImport = 0;
if (state == Unknown) {
state = State(ExpectAnyTarget | ExpectVersion);
break;
}
} else if (tokenString == QLatin1String("import")) {
if (state == Unknown || (state & ExpectImport))
if (state == Unknown || (state & ExpectImport)) {
if (isInLibVersionImport == -1 && token.end() < m_cursor.position())
isInLibVersionImport = 1;
m_inImport = true;
}
} else {
if (state == Unknown || (state & ExpectAnyTarget)
|| (state & ExpectTargetIdentifier)) {
state = State(ExpectImport | ExpectTargetDot);
libVersionImport.prepend(tokenString.toString());
if (isInLibVersionImport == -1) {
if (token.end() < m_cursor.position())
libVersionImport.append(QLatin1String(" "));
isInLibVersionImport = 1;
}
break;
}
}
......@@ -260,6 +274,10 @@ void CompletionContextFinder::checkImport()
case Token::Number:
if (state == Unknown || (state & ExpectVersion)) {
state = ExpectAnyTarget;
libVersionImport.prepend(yyLine->midRef(token.begin(), token.length).toString());
libVersionImport.prepend(QLatin1String(" "));
if (isInLibVersionImport == -1)
isInLibVersionImport = 1;
break;
}
stop = true;
......@@ -267,6 +285,9 @@ void CompletionContextFinder::checkImport()
case Token::Dot:
if (state == Unknown || (state & ExpectTargetDot)) {
state = ExpectTargetIdentifier;
libVersionImport.prepend(QLatin1String("."));
if (isInLibVersionImport == -1)
isInLibVersionImport = 1;
break;
}
stop = true;
......@@ -276,11 +297,19 @@ void CompletionContextFinder::checkImport()
stop = true;
break;
}
if (isInLibVersionImport == -1)
isInLibVersionImport = 0;
--i;
}
YY_RESTORE();
if (m_inImport && isInLibVersionImport == 1) {
m_libVersion = libVersionImport.join(QLatin1String(""));
if (m_libVersion.isNull())
m_libVersion = QLatin1String("");
} else {
m_libVersion = QString();
}
}
QStringList CompletionContextFinder::qmlObjectTypeName() const
......@@ -327,6 +356,11 @@ bool QmlJS::CompletionContextFinder::isInImport() const
return m_inImport;
}
QString CompletionContextFinder::libVersionImport() const
{
return m_libVersion;
}
int CompletionContextFinder::findOpeningBrace(int startTokenIndex)
{
YY_SAVE();
......
......@@ -54,6 +54,7 @@ public:
bool isInStringLiteral() const;
bool isInImport() const;
QString libVersionImport() const;
private:
int findOpeningBrace(int startTokenIndex);
......@@ -69,6 +70,7 @@ private:
bool m_behaviorBinding;
bool m_inStringLiteral;
bool m_inImport;
QString m_libVersion;
};
} // namespace QmlJS
......
......@@ -166,6 +166,11 @@ Document::Language Document::language() const
return _language;
}
void Document::setLanguage(Document::Language l)
{
_language = l;
}
AST::UiProgram *Document::qmlProgram() const
{
return cast<UiProgram *>(_ast);
......
......@@ -78,6 +78,7 @@ public:
bool isQmlDocument() const;
Language language() const;
void setLanguage(Language l);
AST::UiProgram *qmlProgram() const;
AST::Program *jsProgram() const;
......
......@@ -68,3 +68,4 @@ ModelManagerInterface *ModelManagerInterface::instance()
{
return g_instance;
}
......@@ -32,6 +32,7 @@
#include "qmljs_global.h"
#include "qmljsdocument.h"
#include "qmljsbundle.h"
#include <utils/environment.h>
......@@ -73,6 +74,8 @@ public:
bool isNull() const
{ return project.isNull(); }
QStringList completeImportPaths();
public: // attributes
QPointer<ProjectExplorer::Project> project;
QStringList sourceFiles;
......@@ -86,6 +89,8 @@ public:
QString qtImportsPath;
QString qtQmlPath;
QString qtVersionString;
QmlJS::QmlLanguageBundles activeBundle;
QmlJS::QmlLanguageBundles extendedBundle;
};
class WorkingCopy
......@@ -141,8 +146,11 @@ public:
virtual ProjectInfo projectInfo(ProjectExplorer::Project *project) const = 0;
virtual void updateProjectInfo(const ProjectInfo &pinfo) = 0;
Q_SLOT virtual void removeProjectInfo(ProjectExplorer::Project *project) = 0;
virtual ProjectInfo projectInfoForPath(QString path) = 0;
virtual QStringList importPaths() const = 0;
virtual QmlJS::QmlLanguageBundles activeBundles() const = 0;
virtual QmlJS::QmlLanguageBundles extendedBundles() const = 0;
virtual void loadPluginTypes(const QString &libraryPath, const QString &importPath,
const QString &importUri, const QString &importVersion) = 0;
......
......@@ -54,6 +54,7 @@
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <qtsupport/qtkitinformation.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
......@@ -603,61 +604,10 @@ void QbsProject::updateCppCodeModel(const qbs::ProjectData *prj)
void QbsProject::updateQmlJsCodeModel(const qbs::ProjectData *prj)
{
// FIXME: No information about import directories, so ignore this for now.
#if 1
Q_UNUSED(prj);
#else
QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance();
if (!modelManager)
return;
QmlJS::ModelManagerInterface::ProjectInfo projectInfo = modelManager->projectInfo(this);
projectInfo.sourceFiles = m_projectFiles->files[QMLType];
FindQt4ProFiles findQt4ProFiles;
QList<Qt4ProFileNode *> proFiles = findQt4ProFiles(rootProjectNode());
projectInfo.importPaths.clear();
foreach (Qt4ProFileNode *node, proFiles) {
projectInfo.importPaths.append(node->variableValue(QmlImportPathVar));
}
bool preferDebugDump = false;
projectInfo.tryQmlDump = false;
ProjectExplorer::Target *t = activeTarget();
ProjectExplorer::Kit *k = t ? t->kit() : ProjectExplorer::KitManager::instance()->defaultKit();
QtSupport::BaseQtVersion *qtVersion = QtSupport::QtKitInformation::qtVersion(k);
if (t) {
if (Qt4BuildConfiguration *bc = qobject_cast<Qt4BuildConfiguration *>(t->activeBuildConfiguration()))
preferDebugDump = bc->qmakeBuildConfiguration() & QtSupport::BaseQtVersion::DebugBuild;
} else {
if (qtVersion)
preferDebugDump = qtVersion->defaultBuildConfig() & QtSupport::BaseQtVersion::DebugBuild;
}
if (qtVersion && qtVersion->isValid()) {
projectInfo.tryQmlDump = qtVersion->type() == QLatin1String(QtSupport::Constants::DESKTOPQT)
|| qtVersion->type() == QLatin1String(QtSupport::Constants::SIMULATORQT);
projectInfo.qtImportsPath = qtVersion->qmakeProperty("QT_INSTALL_IMPORTS");
if (!projectInfo.qtImportsPath.isEmpty())
projectInfo.importPaths += projectInfo.qtImportsPath;
projectInfo.qtVersionString = qtVersion->qtVersionString();
}
projectInfo.importPaths.removeDuplicates();
if (projectInfo.tryQmlDump) {
QtSupport::QmlDumpTool::pathAndEnvironment(this, qtVersion,
ToolChainKitInformation::toolChain(k),
preferDebugDump, &projectInfo.qmlDumpPath,
&projectInfo.qmlDumpEnvironment);
} else {
projectInfo.qmlDumpPath.clear();
projectInfo.qmlDumpEnvironment.clear();
}
QmlJS::ModelManagerInterface::ProjectInfo projectInfo =
QmlJSTools::defaultProjectInfoForProject(this);
modelManager->updateProjectInfo(projectInfo);
#endif
}
} // namespace Internal
......
......@@ -17,6 +17,7 @@ QtcPlugin {
Depends { name: "TextEditor" }
Depends { name: "QtSupport" }
Depends { name: "QmlJS" }
Depends { name: "QmlJSTools" }
Depends { name: "cpp" }
......
......@@ -2,3 +2,4 @@ include(../../plugins/projectexplorer/projectexplorer.pri)