mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-26 11:52:54 +00:00
507 lines
17 KiB
C++
507 lines
17 KiB
C++
/*
|
|
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 <language/codegen/coderepresentation.h>
|
|
#include "qtfunctiondeclaration.h"
|
|
#include "declarationbuilder.h"
|
|
#include "templateparameterdeclaration.h"
|
|
#include <language/duchain/parsingenvironment.h>
|
|
#include <language/duchain/stringhelpers.h>
|
|
|
|
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<SourceCodeInsertion::SignatureItem> 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<SignatureItem> 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<TopDUContext*>(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<ClassMemberDeclaration*>(decl);
|
|
if(m_context->type() != DUContext::Class || (classMem && classMem->accessPolicy() == m_access)) {
|
|
|
|
Cpp::QtFunctionDeclaration* qtFunction = dynamic_cast<Cpp::QtFunctionDeclaration*>(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<AbstractFunctionDeclaration*>(decl)) ||
|
|
(kind == Variable && decl->kind() == Declaration::Instance && !dynamic_cast<AbstractFunctionDeclaration*>(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<ClassDeclaration*>(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<KDevelop::EnumerationType>()) {
|
|
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<TemplateParameterDeclaration*>(_paramDecl);
|
|
if(!paramDecl)
|
|
continue;
|
|
if(!first) {
|
|
forwardDeclaration += ", ";
|
|
}else{
|
|
first = false;
|
|
}
|
|
|
|
CppTemplateParameterType::Ptr templParamType = paramDecl->type<CppTemplateParameterType>();
|
|
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<ClassDeclaration *>(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);
|
|
}
|