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>
This commit is contained in:
Erik Verbruggen
2013-04-17 10:58:20 +02:00
committed by Nikolai Kosjar
parent e8d59fb76f
commit 0c27b27658
11 changed files with 641 additions and 640 deletions

View File

@@ -64,6 +64,10 @@ public:
CppHighlightingSupport(TextEditor::ITextEditor *editor);
virtual ~CppHighlightingSupport() = 0;
virtual bool requiresSemanticInfo() const = 0;
virtual bool hightlighterHandlesDiagnostics() const = 0;
virtual QFuture<TextEditor::HighlightingResult> highlightingFuture(
const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot) const = 0;
@@ -82,8 +86,6 @@ public:
virtual ~CppHighlightingSupportFactory() = 0;
virtual CppHighlightingSupport *highlightingSupport(TextEditor::ITextEditor *editor) = 0;
virtual bool hightlighterHandlesDiagnostics() const = 0;
};
} // namespace CppTools

View File

@@ -43,6 +43,12 @@ public:
CppHighlightingSupportInternal(TextEditor::ITextEditor *editor);
virtual ~CppHighlightingSupportInternal();
virtual bool requiresSemanticInfo() const
{ return true; }
virtual bool hightlighterHandlesDiagnostics() const
{ return false; }
virtual QFuture<TextEditor::HighlightingResult> highlightingFuture(
const CPlusPlus::Document::Ptr &doc,
const CPlusPlus::Snapshot &snapshot) const;
@@ -54,9 +60,6 @@ public:
virtual ~CppHighlightingSupportInternalFactory();
virtual CppHighlightingSupport *highlightingSupport(TextEditor::ITextEditor *editor);
virtual bool hightlighterHandlesDiagnostics() const
{ return false; }
};
} // namespace Internal

View File

@@ -643,12 +643,6 @@ CppModelManager::CppModelManager(QObject *parent)
QTC_ASSERT(pe, return);
ProjectExplorer::SessionManager *session = pe->session();
m_updateEditorSelectionsTimer = new QTimer(this);
m_updateEditorSelectionsTimer->setInterval(500);
m_updateEditorSelectionsTimer->setSingleShot(true);
connect(m_updateEditorSelectionsTimer, SIGNAL(timeout()),
this, SLOT(updateEditorSelections()));
connect(session, SIGNAL(projectAdded(ProjectExplorer::Project*)),
this, SLOT(onProjectAdded(ProjectExplorer::Project*)));
@@ -663,16 +657,7 @@ CppModelManager::CppModelManager(QObject *parent)
qRegisterMetaType<CPlusPlus::Document::Ptr>("CPlusPlus::Document::Ptr");
// thread connections
connect(this, SIGNAL(documentUpdated(CPlusPlus::Document::Ptr)),
this, SLOT(onDocumentUpdated(CPlusPlus::Document::Ptr)));
connect(this, SIGNAL(extraDiagnosticsUpdated(QString)),
this, SLOT(onExtraDiagnosticsUpdated(QString)));
// Listen for editor closed and opened events so that we can keep track of changing files
connect(Core::ICore::editorManager(), SIGNAL(editorOpened(Core::IEditor*)),
this, SLOT(editorOpened(Core::IEditor*)));
// Listen for editor closed events so that we can keep track of changing files
connect(Core::ICore::editorManager(), SIGNAL(editorAboutToClose(Core::IEditor*)),
this, SLOT(editorAboutToClose(Core::IEditor*)));
@@ -868,6 +853,22 @@ void CppModelManager::removeEditorSupport(AbstractEditorSupport *editorSupport)
m_addtionalEditorSupport.remove(editorSupport);
}
/// \brief Returns the \c CppEditorSupport for the given text editor. It will
/// create one when none exists yet.
CppEditorSupport *CppModelManager::cppEditorSupport(TextEditor::BaseTextEditor *editor)
{
Q_ASSERT(editor);
QMutexLocker locker(&m_editorSupportMutex);
CppEditorSupport *editorSupport = m_editorSupport.value(editor, 0);
if (!editorSupport) {
editorSupport = new CppEditorSupport(this, editor);
m_editorSupport.insert(editor, editorSupport);
}
return editorSupport;
}
QList<int> CppModelManager::references(CPlusPlus::Symbol *symbol, const LookupContext &context)
{
return m_findReferences->references(symbol, context);
@@ -904,14 +905,17 @@ void CppModelManager::replaceSnapshot(const CPlusPlus::Snapshot &newSnapshot)
CppModelManager::WorkingCopy CppModelManager::buildWorkingCopyList()
{
QList<CppEditorSupport *> supporters;
{
QMutexLocker locker(&m_editorSupportMutex);
supporters = m_editorSupport.values();
}
WorkingCopy workingCopy;
QMapIterator<TextEditor::ITextEditor *, CppEditorSupport *> it(m_editorSupport);
while (it.hasNext()) {
it.next();
TextEditor::ITextEditor *textEditor = it.key();
CppEditorSupport *editorSupport = it.value();
QString fileName = textEditor->document()->fileName();
workingCopy.insert(fileName, editorSupport->contents(), editorSupport->editorRevision());
foreach (const CppEditorSupport *editorSupport, supporters) {
workingCopy.insert(editorSupport->fileName(), editorSupport->contents(),
editorSupport->editorRevision());
}
QSetIterator<AbstractEditorSupport *> jt(m_addtionalEditorSupport);
@@ -1009,36 +1013,19 @@ QList<ProjectPart::Ptr> CppModelManager::projectPart(const QString &fileName) co
return parts;
}
/*!
\fn void CppModelManager::editorOpened(Core::IEditor *editor)
\brief If a C++ editor is opened, the model manager listens to content changes
in order to update the CppCodeModel accordingly. It also updates the
CppCodeModel for the first time with this editor.
\sa void CppModelManager::editorContentsChanged()
*/
void CppModelManager::editorOpened(Core::IEditor *editor)
{
if (isCppEditor(editor)) {
TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor *>(editor);
QTC_ASSERT(textEditor, return);
CppEditorSupport *editorSupport = new CppEditorSupport(this);
editorSupport->setTextEditor(textEditor);
m_editorSupport[textEditor] = editorSupport;
}
}
/// \brief Removes the CppEditorSupport for the closed editor.
void CppModelManager::editorAboutToClose(Core::IEditor *editor)
{
if (isCppEditor(editor)) {
TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor *>(editor);
QTC_ASSERT(textEditor, return);
if (!isCppEditor(editor))
return;
CppEditorSupport *editorSupport = m_editorSupport.value(textEditor);
m_editorSupport.remove(textEditor);
delete editorSupport;
}
TextEditor::BaseTextEditor *textEditor = qobject_cast<TextEditor::BaseTextEditor *>(editor);
QTC_ASSERT(textEditor, return);
QMutexLocker locker(&m_editorSupportMutex);
CppEditorSupport *editorSupport = m_editorSupport.value(textEditor, 0);
m_editorSupport.remove(textEditor);
delete editorSupport;
}
bool CppModelManager::isCppEditor(Core::IEditor *editor) const
@@ -1047,141 +1034,9 @@ bool CppModelManager::isCppEditor(Core::IEditor *editor) const
}
void CppModelManager::emitDocumentUpdated(Document::Ptr doc)
{
emit documentUpdated(doc);
}
void CppModelManager::onDocumentUpdated(Document::Ptr doc)
{
if (replaceDocument(doc))
updateEditor(doc);
}
void CppModelManager::onExtraDiagnosticsUpdated(const QString &fileName)
{
if (Document::Ptr doc = document(fileName))
updateEditor(doc);
}
void CppModelManager::updateEditor(Document::Ptr doc)
{
const QString fileName = doc->fileName();
QList<Core::IEditor *> openedEditors = Core::ICore::editorManager()->openedEditors();
foreach (Core::IEditor *editor, openedEditors) {
if (editor->document()->fileName() == fileName) {
TextEditor::ITextEditor *textEditor = qobject_cast<TextEditor::ITextEditor *>(editor);
if (! textEditor)
continue;
TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(textEditor->widget());
if (! ed)
continue;
QList<TextEditor::BaseTextEditorWidget::BlockRange> blockRanges;
foreach (const Document::Block &block, doc->skippedBlocks()) {
blockRanges.append(TextEditor::BaseTextEditorWidget::BlockRange(block.begin(), block.end()));
}
// 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);
QList<Editor> todo;
foreach (const Editor &e, m_todo) {
if (e.textEditor != textEditor)
todo.append(e);
}
Editor e;
if (m_highlightingFactory->hightlighterHandlesDiagnostics()) {
e.updateSelections = false;
} else {
QSet<int> lines;
QList<Document::DiagnosticMessage> messages = doc->diagnosticMessages();
messages += extraDiagnostics(doc->fileName());
foreach (const Document::DiagnosticMessage &m, messages) {
if (m.fileName() != fileName)
continue;
else if (lines.contains(m.line()))
continue;
lines.insert(m.line());
QTextEdit::ExtraSelection sel;
if (m.isWarning())
sel.format = warningFormat;
else
sel.format = errorFormat;
QTextCursor c(ed->document()->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());
e.selections.append(sel);
}
}
e.revision = ed->document()->revision();
e.textEditor = textEditor;
e.ifdefedOutBlocks = blockRanges;
todo.append(e);
m_todo = todo;
postEditorUpdate();
break;
}
}
}
void CppModelManager::postEditorUpdate()
{
m_updateEditorSelectionsTimer->start(500);
}
void CppModelManager::updateEditorSelections()
{
foreach (const Editor &ed, m_todo) {
if (! ed.textEditor)
continue;
TextEditor::ITextEditor *textEditor = ed.textEditor;
TextEditor::BaseTextEditorWidget *editor = qobject_cast<TextEditor::BaseTextEditorWidget *>(textEditor->widget());
if (! editor)
continue;
else if (editor->document()->revision() != ed.revision)
continue; // outdated
if (ed.updateSelections)
editor->setExtraSelections(TextEditor::BaseTextEditorWidget::CodeWarningsSelection,
ed.selections);
editor->setIfdefedOutBlocks(ed.ifdefedOutBlocks);
}
m_todo.clear();
emit documentUpdated(doc);
}
void CppModelManager::onProjectAdded(ProjectExplorer::Project *)
@@ -1308,26 +1163,20 @@ CppIndexingSupport *CppModelManager::indexingSupport()
return m_indexingSupporter ? m_indexingSupporter : m_internalIndexingSupport;
}
void CppModelManager::setExtraDiagnostics(const QString &fileName, int kind,
void CppModelManager::setExtraDiagnostics(const QString &fileName, const QString &kind,
const QList<Document::DiagnosticMessage> &diagnostics)
{
{
QMutexLocker locker(&m_protectExtraDiagnostics);
if (m_extraDiagnostics[fileName][kind] == diagnostics)
return;
m_extraDiagnostics[fileName].insert(kind, diagnostics);
}
emit extraDiagnosticsUpdated(fileName);
}
QList<CppEditorSupport *> supporters;
QList<Document::DiagnosticMessage> CppModelManager::extraDiagnostics(const QString &fileName, int kind) const
{
QMutexLocker locker(&m_protectExtraDiagnostics);
if (kind == -1) {
QList<Document::DiagnosticMessage> messages;
foreach (const QList<Document::DiagnosticMessage> &list, m_extraDiagnostics.value(fileName))
messages += list;
return messages;
{
QMutexLocker locker(&m_editorSupportMutex);
supporters = m_editorSupport.values();
}
foreach (CppEditorSupport *supporter, supporters) {
if (supporter->fileName() == fileName) {
supporter->setExtraDiagnostics(kind, diagnostics);
break;
}
}
return m_extraDiagnostics.value(fileName).value(kind);
}

View File

@@ -59,11 +59,11 @@ namespace CPlusPlus { class ParseManager; }
namespace CppTools {
class CppCompletionSupportFactory;
class CppEditorSupport;
class CppHighlightingSupportFactory;
namespace Internal {
class CppEditorSupport;
class CppPreprocessor;
class CppFindReferences;
@@ -95,16 +95,11 @@ public:
virtual bool isCppEditor(Core::IEditor *editor) const;
CppEditorSupport *editorSupport(TextEditor::ITextEditor *editor) const
{ return m_editorSupport.value(editor); }
void emitDocumentUpdated(CPlusPlus::Document::Ptr doc);
void stopEditorSelectionsUpdate()
{ m_updateEditorSelectionsTimer->stop(); }
virtual void addEditorSupport(AbstractEditorSupport *editorSupport);
virtual void removeEditorSupport(AbstractEditorSupport *editorSupport);
virtual CppEditorSupport *cppEditorSupport(TextEditor::BaseTextEditor *editor);
virtual QList<int> references(CPlusPlus::Symbol *symbol, const CPlusPlus::LookupContext &context);
@@ -115,10 +110,8 @@ public:
virtual void findMacroUsages(const CPlusPlus::Macro &macro);
virtual void renameMacroUsages(const CPlusPlus::Macro &macro, const QString &replacement);
virtual void setExtraDiagnostics(const QString &fileName, int key,
virtual void setExtraDiagnostics(const QString &fileName, const QString &key,
const QList<Document::DiagnosticMessage> &diagnostics);
virtual QList<Document::DiagnosticMessage> extraDiagnostics(
const QString &fileName, int key = AllExtraDiagnostics) const;
void finishedRefreshingSourceFiles(const QStringList &files);
@@ -161,24 +154,17 @@ Q_SIGNALS:
void aboutToRemoveFiles(const QStringList &files);
public Q_SLOTS:
void editorOpened(Core::IEditor *editor);
void editorAboutToClose(Core::IEditor *editor);
virtual void updateModifiedSourceFiles();
private Q_SLOTS:
// this should be executed in the GUI thread.
void onDocumentUpdated(CPlusPlus::Document::Ptr doc);
void onExtraDiagnosticsUpdated(const QString &fileName);
void onAboutToRemoveProject(ProjectExplorer::Project *project);
void onAboutToUnloadSession();
void onCoreAboutToClose();
void onProjectAdded(ProjectExplorer::Project *project);
void postEditorUpdate();
void updateEditorSelections();
private:
void updateEditor(Document::Ptr doc);
void replaceSnapshot(const CPlusPlus::Snapshot &newSnapshot);
WorkingCopy buildWorkingCopyList();
@@ -206,7 +192,8 @@ private:
QByteArray m_definedMacros;
// editor integration
QMap<TextEditor::ITextEditor *, CppEditorSupport *> m_editorSupport;
mutable QMutex m_editorSupportMutex;
QMap<TextEditor::BaseTextEditor *, CppEditorSupport *> m_editorSupport;
QSet<AbstractEditorSupport *> m_addtionalEditorSupport;
@@ -216,28 +203,9 @@ private:
mutable QMutex m_mutex;
mutable QMutex m_protectSnapshot;
struct Editor {
Editor()
: revision(-1)
, updateSelections(true)
{}
int revision;
bool updateSelections;
QPointer<TextEditor::ITextEditor> textEditor;
QList<QTextEdit::ExtraSelection> selections;
QList<TextEditor::BaseTextEditorWidget::BlockRange> ifdefedOutBlocks;
};
QList<Editor> m_todo;
QTimer *m_updateEditorSelectionsTimer;
CppFindReferences *m_findReferences;
bool m_indexerEnabled;
mutable QMutex m_protectExtraDiagnostics;
QHash<QString, QHash<int, QList<Document::DiagnosticMessage> > > m_extraDiagnostics;
QMap<QString, QList<CppTools::ProjectPart::Ptr> > m_srcToProjectPart;
CppCompletionAssistProvider *m_completionAssistProvider;

View File

@@ -44,11 +44,13 @@
namespace Core { class IEditor; }
namespace CPlusPlus { class LookupContext; }
namespace ProjectExplorer { class Project; }
namespace TextEditor { class BaseTextEditor; }
namespace CppTools {
class AbstractEditorSupport;
class CppCompletionSupport;
class CppCompletionAssistProvider;
class CppEditorSupport;
class CppHighlightingSupport;
class CppHighlightingSupportFactory;
class CppIndexingSupport;
@@ -187,13 +189,6 @@ public:
Table _elements;
};
enum ExtraDiagnosticKind
{
AllExtraDiagnostics = -1,
ExportedQmlTypesDiagnostic,
CppSemanticsDiagnostic
};
public:
static const QString configurationFileName();
@@ -215,6 +210,7 @@ public:
virtual void addEditorSupport(CppTools::AbstractEditorSupport *editorSupport) = 0;
virtual void removeEditorSupport(CppTools::AbstractEditorSupport *editorSupport) = 0;
virtual CppEditorSupport *cppEditorSupport(TextEditor::BaseTextEditor *editor) = 0;
virtual QList<int> references(CPlusPlus::Symbol *symbol,
const CPlusPlus::LookupContext &context) = 0;
@@ -226,10 +222,8 @@ public:
virtual void renameMacroUsages(const CPlusPlus::Macro &macro, const QString &replacement = QString()) = 0;
virtual void findMacroUsages(const CPlusPlus::Macro &macro) = 0;
virtual void setExtraDiagnostics(const QString &fileName, int key,
virtual void setExtraDiagnostics(const QString &fileName, const QString &kind,
const QList<CPlusPlus::Document::DiagnosticMessage> &diagnostics) = 0;
virtual QList<CPlusPlus::Document::DiagnosticMessage> extraDiagnostics(
const QString &fileName, int key = AllExtraDiagnostics) const = 0;
virtual CppTools::CppCompletionSupport *completionSupport(Core::IEditor *editor) const = 0;
virtual void setCppCompletionAssistProvider(CppTools::CppCompletionAssistProvider *completionAssistProvider) = 0;
@@ -243,7 +237,6 @@ public:
Q_SIGNALS:
void documentUpdated(CPlusPlus::Document::Ptr doc);
void sourceFilesRefreshed(const QStringList &files);
void extraDiagnosticsUpdated(QString fileName);
/// \brief Emitted after updateProjectInfo method is called on the model-manager.
///

View File

@@ -42,6 +42,33 @@ namespace CppTools {
class CPPTOOLS_EXPORT SemanticInfo
{
public:
struct Source
{
const CPlusPlus::Snapshot snapshot;
const QString fileName;
const QString code;
const int line;
const int column;
const unsigned revision;
const bool force;
Source()
: line(0), column(0), revision(0), force(false)
{ }
Source(const CPlusPlus::Snapshot &snapshot,
const QString &fileName,
const QString &code,
int line, int column,
unsigned revision,
bool force)
: snapshot(snapshot), fileName(fileName),
code(code), line(line), column(column),
revision(revision), force(force)
{ }
};
public:
typedef TextEditor::HighlightingResult Use;

View File

@@ -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;
CppEditorSupport::CppEditorSupport(CppModelManager *modelManager)
: QObject(modelManager),
_modelManager(modelManager),
_updateDocumentInterval(UPDATE_DOCUMENT_DEFAULT_INTERVAL)
namespace {
class FunctionDefinitionUnderCursor: protected ASTVisitor
{
_revision = 0;
unsigned _line;
unsigned _column;
DeclarationAST *_functionDefinition;
_updateDocumentTimer = new QTimer(this);
_updateDocumentTimer->setSingleShot(true);
_updateDocumentTimer->setInterval(_updateDocumentInterval);
connect(_updateDocumentTimer, SIGNAL(timeout()), this, SLOT(updateDocumentNow()));
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);
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))
{
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()));
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; }
void CppEditorSupport::setTextEditor(TextEditor::ITextEditor *textEditor)
QString CppEditorSupport::fileName() const
{
_textEditor = textEditor;
if (_textEditor) {
connect(_textEditor, SIGNAL(contentsChanged()), this, SIGNAL(contentsChanged()));
connect(this, SIGNAL(contentsChanged()), this, SLOT(updateDocument()));
updateDocument();
}
return m_textEditor->document()->fileName();
}
QString CppEditorSupport::contents()
QString CppEditorSupport::contents() const
{
if (! _textEditor)
return QString();
else if (! _cachedContents.isEmpty())
_cachedContents = _textEditor->textDocument()->contents();
const int editorRev = editorRevision();
if (m_cachedContentsEditorRevision != editorRev) {
m_cachedContentsEditorRevision = editorRev;
m_cachedContents = m_textEditor->textDocument()->contents();
}
return _cachedContents;
return m_cachedContents;
}
unsigned CppEditorSupport::editorRevision() const
{
if (_textEditor) {
if (TextEditor::BaseTextEditorWidget *ed = qobject_cast<TextEditor::BaseTextEditorWidget *>(_textEditor->widget()))
return ed->document()->revision();
}
return 0;
return m_textEditor->editorWidget()->document()->revision();
}
int CppEditorSupport::updateDocumentInterval() const
{ return _updateDocumentInterval; }
void CppEditorSupport::setExtraDiagnostics(const QString &key,
const QList<Document::DiagnosticMessage> &messages)
{
{
QMutexLocker locker(&m_diagnosticsMutex);
m_allDiagnostics.insert(key, messages);
}
void CppEditorSupport::setUpdateDocumentInterval(int updateDocumentInterval)
{ _updateDocumentInterval = updateDocumentInterval; }
emit diagnosticsChanged();
}
SemanticInfo CppEditorSupport::recalculateSemanticInfo(bool emitSignalWhenFinished)
{
m_futureSemanticInfo.cancel();
SemanticInfo::Source source = currentSource(false);
recalculateSemanticInfoNow(source, emitSignalWhenFinished);
return m_lastSemanticInfo;
}
void CppEditorSupport::recalculateSemanticInfoDetached(bool force)
{
m_futureSemanticInfo.cancel();
SemanticInfo::Source source = currentSource(force);
m_futureSemanticInfo = QtConcurrent::run<CppEditorSupport, void>(
&CppEditorSupport::recalculateSemanticInfoDetached_helper, this, source);
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 {
_updateDocumentTimer->stop();
m_updateDocumentTimer->stop();
QStringList sourceFiles(_textEditor->document()->fileName());
_cachedContents = _textEditor->textDocument()->contents();
_documentParser = _modelManager->updateSourceFiles(sourceFiles);
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 {
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;
// 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);
}

View File

@@ -30,63 +30,154 @@
#ifndef CPPTOOLSEDITORSUPPORT_H
#define CPPTOOLSEDITORSUPPORT_H
#include "cpphighlightingsupport.h"
#include "cppmodelmanager.h"
#include "cppsemanticinfo.h"
#include <cplusplus/CppDocument.h>
#include <QFuture>
#include <QObject>
#include <QPointer>
#include <QFuture>
QT_BEGIN_NAMESPACE
class QTimer;
QT_END_NAMESPACE
#include <QTimer>
namespace CPlusPlus { class AST; }
namespace TextEditor {
class ITextEditor;
class BaseTextEditor;
class ITextMark;
} // namespace TextEditor
namespace CppTools {
namespace Internal {
class CppModelManager;
class CppEditorSupport: public QObject
/**
* \brief The CppEditorSupport class oversees the actions that happen when a C++ text editor updates
* its document.
*
* The following steps are taken:
* 1. the text editor fires a contentsChanged() signal that triggers updateDocument
* 2. update document will start a timer, or reset the timer if it was already running. This way
* subsequent updates (e.g. keypresses) get bunched together instead of running the subsequent
* actions for every key press
* 3. when the timer from step 2 fires, updateDocumentNow() is triggered. That tells the
* model-manager to update the CPlusPlus::Document by re-indexing it.
* 4. when the model-manager finishes, it fires a documentUpdated(CPlusPlus::Document::Ptr) signal,
* that is connected to onDocumentUpdated(CPlusPlus::Document::Ptr), which does 4 things:
* a) updates the ifdeffed-out blocks in the EditorUpdate
* b) calls setExtraDiagnostics with the diagnostics from the parser, which in turn calls
* onDiagnosticsChanged on the UI thread, and that schedules an editor update timer. When this
* timer fires, updateEditorNow() is called, which will apply the updates to the editor.
* c) a semantic-info recalculation is started in a future
* d) the documentUpdated() signal is emitted, which can be used by a widget to do things
* 5. semantic-info calculation from 4c is done by a future that calls recalculateSemanticInfoNow(),
* which emits semanticInfoUpdated() when it is finished. Editors can also listen in on this
* signal to do things like highlighting the local usages.
* 6. the semanticInfoUpdated() is connected to the startHighlighting() slot, which will start
* another future for doing the semantic highlighting. The highlighterStarted signal is emitted,
* with the highlighting future as a parameter, so editors can hook it up to a QFutureWatcher
* and get notifications.
*
* Both the semantic info calculation and the highlighting calculation will cancel an already running
* future. They will also check that the result of a previous step is not already outdated, meaning
* that they check the revision of the editor document to see if a user changed the document while
* the calculation was running.
*/
class CPPTOOLS_EXPORT CppEditorSupport: public QObject
{
Q_OBJECT
public:
CppEditorSupport(CppModelManager *modelManager);
CppEditorSupport(Internal::CppModelManager *modelManager, TextEditor::BaseTextEditor *textEditor);
virtual ~CppEditorSupport();
TextEditor::ITextEditor *textEditor() const;
void setTextEditor(TextEditor::ITextEditor *textEditor);
QString fileName() const;
int updateDocumentInterval() const;
void setUpdateDocumentInterval(int updateDocumentInterval);
QString contents();
QString contents() const;
unsigned editorRevision() const;
Q_SIGNALS:
void contentsChanged();
void setExtraDiagnostics(const QString &key,
const QList<CPlusPlus::Document::DiagnosticMessage> &messages);
private Q_SLOTS:
/// Retrieve the semantic info, which will get recalculated on the current
/// thread if it is outdate.
SemanticInfo recalculateSemanticInfo(bool emitSignalWhenFinished = true);
/// Recalculates the semantic info in a future, and will emit the semanticInfoUpdated() signal
/// when finished.
/// \param force do not check if the old semantic info is still valid
void recalculateSemanticInfoDetached(bool force = false);
signals:
void documentUpdated();
void diagnosticsChanged();
void semanticInfoUpdated(CppTools::SemanticInfo);
void highlighterStarted(QFuture<TextEditor::HighlightingResult>, unsigned revision);
private slots:
void updateDocument();
void updateDocumentNow();
private:
enum { UPDATE_DOCUMENT_DEFAULT_INTERVAL = 150 };
void onDocumentUpdated(CPlusPlus::Document::Ptr doc);
void startHighlighting();
CppModelManager *_modelManager;
QPointer<TextEditor::ITextEditor> _textEditor;
QTimer *_updateDocumentTimer;
int _updateDocumentInterval;
unsigned _revision;
QFuture<void> _documentParser;
QString _cachedContents;
void onDiagnosticsChanged();
void updateEditor();
void updateEditorNow();
private:
typedef TextEditor::BaseTextEditorWidget::BlockRange BlockRange;
struct EditorUpdates {
EditorUpdates()
: revision(-1)
{}
int revision;
QList<QTextEdit::ExtraSelection> selections;
QList<BlockRange> ifdefedOutBlocks;
};
enum {
UpdateDocumentDefaultInterval = 150,
UpdateEditorInterval = 300
};
private:
SemanticInfo::Source currentSource(bool force);
void recalculateSemanticInfoNow(const SemanticInfo::Source &source, bool emitSignalWhenFinished,
CPlusPlus::TopLevelDeclarationProcessor *processor = 0);
void recalculateSemanticInfoDetached_helper(QFutureInterface<void> &future,
SemanticInfo::Source source);
private:
Internal::CppModelManager *m_modelManager;
QPointer<TextEditor::BaseTextEditor> m_textEditor;
QTimer *m_updateDocumentTimer;
int m_updateDocumentInterval;
unsigned m_revision;
QFuture<void> m_documentParser;
// content caching
mutable QString m_cachedContents;
mutable int m_cachedContentsEditorRevision;
QTimer *m_updateEditorTimer;
EditorUpdates m_editorUpdates;
QMutex m_diagnosticsMutex;
QHash<QString, QList<CPlusPlus::Document::DiagnosticMessage> > m_allDiagnostics;
// Semantic info:
bool m_initialized;
mutable QMutex m_lastSemanticInfoLock;
SemanticInfo m_lastSemanticInfo;
QFuture<void> m_futureSemanticInfo;
// Highlighting:
unsigned m_lastHighlightRevision;
QFuture<TextEditor::HighlightingResult> m_highlighter;
QScopedPointer<CppTools::CppHighlightingSupport> m_highlightingSupport;
};
} // namespace Internal
} // namespace CppTools
#endif // CPPTOOLSEDITORSUPPORT_H