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,
bool/* resolveTarget*/)
bool/* resolveTarget*/, bool /*inNextSplit*/)
{
Link link;

View File

@@ -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();

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,262 +170,15 @@ inline LookupItem skipForwardDeclarations(const QList<LookupItem> &resolvedSymbo
return result;
}
} // anonymous namespace
FollowSymbolUnderCursor::FollowSymbolUnderCursor(CPPEditorWidget *widget, const QTextCursor &cursor,
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)
CPPEditorWidget::Link attemptFuncDeclDef(const QTextCursor &cursor,
CPPEditorWidget *widget, CPlusPlus::Snapshot snapshot, const CPlusPlus::Document::Ptr &document,
SymbolFinder *symbolFinder)
{
}
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);
snapshot.insert(document);
Link result;
QList<AST *> path = ASTPath(m_document)(cursor);
QList<AST *> path = ASTPath(document)(cursor);
if (path.size() < 5)
return result;
@@ -434,21 +219,21 @@ CPPEditorWidget::Link FollowSymbolUnderCursor::attemptFuncDeclDef(const QTextCur
Symbol *target = 0;
if (FunctionDefinitionAST *funDef = declParent->asFunctionDefinition()) {
QList<Declaration *> candidates =
m_symbolFinder->findMatchingDeclaration(LookupContext(m_document, m_snapshot),
symbolFinder->findMatchingDeclaration(LookupContext(document, snapshot),
funDef->symbol);
if (!candidates.isEmpty()) // TODO: improve disambiguation
target = candidates.first();
} else if (declParent->asSimpleDeclaration()) {
target = m_symbolFinder->findMatchingDefinition(funcDecl->symbol, m_snapshot);
target = symbolFinder->findMatchingDefinition(funcDecl->symbol, snapshot);
}
if (target) {
result = m_widget->linkToSymbol(target);
result = widget->linkToSymbol(target);
unsigned startLine, startColumn, endLine, endColumn;
m_document->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine,
document->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine,
&startColumn);
m_document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine,
document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine,
&endColumn);
QTextDocument *textDocument = cursor.document();
@@ -461,7 +246,7 @@ CPPEditorWidget::Link FollowSymbolUnderCursor::attemptFuncDeclDef(const QTextCur
return result;
}
Symbol *FollowSymbolUnderCursor::findDefinition(Symbol *symbol, const Snapshot &snapshot) const
Symbol *findDefinition(Symbol *symbol, const Snapshot &snapshot, SymbolFinder *symbolFinder)
{
if (symbol->isFunction())
return 0; // symbol is a function definition.
@@ -469,5 +254,282 @@ Symbol *FollowSymbolUnderCursor::findDefinition(Symbol *symbol, const Snapshot &
else if (!symbol->type()->isFunctionType())
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 <texteditor/basetexteditor.h>
#include <QTextCursor>
QT_BEGIN_NAMESPACE
class QTextCursor;
QT_END_NAMESPACE
namespace CppTools { class SymbolFinder; }
@@ -42,32 +44,27 @@ namespace CppEditor {
namespace Internal {
class CPPEditorWidget;
class VirtualFunctionAssistProvider;
class FollowSymbolUnderCursor
{
public:
typedef TextEditor::BaseTextEditorWidget::Link Link;
// Ownership of widget and symbolFinder is *not* transferred.
FollowSymbolUnderCursor(CPPEditorWidget *widget, const QTextCursor &cursor, bool resolveTarget,
FollowSymbolUnderCursor(CPPEditorWidget *widget);
~FollowSymbolUnderCursor();
Link findLink(const QTextCursor &cursor, bool resolveTarget,
const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::Document::Ptr &documentFromSemanticInfo,
CppTools::SymbolFinder *symbolFinder);
CppTools::SymbolFinder *symbolFinder, bool inNextSplit);
Link findLink();
VirtualFunctionAssistProvider *virtualFunctionAssistProvider();
void setVirtualFunctionAssistProvider(VirtualFunctionAssistProvider *provider);
private:
Link attemptFuncDeclDef(const QTextCursor &cursor);
CPlusPlus::Symbol *findDefinition(CPlusPlus::Symbol *symbol,
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;
CPPEditorWidget *m_widget;
VirtualFunctionAssistProvider *m_virtualFunctionAssistProvider;
};
} // 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 "cppeditorplugin.h"
#include "cppelementevaluator.h"
#include "cppvirtualfunctionassistprovider.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 <QDebug>
@@ -57,6 +62,60 @@ using namespace Core;
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;
typedef QSharedPointer<TestDocument> TestDocumentPtr;
@@ -136,12 +195,16 @@ class TestCase
{
public:
enum CppEditorAction {
FollowSymbolUnderCursor,
SwitchBetweenMethodDeclarationDefinition
FollowSymbolUnderCursorAction,
SwitchBetweenMethodDeclarationDefinitionAction
};
TestCase(CppEditorAction action, const QByteArray &source);
TestCase(CppEditorAction action, const QList<TestDocumentPtr> theTestFiles);
TestCase(CppEditorAction action, const QByteArray &source,
const QStringList &expectedVirtualFunctionImmediateProposal = QStringList(),
const QStringList &expectedVirtualFunctionFinalProposal = QStringList());
TestCase(CppEditorAction action, const QList<TestDocumentPtr> theTestFiles,
const QStringList &expectedVirtualSymbolsImmediateProposal = QStringList(),
const QStringList &expectedVirtualSymbolsFinalProposal = QStringList());
~TestCase();
void run(bool expectedFail = false);
@@ -158,12 +221,18 @@ private:
private:
CppEditorAction m_action;
QList<TestDocumentPtr> m_testFiles;
QStringList m_expectedVirtualSymbolsImmediateProposal; // for virtual functions
QStringList m_expectedVirtualSymbolsFinalProposals; // for virtual functions
};
/// Convenience function for creating a 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_expectedVirtualSymbolsImmediateProposal(expectedVirtualFunctionImmediateProposal)
, m_expectedVirtualSymbolsFinalProposals(expectedVirtualFunctionFinalProposal)
{
m_testFiles << TestDocument::create(source, QLatin1String("file.cpp"));
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 target position marker.
/// 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_testFiles(theTestFiles)
, m_expectedVirtualSymbolsImmediateProposal(expectedVirtualSymbolsImmediateProposal)
, m_expectedVirtualSymbolsFinalProposals(expectedVirtualSymbolsFinalProposal)
{
init();
}
@@ -292,12 +365,29 @@ void TestCase::run(bool expectedFail)
// qDebug() << "Initial line:" << initialTestFile->editor->currentLine();
// qDebug() << "Initial column:" << initialTestFile->editor->currentColumn() - 1;
QStringList immediateVirtualSymbolResults;
QStringList finalVirtualSymbolResults;
// Trigger the 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();
immediateVirtualSymbolResults = testProvider->m_immediateItems;
finalVirtualSymbolResults = testProvider->m_finalItems;
// Restore original test provider
delegate->setVirtualFunctionAssistProvider(original);
break;
case SwitchBetweenMethodDeclarationDefinition:
}
case SwitchBetweenMethodDeclarationDefinitionAction:
CppEditorPlugin::instance()->switchDeclarationDefinition();
break;
default:
@@ -318,10 +408,16 @@ void TestCase::run(bool expectedFail)
&expectedLine, &expectedColumn);
// qDebug() << "Expected line:" << expectedLine;
// qDebug() << "Expected column:" << expectedColumn;
if (expectedFail)
QEXPECT_FAIL("", "Contributor works on a fix.", Abort);
QCOMPARE(currentTextEditor->currentLine(), expectedLine);
QCOMPARE(currentTextEditor->currentColumn() - 1, expectedColumn);
// qDebug() << immediateVirtualSymbolResults;
// qDebug() << finalVirtualSymbolResults;
QCOMPARE(immediateVirtualSymbolResults, m_expectedVirtualSymbolsImmediateProposal);
QCOMPARE(finalVirtualSymbolResults, m_expectedVirtualSymbolsFinalProposals);
}
} // anonymous namespace
@@ -354,7 +450,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromFunctionDeclara
;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles);
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run();
}
@@ -386,7 +482,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromFunctionDefinit
;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles);
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run();
}
@@ -418,7 +514,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromFunctionBody()
;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles);
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run();
}
@@ -450,7 +546,7 @@ void CppEditorPlugin::test_SwitchMethodDeclarationDefinition_fromReturnType()
;
testFiles << TestDocument::create(sourceContents, QLatin1String("file.cpp"));
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinition, testFiles);
TestCase test(TestCase::SwitchBetweenMethodDeclarationDefinitionAction, testFiles);
test.run();
}
@@ -465,7 +561,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_globalVarFromFunction()
"}\n" // Line 5
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -483,7 +579,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funLocalVarHidesClassMember()
"};\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -503,7 +599,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funLocalVarHidesNamespaceMemb
"}\n" // Line 10
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -521,7 +617,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_loopLocalVarHidesOuterScopeVa
"}\n";
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -539,7 +635,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_loopLocalVarHidesOuterScopeVa
"}\n";
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -553,7 +649,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_subsequentDefinedClassMember(
"};\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -569,7 +665,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_classMemberHidesOuterTypeDef(
"};\n" // Line 5
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -584,7 +680,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_globalVarFromEnum()
"}\n" // Line 5
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run(/*expectedFail =*/ true);
}
@@ -599,7 +695,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_selfInitialization()
"}\n" // Line 5
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -612,7 +708,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_pointerToClassInClassDefiniti
"};\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -626,7 +722,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_previouslyDefinedMemberFromAr
"};\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -648,7 +744,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_outerStaticMemberVariableFrom
"};\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -668,7 +764,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_memberVariableFollowingDotOpe
"}\n" // Line 10
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -688,7 +784,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_memberVariableFollowingArrowO
"}\n" // Line 10
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -707,7 +803,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_staticMemberVariableFollowing
"}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -727,7 +823,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_staticMemberVariableFollowing
"}\n" // Line 10
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -748,7 +844,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_staticMemberVariableFollowing
"}\n" // Line 10
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -762,7 +858,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_previouslyDefinedEnumValueFro
"};\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -785,7 +881,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_nsMemberHidesNsMemberIntroduc
"}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -812,7 +908,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_baseClassFunctionIntroducedBy
"}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -839,7 +935,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funWithSameNameAsBaseClassFun
"}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -858,7 +954,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_funLocalVarHidesOuterClass()
"}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -876,7 +972,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_classConstructor()
"{\n"
"}\n";
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -894,7 +990,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_classDestructor()
"{\n"
"}\n";
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -988,7 +1084,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_QObject_connect()
return;
}
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -1005,7 +1101,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_global
"}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -1024,7 +1120,7 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_namesp
"}\n"
;
TestCase test(TestCase::FollowSymbolUnderCursor, source);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source);
test.run();
}
@@ -1041,7 +1137,177 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_using_QTCREATORBUG7903_inside
"}\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();
}

View File

@@ -1063,7 +1063,9 @@ void QmlJSTextEditorWidget::createToolBar(QmlJSEditor *editor)
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;
if (! semanticInfo.isValid())

View File

@@ -161,7 +161,9 @@ protected:
void scrollContentsBy(int dx, int dy);
TextEditor::BaseTextEditor *createEditor();
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;
private:

View File

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

View File

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

View File

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

View File

@@ -503,7 +503,8 @@ protected:
\a resolveTarget is set to true when the target of the link is relevant
(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

View File

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

View File

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

View File

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