/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2009 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. ** **************************************************************************/ #include "qmljseditor.h" #include "qmljseditorconstants.h" #include "qmlhighlighter.h" #include "qmljseditorplugin.h" #include "qmlmodelmanager.h" #include "qmlexpressionundercursor.h" #include "qmllookupcontext.h" #include "qmlresolveexpression.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 250, UPDATE_USES_DEFAULT_INTERVAL = 150 }; using namespace Qml; using namespace QmlJS; using namespace QmlJS::AST; using namespace SharedTools; namespace { int blockBraceDepth(const QTextBlock &block) { int state = block.userState(); if (state == -1) return 0; return (state >> 8) & 0xFF; } int blockStartState(const QTextBlock &block) { int state = block.userState(); if (state == -1) return 0; else return state & 0xff; } } // end of anonymous namespace namespace QmlJSEditor { namespace Internal { class FindIdDeclarations: protected Visitor { public: typedef QMap > Result; Result operator()(AST::Node *node) { _ids.clear(); _maybeIds.clear(); accept(node); return _ids; } protected: QString asString(AST::UiQualifiedId *id) { QString text; for (; id; id = id->next) { if (id->name) text += id->name->asString(); else text += QLatin1Char('?'); if (id->next) text += QLatin1Char('.'); } return text; } void accept(AST::Node *node) { AST::Node::acceptChild(node, this); } using Visitor::visit; using Visitor::endVisit; virtual bool visit(AST::UiScriptBinding *node) { if (asString(node->qualifiedId) == QLatin1String("id")) { if (AST::ExpressionStatement *stmt = AST::cast(node->statement)) { if (AST::IdentifierExpression *idExpr = AST::cast(stmt->expression)) { if (idExpr->name) { const QString id = idExpr->name->asString(); QList *locs = &_ids[id]; locs->append(idExpr->firstSourceLocation()); locs->append(_maybeIds.value(id)); _maybeIds.remove(id); return false; } } } } accept(node->statement); return false; } virtual bool visit(AST::IdentifierExpression *node) { if (node->name) { const QString name = node->name->asString(); if (_ids.contains(name)) _ids[name].append(node->identifierToken); else _maybeIds[name].append(node->identifierToken); } return false; } private: Result _ids; Result _maybeIds; }; class FindDeclarations: protected Visitor { QList _declarations; int _depth; public: QList operator()(AST::Node *node) { _depth = -1; _declarations.clear(); accept(node); return _declarations; } protected: using Visitor::visit; using Visitor::endVisit; QString asString(AST::UiQualifiedId *id) { QString text; for (; id; id = id->next) { if (id->name) text += id->name->asString(); else text += QLatin1Char('?'); if (id->next) text += QLatin1Char('.'); } return text; } void accept(AST::Node *node) { AST::Node::acceptChild(node, this); } void init(Declaration *decl, AST::UiObjectMember *member) { const SourceLocation first = member->firstSourceLocation(); const SourceLocation last = member->lastSourceLocation(); decl->startLine = first.startLine; decl->startColumn = first.startColumn; decl->endLine = last.startLine; decl->endColumn = last.startColumn + last.length; } virtual bool visit(AST::UiObjectDefinition *node) { ++_depth; Declaration decl; init(&decl, node); decl.text.fill(QLatin1Char(' '), _depth); if (node->qualifiedTypeNameId) decl.text.append(asString(node->qualifiedTypeNameId)); else decl.text.append(QLatin1Char('?')); _declarations.append(decl); return true; // search for more bindings } virtual void endVisit(AST::UiObjectDefinition *) { --_depth; } virtual bool visit(AST::UiObjectBinding *node) { ++_depth; Declaration decl; init(&decl, node); decl.text.fill(QLatin1Char(' '), _depth); decl.text.append(asString(node->qualifiedId)); decl.text.append(QLatin1String(": ")); if (node->qualifiedTypeNameId) decl.text.append(asString(node->qualifiedTypeNameId)); else decl.text.append(QLatin1Char('?')); _declarations.append(decl); return true; // search for more bindings } virtual void endVisit(AST::UiObjectBinding *) { --_depth; } #if 0 // ### ignore script bindings for now. virtual bool visit(AST::UiScriptBinding *node) { ++_depth; Declaration decl; init(&decl, node); decl.text.fill(QLatin1Char(' '), _depth); decl.text.append(asString(node->qualifiedId)); _declarations.append(decl); return false; // more more bindings in this subtree. } virtual void endVisit(AST::UiScriptBinding *) { --_depth; } #endif }; QmlJSEditorEditable::QmlJSEditorEditable(QmlJSTextEditor *editor) : BaseTextEditorEditable(editor) { Core::UniqueIDManager *uidm = Core::UniqueIDManager::instance(); m_context << uidm->uniqueIdentifier(QmlJSEditor::Constants::C_QMLJSEDITOR_ID); m_context << uidm->uniqueIdentifier(TextEditor::Constants::C_TEXTEDITOR); } QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) : TextEditor::BaseTextEditor(parent), m_methodCombo(0), m_modelManager(0), m_typeSystem(0) { m_idsRevision = -1; setParenthesesMatchingEnabled(true); setMarksVisible(true); setCodeFoldingSupported(true); setCodeFoldingVisible(true); m_updateDocumentTimer = new QTimer(this); m_updateDocumentTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL); m_updateDocumentTimer->setSingleShot(true); connect(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow())); m_updateUsesTimer = new QTimer(this); m_updateUsesTimer->setInterval(UPDATE_USES_DEFAULT_INTERVAL); m_updateUsesTimer->setSingleShot(true); connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow())); connect(this, SIGNAL(textChanged()), this, SLOT(updateDocument())); connect(this, SIGNAL(textChanged()), this, SLOT(updateUses())); baseTextDocument()->setSyntaxHighlighter(new QmlHighlighter); m_modelManager = ExtensionSystem::PluginManager::instance()->getObject(); m_typeSystem = ExtensionSystem::PluginManager::instance()->getObject(); if (m_modelManager) { connect(m_modelManager, SIGNAL(documentUpdated(Qml::QmlDocument::Ptr)), this, SLOT(onDocumentUpdated(Qml::QmlDocument::Ptr))); } } QmlJSTextEditor::~QmlJSTextEditor() { } QList QmlJSTextEditor::declarations() const { return m_declarations; } Core::IEditor *QmlJSEditorEditable::duplicate(QWidget *parent) { QmlJSTextEditor *newEditor = new QmlJSTextEditor(parent); newEditor->duplicateFrom(editor()); QmlJSEditorPlugin::instance()->initializeEditor(newEditor); return newEditor->editableInterface(); } QString QmlJSEditorEditable::id() const { return QLatin1String(QmlJSEditor::Constants::C_QMLJSEDITOR_ID); } bool QmlJSEditorEditable::open(const QString &fileName) { bool b = TextEditor::BaseTextEditorEditable::open(fileName); editor()->setMimeType(Core::ICore::instance()->mimeDatabase()->findByFile(QFileInfo(fileName)).type()); return b; } QmlJSTextEditor::Context QmlJSEditorEditable::context() const { return m_context; } void QmlJSTextEditor::updateDocument() { m_updateDocumentTimer->start(UPDATE_DOCUMENT_DEFAULT_INTERVAL); } void QmlJSTextEditor::updateDocumentNow() { // ### move in the parser thread. m_updateDocumentTimer->stop(); const QString fileName = file()->fileName(); m_modelManager->updateSourceFiles(QStringList() << fileName); } void QmlJSTextEditor::onDocumentUpdated(Qml::QmlDocument::Ptr doc) { if (file()->fileName() != doc->fileName()) return; m_document = doc; FindIdDeclarations updateIds; m_idsRevision = document()->revision(); m_ids = updateIds(doc->program()); if (doc->isParsedCorrectly()) { FindDeclarations findDeclarations; m_declarations = findDeclarations(doc->program()); QStringList items; items.append(tr("