forked from qt-creator/qt-creator
QmlJS: Make the astPath available.
Change-Id: Ice51730ee118723dbd755467dd221eb9940775fd Reviewed-on: http://codereview.qt.nokia.com/2774 Reviewed-by: Fawzi Mohamed <fawzi.mohamed@nokia.com>
This commit is contained in:
@@ -77,6 +77,7 @@
|
|||||||
#include <projectexplorer/projectexplorerconstants.h>
|
#include <projectexplorer/projectexplorerconstants.h>
|
||||||
#include <utils/changeset.h>
|
#include <utils/changeset.h>
|
||||||
#include <utils/uncommentselection.h>
|
#include <utils/uncommentselection.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
|
|
||||||
#include <QtCore/QFileInfo>
|
#include <QtCore/QFileInfo>
|
||||||
#include <QtCore/QSignalMapper>
|
#include <QtCore/QSignalMapper>
|
||||||
@@ -447,13 +448,25 @@ protected:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ### does not necessarily give the full AST path!
|
||||||
class CollectASTNodes: protected AST::Visitor
|
// intentionally does not contain lists like
|
||||||
|
// UiImportList, SourceElements, UiObjectMemberList
|
||||||
|
class AstPath: protected AST::Visitor
|
||||||
{
|
{
|
||||||
|
QList<AST::Node *> _path;
|
||||||
|
unsigned _offset;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QList<AST::UiQualifiedId *> qualifiedIds;
|
QList<AST::Node *> operator()(AST::Node *node, unsigned offset)
|
||||||
QList<AST::IdentifierExpression *> identifiers;
|
{
|
||||||
QList<AST::FieldMemberExpression *> fieldMembers;
|
_offset = offset;
|
||||||
|
_path.clear();
|
||||||
|
accept(node);
|
||||||
|
return _path;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using AST::Visitor::visit;
|
||||||
|
|
||||||
void accept(AST::Node *node)
|
void accept(AST::Node *node)
|
||||||
{
|
{
|
||||||
@@ -461,26 +474,69 @@ public:
|
|||||||
node->accept(this);
|
node->accept(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
bool containsOffset(AST::SourceLocation start, AST::SourceLocation end)
|
||||||
using AST::Visitor::visit;
|
{
|
||||||
|
return _offset >= start.begin() && _offset <= end.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle(AST::Node *ast,
|
||||||
|
AST::SourceLocation start, AST::SourceLocation end,
|
||||||
|
bool addToPath = true)
|
||||||
|
{
|
||||||
|
if (containsOffset(start, end)) {
|
||||||
|
if (addToPath)
|
||||||
|
_path.append(ast);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
bool handleLocationAst(T *ast, bool addToPath = true)
|
||||||
|
{
|
||||||
|
return handle(ast, ast->firstSourceLocation(), ast->lastSourceLocation(), addToPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool preVisit(AST::Node *node)
|
||||||
|
{
|
||||||
|
if (Statement *stmt = node->statementCast()) {
|
||||||
|
return handleLocationAst(stmt);
|
||||||
|
} else if (ExpressionNode *exp = node->expressionCast()) {
|
||||||
|
return handleLocationAst(exp);
|
||||||
|
} else if (UiObjectMember *ui = node->uiObjectMemberCast()) {
|
||||||
|
return handleLocationAst(ui);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
virtual bool visit(AST::UiQualifiedId *ast)
|
virtual bool visit(AST::UiQualifiedId *ast)
|
||||||
{
|
{
|
||||||
qualifiedIds.append(ast);
|
AST::SourceLocation first = ast->identifierToken;
|
||||||
|
AST::SourceLocation last;
|
||||||
|
for (AST::UiQualifiedId *it = ast; it; it = it->next)
|
||||||
|
last = it->identifierToken;
|
||||||
|
if (containsOffset(first, last))
|
||||||
|
_path.append(ast);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool visit(AST::IdentifierExpression *ast)
|
virtual bool visit(AST::UiProgram *ast)
|
||||||
{
|
{
|
||||||
identifiers.append(ast);
|
_path.append(ast);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual bool visit(AST::FieldMemberExpression *ast)
|
|
||||||
{
|
|
||||||
fieldMembers.append(ast);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool visit(AST::Program *ast)
|
||||||
|
{
|
||||||
|
_path.append(ast);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool visit(AST::UiImport *ast)
|
||||||
|
{
|
||||||
|
return handleLocationAst(ast);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // end of anonymous namespace
|
} // end of anonymous namespace
|
||||||
@@ -560,47 +616,22 @@ ScopeChain SemanticInfo::scopeChain(const QList<QmlJS::AST::Node *> &path) const
|
|||||||
return scope;
|
return scope;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool importContainsCursor(UiImport *importAst, unsigned cursorPosition)
|
QList<AST::Node *> SemanticInfo::astPath(int pos) const
|
||||||
{
|
{
|
||||||
return cursorPosition >= importAst->firstSourceLocation().begin()
|
QList<AST::Node *> result;
|
||||||
&& cursorPosition <= importAst->lastSourceLocation().end();
|
if (! document)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
AstPath astPath;
|
||||||
|
return astPath(document->ast(), pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
AST::Node *SemanticInfo::nodeUnderCursor(int pos) const
|
AST::Node *SemanticInfo::astNodeAt(int pos) const
|
||||||
{
|
{
|
||||||
if (! document)
|
const QList<AST::Node *> path = astPath(pos);
|
||||||
|
if (path.isEmpty())
|
||||||
return 0;
|
return 0;
|
||||||
|
return path.last();
|
||||||
const unsigned cursorPosition = pos;
|
|
||||||
|
|
||||||
foreach (const ImportInfo &import, document->bind()->imports()) {
|
|
||||||
if (importContainsCursor(import.ast(), cursorPosition))
|
|
||||||
return import.ast();
|
|
||||||
}
|
|
||||||
|
|
||||||
CollectASTNodes nodes;
|
|
||||||
nodes.accept(document->ast());
|
|
||||||
|
|
||||||
foreach (AST::UiQualifiedId *q, nodes.qualifiedIds) {
|
|
||||||
if (cursorPosition >= q->identifierToken.begin()) {
|
|
||||||
for (AST::UiQualifiedId *tail = q; tail; tail = tail->next) {
|
|
||||||
if (! tail->next && cursorPosition <= tail->identifierToken.end())
|
|
||||||
return q;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (AST::IdentifierExpression *id, nodes.identifiers) {
|
|
||||||
if (cursorPosition >= id->identifierToken.begin() && cursorPosition <= id->identifierToken.end())
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (AST::FieldMemberExpression *mem, nodes.fieldMembers) {
|
|
||||||
if (mem->name && cursorPosition >= mem->identifierToken.begin() && cursorPosition <= mem->identifierToken.end())
|
|
||||||
return mem;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SemanticInfo::isValid() const
|
bool SemanticInfo::isValid() const
|
||||||
@@ -1245,7 +1276,8 @@ TextEditor::BaseTextEditorWidget::Link QmlJSTextEditorWidget::findLinkAt(const Q
|
|||||||
|
|
||||||
const unsigned cursorPosition = cursor.position();
|
const unsigned cursorPosition = cursor.position();
|
||||||
|
|
||||||
AST::Node *node = semanticInfo.nodeUnderCursor(cursorPosition);
|
AST::Node *node = semanticInfo.astNodeAt(cursorPosition);
|
||||||
|
QTC_ASSERT(node, return Link());
|
||||||
|
|
||||||
if (AST::UiImport *importAst = cast<AST::UiImport *>(node)) {
|
if (AST::UiImport *importAst = cast<AST::UiImport *>(node)) {
|
||||||
// if it's a file import, link to the file
|
// if it's a file import, link to the file
|
||||||
|
@@ -107,16 +107,22 @@ public:
|
|||||||
bool isValid() const;
|
bool isValid() const;
|
||||||
int revision() const;
|
int revision() const;
|
||||||
|
|
||||||
|
// Returns the AST path
|
||||||
|
QList<QmlJS::AST::Node *> astPath(int cursorPosition) const;
|
||||||
|
|
||||||
|
// Returns the AST node at the offset (the last member of the astPath)
|
||||||
|
QmlJS::AST::Node *astNodeAt(int cursorPosition) const;
|
||||||
|
|
||||||
|
// Returns the list of declaration-type nodes that enclose the given position.
|
||||||
|
// It is more robust than astPath because it tracks ranges with text cursors
|
||||||
|
// and will thus be correct even if the document was changed and not yet
|
||||||
|
// reparsed. It does not return the full path of AST nodes.
|
||||||
|
QList<QmlJS::AST::Node *> rangePath(int cursorPosition) const;
|
||||||
|
|
||||||
// Returns the declaring member
|
// Returns the declaring member
|
||||||
QmlJS::AST::Node *rangeAt(int cursorPosition) const;
|
QmlJS::AST::Node *rangeAt(int cursorPosition) const;
|
||||||
QmlJS::AST::Node *declaringMemberNoProperties(int cursorPosition) const;
|
QmlJS::AST::Node *declaringMemberNoProperties(int cursorPosition) const;
|
||||||
|
|
||||||
// Returns the AST node under cursor
|
|
||||||
QmlJS::AST::Node *nodeUnderCursor(int cursorPosition) const;
|
|
||||||
|
|
||||||
// Returns the list of nodes that enclose the given position.
|
|
||||||
QList<QmlJS::AST::Node *> rangePath(int cursorPosition) const;
|
|
||||||
|
|
||||||
// Returns a scopeChain for the given path
|
// Returns a scopeChain for the given path
|
||||||
QmlJS::ScopeChain scopeChain(const QList<QmlJS::AST::Node *> &path = QList<QmlJS::AST::Node *>()) const;
|
QmlJS::ScopeChain scopeChain(const QList<QmlJS::AST::Node *> &path = QList<QmlJS::AST::Node *>()) const;
|
||||||
|
|
||||||
|
@@ -38,6 +38,7 @@
|
|||||||
#include <coreplugin/editormanager/ieditor.h>
|
#include <coreplugin/editormanager/ieditor.h>
|
||||||
#include <coreplugin/editormanager/editormanager.h>
|
#include <coreplugin/editormanager/editormanager.h>
|
||||||
#include <coreplugin/helpmanager.h>
|
#include <coreplugin/helpmanager.h>
|
||||||
|
#include <utils/qtcassert.h>
|
||||||
#include <extensionsystem/pluginmanager.h>
|
#include <extensionsystem/pluginmanager.h>
|
||||||
#include <qmljs/qmljscontext.h>
|
#include <qmljs/qmljscontext.h>
|
||||||
#include <qmljs/qmljsscopechain.h>
|
#include <qmljs/qmljsscopechain.h>
|
||||||
@@ -121,18 +122,28 @@ void HoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos)
|
|||||||
if (! semanticInfo.isValid() || semanticInfo.revision() != qmlEditor->editorRevision())
|
if (! semanticInfo.isValid() || semanticInfo.revision() != qmlEditor->editorRevision())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
QList<AST::Node *> astPath = semanticInfo.rangePath(pos);
|
QList<AST::Node *> rangePath = semanticInfo.rangePath(pos);
|
||||||
|
|
||||||
const Document::Ptr qmlDocument = semanticInfo.document;
|
const Document::Ptr qmlDocument = semanticInfo.document;
|
||||||
ScopeChain scopeChain = semanticInfo.scopeChain(astPath);
|
ScopeChain scopeChain = semanticInfo.scopeChain(rangePath);
|
||||||
|
|
||||||
AST::Node *node = semanticInfo.nodeUnderCursor(pos);
|
QList<AST::Node *> astPath = semanticInfo.astPath(pos);
|
||||||
if (astPath.isEmpty()) {
|
QTC_ASSERT(!astPath.isEmpty(), return);
|
||||||
if (AST::UiImport *import = AST::cast<AST::UiImport *>(node))
|
AST::Node *node = astPath.last();
|
||||||
|
|
||||||
|
if (rangePath.isEmpty()) {
|
||||||
|
// Is the cursor on an import? The ast path will have an UiImport
|
||||||
|
// member in the last or second to last position!
|
||||||
|
AST::UiImport *import = 0;
|
||||||
|
if (astPath.size() >= 1)
|
||||||
|
import = AST::cast<AST::UiImport *>(astPath.last());
|
||||||
|
if (!import && astPath.size() >= 2)
|
||||||
|
import = AST::cast<AST::UiImport *>(astPath.at(astPath.size() - 2));
|
||||||
|
if (import)
|
||||||
handleImport(scopeChain, import);
|
handleImport(scopeChain, import);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (matchColorItem(scopeChain, qmlDocument, astPath, pos))
|
if (matchColorItem(scopeChain, qmlDocument, rangePath, pos))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
handleOrdinaryMatch(scopeChain, node);
|
handleOrdinaryMatch(scopeChain, node);
|
||||||
|
@@ -218,7 +218,7 @@ void InspectorUi::showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextE
|
|||||||
QString query;
|
QString query;
|
||||||
QLatin1Char doubleQuote('"');
|
QLatin1Char doubleQuote('"');
|
||||||
|
|
||||||
QmlJS::AST::Node *qmlNode = qmlEditor->semanticInfo().nodeUnderCursor(cursorPos);
|
QmlJS::AST::Node *qmlNode = qmlEditor->semanticInfo().astNodeAt(cursorPos);
|
||||||
if (!qmlNode)
|
if (!qmlNode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user