diff --git a/src/libs/qmljs/qmljs-lib.pri b/src/libs/qmljs/qmljs-lib.pri index c0a786c56cc66cabdf180ddd71e33f5e2949afff..a5e154a0eb0c285468aa1c76519ccba1e07e7847 100644 --- a/src/libs/qmljs/qmljs-lib.pri +++ b/src/libs/qmljs/qmljs-lib.pri @@ -16,7 +16,8 @@ HEADERS += \ $$PWD/qmljsdocument.h \ $$PWD/qmljsscanner.h \ $$PWD/qmljsinterpreter.h \ - $$PWD/qmljslink.h + $$PWD/qmljslink.h \ + $$PWD/qmljscheck.h SOURCES += \ $$PWD/qmljsbind.cpp \ @@ -25,7 +26,8 @@ SOURCES += \ $$PWD/qmljsscanner.cpp \ $$PWD/qmljsinterpreter.cpp \ $$PWD/qmljsmetatypesystem.cpp \ - $$PWD/qmljslink.cpp + $$PWD/qmljslink.cpp \ + $$PWD/qmljscheck.cpp contains(QT_CONFIG, declarative) { QT += declarative diff --git a/src/libs/qmljs/qmljscheck.cpp b/src/libs/qmljs/qmljscheck.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7a4108cf57b67e210d65fa997e2ba183be1081d1 --- /dev/null +++ b/src/libs/qmljs/qmljscheck.cpp @@ -0,0 +1,140 @@ +/************************************************************************** +** +** 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 "qmljscheck.h" +#include "qmljsbind.h" +#include "qmljsinterpreter.h" +#include "parser/qmljsast_p.h" + +#include <QtCore/QDebug> + +using namespace QmlJS; +using namespace QmlJS::AST; +using namespace QmlJS::Interpreter; + +Check::Check(Document::Ptr doc, const Snapshot &snapshot) + : _doc(doc) + , _snapshot(snapshot) + , _context(&_engine) + , _link(&_context, doc, snapshot) +{ +} + +Check::~Check() +{ +} + +QList<DiagnosticMessage> Check::operator()() +{ + _messages.clear(); + Node::accept(_doc->ast(), this); + return _messages; +} + +bool Check::visit(UiProgram *ast) +{ + // build the initial scope chain + if (ast->members && ast->members->member) + _link.scopeChainAt(_doc, ast->members->member); + + return true; +} + +bool Check::visit(UiObjectDefinition *ast) +{ + const ObjectValue *oldScopeObject = _context.qmlScopeObject(); + _context.setQmlScopeObject(_doc->bind()->findQmlObject(ast)); + + Node::accept(ast->initializer, this); + + _context.setQmlScopeObject(oldScopeObject); + return false; +} + +bool Check::visit(UiObjectBinding *ast) +{ + checkScopeObjectMember(ast->qualifiedId); + + const ObjectValue *oldScopeObject = _context.qmlScopeObject(); + _context.setQmlScopeObject(_doc->bind()->findQmlObject(ast)); + + Node::accept(ast->initializer, this); + + _context.setQmlScopeObject(oldScopeObject); + return false; +} + +bool Check::visit(UiScriptBinding *ast) +{ + checkScopeObjectMember(ast->qualifiedId); + + return true; +} + +bool Check::visit(UiArrayBinding *ast) +{ + checkScopeObjectMember(ast->qualifiedId); + + return true; +} + +void Check::checkScopeObjectMember(const AST::UiQualifiedId *id) +{ + const ObjectValue *scopeObject = _context.qmlScopeObject(); + + if (! id) + return; // ### error? + + const QString propertyName = id->name->asString(); + + if (propertyName == QLatin1String("id") && ! id->next) + return; + + // attached properties + if (! propertyName.isEmpty() && propertyName[0].isUpper()) + scopeObject = _context.typeEnvironment(_doc.data()); + + const Value *value = scopeObject->lookupMember(propertyName, &_context); + if (!value) { + error(id->identifierToken, + QString("'%1' is not a valid property name").arg(propertyName)); + } + + // ### check for rest of qualifiedId +} + +void Check::error(const AST::SourceLocation &loc, const QString &message) +{ + _messages.append(DiagnosticMessage(DiagnosticMessage::Error, loc, message)); +} + +void Check::warning(const AST::SourceLocation &loc, const QString &message) +{ + _messages.append(DiagnosticMessage(DiagnosticMessage::Warning, loc, message)); +} diff --git a/src/libs/qmljs/qmljscheck.h b/src/libs/qmljs/qmljscheck.h new file mode 100644 index 0000000000000000000000000000000000000000..7a93ca8f9ef2cf2f41d3c6138d9682def5ef5dae --- /dev/null +++ b/src/libs/qmljs/qmljscheck.h @@ -0,0 +1,73 @@ +/************************************************************************** +** +** 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. +** +**************************************************************************/ + +#ifndef QMLJSCHECK_H +#define QMLJSCHECK_H + +#include <qmljs/qmljsdocument.h> +#include <qmljs/qmljsinterpreter.h> +#include <qmljs/qmljslink.h> +#include <qmljs/parser/qmljsastvisitor_p.h> + +namespace QmlJS { + +class QMLJS_EXPORT Check: protected AST::Visitor +{ +public: + Check(Document::Ptr doc, const Snapshot &snapshot); + virtual ~Check(); + + QList<DiagnosticMessage> operator()(); + +protected: + virtual bool visit(AST::UiProgram *ast); + virtual bool visit(AST::UiObjectDefinition *ast); + virtual bool visit(AST::UiObjectBinding *ast); + virtual bool visit(AST::UiScriptBinding *ast); + virtual bool visit(AST::UiArrayBinding *ast); + +private: + void checkScopeObjectMember(const AST::UiQualifiedId *id); + + void warning(const AST::SourceLocation &loc, const QString &message); + void error(const AST::SourceLocation &loc, const QString &message); + + Document::Ptr _doc; + Snapshot _snapshot; + + Interpreter::Engine _engine; + Interpreter::Context _context; + Link _link; + + QList<DiagnosticMessage> _messages; +}; + +} // namespace QmlJS + +#endif // QMLJSCHECK_H diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp index 8cba05b85834ec68c149d23b5a5a5888ceac1de0..a23843475a412df0fdc38aedb9117c0868d50893 100644 --- a/src/libs/qmljs/qmljsdocument.cpp +++ b/src/libs/qmljs/qmljsdocument.cpp @@ -216,6 +216,19 @@ void Snapshot::insert(const Document::Ptr &document) _documents.insert(document->fileName(), document); } +Document::Ptr Snapshot::documentFromSource(const QString &code, + const QString &fileName) const +{ + Document::Ptr newDoc = Document::create(fileName); + + if (Document::Ptr thisDocument = document(fileName)) { + newDoc->_documentRevision = thisDocument->_documentRevision; + } + + newDoc->setSource(code); + return newDoc; +} + QList<Document::Ptr> Snapshot::importedDocuments(const Document::Ptr &doc, const QString &importPath) const { // ### TODO: maybe we should add all imported documents in the parse Document::parse() method, regardless of whether they're in the path or not. diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h index 14b669910d200d84da3c7b32de998a859d87e8b8..99c01fed53555f294e08317674e2b29da99fcba0 100644 --- a/src/libs/qmljs/qmljsdocument.h +++ b/src/libs/qmljs/qmljsdocument.h @@ -41,6 +41,7 @@ namespace QmlJS { class Bind; +class Snapshot; class QMLJS_EXPORT Document { @@ -96,6 +97,9 @@ private: QString _path; QString _componentName; QString _source; + + // for documentFromSource + friend class Snapshot; }; class QMLJS_EXPORT Snapshot @@ -118,6 +122,9 @@ public: Document::Ptr document(const QString &fileName) const { return _documents.value(fileName); } + Document::Ptr documentFromSource(const QString &code, + const QString &fileName) const; + QList<Document::Ptr> importedDocuments(const Document::Ptr &doc, const QString &importPath) const; QMap<QString, Document::Ptr> componentsDefinedByImportedDocuments(const Document::Ptr &doc, const QString &importPath) const; }; diff --git a/src/libs/qmljs/qmljsinterpreter.cpp b/src/libs/qmljs/qmljsinterpreter.cpp index 1cdd15bd4eac07a05c35a2761e230b2b39c54214..9948dc67ca9560b1395c06365104dff1046d0402 100644 --- a/src/libs/qmljs/qmljsinterpreter.cpp +++ b/src/libs/qmljs/qmljsinterpreter.cpp @@ -686,7 +686,9 @@ void StringValue::accept(ValueVisitor *visitor) const Context::Context(Engine *engine) : _engine(engine), - _lookupMode(JSLookup) + _lookupMode(JSLookup), + _qmlScopeObjectIndex(-1), + _qmlScopeObjectSet(false) { } @@ -739,6 +741,39 @@ void Context::pushScope(const ObjectValue *object) void Context::popScope() { _scopeChain.removeLast(); + if (_scopeChain.length() <= _qmlScopeObjectIndex) + _qmlScopeObjectSet = false; +} + +// Marks this to be the location where a scope object can be inserted. +void Context::markQmlScopeObject() +{ + _qmlScopeObjectIndex = _scopeChain.length(); +} + +// Sets or inserts the scope object if scopeObject != 0, removes it otherwise. +void Context::setQmlScopeObject(const ObjectValue *scopeObject) +{ + if (_qmlScopeObjectSet) { + if (scopeObject == 0) { + _scopeChain.removeAt(_qmlScopeObjectIndex); + _qmlScopeObjectSet = false; + } else { + _scopeChain[_qmlScopeObjectIndex] = scopeObject; + } + } else if (scopeObject != 0 && _scopeChain.length() >= _qmlScopeObjectIndex) { + _scopeChain.insert(_qmlScopeObjectIndex, scopeObject); + _qmlScopeObjectSet = true; + } +} + +// Gets the scope object, if set. Returns 0 otherwise. +const ObjectValue *Context::qmlScopeObject() const +{ + if (!_qmlScopeObjectSet) + return 0; + else + return _scopeChain[_qmlScopeObjectIndex]; } const Value *Context::lookup(const QString &name) diff --git a/src/libs/qmljs/qmljsinterpreter.h b/src/libs/qmljs/qmljsinterpreter.h index b07a14915137bcac113fd43096b3df83c53a7a7b..8e394c7339dfc0b36d60111901ab9fc84056433c 100644 --- a/src/libs/qmljs/qmljsinterpreter.h +++ b/src/libs/qmljs/qmljsinterpreter.h @@ -241,6 +241,10 @@ public: void pushScope(const ObjectValue *object); void popScope(); + void markQmlScopeObject(); + void setQmlScopeObject(const ObjectValue *scopeObject); + const ObjectValue *qmlScopeObject() const; + const Value *lookup(const QString &name); const ObjectValue *lookupType(const Document *doc, AST::UiQualifiedId *qmlTypeName); @@ -253,8 +257,10 @@ private: Engine *_engine; LookupMode _lookupMode; QHash<const ObjectValue *, Properties> _properties; - ScopeChain _scopeChain; QHash<const Document *, const ObjectValue *> _typeEnvironments; + ScopeChain _scopeChain; + int _qmlScopeObjectIndex; + bool _qmlScopeObjectSet; }; class QMLJS_EXPORT Reference: public Value diff --git a/src/libs/qmljs/qmljslink.cpp b/src/libs/qmljs/qmljslink.cpp index 18da93a386c56d876ce18466cc44453a85b1b879..f67c428d37c9f739f7ca7688da59db01ec80cb01 100644 --- a/src/libs/qmljs/qmljslink.cpp +++ b/src/libs/qmljs/qmljslink.cpp @@ -102,8 +102,11 @@ void Link::pushScopeChainForComponent(Document::Ptr doc, QStringList *linkedDocs if (bind->rootObjectValue()) _context->pushScope(bind->rootObjectValue()); - if (scopeObject && scopeObject != bind->rootObjectValue()) - _context->pushScope(scopeObject); + if (scopeObject) { + _context->markQmlScopeObject(); + if (scopeObject != bind->rootObjectValue()) + _context->setQmlScopeObject(scopeObject); + } const QStringList &includedScripts = bind->includedScripts(); for (int index = includedScripts.size() - 1; index != -1; --index) { diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index 7a170075ba165b1302a07b35b930a331045b2e88..6360da2a87ea1960cc83da51cc0a437108fd5b64 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -36,6 +36,7 @@ #include <qmljs/qmljsindenter.h> #include <qmljs/qmljsinterpreter.h> #include <qmljs/qmljsbind.h> +#include <qmljs/qmljscheck.h> #include <qmljs/qmljsevaluate.h> #include <qmljs/qmljsdocument.h> #include <qmljs/parser/qmljsastvisitor_p.h> @@ -563,6 +564,11 @@ QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) : m_methodCombo(0), m_modelManager(0) { + qRegisterMetaType<SemanticInfo>("SemanticInfo"); + + m_semanticHighlighter = new SemanticHighlighter(this); + m_semanticHighlighter->start(); + setParenthesesMatchingEnabled(true); setMarksVisible(true); setCodeFoldingSupported(true); @@ -589,10 +595,15 @@ QmlJSTextEditor::QmlJSTextEditor(QWidget *parent) : connect(m_modelManager, SIGNAL(documentUpdated(QmlJS::Document::Ptr)), this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr))); } + + connect(m_semanticHighlighter, SIGNAL(changed(SemanticInfo)), + this, SLOT(updateSemanticInfo(SemanticInfo))); } QmlJSTextEditor::~QmlJSTextEditor() { + m_semanticHighlighter->abort(); + m_semanticHighlighter->wait(); } SemanticInfo QmlJSTextEditor::semanticInfo() const @@ -660,62 +671,9 @@ void QmlJSTextEditor::onDocumentUpdated(QmlJS::Document::Ptr doc) if (doc->ast()) { // got a correctly parsed (or recovered) file. - // create the ranges and update the semantic info. - CreateRanges createRanges; - SemanticInfo sem; - sem.snapshot = m_modelManager->snapshot(); - sem.document = doc; - sem.ranges = createRanges(document(), doc); - - // Refresh the ids - FindIdDeclarations updateIds; - sem.idLocations = updateIds(doc); - - if (doc->isParsedCorrectly()) { - FindDeclarations findDeclarations; - sem.declarations = findDeclarations(doc->ast()); - - QStringList items; - items.append(tr("<Select Symbol>")); - - foreach (Declaration decl, sem.declarations) - items.append(decl.text); - - m_methodCombo->clear(); - m_methodCombo->addItems(items); - updateMethodBoxIndex(); - } - - m_semanticInfo = sem; + const SemanticHighlighter::Source source = currentSource(/*force = */ true); + m_semanticHighlighter->rehighlight(source); } - - QList<QTextEdit::ExtraSelection> selections; - - foreach (const DiagnosticMessage &d, doc->diagnosticMessages()) { - if (d.isWarning()) - continue; - - const int line = d.loc.startLine; - const int column = qMax(1U, d.loc.startColumn); - - QTextEdit::ExtraSelection sel; - QTextCursor c(document()->findBlockByNumber(line - 1)); - sel.cursor = c; - - sel.cursor.setPosition(c.position() + column - 1); - if (sel.cursor.atBlockEnd()) - sel.cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); - else - sel.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); - - sel.format.setUnderlineColor(Qt::red); - sel.format.setUnderlineStyle(QTextCharFormat::WaveUnderline); - sel.format.setToolTip(d.message); - - selections.append(sel); - } - - setExtraSelections(CodeWarningsSelection, selections); } void QmlJSTextEditor::jumpToMethod(int index) @@ -1150,3 +1108,196 @@ QString QmlJSTextEditor::insertParagraphSeparator(const QTextCursor &) const return QLatin1String("}\n"); } +static void appendExtraSelectionsForMessages( + QList<QTextEdit::ExtraSelection> *selections, + const QList<DiagnosticMessage> &messages, + const QTextDocument *document) +{ + foreach (const DiagnosticMessage &d, messages) { + if (d.isWarning()) + continue; + + const int line = d.loc.startLine; + const int column = qMax(1U, d.loc.startColumn); + + QTextEdit::ExtraSelection sel; + QTextCursor c(document->findBlockByNumber(line - 1)); + sel.cursor = c; + + sel.cursor.setPosition(c.position() + column - 1); + if (sel.cursor.atBlockEnd()) + sel.cursor.movePosition(QTextCursor::StartOfWord, QTextCursor::KeepAnchor); + else + sel.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + + sel.format.setUnderlineColor(Qt::red); + sel.format.setUnderlineStyle(QTextCharFormat::WaveUnderline); + sel.format.setToolTip(d.message); + + selections->append(sel); + } +} + +void QmlJSTextEditor::semanticRehighlight() +{ + m_semanticHighlighter->rehighlight(currentSource()); +} + +void QmlJSTextEditor::updateSemanticInfo(const SemanticInfo &semanticInfo) +{ + if (semanticInfo.revision() != document()->revision()) { + // got outdated semantic info + semanticRehighlight(); + return; + } + + m_semanticInfo = semanticInfo; + Document::Ptr doc = semanticInfo.document; + + // create the ranges + CreateRanges createRanges; + m_semanticInfo.ranges = createRanges(document(), doc); + + // Refresh the ids + FindIdDeclarations updateIds; + m_semanticInfo.idLocations = updateIds(doc); + + if (doc->isParsedCorrectly()) { + FindDeclarations findDeclarations; + m_semanticInfo.declarations = findDeclarations(doc->ast()); + + QStringList items; + items.append(tr("<Select Symbol>")); + + foreach (Declaration decl, m_semanticInfo.declarations) + items.append(decl.text); + + m_methodCombo->clear(); + m_methodCombo->addItems(items); + updateMethodBoxIndex(); + } + + // update warning/error extra selections + QList<QTextEdit::ExtraSelection> selections; + appendExtraSelectionsForMessages(&selections, doc->diagnosticMessages(), document()); + appendExtraSelectionsForMessages(&selections, m_semanticInfo.semanticMessages, document()); + setExtraSelections(CodeWarningsSelection, selections); +} + +SemanticHighlighter::Source QmlJSTextEditor::currentSource(bool force) +{ + int line = 0, column = 0; + convertPosition(position(), &line, &column); + + const Snapshot snapshot = m_modelManager->snapshot(); + const QString fileName = file()->fileName(); + + QString code; + if (force || m_semanticInfo.revision() != document()->revision()) + code = toPlainText(); // get the source code only when needed. + + const unsigned revision = document()->revision(); + SemanticHighlighter::Source source(snapshot, fileName, code, + line, column, revision); + source.force = force; + return source; +} + +SemanticHighlighter::SemanticHighlighter(QObject *parent) + : QThread(parent), + m_done(false) +{ +} + +SemanticHighlighter::~SemanticHighlighter() +{ +} + +void SemanticHighlighter::abort() +{ + QMutexLocker locker(&m_mutex); + m_done = true; + m_condition.wakeOne(); +} + +void SemanticHighlighter::rehighlight(const Source &source) +{ + QMutexLocker locker(&m_mutex); + m_source = source; + m_condition.wakeOne(); +} + +bool SemanticHighlighter::isOutdated() +{ + QMutexLocker locker(&m_mutex); + const bool outdated = ! m_source.fileName.isEmpty() || m_done; + return outdated; +} + +void SemanticHighlighter::run() +{ + setPriority(QThread::IdlePriority); + + forever { + m_mutex.lock(); + + while (! (m_done || ! m_source.fileName.isEmpty())) + m_condition.wait(&m_mutex); + + const bool done = m_done; + const Source source = m_source; + m_source.clear(); + + m_mutex.unlock(); + + if (done) + break; + + const SemanticInfo info = semanticInfo(source); + + if (! isOutdated()) { + m_mutex.lock(); + m_lastSemanticInfo = info; + m_mutex.unlock(); + + emit changed(info); + } + } +} + +SemanticInfo SemanticHighlighter::semanticInfo(const Source &source) +{ + m_mutex.lock(); + const int revision = m_lastSemanticInfo.revision(); + m_mutex.unlock(); + + Snapshot snapshot; + Document::Ptr doc; + + if (! source.force && revision == source.revision) { + m_mutex.lock(); + snapshot = m_lastSemanticInfo.snapshot; + doc = m_lastSemanticInfo.document; + m_mutex.unlock(); + } + + if (! doc) { + snapshot = source.snapshot; + doc = snapshot.documentFromSource(source.code, source.fileName); + + // ### This doesn't really work: what if snapshot doesn't have the doc? + if (snapshot.document(source.fileName)->qmlProgram()) + doc->parseQml(); + else if (snapshot.document(source.fileName)->jsProgram()) + doc->parseJavaScript(); + } + + SemanticInfo semanticInfo; + semanticInfo.snapshot = snapshot; + semanticInfo.document = doc; + + Check checker(doc, snapshot); + semanticInfo.semanticMessages = checker(); + + return semanticInfo; +} diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h index a204585a56aaae75d088e50deeed39b403944389..956684e1d2a9015ef72fbd2a8fc68f25594b2518 100644 --- a/src/plugins/qmljseditor/qmljseditor.h +++ b/src/plugins/qmljseditor/qmljseditor.h @@ -34,6 +34,10 @@ #include <qmljs/qmljsscanner.h> #include <texteditor/basetexteditor.h> +#include <QtCore/QWaitCondition> +#include <QtCore/QMutex> +#include <QtCore/QThread> + QT_BEGIN_NAMESPACE class QComboBox; class QTimer; @@ -117,6 +121,75 @@ public: // attributes QList<Range> ranges; QHash<QString, QList<QmlJS::AST::SourceLocation> > idLocations; QList<Declaration> declarations; + + // these are in addition to the parser messages in the document + QList<QmlJS::DiagnosticMessage> semanticMessages; +}; + +class SemanticHighlighter: public QThread +{ + Q_OBJECT + +public: + SemanticHighlighter(QObject *parent = 0); + virtual ~SemanticHighlighter(); + + void abort(); + + struct Source + { + QmlJS::Snapshot snapshot; + QString fileName; + QString code; + int line; + int column; + int revision; + bool force; + + Source() + : line(0), column(0), revision(0), force(false) + { } + + Source(const QmlJS::Snapshot &snapshot, + const QString &fileName, + const QString &code, + int line, int column, + int revision) + : snapshot(snapshot), fileName(fileName), + code(code), line(line), column(column), + revision(revision), force(false) + { } + + void clear() + { + snapshot = QmlJS::Snapshot(); + fileName.clear(); + code.clear(); + line = 0; + column = 0; + revision = 0; + force = false; + } + }; + + void rehighlight(const Source &source); + +Q_SIGNALS: + void changed(const SemanticInfo &semanticInfo); + +protected: + virtual void run(); + +private: + bool isOutdated(); + SemanticInfo semanticInfo(const Source &source); + +private: + QMutex m_mutex; + QWaitCondition m_condition; + bool m_done; + Source m_source; + SemanticInfo m_lastSemanticInfo; }; class QmlJSTextEditor : public TextEditor::BaseTextEditor @@ -154,6 +227,9 @@ private slots: // refactoring ops void renameIdUnderCursor(); + void semanticRehighlight(); + void updateSemanticInfo(const SemanticInfo &semanticInfo); + protected: void contextMenuEvent(QContextMenuEvent *e); TextEditor::BaseTextEditorEditable *createEditableInterface(); @@ -173,6 +249,8 @@ private: QString wordUnderCursor() const; + SemanticHighlighter::Source currentSource(bool force = false); + const Context m_context; QTimer *m_updateDocumentTimer; @@ -183,6 +261,7 @@ private: QTextCharFormat m_occurrencesUnusedFormat; QTextCharFormat m_occurrenceRenameFormat; + SemanticHighlighter *m_semanticHighlighter; SemanticInfo m_semanticInfo; }; diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp index e1103325dcb33dd641e73e00dc0ac6f90f7a8c82..9f06b6687827d9b3d15f90132c9b996089742382 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.cpp +++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp @@ -165,7 +165,7 @@ void HoverHandler::updateHelpIdAndTooltip(TextEditor::ITextEditor *editor, int p } QString symbolName = QLatin1String("<unknown>"); - if (m_helpId.isEmpty()) { + if (m_helpId.isEmpty() && m_toolTip.isEmpty()) { AST::Node *node = semanticInfo.nodeUnderCursor(pos); if (node && !(AST::cast<AST::StringLiteral *>(node) != 0 || AST::cast<AST::NumericLiteral *>(node) != 0)) { AST::Node *declaringMember = semanticInfo.declaringMember(pos);