Commit 8dd13fde authored by Erik Verbruggen's avatar Erik Verbruggen

Rewrote and improved navigation.

parent b92e1a0a
......@@ -27,6 +27,7 @@
**
**************************************************************************/
#include "idcollector.h"
#include "duidocument.h"
#include "qmljsast_p.h"
#include "qmljslexer_p.h"
......@@ -44,12 +45,21 @@ DuiDocument::DuiDocument(const QString &fileName)
, _fileName(fileName)
, _parsedCorrectly(false)
{
const int slashIdx = fileName.lastIndexOf('/');
if (slashIdx != -1)
_path = fileName.left(slashIdx);
if (fileName.toLower().endsWith(".qml"))
_componentName = fileName.mid(slashIdx + 1, fileName.size() - (slashIdx + 1) - 4);
}
DuiDocument::~DuiDocument()
{
delete _engine;
delete _pool;
if (_engine)
delete _engine;
if (_pool)
delete _pool;
}
DuiDocument::Ptr DuiDocument::create(const QString &fileName)
......@@ -81,6 +91,7 @@ bool DuiDocument::parse()
_engine = new Engine();
_pool = new NodePool(_fileName, _engine);
_ids.clear();
Lexer lexer(_engine);
Parser parser(_engine);
......@@ -90,6 +101,12 @@ bool DuiDocument::parse()
_parsedCorrectly = parser.parse();
_program = parser.ast();
_diagnosticMessages = parser.diagnosticMessages();
if (_parsedCorrectly && _program) {
Internal::IdCollector collect;
_ids = collect(_program);
}
return _parsedCorrectly;
}
......@@ -100,3 +117,46 @@ Snapshot::Snapshot()
Snapshot::~Snapshot()
{
}
void Snapshot::insert(const DuiDocument::Ptr &document)
{
QMap<QString, DuiDocument::Ptr>::insert(document->fileName(), document);
}
DuiDocument::PtrList Snapshot::importedDocuments(const DuiDocument::Ptr &doc, const QString &importPath)
{
DuiDocument::PtrList result;
const QString docPath = doc->path() + '/' + importPath;
for (Iterator i = iterator(); i.hasNext();) {
DuiDocument::Ptr candidate = i.next().value();
if (candidate == doc)
continue;
if (candidate->path() == doc->path() || candidate->path() == docPath)
result.append(candidate);
}
return result;
}
QMap<QString, DuiDocument::Ptr> Snapshot::componentsDefinedByImportedDocuments(const DuiDocument::Ptr &doc, const QString &importPath)
{
QMap<QString, DuiDocument::Ptr> result;
const QString docPath = doc->path() + '/' + importPath;
for (Iterator i = iterator(); i.hasNext();) {
DuiDocument::Ptr candidate = i.next().value();
if (candidate == doc)
continue;
if (candidate->path() == doc->path() || candidate->path() == docPath)
result.insert(candidate->componentName(), candidate);
}
return result;
}
......@@ -29,8 +29,10 @@
#ifndef DUIDOCUMENT_H
#define DUIDOCUMENT_H
#include <QtCore/QSharedPointer>
#include <QtCore/QList>
#include <QtCore/QMap>
#include <QtCore/QPair>
#include <QtCore/QSharedPointer>
#include <QtCore/QString>
#include "duieditor_global.h"
......@@ -44,6 +46,8 @@ class DUIEDITOR_EXPORT DuiDocument
{
public:
typedef QSharedPointer<DuiDocument> Ptr;
typedef QList<DuiDocument::Ptr> PtrList;
typedef QMap<QString, QPair<QmlJS::AST::SourceLocation, QmlJS::AST::Node*> > IdTable;
protected:
DuiDocument(const QString &fileName);
......@@ -62,7 +66,11 @@ public:
bool isParsedCorrectly() const
{ return _parsedCorrectly; }
IdTable ids() const { return _ids; }
QString fileName() const { return _fileName; }
QString path() const { return _path; }
QString componentName() const { return _componentName; }
private:
QmlJS::Engine *_engine;
......@@ -70,8 +78,11 @@ private:
QmlJS::AST::UiProgram *_program;
QList<QmlJS::DiagnosticMessage> _diagnosticMessages;
QString _fileName;
QString _path;
QString _componentName;
QString _source;
bool _parsedCorrectly;
IdTable _ids;
};
class DUIEDITOR_EXPORT Snapshot: protected QMap<QString, DuiDocument::Ptr>
......@@ -80,8 +91,7 @@ public:
Snapshot();
~Snapshot();
void insert(const DuiDocument::Ptr &document)
{ QMap<QString, DuiDocument::Ptr>::insert(document->fileName(), document); }
void insert(const DuiDocument::Ptr &document);
typedef QMapIterator<QString, DuiDocument::Ptr> Iterator;
Iterator iterator() const
......@@ -89,6 +99,9 @@ public:
DuiDocument::Ptr document(const QString &fileName) const
{ return value(fileName); }
DuiDocument::PtrList importedDocuments(const DuiDocument::Ptr &doc, const QString &importPath);
QMap<QString, DuiDocument::Ptr> componentsDefinedByImportedDocuments(const DuiDocument::Ptr &doc, const QString &importPath);
};
} // emd of namespace DuiEditor
......
......@@ -714,11 +714,10 @@ TextEditor::BaseTextEditor::Link ScriptEditor::findLinkAt(const QTextCursor &cur
if (!doc)
return link;
QMap<QString, QmlJS::AST::SourceLocation> idPositions = IdCollector()(doc->program());
NavigationTokenFinder finder;
if (finder(doc->program(), cursor.position(), resolveTarget, idPositions)) {
link.fileName = file()->fileName();
finder(doc, cursor.position(), snapshot);
if (finder.targetFound()) {
link.fileName = finder.fileName();
link.pos = finder.linkPosition();
link.length = finder.linkLength();
......
......@@ -69,34 +69,36 @@ void DuiModelManager::updateSourceFiles(const QStringList &files)
QFuture<void> DuiModelManager::refreshSourceFiles(const QStringList &sourceFiles)
{
if (! sourceFiles.isEmpty()) {
const QMap<QString, QString> workingCopy = buildWorkingCopyList();
if (sourceFiles.isEmpty()) {
return QFuture<void>();
}
QFuture<void> result = QtConcurrent::run(&DuiModelManager::parse,
workingCopy, sourceFiles,
this);
const QMap<QString, QString> workingCopy = buildWorkingCopyList();
if (m_synchronizer.futures().size() > 10) {
QList<QFuture<void> > futures = m_synchronizer.futures();
QFuture<void> result = QtConcurrent::run(&DuiModelManager::parse,
workingCopy, sourceFiles,
this);
m_synchronizer.clearFutures();
if (m_synchronizer.futures().size() > 10) {
QList<QFuture<void> > futures = m_synchronizer.futures();
foreach (QFuture<void> future, futures) {
if (! (future.isFinished() || future.isCanceled()))
m_synchronizer.addFuture(future);
}
m_synchronizer.clearFutures();
foreach (QFuture<void> future, futures) {
if (! (future.isFinished() || future.isCanceled()))
m_synchronizer.addFuture(future);
}
}
m_synchronizer.addFuture(result);
m_synchronizer.addFuture(result);
if (sourceFiles.count() > 1) {
m_core->progressManager()->addTask(result, tr("Indexing"),
DuiEditor::Constants::TASK_INDEX,
Core::ProgressManager::CloseOnSuccess);
}
return result;
if (sourceFiles.count() > 1) {
m_core->progressManager()->addTask(result, tr("Indexing"),
DuiEditor::Constants::TASK_INDEX,
Core::ProgressManager::CloseOnSuccess);
}
return QFuture<void>();
return result;
}
QMap<QString, QString> DuiModelManager::buildWorkingCopyList()
......
......@@ -6,13 +6,35 @@ using namespace QmlJS;
using namespace QmlJS::AST;
using namespace DuiEditor::Internal;
QMap<QString, QmlJS::AST::SourceLocation> IdCollector::operator()(QmlJS::AST::UiProgram *ast)
QMap<QString, QPair<QmlJS::AST::SourceLocation, QmlJS::AST::Node*> > IdCollector::operator()(QmlJS::AST::UiProgram *ast)
{
_idLocations.clear();
_ids.clear();
Node::accept(ast, this);
return _idLocations;
return _ids;
}
bool IdCollector::visit(QmlJS::AST::UiObjectBinding *ast)
{
_scopes.push(ast);
return true;
}
bool IdCollector::visit(QmlJS::AST::UiObjectDefinition *ast)
{
_scopes.push(ast);
return true;
}
void IdCollector::endVisit(QmlJS::AST::UiObjectBinding *)
{
_scopes.pop();
}
void IdCollector::endVisit(QmlJS::AST::UiObjectDefinition *)
{
_scopes.pop();
}
bool IdCollector::visit(QmlJS::AST::UiScriptBinding *ast)
......@@ -29,6 +51,6 @@ void IdCollector::addId(QmlJS::NameId* id, const QmlJS::AST::SourceLocation &idL
{
const QString idString = id->asString();
if (!_idLocations.contains(idString))
_idLocations[idString] = idLocation;
if (!_ids.contains(idString))
_ids[idString] = qMakePair(idLocation, _scopes.top());
}
......@@ -2,6 +2,8 @@
#define IDCOLLECTOR_H
#include <QMap>
#include <QPair>
#include <QStack>
#include <QString>
#include "qmljsastvisitor_p.h"
......@@ -14,16 +16,22 @@ namespace Internal {
class IdCollector: protected QmlJS::AST::Visitor
{
public:
QMap<QString, QmlJS::AST::SourceLocation> operator()(QmlJS::AST::UiProgram *ast);
QMap<QString, QPair<QmlJS::AST::SourceLocation, QmlJS::AST::Node*> > operator()(QmlJS::AST::UiProgram *ast);
protected:
virtual bool visit(QmlJS::AST::UiObjectBinding *ast);
virtual bool visit(QmlJS::AST::UiObjectDefinition *ast);
virtual bool visit(QmlJS::AST::UiScriptBinding *ast);
virtual void endVisit(QmlJS::AST::UiObjectBinding *);
virtual void endVisit(QmlJS::AST::UiObjectDefinition *);
private:
void addId(QmlJS::NameId* id, const QmlJS::AST::SourceLocation &idLocation);
private:
QMap<QString, QmlJS::AST::SourceLocation> _idLocations;
QMap<QString, QPair<QmlJS::AST::SourceLocation, QmlJS::AST::Node*> > _ids;
QStack<QmlJS::AST::Node *> _scopes;
};
} // namespace Internal
......
......@@ -5,23 +5,60 @@
using namespace QmlJS;
using namespace QmlJS::AST;
using namespace DuiEditor;
using namespace DuiEditor::Internal;
bool NavigationTokenFinder::operator()(QmlJS::AST::UiProgram *ast, int position, bool resolveTarget, const QMap<QString, QmlJS::AST::SourceLocation> &idPositions)
void NavigationTokenFinder::operator()(const DuiDocument::Ptr &doc, int position, const Snapshot &snapshot)
{
_resolveTarget = resolveTarget;
_doc = doc;
_snapshot = snapshot;
_pos = position;
_idPositions = idPositions;
_scopes.clear();
_linkPosition = -1;
_fileName.clear();
_targetLine = -1;
Node::accept(ast, this);
Node::accept(doc->program(), this);
}
static QStringList buildQualifiedId(QmlJS::AST::FieldMemberExpression *expr)
{
QStringList qId;
if (FieldMemberExpression *baseExpr = cast<FieldMemberExpression*>(expr->base)) {
qId = buildQualifiedId(baseExpr);
} else if (IdentifierExpression *idExpr = cast<IdentifierExpression*>(expr->base)) {
qId.append(idExpr->name->asString());
} else {
return qId;
}
if (resolveTarget)
return targetFound();
else
return linkFound();
qId.append(expr->name->asString());
return qId;
}
bool NavigationTokenFinder::visit(QmlJS::AST::FieldMemberExpression *ast)
{
if (linkFound())
return false;
if (ast->firstSourceLocation().offset <= _pos && _pos <= ast->lastSourceLocation().end()) {
if (ast->identifierToken.offset <= _pos && _pos <= ast->identifierToken.end()) {
// found it:
_linkPosition = ast->identifierToken.offset;
_linkLength = ast->identifierToken.end() - _linkPosition;
const QStringList qualifiedId(buildQualifiedId(ast));
if (!qualifiedId.isEmpty())
findDeclaration(qualifiedId);
} else {
Node::accept(ast->base, this);
}
return false;
}
return true;
}
bool NavigationTokenFinder::visit(QmlJS::AST::IdentifierExpression *ast)
......@@ -33,8 +70,7 @@ bool NavigationTokenFinder::visit(QmlJS::AST::IdentifierExpression *ast)
_linkPosition = ast->identifierToken.offset;
_linkLength = ast->identifierToken.length;
if (Node *node = findDeclarationInScopesOrIds(ast->name))
rememberStartPosition(node);
findDeclaration(QStringList() << ast->name->asString());
}
return false;
......@@ -50,6 +86,11 @@ bool NavigationTokenFinder::visit(QmlJS::AST::UiArrayBinding *ast)
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiImportList *)
{
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiPublicMember *ast)
{
if (linkFound())
......@@ -64,10 +105,7 @@ bool NavigationTokenFinder::visit(QmlJS::AST::Block *ast)
{
_scopes.push(ast);
if (linkFound())
return false;
return true;
return !linkFound();
}
void NavigationTokenFinder::endVisit(QmlJS::AST::Block *)
......@@ -79,11 +117,10 @@ bool NavigationTokenFinder::visit(QmlJS::AST::UiObjectBinding *ast)
{
_scopes.push(ast);
if (linkFound())
return false;
Node::accept(ast->qualifiedTypeNameId, this);
Node::accept(ast->initializer, this);
if (!linkFound()) {
checkType(ast->qualifiedTypeNameId);
Node::accept(ast->initializer, this);
}
return false;
}
......@@ -97,10 +134,12 @@ bool NavigationTokenFinder::visit(QmlJS::AST::UiObjectDefinition *ast)
{
_scopes.push(ast);
if (linkFound())
return false;
if (!linkFound()) {
checkType(ast->qualifiedTypeNameId);
Node::accept(ast->initializer, this);
}
return true;
return false;
}
void NavigationTokenFinder::endVisit(QmlJS::AST::UiObjectDefinition *)
......@@ -108,36 +147,34 @@ void NavigationTokenFinder::endVisit(QmlJS::AST::UiObjectDefinition *)
_scopes.pop();
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiQualifiedId *ast)
void NavigationTokenFinder::checkType(QmlJS::AST::UiQualifiedId *ast)
{
if (linkFound())
return false;
if (ast->identifierToken.offset <= _pos) {
for (UiQualifiedId *iter = ast; iter; iter = iter->next) {
if (_pos <= iter->identifierToken.end()) {
_linkPosition = ast->identifierToken.offset;
for (UiQualifiedId *iter2 = ast; iter2; iter2 = iter2->next)
QStringList names;
for (UiQualifiedId *iter2 = ast; iter2; iter2 = iter2->next) {
_linkLength = iter2->identifierToken.end() - _linkPosition;
names.append(iter2->name->asString());
}
findTypeDeclaration(names);
if (Node *node = findDeclarationInScopesOrIds(ast))
rememberStartPosition(node);
return false;
return;
}
}
}
return false;
}
bool NavigationTokenFinder::visit(QmlJS::AST::UiScriptBinding *ast)
{
if (linkFound())
return false;
Node::accept(ast->statement, this);
if (!linkFound()) {
if (ast->qualifiedId && !ast->qualifiedId->next && ast->qualifiedId->name && ast->qualifiedId->name->asString() == "id")
return false;
else
Node::accept(ast->statement, this);
}
return false;
}
......@@ -147,134 +184,172 @@ bool NavigationTokenFinder::visit(QmlJS::AST::UiSourceElement * /*ast*/)
return false;
}
void NavigationTokenFinder::rememberStartPosition(QmlJS::AST::Node *node)
bool NavigationTokenFinder::findInJS(const QStringList &qualifiedId, QmlJS::AST::Block *block)
{
if (UiObjectMember *om = dynamic_cast<UiObjectMember*>(node)) {
_targetLine = om->firstSourceLocation().startLine;
_targetColumn = om->firstSourceLocation().startColumn;
} else if (VariableDeclaration *vd = cast<VariableDeclaration*>(node)) {
_targetLine = vd->identifierToken.startLine;
_targetColumn = vd->identifierToken.startColumn;
} else {
// qWarning() << "Found declaration of unknown type as a navigation target";
}
}
if (qualifiedId.size() > 1)
return false; // we can only find "simple" JavaScript variables this way
void NavigationTokenFinder::rememberStartPosition(const QmlJS::AST::SourceLocation &location)
{
_targetLine = location.startLine;
_targetColumn = location.startColumn;
}
const QString id = qualifiedId[0];
static QmlJS::AST::Node *findDeclaration(const QString &nameId, QmlJS::AST::UiObjectMember *m)
{
if (UiPublicMember *p = cast<UiPublicMember*>(m)) {
if (p->name->asString() == nameId)
return p;
} else if (UiObjectBinding *o = cast<UiObjectBinding*>(m)) {
if (!(o->qualifiedId->next) && o->qualifiedId->name->asString() == nameId)
return o;
} else if (UiArrayBinding *a = cast<UiArrayBinding*>(m)) {
if (!(a->qualifiedId->next) && a->qualifiedId->name->asString() == nameId)
return a;
for (StatementList *iter = block->statements; iter; iter = iter->next) {
Statement *stmt = iter->statement;
if (VariableStatement *varStmt = cast<VariableStatement*>(stmt)) {
for (VariableDeclarationList *varIter = varStmt->declarations; varIter; varIter = varIter->next) {
if (varIter->declaration && varIter->declaration->name && varIter->declaration->name->asString() == id) {
rememberLocation(varIter->declaration->identifierToken);
return true;
}
}
}
}
return 0;
return false;
}
static QmlJS::AST::Node *findDeclaration(const QString &nameId, QmlJS::AST::UiObjectMemberList *l)
static bool matches(UiQualifiedId *candidate, const QStringList &wanted)
{
for (UiObjectMemberList *iter = l; iter; iter = iter->next)
if (Node *n = findDeclaration(nameId, iter->member))
return n;
UiQualifiedId *iter = candidate;
for (int i = 0; i < wanted.size(); ++i) {
if (!iter)
return false;
if (iter->name->asString() != wanted[i])
return false;
iter = iter->next;
}
return 0;
return !iter;
}
static QmlJS::AST::Node *findDeclaration(const QString &nameId, QmlJS::AST::Statement *s)
bool NavigationTokenFinder::findProperty(const QStringList &qualifiedId, QmlJS::AST::UiQualifiedId *typeId, QmlJS::AST::UiObjectMemberList *ast, int scopeLevel)
{
if (VariableStatement *v = cast<VariableStatement*>(s)) {
for (VariableDeclarationList *l = v->declarations; l; l = l->next) {
if (l->declaration->name->asString() == nameId)
return l->declaration;
// 1. try the "overridden" properties:
for (UiObjectMemberList *iter = ast; iter; iter = iter->next) {
UiObjectMember *member = iter->member;
if (UiPublicMember *publicMember = cast<UiPublicMember*>(member)) {
if (publicMember->name && qualifiedId.size() == 1 && publicMember->name->asString() == qualifiedId.first()) {
rememberLocation(publicMember->identifierToken);
return true;
}
} e