diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index 1476193d1fac50578deee726e8c23fcc713de210..6a267ad9578afd494988ed86639d5da61a5a9a5f 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -77,6 +77,7 @@ #include <projectexplorer/projectexplorerconstants.h> #include <utils/changeset.h> #include <utils/uncommentselection.h> +#include <utils/qtcassert.h> #include <QtCore/QFileInfo> #include <QtCore/QSignalMapper> @@ -447,13 +448,25 @@ protected: }; - -class CollectASTNodes: protected AST::Visitor +// ### does not necessarily give the full AST path! +// intentionally does not contain lists like +// UiImportList, SourceElements, UiObjectMemberList +class AstPath: protected AST::Visitor { + QList<AST::Node *> _path; + unsigned _offset; + public: - QList<AST::UiQualifiedId *> qualifiedIds; - QList<AST::IdentifierExpression *> identifiers; - QList<AST::FieldMemberExpression *> fieldMembers; + QList<AST::Node *> operator()(AST::Node *node, unsigned offset) + { + _offset = offset; + _path.clear(); + accept(node); + return _path; + } + +protected: + using AST::Visitor::visit; void accept(AST::Node *node) { @@ -461,26 +474,69 @@ public: node->accept(this); } -protected: - using AST::Visitor::visit; + bool containsOffset(AST::SourceLocation start, AST::SourceLocation end) + { + return _offset >= start.begin() && _offset <= end.end(); + } - virtual bool visit(AST::UiQualifiedId *ast) + bool handle(AST::Node *ast, + AST::SourceLocation start, AST::SourceLocation end, + bool addToPath = true) { - qualifiedIds.append(ast); + if (containsOffset(start, end)) { + if (addToPath) + _path.append(ast); + return true; + } return false; } - virtual bool visit(AST::IdentifierExpression *ast) + 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) { - identifiers.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; } - virtual bool visit(AST::FieldMemberExpression *ast) + virtual bool visit(AST::UiProgram *ast) { - fieldMembers.append(ast); + _path.append(ast); 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 @@ -560,47 +616,22 @@ ScopeChain SemanticInfo::scopeChain(const QList<QmlJS::AST::Node *> &path) const return scope; } -static bool importContainsCursor(UiImport *importAst, unsigned cursorPosition) +QList<AST::Node *> SemanticInfo::astPath(int pos) const { - return cursorPosition >= importAst->firstSourceLocation().begin() - && cursorPosition <= importAst->lastSourceLocation().end(); + QList<AST::Node *> result; + 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; - - 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; + return path.last(); } bool SemanticInfo::isValid() const @@ -1245,7 +1276,8 @@ TextEditor::BaseTextEditorWidget::Link QmlJSTextEditorWidget::findLinkAt(const Q 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 it's a file import, link to the file diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h index 31206b4481430c074d6f186dad4a0292986c8b6a..ed271c8ef60329f030d92854c5010167c4a34f6d 100644 --- a/src/plugins/qmljseditor/qmljseditor.h +++ b/src/plugins/qmljseditor/qmljseditor.h @@ -107,16 +107,22 @@ public: bool isValid() const; int revision() const; - // Returns the declaring member - QmlJS::AST::Node *rangeAt(int cursorPosition) const; - QmlJS::AST::Node *declaringMemberNoProperties(int cursorPosition) const; + // Returns the AST path + QList<QmlJS::AST::Node *> astPath(int cursorPosition) const; - // Returns the AST node under cursor - QmlJS::AST::Node *nodeUnderCursor(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 nodes that enclose the given position. + // 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 + QmlJS::AST::Node *rangeAt(int cursorPosition) const; + QmlJS::AST::Node *declaringMemberNoProperties(int cursorPosition) const; + // Returns a scopeChain for the given path QmlJS::ScopeChain scopeChain(const QList<QmlJS::AST::Node *> &path = QList<QmlJS::AST::Node *>()) const; diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp index 085c4c031dda7ad6ca45b5fc373b7ae80b07d145..d8602bfbaf28ca2bddb10af2d05b4f054f018ca4 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.cpp +++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp @@ -38,6 +38,7 @@ #include <coreplugin/editormanager/ieditor.h> #include <coreplugin/editormanager/editormanager.h> #include <coreplugin/helpmanager.h> +#include <utils/qtcassert.h> #include <extensionsystem/pluginmanager.h> #include <qmljs/qmljscontext.h> #include <qmljs/qmljsscopechain.h> @@ -121,18 +122,28 @@ void HoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos) if (! semanticInfo.isValid() || semanticInfo.revision() != qmlEditor->editorRevision()) return; - QList<AST::Node *> astPath = semanticInfo.rangePath(pos); + QList<AST::Node *> rangePath = semanticInfo.rangePath(pos); const Document::Ptr qmlDocument = semanticInfo.document; - ScopeChain scopeChain = semanticInfo.scopeChain(astPath); - - AST::Node *node = semanticInfo.nodeUnderCursor(pos); - if (astPath.isEmpty()) { - if (AST::UiImport *import = AST::cast<AST::UiImport *>(node)) + ScopeChain scopeChain = semanticInfo.scopeChain(rangePath); + + QList<AST::Node *> astPath = semanticInfo.astPath(pos); + QTC_ASSERT(!astPath.isEmpty(), return); + 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); return; } - if (matchColorItem(scopeChain, qmlDocument, astPath, pos)) + if (matchColorItem(scopeChain, qmlDocument, rangePath, pos)) return; handleOrdinaryMatch(scopeChain, node); diff --git a/src/plugins/qmljsinspector/qmljsinspector.cpp b/src/plugins/qmljsinspector/qmljsinspector.cpp index bc204ded6ad60b5ee9fac5c00811c3974950f78c..31c72a7d339d0c13af1d1b59ed98ff122682da78 100644 --- a/src/plugins/qmljsinspector/qmljsinspector.cpp +++ b/src/plugins/qmljsinspector/qmljsinspector.cpp @@ -218,7 +218,7 @@ void InspectorUi::showDebuggerTooltip(const QPoint &mousePos, TextEditor::ITextE QString query; QLatin1Char doubleQuote('"'); - QmlJS::AST::Node *qmlNode = qmlEditor->semanticInfo().nodeUnderCursor(cursorPos); + QmlJS::AST::Node *qmlNode = qmlEditor->semanticInfo().astNodeAt(cursorPos); if (!qmlNode) return;