/************************************************************************** ** ** 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 "qmleditor.h" #include "qmleditorconstants.h" #include "qmlhighlighter.h" #include "qmleditorplugin.h" #include "qmldocument.h" #include "qmlmodelmanager.h" #include "qmljsastvisitor_p.h" #include "qmljsast_p.h" #include "qmljsengine_p.h" #include "qmlexpressionundercursor.h" #include "qmllookupcontext.h" #include "qmlresolveexpression.h" #include "rewriter_p.h" #include "idcollector.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 250 }; using namespace QmlJS; using namespace QmlJS::AST; namespace QmlEditor { namespace Internal { class FindWords: protected Visitor { public: QStringList operator()(AST::Node *node) { _words.clear(); accept(node); return QStringList(_words.toList()); } protected: void accept(AST::Node *node) { AST::Node::acceptChild(node, this); } using Visitor::visit; using Visitor::endVisit; void addWords(AST::UiQualifiedId *id) { for (; id; id = id->next) { if (id->name) _words.insert(id->name->asString()); } } virtual bool visit(AST::UiPublicMember *node) { if (node->name) _words.insert(node->name->asString()); return true; } virtual bool visit(AST::UiQualifiedId *node) { if (node->name) _words.insert(node->name->asString()); return true; } virtual bool visit(AST::IdentifierExpression *node) { if (node->name) _words.insert(node->name->asString()); return true; } virtual bool visit(AST::FieldMemberExpression *node) { if (node->name) _words.insert(node->name->asString()); return true; } virtual bool visit(AST::FunctionExpression *node) { if (node->name) _words.insert(node->name->asString()); for (AST::FormalParameterList *it = node->formals; it; it = it->next) { if (it->name) _words.insert(it->name->asString()); } return true; } virtual bool visit(AST::FunctionDeclaration *node) { if (node->name) _words.insert(node->name->asString()); for (AST::FormalParameterList *it = node->formals; it; it = it->next) { if (it->name) _words.insert(it->name->asString()); } return true; } virtual bool visit(AST::VariableDeclaration *node) { if (node->name) _words.insert(node->name->asString()); return true; } private: QSet _words; }; 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 }; ScriptEditorEditable::ScriptEditorEditable(ScriptEditor *editor, const QList &context) : BaseTextEditorEditable(editor), m_context(context) { } ScriptEditor::ScriptEditor(const Context &context, QWidget *parent) : TextEditor::BaseTextEditor(parent), m_context(context), m_methodCombo(0), m_modelManager(0) { setParenthesesMatchingEnabled(true); setMarksVisible(true); setCodeFoldingSupported(true); setCodeFoldingVisible(true); setMimeType(QmlEditor::Constants::QMLEDITOR_MIMETYPE); m_updateDocumentTimer = new QTimer(this); m_updateDocumentTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL); m_updateDocumentTimer->setSingleShot(true); connect(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow())); connect(this, SIGNAL(textChanged()), this, SLOT(updateDocument())); baseTextDocument()->setSyntaxHighlighter(new QmlHighlighter); m_modelManager = ExtensionSystem::PluginManager::instance()->getObject(); if (m_modelManager) { connect(m_modelManager, SIGNAL(documentUpdated(QmlDocument::Ptr)), this, SLOT(onDocumentUpdated(QmlDocument::Ptr))); } } ScriptEditor::~ScriptEditor() { } QList ScriptEditor::declarations() const { return m_declarations; } QStringList ScriptEditor::words() const { return m_words; } Core::IEditor *ScriptEditorEditable::duplicate(QWidget *parent) { ScriptEditor *newEditor = new ScriptEditor(m_context, parent); newEditor->duplicateFrom(editor()); QmlEditorPlugin::instance()->initializeEditor(newEditor); return newEditor->editableInterface(); } const char *ScriptEditorEditable::kind() const { return QmlEditor::Constants::C_QMLEDITOR; } ScriptEditor::Context ScriptEditorEditable::context() const { return m_context; } void ScriptEditor::updateDocument() { m_updateDocumentTimer->start(UPDATE_DOCUMENT_DEFAULT_INTERVAL); } void ScriptEditor::updateDocumentNow() { // ### move in the parser thread. m_updateDocumentTimer->stop(); const QString fileName = file()->fileName(); m_modelManager->updateSourceFiles(QStringList() << fileName); } void ScriptEditor::onDocumentUpdated(QmlDocument::Ptr doc) { if (file()->fileName() != doc->fileName()) return; m_document = doc; FindIdDeclarations updateIds; m_ids = updateIds(doc->program()); if (doc->isParsedCorrectly()) { FindDeclarations findDeclarations; m_declarations = findDeclarations(doc->program()); FindWords findWords; m_words = findWords(doc->program()); QStringList items; items.append(tr("