Commit 6c7b2d1c authored by Oswald Buddenhagen's avatar Oswald Buddenhagen

actually edit pro files instead of rewriting them from the "AST"

that way the file formatting is better preserved.
parent 93b6820e
......@@ -76,3 +76,4 @@ tests/manual/cplusplus/cplusplus0
tests/auto/qml/qmldesigner/bauhaustests/tst_bauhaus
tests/auto/qml/qmldesigner/coretests/tst_qmldesigner_core
tests/auto/qml/qmldesigner/propertyeditortests/tst_propertyeditor
tests/auto/profilewriter/tst_profilewriter
......@@ -606,23 +606,6 @@ bool Qt4PriFileNode::saveModifiedEditors(const QString &path)
return true;
}
static void findProVariables(ProBlock *block, const QStringList &vars,
QList<ProVariable *> *proVars)
{
foreach (ProItem *item, block->items()) {
if (item->kind() == ProItem::BlockKind) {
ProBlock *subBlock = static_cast<ProBlock *>(item);
if (subBlock->blockKind() == ProBlock::VariableKind) {
ProVariable *proVar = static_cast<ProVariable*>(subBlock);
if (vars.contains(proVar->variable()))
*proVars << proVar;
} else {
findProVariables(subBlock, vars, proVars);
}
}
}
}
void Qt4PriFileNode::changeFiles(const FileType fileType,
const QStringList &filePaths,
QStringList *notChanged,
......@@ -637,101 +620,67 @@ void Qt4PriFileNode::changeFiles(const FileType fileType,
if (!saveModifiedEditors(m_projectFilePath))
return;
ProFileReader *reader = m_project->createProFileReader(m_qt4ProFileNode);
ProFile *includeFile = reader->parsedProFile(m_projectFilePath);
m_project->destroyProFileReader(reader);
if (!includeFile) {
m_project->proFileParseError(tr("Error while changing pro file %1.").arg(m_projectFilePath));
return;
QStringList lines;
ProFile *includeFile;
{
QString contents;
{
QFile qfile(m_projectFilePath);
if (qfile.open(QIODevice::ReadOnly | QIODevice::Text)) {
contents = QString::fromLatin1(qfile.readAll()); // yes, really latin1
qfile.close();
lines = contents.split(QLatin1Char('\n'));
while (lines.last().isEmpty())
lines.removeLast();
} else {
m_project->proFileParseError(tr("Error while reading PRO file %1: %2")
.arg(m_projectFilePath, qfile.errorString()));
return;
}
}
ProFileReader *reader = m_project->createProFileReader(m_qt4ProFileNode);
includeFile = reader->parsedProFile(m_projectFilePath, contents);
m_project->destroyProFileReader(reader);
}
const QStringList vars = varNames(fileType);
QDir priFileDir = QDir(m_qt4ProFileNode->m_projectDir);
if (change == AddToProFile) {
ProVariable *proVar = 0;
// Check if variable item exists as child of root item
foreach (ProItem *item, includeFile->items()) {
if (item->kind() == ProItem::BlockKind) {
ProBlock *block = static_cast<ProBlock *>(item);
if (block->blockKind() == ProBlock::VariableKind) {
proVar = static_cast<ProVariable*>(block);
if (vars.contains(proVar->variable())
&& proVar->variableOperator() != ProVariable::RemoveOperator
&& proVar->variableOperator() != ProVariable::ReplaceOperator)
break;
proVar = 0;
}
}
}
if (!proVar) {
// Create & append new variable item
// TODO: This will always store e.g. a source file in SOURCES and not OBJECTIVE_SOURCES
proVar = new ProVariable(vars.first(), includeFile);
proVar->setVariableOperator(ProVariable::AddOperator);
includeFile->appendItem(proVar);
}
const QString &proFilePath = includeFile->fileName();
foreach (const QString &filePath, filePaths) {
if (filePath == proFilePath)
continue;
const QString &relativeFilePath = priFileDir.relativeFilePath(filePath);
proVar->appendItem(new ProValue(relativeFilePath, proVar));
notChanged->removeOne(filePath);
}
ProWriter::addFiles(includeFile, &lines, priFileDir, filePaths, vars);
notChanged->clear();
} else { // RemoveFromProFile
QList<ProVariable *> proVars;
findProVariables(includeFile, vars, &proVars);
QStringList relativeFilePaths;
foreach (const QString &absoluteFilePath, filePaths)
relativeFilePaths << priFileDir.relativeFilePath(absoluteFilePath);
foreach (ProVariable *proVar, proVars) {
if (proVar->variableOperator() != ProVariable::RemoveOperator
&& proVar->variableOperator() != ProVariable::ReplaceOperator) {
QList<ProItem *> values = proVar->items();
for (int i = values.count(); --i >= 0; ) {
ProItem *item = values.at(i);
if (item->kind() == ProItem::ValueKind) {
ProValue *val = static_cast<ProValue *>(item);
if (relativeFilePaths.contains(val->value())) {
notChanged->removeOne(priFileDir.absoluteFilePath(val->value()));
delete values.takeAt(i);
}
}
}
proVar->setItems(values);
}
}
*notChanged = ProWriter::removeFiles(includeFile, &lines, priFileDir, filePaths, vars);
}
// save file
save(includeFile);
save(lines);
includeFile->deref();
}
void Qt4PriFileNode::save(ProFile *includeFile)
void Qt4PriFileNode::save(const QStringList &lines)
{
Core::ICore *core = Core::ICore::instance();
Core::FileManager *fileManager = core->fileManager();
QList<Core::IFile *> allFileHandles = fileManager->managedFiles(includeFile->fileName());
QList<Core::IFile *> allFileHandles = fileManager->managedFiles(m_projectFilePath);
Core::IFile *modifiedFileHandle = 0;
foreach (Core::IFile *file, allFileHandles)
if (file->fileName() == includeFile->fileName())
if (file->fileName() == m_projectFilePath)
modifiedFileHandle = file;
if (modifiedFileHandle)
fileManager->blockFileChange(modifiedFileHandle);
ProWriter pw;
const bool ok = pw.write(includeFile, includeFile->fileName());
Q_UNUSED(ok)
m_project->qt4ProjectManager()->notifyChanged(includeFile->fileName());
QFile qfile(m_projectFilePath);
if (qfile.open(QIODevice::WriteOnly | QIODevice::Text)) {
foreach (const QString &str, lines) {
qfile.write(str.toLatin1()); // yes, really latin1
qfile.write("\n");
}
qfile.close();
}
m_project->qt4ProjectManager()->notifyChanged(m_projectFilePath);
if (modifiedFileHandle)
fileManager->unblockFileChange(modifiedFileHandle);
......
......@@ -174,7 +174,7 @@ private slots:
void scheduleUpdate();
private:
void save(ProFile *includeFile);
void save(const QStringList &lines);
bool priFileWritable(const QString &path);
bool saveModifiedEditors(const QString &path);
......
......@@ -30,175 +30,200 @@
#include "proitems.h"
#include "prowriter.h"
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QPair>
using namespace Qt4ProjectManager::Internal;
bool ProWriter::write(ProFile *profile, const QString &fileName)
void ProWriter::addFiles(ProFile *profile, QStringList *lines,
const QDir &proFileDir, const QStringList &filePaths,
const QStringList &vars)
{
QFile data(fileName);
if (!data.open(QFile::WriteOnly|QFile::Text))
return false;
m_writeState = 0;
m_comment.clear();
m_out.setDevice(&data);
writeItem(profile, QString());
data.close();
return true;
}
QString ProWriter::contents(ProFile *profile)
{
QString result;
m_writeState = 0;
m_comment.clear();
m_out.setString(&result, QIODevice::WriteOnly);
writeItem(profile, QString());
return result;
}
QString ProWriter::fixComment(const QString &comment, const QString &indent) const
{
QString result = comment;
result = result.replace(QLatin1Char('\n'),
QLatin1Char('\n') + indent + QLatin1String("# "));
return QLatin1String("# ") + result;
}
void ProWriter::writeValue(ProValue *value, const QString &indent)
{
if (m_writeState & NewLine) {
m_out << indent << QLatin1String(" ");
m_writeState &= ~NewLine;
}
m_out << value->value();
if (!(m_writeState & LastItem))
m_out << QLatin1String(" \\");
if (!value->comment().isEmpty())
m_out << QLatin1Char(' ') << fixComment(value->comment(), indent);
m_out << endl;
m_writeState |= NewLine;
}
void ProWriter::writeOther(ProItem *item, const QString &indent)
{
if (m_writeState & NewLine) {
m_out << indent;
m_writeState &= ~NewLine;
}
if (item->kind() == ProItem::FunctionKind) {
ProFunction *v = static_cast<ProFunction*>(item);
m_out << v->text();
} else if (item->kind() == ProItem::ConditionKind) {
ProCondition *v = static_cast<ProCondition*>(item);
m_out << v->text();
} else if (item->kind() == ProItem::OperatorKind) {
ProOperator *v = static_cast<ProOperator*>(item);
if (v->operatorKind() == ProOperator::OrOperator)
m_out << QLatin1Char('|');
else
m_out << QLatin1Char('!');
// Check if variable item exists as child of root item
foreach (ProItem *item, profile->items()) {
if (item->kind() == ProItem::BlockKind) {
ProBlock *block = static_cast<ProBlock *>(item);
if (block->blockKind() == ProBlock::VariableKind) {
ProVariable *proVar = static_cast<ProVariable*>(block);
if (vars.contains(proVar->variable())
&& proVar->variableOperator() != ProVariable::RemoveOperator
&& proVar->variableOperator() != ProVariable::ReplaceOperator) {
int lineNo = proVar->lineNumber() - 1;
for (; lineNo < lines->count(); lineNo++) {
QString line = lines->at(lineNo);
int idx = line.indexOf(QLatin1Char('#'));
if (idx >= 0)
line.truncate(idx);
while (line.endsWith(QLatin1Char(' ')) || line.endsWith(QLatin1Char('\t')))
line.chop(1);
if (line.isEmpty()) {
if (idx >= 0)
continue;
break;
}
if (!line.endsWith(QLatin1Char('\\'))) {
(*lines)[lineNo].insert(line.length(), QLatin1String(" \\"));
lineNo++;
break;
}
}
QString added;
foreach (const QString &filePath, filePaths)
added += QLatin1String(" ") + proFileDir.relativeFilePath(filePath)
+ QLatin1String(" \\\n");
added.chop(3);
lines->insert(lineNo, added);
return;
}
}
}
}
if (!item->comment().isEmpty()) {
if (!m_comment.isEmpty())
m_comment += QLatin1Char('\n');
m_comment += item->comment();
}
// Create & append new variable item
QString added = QLatin1Char('\n') + vars.first() + QLatin1String(" +=");
foreach (const QString &filePath, filePaths)
added += QLatin1String(" \\\n ") + proFileDir.relativeFilePath(filePath);
*lines << added;
}
void ProWriter::writeBlock(ProBlock *block, const QString &indent)
static void findProVariables(ProBlock *block, const QStringList &vars,
QList<ProVariable *> *proVars)
{
if (m_writeState & NewLine) {
m_out << indent;
m_writeState &= ~NewLine;
}
if (!block->comment().isEmpty()) {
if (!(m_writeState & FirstItem))
m_out << endl << indent;
m_out << fixComment(block->comment(), indent) << endl << indent;
}
QString newindent = indent;
if (block->blockKind() & ProBlock::VariableKind) {
ProVariable *v = static_cast<ProVariable*>(block);
m_out << v->variable();
switch (v->variableOperator()) {
case ProVariable::AddOperator:
m_out << QLatin1String(" += "); break;
case ProVariable::RemoveOperator:
m_out << QLatin1String(" -= "); break;
case ProVariable::ReplaceOperator:
m_out << QLatin1String(" ~= "); break;
case ProVariable::SetOperator:
m_out << QLatin1String(" = "); break;
case ProVariable::UniqueAddOperator:
m_out << QLatin1String(" *= "); break;
}
} else if (block->blockKind() & ProBlock::ScopeContentsKind) {
if (block->items().count() > 1) {
newindent = indent + QLatin1String(" ");
m_out << QLatin1String(" { ");
if (!m_comment.isEmpty()) {
m_out << fixComment(m_comment, indent);
m_comment.clear();
foreach (ProItem *item, block->items()) {
if (item->kind() == ProItem::BlockKind) {
ProBlock *subBlock = static_cast<ProBlock *>(item);
if (subBlock->blockKind() == ProBlock::VariableKind) {
ProVariable *proVar = static_cast<ProVariable*>(subBlock);
if (vars.contains(proVar->variable()))
*proVars << proVar;
} else {
findProVariables(subBlock, vars, proVars);
}
m_out << endl;
m_writeState |= NewLine;
} else {
m_out << QLatin1Char(':');
}
}
QList<ProItem*> items = block->items();
for (int i = 0; i < items.count(); ++i) {
m_writeState &= ~LastItem;
m_writeState &= ~FirstItem;
if (i == 0)
m_writeState |= FirstItem;
if (i == items.count() - 1)
m_writeState |= LastItem;
writeItem(items.at(i), newindent);
}
if ((block->blockKind() & ProBlock::ScopeContentsKind) && (block->items().count() > 1)) {
if (m_writeState & NewLine) {
m_out << indent;
m_writeState &= ~NewLine;
}
m_out << QLatin1Char('}');
}
if (!m_comment.isEmpty()) {
m_out << fixComment(m_comment, indent);
m_out << endl;
m_writeState |= NewLine;
m_comment.clear();
}
if (!(m_writeState & NewLine)) {
m_out << endl;
m_writeState |= NewLine;
}
}
void ProWriter::writeItem(ProItem *item, const QString &indent)
QStringList ProWriter::removeFiles(ProFile *profile, QStringList *lines,
const QDir &proFileDir, const QStringList &filePaths,
const QStringList &vars)
{
if (item->kind() == ProItem::ValueKind) {
writeValue(static_cast<ProValue*>(item), indent);
} else if (item->kind() == ProItem::BlockKind) {
writeBlock(static_cast<ProBlock*>(item), indent);
} else {
writeOther(item, indent);
QStringList notChanged = filePaths;
QList<ProVariable *> proVars;
findProVariables(profile, vars, &proVars);
// This is a tad stupid - basically, it can remove only entries which
// the above code added.
QStringList relativeFilePaths;
foreach (const QString &absoluteFilePath, filePaths)
relativeFilePaths << proFileDir.relativeFilePath(absoluteFilePath);
// This code expects proVars to be sorted by the variables' appearance in the file.
int delta = 1;
foreach (ProVariable *proVar, proVars) {
if (proVar->variableOperator() != ProVariable::RemoveOperator
&& proVar->variableOperator() != ProVariable::ReplaceOperator) {
bool first = true;
int lineNo = proVar->lineNumber() - delta;
typedef QPair<int, int> ContPos;
QList<ContPos> contPos;
while (lineNo < lines->count()) {
QString &line = (*lines)[lineNo];
int lineLen = line.length();
bool killed = false;
bool saved = false;
int idx = line.indexOf(QLatin1Char('#'));
if (idx >= 0)
lineLen = idx;
QChar *chars = line.data();
forever {
if (!lineLen) {
if (idx >= 0)
goto nextLine;
goto nextVar;
}
QChar c = chars[lineLen - 1];
if (c != QLatin1Char(' ') && c != QLatin1Char('\t'))
break;
lineLen--;
}
{
int contCol = -1;
if (chars[lineLen - 1] == QLatin1Char('\\'))
contCol = --lineLen;
int colNo = 0;
if (first) {
colNo = line.indexOf(QLatin1Char('=')) + 1;
first = false;
saved = true;
}
while (colNo < lineLen) {
QChar c = chars[colNo];
if (c == QLatin1Char(' ') || c == QLatin1Char('\t')) {
colNo++;
continue;
}
int varCol = colNo;
while (colNo < lineLen) {
QChar c = chars[colNo];
if (c == QLatin1Char(' ') || c == QLatin1Char('\t'))
break;
colNo++;
}
QString fn = line.mid(varCol, colNo - varCol);
if (relativeFilePaths.contains(fn)) {
notChanged.removeOne(QDir::cleanPath(proFileDir.absoluteFilePath(fn)));
if (colNo < lineLen)
colNo++;
else if (varCol)
varCol--;
int len = colNo - varCol;
colNo = varCol;
line.remove(varCol, len);
lineLen -= len;
contCol -= len;
idx -= len;
if (idx >= 0)
line.insert(idx, QLatin1String("# ") + fn + QLatin1Char(' '));
killed = true;
} else {
saved = true;
}
}
if (saved) {
// Entries remained
contPos.clear();
} else if (killed) {
// Entries existed, but were all removed
if (contCol < 0) {
// This is the last line, so clear continuations leading to it
foreach (const ContPos &pos, contPos) {
QString &bline = (*lines)[pos.first];
bline.remove(pos.second, 1);
if (pos.second == bline.length())
while (bline.endsWith(QLatin1Char(' '))
|| bline.endsWith(QLatin1Char('\t')))
bline.chop(1);
}
contPos.clear();
}
if (idx < 0) {
// Not even a comment stayed behind, so zap the line
lines->removeAt(lineNo);
delta++;
continue;
}
}
if (contCol >= 0)
contPos.append(qMakePair(lineNo, contCol));
}
nextLine:
lineNo++;
}
nextVar: ;
}
}
return notChanged;
}
......@@ -32,13 +32,11 @@
#include "namespace_global.h"
#include <QtCore/QTextStream>
#include <QStringList>
QT_BEGIN_NAMESPACE
class QDir;
class ProFile;
class ProValue;
class ProItem;
class ProBlock;
QT_END_NAMESPACE
namespace Qt4ProjectManager {
......@@ -47,26 +45,12 @@ namespace Internal {
class ProWriter
{
public:
bool write(ProFile *profile, const QString &fileName);
QString contents(ProFile *profile);
protected:
QString fixComment(const QString &comment, const QString &indent) const;
void writeValue(ProValue *value, const QString &indent);
void writeOther(ProItem *item, const QString &indent);
void writeBlock(ProBlock *block, const QString &indent);
void writeItem(ProItem *item, const QString &indent);
private:
enum ProWriteState {
NewLine = 0x01,
FirstItem = 0x02,
LastItem = 0x04
};
QTextStream m_out;
int m_writeState;
QString m_comment;
static void addFiles(ProFile *profile, QStringList *lines,
const QDir &proFileDir, const QStringList &filePaths,
const QStringList &vars);
static QStringList removeFiles(ProFile *profile, QStringList *lines,
const QDir &proFileDir, const QStringList &filePaths,
const QStringList &vars);
};
} // namespace Internal
......
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** 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.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "profileevaluator.h"