diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index ba5f3653c2e515c78988225481c6dfec4c1c99bf..ad2d4cd0cd8a574dac76451c8c9b4e2690c5ec31 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -677,19 +677,19 @@ QmlJSTextEditorWidget::QmlJSTextEditorWidget(QWidget *parent) : 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(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(reparseDocumentNow())); m_updateUsesTimer = new QTimer(this); m_updateUsesTimer->setInterval(UPDATE_USES_DEFAULT_INTERVAL); m_updateUsesTimer->setSingleShot(true); connect(m_updateUsesTimer, SIGNAL(timeout()), this, SLOT(updateUsesNow())); - m_localReparseTimer = new QTimer(this); - m_localReparseTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL); - m_localReparseTimer->setSingleShot(true); - connect(m_localReparseTimer, SIGNAL(timeout()), this, SLOT(forceReparseIfCurrentEditor())); + m_updateSemanticInfoTimer = new QTimer(this); + m_updateSemanticInfoTimer->setInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL); + m_updateSemanticInfoTimer->setSingleShot(true); + connect(m_updateSemanticInfoTimer, SIGNAL(timeout()), this, SLOT(updateSemanticInfoNow())); - connect(this, SIGNAL(textChanged()), this, SLOT(updateDocument())); + connect(this, SIGNAL(textChanged()), this, SLOT(reparseDocument())); connect(this, SIGNAL(textChanged()), this, SLOT(updateUses())); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(updateUses())); @@ -723,16 +723,15 @@ QmlJSTextEditorWidget::QmlJSTextEditorWidget(QWidget *parent) : m_oldCursorPosition = -1; if (m_modelManager) { - m_semanticInfoUpdater->setModelManager(m_modelManager); connect(m_modelManager, SIGNAL(documentUpdated(QmlJS::Document::Ptr)), this, SLOT(onDocumentUpdated(QmlJS::Document::Ptr))); connect(m_modelManager, SIGNAL(libraryInfoUpdated(QString,QmlJS::LibraryInfo)), - this, SLOT(forceReparseIfCurrentEditor())); + this, SLOT(updateSemanticInfo())); connect(this->document(), SIGNAL(modificationChanged(bool)), this, SLOT(modificationChanged(bool))); } connect(m_semanticInfoUpdater, SIGNAL(updated(QmlJSEditor::SemanticInfo)), - this, SLOT(updateSemanticInfo(QmlJSEditor::SemanticInfo))); + this, SLOT(acceptNewSemanticInfo(QmlJSEditor::SemanticInfo))); connect(this, SIGNAL(refactorMarkerClicked(TextEditor::RefactorMarker)), SLOT(onRefactorMarkerClicked(TextEditor::RefactorMarker))); @@ -757,7 +756,7 @@ int QmlJSTextEditorWidget::editorRevision() const return document()->revision(); } -bool QmlJSTextEditorWidget::isOutdated() const +bool QmlJSTextEditorWidget::isSemanticInfoOutdated() const { if (m_semanticInfo.revision() != editorRevision()) return true; @@ -799,19 +798,16 @@ bool QmlJSEditorEditable::open(QString *errorString, const QString &fileName, co return b; } -void QmlJSTextEditorWidget::updateDocument() +void QmlJSTextEditorWidget::reparseDocument() { - m_updateDocumentTimer->start(UPDATE_DOCUMENT_DEFAULT_INTERVAL); + m_updateDocumentTimer->start(); } -void QmlJSTextEditorWidget::updateDocumentNow() +void QmlJSTextEditorWidget::reparseDocumentNow() { - // ### move in the parser thread. - m_updateDocumentTimer->stop(); const QString fileName = file()->fileName(); - m_modelManager->updateSourceFiles(QStringList() << fileName, false); } @@ -892,19 +888,23 @@ static void appendExtraSelectionsForMessages( void QmlJSTextEditorWidget::onDocumentUpdated(QmlJS::Document::Ptr doc) { - if (file()->fileName() != doc->fileName() - || doc->editorRevision() != document()->revision()) { - // maybe a dependency changed: schedule a potential rehighlight - // will not rehighlight if the current editor changes away from this file - m_localReparseTimer->start(); + if (file()->fileName() != doc->fileName()) + return; + + if (doc->editorRevision() != document()->revision()) { + // Maybe a dependency changed and our semantic info is now outdated. + // Ignore 0-revision documents though, we get them when a file is initially opened + // in an editor. + if (doc->editorRevision() != 0) + updateSemanticInfo(); return; } + //qDebug() << doc->fileName() << "was reparsed"; + if (doc->ast()) { // got a correctly parsed (or recovered) file. - - const SemanticInfoUpdaterSource source = currentSource(/*force = */ true); - m_semanticInfoUpdater->update(source); + m_semanticInfoUpdater->update(SemanticInfoUpdaterSource(doc, m_modelManager->snapshot())); } else { // show parsing errors QList<QTextEdit::ExtraSelection> selections; @@ -1063,7 +1063,7 @@ void QmlJSTextEditorWidget::setUpdateSelectedElements(bool value) void QmlJSTextEditorWidget::updateUsesNow() { - if (document()->revision() != m_semanticInfo.revision()) { + if (isSemanticInfoOutdated()) { updateUses(); return; } @@ -1427,7 +1427,7 @@ void QmlJSTextEditorWidget::contextMenuEvent(QContextMenuEvent *e) QSignalMapper mapper; connect(&mapper, SIGNAL(mapped(int)), this, SLOT(performQuickFix(int))); - if (! isOutdated()) { + if (! isSemanticInfoOutdated()) { TextEditor::IAssistInterface *interface = createAssistInterface(TextEditor::QuickFix, TextEditor::ExplicitlyInvoked); if (interface) { @@ -1531,31 +1531,43 @@ void QmlJSTextEditorWidget::setTabSettings(const TextEditor::TabSettings &ts) TextEditor::BaseTextEditorWidget::setTabSettings(ts); } -void QmlJSTextEditorWidget::forceReparse() +void QmlJSTextEditorWidget::updateSemanticInfo() { - m_semanticInfoUpdater->update(currentSource(/* force = */ true)); -} + // If the document is already out of date, new semantic infos + // won't be accepted anyway. What we need is a reparse. + if (isSemanticInfoOutdated()) + return; -void QmlJSEditor::QmlJSTextEditorWidget::forceReparseIfCurrentEditor() -{ + // Save time by not doing it for non-active editors. Core::EditorManager *editorManager = Core::EditorManager::instance(); - if (editorManager->currentEditor() == editor()) - forceReparse(); + if (editorManager->currentEditor() != editor()) + return; + + m_updateSemanticInfoTimer->start(); } -void QmlJSTextEditorWidget::reparse() +void QmlJSTextEditorWidget::updateSemanticInfoNow() { - m_semanticInfoUpdater->update(currentSource()); + // If the document is already out of date, new semantic infos + // won't be accepted anyway. What we need is a reparse. + if (isSemanticInfoOutdated()) + return; + + m_updateSemanticInfoTimer->stop(); + + m_semanticInfoUpdater->update( + SemanticInfoUpdaterSource(m_semanticInfo.document, m_semanticInfo.snapshot)); } -void QmlJSTextEditorWidget::updateSemanticInfo(const SemanticInfo &semanticInfo) +void QmlJSTextEditorWidget::acceptNewSemanticInfo(const SemanticInfo &semanticInfo) { - if (semanticInfo.revision() != document()->revision()) { - // got outdated semantic info - reparse(); + if (semanticInfo.document->editorRevision() != document()->revision()) { + // ignore outdated semantic infos return; } + //qDebug() << file()->fileName() << "got new semantic info"; + m_semanticInfo = semanticInfo; Document::Ptr doc = semanticInfo.document; @@ -1660,25 +1672,6 @@ QVector<QString> QmlJSTextEditorWidget::highlighterFormatCategories() return categories; } -SemanticInfoUpdaterSource QmlJSTextEditorWidget::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(); - SemanticInfoUpdaterSource source(snapshot, fileName, code, - line, column, revision); - source.force = force; - return source; -} - TextEditor::IAssistInterface *QmlJSTextEditorWidget::createAssistInterface( TextEditor::AssistKind assistKind, TextEditor::AssistReason reason) const diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h index 35f80f113873fc8056b0304583d1ea5085b95fc8..7cc67875537fb7a5c70ebba6b510b8d3f2bacca6 100644 --- a/src/plugins/qmljseditor/qmljseditor.h +++ b/src/plugins/qmljseditor/qmljseditor.h @@ -156,8 +156,8 @@ public: virtual void unCommentSelection(); SemanticInfo semanticInfo() const; + bool isSemanticInfoOutdated() const; int editorRevision() const; - bool isOutdated() const; Internal::QmlOutlineModel *outlineModel() const; QModelIndex outlineModelIndex(); @@ -172,7 +172,10 @@ public: public slots: virtual void setTabSettings(const TextEditor::TabSettings &ts); - void forceReparse(); + void reparseDocument(); + void reparseDocumentNow(); + void updateSemanticInfo(); + void updateSemanticInfoNow(); void followSymbolUnderCursor(); void findUsages(); void renameUsages(); @@ -188,8 +191,6 @@ private slots: void onDocumentUpdated(QmlJS::Document::Ptr doc); void modificationChanged(bool); - void updateDocument(); - void updateDocumentNow(); void jumpToOutlineElement(int index); void updateOutlineNow(); void updateOutlineIndexNow(); @@ -200,9 +201,7 @@ private slots: void updateUses(); void updateUsesNow(); - void reparse(); - void forceReparseIfCurrentEditor(); - void updateSemanticInfo(const QmlJSEditor::SemanticInfo &semanticInfo); + void acceptNewSemanticInfo(const QmlJSEditor::SemanticInfo &semanticInfo); void onCursorPositionChanged(); void onRefactorMarkerClicked(const TextEditor::RefactorMarker &marker); @@ -225,7 +224,6 @@ private: void setSelectedElements(); QString wordUnderCursor() const; - Internal::SemanticInfoUpdaterSource currentSource(bool force = false); QModelIndex indexForPosition(unsigned cursorPosition, const QModelIndex &rootIndex = QModelIndex()) const; bool hideContextPane(); @@ -233,7 +231,7 @@ private: QTimer *m_updateDocumentTimer; QTimer *m_updateUsesTimer; - QTimer *m_localReparseTimer; + QTimer *m_updateSemanticInfoTimer; QTimer *m_updateOutlineTimer; QTimer *m_updateOutlineIndexTimer; QTimer *m_cursorPositionTimer; diff --git a/src/plugins/qmljseditor/qmljseditorplugin.cpp b/src/plugins/qmljseditor/qmljseditorplugin.cpp index a596098c2bced56a951e21a0f36d8fcba93bdd57..92d9ed8f805d557b66c5b7fc604022d350ed2407 100644 --- a/src/plugins/qmljseditor/qmljseditorplugin.cpp +++ b/src/plugins/qmljseditor/qmljseditorplugin.cpp @@ -311,7 +311,7 @@ void QmlJSEditorPlugin::reformatFile() { Core::EditorManager *em = Core::EditorManager::instance(); if (QmlJSTextEditorWidget *editor = qobject_cast<QmlJSTextEditorWidget*>(em->currentEditor()->widget())) { - QTC_ASSERT(!editor->isOutdated(), return); + QTC_ASSERT(!editor->isSemanticInfoOutdated(), return); const QString &newText = QmlJS::reformat(editor->semanticInfo().document); QTextCursor tc(editor->textCursor()); @@ -364,7 +364,7 @@ void QmlJSEditorPlugin::currentEditorChanged(Core::IEditor *editor) this, SLOT(checkCurrentEditorSemanticInfoUpToDate())); connect(newTextEditor, SIGNAL(semanticInfoUpdated()), this, SLOT(checkCurrentEditorSemanticInfoUpToDate())); - newTextEditor->forceReparse(); + newTextEditor->reparseDocumentNow(); } } @@ -378,7 +378,7 @@ void QmlJSEditorPlugin::runSemanticScan() void QmlJSEditorPlugin::checkCurrentEditorSemanticInfoUpToDate() { - const bool semanticInfoUpToDate = m_currentEditor && !m_currentEditor->isOutdated(); + const bool semanticInfoUpToDate = m_currentEditor && !m_currentEditor->isSemanticInfoOutdated(); m_reformatFileAction->setEnabled(semanticInfoUpToDate); } diff --git a/src/plugins/qmljseditor/qmljshoverhandler.cpp b/src/plugins/qmljseditor/qmljshoverhandler.cpp index 3e8a2b199a556346d04452747312ddbf1ec391b4..cfcd62b83dcbc0860b59d7abb04951815b67e28b 100644 --- a/src/plugins/qmljseditor/qmljshoverhandler.cpp +++ b/src/plugins/qmljseditor/qmljshoverhandler.cpp @@ -119,7 +119,7 @@ void HoverHandler::identifyMatch(TextEditor::ITextEditor *editor, int pos) return; const QmlJSEditor::SemanticInfo &semanticInfo = qmlEditor->semanticInfo(); - if (! semanticInfo.isValid() || semanticInfo.revision() != qmlEditor->editorRevision()) + if (! semanticInfo.isValid() || qmlEditor->isSemanticInfoOutdated()) return; QList<AST::Node *> rangePath = semanticInfo.rangePath(pos); diff --git a/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp b/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp index 05a0485c97f93be73fc24cf4cf1b1f427b29a381..a4ef2ccb39d25e0bc821d245f732e6d37ba69ac9 100644 --- a/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp +++ b/src/plugins/qmljseditor/qmljssemanticinfoupdater.cpp @@ -43,9 +43,8 @@ namespace QmlJSEditor { namespace Internal { SemanticInfoUpdater::SemanticInfoUpdater(QObject *parent) - : QThread(parent), - m_done(false), - m_modelManager(0) + : QThread(parent) + , m_wasCancelled(false) { } @@ -56,7 +55,7 @@ SemanticInfoUpdater::~SemanticInfoUpdater() void SemanticInfoUpdater::abort() { QMutexLocker locker(&m_mutex); - m_done = true; + m_wasCancelled = true; m_condition.wakeOne(); } @@ -67,13 +66,6 @@ void SemanticInfoUpdater::update(const SemanticInfoUpdaterSource &source) m_condition.wakeOne(); } -bool SemanticInfoUpdater::isOutdated() -{ - QMutexLocker locker(&m_mutex); - const bool outdated = ! m_source.fileName.isEmpty() || m_done; - return outdated; -} - void SemanticInfoUpdater::run() { setPriority(QThread::LowestPriority); @@ -81,10 +73,10 @@ void SemanticInfoUpdater::run() forever { m_mutex.lock(); - while (! (m_done || ! m_source.fileName.isEmpty())) + while (! (m_wasCancelled || m_source.isValid())) m_condition.wait(&m_mutex); - const bool done = m_done; + const bool done = m_wasCancelled; const SemanticInfoUpdaterSource source = m_source; m_source.clear(); @@ -93,73 +85,42 @@ void SemanticInfoUpdater::run() if (done) break; - const SemanticInfo info = semanticInfo(source); + const SemanticInfo info = makeNewSemanticInfo(source); - if (! isOutdated()) { - m_mutex.lock(); - m_lastSemanticInfo = info; - m_mutex.unlock(); + m_mutex.lock(); + const bool cancelledOrNewData = m_wasCancelled || m_source.isValid(); + m_mutex.unlock(); + if (! cancelledOrNewData) { + m_lastSemanticInfo = info; emit updated(info); } } } -SemanticInfo SemanticInfoUpdater::semanticInfo(const SemanticInfoUpdaterSource &source) +SemanticInfo SemanticInfoUpdater::makeNewSemanticInfo(const SemanticInfoUpdaterSource &source) { - m_mutex.lock(); - const int revision = m_lastSemanticInfo.revision(); - m_mutex.unlock(); - - QmlJS::Snapshot snapshot; - QmlJS::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; - QmlJS::Document::Language language; - if (m_lastSemanticInfo.document) - language = m_lastSemanticInfo.document->language(); - else - language = QmlJSTools::languageOfFile(source.fileName); - QmlJS::Document::MutablePtr newDoc = snapshot.documentFromSource( - source.code, source.fileName, language); - newDoc->setEditorRevision(source.revision); - newDoc->parse(); - snapshot.insert(newDoc); - doc = newDoc; - } + using namespace QmlJS; SemanticInfo semanticInfo; - semanticInfo.snapshot = snapshot; - semanticInfo.document = doc; + const Document::Ptr &doc = semanticInfo.document = source.document; + semanticInfo.snapshot = source.snapshot; - QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); + ModelManagerInterface *modelManager = ModelManagerInterface::instance(); - QmlJS::Link link(snapshot, modelManager->importPaths(), modelManager->builtins(doc)); + Link link(semanticInfo.snapshot, modelManager->importPaths(), modelManager->builtins(doc)); semanticInfo.context = link(doc, &semanticInfo.semanticMessages); - QmlJS::ScopeChain *scopeChain = new QmlJS::ScopeChain(doc, semanticInfo.context); - semanticInfo.m_rootScopeChain = QSharedPointer<const QmlJS::ScopeChain>(scopeChain); + ScopeChain *scopeChain = new ScopeChain(doc, semanticInfo.context); + semanticInfo.m_rootScopeChain = QSharedPointer<const ScopeChain>(scopeChain); - if (doc->language() != QmlJS::Document::JsonLanguage) { - QmlJS::Check checker(doc, semanticInfo.context); + if (doc->language() != Document::JsonLanguage) { + Check checker(doc, semanticInfo.context); semanticInfo.staticAnalysisMessages = checker(); } return semanticInfo; } -void SemanticInfoUpdater::setModelManager(QmlJS::ModelManagerInterface *modelManager) -{ - m_modelManager = modelManager; -} - } // namespace Internal } // namespace QmlJSEditor diff --git a/src/plugins/qmljseditor/qmljssemanticinfoupdater.h b/src/plugins/qmljseditor/qmljssemanticinfoupdater.h index 350751d2a171cf21b5dd6fddefeb692ca4485559..ed7a9a026f329d99d34cf41d4ef99127b7b17db1 100644 --- a/src/plugins/qmljseditor/qmljssemanticinfoupdater.h +++ b/src/plugins/qmljseditor/qmljssemanticinfoupdater.h @@ -45,37 +45,25 @@ namespace Internal { struct SemanticInfoUpdaterSource { + QmlJS::Document::Ptr document; QmlJS::Snapshot snapshot; - QString fileName; - QString code; - int line; - int column; - int revision; - bool force; SemanticInfoUpdaterSource() - : line(0), column(0), revision(0), force(false) { } - SemanticInfoUpdaterSource(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) + SemanticInfoUpdaterSource(const QmlJS::Document::Ptr &document, + const QmlJS::Snapshot &snapshot) + : document(document) + , snapshot(snapshot) { } + bool isValid() const + { return document; } + void clear() { + document.clear(); snapshot = QmlJS::Snapshot(); - fileName.clear(); - code.clear(); - line = 0; - column = 0; - revision = 0; - force = false; } }; @@ -89,7 +77,6 @@ public: void abort(); void update(const SemanticInfoUpdaterSource &source); - void setModelManager(QmlJS::ModelManagerInterface *modelManager); Q_SIGNALS: void updated(const QmlJSEditor::SemanticInfo &semanticInfo); @@ -98,16 +85,14 @@ protected: virtual void run(); private: - bool isOutdated(); - SemanticInfo semanticInfo(const SemanticInfoUpdaterSource &source); + SemanticInfo makeNewSemanticInfo(const SemanticInfoUpdaterSource &source); private: QMutex m_mutex; QWaitCondition m_condition; - bool m_done; + bool m_wasCancelled; SemanticInfoUpdaterSource m_source; SemanticInfo m_lastSemanticInfo; - QmlJS::ModelManagerInterface *m_modelManager; }; } // namespace Internal diff --git a/src/plugins/qmljseditor/qmloutlinemodel.cpp b/src/plugins/qmljseditor/qmloutlinemodel.cpp index 4eee646120df19ef432c81df87b7de6e843d3fcd..092dc03693ad1a66a5b1d72e6c78c547fc673408 100644 --- a/src/plugins/qmljseditor/qmloutlinemodel.cpp +++ b/src/plugins/qmljseditor/qmloutlinemodel.cpp @@ -408,7 +408,7 @@ Qt::ItemFlags QmlOutlineModel::flags(const QModelIndex &index) const // only allow drag&drop if we're in sync if (m_semanticInfo.isValid() - && m_semanticInfo.revision() == m_textEditor->editorRevision()) { + && !m_textEditor->isSemanticInfoOutdated()) { if (index.parent().isValid()) flags |= Qt::ItemIsDragEnabled; if (index.data(ItemTypeRole) != NonElementBindingType)