Commit 3f505e99 authored by Christian Kamm's avatar Christian Kamm

QmlJS: Add initial 'Find Usages' support.

parent 6755edc3
......@@ -172,7 +172,7 @@ Check::Check(Document::Ptr doc, const Snapshot &snapshot, const Context *linkedC
: _doc(doc)
, _snapshot(snapshot)
, _context(*linkedContextNoScope)
, _scopeBuilder(doc, &_context)
, _scopeBuilder(&_context, doc, snapshot)
, _ignoreTypeErrors(false)
{
}
......
......@@ -1424,17 +1424,21 @@ void Context::setTypeEnvironment(const QmlJS::Document *doc, const TypeEnvironme
_typeEnvironments[doc->fileName()] = typeEnvironment;
}
const Value *Context::lookup(const QString &name) const
const Value *Context::lookup(const QString &name, const ObjectValue **foundInScope) const
{
QList<const ObjectValue *> scopes = _scopeChain.all();
for (int index = scopes.size() - 1; index != -1; --index) {
const ObjectValue *scope = scopes.at(index);
if (const Value *member = scope->lookupMember(name, this)) {
if (foundInScope)
*foundInScope = scope;
return member;
}
}
if (foundInScope)
*foundInScope = 0;
return _engine->undefinedValue();
}
......
......@@ -285,7 +285,7 @@ public:
const TypeEnvironment *typeEnvironment(const Document *doc) const;
void setTypeEnvironment(const Document *doc, const TypeEnvironment *typeEnvironment);
const Value *lookup(const QString &name) const;
const Value *lookup(const QString &name, const ObjectValue **foundInScope = 0) const;
const ObjectValue *lookupType(const Document *doc, AST::UiQualifiedId *qmlTypeName) const;
const ObjectValue *lookupType(const Document *doc, const QStringList &qmlTypeName) const;
const Value *lookupReference(const Reference *reference) const;
......
......@@ -114,7 +114,6 @@ Link::Link(Context *context, const Document::Ptr &doc, const Snapshot &snapshot,
d->importPaths = importPaths;
linkImports();
initializeScopeChain();
}
Link::~Link()
......@@ -133,86 +132,6 @@ QList<DiagnosticMessage> Link::diagnosticMessages() const
return d->diagnosticMessages;
}
void Link::initializeScopeChain()
{
Q_D(Link);
ScopeChain &scopeChain = d->context->scopeChain();
// ### TODO: This object ought to contain the global namespace additions by QML.
scopeChain.globalScope = engine()->globalObject();
if (! d->doc) {
scopeChain.update();
return;
}
Bind *bind = d->doc->bind();
QHash<Document *, ScopeChain::QmlComponentChain *> componentScopes;
ScopeChain::QmlComponentChain *chain = new ScopeChain::QmlComponentChain;
scopeChain.qmlComponentScope = QSharedPointer<const ScopeChain::QmlComponentChain>(chain);
if (d->doc->qmlProgram()) {
componentScopes.insert(d->doc.data(), chain);
makeComponentChain(d->doc, chain, &componentScopes);
if (const TypeEnvironment *typeEnvironment = d->context->typeEnvironment(d->doc.data()))
scopeChain.qmlTypes = typeEnvironment;
} else {
// add scope chains for all components that import this file
foreach (Document::Ptr otherDoc, d->snapshot) {
foreach (const ImportInfo &import, otherDoc->bind()->imports()) {
if (import.type() == ImportInfo::FileImport && d->doc->fileName() == import.name()) {
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
componentScopes.insert(otherDoc.data(), component);
chain->instantiatingComponents += component;
makeComponentChain(otherDoc, component, &componentScopes);
}
}
}
// ### TODO: Which type environment do scripts see?
if (bind->rootObjectValue())
scopeChain.jsScopes += bind->rootObjectValue();
}
scopeChain.update();
}
void Link::makeComponentChain(
Document::Ptr doc,
ScopeChain::QmlComponentChain *target,
QHash<Document *, ScopeChain::QmlComponentChain *> *components)
{
Q_D(Link);
if (!doc->qmlProgram())
return;
Bind *bind = doc->bind();
// add scopes for all components instantiating this one
foreach (Document::Ptr otherDoc, d->snapshot) {
if (otherDoc == doc)
continue;
if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), d->context)) {
if (components->contains(otherDoc.data())) {
// target->instantiatingComponents += components->value(otherDoc.data());
} else {
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
components->insert(otherDoc.data(), component);
target->instantiatingComponents += component;
makeComponentChain(otherDoc, component, components);
}
}
}
// build this component scope
target->document = doc;
}
void Link::linkImports()
{
Q_D(Link);
......
......@@ -63,17 +63,9 @@ public:
private:
Interpreter::Engine *engine();
void makeComponentChain(
Document::Ptr doc,
Interpreter::ScopeChain::QmlComponentChain *target,
QHash<Document *, Interpreter::ScopeChain::QmlComponentChain *> *components);
static QList<Document::Ptr> reachableDocuments(Document::Ptr startDoc, const Snapshot &snapshot,
const QStringList &importPaths);
static AST::UiQualifiedId *qualifiedTypeNameId(AST::Node *node);
void linkImports();
void initializeScopeChain();
void populateImportedTypes(Interpreter::TypeEnvironment *typeEnv, Document::Ptr doc);
Interpreter::ObjectValue *importFile(Document::Ptr doc, const Interpreter::ImportInfo &importInfo);
......
......@@ -46,7 +46,7 @@ public:
// since we keep the document and snapshot around, we don't need to keep the Link instance
Link link(&context, doc, snapshot, ModelManagerInterface::instance()->importPaths());
ScopeBuilder scopeBuilder(doc, &context);
ScopeBuilder scopeBuilder(&context, doc, snapshot);
scopeBuilder.push(path);
}
......@@ -57,7 +57,7 @@ public:
doc(doc),
snapshot(snapshot)
{
ScopeBuilder scopeBuilder(doc, &context);
ScopeBuilder scopeBuilder(&context, doc, snapshot);
scopeBuilder.push(path);
}
......
......@@ -80,13 +80,39 @@ public:
QStringList importPaths;
};
class WorkingCopy
{
public:
typedef QHash<QString, QPair<QString, int> > Table;
void insert(const QString &fileName, const QString &source, int revision = 0)
{ _elements.insert(fileName, qMakePair(source, revision)); }
bool contains(const QString &fileName) const
{ return _elements.contains(fileName); }
QString source(const QString &fileName) const
{ return _elements.value(fileName).first; }
QPair<QString, int> get(const QString &fileName) const
{ return _elements.value(fileName); }
Table all() const
{ return _elements; }
private:
Table _elements;
};
public:
ModelManagerInterface(QObject *parent = 0);
virtual ~ModelManagerInterface();
static ModelManagerInterface *instance();
virtual WorkingCopy workingCopy() const = 0;
virtual QmlJS::Snapshot snapshot() const = 0;
virtual void updateSourceFiles(const QStringList &files,
bool emitDocumentOnDiskChanged) = 0;
virtual void fileChangedOnDisk(const QString &path) = 0;
......
......@@ -38,10 +38,12 @@ using namespace QmlJS;
using namespace QmlJS::Interpreter;
using namespace QmlJS::AST;
ScopeBuilder::ScopeBuilder(Document::Ptr doc, Interpreter::Context *context)
ScopeBuilder::ScopeBuilder(Context *context, Document::Ptr doc, const Snapshot &snapshot)
: _doc(doc)
, _snapshot(snapshot)
, _context(context)
{
initializeScopeChain();
}
ScopeBuilder::~ScopeBuilder()
......@@ -92,6 +94,85 @@ void ScopeBuilder::pop()
_context->scopeChain().update();
}
void ScopeBuilder::initializeScopeChain()
{
ScopeChain &scopeChain = _context->scopeChain();
scopeChain = ScopeChain(); // reset
Interpreter::Engine *engine = _context->engine();
// ### TODO: This object ought to contain the global namespace additions by QML.
scopeChain.globalScope = engine->globalObject();
if (! _doc) {
scopeChain.update();
return;
}
Bind *bind = _doc->bind();
QHash<Document *, ScopeChain::QmlComponentChain *> componentScopes;
ScopeChain::QmlComponentChain *chain = new ScopeChain::QmlComponentChain;
scopeChain.qmlComponentScope = QSharedPointer<const ScopeChain::QmlComponentChain>(chain);
if (_doc->qmlProgram()) {
componentScopes.insert(_doc.data(), chain);
makeComponentChain(_doc, chain, &componentScopes);
if (const TypeEnvironment *typeEnvironment = _context->typeEnvironment(_doc.data()))
scopeChain.qmlTypes = typeEnvironment;
} else {
// add scope chains for all components that import this file
foreach (Document::Ptr otherDoc, _snapshot) {
foreach (const ImportInfo &import, otherDoc->bind()->imports()) {
if (import.type() == ImportInfo::FileImport && _doc->fileName() == import.name()) {
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
componentScopes.insert(otherDoc.data(), component);
chain->instantiatingComponents += component;
makeComponentChain(otherDoc, component, &componentScopes);
}
}
}
// ### TODO: Which type environment do scripts see?
if (bind->rootObjectValue())
scopeChain.jsScopes += bind->rootObjectValue();
}
scopeChain.update();
}
void ScopeBuilder::makeComponentChain(
Document::Ptr doc,
ScopeChain::QmlComponentChain *target,
QHash<Document *, ScopeChain::QmlComponentChain *> *components)
{
if (!doc->qmlProgram())
return;
Bind *bind = doc->bind();
// add scopes for all components instantiating this one
foreach (Document::Ptr otherDoc, _snapshot) {
if (otherDoc == doc)
continue;
if (otherDoc->bind()->usesQmlPrototype(bind->rootObjectValue(), _context)) {
if (components->contains(otherDoc.data())) {
// target->instantiatingComponents += components->value(otherDoc.data());
} else {
ScopeChain::QmlComponentChain *component = new ScopeChain::QmlComponentChain;
components->insert(otherDoc.data(), component);
target->instantiatingComponents += component;
makeComponentChain(otherDoc, component, components);
}
}
}
// build this component scope
target->document = doc;
}
void ScopeBuilder::setQmlScopeObject(Node *node)
{
ScopeChain &scopeChain = _context->scopeChain();
......
......@@ -31,6 +31,7 @@
#define QMLJSSCOPEBUILDER_H
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljsinterpreter.h>
#include <QtCore/QList>
......@@ -40,16 +41,10 @@ namespace AST {
class Node;
}
namespace Interpreter {
class Context;
class Value;
class ObjectValue;
}
class QMLJS_EXPORT ScopeBuilder
{
public:
ScopeBuilder(Document::Ptr doc, Interpreter::Context *context);
ScopeBuilder(Interpreter::Context *context, Document::Ptr doc, const Snapshot &snapshot);
~ScopeBuilder();
void push(AST::Node *node);
......@@ -59,10 +54,15 @@ public:
static const Interpreter::ObjectValue *isPropertyChangesObject(const Interpreter::Context *context, const Interpreter::ObjectValue *object);
private:
void initializeScopeChain();
void makeComponentChain(Document::Ptr doc, Interpreter::ScopeChain::QmlComponentChain *target,
QHash<Document *, Interpreter::ScopeChain::QmlComponentChain *> *components);
void setQmlScopeObject(AST::Node *node);
const Interpreter::Value *scopeObjectLookup(AST::UiQualifiedId *id);
Document::Ptr _doc;
Snapshot _snapshot;
Interpreter::Context *_context;
QList<AST::Node *> _nodes;
};
......
......@@ -205,7 +205,7 @@ public:
, m_doc(doc)
, m_context(new Interpreter::Context)
, m_link(m_context, doc, snapshot, importPaths)
, m_scopeBuilder(doc, m_context)
, m_scopeBuilder(m_context, doc, snapshot)
{
}
......
......@@ -35,6 +35,7 @@
#include "qmljseditorcodeformatter.h"
#include "qmljsquickfix.h"
#include "qmloutlinemodel.h"
#include "qmljsfindreferences.h"
#include <qmljs/qmljsbind.h>
#include <qmljs/qmljscheck.h>
......@@ -676,7 +677,8 @@ QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) :
m_outlineModel(new QmlOutlineModel(this)),
m_modelManager(0),
m_contextPane(0),
m_updateSelectedElements(false)
m_updateSelectedElements(false),
m_findReferences(new FindReferences(this))
{
qRegisterMetaType<QmlJSEditor::Internal::SemanticInfo>("QmlJSEditor::Internal::SemanticInfo");
......@@ -1434,6 +1436,11 @@ void QmlJSTextEditor::followSymbolUnderCursor()
openLink(findLinkAt(textCursor()));
}
void QmlJSTextEditor::findUsages()
{
m_findReferences->findUsages(file()->fileName(), textCursor().position());
}
void QmlJSTextEditor::showContextPane()
{
if (m_contextPane) {
......
......@@ -63,6 +63,7 @@ namespace QmlJS {
*/
namespace QmlJSEditor {
class Highlighter;
class FindReferences;
namespace Internal {
......@@ -244,6 +245,7 @@ public:
public slots:
void followSymbolUnderCursor();
void findUsages();
void showContextPane();
virtual void setFontSettings(const TextEditor::FontSettings &);
......@@ -331,6 +333,8 @@ private:
QmlJS::IContextPane *m_contextPane;
int m_oldCursorPosition;
bool m_updateSelectedElements;
FindReferences *m_findReferences;
};
} // namespace Internal
......
......@@ -31,7 +31,8 @@ HEADERS += \
qmljsoutlinetreeview.h \
quicktoolbarsettingspage.h \
quicktoolbar.h \
qmljscomponentnamedialog.h
qmljscomponentnamedialog.h \
qmljsfindreferences.h
SOURCES += \
qmljscodecompletion.cpp \
......@@ -56,7 +57,8 @@ SOURCES += \
qmljsoutlinetreeview.cpp \
quicktoolbarsettingspage.cpp \
quicktoolbar.cpp \
qmljscomponentnamedialog.cpp
qmljscomponentnamedialog.cpp \
qmljsfindreferences.cpp
RESOURCES += qmljseditor.qrc
OTHER_FILES += QmlJSEditor.pluginspec QmlJSEditor.mimetypes.xml
......
......@@ -47,8 +47,10 @@ const char * const RUN_SEP = "QmlJSEditor.Run.Separator";
const char * const C_QMLJSEDITOR_ID = "QMLProjectManager.QMLJSEditor";
const char * const C_QMLJSEDITOR_DISPLAY_NAME = QT_TRANSLATE_NOOP("OpenWith::Editors", "QMLJS Editor");
const char * const TASK_INDEX = "QmlJSEditor.TaskIndex";
const char * const TASK_SEARCH = "QmlJSEditor.TaskSearch";
const char * const FOLLOW_SYMBOL_UNDER_CURSOR = "QmlJSEditor.FollowSymbolUnderCursor";
const char * const FIND_USAGES = "QmlJSEditor.FindUsages";
const char * const SHOW_QT_QUICK_HELPER = "QmlJSEditor.ShowQtQuickHelper";
const char * const QML_MIMETYPE = "application/x-qml";
......
......@@ -164,6 +164,13 @@ bool QmlJSEditorPlugin::initialize(const QStringList & /*arguments*/, QString *e
contextMenu->addAction(cmd);
qmlToolsMenu->addAction(cmd);
QAction *findUsagesAction = new QAction(tr("Find Usages"), this);
cmd = am->registerAction(findUsagesAction, Constants::FIND_USAGES, context);
cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+U")));
connect(findUsagesAction, SIGNAL(triggered()), this, SLOT(findUsages()));
contextMenu->addAction(cmd);
qmlToolsMenu->addAction(cmd);
QAction *showQuickToolbar = new QAction(tr("Show Qt Quick Toolbar"), this);
cmd = am->registerAction(showQuickToolbar, Constants::SHOW_QT_QUICK_HELPER, context);
cmd->setDefaultKeySequence(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_Space));
......@@ -261,6 +268,13 @@ void QmlJSEditorPlugin::followSymbolUnderCursor()
editor->followSymbolUnderCursor();
}
void QmlJSEditorPlugin::findUsages()
{
Core::EditorManager *em = Core::EditorManager::instance();
if (QmlJSTextEditor *editor = qobject_cast<QmlJSTextEditor*>(em->currentEditor()->widget()))
editor->findUsages();
}
void QmlJSEditorPlugin::showContextPane()
{
Core::EditorManager *em = Core::EditorManager::instance();
......
......@@ -89,6 +89,7 @@ public:
public Q_SLOTS:
void followSymbolUnderCursor();
void findUsages();
void showContextPane();
private Q_SLOTS:
......
This diff is collapsed.
/**************************************************************************
**
** 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 QMLJSFINDREFERENCES_H
#define QMLJSFINDREFERENCES_H
#include <QtCore/QMutex>
#include <QtCore/QObject>
#include <QtCore/QPointer>
#include <QtCore/QFuture>
#include <QtCore/QFutureWatcher>
#include <utils/filesearch.h>
#include <qmljs/qmljsdocument.h>
#include <qmljs/qmljslookupcontext.h>
QT_FORWARD_DECLARE_CLASS(QTimer)
namespace Find {
class SearchResultWindow;
struct SearchResultItem;
} // end of namespace Find
namespace QmlJSEditor {
class FindReferences: public QObject
{
Q_OBJECT
public:
class Usage
{
public:
Usage()
: line(0), col(0), len(0) {}
Usage(const QString &path, const QString &lineText, int line, int col, int len)
: path(path), lineText(lineText), line(line), col(col), len(len) {}
public:
QString path;
QString lineText;
int line;
int col;
int len;
};
public:
FindReferences(QObject *parent = 0);
virtual ~FindReferences();
Q_SIGNALS:
void changed();
public:
void findUsages(const QString &fileName, quint32 offset);
private Q_SLOTS:
void displayResults(int first, int last);
void searchFinished();
void openEditor(const Find::SearchResultItem &item);
private:
void findAll_helper(const QString &fileName, quint32 offset);
private:
Find::SearchResultWindow *_resultWindow;
QFutureWatcher<Usage> m_watcher;
};
} // end of namespace QmlJSEditor
#endif // QMLJSFINDREFERENCES_H
......@@ -103,6 +103,24 @@ void ModelManager::loadQmlTypeDescriptions(const QString &resourcePath)
//loadQmlPluginTypes(QString());
}
ModelManagerInterface::WorkingCopy ModelManager::workingCopy() const
{
WorkingCopy workingCopy;