From c9efafcb98cdbed4c265e1de767982a8727bfd87 Mon Sep 17 00:00:00 2001
From: Roberto Raggi <roberto.raggi@nokia.com>
Date: Mon, 25 Jan 2010 14:18:53 +0100
Subject: [PATCH] Introduced ranges and versioning of QML/JS documents.

---
 src/libs/qmljs/qmljsdocument.cpp              | 13 +++-
 src/libs/qmljs/qmljsdocument.h                |  6 +-
 src/plugins/qmljseditor/qmlcodecompletion.cpp | 67 +++---------------
 src/plugins/qmljseditor/qmljseditor.cpp       | 70 ++++++++++++++++---
 src/plugins/qmljseditor/qmljseditor.h         | 34 +++++++--
 src/plugins/qmljseditor/qmlmodelmanager.cpp   | 53 ++++++++------
 src/plugins/qmljseditor/qmlmodelmanager.h     | 11 ++-
 7 files changed, 154 insertions(+), 100 deletions(-)

diff --git a/src/libs/qmljs/qmljsdocument.cpp b/src/libs/qmljs/qmljsdocument.cpp
index 58900fec932..951d9877253 100644
--- a/src/libs/qmljs/qmljsdocument.cpp
+++ b/src/libs/qmljs/qmljsdocument.cpp
@@ -43,8 +43,9 @@ Document::Document(const QString &fileName)
     : _engine(0)
     , _pool(0)
     , _ast(0)
-    , _fileName(fileName)
+    , _documentRevision(0)
     , _parsedCorrectly(false)
+    , _fileName(fileName)
 {
     const int slashIdx = fileName.lastIndexOf('/');
     if (slashIdx != -1)
@@ -101,6 +102,16 @@ void Document::setSource(const QString &source)
     _source = source;
 }
 
+int Document::documentRevision() const
+{
+    return _documentRevision;
+}
+
+void Document::setDocumentRevision(int revision)
+{
+    _documentRevision = revision;
+}
+
 bool Document::parseQml()
 {
     Q_ASSERT(! _engine);
diff --git a/src/libs/qmljs/qmljsdocument.h b/src/libs/qmljs/qmljsdocument.h
index 3ea931746a3..bbbdc2e000a 100644
--- a/src/libs/qmljs/qmljsdocument.h
+++ b/src/libs/qmljs/qmljsdocument.h
@@ -72,6 +72,9 @@ public:
     bool isParsedCorrectly() const
     { return _parsedCorrectly; }
 
+    int documentRevision() const;
+    void setDocumentRevision(int documentRevision);
+
     IdTable ids() const { return _ids; }
 
     QString fileName() const { return _fileName; }
@@ -86,12 +89,13 @@ private:
     QmlJS::Engine *_engine;
     QmlJS::NodePool *_pool;
     QmlJS::AST::Node *_ast;
+    int _documentRevision;
+    bool _parsedCorrectly;
     QList<QmlJS::DiagnosticMessage> _diagnosticMessages;
     QString _fileName;
     QString _path;
     QString _componentName;
     QString _source;
-    bool _parsedCorrectly;
     IdTable _ids;
     QmlJS::Symbol::List _symbols;
 };
diff --git a/src/plugins/qmljseditor/qmlcodecompletion.cpp b/src/plugins/qmljseditor/qmlcodecompletion.cpp
index 2f98abf9563..dfc3ab665e2 100644
--- a/src/plugins/qmljseditor/qmlcodecompletion.cpp
+++ b/src/plugins/qmljseditor/qmlcodecompletion.cpp
@@ -94,41 +94,6 @@ static QIcon iconForColor(const QColor &color)
 
 namespace {
 
-class FindMembers: protected AST::Visitor
-{
-    QList<AST::UiObjectMember *> _members;
-
-public:
-    QList<AST::UiObjectMember *> operator()(Document::Ptr doc)
-    {
-        _members.clear();
-        if (doc && doc->qmlProgram())
-            doc->qmlProgram()->accept(this);
-        return _members;
-    }
-
-protected:
-    using AST::Visitor::visit;
-
-    virtual bool visit(AST::UiArrayBinding *ast)
-    {
-        _members.append(ast);
-        return true;
-    }
-
-    virtual bool visit(AST::UiObjectBinding *ast)
-    {
-        _members.append(ast);
-        return true;
-    }
-
-    virtual bool visit(AST::UiObjectDefinition *ast)
-    {
-        _members.append(ast);
-        return true;
-    }
-};
-
 class ExpressionUnderCursor
 {
     QTextCursor _cursor;
@@ -692,9 +657,11 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
     m_completions.clear();
 
     QmlJS::Snapshot snapshot = m_modelManager->snapshot();
-    Document::Ptr qmlDocument = snapshot.document(fileName);
 
-    const QFileInfo currentFileInfo(qmlDocument->fileName());
+    SemanticInfo semanticInfo = edit->semanticInfo();
+    Document::Ptr qmlDocument = semanticInfo.document;
+
+    const QFileInfo currentFileInfo(fileName);
     const QString currentFilePath = currentFileInfo.absolutePath();
 
     const QIcon componentIcon = iconForColor(Qt::yellow);
@@ -737,27 +704,11 @@ int QmlCodeCompletion::startCompletion(TextEditor::ITextEditable *editor)
     AST::UiObjectMember *declaringMember = 0;
     AST::UiObjectMember *parentMember = 0;
 
-    const int cursorLine = edit->textCursor().blockNumber() + 1;
-    const int cursorColumn = edit->textCursor().columnNumber() + 1;
-
-    FindMembers findMembers;
-    const QList<AST::UiObjectMember *> members = findMembers(qmlDocument);
-    for (int index = 0; index < members.size(); ++index) {
-        AST::UiObjectMember *member = members.at(index);
-
-        AST::SourceLocation pos = member->firstSourceLocation();
-        const int startLine = pos.startLine;
-        const int startColumn = pos.startColumn;
-
-        if (startLine < cursorLine || (startLine == cursorLine && cursorColumn >= startColumn)) {
-            AST::SourceLocation endPos = member->lastSourceLocation();
-            const int endLine = endPos.startLine;
-            const int endColumn = endPos.startColumn + endPos.length;
-
-            if (cursorLine < endLine || (cursorLine == endLine && cursorColumn <= endColumn)) {
-                parentMember = declaringMember;
-                declaringMember = member;
-            }
+    const int cursorPosition = editor->position();
+    foreach (const Range &range, semanticInfo.ranges) {
+        if (cursorPosition >= range.begin.position() && cursorPosition <= range.end.position()) {
+            parentMember = declaringMember;
+            declaringMember = range.ast;
         }
     }
 
diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp
index 81b39c04452..19c68c46630 100644
--- a/src/plugins/qmljseditor/qmljseditor.cpp
+++ b/src/plugins/qmljseditor/qmljseditor.cpp
@@ -76,8 +76,10 @@ enum {
 
 using namespace QmlJS;
 using namespace QmlJS::AST;
+using namespace QmlJSEditor::Internal;
 
 namespace {
+
 int blockBraceDepth(const QTextBlock &block)
 {
     int state = block.userState();
@@ -120,11 +122,6 @@ bool shouldInsertMatchingText(const QTextCursor &tc)
     return shouldInsertMatchingText(doc->characterAt(tc.selectionEnd()));
 }
 
-} // end of anonymous namespace
-
-namespace QmlJSEditor {
-namespace Internal {
-
 class FindIdDeclarations: protected Visitor
 {
 public:
@@ -383,6 +380,54 @@ protected:
     }
 };
 
+class CreateRanges: protected AST::Visitor
+{
+    QTextDocument *_textDocument;
+    QList<Range> _ranges;
+
+public:
+    QList<Range> operator()(QTextDocument *textDocument, Document::Ptr doc)
+    {
+        _textDocument = textDocument;
+        _ranges.clear();
+        if (doc && doc->qmlProgram() != 0)
+            doc->qmlProgram()->accept(this);
+        return _ranges;
+    }
+
+protected:
+    using AST::Visitor::visit;
+
+    virtual bool visit(AST::UiObjectBinding *ast)
+    {
+        _ranges.append(createRange(ast));
+        return true;
+    }
+
+    virtual bool visit(AST::UiObjectDefinition *ast)
+    {
+        _ranges.append(createRange(ast));
+        return true;
+    }
+
+    Range createRange(AST::UiObjectMember *ast)
+    {
+        Range range;
+
+        range.ast = ast;
+
+        range.begin = QTextCursor(_textDocument);
+        range.begin.setPosition(ast->firstSourceLocation().begin());
+
+        range.end = QTextCursor(_textDocument);
+        range.end.setPosition(ast->lastSourceLocation().end());
+        return range;
+    }
+};
+
+} // end of anonymous namespace
+
+
 QmlJSEditorEditable::QmlJSEditorEditable(QmlJSTextEditor *editor)
     : BaseTextEditorEditable(editor)
 {
@@ -481,13 +526,23 @@ void QmlJSTextEditor::onDocumentUpdated(QmlJS::Document::Ptr doc)
     if (file()->fileName() != doc->fileName())
         return;
 
-    m_document = doc;
+    if (doc->documentRevision() != document()->revision()) {
+        // got an outdated document.
+        return;
+    }
 
     FindIdDeclarations updateIds;
     m_idsRevision = document()->revision();
     m_ids = updateIds(doc->qmlProgram());
 
     if (doc->isParsedCorrectly()) {
+        // create the ranges
+        CreateRanges createRanges;
+        SemanticInfo sem;
+        sem.document = doc;
+        sem.ranges = createRanges(document(), doc);
+        m_semanticInfo = sem;
+
         FindDeclarations findDeclarations;
         m_declarations = findDeclarations(doc->ast());
 
@@ -956,6 +1011,3 @@ QString QmlJSTextEditor::insertParagraphSeparator(const QTextCursor &) const
 {
     return QLatin1String("}\n");
 }
-
-} // namespace Internal
-} // namespace QmlJSEditor
diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h
index a9915a3879e..d6970c969a5 100644
--- a/src/plugins/qmljseditor/qmljseditor.h
+++ b/src/plugins/qmljseditor/qmljseditor.h
@@ -91,6 +91,27 @@ struct Declaration
     { }
 };
 
+class Range
+{
+public:
+    Range(): ast(0) {}
+
+public: // attributes
+    QmlJS::AST::UiObjectMember *ast;
+    QTextCursor begin;
+    QTextCursor end;
+};
+
+class SemanticInfo
+{
+public:
+    SemanticInfo() {}
+
+public: // attributes
+    QmlJS::Document::Ptr document;
+    QList<Range> ranges;
+};
+
 class QmlJSTextEditor : public TextEditor::BaseTextEditor
 {
     Q_OBJECT
@@ -109,7 +130,7 @@ public:
 
     virtual void unCommentSelection();
 
-    QmlJS::Document::Ptr qmlDocument() const { return m_document; }
+    SemanticInfo semanticInfo() const { return m_semanticInfo; }
 
 public slots:
     virtual void setFontSettings(const TextEditor::FontSettings &);
@@ -154,14 +175,15 @@ private:
     QTimer *m_updateDocumentTimer;
     QTimer *m_updateUsesTimer;
     QComboBox *m_methodCombo;
-    QList<Declaration> m_declarations;
-    QMap<QString, QList<QmlJS::AST::SourceLocation> > m_ids; // ### use QMultiMap
-    int m_idsRevision;
-    QList<QmlJS::DiagnosticMessage> m_diagnosticMessages;
-    QmlJS::Document::Ptr m_document;
+    QList<Declaration> m_declarations; // ### remove me
+    QMap<QString, QList<QmlJS::AST::SourceLocation> > m_ids; // ### remove me
+    int m_idsRevision; // ### remove me
+    QList<QmlJS::DiagnosticMessage> m_diagnosticMessages; // ### remove me
     QmlModelManagerInterface *m_modelManager;
     QmlJS::TypeSystem *m_typeSystem;
     QTextCharFormat m_occurrencesFormat;
+
+    SemanticInfo m_semanticInfo;
 };
 
 } // namespace Internal
diff --git a/src/plugins/qmljseditor/qmlmodelmanager.cpp b/src/plugins/qmljseditor/qmlmodelmanager.cpp
index 3c4f7033836..6efbc801f15 100644
--- a/src/plugins/qmljseditor/qmlmodelmanager.cpp
+++ b/src/plugins/qmljseditor/qmlmodelmanager.cpp
@@ -29,6 +29,7 @@
 
 #include "qmljseditorconstants.h"
 #include "qmlmodelmanager.h"
+#include "qmljseditor.h"
 
 #include <coreplugin/icore.h>
 #include <coreplugin/editormanager/editormanager.h>
@@ -42,6 +43,7 @@
 #include <qtconcurrent/runextensions.h>
 #include <QTextStream>
 
+using namespace QmlJS;
 using namespace QmlJSEditor;
 using namespace QmlJSEditor::Internal;
 
@@ -57,7 +59,7 @@ QmlModelManager::QmlModelManager(QObject *parent):
             this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr)));
 }
 
-QmlJS::Snapshot QmlModelManager::snapshot() const
+Snapshot QmlModelManager::snapshot() const
 {
     QMutexLocker locker(&m_mutex);
 
@@ -75,7 +77,7 @@ QFuture<void> QmlModelManager::refreshSourceFiles(const QStringList &sourceFiles
         return QFuture<void>();
     }
 
-    const QMap<QString, QString> workingCopy = buildWorkingCopyList();
+    const QMap<QString, WorkingCopy> workingCopy = buildWorkingCopyList();
 
     QFuture<void> result = QtConcurrent::run(&QmlModelManager::parse,
                                               workingCopy, sourceFiles,
@@ -102,26 +104,29 @@ QFuture<void> QmlModelManager::refreshSourceFiles(const QStringList &sourceFiles
     return result;
 }
 
-QMap<QString, QString> QmlModelManager::buildWorkingCopyList()
+QMap<QString, QmlModelManager::WorkingCopy> QmlModelManager::buildWorkingCopyList()
 {
-    QMap<QString, QString> workingCopy;
+    QMap<QString, WorkingCopy> workingCopy;
     Core::EditorManager *editorManager = m_core->editorManager();
 
     foreach (Core::IEditor *editor, editorManager->openedEditors()) {
         const QString key = editor->file()->fileName();
 
         if (TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor*>(editor)) {
-            workingCopy[key] = textEditor->contents();
+            if (QmlJSTextEditor *ed = qobject_cast<QmlJSTextEditor *>(textEditor->widget())) {
+                workingCopy[key].contents = ed->toPlainText();
+                workingCopy[key].documentRevision = ed->document()->revision();
+            }
         }
     }
 
     return workingCopy;
 }
 
-void QmlModelManager::emitDocumentUpdated(QmlJS::Document::Ptr doc)
+void QmlModelManager::emitDocumentUpdated(Document::Ptr doc)
 { emit documentUpdated(doc); }
 
-void QmlModelManager::onDocumentUpdated(QmlJS::Document::Ptr doc)
+void QmlModelManager::onDocumentUpdated(Document::Ptr doc)
 {
     QMutexLocker locker(&m_mutex);
 
@@ -129,10 +134,14 @@ void QmlModelManager::onDocumentUpdated(QmlJS::Document::Ptr doc)
 }
 
 void QmlModelManager::parse(QFutureInterface<void> &future,
-                            QMap<QString, QString> workingCopy,
+                            QMap<QString, WorkingCopy> workingCopy,
                             QStringList files,
                             QmlModelManager *modelManager)
 {
+    Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();
+    Core::MimeType jsSourceTy = db->findByType(QLatin1String("application/javascript"));
+    Core::MimeType qmlSourceTy = db->findByType(QLatin1String("application/x-qml"));
+
     future.setProgressRange(0, files.size());
 
     for (int i = 0; i < files.size(); ++i) {
@@ -140,9 +149,12 @@ void QmlModelManager::parse(QFutureInterface<void> &future,
 
         const QString fileName = files.at(i);
         QString contents;
+        int documentRevision = 0;
 
         if (workingCopy.contains(fileName)) {
-            contents = workingCopy.value(fileName);
+            WorkingCopy wc = workingCopy.value(fileName);
+            contents = wc.contents;
+            documentRevision = wc.documentRevision;
         } else {
             QFile inFile(fileName);
 
@@ -153,23 +165,18 @@ void QmlModelManager::parse(QFutureInterface<void> &future,
             }
         }
 
-        QmlJS::Document::Ptr doc = QmlJS::Document::create(fileName);
+        Document::Ptr doc = Document::create(fileName);
+        doc->setDocumentRevision(documentRevision);
         doc->setSource(contents);
 
-        {
-            Core::MimeDatabase *db = Core::ICore::instance()->mimeDatabase();
-            Core::MimeType jsSourceTy = db->findByType(QLatin1String("application/javascript"));
-            Core::MimeType qmlSourceTy = db->findByType(QLatin1String("application/x-qml"));
+        const QFileInfo fileInfo(fileName);
 
-            const QFileInfo fileInfo(fileName);
-
-            if (jsSourceTy.matchesFile(fileInfo))
-                doc->parseJavaScript();
-            else if (qmlSourceTy.matchesFile(fileInfo))
-                doc->parseQml();
-            else
-                qWarning() << "Don't know how to treat" << fileName;
-        }
+        if (jsSourceTy.matchesFile(fileInfo))
+            doc->parseJavaScript();
+        else if (qmlSourceTy.matchesFile(fileInfo))
+            doc->parseQml();
+        else
+            qWarning() << "Don't know how to treat" << fileName;
 
         modelManager->emitDocumentUpdated(doc);
     }
diff --git a/src/plugins/qmljseditor/qmlmodelmanager.h b/src/plugins/qmljseditor/qmlmodelmanager.h
index a015b8ea393..4677248bd86 100644
--- a/src/plugins/qmljseditor/qmlmodelmanager.h
+++ b/src/plugins/qmljseditor/qmlmodelmanager.h
@@ -66,11 +66,18 @@ private Q_SLOTS:
     void onDocumentUpdated(QmlJS::Document::Ptr doc);
 
 protected:
+    struct WorkingCopy
+    {
+        WorkingCopy(int revision = 0): documentRevision(revision) {}
+        int documentRevision;
+        QString contents;
+    };
+
     QFuture<void> refreshSourceFiles(const QStringList &sourceFiles);
-    QMap<QString, QString> buildWorkingCopyList();
+    QMap<QString, WorkingCopy> buildWorkingCopyList();
 
     static void parse(QFutureInterface<void> &future,
-                      QMap<QString, QString> workingCopy,
+                      QMap<QString, WorkingCopy> workingCopy,
                       QStringList files,
                       QmlModelManager *modelManager);
 
-- 
GitLab