diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp index f21f256a06e..3ae4a1e730c 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp @@ -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; diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.h b/src/plugins/cmakeprojectmanager/cmakeeditor.h index c35eebb26d9..b5102dabfbe 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.h +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.h @@ -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(); diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index bf323e1b9fb..4ec6cb4e5a9 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -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("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(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) diff --git a/src/plugins/cppeditor/cppeditor.h b/src/plugins/cppeditor/cppeditor.h index 4714886f25d..ba8f85db77f 100644 --- a/src/plugins/cppeditor/cppeditor.h +++ b/src/plugins/cppeditor/cppeditor.h @@ -30,6 +30,7 @@ #ifndef CPPEDITOR_H #define CPPEDITOR_H +#include "cppfollowsymbolundercursor.h" #include "cppfunctiondecldeflink.h" #include @@ -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 m_declDefLink; CppTools::CommentsSettings m_commentsSettings; + + QScopedPointer m_followSymbolUnderCursor; }; } // namespace Internal diff --git a/src/plugins/cppeditor/cppeditor.pro b/src/plugins/cppeditor/cppeditor.pro index 740a70d2b98..e793b5d4296 100644 --- a/src/plugins/cppeditor/cppeditor.pro +++ b/src/plugins/cppeditor/cppeditor.pro @@ -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 diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs index fd3da1fe840..941600b6b24 100644 --- a/src/plugins/cppeditor/cppeditor.qbs +++ b/src/plugins/cppeditor/cppeditor.qbs @@ -60,6 +60,8 @@ QtcPlugin { "cppsnippetprovider.h", "cpptypehierarchy.cpp", "cpptypehierarchy.h", + "cppvirtualfunctionassistprovider.cpp", + "cppvirtualfunctionassistprovider.h", ] Group { diff --git a/src/plugins/cppeditor/cppeditorplugin.h b/src/plugins/cppeditor/cppeditorplugin.h index 7442d669240..389ccd43e65 100644 --- a/src/plugins/cppeditor/cppeditorplugin.h +++ b/src/plugins/cppeditor/cppeditorplugin.h @@ -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(); diff --git a/src/plugins/cppeditor/cppelementevaluator.cpp b/src/plugins/cppeditor/cppelementevaluator.cpp index c7cc6dfa68f..fe7dacbbd8a 100644 --- a/src/plugins/cppeditor/cppelementevaluator.cpp +++ b/src/plugins/cppeditor/cppelementevaluator.cpp @@ -272,10 +272,11 @@ CppMacro::CppMacro(const Macro ¯o) // 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 Data; diff --git a/src/plugins/cppeditor/cppelementevaluator.h b/src/plugins/cppeditor/cppelementevaluator.h index 33baf0eac67..498d58ba1f4 100644 --- a/src/plugins/cppeditor/cppelementevaluator.h +++ b/src/plugins/cppeditor/cppelementevaluator.h @@ -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); diff --git a/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp b/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp index 4446c14ac06..4b91d3ce821 100644 --- a/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp +++ b/src/plugins/cppeditor/cppfollowsymbolundercursor.cpp @@ -30,6 +30,7 @@ #include "cppfollowsymbolundercursor.h" #include "cppeditor.h" +#include "cppvirtualfunctionassistprovider.h" #include #include @@ -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 *processed) { @@ -138,262 +170,15 @@ inline LookupItem skipForwardDeclarations(const QList &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 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(¯oCursor).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 ¯o = 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 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(¯oCursor).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 path = ASTPath(m_document)(cursor); + QList path = ASTPath(document)(cursor); if (path.size() < 5) return result; @@ -434,22 +219,22 @@ CPPEditorWidget::Link FollowSymbolUnderCursor::attemptFuncDeclDef(const QTextCur Symbol *target = 0; if (FunctionDefinitionAST *funDef = declParent->asFunctionDefinition()) { QList 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, - &startColumn); - m_document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine, - &endColumn); + document->translationUnit()->getTokenStartPosition(name->firstToken(), &startLine, + &startColumn); + document->translationUnit()->getTokenEndPosition(name->lastToken() - 1, &endLine, + &endColumn); QTextDocument *textDocument = cursor.document(); result.linkTextStart = @@ -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 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(¯oCursor).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 ¯o = 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 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(¯oCursor).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; } diff --git a/src/plugins/cppeditor/cppfollowsymbolundercursor.h b/src/plugins/cppeditor/cppfollowsymbolundercursor.h index 5ef1f1902ad..4d88ecf0cd7 100644 --- a/src/plugins/cppeditor/cppfollowsymbolundercursor.h +++ b/src/plugins/cppeditor/cppfollowsymbolundercursor.h @@ -34,7 +34,9 @@ #include #include -#include +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, - const CPlusPlus::Snapshot &snapshot, - const CPlusPlus::Document::Ptr &documentFromSemanticInfo, - CppTools::SymbolFinder *symbolFinder); + FollowSymbolUnderCursor(CPPEditorWidget *widget); + ~FollowSymbolUnderCursor(); - Link findLink(); + Link findLink(const QTextCursor &cursor, bool resolveTarget, + const CPlusPlus::Snapshot &snapshot, + const CPlusPlus::Document::Ptr &documentFromSemanticInfo, + CppTools::SymbolFinder *symbolFinder, bool inNextSplit); + + 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 diff --git a/src/plugins/cppeditor/cppvirtualfunctionassistprovider.cpp b/src/plugins/cppeditor/cppvirtualfunctionassistprovider.cpp new file mode 100644 index 00000000000..3cbb4f99dff --- /dev/null +++ b/src/plugins/cppeditor/cppvirtualfunctionassistprovider.cpp @@ -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 +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +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(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 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 overrides = FunctionHelper::overrides(m_startClass, m_function, + m_snapshot); + QList 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 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 FunctionHelper::overrides(Class *startClass, Function *function, + const Snapshot &snapshot) +{ + QList 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 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; +} diff --git a/src/plugins/cppeditor/cppvirtualfunctionassistprovider.h b/src/plugins/cppeditor/cppvirtualfunctionassistprovider.h new file mode 100644 index 00000000000..b3bb76c24d1 --- /dev/null +++ b/src/plugins/cppeditor/cppvirtualfunctionassistprovider.h @@ -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 + +#include +#include + +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 overrides(CPlusPlus::Class *startClass, + CPlusPlus::Function *function, const CPlusPlus::Snapshot &snapshot); +}; + +} // namespace Internal +} // namespace CppEditor + +#endif // CPPFOLLOWVIRTUALSYMBOLS_H diff --git a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp index 46ff3e159ba..f922b20de3c 100644 --- a/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp +++ b/src/plugins/cppeditor/followsymbol_switchmethoddecldef_test.cpp @@ -29,8 +29,13 @@ #include "cppeditor.h" #include "cppeditorplugin.h" +#include "cppelementevaluator.h" +#include "cppvirtualfunctionassistprovider.h" #include +#include +#include +#include #include #include @@ -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(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 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 theTestFiles); + TestCase(CppEditorAction action, const QByteArray &source, + const QStringList &expectedVirtualFunctionImmediateProposal = QStringList(), + const QStringList &expectedVirtualFunctionFinalProposal = QStringList()); + TestCase(CppEditorAction action, const QList 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 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 theTestFiles) +TestCase::TestCase(CppEditorAction action, const QList 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 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(); } diff --git a/src/plugins/qmljseditor/qmljseditor.cpp b/src/plugins/qmljseditor/qmljseditor.cpp index f54d440834b..c8b71c9bdad 100644 --- a/src/plugins/qmljseditor/qmljseditor.cpp +++ b/src/plugins/qmljseditor/qmljseditor.cpp @@ -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()) diff --git a/src/plugins/qmljseditor/qmljseditor.h b/src/plugins/qmljseditor/qmljseditor.h index 596bbcf4276..5ce152d3d31 100644 --- a/src/plugins/qmljseditor/qmljseditor.h +++ b/src/plugins/qmljseditor/qmljseditor.h @@ -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: diff --git a/src/plugins/qt4projectmanager/profileeditor.cpp b/src/plugins/qt4projectmanager/profileeditor.cpp index ab35944aaa7..3460c76b982 100644 --- a/src/plugins/qt4projectmanager/profileeditor.cpp +++ b/src/plugins/qt4projectmanager/profileeditor.cpp @@ -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; diff --git a/src/plugins/qt4projectmanager/profileeditor.h b/src/plugins/qt4projectmanager/profileeditor.h index e8a8fb3ad08..3e4150b2aea 100644 --- a/src/plugins/qt4projectmanager/profileeditor.h +++ b/src/plugins/qt4projectmanager/profileeditor.h @@ -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 *); diff --git a/src/plugins/texteditor/basetexteditor.cpp b/src/plugins/texteditor/basetexteditor.cpp index 504404b9797..9588585f8f4 100644 --- a/src/plugins/texteditor/basetexteditor.cpp +++ b/src/plugins/texteditor/basetexteditor.cpp @@ -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(); } diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h index 2f7ee2ee931..3db73fb4826 100644 --- a/src/plugins/texteditor/basetexteditor.h +++ b/src/plugins/texteditor/basetexteditor.h @@ -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 diff --git a/src/plugins/texteditor/codeassist/assistenums.h b/src/plugins/texteditor/codeassist/assistenums.h index 31447cec1bc..b71a96ce467 100644 --- a/src/plugins/texteditor/codeassist/assistenums.h +++ b/src/plugins/texteditor/codeassist/assistenums.h @@ -35,7 +35,8 @@ namespace TextEditor { enum AssistKind { Completion, - QuickFix + QuickFix, + FollowSymbol }; enum AssistReason diff --git a/src/plugins/texteditor/codeassist/genericproposalwidget.cpp b/src/plugins/texteditor/codeassist/genericproposalwidget.cpp index 22b7d158806..52303c1dc0d 100644 --- a/src/plugins/texteditor/codeassist/genericproposalwidget.cpp +++ b/src/plugins/texteditor/codeassist/genericproposalwidget.cpp @@ -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 diff --git a/src/plugins/texteditor/codeassist/genericproposalwidget.h b/src/plugins/texteditor/codeassist/genericproposalwidget.h index cc68c0253eb..f1f5f5a61ec 100644 --- a/src/plugins/texteditor/codeassist/genericproposalwidget.h +++ b/src/plugins/texteditor/codeassist/genericproposalwidget.h @@ -32,11 +32,13 @@ #include "iassistproposalwidget.h" +#include + 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;