/* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "adaptsignatureassistant.h" #include #include #include #include #include #include #include #include #include #include #include "cppduchain.h" #include "qtfunctiondeclaration.h" using namespace KDevelop; using namespace Cpp; namespace { Declaration *getDeclarationAtCursor(const SimpleCursor &cursor, const KUrl &documentUrl) { ENSURE_CHAIN_READ_LOCKED ReferencedTopDUContext top(DUChainUtils::standardContextForUrl(documentUrl)); if(!top) return 0; Declaration* functionDecl = DUChainUtils::declarationInLine(cursor, top.data()); return functionDecl; } Signature getDeclarationSignature(const Declaration *functionDecl, const DUContext *functionCtxt, bool includeDefaults) { ENSURE_CHAIN_READ_LOCKED int pos = 0; Signature signature; const AbstractFunctionDeclaration* abstractFunDecl = dynamic_cast(functionDecl); foreach(Declaration* parameter, functionCtxt->localDeclarations()) { signature.defaultParams << (includeDefaults ? abstractFunDecl->defaultParameterForArgument(pos).str() : ""); signature.parameters << qMakePair(parameter->indexedType(), parameter->identifier().identifier().str()); ++pos; } signature.isConst = functionDecl->abstractType() && functionDecl->abstractType()->modifiers() & AbstractType::ConstModifier; FunctionType::Ptr funType = functionDecl->type(); if(funType) signature.returnType = funType->returnType()->indexed(); return signature; } } AdaptSignatureAssistant::AdaptSignatureAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) { connect(ICore::self()->languageController()->backgroundParser(), SIGNAL(parseJobFinished(KDevelop::ParseJob*)), SLOT(parseJobFinished(KDevelop::ParseJob*))); } QString AdaptSignatureAssistant::title() const { return tr("Adapt Signature"); } void AdaptSignatureAssistant::reset() { doHide(); clearActions(); m_editingDefinition = {}; m_declarationName = {}; m_otherSideId = {}; m_otherSideTopContext = {}; m_otherSideContext = {}; m_oldSignature = Signature(); m_document = {}; m_view.clear(); } void AdaptSignatureAssistant::textChanged(KTextEditor::View* view, const KTextEditor::Range& invocationRange, const QString& removedText) { reset(); m_view = view; //FIXME: update signature assistant to play well with the rename assistant KTextEditor::Range sigAssistRange = invocationRange; if (!removedText.isEmpty()) { sigAssistRange.setRange(sigAssistRange.start(), sigAssistRange.start()); } m_document = view->document()->url(); DUChainReadLocker lock(DUChain::lock(), 300); if(!lock.locked()) { kDebug() << "failed to lock duchain in time"; return; } SimpleRange simpleInvocationRange = SimpleRange(sigAssistRange); Declaration* funDecl = getDeclarationAtCursor(simpleInvocationRange.start, m_document); if(!funDecl || !funDecl->type()) return; if(QtFunctionDeclaration* classFun = dynamic_cast(funDecl)) { if (classFun->isSignal()) { // do not offer to change signature of a signal, as the implementation will be generated by moc return; } } Declaration* otherSide = 0; FunctionDefinition* definition = dynamic_cast(funDecl); if (definition) { m_editingDefinition = true; otherSide = definition->declaration(); } else if ((definition = FunctionDefinition::definition(funDecl))) { m_editingDefinition = false; otherSide = definition; } if (!otherSide) return; m_otherSideContext = DUContextPointer(DUChainUtils::getFunctionContext(otherSide)); if (!m_otherSideContext) return; m_declarationName = funDecl->identifier(); m_otherSideId = otherSide->id(); m_otherSideTopContext = ReferencedTopDUContext(otherSide->topContext()); m_oldSignature = getDeclarationSignature(otherSide, m_otherSideContext.data(), true); //Schedule an update, to make sure the ranges match DUChain::self()->updateContextForUrl(m_otherSideTopContext->url(), TopDUContext::AllDeclarationsAndContexts); } bool AdaptSignatureAssistant::isUseful() const { return !m_declarationName.isEmpty() && m_otherSideId.isValid(); } bool AdaptSignatureAssistant::getSignatureChanges(const Signature& newSignature, QList< int >& oldPositions) const { bool changed = false; for (int i = 0; i < newSignature.parameters.size(); ++i) oldPositions.append(-1); for (int curNewParam = newSignature.parameters.size() - 1; curNewParam >= 0 ; --curNewParam) { int foundAt = -1; for (int curOldParam = m_oldSignature.parameters.size() - 1; curOldParam >= 0 ; --curOldParam) { if (newSignature.parameters[curNewParam].first != m_oldSignature.parameters[curOldParam].first) continue; //Different type == different parameters if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second || curOldParam == curNewParam) { //given the same type and either the same position or the same name, it's (probably) the same argument foundAt = curOldParam; if (newSignature.parameters[curNewParam].second != m_oldSignature.parameters[curOldParam].second || curOldParam != curNewParam) changed = true; //Either the name changed at this position, or position of this name has changed if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second) break; //Found an argument with the same name and type, no need to look further //else: position/type match, but name match will trump, allowing: (int i=0, int j=1) => (int j=1, int i=0) } } if (foundAt < 0) changed = true; oldPositions[curNewParam] = foundAt; } if(newSignature.parameters.size() != m_oldSignature.parameters.size()) changed = true; if(newSignature.isConst != m_oldSignature.isConst) changed = true; if(newSignature.returnType != m_oldSignature.returnType) changed = true; return changed; } void AdaptSignatureAssistant::setDefaultParams(Signature& newSignature, const QList< int >& oldPositions) const { for(int i = newSignature.parameters.size() - 1; i >= 0; --i) { if (oldPositions[i] == -1) return; //this param is new, no further defaults possible if (i == newSignature.defaultParams.size() - 1 || !newSignature.defaultParams[i+1].isEmpty()) newSignature.defaultParams[i] = m_oldSignature.defaultParams[oldPositions[i]]; } } QList< RenameAction* > AdaptSignatureAssistant::getRenameActions(const Signature &newSignature, const QList &oldPositions) const { Q_ASSERT(DUChain::lock()->currentThreadHasReadLock()); QList renameActions; if (!m_otherSideContext) return renameActions; for(int i = newSignature.parameters.size() - 1; i >= 0; --i) { if (oldPositions[i] == -1) continue; //new parameter Declaration *renamedDecl = m_otherSideContext->localDeclarations()[oldPositions[i]]; if (newSignature.parameters[i].second != m_oldSignature.parameters[oldPositions[i]].second) { QMap< IndexedString, QList< RangeInRevision > > uses = renamedDecl->uses(); if (!uses.isEmpty()) { renameActions << new RenameAction(renamedDecl->identifier(), newSignature.parameters[i].second, RevisionedFileRanges::convert(uses)); } } } return renameActions; } void AdaptSignatureAssistant::parseJobFinished(KDevelop::ParseJob* job) { if (job->document().toUrl() != m_document || !m_view) return; clearActions(); DUChainReadLocker lock; Declaration *functionDecl = getDeclarationAtCursor(SimpleCursor(m_view.data()->cursorPosition()), m_document); if (!functionDecl || functionDecl->identifier() != m_declarationName) return; DUContext *functionCtxt = DUChainUtils::getFunctionContext(functionDecl); if (!functionCtxt) return; if(QtFunctionDeclaration* classFun = dynamic_cast(functionDecl)) { if (classFun->isSignal()) { // do not offer to change signature of a signal, as the implementation will be generated by moc return; } } //ParseJob having finished, get the signature that was modified Signature newSignature = getDeclarationSignature(functionDecl, functionCtxt, false); //Check for changes between m_oldSignature and newSignature, use oldPositions to store old<->new param index mapping QList oldPositions; if (!getSignatureChanges(newSignature, oldPositions)) { reset(); return; //No changes to signature } QList renameActions; if (m_editingDefinition) setDefaultParams(newSignature, oldPositions); //restore default parameters before updating the declarations else renameActions = getRenameActions(newSignature, oldPositions); //rename as needed when updating the definition IAssistantAction::Ptr action(new AdaptSignatureAction(m_otherSideId, m_otherSideTopContext, m_oldSignature, newSignature, m_editingDefinition, renameActions)); connect(action.data(), SIGNAL(executed(IAssistantAction*)), SLOT(reset())); addAction(action); emit actionsChanged(); } #include "moc_adaptsignatureassistant.cpp"