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);