forked from qt-creator/qt-creator
452 lines
15 KiB
C++
452 lines
15 KiB
C++
|
|
/****************************************************************************
|
||
|
|
**
|
||
|
|
** 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 "cppuseselectionsupdater.h"
|
||
|
|
|
||
|
|
#include "cppcanonicalsymbol.h"
|
||
|
|
#include "cppeditor.h"
|
||
|
|
|
||
|
|
#include <cpptools/cpplocalsymbols.h>
|
||
|
|
#include <cpptools/cppmodelmanagerinterface.h>
|
||
|
|
#include <cpptools/cpptoolsreuse.h>
|
||
|
|
#include <texteditor/basetexteditor.h>
|
||
|
|
#include <texteditor/fontsettings.h>
|
||
|
|
|
||
|
|
#include <cplusplus/Macro.h>
|
||
|
|
#include <cplusplus/TranslationUnit.h>
|
||
|
|
|
||
|
|
#include <utils/qtcassert.h>
|
||
|
|
|
||
|
|
#include <QtConcurrentRun>
|
||
|
|
#include <QTextBlock>
|
||
|
|
#include <QTextCursor>
|
||
|
|
#include <QTextEdit>
|
||
|
|
|
||
|
|
using namespace CPlusPlus;
|
||
|
|
|
||
|
|
enum { updateUseSelectionsInternalInMs = 500 };
|
||
|
|
|
||
|
|
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;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
QTextEdit::ExtraSelection extraSelection(const QTextCharFormat &format, const QTextCursor &cursor)
|
||
|
|
{
|
||
|
|
QTextEdit::ExtraSelection selection;
|
||
|
|
selection.format = format;
|
||
|
|
selection.cursor = cursor;
|
||
|
|
return selection;
|
||
|
|
}
|
||
|
|
|
||
|
|
struct Params
|
||
|
|
{
|
||
|
|
// 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 CppEditor::Internal::CanonicalSymbol;
|
||
|
|
if (Symbol *s = CanonicalSymbol::canonicalSymbol(p.scope, p.expression, typeOfExpression)) {
|
||
|
|
CppTools::CppModelManagerInterface *mmi = CppTools::CppModelManagerInterface::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::BaseTextEditorWidget *editorWidget)
|
||
|
|
: m_editorWidget(editorWidget)
|
||
|
|
, m_findUsesRevision(-1)
|
||
|
|
{
|
||
|
|
m_timer.setSingleShot(true);
|
||
|
|
m_timer.setInterval(updateUseSelectionsInternalInMs);
|
||
|
|
connect(&m_timer, SIGNAL(timeout()), this, SLOT(update()));
|
||
|
|
}
|
||
|
|
|
||
|
|
void CppUseSelectionsUpdater::scheduleUpdate()
|
||
|
|
{
|
||
|
|
m_timer.start();
|
||
|
|
}
|
||
|
|
|
||
|
|
void CppUseSelectionsUpdater::abortSchedule()
|
||
|
|
{
|
||
|
|
m_timer.stop();
|
||
|
|
}
|
||
|
|
|
||
|
|
void CppUseSelectionsUpdater::update()
|
||
|
|
{
|
||
|
|
CppEditorWidget *cppEditorWidget = qobject_cast<CppEditorWidget *>(m_editorWidget);
|
||
|
|
QTC_ASSERT(cppEditorWidget, return);
|
||
|
|
QTC_CHECK(cppEditorWidget->isSemanticInfoValidExceptLocalUses());
|
||
|
|
const CppTools::SemanticInfo semanticInfo = cppEditorWidget->semanticInfo();
|
||
|
|
const Document::Ptr document = semanticInfo.doc;
|
||
|
|
const Snapshot snapshot = semanticInfo.snapshot;
|
||
|
|
|
||
|
|
if (!document || document->editorRevision() != static_cast<unsigned>(textDocument()->revision()))
|
||
|
|
return;
|
||
|
|
|
||
|
|
QTC_ASSERT(document->translationUnit(), return);
|
||
|
|
QTC_ASSERT(document->translationUnit()->ast(), return);
|
||
|
|
QTC_ASSERT(!snapshot.isEmpty(), return);
|
||
|
|
|
||
|
|
QTextCursor textCursor = m_editorWidget->textCursor();
|
||
|
|
|
||
|
|
if (handleMacroCase(textCursor, document)) {
|
||
|
|
emit finished(CppTools::SemanticInfo::LocalUseMap());
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
handleSymbolCase(textCursor, 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;
|
||
|
|
|
||
|
|
const UseSelectionsResult result = m_findUsesWatcher->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());
|
||
|
|
}
|
||
|
|
|
||
|
|
updateUnusedSelections(toExtraSelections(result.selectionsForLocalUnusedVariables,
|
||
|
|
TextEditor::C_OCCURRENCES_UNUSED));
|
||
|
|
|
||
|
|
m_findUsesWatcher.reset();
|
||
|
|
m_document.reset();
|
||
|
|
m_snapshot = Snapshot();
|
||
|
|
|
||
|
|
emit selectionsForVariableUnderCursorUpdated(localVariableSelections);
|
||
|
|
emit finished(result.localUses);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CppUseSelectionsUpdater::handleMacroCase(const QTextCursor &textCursor,
|
||
|
|
const Document::Ptr document)
|
||
|
|
{
|
||
|
|
const Macro *macro = CppTools::findCanonicalMacro(textCursor, document);
|
||
|
|
if (!macro)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
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));
|
||
|
|
}
|
||
|
|
|
||
|
|
// 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;
|
||
|
|
|
||
|
|
QTextCursor cursor(textDocument());
|
||
|
|
cursor.setPosition(use.utf16charsBegin());
|
||
|
|
cursor.setPosition(use.utf16charsEnd(), QTextCursor::KeepAnchor);
|
||
|
|
|
||
|
|
selections.append(extraSelection(occurrencesFormat, cursor));
|
||
|
|
}
|
||
|
|
|
||
|
|
updateUseSelections(selections);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void CppUseSelectionsUpdater::handleSymbolCase(const QTextCursor &textCursor,
|
||
|
|
const Document::Ptr document,
|
||
|
|
const Snapshot &snapshot)
|
||
|
|
{
|
||
|
|
m_document = document;
|
||
|
|
m_snapshot = snapshot;
|
||
|
|
|
||
|
|
if (m_findUsesWatcher)
|
||
|
|
m_findUsesWatcher->cancel();
|
||
|
|
m_findUsesWatcher.reset(new QFutureWatcher<UseSelectionsResult>);
|
||
|
|
connect(m_findUsesWatcher.data(), SIGNAL(finished()), this, SLOT(onFindUsesFinished()));
|
||
|
|
|
||
|
|
m_findUsesRevision = textDocument()->revision();
|
||
|
|
m_findUsesCursorPosition = m_editorWidget->position();
|
||
|
|
|
||
|
|
Params params;
|
||
|
|
params.document = document;
|
||
|
|
m_editorWidget->convertPosition(m_findUsesCursorPosition, ¶ms.line, ¶ms.column);
|
||
|
|
CanonicalSymbol canonicalSymbol(document, snapshot);
|
||
|
|
params.scope = canonicalSymbol.getScopeAndExpression(textCursor, ¶ms.expression);
|
||
|
|
params.snapshot = snapshot;
|
||
|
|
|
||
|
|
m_findUsesWatcher->setFuture(QtConcurrent::run(&findUses, params));
|
||
|
|
}
|
||
|
|
|
||
|
|
ExtraSelections CppUseSelectionsUpdater::toExtraSelections(const SemanticUses &uses,
|
||
|
|
TextEditor::TextStyle style) const
|
||
|
|
{
|
||
|
|
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);
|
||
|
|
|
||
|
|
result.append(sel);
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
ExtraSelections CppUseSelectionsUpdater::toExtraSelections(const QList<int> &references,
|
||
|
|
TextEditor::TextStyle style) const
|
||
|
|
{
|
||
|
|
ExtraSelections selections;
|
||
|
|
foreach (int index, references) {
|
||
|
|
unsigned line, column;
|
||
|
|
TranslationUnit *unit = m_document->translationUnit();
|
||
|
|
unit->getTokenPosition(index, &line, &column);
|
||
|
|
|
||
|
|
if (column)
|
||
|
|
--column; // adjust the column position.
|
||
|
|
|
||
|
|
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);
|
||
|
|
|
||
|
|
selections.append(extraSelection(textCharFormat(style), cursor));
|
||
|
|
}
|
||
|
|
|
||
|
|
return selections;
|
||
|
|
}
|
||
|
|
|
||
|
|
QTextCharFormat CppUseSelectionsUpdater::textCharFormat(TextEditor::TextStyle category) const
|
||
|
|
{
|
||
|
|
return m_editorWidget->textDocument()->fontSettings().toTextCharFormat(category);
|
||
|
|
}
|
||
|
|
|
||
|
|
QTextDocument *CppUseSelectionsUpdater::textDocument() const
|
||
|
|
{
|
||
|
|
return m_editorWidget->document();
|
||
|
|
}
|
||
|
|
|
||
|
|
ExtraSelections CppUseSelectionsUpdater::currentUseSelections() const
|
||
|
|
{
|
||
|
|
return m_editorWidget->extraSelections(
|
||
|
|
TextEditor::BaseTextEditorWidget::CodeSemanticsSelection);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CppUseSelectionsUpdater::updateUseSelections(const ExtraSelections &selections)
|
||
|
|
{
|
||
|
|
m_editorWidget->setExtraSelections(TextEditor::BaseTextEditorWidget::CodeSemanticsSelection,
|
||
|
|
selections);
|
||
|
|
}
|
||
|
|
|
||
|
|
void CppUseSelectionsUpdater::updateUnusedSelections(const ExtraSelections &selections)
|
||
|
|
{
|
||
|
|
m_editorWidget->setExtraSelections(TextEditor::BaseTextEditorWidget::UnusedSymbolSelection,
|
||
|
|
selections);
|
||
|
|
}
|
||
|
|
|
||
|
|
} // namespace Internal
|
||
|
|
} // namespace CppEditor
|