/************************************************************************** ** ** This file is part of Qt Creator ** ** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). ** ** Contact: Qt Software Information (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 qt-sales@nokia.com. ** **************************************************************************/ #include "duieditor.h" #include "duieditorconstants.h" #include "duihighlighter.h" #include "duieditorplugin.h" #include "parser/javascriptengine_p.h" #include "parser/javascriptparser_p.h" #include "parser/javascriptlexer_p.h" #include "parser/javascriptnodepool_p.h" #include "parser/javascriptastvisitor_p.h" #include "parser/javascriptast_p.h" #include #include #include #include #include #include #include #include #include #include #include enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 250 }; using namespace JavaScript; using namespace JavaScript::AST; namespace DuiEditor { namespace Internal { class IdDeclarations: 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); } } } } 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->name) decl.text.append(node->name->asString()); 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->name) decl.text.append(node->name->asString()); else decl.text.append(QLatin1Char('?')); _declarations.append(decl); return true; // search for more bindings } virtual void endVisit(AST::UiObjectBinding *) { --_depth; } 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; } }; class HighlightBindings: protected Visitor { public: HighlightBindings(QTextDocument *doc) : _doc(doc) { } void setFormat(const QTextCharFormat &format) { _format = format; } QList operator()(AST::Node *node) { _selections.clear(); accept(node); return _selections; } protected: using Visitor::visit; void accept(AST::Node *node) { AST::Node::acceptChild(node, this); } void highlight(int begin, int end) { QTextCursor cursor(_doc); cursor.setPosition(begin); cursor.setPosition(end, QTextCursor::KeepAnchor); QTextEdit::ExtraSelection sel; sel.cursor = cursor; sel.format = _format; _selections.append(sel); } void highlight(AST::UiQualifiedId *id) { for (; id; id = id->next) { highlight(id->identifierToken.begin(), id->identifierToken.end()); } } virtual bool visit(AST::UiScriptBinding *node) { highlight(node->qualifiedId); return false; // there's no need to visit the JS statement, we can't find bindings there. } virtual bool visit(AST::UiObjectBinding *node) { highlight(node->qualifiedId); return true; // search for more bindings } virtual bool visit(AST::UiArrayBinding *node) { highlight(node->qualifiedId); return true; // search for more bindings } private: QTextDocument *_doc; QTextCharFormat _format; QList _selections; }; 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) { setParenthesesMatchingEnabled(true); setMarksVisible(true); setCodeFoldingSupported(true); setCodeFoldingVisible(true); setMimeType(DuiEditor::Constants::C_DUIEDITOR_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 DuiHighlighter); } 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()); DuiEditorPlugin::instance()->initializeEditor(newEditor); return newEditor->editableInterface(); } const char *ScriptEditorEditable::kind() const { return DuiEditor::Constants::C_DUIEDITOR; } 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(); const QString code = toPlainText(); JavaScriptParser parser; JavaScriptEnginePrivate driver; NodePool nodePool(fileName, &driver); driver.setNodePool(&nodePool); Lexer lexer(&driver); lexer.setCode(code, /*line = */ 1); driver.setLexer(&lexer); bool parsed = parser.parse(&driver); if (parsed) { IdDeclarations updateIds; m_ids = updateIds(parser.ast()); if (DuiHighlighter *highlighter = qobject_cast(baseTextDocument()->syntaxHighlighter())) { HighlightBindings highlightIds(document()); highlightIds.setFormat(highlighter->labelTextCharFormat()); setExtraSelections(CodeSemanticsSelection, highlightIds(parser.ast())); } FindDeclarations findDeclarations; m_declarations = findDeclarations(parser.ast()); m_words.clear(); foreach (const JavaScriptNameIdImpl &id, driver.literals()) m_words.append(id.asString()); QStringList items; items.append(tr("