diff options
author | Erik Verbruggen <erik.verbruggen@digia.com> | 2013-04-17 10:58:20 +0200 |
---|---|---|
committer | Nikolai Kosjar <nikolai.kosjar@digia.com> | 2013-04-26 12:47:06 +0200 |
commit | 0c27b276584691f05e3efc88c95014f5bb5c3fa6 (patch) | |
tree | af65cf9dc162e0579d721bf27875d01b41e406f0 /src/plugins/cpptools/cpptoolseditorsupport.cpp | |
parent | e8d59fb76f6c59d8a0dd1e9d6cbdcb4bcfb3c9f0 (diff) | |
download | qt-creator-0c27b276584691f05e3efc88c95014f5bb5c3fa6.tar.gz |
C++ Detach the CppEditor from code-model internals.
- Moved document update handling into CppTools.
- Moved semantic info calculation into CppTools.
- Moved semantic highlighting into CppTools.
Change-Id: I253861bf074a64b1f657f7a4a8e6583871b5285f
Reviewed-by: Nikolai Kosjar <nikolai.kosjar@digia.com>
Diffstat (limited to 'src/plugins/cpptools/cpptoolseditorsupport.cpp')
-rw-r--r-- | src/plugins/cpptools/cpptoolseditorsupport.cpp | 437 |
1 files changed, 392 insertions, 45 deletions
diff --git a/src/plugins/cpptools/cpptoolseditorsupport.cpp b/src/plugins/cpptools/cpptoolseditorsupport.cpp index 32f1eac904..bc2e91b56c 100644 --- a/src/plugins/cpptools/cpptoolseditorsupport.cpp +++ b/src/plugins/cpptools/cpptoolseditorsupport.cpp @@ -28,92 +28,439 @@ ****************************************************************************/ #include "cpptoolseditorsupport.h" - #include "cppmodelmanager.h" +#include "cpplocalsymbols.h" + +#include <utils/runextensions.h> +#include <QList> +#include <QMutexLocker> +#include <QTextBlock> #include <QTimer> 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; + + else if (FunctionDefinitionAST *def = ast->asFunctionDefinition()) { + return checkDeclaration(def); + } + + else 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); -CppEditorSupport::CppEditorSupport(CppModelManager *modelManager) - : QObject(modelManager), - _modelManager(modelManager), - _updateDocumentInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL) + 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_cachedContentsEditorRevision(-1) + , m_initialized(false) + , m_lastHighlightRevision(0) + , m_highlightingSupport(modelManager->highlightingSupport(textEditor)) { - _revision = 0; + connect(m_modelManager, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)), + this, SLOT(onDocumentUpdated(CPlusPlus::Document::Ptr))); + + if (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())); - _updateDocumentTimer = new QTimer(this); - _updateDocumentTimer->setSingleShot(true); - _updateDocumentTimer->setInterval(_updateDocumentInterval); - connect(_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())); + + updateDocument(); } CppEditorSupport::~CppEditorSupport() { } -TextEditor::ITextEditor *CppEditorSupport::textEditor() const -{ return _textEditor; } +QString CppEditorSupport::fileName() const +{ + return m_textEditor->document()->fileName(); +} -void CppEditorSupport::setTextEditor(TextEditor::ITextEditor *textEditor) +QString CppEditorSupport::contents() const { - _textEditor = textEditor; + const int editorRev = editorRevision(); + if (m_cachedContentsEditorRevision != editorRev) { + m_cachedContentsEditorRevision = editorRev; + m_cachedContents = m_textEditor->textDocument()->contents(); + } - if (_textEditor) { - connect(_textEditor, SIGNAL(contentsChanged()), this, SIGNAL(contentsChanged())); - connect(this, SIGNAL(contentsChanged()), this, SLOT(updateDocument())); + return m_cachedContents; +} - updateDocument(); - } +unsigned CppEditorSupport::editorRevision() const +{ + return m_textEditor->editorWidget()->document()->revision(); } -QString CppEditorSupport::contents() +void CppEditorSupport::setExtraDiagnostics(const QString &key, + const QList<Document::DiagnosticMessage> &messages) { - if (! _textEditor) - return QString(); - else if (! _cachedContents.isEmpty()) - _cachedContents = _textEditor->textDocument()->contents(); + { + QMutexLocker locker(&m_diagnosticsMutex); + m_allDiagnostics.insert(key, messages); + } - return _cachedContents; + emit diagnosticsChanged(); } -unsigned CppEditorSupport::editorRevision() const +SemanticInfo CppEditorSupport::recalculateSemanticInfo(bool emitSignalWhenFinished) { - if (_textEditor) { - if (TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(_textEditor->widget())) - return ed->document()->revision(); - } + m_futureSemanticInfo.cancel(); - return 0; + SemanticInfo::Source source = currentSource(false); + recalculateSemanticInfoNow(source, emitSignalWhenFinished); + return m_lastSemanticInfo; } -int CppEditorSupport::updateDocumentInterval() const -{ return _updateDocumentInterval; } +void CppEditorSupport::recalculateSemanticInfoDetached(bool force) +{ + m_futureSemanticInfo.cancel(); + SemanticInfo::Source source = currentSource(force); + m_futureSemanticInfo = QtConcurrent::run<CppEditorSupport, void>( + &CppEditorSupport::recalculateSemanticInfoDetached_helper, this, source); -void CppEditorSupport::setUpdateDocumentInterval(int updateDocumentInterval) -{ _updateDocumentInterval = updateDocumentInterval; } + if (force && !m_highlightingSupport->requiresSemanticInfo()) + startHighlighting(); +} void CppEditorSupport::updateDocument() { - _revision = editorRevision(); + m_revision = editorRevision(); - if (qobject_cast<TextEditor::BaseTextEditorWidget*>(_textEditor->widget()) != 0) - _modelManager->stopEditorSelectionsUpdate(); + if (qobject_cast<BaseTextEditorWidget*>(m_textEditor->widget()) != 0) + m_updateEditorTimer->stop(); - _updateDocumentTimer->start(_updateDocumentInterval); + m_updateDocumentTimer->start(m_updateDocumentInterval); } void CppEditorSupport::updateDocumentNow() { - if (_documentParser.isRunning() || _revision != editorRevision()) { - _updateDocumentTimer->start(_updateDocumentInterval); + if (m_documentParser.isRunning() || m_revision != editorRevision()) { + m_updateDocumentTimer->start(m_updateDocumentInterval); + } else { + m_updateDocumentTimer->stop(); + + if (!m_highlightingSupport->requiresSemanticInfo()) { + startHighlighting(); + } + + const QStringList sourceFiles(m_textEditor->document()->fileName()); + m_documentParser = m_modelManager->updateSourceFiles(sourceFiles); + } +} + +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: + QList<Document::Block> skippedBlocks = doc->skippedBlocks(); + m_editorUpdates.ifdefedOutBlocks.clear(); + m_editorUpdates.ifdefedOutBlocks.reserve(skippedBlocks.size()); + foreach (const Document::Block &block, skippedBlocks) { + m_editorUpdates.ifdefedOutBlocks.append(BlockRange(block.begin(), block.end())); + } + + 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; + + if (!m_textEditor->widget()->isVisible()) + 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 { - _updateDocumentTimer->stop(); + 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 method has to be called +/// on the UI thread. +void CppEditorSupport::onDiagnosticsChanged() +{ + QList<Document::DiagnosticMessage> allDiagnostics; + { + QMutexLocker locker(&m_diagnosticsMutex); + foreach (const QList<Document::DiagnosticMessage> &msgs, m_allDiagnostics.values()) + allDiagnostics.append(msgs); + } + + if (!m_textEditor) + return; - QStringList sourceFiles(_textEditor->document()->fileName()); - _cachedContents = _textEditor->textDocument()->contents(); - _documentParser = _modelManager->updateSourceFiles(sourceFiles); + // 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); } +SemanticInfo::Source CppEditorSupport::currentSource(bool force) +{ + int line = 0, column = 0; + m_textEditor->convertPosition(m_textEditor->editorWidget()->position(), &line, &column); + + const Snapshot snapshot = m_modelManager->snapshot(); + + QString 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; + } + } + + 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<void> &future, SemanticInfo::Source source) +{ + class TLDProc: public TopLevelDeclarationProcessor + { + QFutureInterface<void> m_theFuture; + + public: + TLDProc(QFutureInterface<void> &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); +} |