/**************************************************************************** ** ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** 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. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "cppcodemodelsettings.h" #include "cppcompletionassistprovider.h" #include "cpptoolseditorsupport.h" #include "cpptoolsplugin.h" #include "cppmodelmanager.h" #include "cpplocalsymbols.h" #include #include #include #include #include #include #include using namespace CppTools; using namespace CppTools::Internal; using namespace CPlusPlus; using namespace TextEditor; namespace { class FunctionDefinitionUnderCursor: protected ASTVisitor { unsigned _line; unsigned _column; DeclarationAST *_functionDefinition; public: FunctionDefinitionUnderCursor(TranslationUnit *translationUnit) : ASTVisitor(translationUnit), _line(0), _column(0) { } DeclarationAST *operator()(AST *ast, unsigned line, unsigned column) { _functionDefinition = 0; _line = line; _column = column; accept(ast); return _functionDefinition; } protected: virtual bool preVisit(AST *ast) { if (_functionDefinition) return false; if (FunctionDefinitionAST *def = ast->asFunctionDefinition()) return checkDeclaration(def); if (ObjCMethodDeclarationAST *method = ast->asObjCMethodDeclaration()) { if (method->function_body) return checkDeclaration(method); } return true; } private: bool checkDeclaration(DeclarationAST *ast) { unsigned startLine, startColumn; unsigned endLine, endColumn; getTokenStartPosition(ast->firstToken(), &startLine, &startColumn); getTokenEndPosition(ast->lastToken() - 1, &endLine, &endColumn); if (_line > startLine || (_line == startLine && _column >= startColumn)) { if (_line < endLine || (_line == endLine && _column < endColumn)) { _functionDefinition = ast; return false; } } return true; } }; } // anonymous namespace CppEditorSupport::CppEditorSupport(CppModelManager *modelManager, BaseTextEditor *textEditor) : QObject(modelManager) , m_modelManager(modelManager) , m_textEditor(textEditor) , m_updateDocumentInterval(UpdateDocumentDefaultInterval) , m_revision(0) , m_editorVisible(textEditor->widget()->isVisible()) , m_cachedContentsEditorRevision(-1) , m_fileIsBeingReloaded(false) , m_initialized(false) , m_lastHighlightRevision(0) , m_highlightingSupport(modelManager->highlightingSupport(textEditor)) , m_completionAssistProvider(m_modelManager->completionAssistProvider(textEditor)) { connect(m_modelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)), this, SLOT(onDocumentUpdated(CPlusPlus::Document::Ptr))); if (m_highlightingSupport && m_highlightingSupport->requiresSemanticInfo()) { connect(this, SIGNAL(semanticInfoUpdated(CppTools::SemanticInfo)), this, SLOT(startHighlighting())); } m_updateDocumentTimer = new QTimer(this); m_updateDocumentTimer->setSingleShot(true); m_updateDocumentTimer->setInterval(m_updateDocumentInterval); connect(m_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow())); m_updateEditorTimer = new QTimer(this); m_updateEditorTimer->setInterval(UpdateEditorInterval); m_updateEditorTimer->setSingleShot(true); connect(m_updateEditorTimer, SIGNAL(timeout()), this, SLOT(updateEditorNow())); connect(m_textEditor, SIGNAL(contentsChanged()), this, SLOT(updateDocument())); connect(this, SIGNAL(diagnosticsChanged()), this, SLOT(onDiagnosticsChanged())); connect(m_textEditor->document(), SIGNAL(mimeTypeChanged()), this, SLOT(onMimeTypeChanged())); connect(m_textEditor->document(), SIGNAL(aboutToReload()), this, SLOT(onAboutToReload())); connect(m_textEditor->document(), SIGNAL(reloadFinished(bool)), this, SLOT(onReloadFinished())); connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor *)), this, SLOT(onCurrentEditorChanged())); m_editorGCTimer = new QTimer(this); m_editorGCTimer->setSingleShot(true); m_editorGCTimer->setInterval(EditorHiddenGCTimeout); connect(m_editorGCTimer, SIGNAL(timeout()), this, SLOT(releaseResources())); updateDocument(); } CppEditorSupport::~CppEditorSupport() { m_documentParser.cancel(); m_highlighter.cancel(); m_futureSemanticInfo.cancel(); m_documentParser.waitForFinished(); m_highlighter.waitForFinished(); m_futureSemanticInfo.waitForFinished(); } QString CppEditorSupport::fileName() const { return m_textEditor->document()->filePath(); } QByteArray CppEditorSupport::contents() const { QMutexLocker locker(&m_cachedContentsLock); const int editorRev = editorRevision(); if (m_cachedContentsEditorRevision != editorRev && !m_fileIsBeingReloaded) { m_cachedContentsEditorRevision = editorRev; m_cachedContents = m_textEditor->textDocument()->contents().toUtf8(); } return m_cachedContents; } unsigned CppEditorSupport::editorRevision() const { return m_textEditor->editorWidget()->document()->revision(); } void CppEditorSupport::setExtraDiagnostics(const QString &key, const QList &messages) { { QMutexLocker locker(&m_diagnosticsMutex); m_allDiagnostics.insert(key, messages); } emit diagnosticsChanged(); } void CppEditorSupport::setIfdefedOutBlocks(const QList &ifdefedOutBlocks) { m_editorUpdates.ifdefedOutBlocks = ifdefedOutBlocks; emit diagnosticsChanged(); } bool CppEditorSupport::initialized() { return m_initialized; } SemanticInfo CppEditorSupport::recalculateSemanticInfo(bool emitSignalWhenFinished) { m_futureSemanticInfo.cancel(); SemanticInfo::Source source = currentSource(false); recalculateSemanticInfoNow(source, emitSignalWhenFinished); return m_lastSemanticInfo; } Document::Ptr CppEditorSupport::lastSemanticInfoDocument() const { QMutexLocker locker(&m_lastSemanticInfoLock); return m_lastSemanticInfo.doc; } void CppEditorSupport::recalculateSemanticInfoDetached(bool force) { // Block premature calculation caused by CppEditorPlugin::currentEditorChanged // when the editor is created. if (!m_initialized) return; m_futureSemanticInfo.cancel(); SemanticInfo::Source source = currentSource(force); m_futureSemanticInfo = QtConcurrent::run( &CppEditorSupport::recalculateSemanticInfoDetached_helper, this, source); if (force && m_highlightingSupport && !m_highlightingSupport->requiresSemanticInfo()) startHighlighting(); } CppCompletionAssistProvider *CppEditorSupport::completionAssistProvider() const { return m_completionAssistProvider; } QSharedPointer CppEditorSupport::snapshotUpdater() { QSharedPointer updater = m_snapshotUpdater; if (!updater) { updater = QSharedPointer(new SnapshotUpdater(fileName())); m_snapshotUpdater = updater; QSharedPointer cms = CppToolsPlugin::instance()->codeModelSettings(); updater->setUsePrecompiledHeaders(cms->pchUsage() != CppCodeModelSettings::PchUse_None); } return updater; } void CppEditorSupport::updateDocument() { m_revision = editorRevision(); if (qobject_cast(m_textEditor->widget()) != 0) m_updateEditorTimer->stop(); m_updateDocumentTimer->start(m_updateDocumentInterval); } static void parse(QFutureInterface &future, QSharedPointer updater) { future.setProgressRange(0, 1); if (future.isCanceled()) { future.setProgressValue(1); return; } CppModelManager *cmm = qobject_cast(CppModelManager::instance()); updater->update(cmm->workingCopy()); cmm->finishedRefreshingSourceFiles(QStringList(updater->fileInEditor())); future.setProgressValue(1); } void CppEditorSupport::updateDocumentNow() { if (m_documentParser.isRunning() || m_revision != editorRevision()) { m_updateDocumentTimer->start(m_updateDocumentInterval); } else { m_updateDocumentTimer->stop(); if (m_fileIsBeingReloaded || fileName().isEmpty()) return; if (m_highlightingSupport && !m_highlightingSupport->requiresSemanticInfo()) startHighlighting(); m_documentParser = QtConcurrent::run(&parse, snapshotUpdater()); } } bool CppEditorSupport::isUpdatingDocument() { return m_updateDocumentTimer->isActive() || m_documentParser.isRunning(); } void CppEditorSupport::onDocumentUpdated(Document::Ptr doc) { if (doc.isNull()) return; if (doc->fileName() != fileName()) return; // some other document got updated if (doc->editorRevision() != editorRevision()) return; // outdated content, wait for a new document to be parsed // Update the ifdeffed-out blocks: if (m_highlightingSupport && !m_highlightingSupport->hightlighterHandlesIfdefedOutBlocks()) { QList skippedBlocks = doc->skippedBlocks(); QList ifdefedOutBlocks; ifdefedOutBlocks.reserve(skippedBlocks.size()); foreach (const Document::Block &block, skippedBlocks) ifdefedOutBlocks.append(BlockRange(block.begin(), block.end())); setIfdefedOutBlocks(ifdefedOutBlocks); } if (m_highlightingSupport && !m_highlightingSupport->hightlighterHandlesDiagnostics()) { // Update the parser errors/warnings: static const QString key = QLatin1String("CppTools.ParserDiagnostics"); setExtraDiagnostics(key, doc->diagnosticMessages()); } // update semantic info in a future if (!m_initialized || (m_textEditor->widget()->isVisible() && (m_lastSemanticInfo.doc.isNull() || m_lastSemanticInfo.doc->translationUnit()->ast() == 0 || m_lastSemanticInfo.doc->fileName() != fileName()))) { m_initialized = true; recalculateSemanticInfoDetached(/* force = */ true); } // notify the editor that the document is updated emit documentUpdated(); } void CppEditorSupport::startHighlighting() { if (!m_highlightingSupport) return; // Start highlighting only if the editor is or would be visible // (in case another mode is active) in the edit mode. if (!Core::EditorManager::visibleEditors().contains(m_textEditor)) return; if (m_highlightingSupport->requiresSemanticInfo()) { Snapshot snapshot; Document::Ptr doc; unsigned revision; bool forced; { QMutexLocker locker(&m_lastSemanticInfoLock); snapshot = m_lastSemanticInfo.snapshot; doc = m_lastSemanticInfo.doc; revision = m_lastSemanticInfo.revision; forced = m_lastSemanticInfo.forced; } if (doc.isNull()) return; if (!forced && m_lastHighlightRevision == revision) return; m_highlighter.cancel(); m_highlighter = m_highlightingSupport->highlightingFuture(doc, snapshot); m_lastHighlightRevision = revision; emit highlighterStarted(&m_highlighter, m_lastHighlightRevision); } else { static const Document::Ptr dummyDoc; static const Snapshot dummySnapshot; m_highlighter = m_highlightingSupport->highlightingFuture(dummyDoc, dummySnapshot); m_lastHighlightRevision = editorRevision(); emit highlighterStarted(&m_highlighter, m_lastHighlightRevision); } } /// \brief This slot puts the new diagnostics into the editorUpdates. This function has to be called /// on the UI thread. void CppEditorSupport::onDiagnosticsChanged() { QList allDiagnostics; { QMutexLocker locker(&m_diagnosticsMutex); foreach (const QList &msgs, m_allDiagnostics.values()) allDiagnostics.append(msgs); } if (!m_textEditor) return; // set up the format for the errors QTextCharFormat errorFormat; errorFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); errorFormat.setUnderlineColor(Qt::red); // set up the format for the warnings. QTextCharFormat warningFormat; warningFormat.setUnderlineStyle(QTextCharFormat::WaveUnderline); warningFormat.setUnderlineColor(Qt::darkYellow); QTextDocument *doc = m_textEditor->editorWidget()->document(); m_editorUpdates.selections.clear(); foreach (const Document::DiagnosticMessage &m, allDiagnostics) { QTextEdit::ExtraSelection sel; if (m.isWarning()) sel.format = warningFormat; else sel.format = errorFormat; QTextCursor c(doc->findBlockByNumber(m.line() - 1)); const QString text = c.block().text(); if (m.length() > 0 && m.column() + m.length() < (unsigned)text.size()) { int column = m.column() > 0 ? m.column() - 1 : 0; c.setPosition(c.position() + column); c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, m.length()); } else { for (int i = 0; i < text.size(); ++i) { if (!text.at(i).isSpace()) { c.setPosition(c.position() + i); break; } } c.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); } sel.cursor = c; sel.format.setToolTip(m.text()); m_editorUpdates.selections.append(sel); } m_editorUpdates.revision = doc->revision(); updateEditor(); } void CppEditorSupport::updateEditor() { m_updateEditorTimer->start(UpdateEditorInterval); } void CppEditorSupport::updateEditorNow() { if (!m_textEditor) return; BaseTextEditorWidget *editorWidget = m_textEditor->editorWidget(); if (editorWidget->document()->revision() != m_editorUpdates.revision) return; // outdated editorWidget->setExtraSelections(BaseTextEditorWidget::CodeWarningsSelection, m_editorUpdates.selections); editorWidget->setIfdefedOutBlocks(m_editorUpdates.ifdefedOutBlocks); } void CppEditorSupport::onCurrentEditorChanged() { bool editorVisible = m_textEditor->widget()->isVisible(); if (m_editorVisible != editorVisible) { m_editorVisible = editorVisible; if (editorVisible) { m_editorGCTimer->stop(); QMutexLocker locker(&m_lastSemanticInfoLock); if (!m_lastSemanticInfo.doc) updateDocumentNow(); } else { m_editorGCTimer->start(EditorHiddenGCTimeout); } } } void CppEditorSupport::releaseResources() { snapshotUpdater()->releaseSnapshot(); QMutexLocker semanticLocker(&m_lastSemanticInfoLock); m_lastSemanticInfo = SemanticInfo(); } SemanticInfo::Source CppEditorSupport::currentSource(bool force) { int line = 0, column = 0; m_textEditor->convertPosition(m_textEditor->editorWidget()->position(), &line, &column); const Snapshot snapshot = m_snapshotUpdater->snapshot(); QByteArray code; if (force || m_lastSemanticInfo.revision != editorRevision()) code = contents(); // get the source code only when needed. const unsigned revision = editorRevision(); SemanticInfo::Source source(snapshot, fileName(), code, line, column, revision, force); return source; } void CppEditorSupport::recalculateSemanticInfoNow(const SemanticInfo::Source &source, bool emitSignalWhenFinished, TopLevelDeclarationProcessor *processor) { SemanticInfo semanticInfo; { QMutexLocker locker(&m_lastSemanticInfoLock); semanticInfo.revision = m_lastSemanticInfo.revision; semanticInfo.forced = source.force; if (!source.force && m_lastSemanticInfo.revision == source.revision && m_lastSemanticInfo.doc && m_lastSemanticInfo.doc->translationUnit()->ast() && m_lastSemanticInfo.doc->fileName() == source.fileName) { semanticInfo.snapshot = m_lastSemanticInfo.snapshot; // ### TODO: use the new snapshot. semanticInfo.doc = m_lastSemanticInfo.doc; } } if (semanticInfo.doc.isNull()) { semanticInfo.snapshot = source.snapshot; if (source.snapshot.contains(source.fileName)) { Document::Ptr doc = source.snapshot.preprocessedDocument(source.code, source.fileName); if (processor) doc->control()->setTopLevelDeclarationProcessor(processor); doc->check(); semanticInfo.doc = doc; } else { return; } } if (semanticInfo.doc) { TranslationUnit *translationUnit = semanticInfo.doc->translationUnit(); AST * ast = translationUnit->ast(); FunctionDefinitionUnderCursor functionDefinitionUnderCursor(semanticInfo.doc->translationUnit()); DeclarationAST *currentFunctionDefinition = functionDefinitionUnderCursor(ast, source.line, source.column); const LocalSymbols useTable(semanticInfo.doc, currentFunctionDefinition); semanticInfo.revision = source.revision; semanticInfo.localUses = useTable.uses; } { QMutexLocker locker(&m_lastSemanticInfoLock); m_lastSemanticInfo = semanticInfo; } if (emitSignalWhenFinished) emit semanticInfoUpdated(semanticInfo); } void CppEditorSupport::recalculateSemanticInfoDetached_helper(QFutureInterface &future, SemanticInfo::Source source) { class TLDProc: public TopLevelDeclarationProcessor { QFutureInterface m_theFuture; public: TLDProc(QFutureInterface &aFuture): m_theFuture(aFuture) {} virtual ~TLDProc() {} virtual bool processDeclaration(DeclarationAST *ast) { Q_UNUSED(ast); return m_theFuture.isCanceled(); } }; TLDProc tldProc(future); recalculateSemanticInfoNow(source, true, &tldProc); } void CppEditorSupport::onMimeTypeChanged() { m_highlighter.cancel(); m_highlighter.waitForFinished(); m_highlightingSupport.reset(m_modelManager->highlightingSupport(m_textEditor)); disconnect(this, SIGNAL(semanticInfoUpdated(CppTools::SemanticInfo)), this, SLOT(startHighlighting())); if (m_highlightingSupport && m_highlightingSupport->requiresSemanticInfo()) connect(this, SIGNAL(semanticInfoUpdated(CppTools::SemanticInfo)), this, SLOT(startHighlighting())); m_completionAssistProvider = m_modelManager->completionAssistProvider(m_textEditor); updateDocumentNow(); } void CppEditorSupport::onAboutToReload() { QTC_CHECK(!m_fileIsBeingReloaded); m_fileIsBeingReloaded = true; } void CppEditorSupport::onReloadFinished() { QTC_CHECK(m_fileIsBeingReloaded); m_fileIsBeingReloaded = false; updateDocument(); }