Commit 11aeaea8 authored by Nikolai Kosjar's avatar Nikolai Kosjar
Browse files

CppEditor: "Follow Symbol Under Cursor" for virtual functions



F2 on a virtual function call presents a list of overrides in derived
classes. The function declaration of the static type is shown
immediately at the top.

Task-number: QTCREATORBUG-9611

Change-Id: I80ce906fa06272dc9fbd1662cd17500b8c77067f
Reviewed-by: default avatarJoerg Bornemann <joerg.bornemann@digia.com>
parent 3a64f8a3
......@@ -159,7 +159,7 @@ static bool isValidFileNameChar(const QChar &c)
}
CMakeEditorWidget::Link CMakeEditorWidget::findLinkAt(const QTextCursor &cursor,
bool/* resolveTarget*/)
bool/* resolveTarget*/, bool /*inNextSplit*/)
{
Link link;
......
......@@ -78,8 +78,7 @@ public:
CMakeEditorFactory *factory() { return m_factory; }
TextEditor::TextEditorActionHandler *actionHandler() const { return m_ah; }
Link findLinkAt(const QTextCursor &cursor,
bool resolveTarget = true);
Link findLinkAt(const QTextCursor &cursor, bool resolveTarget = true, bool inNextSplit = false);
protected:
TextEditor::BaseTextEditor *createEditor();
......
......@@ -511,6 +511,7 @@ CPPEditorWidget::CPPEditorWidget(QWidget *parent)
, m_firstRenameChange(false)
, m_objcEnabled(false)
, m_commentsSettings(CppTools::CppToolsSettings::instance()->commentsSettings())
, m_followSymbolUnderCursor(new FollowSymbolUnderCursor(this))
{
qRegisterMetaType<SemanticInfo>("CppTools::SemanticInfo");
......@@ -1239,14 +1240,14 @@ QString CPPEditorWidget::identifierUnderCursor(QTextCursor *macroCursor)
return macroCursor->selectedText();
}
CPPEditorWidget::Link CPPEditorWidget::findLinkAt(const QTextCursor &cursor, bool resolveTarget)
CPPEditorWidget::Link CPPEditorWidget::findLinkAt(const QTextCursor &cursor, bool resolveTarget,
bool inNextSplit)
{
if (!m_modelManager)
return Link();
FollowSymbolUnderCursor followSymbolUnderCursor(this, cursor, resolveTarget,
m_modelManager->snapshot(), m_lastSemanticInfo.doc, symbolFinder());
return followSymbolUnderCursor.findLink();
return m_followSymbolUnderCursor->findLink(cursor, resolveTarget, m_modelManager->snapshot(),
m_lastSemanticInfo.doc, symbolFinder(), inNextSplit);
}
unsigned CPPEditorWidget::editorRevision() const
......@@ -1720,6 +1721,8 @@ TextEditor::IAssistInterface *CPPEditorWidget::createAssistInterface(
if (!semanticInfo().doc || isOutdated())
return 0;
return new CppQuickFixAssistInterface(const_cast<CPPEditorWidget *>(this), reason);
} else {
return BaseTextEditorWidget::createAssistInterface(kind, reason);
}
return 0;
}
......@@ -1812,6 +1815,11 @@ void CPPEditorWidget::updateContentsChangedSignal()
this, SLOT(onContentsChanged(int,int,int)));
}
FollowSymbolUnderCursor *CPPEditorWidget::followSymbolUnderCursorDelegate()
{
return m_followSymbolUnderCursor.data();
}
void CPPEditorWidget::abortDeclDefLink()
{
if (!m_declDefLink)
......
......@@ -30,6 +30,7 @@
#ifndef CPPEDITOR_H
#define CPPEDITOR_H
#include "cppfollowsymbolundercursor.h"
#include "cppfunctiondecldeflink.h"
#include <cpptools/commentssettings.h>
......@@ -132,6 +133,8 @@ public:
void updateContentsChangedSignal();
FollowSymbolUnderCursor *followSymbolUnderCursorDelegate(); // exposed for tests
Q_SIGNALS:
void outlineModelIndexChanged(const QModelIndex &index);
......@@ -204,7 +207,7 @@ private:
Q_SLOT void abortDeclDefLink();
Link findLinkAt(const QTextCursor &, bool resolveTarget = true);
Link findLinkAt(const QTextCursor &, bool resolveTarget = true, bool inNextSplit = false);
bool openCppEditorAt(const Link &, bool inNextSplit = false);
QModelIndex indexForPosition(int line, int column,
......@@ -254,6 +257,8 @@ private:
QSharedPointer<FunctionDeclDefLink> m_declDefLink;
CppTools::CommentsSettings m_commentsSettings;
QScopedPointer<FollowSymbolUnderCursor> m_followSymbolUnderCursor;
};
} // namespace Internal
......
......@@ -23,7 +23,8 @@ HEADERS += cppeditorplugin.h \
cppincludehierarchy.h \
cppincludehierarchymodel.h \
cppincludehierarchyitem.h \
cppincludehierarchytreeview.h
cppincludehierarchytreeview.h \
cppvirtualfunctionassistprovider.h
SOURCES += cppeditorplugin.cpp \
cppautocompleter.cpp \
......@@ -45,7 +46,8 @@ SOURCES += cppeditorplugin.cpp \
cppincludehierarchy.cpp \
cppincludehierarchymodel.cpp \
cppincludehierarchyitem.cpp \
cppincludehierarchytreeview.cpp
cppincludehierarchytreeview.cpp \
cppvirtualfunctionassistprovider.cpp
RESOURCES += cppeditor.qrc
......
......@@ -60,6 +60,8 @@ QtcPlugin {
"cppsnippetprovider.h",
"cpptypehierarchy.cpp",
"cpptypehierarchy.h",
"cppvirtualfunctionassistprovider.cpp",
"cppvirtualfunctionassistprovider.h",
]
Group {
......
......@@ -128,6 +128,13 @@ private slots:
void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_globalNamespace();
void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_namespace();
void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_insideFunction();
void test_FollowSymbolUnderCursor_virtualFunctionCall_allOverrides();
void test_FollowSymbolUnderCursor_virtualFunctionCall_possibleOverrides1();
void test_FollowSymbolUnderCursor_virtualFunctionCall_possibleOverrides2();
void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnQualified();
void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnDeclaration();
void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnDefinition();
void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnNonPointerNonReference();
void test_doxygen_comments_qt_style();
void test_doxygen_comments_qt_style_continuation();
......
......@@ -272,10 +272,11 @@ CppMacro::CppMacro(const Macro &macro)
// CppDeclarableElement
CppDeclarableElement::CppDeclarableElement(Symbol *declaration) : CppElement()
CppDeclarableElement::CppDeclarableElement(Symbol *declaration)
: CppElement()
, declaration(declaration)
, icon(Icons().iconForSymbol(declaration))
{
icon = Icons().iconForSymbol(declaration);
Overview overview;
overview.showArgumentNames = true;
overview.showReturnTypes = true;
......@@ -309,6 +310,11 @@ CppClass::CppClass(Symbol *declaration) : CppDeclarableElement(declaration)
tooltip = qualifiedName;
}
bool CppClass::operator==(const CppClass &other)
{
return this->declaration == other.declaration;
}
void CppClass::lookupBases(Symbol *declaration, const CPlusPlus::LookupContext &context)
{
typedef QPair<ClassOrNamespace *, CppClass *> Data;
......
......@@ -135,6 +135,7 @@ public:
explicit CppDeclarableElement(CPlusPlus::Symbol *declaration);
public:
CPlusPlus::Symbol *declaration;
QString name;
QString qualifiedName;
QString type;
......@@ -153,6 +154,8 @@ public:
CppClass();
explicit CppClass(CPlusPlus::Symbol *declaration);
bool operator==(const CppClass &other);
void lookupBases(CPlusPlus::Symbol *declaration, const CPlusPlus::LookupContext &context);
void lookupDerived(CPlusPlus::Symbol *declaration, const CPlusPlus::Snapshot &snapshot);
......
......@@ -30,6 +30,7 @@
#include "cppfollowsymbolundercursor.h"
#include "cppeditor.h"
#include "cppvirtualfunctionassistprovider.h"
#include <cplusplus/ASTPath.h>
#include <cplusplus/BackwardsScanner.h>
......@@ -54,6 +55,37 @@ typedef BaseTextEditorWidget::Link Link;
namespace {
bool lookupVirtualFunctionOverrides(const QString &expression, Function *function, Scope *scope,
const Snapshot &snapshot)
{
if (expression.isEmpty() || !function || !scope || scope->isClass() || snapshot.isEmpty())
return false;
bool result = false;
Document::Ptr expressionDocument = documentForExpression(expression.toUtf8());
if (ExpressionAST *expressionAST = extractExpressionAST(expressionDocument)) {
if (CallAST *callAST = expressionAST->asCall()) {
if (ExpressionAST *baseExpressionAST = callAST->base_expression) {
if (IdExpressionAST *idExpressionAST = baseExpressionAST->asIdExpression()) {
NameAST *name = idExpressionAST->name;
result = name && !name->asQualifiedName();
} else if (MemberAccessAST *memberAccessAST = baseExpressionAST->asMemberAccess()) {
NameAST *name = memberAccessAST->member_name;
const bool nameIsQualified = name && name->asQualifiedName();
TranslationUnit *unit = expressionDocument->translationUnit();
QTC_ASSERT(unit, return false);
const int tokenKind = unit->tokenKind(memberAccessAST->access_token);
result = tokenKind == T_ARROW && !nameIsQualified;
}
}
}
}
return result && FunctionHelper::isVirtualFunction(function, snapshot);
}
Link findMacroLink_helper(const QByteArray &name, Document::Ptr doc, const Snapshot &snapshot,
QSet<QString> *processed)
{
......@@ -138,48 +170,139 @@ inline LookupItem skipForwardDeclarations(const QList<LookupItem> &resolvedSymbo
return result;
}
CPPEditorWidget::Link attemptFuncDeclDef(const QTextCursor &cursor,
CPPEditorWidget *widget, CPlusPlus::Snapshot snapshot, const CPlusPlus::Document::Ptr &document,
SymbolFinder *symbolFinder)
{
snapshot.insert(document);
Link result;
QList<AST *> path = ASTPath(document)(cursor);
if (path.size() < 5)
return result;
NameAST *name = path.last()->asName();
if (!name)
return result;
if (QualifiedNameAST *qName = path.at(path.size() - 2)->asQualifiedName()) {
// TODO: check which part of the qualified name we're on
if (qName->unqualified_name != name)
return result;
}
for (int i = path.size() - 1; i != -1; --i) {
AST *node = path.at(i);
if (node->asParameterDeclaration() != 0)
return result;
}
AST *declParent = 0;
DeclaratorAST *decl = 0;
for (int i = path.size() - 2; i > 0; --i) {
if ((decl = path.at(i)->asDeclarator()) != 0) {
declParent = path.at(i - 1);
break;
}
}
if (!decl || !declParent)
return result;
if (!decl->postfix_declarator_list || !decl->postfix_declarator_list->value)
return result;
FunctionDeclaratorAST *funcDecl = decl->postfix_declarator_list->value->asFunctionDeclarator();
if (!funcDecl)
return result;
Symbol *target = 0;
if (FunctionDefinitionAST *funDef = declParent->asFunctionDefinition()) {
QList<Declaration *> candidates =
symbolFinder->findMatchingDeclaration(LookupContext(document, snapshot),
funDef->symbol);
if (!candidates.isEmpty()) // TODO: improve disambiguation
target = candidates.first();
} else if (declParent->asSimpleDeclaration()) {
target = symbolFinder->findMatchingDefinition(funcDecl->symbol, snapshot);
}
if (target) {
result = widget->linkToSymbol(target);
unsigned startLine, startColumn, endLine, endColumn;
document->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine,
&startColumn);
document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine,
&endColumn);
QTextDocument *textDocument = cursor.document();
result.linkTextStart =
textDocument->findBlockByNumber(startLine - 1).position() + startColumn - 1;
result.linkTextEnd =
textDocument->findBlockByNumber(endLine - 1).position() + endColumn - 1;
}
return result;
}
Symbol *findDefinition(Symbol *symbol, const Snapshot &snapshot, SymbolFinder *symbolFinder)
{
if (symbol->isFunction())
return 0; // symbol is a function definition.
else if (!symbol->type()->isFunctionType())
return 0; // not a function declaration
return symbolFinder->findMatchingDefinition(symbol, snapshot);
}
} // anonymous namespace
FollowSymbolUnderCursor::FollowSymbolUnderCursor(CPPEditorWidget *widget, const QTextCursor &cursor,
bool resolveTarget, const Snapshot &snapshot, const Document::Ptr &documentFromSemanticInfo,
CppTools::SymbolFinder *symbolFinder)
FollowSymbolUnderCursor::FollowSymbolUnderCursor(CPPEditorWidget *widget)
: m_widget(widget)
, m_cursor(cursor)
, m_resolveTarget(resolveTarget)
, m_snapshot(snapshot)
, m_document(documentFromSemanticInfo)
, m_symbolFinder(symbolFinder)
, m_virtualFunctionAssistProvider(new VirtualFunctionAssistProvider)
{
}
FollowSymbolUnderCursor::~FollowSymbolUnderCursor()
{
delete m_virtualFunctionAssistProvider;
}
BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink(const QTextCursor &cursor,
bool resolveTarget, const Snapshot &theSnapshot, const Document::Ptr &documentFromSemanticInfo,
SymbolFinder *symbolFinder, bool inNextSplit)
{
Link link;
Snapshot snapshot = theSnapshot;
// Move to end of identifier
QTextCursor tc = m_cursor;
QTextCursor tc = cursor;
QChar ch = m_widget->document()->characterAt(tc.position());
while (ch.isLetterOrNumber() || ch == QLatin1Char('_')) {
tc.movePosition(QTextCursor::NextCharacter);
ch = m_widget->document()->characterAt(tc.position());
}
// Try to match decl/def. For this we need the semantic doc with the AST.
if (m_document
&& m_document->translationUnit()
&& m_document->translationUnit()->ast()) {
// Try to macth decl/def. For this we need the semantic doc with the AST.
if (documentFromSemanticInfo
&& documentFromSemanticInfo->translationUnit()
&& documentFromSemanticInfo->translationUnit()->ast()) {
int pos = tc.position();
while (m_widget->document()->characterAt(pos).isSpace())
++pos;
if (m_widget->document()->characterAt(pos) == QLatin1Char('(')) {
link = attemptFuncDeclDef(m_cursor);
link = attemptFuncDeclDef(cursor, m_widget, snapshot, documentFromSemanticInfo,
symbolFinder);
if (link.hasValidLinkText())
return link;
}
}
int lineNumber = 0, positionInBlock = 0;
m_widget->convertPosition(m_cursor.position(), &lineNumber, &positionInBlock);
m_widget->convertPosition(cursor.position(), &lineNumber, &positionInBlock);
const unsigned line = lineNumber;
const unsigned column = positionInBlock + 1;
......@@ -189,9 +312,9 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
SimpleLexer tokenize;
tokenize.setQtMocRunEnabled(true);
const QString blockText = m_cursor.block().text();
const QString blockText = cursor.block().text();
const QList<Token> tokens = tokenize(blockText,
BackwardsScanner::previousBlockState(m_cursor.block()));
BackwardsScanner::previousBlockState(cursor.block()));
bool recognizedQtMethod = false;
......@@ -223,7 +346,7 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
}
if (j < tokens.size()) {
QTextBlock block = m_cursor.block();
QTextBlock block = cursor.block();
beginOfToken = block.position() + tokens.at(i).begin();
endOfToken = block.position() + tokens.at(i).end();
......@@ -238,17 +361,17 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
}
// Now we prefer the doc from the snapshot with macros expanded.
Document::Ptr doc = m_snapshot.document(m_widget->editorDocument()->filePath());
Document::Ptr doc = snapshot.document(m_widget->editorDocument()->filePath());
if (!doc) {
doc = m_document;
doc = documentFromSemanticInfo;
if (!doc)
return link;
}
if (!recognizedQtMethod) {
const QTextBlock block = tc.block();
int pos = m_cursor.positionInBlock();
QChar ch = m_widget->document()->characterAt(m_cursor.position());
int pos = cursor.positionInBlock();
QChar ch = m_widget->document()->characterAt(cursor.position());
if (pos > 0 && !(ch.isLetterOrNumber() || ch == QLatin1Char('_')))
--pos; // positionInBlock points to a delimiter character.
const Token tk = SimpleLexer::tokenAt(block.text(), pos,
......@@ -259,7 +382,7 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
// Handle include directives
if (tk.is(T_STRING_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL)) {
const unsigned lineno = m_cursor.blockNumber() + 1;
const unsigned lineno = cursor.blockNumber() + 1;
foreach (const Document::Include &incl, doc->resolvedIncludes()) {
if (incl.line() == lineno) {
link.targetFileName = incl.resolvedFileName();
......@@ -279,7 +402,7 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
// Handle macro uses
const Macro *macro = doc->findMacroDefinitionAt(line);
if (macro) {
QTextCursor macroCursor = m_cursor;
QTextCursor macroCursor = cursor;
const QByteArray name = CPPEditorWidget::identifierUnderCursor(&macroCursor).toLatin1();
if (macro->name() == name)
return link; //already on definition!
......@@ -318,7 +441,7 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
}
TypeOfExpression typeOfExpression;
typeOfExpression.init(doc, m_snapshot);
typeOfExpression.init(doc, snapshot);
// make possible to instantiate templates
typeOfExpression.setExpandTemplates(true);
const QList<LookupItem> resolvedSymbols =
......@@ -355,16 +478,30 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
if (Symbol *symbol = result.declaration()) {
Symbol *def = 0;
if (m_resolveTarget) {
// Consider to show a pop-up displaying overrides for the function
Function *function = symbol->type()->asFunctionType();
if (lookupVirtualFunctionOverrides(expression, function, scope, snapshot)) {
Class *klass = symbolFinder->findMatchingClassDeclaration(function, snapshot);
QTC_CHECK(klass);
if (m_virtualFunctionAssistProvider->configure(klass, function, snapshot,
inNextSplit)) {
m_widget->invokeAssist(TextEditor::FollowSymbol,
m_virtualFunctionAssistProvider);
}
return Link();
}
if (resolveTarget) {
Symbol *lastVisibleSymbol = doc->lastVisibleSymbolAt(line, column);
def = findDefinition(symbol, m_snapshot);
def = findDefinition(symbol, snapshot, symbolFinder);
if (def == lastVisibleSymbol)
def = 0; // jump to declaration then.
if (symbol->isForwardClassDeclaration())
def = m_symbolFinder->findMatchingClassDeclaration(symbol, m_snapshot);
def = symbolFinder->findMatchingClassDeclaration(symbol, snapshot);
}
link = m_widget->linkToSymbol(def ? def : symbol);
......@@ -375,9 +512,9 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
}
// Handle macro uses
QTextCursor macroCursor = m_cursor;
QTextCursor macroCursor = cursor;
const QByteArray name = CPPEditorWidget::identifierUnderCursor(&macroCursor).toLatin1();
link = findMacroLink(name, m_document);
link = findMacroLink(name, documentFromSemanticInfo);
if (link.hasValidTarget()) {
link.linkTextStart = macroCursor.selectionStart();
link.linkTextEnd = macroCursor.selectionEnd();
......@@ -387,87 +524,12 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
return Link();
}
CPPEditorWidget::Link FollowSymbolUnderCursor::attemptFuncDeclDef(const QTextCursor &cursor)
VirtualFunctionAssistProvider *FollowSymbolUnderCursor::virtualFunctionAssistProvider()
{
m_snapshot.insert(m_document);
Link result;
QList<AST *> path = ASTPath(m_document)(cursor);
if (path.size() < 5)
return result;
NameAST *name = path.last()->asName();
if (!name)
return result;
if (QualifiedNameAST *qName = path.at(path.size() - 2)->asQualifiedName()) {
// TODO: check which part of the qualified name we're on
if (qName->unqualified_name != name)
return result;
}
for (int i = path.size() - 1; i != -1; --i) {
AST *node = path.at(i);
if (node->asParameterDeclaration() != 0)
return result;
}
AST *declParent = 0;
DeclaratorAST *decl = 0;
for (int i = path.size() - 2; i > 0; --i) {
if ((decl = path.at(i)->asDeclarator()) != 0) {
declParent = path.at(i - 1);
break;
}
}
if (!decl || !declParent)
return result;
if (!decl->postfix_declarator_list || !decl->postfix_declarator_list->value)
return result;
FunctionDeclaratorAST *funcDecl = decl->postfix_declarator_list->value->asFunctionDeclarator();
if (!funcDecl)
return result;
Symbol *target = 0;
if (FunctionDefinitionAST *funDef = declParent->asFunctionDefinition()) {
QList<Declaration *> candidates =
m_symbolFinder->findMatchingDeclaration(LookupContext(m_document, m_snapshot),
funDef->symbol);
if (!candidates.isEmpty()) // TODO: improve disambiguation
target = candidates.first();
} else if (declParent->asSimpleDeclaration()) {
target = m_symbolFinder->findMatchingDefinition(funcDecl->symbol, m_snapshot);
}
if (target) {
result = m_widget->linkToSymbol(target);
unsigned startLine, startColumn, endLine, endColumn;
m_document->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine,
&startColumn);
m_document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine,
&endColumn);
QTextDocument *textDocument = cursor.document();
result.linkTextStart =
textDocument->findBlockByNumber(startLine - 1).position() + startColumn - 1;
result.linkTextEnd =
textDocument->findBlockByNumber(endLine - 1).position() + endColumn - 1;
}
return result;
return m_virtualFunctionAssistProvider;
}