/* 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 "sourcemanipulation.h" #include #include "qtfunctiondeclaration.h" #include "declarationbuilder.h" #include "templateparameterdeclaration.h" #include #include using namespace KDevelop; ///Makes sure the line is not in a comment, moving it behind if needed. Just does very simple matching, should be ok for header copyright-notices and such. int KDevelop::SourceCodeInsertion::firstValidCodeLineBefore(int lineNumber) const { if(lineNumber == -1) lineNumber = 1000000; if(lineNumber > 300) lineNumber = 300; //Don't do too much processing int checkLines = m_codeRepresentation->lines() < lineNumber ? m_codeRepresentation->lines() : lineNumber; int chosen = -1; //Add some whitespace so we always have some comment clearing done, in every line QString allText = " \n"; // we've added a line, hence increase our limit as well checkLines += 1; for(int a = 0; a < checkLines; ++a) allText += m_codeRepresentation->line(a) + " \n"; allText = KDevelop::clearComments(allText, '$'); QStringList lines = allText.split('\n'); if(lines.count() < checkLines) checkLines = lines.count(); int lastDefine = -1; int lastComment = -1; for(int a = 0; a < checkLines; ++a) { if(lines.at(a).startsWith('$')) { chosen = -1; lastComment = a; continue; } QString trimmedLine = lines.at(a).trimmed(); if(trimmedLine.startsWith('#')) { lastDefine = a; chosen = -1; continue; } if(trimmedLine.isEmpty() && chosen == -1) chosen = a; if(!trimmedLine.isEmpty()) break; } if(chosen == -1 && lastDefine != -1) { chosen = lastDefine + 1; } if (chosen == -1 && lastComment != -1) { chosen = lastComment + 1; } if(chosen != -1) { // if chosen == 0 we can only add at the start of the document // NOTE: chosen must be reduced by one since we add one line // at the beginning, see above (allText = ...) return qMax(0, chosen - 1); } else return lineNumber; } //Re-indents the code so the leftmost line starts at zero QString zeroIndentation(QString str, int fromLine = 0) { QStringList lines = str.split('\n'); QStringList ret; if(fromLine < lines.size()) { ret = lines.mid(0, fromLine); lines = lines.mid(fromLine); } QRegExp nonWhiteSpace("\\S"); int minLineStart = 10000; foreach(const QString& line, lines) { int lineStart = line.indexOf(nonWhiteSpace); if(lineStart < minLineStart) minLineStart = lineStart; } foreach(const QString& line, lines) ret << line.mid(minLineStart); return ret.join("\n"); } KDevelop::DocumentChangeSet& KDevelop::SourceCodeInsertion::changes() { return m_changeSet; } void KDevelop::SourceCodeInsertion::setInsertBefore(KDevelop::SimpleCursor position) { m_insertBefore = position; } void KDevelop::SourceCodeInsertion::setContext(KDevelop::DUContext* context) { m_context = context; } void KDevelop::SourceCodeInsertion::setSubScope(KDevelop::QualifiedIdentifier scope) { m_scope = scope; DUContext* context = m_topContext; if(m_context) context = m_context; if(!context) return; QStringList needNamespace = m_scope.toStringList(); bool foundChild = true; while(!needNamespace.isEmpty() && foundChild) { foundChild = false; foreach(DUContext* child, context->childContexts()) { kDebug() << "checking child" << child->localScopeIdentifier().toString() << "against" << needNamespace.first(); if(child->localScopeIdentifier().toString() == needNamespace.first() && child->type() == DUContext::Namespace && (child->rangeInCurrentRevision().start < m_insertBefore || !m_insertBefore.isValid())) { kDebug() << "taking"; context = child; foundChild = true; needNamespace.pop_front(); break; } } } m_context = context; m_scope = Cpp::stripPrefixes(context, QualifiedIdentifier(needNamespace.join("::"))); } QString KDevelop::SourceCodeInsertion::applySubScope(QString decl) const { QString ret; QString scopeType = "namespace"; QString scopeClose; if(m_context && m_context->type() == DUContext::Class) { scopeType = "struct"; scopeClose = ";"; } foreach(const QString& scope, m_scope.toStringList()) ret += scopeType + " " + scope + " {\n"; ret += decl; ret += QString("}" + scopeClose + "\n").repeated(m_scope.count()); return ret; } void KDevelop::SourceCodeInsertion::setAccess(KDevelop::Declaration::AccessPolicy access) { m_access = access; } KDevelop::SourceCodeInsertion::SourceCodeInsertion(KDevelop::TopDUContext* topContext) : m_context(topContext), m_access(Declaration::Public), m_topContext(topContext), m_codeRepresentation(KDevelop::createCodeRepresentation(m_topContext->url())) { if(m_topContext->parsingEnvironmentFile() && m_topContext->parsingEnvironmentFile()->isProxyContext()) { kWarning() << "source-code manipulation on proxy-context is wrong!!!" << m_topContext->url().toUrl(); } m_insertBefore = SimpleCursor::invalid(); } KDevelop::SourceCodeInsertion::~SourceCodeInsertion() { } QString KDevelop::SourceCodeInsertion::accessString() const { switch(m_access) { case KDevelop::Declaration::Public: return "public"; case KDevelop::Declaration::Protected: return "protected"; case KDevelop::Declaration::Private: return "private"; default: return QString(); } } QString KDevelop::SourceCodeInsertion::indentation() const { if(!m_codeRepresentation || !m_context || m_context->localDeclarations(m_topContext).size() == 0) { kDebug() << "cannot do indentation"; return QString(); } foreach(Declaration* decl, m_context->localDeclarations(m_topContext)) { if(decl->range().isEmpty() || decl->range().start.column == 0) continue; //Skip declarations with empty range, that were expanded from macros int spaces = 0; QString textLine = m_codeRepresentation->line(decl->range().start.line); for(int a = 0; a < textLine.size(); ++a) { if(textLine.at(a).isSpace()) ++spaces; else break; } return textLine.left(spaces); } return QString(); } QString KDevelop::SourceCodeInsertion::applyIndentation(QString decl) const { QStringList lines = decl.split('\n'); QString ind = indentation(); QStringList ret; foreach(const QString& line, lines) { if(!line.isEmpty()) ret << ind + line; else ret << line; } return ret.join("\n");; } QString makeSignatureString(QList signature, DUContext* context) { QString ret; foreach(const SourceCodeInsertion::SignatureItem& item, signature) { if(!ret.isEmpty()) ret += ", "; AbstractType::Ptr type = TypeUtils::removeConstants(item.type, context->topContext()); ret += Cpp::simplifiedTypeString(type, context); if(!item.name.isEmpty()) ret += " " + item.name; } return ret; } SimpleRange SourceCodeInsertion::insertionRange(int line) { if(line == 0 || !m_codeRepresentation) return SimpleRange(line, 0, line, 0); else { SimpleRange range(line-1, m_codeRepresentation->line(line-1).size(), line-1, m_codeRepresentation->line(line-1).size()); //If the context finishes on that line, then this will need adjusting if(!m_context->rangeInCurrentRevision().textRange().contains(range.textRange())) { range.start = m_context->rangeInCurrentRevision().end; if(range.start.column > 0) range.start.column -= 1; range.end = range.start; } return range; } } bool KDevelop::SourceCodeInsertion::insertFunctionDeclaration(KDevelop::Identifier name, AbstractType::Ptr returnType, QList signature, bool isConstant, QString body) { if(!m_context) return false; returnType = TypeUtils::removeConstants(returnType, m_topContext); QString decl = (returnType ? (Cpp::simplifiedTypeString(returnType, m_context) + " ") : QString()) + name.toString() + "(" + makeSignatureString(signature, m_context) + ")"; if(isConstant) decl += " const"; if(body.isEmpty()) decl += ";"; else { if (!body.startsWith(' ') && !body.startsWith('\n')) { decl += " "; } decl += zeroIndentation(body); } InsertionPoint insertion = findInsertionPoint(m_access, Function); decl = "\n" + applyIndentation(applySubScope(insertion.prefix +decl)); return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(insertion.line), QString(), decl)); } bool KDevelop::SourceCodeInsertion::insertVariableDeclaration(KDevelop::Identifier name, KDevelop::AbstractType::Ptr type) { if(!m_context) return false; type = TypeUtils::removeConstants(type, m_topContext); QString decl = Cpp::simplifiedTypeString(type, m_context) + " " + name.toString() + ";"; InsertionPoint insertion = findInsertionPoint(m_access, Variable); decl = "\n" + applyIndentation(applySubScope(insertion.prefix + decl)); return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(insertion.line), QString(), decl)); } SimpleCursor SourceCodeInsertion::end() const { SimpleCursor ret = m_context->rangeInCurrentRevision().end; if(m_codeRepresentation && m_codeRepresentation->lines() && dynamic_cast(m_context)) { ret.line = m_codeRepresentation->lines()-1; ret.column = m_codeRepresentation->line(ret.line).size(); } return ret; } SourceCodeInsertion::InsertionPoint SourceCodeInsertion::findInsertionPoint(KDevelop::Declaration::AccessPolicy policy, InsertionKind kind) const { Q_UNUSED(policy); InsertionPoint ret; ret.line = end().line; bool behindExistingItem = false; //Try twice, in the second run, only match the "access" for(int anyMatch = 0; anyMatch <= 1 && !behindExistingItem; ++anyMatch) { foreach(Declaration* decl, m_context->localDeclarations()) { ClassMemberDeclaration* classMem = dynamic_cast(decl); if(m_context->type() != DUContext::Class || (classMem && classMem->accessPolicy() == m_access)) { Cpp::QtFunctionDeclaration* qtFunction = dynamic_cast(decl); if( (kind != Slot && anyMatch) || //Only allow anyMatch if not searching a slot, since else it may end up in a wrong section, not being a slot at all (kind == Slot && qtFunction && qtFunction->isSlot()) || (kind == Function && dynamic_cast(decl)) || (kind == Variable && decl->kind() == Declaration::Instance && !dynamic_cast(decl)) ) { behindExistingItem = true; ret.line = decl->range().end.line+1; if(decl->internalContext()) ret.line = decl->internalContext()->range().end.line+1; } } } } kDebug() << ret.line << m_context->scopeIdentifier(true) << m_context->rangeInCurrentRevision().textRange() << behindExistingItem << m_context->url().toUrl() << m_context->parentContext(); kDebug() << "is proxy:" << m_context->topContext()->parsingEnvironmentFile()->isProxyContext() << "count of declarations:" << m_context->topContext()->localDeclarations().size(); if(!behindExistingItem) { ClassDeclaration* classDecl = dynamic_cast(m_context->owner()); if(kind != Slot && m_access == Declaration::Public && classDecl && classDecl->classType() == ClassDeclarationData::Struct) { //Nothing to do, we can just insert into a struct if it should be public }else if(m_context->type() == DUContext::Class) { ret.prefix = accessString(); if(kind == Slot) ret.prefix += " slots"; ret.prefix += ":\n"; } } return ret; } bool Cpp::SourceCodeInsertion::insertSlot(QString name, QString normalizedSignature) { if(!m_context || !m_codeRepresentation) return false; InsertionPoint insertion = findInsertionPoint(m_access, Slot); QString add = insertion.prefix; QString sig; add += "void " + name + "(" + normalizedSignature + ");"; if(insertion.line > m_codeRepresentation->lines()) return false; add = "\n" + applyIndentation(add); return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(insertion.line), QString(), add)); } bool Cpp::SourceCodeInsertion::insertForwardDeclaration(KDevelop::Declaration* decl) { setSubScope(decl->context()->scopeIdentifier(true)); if(!m_context) { kDebug() << "no context"; return false; } QString forwardDeclaration; if(decl->kind() == Declaration::Instance) { //A simple variable declaration forwardDeclaration = "extern " + Cpp::simplifiedTypeString(decl->abstractType(), m_context) + " " + decl->identifier().toString() + ";"; }else if(decl->type()) { forwardDeclaration = "enum " + decl->identifier().toString() + ";"; }else if(decl->isTypeAlias()) { if(!decl->abstractType()) { kDebug() << "no type"; return false; } forwardDeclaration = "typedef " + Cpp::simplifiedTypeString(decl->abstractType(), m_context) + " " + decl->identifier().toString() + ";"; }else{ DUContext* templateContext = getTemplateContext(decl); if(templateContext) { forwardDeclaration += "template<"; bool first = true; foreach(Declaration* _paramDecl, templateContext->localDeclarations()) { TemplateParameterDeclaration* paramDecl = dynamic_cast(_paramDecl); if(!paramDecl) continue; if(!first) { forwardDeclaration += ", "; }else{ first = false; } CppTemplateParameterType::Ptr templParamType = paramDecl->type(); if(templParamType) { forwardDeclaration += "class "; }else if(paramDecl->abstractType()) { forwardDeclaration += Cpp::simplifiedTypeString(paramDecl->abstractType(), m_context) + " "; } forwardDeclaration += paramDecl->identifier().toString(); if(!paramDecl->defaultParameter().isEmpty()) { forwardDeclaration += " = " + paramDecl->defaultParameter().toString(); } } forwardDeclaration += " >\n"; } ClassDeclaration * classDecl = dynamic_cast(decl); if(classDecl) { forwardDeclaration += classDecl->toString() + ";"; }else{ forwardDeclaration += "class " + decl->identifier().toString() + ";"; } } //Put declarations to the end, and namespaces to the begin KTextEditor::Cursor position; bool needNewLine = true; if(!m_scope.isEmpty() || (m_insertBefore.isValid() && m_context->rangeInCurrentRevision().end > m_insertBefore)) { //To the begin position = m_context->rangeInCurrentRevision().start.textCursor(); if(m_context->type() == DUContext::Namespace) { position += KTextEditor::Cursor(0, 1); //Skip over the opening '{' paren //Put the newline to the beginning instead of the end forwardDeclaration = "\n" + forwardDeclaration; if(forwardDeclaration.endsWith("\n")) forwardDeclaration = forwardDeclaration.left(forwardDeclaration.length()-1); } } else{ //To the end position = end().textCursor(); if(position.column() != 0 && m_context->type() == DUContext::Namespace) { position -= KTextEditor::Cursor(0, 1); forwardDeclaration += "\n"; needNewLine = false; } } int firstValidLine = firstValidCodeLineBefore(m_insertBefore.line); if(firstValidLine > position.line() && m_context == m_topContext && (!m_insertBefore.isValid() || firstValidLine < m_insertBefore.line)) { position.setLine(firstValidLine); position.setColumn(0); } forwardDeclaration = applySubScope(forwardDeclaration); if(needNewLine) forwardDeclaration = "\n" + forwardDeclaration; kDebug() << "inserting at" << position << forwardDeclaration; return m_changeSet.addChange(DocumentChange(m_context->url(), SimpleRange(position.line(), position.column(), position.line(), position.column()), QString(), forwardDeclaration)); } Cpp::SourceCodeInsertion::SourceCodeInsertion(TopDUContext* topContext) : KDevelop::SourceCodeInsertion(topContext){ m_changeSet.setFormatPolicy(KDevelop::DocumentChangeSet::AutoFormatChanges); }