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: Joerg Bornemann <joerg.bornemann@digia.com>
This commit is contained in:
Nikolai Kosjar
2013-07-04 20:11:10 +02:00
parent 3a64f8a344
commit 11aeaea86f
23 changed files with 1130 additions and 352 deletions

View File

@@ -159,7 +159,7 @@ static bool isValidFileNameChar(const QChar &c)
} }
CMakeEditorWidget::Link CMakeEditorWidget::findLinkAt(const QTextCursor &cursor, CMakeEditorWidget::Link CMakeEditorWidget::findLinkAt(const QTextCursor &cursor,
bool/* resolveTarget*/) bool/* resolveTarget*/, bool /*inNextSplit*/)
{ {
Link link; Link link;

View File

@@ -78,8 +78,7 @@ public:
CMakeEditorFactory *factory() { return m_factory; } CMakeEditorFactory *factory() { return m_factory; }
TextEditor::TextEditorActionHandler *actionHandler() const { return m_ah; } TextEditor::TextEditorActionHandler *actionHandler() const { return m_ah; }
Link findLinkAt(const QTextCursor &cursor, Link findLinkAt(const QTextCursor &cursor, bool resolveTarget = true, bool inNextSplit = false);
bool resolveTarget = true);
protected: protected:
TextEditor::BaseTextEditor *createEditor(); TextEditor::BaseTextEditor *createEditor();

View File

@@ -511,6 +511,7 @@ CPPEditorWidget::CPPEditorWidget(QWidget *parent)
, m_firstRenameChange(false) , m_firstRenameChange(false)
, m_objcEnabled(false) , m_objcEnabled(false)
, m_commentsSettings(CppTools::CppToolsSettings::instance()->commentsSettings()) , m_commentsSettings(CppTools::CppToolsSettings::instance()->commentsSettings())
, m_followSymbolUnderCursor(new FollowSymbolUnderCursor(this))
{ {
qRegisterMetaType<SemanticInfo>("CppTools::SemanticInfo"); qRegisterMetaType<SemanticInfo>("CppTools::SemanticInfo");
@@ -1239,14 +1240,14 @@ QString CPPEditorWidget::identifierUnderCursor(QTextCursor *macroCursor)
return macroCursor->selectedText(); 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) if (!m_modelManager)
return Link(); return Link();
FollowSymbolUnderCursor followSymbolUnderCursor(this, cursor, resolveTarget, return m_followSymbolUnderCursor->findLink(cursor, resolveTarget, m_modelManager->snapshot(),
m_modelManager->snapshot(), m_lastSemanticInfo.doc, symbolFinder()); m_lastSemanticInfo.doc, symbolFinder(), inNextSplit);
return followSymbolUnderCursor.findLink();
} }
unsigned CPPEditorWidget::editorRevision() const unsigned CPPEditorWidget::editorRevision() const
@@ -1720,6 +1721,8 @@ TextEditor::IAssistInterface *CPPEditorWidget::createAssistInterface(
if (!semanticInfo().doc || isOutdated()) if (!semanticInfo().doc || isOutdated())
return 0; return 0;
return new CppQuickFixAssistInterface(const_cast<CPPEditorWidget *>(this), reason); return new CppQuickFixAssistInterface(const_cast<CPPEditorWidget *>(this), reason);
} else {
return BaseTextEditorWidget::createAssistInterface(kind, reason);
} }
return 0; return 0;
} }
@@ -1812,6 +1815,11 @@ void CPPEditorWidget::updateContentsChangedSignal()
this, SLOT(onContentsChanged(int,int,int))); this, SLOT(onContentsChanged(int,int,int)));
} }
FollowSymbolUnderCursor *CPPEditorWidget::followSymbolUnderCursorDelegate()
{
return m_followSymbolUnderCursor.data();
}
void CPPEditorWidget::abortDeclDefLink() void CPPEditorWidget::abortDeclDefLink()
{ {
if (!m_declDefLink) if (!m_declDefLink)

View File

@@ -30,6 +30,7 @@
#ifndef CPPEDITOR_H #ifndef CPPEDITOR_H
#define CPPEDITOR_H #define CPPEDITOR_H
#include "cppfollowsymbolundercursor.h"
#include "cppfunctiondecldeflink.h" #include "cppfunctiondecldeflink.h"
#include <cpptools/commentssettings.h> #include <cpptools/commentssettings.h>
@@ -132,6 +133,8 @@ public:
void updateContentsChangedSignal(); void updateContentsChangedSignal();
FollowSymbolUnderCursor *followSymbolUnderCursorDelegate(); // exposed for tests
Q_SIGNALS: Q_SIGNALS:
void outlineModelIndexChanged(const QModelIndex &index); void outlineModelIndexChanged(const QModelIndex &index);
@@ -204,7 +207,7 @@ private:
Q_SLOT void abortDeclDefLink(); 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); bool openCppEditorAt(const Link &, bool inNextSplit = false);
QModelIndex indexForPosition(int line, int column, QModelIndex indexForPosition(int line, int column,
@@ -254,6 +257,8 @@ private:
QSharedPointer<FunctionDeclDefLink> m_declDefLink; QSharedPointer<FunctionDeclDefLink> m_declDefLink;
CppTools::CommentsSettings m_commentsSettings; CppTools::CommentsSettings m_commentsSettings;
QScopedPointer<FollowSymbolUnderCursor> m_followSymbolUnderCursor;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -23,7 +23,8 @@ HEADERS += cppeditorplugin.h \
cppincludehierarchy.h \ cppincludehierarchy.h \
cppincludehierarchymodel.h \ cppincludehierarchymodel.h \
cppincludehierarchyitem.h \ cppincludehierarchyitem.h \
cppincludehierarchytreeview.h cppincludehierarchytreeview.h \
cppvirtualfunctionassistprovider.h
SOURCES += cppeditorplugin.cpp \ SOURCES += cppeditorplugin.cpp \
cppautocompleter.cpp \ cppautocompleter.cpp \
@@ -45,7 +46,8 @@ SOURCES += cppeditorplugin.cpp \
cppincludehierarchy.cpp \ cppincludehierarchy.cpp \
cppincludehierarchymodel.cpp \ cppincludehierarchymodel.cpp \
cppincludehierarchyitem.cpp \ cppincludehierarchyitem.cpp \
cppincludehierarchytreeview.cpp cppincludehierarchytreeview.cpp \
cppvirtualfunctionassistprovider.cpp
RESOURCES += cppeditor.qrc RESOURCES += cppeditor.qrc

View File

@@ -60,6 +60,8 @@ QtcPlugin {
"cppsnippetprovider.h", "cppsnippetprovider.h",
"cpptypehierarchy.cpp", "cpptypehierarchy.cpp",
"cpptypehierarchy.h", "cpptypehierarchy.h",
"cppvirtualfunctionassistprovider.cpp",
"cppvirtualfunctionassistprovider.h",
] ]
Group { Group {

View File

@@ -128,6 +128,13 @@ private slots:
void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_globalNamespace(); void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_globalNamespace();
void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_namespace(); void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_namespace();
void test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_insideFunction(); 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();
void test_doxygen_comments_qt_style_continuation(); void test_doxygen_comments_qt_style_continuation();

View File

@@ -272,10 +272,11 @@ CppMacro::CppMacro(const Macro &macro)
// CppDeclarableElement // CppDeclarableElement
CppDeclarableElement::CppDeclarableElement(Symbol *declaration) : CppElement() CppDeclarableElement::CppDeclarableElement(Symbol *declaration)
: CppElement()
, declaration(declaration)
, icon(Icons().iconForSymbol(declaration))
{ {
icon = Icons().iconForSymbol(declaration);
Overview overview; Overview overview;
overview.showArgumentNames = true; overview.showArgumentNames = true;
overview.showReturnTypes = true; overview.showReturnTypes = true;
@@ -309,6 +310,11 @@ CppClass::CppClass(Symbol *declaration) : CppDeclarableElement(declaration)
tooltip = qualifiedName; tooltip = qualifiedName;
} }
bool CppClass::operator==(const CppClass &other)
{
return this->declaration == other.declaration;
}
void CppClass::lookupBases(Symbol *declaration, const CPlusPlus::LookupContext &context) void CppClass::lookupBases(Symbol *declaration, const CPlusPlus::LookupContext &context)
{ {
typedef QPair<ClassOrNamespace *, CppClass *> Data; typedef QPair<ClassOrNamespace *, CppClass *> Data;

View File

@@ -135,6 +135,7 @@ public:
explicit CppDeclarableElement(CPlusPlus::Symbol *declaration); explicit CppDeclarableElement(CPlusPlus::Symbol *declaration);
public: public:
CPlusPlus::Symbol *declaration;
QString name; QString name;
QString qualifiedName; QString qualifiedName;
QString type; QString type;
@@ -153,6 +154,8 @@ public:
CppClass(); CppClass();
explicit CppClass(CPlusPlus::Symbol *declaration); explicit CppClass(CPlusPlus::Symbol *declaration);
bool operator==(const CppClass &other);
void lookupBases(CPlusPlus::Symbol *declaration, const CPlusPlus::LookupContext &context); void lookupBases(CPlusPlus::Symbol *declaration, const CPlusPlus::LookupContext &context);
void lookupDerived(CPlusPlus::Symbol *declaration, const CPlusPlus::Snapshot &snapshot); void lookupDerived(CPlusPlus::Symbol *declaration, const CPlusPlus::Snapshot &snapshot);

View File

@@ -30,6 +30,7 @@
#include "cppfollowsymbolundercursor.h" #include "cppfollowsymbolundercursor.h"
#include "cppeditor.h" #include "cppeditor.h"
#include "cppvirtualfunctionassistprovider.h"
#include <cplusplus/ASTPath.h> #include <cplusplus/ASTPath.h>
#include <cplusplus/BackwardsScanner.h> #include <cplusplus/BackwardsScanner.h>
@@ -54,6 +55,37 @@ typedef BaseTextEditorWidget::Link Link;
namespace { 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, Link findMacroLink_helper(const QByteArray &name, Document::Ptr doc, const Snapshot &snapshot,
QSet<QString> *processed) QSet<QString> *processed)
{ {
@@ -138,262 +170,15 @@ inline LookupItem skipForwardDeclarations(const QList<LookupItem> &resolvedSymbo
return result; return result;
} }
} // anonymous namespace CPPEditorWidget::Link attemptFuncDeclDef(const QTextCursor &cursor,
CPPEditorWidget *widget, CPlusPlus::Snapshot snapshot, const CPlusPlus::Document::Ptr &document,
FollowSymbolUnderCursor::FollowSymbolUnderCursor(CPPEditorWidget *widget, const QTextCursor &cursor, SymbolFinder *symbolFinder)
bool resolveTarget, const Snapshot &snapshot, const Document::Ptr &documentFromSemanticInfo,
CppTools::SymbolFinder *symbolFinder)
: m_widget(widget)
, m_cursor(cursor)
, m_resolveTarget(resolveTarget)
, m_snapshot(snapshot)
, m_document(documentFromSemanticInfo)
, m_symbolFinder(symbolFinder)
{ {
} snapshot.insert(document);
BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink()
{
Link link;
// Move to end of identifier
QTextCursor tc = m_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()) {
int pos = tc.position();
while (m_widget->document()->characterAt(pos).isSpace())
++pos;
if (m_widget->document()->characterAt(pos) == QLatin1Char('(')) {
link = attemptFuncDeclDef(m_cursor);
if (link.hasValidLinkText())
return link;
}
}
int lineNumber = 0, positionInBlock = 0;
m_widget->convertPosition(m_cursor.position(), &lineNumber, &positionInBlock);
const unsigned line = lineNumber;
const unsigned column = positionInBlock + 1;
// Try to find a signal or slot inside SIGNAL() or SLOT()
int beginOfToken = 0;
int endOfToken = 0;
SimpleLexer tokenize;
tokenize.setQtMocRunEnabled(true);
const QString blockText = m_cursor.block().text();
const QList<Token> tokens = tokenize(blockText,
BackwardsScanner::previousBlockState(m_cursor.block()));
bool recognizedQtMethod = false;
for (int i = 0; i < tokens.size(); ++i) {
const Token &tk = tokens.at(i);
if (((unsigned) positionInBlock) >= tk.begin()
&& ((unsigned) positionInBlock) <= tk.end()) {
if (i >= 2 && tokens.at(i).is(T_IDENTIFIER) && tokens.at(i - 1).is(T_LPAREN)
&& (tokens.at(i - 2).is(T_SIGNAL) || tokens.at(i - 2).is(T_SLOT))) {
// token[i] == T_IDENTIFIER
// token[i + 1] == T_LPAREN
// token[.....] == ....
// token[i + n] == T_RPAREN
if (i + 1 < tokens.size() && tokens.at(i + 1).is(T_LPAREN)) {
// skip matched parenthesis
int j = i - 1;
int depth = 0;
for (; j < tokens.size(); ++j) {
if (tokens.at(j).is(T_LPAREN)) {
++depth;
} else if (tokens.at(j).is(T_RPAREN)) {
if (!--depth)
break;
}
}
if (j < tokens.size()) {
QTextBlock block = m_cursor.block();
beginOfToken = block.position() + tokens.at(i).begin();
endOfToken = block.position() + tokens.at(i).end();
tc.setPosition(block.position() + tokens.at(j).end());
recognizedQtMethod = true;
}
}
}
break;
}
}
// Now we prefer the doc from the snapshot with macros expanded.
Document::Ptr doc = m_snapshot.document(m_widget->editorDocument()->filePath());
if (!doc) {
doc = m_document;
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());
if (pos > 0 && !(ch.isLetterOrNumber() || ch == QLatin1Char('_')))
--pos; // positionInBlock points to a delimiter character.
const Token tk = SimpleLexer::tokenAt(block.text(), pos,
BackwardsScanner::previousBlockState(block), true);
beginOfToken = block.position() + tk.begin();
endOfToken = block.position() + tk.end();
// Handle include directives
if (tk.is(T_STRING_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL)) {
const unsigned lineno = m_cursor.blockNumber() + 1;
foreach (const Document::Include &incl, doc->resolvedIncludes()) {
if (incl.line() == lineno) {
link.targetFileName = incl.resolvedFileName();
link.linkTextStart = beginOfToken + 1;
link.linkTextEnd = endOfToken - 1;
return link;
}
}
}
if (tk.isNot(T_IDENTIFIER) && tk.kind() < T_FIRST_QT_KEYWORD && tk.kind() > T_LAST_KEYWORD)
return link;
tc.setPosition(endOfToken);
}
// Handle macro uses
const Macro *macro = doc->findMacroDefinitionAt(line);
if (macro) {
QTextCursor macroCursor = m_cursor;
const QByteArray name = CPPEditorWidget::identifierUnderCursor(&macroCursor).toLatin1();
if (macro->name() == name)
return link; //already on definition!
} else {
const Document::MacroUse *use = doc->findMacroUseAt(endOfToken - 1);
if (use && use->macro().fileName() != CppModelManagerInterface::configurationFileName()) {
const Macro &macro = use->macro();
link.targetFileName = macro.fileName();
link.targetLine = macro.line();
link.linkTextStart = use->begin();
link.linkTextEnd = use->end();
return link;
}
}
// Find the last symbol up to the cursor position
Scope *scope = doc->scopeAt(line, column);
if (!scope)
return link;
// Evaluate the type of the expression under the cursor
ExpressionUnderCursor expressionUnderCursor;
QString expression = expressionUnderCursor(tc);
for (int pos = tc.position();; ++pos) {
const QChar ch = m_widget->document()->characterAt(pos);
if (ch.isSpace())
continue;
if (ch == QLatin1Char('(') && !expression.isEmpty()) {
tc.setPosition(pos);
if (TextEditor::TextBlockUserData::findNextClosingParenthesis(&tc, true))
expression.append(tc.selectedText());
}
break;
}
TypeOfExpression typeOfExpression;
typeOfExpression.init(doc, m_snapshot);
// make possible to instantiate templates
typeOfExpression.setExpandTemplates(true);
const QList<LookupItem> resolvedSymbols =
typeOfExpression.reference(expression.toUtf8(), scope, TypeOfExpression::Preprocess);
if (!resolvedSymbols.isEmpty()) {
LookupItem result = skipForwardDeclarations(resolvedSymbols);
foreach (const LookupItem &r, resolvedSymbols) {
if (Symbol *d = r.declaration()) {
if (d->isDeclaration() || d->isFunction()) {
const QString fileName = QString::fromUtf8(d->fileName(), d->fileNameLength());
if (m_widget->editorDocument()->filePath() == fileName) {
if (unsigned(lineNumber) == d->line()
&& unsigned(positionInBlock) >= d->column()) { // TODO: check the end
result = r; // take the symbol under cursor.
break;
}
}
} else if (d->isUsingDeclaration()) {
int tokenBeginLineNumber = 0, tokenBeginColumnNumber = 0;
m_widget->convertPosition(beginOfToken, &tokenBeginLineNumber,
&tokenBeginColumnNumber);
if (unsigned(tokenBeginLineNumber) > d->line()
|| (unsigned(tokenBeginLineNumber) == d->line()
&& unsigned(tokenBeginColumnNumber) > d->column())) {
result = r; // take the symbol under cursor.
break;
}
}
}
}
if (Symbol *symbol = result.declaration()) {
Symbol *def = 0;
if (m_resolveTarget) {
Symbol *lastVisibleSymbol = doc->lastVisibleSymbolAt(line, column);
def = findDefinition(symbol, m_snapshot);
if (def == lastVisibleSymbol)
def = 0; // jump to declaration then.
if (symbol->isForwardClassDeclaration())
def = m_symbolFinder->findMatchingClassDeclaration(symbol, m_snapshot);
}
link = m_widget->linkToSymbol(def ? def : symbol);
link.linkTextStart = beginOfToken;
link.linkTextEnd = endOfToken;
return link;
}
}
// Handle macro uses
QTextCursor macroCursor = m_cursor;
const QByteArray name = CPPEditorWidget::identifierUnderCursor(&macroCursor).toLatin1();
link = findMacroLink(name, m_document);
if (link.hasValidTarget()) {
link.linkTextStart = macroCursor.selectionStart();
link.linkTextEnd = macroCursor.selectionEnd();
return link;
}
return Link();
}
CPPEditorWidget::Link FollowSymbolUnderCursor::attemptFuncDeclDef(const QTextCursor &cursor)
{
m_snapshot.insert(m_document);
Link result; Link result;
QList<AST *> path = ASTPath(m_document)(cursor); QList<AST *> path = ASTPath(document)(cursor);
if (path.size() < 5) if (path.size() < 5)
return result; return result;
@@ -434,21 +219,21 @@ CPPEditorWidget::Link FollowSymbolUnderCursor::attemptFuncDeclDef(const QTextCur
Symbol *target = 0; Symbol *target = 0;
if (FunctionDefinitionAST *funDef = declParent->asFunctionDefinition()) { if (FunctionDefinitionAST *funDef = declParent->asFunctionDefinition()) {
QList<Declaration *> candidates = QList<Declaration *> candidates =
m_symbolFinder->findMatchingDeclaration(LookupContext(m_document, m_snapshot), symbolFinder->findMatchingDeclaration(LookupContext(document, snapshot),
funDef->symbol); funDef->symbol);
if (!candidates.isEmpty()) // TODO: improve disambiguation if (!candidates.isEmpty()) // TODO: improve disambiguation
target = candidates.first(); target = candidates.first();
} else if (declParent->asSimpleDeclaration()) { } else if (declParent->asSimpleDeclaration()) {
target = m_symbolFinder->findMatchingDefinition(funcDecl->symbol, m_snapshot); target = symbolFinder->findMatchingDefinition(funcDecl->symbol, snapshot);
} }
if (target) { if (target) {
result = m_widget->linkToSymbol(target); result = widget->linkToSymbol(target);
unsigned startLine, startColumn, endLine, endColumn; unsigned startLine, startColumn, endLine, endColumn;
m_document->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine, document->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine,
&startColumn); &startColumn);
m_document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine, document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine,
&endColumn); &endColumn);
QTextDocument *textDocument = cursor.document(); QTextDocument *textDocument = cursor.document();
@@ -461,7 +246,7 @@ CPPEditorWidget::Link FollowSymbolUnderCursor::attemptFuncDeclDef(const QTextCur
return result; return result;
} }
Symbol *FollowSymbolUnderCursor::findDefinition(Symbol *symbol, const Snapshot &snapshot) const Symbol *findDefinition(Symbol *symbol, const Snapshot &snapshot, SymbolFinder *symbolFinder)
{ {
if (symbol->isFunction()) if (symbol->isFunction())
return 0; // symbol is a function definition. return 0; // symbol is a function definition.
@@ -469,5 +254,282 @@ Symbol *FollowSymbolUnderCursor::findDefinition(Symbol *symbol, const Snapshot &
else if (!symbol->type()->isFunctionType()) else if (!symbol->type()->isFunctionType())
return 0; // not a function declaration return 0; // not a function declaration
return m_symbolFinder->findMatchingDefinition(symbol, snapshot); return symbolFinder->findMatchingDefinition(symbol, snapshot);
}
} // anonymous namespace
FollowSymbolUnderCursor::FollowSymbolUnderCursor(CPPEditorWidget *widget)
: m_widget(widget)
, m_virtualFunctionAssistProvider(new VirtualFunctionAssistProvider)
{
}
FollowSymbolUnderCursor::~FollowSymbolUnderCursor()
{
delete m_virtualFunctionAssistProvider;
}
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 = 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 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(cursor, m_widget, snapshot, documentFromSemanticInfo,
symbolFinder);
if (link.hasValidLinkText())
return link;
}
}
int lineNumber = 0, positionInBlock = 0;
m_widget->convertPosition(cursor.position(), &lineNumber, &positionInBlock);
const unsigned line = lineNumber;
const unsigned column = positionInBlock + 1;
// Try to find a signal or slot inside SIGNAL() or SLOT()
int beginOfToken = 0;
int endOfToken = 0;
SimpleLexer tokenize;
tokenize.setQtMocRunEnabled(true);
const QString blockText = cursor.block().text();
const QList<Token> tokens = tokenize(blockText,
BackwardsScanner::previousBlockState(cursor.block()));
bool recognizedQtMethod = false;
for (int i = 0; i < tokens.size(); ++i) {
const Token &tk = tokens.at(i);
if (((unsigned) positionInBlock) >= tk.begin()
&& ((unsigned) positionInBlock) <= tk.end()) {
if (i >= 2 && tokens.at(i).is(T_IDENTIFIER) && tokens.at(i - 1).is(T_LPAREN)
&& (tokens.at(i - 2).is(T_SIGNAL) || tokens.at(i - 2).is(T_SLOT))) {
// token[i] == T_IDENTIFIER
// token[i + 1] == T_LPAREN
// token[.....] == ....
// token[i + n] == T_RPAREN
if (i + 1 < tokens.size() && tokens.at(i + 1).is(T_LPAREN)) {
// skip matched parenthesis
int j = i - 1;
int depth = 0;
for (; j < tokens.size(); ++j) {
if (tokens.at(j).is(T_LPAREN)) {
++depth;
} else if (tokens.at(j).is(T_RPAREN)) {
if (!--depth)
break;
}
}
if (j < tokens.size()) {
QTextBlock block = cursor.block();
beginOfToken = block.position() + tokens.at(i).begin();
endOfToken = block.position() + tokens.at(i).end();
tc.setPosition(block.position() + tokens.at(j).end());
recognizedQtMethod = true;
}
}
}
break;
}
}
// Now we prefer the doc from the snapshot with macros expanded.
Document::Ptr doc = snapshot.document(m_widget->editorDocument()->filePath());
if (!doc) {
doc = documentFromSemanticInfo;
if (!doc)
return link;
}
if (!recognizedQtMethod) {
const QTextBlock block = tc.block();
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,
BackwardsScanner::previousBlockState(block), true);
beginOfToken = block.position() + tk.begin();
endOfToken = block.position() + tk.end();
// Handle include directives
if (tk.is(T_STRING_LITERAL) || tk.is(T_ANGLE_STRING_LITERAL)) {
const unsigned lineno = cursor.blockNumber() + 1;
foreach (const Document::Include &incl, doc->resolvedIncludes()) {
if (incl.line() == lineno) {
link.targetFileName = incl.resolvedFileName();
link.linkTextStart = beginOfToken + 1;
link.linkTextEnd = endOfToken - 1;
return link;
}
}
}
if (tk.isNot(T_IDENTIFIER) && tk.kind() < T_FIRST_QT_KEYWORD && tk.kind() > T_LAST_KEYWORD)
return link;
tc.setPosition(endOfToken);
}
// Handle macro uses
const Macro *macro = doc->findMacroDefinitionAt(line);
if (macro) {
QTextCursor macroCursor = cursor;
const QByteArray name = CPPEditorWidget::identifierUnderCursor(&macroCursor).toLatin1();
if (macro->name() == name)
return link; //already on definition!
} else {
const Document::MacroUse *use = doc->findMacroUseAt(endOfToken - 1);
if (use && use->macro().fileName() != CppModelManagerInterface::configurationFileName()) {
const Macro &macro = use->macro();
link.targetFileName = macro.fileName();
link.targetLine = macro.line();
link.linkTextStart = use->begin();
link.linkTextEnd = use->end();
return link;
}
}
// Find the last symbol up to the cursor position
Scope *scope = doc->scopeAt(line, column);
if (!scope)
return link;
// Evaluate the type of the expression under the cursor
ExpressionUnderCursor expressionUnderCursor;
QString expression = expressionUnderCursor(tc);
for (int pos = tc.position();; ++pos) {
const QChar ch = m_widget->document()->characterAt(pos);
if (ch.isSpace())
continue;
if (ch == QLatin1Char('(') && !expression.isEmpty()) {
tc.setPosition(pos);
if (TextEditor::TextBlockUserData::findNextClosingParenthesis(&tc, true))
expression.append(tc.selectedText());
}
break;
}
TypeOfExpression typeOfExpression;
typeOfExpression.init(doc, snapshot);
// make possible to instantiate templates
typeOfExpression.setExpandTemplates(true);
const QList<LookupItem> resolvedSymbols =
typeOfExpression.reference(expression.toUtf8(), scope, TypeOfExpression::Preprocess);
if (!resolvedSymbols.isEmpty()) {
LookupItem result = skipForwardDeclarations(resolvedSymbols);
foreach (const LookupItem &r, resolvedSymbols) {
if (Symbol *d = r.declaration()) {
if (d->isDeclaration() || d->isFunction()) {
const QString fileName = QString::fromUtf8(d->fileName(), d->fileNameLength());
if (m_widget->editorDocument()->filePath() == fileName) {
if (unsigned(lineNumber) == d->line()
&& unsigned(positionInBlock) >= d->column()) { // TODO: check the end
result = r; // take the symbol under cursor.
break;
}
}
} else if (d->isUsingDeclaration()) {
int tokenBeginLineNumber = 0, tokenBeginColumnNumber = 0;
m_widget->convertPosition(beginOfToken, &tokenBeginLineNumber,
&tokenBeginColumnNumber);
if (unsigned(tokenBeginLineNumber) > d->line()
|| (unsigned(tokenBeginLineNumber) == d->line()
&& unsigned(tokenBeginColumnNumber) > d->column())) {
result = r; // take the symbol under cursor.
break;
}
}
}
}
if (Symbol *symbol = result.declaration()) {
Symbol *def = 0;
// 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, snapshot, symbolFinder);
if (def == lastVisibleSymbol)
def = 0; // jump to declaration then.
if (symbol->isForwardClassDeclaration())
def = symbolFinder->findMatchingClassDeclaration(symbol, snapshot);
}
link = m_widget->linkToSymbol(def ? def : symbol);
link.linkTextStart = beginOfToken;
link.linkTextEnd = endOfToken;
return link;
}
}
// Handle macro uses
QTextCursor macroCursor = cursor;
const QByteArray name = CPPEditorWidget::identifierUnderCursor(&macroCursor).toLatin1();
link = findMacroLink(name, documentFromSemanticInfo);
if (link.hasValidTarget()) {
link.linkTextStart = macroCursor.selectionStart();
link.linkTextEnd = macroCursor.selectionEnd();
return link;
}
return Link();
}
VirtualFunctionAssistProvider *FollowSymbolUnderCursor::virtualFunctionAssistProvider()
{
return m_virtualFunctionAssistProvider;
}
void FollowSymbolUnderCursor::setVirtualFunctionAssistProvider(VirtualFunctionAssistProvider *provider)
{
m_virtualFunctionAssistProvider = provider;
} }

View File

@@ -34,7 +34,9 @@
#include <cplusplus/CppDocument.h> #include <cplusplus/CppDocument.h>
#include <texteditor/basetexteditor.h> #include <texteditor/basetexteditor.h>
#include <QTextCursor> QT_BEGIN_NAMESPACE
class QTextCursor;
QT_END_NAMESPACE
namespace CppTools { class SymbolFinder; } namespace CppTools { class SymbolFinder; }
@@ -42,32 +44,27 @@ namespace CppEditor {
namespace Internal { namespace Internal {
class CPPEditorWidget; class CPPEditorWidget;
class VirtualFunctionAssistProvider;
class FollowSymbolUnderCursor class FollowSymbolUnderCursor
{ {
public: public:
typedef TextEditor::BaseTextEditorWidget::Link Link; typedef TextEditor::BaseTextEditorWidget::Link Link;
// Ownership of widget and symbolFinder is *not* transferred. FollowSymbolUnderCursor(CPPEditorWidget *widget);
FollowSymbolUnderCursor(CPPEditorWidget *widget, const QTextCursor &cursor, bool resolveTarget, ~FollowSymbolUnderCursor();
Link findLink(const QTextCursor &cursor, bool resolveTarget,
const CPlusPlus::Snapshot &snapshot, const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::Document::Ptr &documentFromSemanticInfo, const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
CppTools::SymbolFinder *symbolFinder); CppTools::SymbolFinder *symbolFinder, bool inNextSplit);
Link findLink(); VirtualFunctionAssistProvider *virtualFunctionAssistProvider();
void setVirtualFunctionAssistProvider(VirtualFunctionAssistProvider *provider);
private: private:
Link attemptFuncDeclDef(const QTextCursor &cursor); CPPEditorWidget *m_widget;
CPlusPlus::Symbol *findDefinition(CPlusPlus::Symbol *symbol, VirtualFunctionAssistProvider *m_virtualFunctionAssistProvider;
const CPlusPlus::Snapshot &snapshot) const;
private:
CppEditor::Internal::CPPEditorWidget *m_widget;
const QTextCursor m_cursor;
const bool m_resolveTarget;
CPlusPlus::Snapshot m_snapshot;
CPlusPlus::Document::Ptr m_document; // from SemanticInfo, i.e. with AST
CppTools::SymbolFinder *m_symbolFinder;
}; };
} // namespace Internal } // namespace Internal

View File

@@ -0,0 +1,324 @@
/****************************************************************************
**
** Copyright (C) 2013 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 "cppvirtualfunctionassistprovider.h"
#include "cppeditorconstants.h"
#include "cppelementevaluator.h"
#include <cplusplus/Icons.h>
#include <cplusplus/Overview.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
#include <texteditor/codeassist/basicproposalitem.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/genericproposalwidget.h>
#include <texteditor/codeassist/iassistinterface.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/iassistproposal.h>
#include <utils/qtcassert.h>
using namespace CPlusPlus;
using namespace CppEditor::Internal;
using namespace TextEditor;
class VirtualFunctionProposalItem: public BasicProposalItem {
public:
VirtualFunctionProposalItem(const BaseTextEditorWidget::Link &link, bool openInSplit = true)
: m_link(link), m_openInSplit(openInSplit) {}
void apply(BaseTextEditor */*editor*/, int /*basePosition*/) const
{
if (!m_link.hasValidTarget())
return;
Core::EditorManager::OpenEditorFlags flags;
if (m_openInSplit)
flags |= Core::EditorManager::OpenInOtherSplit;
Core::EditorManager::openEditorAt(m_link.targetFileName,
m_link.targetLine,
m_link.targetColumn,
CppEditor::Constants::CPPEDITOR_ID,
flags);
}
private:
BaseTextEditorWidget::Link m_link;
bool m_openInSplit;
};
/// Activate current item with the same shortcut that is configured for Follow Symbol Under Cursor.
/// This is limited to single-key shortcuts without modifiers.
class VirtualFunctionProposalWidget : public GenericProposalWidget
{
public:
VirtualFunctionProposalWidget(bool openInSplit)
{
const char *id = openInSplit
? TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT
: TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR;
if (Core::Command *command = Core::ActionManager::command(id))
m_sequence = command->keySequence();
}
protected:
bool eventFilter(QObject *o, QEvent *e)
{
if (e->type() == QEvent::ShortcutOverride && m_sequence.count() == 1) {
QKeyEvent *ke = static_cast<QKeyEvent *>(e);
const QKeySequence seq(ke->key());
if (seq == m_sequence) {
activateCurrentProposalItem();
e->accept();
return true;
}
}
return GenericProposalWidget::eventFilter(o, e);
}
private:
QKeySequence m_sequence;
};
class VirtualFunctionProposal : public GenericProposal
{
public:
VirtualFunctionProposal(int cursorPos, IGenericProposalModel *model, bool openInSplit)
: GenericProposal(cursorPos, model)
, m_openInSplit(openInSplit)
{}
bool isFragile() const
{ return true; }
IAssistProposalWidget *createWidget() const
{ return new VirtualFunctionProposalWidget(m_openInSplit); }
private:
bool m_openInSplit;
};
class VirtualFunctionsAssistProcessor : public IAssistProcessor
{
public:
VirtualFunctionsAssistProcessor(const VirtualFunctionAssistProvider *provider)
: m_startClass(provider->startClass())
, m_function(provider->function())
, m_snapshot(provider->snapshot())
, m_openInNextSplit(provider->openInNextSplit())
{}
IAssistProposal *immediateProposal(const TextEditor::IAssistInterface *interface)
{
QTC_ASSERT(m_function, return 0);
BasicProposalItem *hintItem = new VirtualFunctionProposalItem(CPPEditorWidget::Link());
hintItem->setText(QCoreApplication::translate("VirtualFunctionsAssistProcessor",
"...searching overrides"));
hintItem->setOrder(-1000);
QList<BasicProposalItem *> items;
items << itemFromSymbol(m_function, m_function);
items << hintItem;
return new VirtualFunctionProposal(interface->position(),
new BasicProposalItemListModel(items),
m_openInNextSplit);
}
IAssistProposal *perform(const IAssistInterface *interface)
{
if (!interface)
return 0;
QTC_ASSERT(m_startClass, return 0);
QTC_ASSERT(m_function, return 0);
QTC_ASSERT(!m_snapshot.isEmpty(), return 0);
const QList<Symbol *> overrides = FunctionHelper::overrides(m_startClass, m_function,
m_snapshot);
QList<BasicProposalItem *> items;
foreach (Symbol *symbol, overrides)
items << itemFromSymbol(symbol, m_function);
return new VirtualFunctionProposal(interface->position(),
new BasicProposalItemListModel(items),
m_openInNextSplit);
}
BasicProposalItem *itemFromSymbol(Symbol *symbol, Symbol *firstSymbol) const
{
const QString text = m_overview.prettyName(LookupContext::fullyQualifiedName(symbol));
const CPPEditorWidget::Link link = CPPEditorWidget::linkToSymbol(symbol);
BasicProposalItem *item = new VirtualFunctionProposalItem(link, m_openInNextSplit);
item->setText(text);
item->setIcon(m_icons.iconForSymbol(symbol));
if (symbol == firstSymbol)
item->setOrder(1000); // Ensure top position for function of static type
return item;
}
private:
Class *m_startClass;
Function *m_function;
Snapshot m_snapshot;
bool m_openInNextSplit;
Overview m_overview;
Icons m_icons;
};
VirtualFunctionAssistProvider::VirtualFunctionAssistProvider()
: m_function(0)
, m_openInNextSplit(false)
{
}
bool VirtualFunctionAssistProvider::configure(Class *startClass, Function *function,
const Snapshot &snapshot, bool openInNextSplit)
{
m_startClass = startClass;
m_function = function;
m_snapshot = snapshot;
m_openInNextSplit = openInNextSplit;
return true;
}
bool VirtualFunctionAssistProvider::isAsynchronous() const
{
return true;
}
bool VirtualFunctionAssistProvider::supportsEditor(const Core::Id &editorId) const
{
return editorId == CppEditor::Constants::CPPEDITOR_ID;
}
IAssistProcessor *VirtualFunctionAssistProvider::createProcessor() const
{
return new VirtualFunctionsAssistProcessor(this);
}
enum VirtualType { Virtual, PureVirtual };
static bool isVirtualFunction_helper(Function *function, const Snapshot &snapshot,
VirtualType virtualType)
{
if (!function)
return false;
if ((virtualType == Virtual && function->isVirtual())
|| (virtualType == PureVirtual && function->isPureVirtual())) {
return true;
}
const QString filePath = QString::fromUtf8(function->fileName(), function->fileNameLength());
if (Document::Ptr document = snapshot.document(filePath)) {
LookupContext context(document, snapshot);
QList<LookupItem> results = context.lookup(function->name(), function->enclosingScope());
if (!results.isEmpty()) {
foreach (const LookupItem &item, results) {
if (Symbol *symbol = item.declaration()) {
if (Function *functionType = symbol->type()->asFunctionType()) {
if (!functionType) {
if (Template *t = item.type()->asTemplateType())
if ((symbol = t->declaration()))
functionType = symbol->type()->asFunctionType();
}
const bool foundSuitable = virtualType == Virtual
? functionType->isVirtual()
: functionType->isPureVirtual();
if (foundSuitable)
return true;
}
}
}
}
}
return false;
}
bool FunctionHelper::isVirtualFunction(Function *function, const Snapshot &snapshot)
{
return isVirtualFunction_helper(function, snapshot, Virtual);
}
bool FunctionHelper::isPureVirtualFunction(Function *function, const Snapshot &snapshot)
{
return isVirtualFunction_helper(function, snapshot, PureVirtual);
}
QList<Symbol *> FunctionHelper::overrides(Class *startClass, Function *function,
const Snapshot &snapshot)
{
QList<Symbol *> result;
QTC_ASSERT(function && startClass, return result);
FullySpecifiedType referenceType = function->type();
const Name *referenceName = function->name();
// Add itself
result << function;
// Find overrides
CppEditor::Internal::CppClass cppClass = CppClass(startClass);
cppClass.lookupDerived(startClass, snapshot);
QList<CppClass> l;
l << cppClass;
while (!l.isEmpty()) {
// Add derived
CppClass clazz = l.takeFirst();
foreach (const CppClass &d, clazz.derived) {
if (!l.contains(d))
l << d;
}
// Check member functions
QTC_ASSERT(clazz.declaration, continue);
Class *c = clazz.declaration->asClass();
QTC_ASSERT(c, continue);
for (int i = 0, total = c->memberCount(); i < total; ++i) {
Symbol *candidate = c->memberAt(i);
const Name *candidateName = candidate->name();
FullySpecifiedType candidateType = candidate->type();
if (candidateName->isEqualTo(referenceName) && candidateType.isEqualTo(referenceType))
result << candidate;
}
}
return result;
}

View File

@@ -0,0 +1,80 @@
/****************************************************************************
**
** Copyright (C) 2013 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.
**
****************************************************************************/
#ifndef CPPFOLLOWVIRTUALSYMBOLS_H
#define CPPFOLLOWVIRTUALSYMBOLS_H
#include <texteditor/codeassist/iassistprovider.h>
#include <cplusplus/CppDocument.h>
#include <cplusplus/Symbols.h>
namespace CppEditor {
namespace Internal {
class VirtualFunctionAssistProvider : public TextEditor::IAssistProvider
{
public:
VirtualFunctionAssistProvider();
virtual bool configure(CPlusPlus::Class *startClass, CPlusPlus::Function *function,
const CPlusPlus::Snapshot &snapshot, bool openInNextSplit);
CPlusPlus::Class *startClass() const { return m_startClass; }
CPlusPlus::Function *function() const { return m_function; }
CPlusPlus::Snapshot snapshot() const { return m_snapshot; }
bool openInNextSplit() const { return m_openInNextSplit; }
bool isAsynchronous() const;
bool supportsEditor(const Core::Id &editorId) const;
TextEditor::IAssistProcessor *createProcessor() const;
private:
CPlusPlus::Class *m_startClass;
CPlusPlus::Function *m_function;
CPlusPlus::Snapshot m_snapshot;
bool m_openInNextSplit;
};
class FunctionHelper
{
public:
static bool isVirtualFunction(CPlusPlus::Function *function,
const CPlusPlus::Snapshot &snapshot);
static bool isPureVirtualFunction(CPlusPlus::Function *function,
const CPlusPlus::Snapshot &snapshot);
static QList<CPlusPlus::Symbol *> overrides(CPlusPlus::Class *startClass,
CPlusPlus::Function *function, const CPlusPlus::Snapshot &snapshot);
};
} // namespace Internal
} // namespace CppEditor
#endif // CPPFOLLOWVIRTUALSYMBOLS_H

View File

@@ -29,8 +29,13 @@
#include "cppeditor.h" #include "cppeditor.h"
#include "cppeditorplugin.h" #include "cppeditorplugin.h"
#include "cppelementevaluator.h"
#include "cppvirtualfunctionassistprovider.h"
#include <coreplugin/plugintestutils.h> #include <coreplugin/plugintestutils.h>
#include <texteditor/codeassist/iassistproposal.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <utils/fileutils.h> #include <utils/fileutils.h>
#include <QDebug> #include <QDebug>
@@ -57,6 +62,60 @@ using namespace Core;
namespace { namespace {
/// A fake virtual functions assist provider that runs processor->perform() already in configure()
class VirtualFunctionTestAssistProvider : public VirtualFunctionAssistProvider
{
public:
VirtualFunctionTestAssistProvider(CPPEditorWidget *editorWidget)
: m_editorWidget(editorWidget)
{}
// Invoke the processor already here to calculate the proposals. Return false in order to
// indicate that configure failed, so the actual code assist invocation leading to a pop-up
// will not happen.
bool configure(CPlusPlus::Class *startClass, CPlusPlus::Function *function,
const CPlusPlus::Snapshot &snapshot, bool openInNextSplit)
{
VirtualFunctionAssistProvider::configure(startClass, function, snapshot, openInNextSplit);
IAssistProcessor *processor = createProcessor();
IAssistInterface *interface = m_editorWidget->createAssistInterface(FollowSymbol,
ExplicitlyInvoked);
IAssistProposal *immediateProposal = processor->immediateProposal(interface);
IAssistProposal *finalProposal = processor->perform(interface);
m_immediateItems = itemList(immediateProposal->model());
m_finalItems = itemList(finalProposal->model());
return false;
}
static QStringList itemList(IAssistProposalModel *imodel)
{
QStringList immediateItems;
BasicProposalItemListModel *model = dynamic_cast<BasicProposalItemListModel *>(imodel);
if (!model)
return immediateItems;
if (model->isSortable(QString()))
model->sort(QString());
model->removeDuplicates();
for (int i = 0, size = model->size(); i < size; ++i) {
const QString text = model->text(i);
immediateItems.append(text);
}
return immediateItems;
}
public:
QStringList m_immediateItems;
QStringList m_finalItems;
private:
CPPEditorWidget *m_editorWidget;
};
class TestDocument; class TestDocument;
typedef QSharedPointer<TestDocument> TestDocumentPtr; typedef QSharedPointer<TestDocument> TestDocumentPtr;
@@ -136,12 +195,16 @@ class TestCase
{ {
public: public:
enum CppEditorAction { enum CppEditorAction {
FollowSymbolUnderCursor, FollowSymbolUnderCursorAction,
SwitchBetweenMethodDeclarationDefinition SwitchBetweenMethodDeclarationDefinitionAction
}; };
TestCase(CppEditorAction action, const QByteArray &source); TestCase(CppEditorAction action, const QByteArray &source,
TestCase(CppEditorAction action, const QList<TestDocumentPtr> theTestFiles); const QStringList &expectedVirtualFunctionImmediateProposal = QStringList(),
const QStringList &expectedVirtualFunctionFinalProposal = QStringList());
TestCase(CppEditorAction action, const QList<TestDocumentPtr> theTestFiles,
const QStringList &expectedVirtualSymbolsImmediateProposal = QStringList(),
const QStringList &expectedVirtualSymbolsFinalProposal = QStringList());
~TestCase(); ~TestCase();
void run(bool expectedFail = false); void run(bool expectedFail = false);
@@ -158,12 +221,18 @@ private:
private: private:
CppEditorAction m_action; CppEditorAction m_action;
QList<TestDocumentPtr> m_testFiles; QList<TestDocumentPtr> m_testFiles;
QStringList m_expectedVirtualSymbolsImmediateProposal; // for virtual functions
QStringList m_expectedVirtualSymbolsFinalProposals; // for virtual functions
}; };
/// Convenience function for creating a TestDocument. /// Convenience function for creating a TestDocument.
/// See TestDocument. /// See TestDocument.
TestCase::TestCase(CppEditorAction action, const QByteArray &source) TestCase::TestCase(CppEditorAction action, const QByteArray &source,
const QStringList &expectedVirtualFunctionImmediateProposal,
const QStringList &expectedVirtualFunctionFinalProposal)
: m_action(action) : m_action(action)
, m_expectedVirtualSymbolsImmediateProposal(expectedVirtualFunctionImmediateProposal)
, m_expectedVirtualSymbolsFinalProposals(expectedVirtualFunctionFinalProposal)
{ {
m_testFiles << TestDocument::create(source, QLatin1String("file.cpp")); m_testFiles << TestDocument::create(source, QLatin1String("file.cpp"));
init(); init();
@@ -173,9 +242,13 @@ TestCase::TestCase(CppEditorAction action, const QByteArray &source)
/// Exactly one test document must be provided that contains '@', the initial position marker. /// Exactly one test document must be provided that contains '@', the initial position marker.
/// Exactly one test document must be provided that contains '$', the target position marker. /// Exactly one test document must be provided that contains '$', the target position marker.
/// It can be the same document. /// It can be the same document.
TestCase::TestCase(CppEditorAction action, const QList<TestDocumentPtr> theTestFiles) TestCase::TestCase(CppEditorAction action, const QList<TestDocumentPtr> theTestFiles,
const QStringList &expectedVirtualSymbolsImmediateProposal,
const QStringList &expectedVirtualSymbolsFinalProposal)
: m_action(action) : m_action(action)
, m_testFiles(theTestFiles) , m_testFiles(theTestFiles)
, m_expectedVirtualSymbolsImmediateProposal(expectedVirtualSymbolsImmediateProposal)
, m_expectedVirtualSymbolsFinalProposals(expectedVirtualSymbolsFinalProposal)
{ {
init(); init();
} }
@@ -292,12 +365,29 @@ void TestCase::run(bool expectedFail)
// qDebug() << "Initial line:" << initialTestFile->editor->currentLine(); // qDebug() << "Initial line:" << initialTestFile->editor->currentLine();
// qDebug() << "Initial column:" << initialTestFile->editor->currentColumn() - 1; // qDebug() << "Initial column:" << initialTestFile->editor->currentColumn() - 1;
QStringList immediateVirtualSymbolResults;
QStringList finalVirtualSymbolResults;
// Trigger the action // Trigger the action
switch (m_action) { switch (m_action) {
case FollowSymbolUnderCursor: case FollowSymbolUnderCursorAction: {
CPPEditorWidget *widget = initialTestFile->editorWidget;
FollowSymbolUnderCursor *delegate = widget->followSymbolUnderCursorDelegate();
VirtualFunctionAssistProvider *original = delegate->virtualFunctionAssistProvider();
// Set test provider, run and get results
QScopedPointer<VirtualFunctionTestAssistProvider> testProvider(
new VirtualFunctionTestAssistProvider(widget));
delegate->setVirtualFunctionAssistProvider(testProvider.data());
initialTestFile->editorWidget->openLinkUnderCursor(); initialTestFile->editorWidget->openLinkUnderCursor();
immediateVirtualSymbolResults = testProvider->m_immediateItems;
finalVirtualSymbolResults = testProvider->m_finalItems;
// Restore original test provider
delegate->setVirtualFunctionAssistProvider(original);
break; break;
case SwitchBetweenMethodDeclarationDefinition: }
case SwitchBetweenMethodDeclarationDefinitionAction:
CppEditorPlugin::instance()->switchDeclarationDefinition(); CppEditorPlugin::instance()->switchDeclarationDefinition();
break; break;
default: default:
@@ -318,10 +408,16 @@ void TestCase::run(bool expectedFail)
&expectedLine, &expectedColumn); &expectedLine, &expectedColumn);
// qDebug() << "Expected line:" << expectedLine; // qDebug() << "Expected line:" << expectedLine;
// qDebug() << "Expected column:" << expectedColumn; // qDebug() << "Expected column:" << expectedColumn;
if (expectedFail) if (expectedFail)
QEXPECT_FAIL("", "Contributor works on a fix.", Abort); QEXPECT_FAIL("", "Contributor works on a fix.", Abort);
QCOMPARE(currentTextEditor->currentLine(), expectedLine); QCOMPARE(currentTextEditor->currentLine(), expectedLine);
QCOMPARE(currentTextEditor->currentColumn() - 1, expectedColumn); QCOMPARE(currentTextEditor->currentColumn() - 1, expectedColumn);
// qDebug() << immediateVirtualSymbolResults;
// qDebug() << finalVirtualSymbolResults;
QCOMPARE(immediateVirtualSymbolResults, m_expectedVirtualSymbolsImmediateProposal);
QCOMPARE(finalVirtualSymbolResults, m_expectedVirtualSymbolsFinalProposals);
} }
} // anonymous namespace } // anonymous namespace
@@ -354,7 +450,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromFunctionDeclara
; ;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp")); testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles); TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run(); test.run();
} }
@@ -386,7 +482,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromFunctionDefinit
; ;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp")); testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles); TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run(); test.run();
} }
@@ -418,7 +514,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromFunctionBody()
; ;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp")); testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles); TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run(); test.run();
} }
@@ -450,7 +546,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromReturnType()
; ;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp")); testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles); TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run(); test.run();
} }
@@ -465,7 +561,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_globalVarFromFunction()
"}\n" // Line 5 "}\n" // Line 5
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -483,7 +579,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funLocalVarHidesClassMember()
"};\n" "};\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -503,7 +599,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funLocalVarHidesNamespaceMemb
"}\n" // Line 10 "}\n" // Line 10
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -521,7 +617,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_loopLocalVarHidesOuterScopeVa
"}\n"; "}\n";
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -539,7 +635,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_loopLocalVarHidesOuterScopeVa
"}\n"; "}\n";
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -553,7 +649,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_subsequentDefinedClassMember(
"};\n" "};\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -569,7 +665,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_classMemberHidesOuterTypeDef(
"};\n" // Line 5 "};\n" // Line 5
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -584,7 +680,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_globalVarFromEnum()
"}\n" // Line 5 "}\n" // Line 5
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(/*expectedFail =*/ true); test.run(/*expectedFail =*/ true);
} }
@@ -599,7 +695,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_selfInitialization()
"}\n" // Line 5 "}\n" // Line 5
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -612,7 +708,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_pointerToClassInClassDefiniti
"};\n" "};\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -626,7 +722,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_previouslyDefinedMemberFromAr
"};\n" "};\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -648,7 +744,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_outerStaticMemberVariableFrom
"};\n" "};\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -668,7 +764,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_memberVariableFollowingDotOpe
"}\n" // Line 10 "}\n" // Line 10
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -688,7 +784,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_memberVariableFollowingArrowO
"}\n" // Line 10 "}\n" // Line 10
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -707,7 +803,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_staticMemberVariableFollowing
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -727,7 +823,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_staticMemberVariableFollowing
"}\n" // Line 10 "}\n" // Line 10
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -748,7 +844,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_staticMemberVariableFollowing
"}\n" // Line 10 "}\n" // Line 10
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -762,7 +858,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_previouslyDefinedEnumValueFro
"};\n" "};\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -785,7 +881,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_nsMemberHidesNsMemberIntroduc
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -812,7 +908,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_baseClassFunctionIntroducedBy
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -839,7 +935,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funWithSameNameAsBaseClassFun
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -858,7 +954,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funLocalVarHidesOuterClass()
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -876,7 +972,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_classConstructor()
"{\n" "{\n"
"}\n"; "}\n";
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -894,7 +990,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_classDestructor()
"{\n" "{\n"
"}\n"; "}\n";
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -988,7 +1084,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_QObject_connect()
return; return;
} }
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -1005,7 +1101,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_global
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -1024,7 +1120,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_namesp
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }
@@ -1041,7 +1137,177 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_inside
"}\n" "}\n"
; ;
TestCase test(TestCase::FollowSymbolUnderCursor, source); TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
/// Check: Static type is base class pointer, all overrides are presented.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_allOverrides()
{
const QByteArray source =
"struct A { virtual void virt() = 0; };\n"
"void A::virt() {}\n"
"\n"
"struct B : A { void virt(); };\n"
"void B::virt() {}\n"
"\n"
"struct C : B { void virt(); };\n"
"void C::virt() {}\n"
"\n"
"struct CD1 : C { void virt(); };\n"
"void CD1::virt() {}\n"
"\n"
"struct CD2 : C { void virt(); };\n"
"void CD2::virt() {}\n"
"\n"
"int f(A *o)\n"
"{\n"
" o->$@virt();\n"
"}\n"
;
const QStringList immediateResults = QStringList()
<< QLatin1String("A::virt")
<< QLatin1String("...searching overrides");
const QStringList finalResults = QStringList()
<< QLatin1String("A::virt")
<< QLatin1String("A::virt") // TODO: Double entry
<< QLatin1String("B::virt")
<< QLatin1String("C::virt")
<< QLatin1String("CD1::virt")
<< QLatin1String("CD2::virt");
TestCase test(TestCase::FollowSymbolUnderCursorAction, source, immediateResults, finalResults);
test.run();
}
/// Check: Static type is derived class pointer, only overrides of sub classes are presented.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_possibleOverrides1()
{
const QByteArray source =
"struct A { virtual void virt() = 0; };\n"
"void A::virt() {}\n"
"\n"
"struct B : A { void virt(); };\n"
"void B::virt() {}\n"
"\n"
"struct C : B { void virt(); };\n"
"void C::virt() {}\n"
"\n"
"struct CD1 : C { void virt(); };\n"
"void CD1::virt() {}\n"
"\n"
"struct CD2 : C { void virt(); };\n"
"void CD2::virt() {}\n"
"\n"
"int f(B *o)\n"
"{\n"
" o->$@virt();\n"
"}\n"
;
const QStringList immediateResults = QStringList()
<< QLatin1String("B::virt")
<< QLatin1String("...searching overrides");
const QStringList finalResults = QStringList()
<< QLatin1String("B::virt")
<< QLatin1String("B::virt") // Double entry
<< QLatin1String("C::virt")
<< QLatin1String("CD1::virt")
<< QLatin1String("CD2::virt");
TestCase test(TestCase::FollowSymbolUnderCursorAction, source, immediateResults, finalResults);
test.run();
}
/// Check: Virtual function call in member of class hierarchy, only possible overrides are presented.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_possibleOverrides2()
{
const QByteArray source =
"struct A { virtual void f(); };\n"
"void A::f() {}\n"
"\n"
"struct B : public A { void f(); };\n"
"void B::f() {}\n"
"\n"
"struct C : public B { void g() { f$@(); } }; \n"
"\n"
"struct D : public C { void f(); };\n"
"void D::f() {}\n"
;
const QStringList immediateResults = QStringList()
<< QLatin1String("B::f")
<< QLatin1String("...searching overrides");
const QStringList finalResults = QStringList()
<< QLatin1String("B::f")
<< QLatin1String("B::f")
<< QLatin1String("D::f");
TestCase test(TestCase::FollowSymbolUnderCursorAction, source, immediateResults, finalResults);
test.run();
}
/// Check: Do not trigger on qualified function calls.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_notOnQualified()
{
const QByteArray source =
"struct A { virtual void f(); };\n"
"void A::$f() {}\n"
"\n"
"struct B : public A {\n"
" void f();\n"
" void g() { A::@f(); }\n"
"};\n"
;
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
/// Check: Do not trigger on member function declaration.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_notOnDeclaration()
{
const QByteArray source =
"struct A { virtual void f(); };\n"
"void A::f() {}\n"
"\n"
"struct B : public A { void f@(); };\n"
"void B::$f() {}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
/// Check: Do not trigger on function definition.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_notOnDefinition()
{
const QByteArray source =
"struct A { virtual void f(); };\n"
"void A::f() {}\n"
"\n"
"struct B : public A { void $f(); };\n"
"void B::@f() {}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_notOnNonPointerNonReference()
{
const QByteArray source =
"struct A { virtual void f(); };\n"
"void A::f() {}\n"
"\n"
"struct B : public A { void f(); };\n"
"void B::$f() {}\n"
"\n"
"void client(B b) { b.@f(); }\n"
;
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(); test.run();
} }

View File

@@ -1063,7 +1063,9 @@ void QmlJSTextEditorWidget::createToolBar(QmlJSEditor *editor)
editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Left, m_outlineCombo); editor->insertExtraToolBarWidget(TextEditor::BaseTextEditor::Left, m_outlineCombo);
} }
TextEditor::BaseTextEditorWidget::Link QmlJSTextEditorWidget::findLinkAt(const QTextCursor &cursor, bool /*resolveTarget*/) TextEditor::BaseTextEditorWidget::Link QmlJSTextEditorWidget::findLinkAt(const QTextCursor &cursor,
bool /*resolveTarget*/,
bool /*inNextSplit*/)
{ {
const SemanticInfo semanticInfo = m_semanticInfo; const SemanticInfo semanticInfo = m_semanticInfo;
if (! semanticInfo.isValid()) if (! semanticInfo.isValid())

View File

@@ -161,7 +161,9 @@ protected:
void scrollContentsBy(int dx, int dy); void scrollContentsBy(int dx, int dy);
TextEditor::BaseTextEditor *createEditor(); TextEditor::BaseTextEditor *createEditor();
void createToolBar(QmlJSEditor *editable); void createToolBar(QmlJSEditor *editable);
TextEditor::BaseTextEditorWidget::Link findLinkAt(const QTextCursor &cursor, bool resolveTarget = true); TextEditor::BaseTextEditorWidget::Link findLinkAt(const QTextCursor &cursor,
bool resolveTarget = true,
bool inNextSplit = false);
QString foldReplacementText(const QTextBlock &block) const; QString foldReplacementText(const QTextBlock &block) const;
private: private:

View File

@@ -112,7 +112,8 @@ static bool isValidFileNameChar(const QChar &c)
} }
ProFileEditorWidget::Link ProFileEditorWidget::findLinkAt(const QTextCursor &cursor, ProFileEditorWidget::Link ProFileEditorWidget::findLinkAt(const QTextCursor &cursor,
bool /* resolveTarget */) bool /*resolveTarget*/,
bool /*inNextSplit*/)
{ {
Link link; Link link;

View File

@@ -72,7 +72,8 @@ public:
void unCommentSelection(); void unCommentSelection();
protected: protected:
virtual Link findLinkAt(const QTextCursor &, bool resolveTarget = true); virtual Link findLinkAt(const QTextCursor &, bool resolveTarget = true,
bool inNextSplit = false);
TextEditor::BaseTextEditor *createEditor(); TextEditor::BaseTextEditor *createEditor();
void contextMenuEvent(QContextMenuEvent *); void contextMenuEvent(QContextMenuEvent *);

View File

@@ -1047,16 +1047,16 @@ void BaseTextEditorWidget::unindent()
void BaseTextEditorWidget::openLinkUnderCursor() void BaseTextEditorWidget::openLinkUnderCursor()
{ {
Link symbolLink = findLinkAt(textCursor()); const bool openInNextSplit = alwaysOpenLinksInNextSplit();
Link symbolLink = findLinkAt(textCursor(), true, openInNextSplit);
openLink(symbolLink, alwaysOpenLinksInNextSplit()); openLink(symbolLink, openInNextSplit);
} }
void BaseTextEditorWidget::openLinkUnderCursorInNextSplit() void BaseTextEditorWidget::openLinkUnderCursorInNextSplit()
{ {
Link symbolLink = findLinkAt(textCursor()); const bool openInNextSplit = !alwaysOpenLinksInNextSplit();
Link symbolLink = findLinkAt(textCursor(), true, openInNextSplit);
openLink(symbolLink, !alwaysOpenLinksInNextSplit()); openLink(symbolLink, openInNextSplit);
} }
void BaseTextEditorWidget::abortAssist() void BaseTextEditorWidget::abortAssist()
@@ -4803,7 +4803,7 @@ void BaseTextEditorWidget::reindent(QTextDocument *doc, const QTextCursor &curso
d->m_indenter->reindent(doc, cursor, tabSettings()); d->m_indenter->reindent(doc, cursor, tabSettings());
} }
BaseTextEditorWidget::Link BaseTextEditorWidget::findLinkAt(const QTextCursor &, bool) BaseTextEditorWidget::Link BaseTextEditorWidget::findLinkAt(const QTextCursor &, bool, bool)
{ {
return Link(); return Link();
} }

View File

@@ -503,7 +503,8 @@ protected:
\a resolveTarget is set to true when the target of the link is relevant \a resolveTarget is set to true when the target of the link is relevant
(it isn't until the link is used). (it isn't until the link is used).
*/ */
virtual Link findLinkAt(const QTextCursor &, bool resolveTarget = true); virtual Link findLinkAt(const QTextCursor &, bool resolveTarget = true,
bool inNextSplit = false);
/*! /*!
Reimplement this function if you want to customize the way a link is Reimplement this function if you want to customize the way a link is

View File

@@ -35,7 +35,8 @@ namespace TextEditor {
enum AssistKind enum AssistKind
{ {
Completion, Completion,
QuickFix QuickFix,
FollowSymbol
}; };
enum AssistReason enum AssistReason

View File

@@ -547,9 +547,7 @@ bool GenericProposalWidget::eventFilter(QObject *o, QEvent *e)
if (fe->reason() == Qt::OtherFocusReason) { if (fe->reason() == Qt::OtherFocusReason) {
// Qt/carbon workaround // Qt/carbon workaround
// focus out is received before the key press event. // focus out is received before the key press event.
if (d->m_completionListView->currentIndex().isValid()) activateCurrentProposalItem();
emit proposalItemActivated(d->m_model->proposalItem(
d->m_completionListView->currentIndex().row()));
} }
} }
if (d->m_infoFrame) if (d->m_infoFrame)
@@ -593,9 +591,7 @@ bool GenericProposalWidget::eventFilter(QObject *o, QEvent *e)
case Qt::Key_Return: case Qt::Key_Return:
if (!useCarbonWorkaround()) { if (!useCarbonWorkaround()) {
abort(); abort();
if (d->m_completionListView->currentIndex().isValid()) activateCurrentProposalItem();
emit proposalItemActivated(d->m_model->proposalItem(
d->m_completionListView->currentIndex().row()));
} }
return true; return true;
@@ -658,6 +654,16 @@ bool GenericProposalWidget::eventFilter(QObject *o, QEvent *e)
return false; return false;
} }
bool GenericProposalWidget::activateCurrentProposalItem()
{
if (d->m_completionListView->currentIndex().isValid()) {
const int currentRow = d->m_completionListView->currentIndex().row();
emit proposalItemActivated(d->m_model->proposalItem(currentRow));
return true;
}
return false;
}
#include "genericproposalwidget.moc" #include "genericproposalwidget.moc"
} // TextEditor } // TextEditor

View File

@@ -32,11 +32,13 @@
#include "iassistproposalwidget.h" #include "iassistproposalwidget.h"
#include <texteditor/texteditor_global.h>
namespace TextEditor { namespace TextEditor {
class GenericProposalWidgetPrivate; class GenericProposalWidgetPrivate;
class GenericProposalWidget : public IAssistProposalWidget class TEXTEDITOR_EXPORT GenericProposalWidget : public IAssistProposalWidget
{ {
Q_OBJECT Q_OBJECT
friend class GenericProposalWidgetPrivate; friend class GenericProposalWidgetPrivate;
@@ -69,6 +71,7 @@ private slots:
protected: protected:
virtual bool eventFilter(QObject *o, QEvent *e); virtual bool eventFilter(QObject *o, QEvent *e);
bool activateCurrentProposalItem();
private: private:
GenericProposalWidgetPrivate *d; GenericProposalWidgetPrivate *d;