Commit fdf0a104 authored by Nikolai Kosjar's avatar Nikolai Kosjar

CppEditor: Generalize CppUseSelectionsUpdater

Let CppUseSelectionsUpdater delegate the work to
*EditorDocumentProcessor so that the clang code model can also provide
results.

Change-Id: I6872afbfeea1a5c4a64fdf19fcb1992f134dde08
Reviewed-by: Marco Bubke's avatarMarco Bubke <marco.bubke@qt.io>
parent cd6bd3c4
......@@ -287,6 +287,12 @@ void ClangEditorDocumentProcessor::setParserConfig(
m_builtinProcessor.parser()->setConfiguration(config);
}
QFuture<CppTools::CursorInfo>
ClangEditorDocumentProcessor::cursorInfo(const CppTools::CursorInfoParams &params)
{
return m_builtinProcessor.cursorInfo(params);
}
ClangBackEnd::FileContainer ClangEditorDocumentProcessor::fileContainerWithArguments() const
{
return fileContainerWithArguments(m_projectPart.data());
......
......@@ -84,6 +84,8 @@ public:
void setParserConfig(const CppTools::BaseEditorDocumentParser::Configuration config) override;
QFuture<CppTools::CursorInfo> cursorInfo(const CppTools::CursorInfoParams &params) override;
ClangBackEnd::FileContainer fileContainerWithArguments() const;
void clearDiagnosticsWithFixIts();
......
......@@ -370,6 +370,12 @@ ParseContextModel &CppEditorDocument::parseContextModel()
return m_parseContextModel;
}
QFuture<CppTools::CursorInfo>
CppEditorDocument::cursorInfo(const CppTools::CursorInfoParams &params)
{
return processor()->cursorInfo(params);
}
const MinimizableInfoBars &CppEditorDocument::minimizableInfoBars() const
{
return m_minimizableInfoBars;
......
......@@ -66,6 +66,8 @@ public:
const MinimizableInfoBars &minimizableInfoBars() const;
ParseContextModel &parseContextModel();
QFuture<CppTools::CursorInfo> cursorInfo(const CppTools::CursorInfoParams &params);
signals:
void codeWarningsUpdated(unsigned contentsRevision,
const QList<QTextEdit::ExtraSelection> selections,
......
......@@ -26,211 +26,21 @@
#include "cppuseselectionsupdater.h"
#include "cppeditor.h"
#include <cpptools/cppcanonicalsymbol.h>
#include <cpptools/cpplocalsymbols.h>
#include <cpptools/cppmodelmanager.h>
#include <cpptools/cpptoolsreuse.h>
#include <texteditor/texteditor.h>
#include <texteditor/textdocument.h>
#include <texteditor/convenience.h>
#include <texteditor/fontsettings.h>
#include <cplusplus/Macro.h>
#include <cplusplus/TranslationUnit.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
#include "cppeditordocument.h"
#include <QTextBlock>
#include <QTextCursor>
#include <QTextEdit>
using namespace CPlusPlus;
#include <utils/qtcassert.h>
enum { updateUseSelectionsInternalInMs = 500 };
namespace {
class FunctionDefinitionUnderCursor: protected ASTVisitor
{
unsigned _line = 0;
unsigned _column = 0;
DeclarationAST *_functionDefinition = nullptr;
public:
FunctionDefinitionUnderCursor(TranslationUnit *translationUnit)
: ASTVisitor(translationUnit)
{ }
DeclarationAST *operator()(AST *ast, unsigned line, unsigned column)
{
_functionDefinition = nullptr;
_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;
}
};
QTextEdit::ExtraSelection extraSelection(const QTextCharFormat &format, const QTextCursor &cursor)
{
QTextEdit::ExtraSelection selection;
selection.format = format;
selection.cursor = cursor;
return selection;
}
class Params
{
public:
Params(const QTextCursor &textCursor, const Document::Ptr document, const Snapshot &snapshot)
: document(document), snapshot(snapshot)
{
TextEditor::Convenience::convertPosition(textCursor.document(), textCursor.position(),
&line, &column);
CppTools::CanonicalSymbol canonicalSymbol(document, snapshot);
scope = canonicalSymbol.getScopeAndExpression(textCursor, &expression);
}
public:
// Shared
Document::Ptr document;
// For local use calculation
int line;
int column;
// For references calculation
Scope *scope;
QString expression;
Snapshot snapshot;
};
using CppEditor::Internal::SemanticUses;
void splitLocalUses(const CppTools::SemanticInfo::LocalUseMap &uses,
const Params &p,
SemanticUses *selectionsForLocalVariableUnderCursor,
SemanticUses *selectionsForLocalUnusedVariables)
{
QTC_ASSERT(selectionsForLocalVariableUnderCursor, return);
QTC_ASSERT(selectionsForLocalUnusedVariables, return);
LookupContext context(p.document, p.snapshot);
CppTools::SemanticInfo::LocalUseIterator it(uses);
while (it.hasNext()) {
it.next();
const SemanticUses &uses = it.value();
bool good = false;
foreach (const CppTools::SemanticInfo::Use &use, uses) {
unsigned l = p.line;
unsigned c = p.column + 1; // convertCursorPosition() returns a 0-based column number.
if (l == use.line && c >= use.column && c <= (use.column + use.length)) {
good = true;
break;
}
}
if (uses.size() == 1) {
if (!CppTools::isOwnershipRAIIType(it.key(), context))
selectionsForLocalUnusedVariables->append(uses); // unused declaration
} else if (good && selectionsForLocalVariableUnderCursor->isEmpty()) {
selectionsForLocalVariableUnderCursor->append(uses);
}
}
}
CppTools::SemanticInfo::LocalUseMap findLocalUses(const Params &p)
{
AST *ast = p.document->translationUnit()->ast();
FunctionDefinitionUnderCursor functionDefinitionUnderCursor(p.document->translationUnit());
DeclarationAST *declaration = functionDefinitionUnderCursor(ast, p.line, p.column);
return CppTools::LocalSymbols(p.document, declaration).uses;
}
QList<int> findReferences(const Params &p)
{
QList<int> result;
if (!p.scope || p.expression.isEmpty())
return result;
TypeOfExpression typeOfExpression;
Snapshot snapshot = p.snapshot;
snapshot.insert(p.document);
typeOfExpression.init(p.document, snapshot);
typeOfExpression.setExpandTemplates(true);
using CppTools::CanonicalSymbol;
if (Symbol *s = CanonicalSymbol::canonicalSymbol(p.scope, p.expression, typeOfExpression)) {
CppTools::CppModelManager *mmi = CppTools::CppModelManager::instance();
result = mmi->references(s, typeOfExpression.context());
}
return result;
}
CppEditor::Internal::UseSelectionsResult findUses(const Params p)
{
CppEditor::Internal::UseSelectionsResult result;
const CppTools::SemanticInfo::LocalUseMap localUses = findLocalUses(p);
result.localUses = localUses;
splitLocalUses(localUses, p, &result.selectionsForLocalVariableUnderCursor,
&result.selectionsForLocalUnusedVariables);
if (!result.selectionsForLocalVariableUnderCursor.isEmpty())
return result;
result.references = findReferences(p);
return result; // OK, result.selectionsForLocalUnusedVariables will be passed on
}
} // anonymous namespace
namespace CppEditor {
namespace Internal {
CppUseSelectionsUpdater::CppUseSelectionsUpdater(TextEditor::TextEditorWidget *editorWidget)
: m_editorWidget(editorWidget)
, m_findUsesRevision(-1)
, m_runnerRevision(-1)
{
m_timer.setSingleShot(true);
m_timer.setInterval(updateUseSelectionsInternalInMs);
......@@ -249,224 +59,112 @@ void CppUseSelectionsUpdater::abortSchedule()
void CppUseSelectionsUpdater::update(CallType callType)
{
CppEditorWidget *cppEditorWidget = qobject_cast<CppEditorWidget *>(m_editorWidget);
auto *cppEditorWidget = qobject_cast<CppEditorWidget *>(m_editorWidget);
QTC_ASSERT(cppEditorWidget, return);
if (!cppEditorWidget->isSemanticInfoValidExceptLocalUses())
return;
const CppTools::SemanticInfo semanticInfo = cppEditorWidget->semanticInfo();
const Document::Ptr document = semanticInfo.doc;
const Snapshot snapshot = semanticInfo.snapshot;
if (!document)
return;
if (semanticInfo.revision != static_cast<unsigned>(textDocument()->revision()))
return;
QTC_ASSERT(document->translationUnit(), return);
QTC_ASSERT(document->translationUnit()->ast(), return);
QTC_ASSERT(!snapshot.isEmpty(), return);
if (handleMacroCase(document)) {
emit finished(CppTools::SemanticInfo::LocalUseMap());
return;
}
if (callType == Asynchronous)
handleSymbolCaseAsynchronously(document, snapshot);
else
handleSymbolCaseSynchronously(document, snapshot);
}
void CppUseSelectionsUpdater::onFindUsesFinished()
{
QTC_ASSERT(m_findUsesWatcher, return);
if (m_findUsesWatcher->isCanceled())
return;
if (m_findUsesRevision != textDocument()->revision())
return;
// Optimizable: If the cursor is still on the same identifier the results are valid.
if (m_findUsesCursorPosition != m_editorWidget->position())
return;
processSymbolCaseResults(m_findUsesWatcher->result());
auto *cppEditorDocument = qobject_cast<CppEditorDocument *>(cppEditorWidget->textDocument());
QTC_ASSERT(cppEditorDocument, return);
m_findUsesWatcher.reset();
m_document.reset();
}
CppTools::CursorInfoParams params;
params.semanticInfo = cppEditorWidget->semanticInfo();
params.textCursor = cppEditorWidget->textCursor();
bool CppUseSelectionsUpdater::handleMacroCase(const Document::Ptr document)
{
const Macro *macro = CppTools::findCanonicalMacro(m_editorWidget->textCursor(), document);
if (!macro)
return false;
if (callType == Asynchronous) {
if (m_runnerWatcher)
m_runnerWatcher->cancel();
const QTextCharFormat &occurrencesFormat = textCharFormat(TextEditor::C_OCCURRENCES);
ExtraSelections selections;
// Macro definition
if (macro->fileName() == document->fileName()) {
QTextCursor cursor(textDocument());
cursor.setPosition(macro->utf16CharOffset());
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,
macro->nameToQString().size());
selections.append(extraSelection(occurrencesFormat, cursor));
}
m_runnerWatcher.reset(new QFutureWatcher<CursorInfo>);
connect(m_runnerWatcher.data(), &QFutureWatcherBase::finished,
this, &CppUseSelectionsUpdater::onFindUsesFinished);
// Other macro uses
foreach (const Document::MacroUse &use, document->macroUses()) {
const Macro &useMacro = use.macro();
if (useMacro.line() != macro->line()
|| useMacro.utf16CharOffset() != macro->utf16CharOffset()
|| useMacro.length() != macro->length()
|| useMacro.fileName() != macro->fileName())
continue;
m_runnerRevision = m_editorWidget->document()->revision();
m_runnerCursorPosition = m_editorWidget->position();
QTextCursor cursor(textDocument());
cursor.setPosition(use.utf16charsBegin());
cursor.setPosition(use.utf16charsEnd(), QTextCursor::KeepAnchor);
m_runnerWatcher->setFuture(cppEditorDocument->cursorInfo(params));
} else { // synchronous case
QFuture<CursorInfo> future = cppEditorDocument->cursorInfo(params);
future.waitForFinished();
selections.append(extraSelection(occurrencesFormat, cursor));
processResults(future.result());
}
updateUseSelections(selections);
return true;
}
void CppUseSelectionsUpdater::handleSymbolCaseAsynchronously(const Document::Ptr document,
const Snapshot &snapshot)
{
m_document = document;
if (m_findUsesWatcher)
m_findUsesWatcher->cancel();
m_findUsesWatcher.reset(new QFutureWatcher<UseSelectionsResult>);
connect(m_findUsesWatcher.data(), &QFutureWatcherBase::finished, this, &CppUseSelectionsUpdater::onFindUsesFinished);
m_findUsesRevision = textDocument()->revision();
m_findUsesCursorPosition = m_editorWidget->position();
const Params params = Params(m_editorWidget->textCursor(), document, snapshot);
m_findUsesWatcher->setFuture(Utils::runAsync(findUses, params));
}
void CppUseSelectionsUpdater::handleSymbolCaseSynchronously(const Document::Ptr document,
const Snapshot &snapshot)
void CppUseSelectionsUpdater::processResults(const CursorInfo &result)
{
const Document::Ptr previousDocument = m_document;
m_document = document;
const Params params = Params(m_editorWidget->textCursor(), document, snapshot);
const UseSelectionsResult result = findUses(params);
processSymbolCaseResults(result);
m_document = previousDocument;
}
void CppUseSelectionsUpdater::processSymbolCaseResults(const UseSelectionsResult &result)
{
const bool hasUsesForLocalVariable = !result.selectionsForLocalVariableUnderCursor.isEmpty();
const bool hasReferences = !result.references.isEmpty();
ExtraSelections localVariableSelections;
if (hasUsesForLocalVariable) {
localVariableSelections = toExtraSelections(result.selectionsForLocalVariableUnderCursor,
TextEditor::C_OCCURRENCES);
updateUseSelections(localVariableSelections);
} else if (hasReferences) {
const ExtraSelections selections = toExtraSelections(result.references,
TextEditor::C_OCCURRENCES);
updateUseSelections(selections);
} else {
if (!currentUseSelections().isEmpty())
updateUseSelections(ExtraSelections());
if (!result.useRanges.isEmpty() || !currentUseSelections().isEmpty()) {
ExtraSelections selections = updateUseSelections(result.useRanges);
if (result.areUseRangesForLocalVariable)
localVariableSelections = selections;
}
updateUnusedSelections(toExtraSelections(result.selectionsForLocalUnusedVariables,
TextEditor::C_OCCURRENCES_UNUSED));
updateUnusedSelections(result.unusedVariablesRanges);
emit selectionsForVariableUnderCursorUpdated(localVariableSelections);
emit finished(result.localUses);
}
ExtraSelections CppUseSelectionsUpdater::toExtraSelections(const SemanticUses &uses,
TextEditor::TextStyle style) const
void CppUseSelectionsUpdater::onFindUsesFinished()
{
ExtraSelections result;
foreach (const CppTools::SemanticInfo::Use &use, uses) {
if (use.isInvalid())
continue;
QTextDocument *document = textDocument();
const int position = document->findBlockByNumber(use.line - 1).position() + use.column - 1;
const int anchor = position + use.length;
QTextEdit::ExtraSelection sel;
sel.format = textCharFormat(style);
sel.cursor = QTextCursor(document);
sel.cursor.setPosition(anchor);
sel.cursor.setPosition(position, QTextCursor::KeepAnchor);
QTC_ASSERT(m_runnerWatcher, return);
if (m_runnerWatcher->isCanceled())
return;
if (m_runnerRevision != m_editorWidget->document()->revision())
return;
// Optimizable: If the cursor is still on the same identifier the results should be valid.
if (m_runnerCursorPosition != m_editorWidget->position())
return;
result.append(sel);
}
processResults(m_runnerWatcher->result());
return result;
m_runnerWatcher.reset();
}
ExtraSelections CppUseSelectionsUpdater::toExtraSelections(const QList<int> &references,
TextEditor::TextStyle style) const
CppUseSelectionsUpdater::ExtraSelections
CppUseSelectionsUpdater::toExtraSelections(const CursorInfo::Ranges &ranges,
TextEditor::TextStyle style)
{
ExtraSelections selections;
QTC_ASSERT(m_document, return selections);
foreach (int index, references) {
unsigned line, column;
TranslationUnit *unit = m_document->translationUnit();
unit->getTokenPosition(index, &line, &column);
CppUseSelectionsUpdater::ExtraSelections selections;
selections.reserve(ranges.size());
if (column)
--column; // adjust the column position.
for (const CursorInfo::Range &range : ranges) {
QTextDocument *document = m_editorWidget->document();
const int position
= document->findBlockByNumber(static_cast<int>(range.line) - 1).position()
+ static_cast<int>(range.column) - 1;
const int anchor = position + static_cast<int>(range.length);
const int len = unit->tokenAt(index).utf16chars();
QTextCursor cursor(textDocument()->findBlockByNumber(line - 1));
cursor.setPosition(cursor.position() + column);
cursor.setPosition(cursor.position() + len, QTextCursor::KeepAnchor);
QTextEdit::ExtraSelection sel;
sel.format = m_editorWidget->textDocument()->fontSettings().toTextCharFormat(style);
sel.cursor = QTextCursor(document);
sel.cursor.setPosition(anchor);
sel.cursor.setPosition(position, QTextCursor::KeepAnchor);
selections.append(extraSelection(textCharFormat(style), cursor));
selections.append(sel);
}
return selections;
}
QTextCharFormat CppUseSelectionsUpdater::textCharFormat(TextEditor::TextStyle category) const
CppUseSelectionsUpdater::ExtraSelections
CppUseSelectionsUpdater::currentUseSelections() const
{
return m_editorWidget->textDocument()->fontSettings().toTextCharFormat(category);
return m_editorWidget->extraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection);
}
QTextDocument *CppUseSelectionsUpdater::textDocument() const
{
return m_editorWidget->document();
}
ExtraSelections CppUseSelectionsUpdater::currentUseSelections() const
{
return m_editorWidget->extraSelections(
TextEditor::TextEditorWidget::CodeSemanticsSelection);
}
void CppUseSelectionsUpdater::updateUseSelections(const ExtraSelections &selections)
CppUseSelectionsUpdater::ExtraSelections
CppUseSelectionsUpdater::updateUseSelections(const CursorInfo::Ranges &ranges)
{
const ExtraSelections selections = toExtraSelections(ranges, TextEditor::C_OCCURRENCES);
m_editorWidget->setExtraSelections(TextEditor::TextEditorWidget::CodeSemanticsSelection,
selections);
return selections;
}
void CppUseSelectionsUpdater::updateUnusedSelections(const ExtraSelections &selections)
void CppUseSelectionsUpdater::updateUnusedSelections(const CursorInfo::Ranges &ranges)
{
const ExtraSelections selections = toExtraSelections(ranges, TextEditor::C_OCCURRENCES_UNUSED);
m_editorWidget->setExtraSelections(TextEditor::TextEditorWidget::UnusedSymbolSelection,
selections);
}
......
......@@ -25,36 +25,18 @@
#pragma once
#include <cpptools/cppcursorinfo.h>
#include <cpptools/cppsemanticinfo.h>
#include <texteditor/texteditorconstants.h>
#include <cplusplus/CppDocument.h>
#include <QFutureWatcher>
#include <QTextEdit>
#include <QTimer>
QT_BEGIN_NAMESPACE
class QTextCharFormat;
class QTextCursor;
QT_END_NAMESPACE
namespace TextEditor { class TextEditorWidget; }
namespace CppEditor {
namespace Internal {
typedef QList<QTextEdit::ExtraSelection> ExtraSelections;
typedef QList<CppTools::SemanticInfo::Use> SemanticUses;
struct UseSelectionsResult
{
CppTools::SemanticInfo::LocalUseMap localUses;
SemanticUses selectionsForLocalVariableUnderCursor;
SemanticUses selectionsForLocalUnusedVariables;
QList<int> references;
};