mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-26 20:03:10 +00:00
575 lines
23 KiB
C++
575 lines
23 KiB
C++
![]() |
/*
|
||
|
Copyright 2008 David Nolden <david.nolden.kdevelop@art-master.de>
|
||
|
|
||
|
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 "documentchangeset.h"
|
||
|
|
||
|
#include "coderepresentation.h"
|
||
|
|
||
|
#include <algorithm>
|
||
|
|
||
|
#include <QStringList>
|
||
|
|
||
|
#include <KLocalizedString>
|
||
|
|
||
|
#include <interfaces/icore.h>
|
||
|
#include <interfaces/ilanguagecontroller.h>
|
||
|
#include <interfaces/idocumentcontroller.h>
|
||
|
|
||
|
#include <language/duchain/duchain.h>
|
||
|
#include <language/duchain/duchainlock.h>
|
||
|
#include <language/duchain/duchainutils.h>
|
||
|
#include <language/duchain/parsingenvironment.h>
|
||
|
#include <language/backgroundparser/backgroundparser.h>
|
||
|
#include <language/editor/modificationrevisionset.h>
|
||
|
|
||
|
#include <interfaces/isourceformattercontroller.h>
|
||
|
#include <interfaces/isourceformatter.h>
|
||
|
#include <interfaces/iproject.h>
|
||
|
#include <interfaces/iprojectcontroller.h>
|
||
|
|
||
|
#include <project/projectmodel.h>
|
||
|
|
||
|
namespace KDevelop {
|
||
|
|
||
|
typedef QList<DocumentChangePointer> ChangesList;
|
||
|
typedef QHash<IndexedString, ChangesList> ChangesHash;
|
||
|
|
||
|
struct DocumentChangeSetPrivate
|
||
|
{
|
||
|
DocumentChangeSet::ReplacementPolicy replacePolicy;
|
||
|
DocumentChangeSet::FormatPolicy formatPolicy;
|
||
|
DocumentChangeSet::DUChainUpdateHandling updatePolicy;
|
||
|
DocumentChangeSet::ActivationPolicy activationPolicy;
|
||
|
|
||
|
ChangesHash changes;
|
||
|
QHash<IndexedString, IndexedString> documentsRename;
|
||
|
|
||
|
DocumentChangeSet::ChangeResult addChange(const DocumentChangePointer& change);
|
||
|
DocumentChangeSet::ChangeResult replaceOldText(CodeRepresentation* repr, const QString& newText,
|
||
|
const ChangesList& sortedChangesList);
|
||
|
DocumentChangeSet::ChangeResult generateNewText(const IndexedString& file,
|
||
|
ChangesList& sortedChanges,
|
||
|
const CodeRepresentation* repr,
|
||
|
QString& output);
|
||
|
DocumentChangeSet::ChangeResult removeDuplicates(const IndexedString& file,
|
||
|
ChangesList& filteredChanges);
|
||
|
void formatChanges();
|
||
|
void updateFiles();
|
||
|
};
|
||
|
|
||
|
// Simple helpers to clear up code clutter
|
||
|
namespace
|
||
|
{
|
||
|
inline bool changeIsValid(const DocumentChange& change, const QStringList& textLines)
|
||
|
{
|
||
|
return change.m_range.start <= change.m_range.end &&
|
||
|
change.m_range.end.line < textLines.size() &&
|
||
|
change.m_range.start.line >= 0 &&
|
||
|
change.m_range.start.column >= 0 &&
|
||
|
change.m_range.start.column <= textLines[change.m_range.start.line].length() &&
|
||
|
change.m_range.end.column >= 0 &&
|
||
|
change.m_range.end.column <= textLines[change.m_range.end.line].length();
|
||
|
}
|
||
|
|
||
|
inline bool duplicateChanges(const DocumentChangePointer& previous, const DocumentChangePointer& current)
|
||
|
{
|
||
|
// Given the option of considering a duplicate two changes in the same range
|
||
|
// but with different old texts to be ignored
|
||
|
return previous->m_range == current->m_range &&
|
||
|
previous->m_newText == current->m_newText &&
|
||
|
(previous->m_oldText == current->m_oldText ||
|
||
|
(previous->m_ignoreOldText && current->m_ignoreOldText));
|
||
|
}
|
||
|
|
||
|
inline QString rangeText(const SimpleRange& range, const QStringList& textLines)
|
||
|
{
|
||
|
QStringList ret;
|
||
|
ret.reserve(range.end.line - range.start.line + 1);
|
||
|
for(int line = range.start.line; line <= range.end.line; ++line) {
|
||
|
const QString lineText = textLines.at(line);
|
||
|
int startColumn = 0;
|
||
|
int endColumn = lineText.length();
|
||
|
if (line == range.start.line) {
|
||
|
startColumn = range.start.column;
|
||
|
}
|
||
|
if (line == range.end.line) {
|
||
|
endColumn = range.end.column;
|
||
|
}
|
||
|
ret << lineText.mid(startColumn, endColumn - startColumn);
|
||
|
}
|
||
|
return ret.join("\n");
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::DocumentChangeSet()
|
||
|
: d(new DocumentChangeSetPrivate)
|
||
|
{
|
||
|
d->replacePolicy = StopOnFailedChange;
|
||
|
d->formatPolicy = AutoFormatChanges;
|
||
|
d->updatePolicy = SimpleUpdate;
|
||
|
d->activationPolicy = DoNotActivate;
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::DocumentChangeSet(const DocumentChangeSet& rhs)
|
||
|
: d(new DocumentChangeSetPrivate(*rhs.d))
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
DocumentChangeSet& DocumentChangeSet::operator=(const DocumentChangeSet& rhs)
|
||
|
{
|
||
|
*d = *rhs.d;
|
||
|
return *this;
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::~DocumentChangeSet()
|
||
|
{
|
||
|
delete d;
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChange& change)
|
||
|
{
|
||
|
return d->addChange(DocumentChangePointer(new DocumentChange(change)));
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const DocumentChangePointer& change)
|
||
|
{
|
||
|
return d->addChange(change);
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSet::addDocumentRenameChange(const IndexedString& oldFile,
|
||
|
const IndexedString& newname)
|
||
|
{
|
||
|
d->documentsRename.insert(oldFile, newname);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(const DocumentChangePointer& change)
|
||
|
{
|
||
|
changes[change->m_document].append(change);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void DocumentChangeSet::setReplacementPolicy(DocumentChangeSet::ReplacementPolicy policy)
|
||
|
{
|
||
|
d->replacePolicy = policy;
|
||
|
}
|
||
|
|
||
|
void DocumentChangeSet::setFormatPolicy(DocumentChangeSet::FormatPolicy policy)
|
||
|
{
|
||
|
d->formatPolicy = policy;
|
||
|
}
|
||
|
|
||
|
void DocumentChangeSet::setUpdateHandling(DocumentChangeSet::DUChainUpdateHandling policy)
|
||
|
{
|
||
|
d->updatePolicy = policy;
|
||
|
}
|
||
|
|
||
|
void DocumentChangeSet::setActivationPolicy(DocumentChangeSet::ActivationPolicy policy)
|
||
|
{
|
||
|
d->activationPolicy = policy;
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllChanges()
|
||
|
{
|
||
|
KUrl oldActiveDoc;
|
||
|
if (IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) {
|
||
|
oldActiveDoc = activeDoc->url();
|
||
|
}
|
||
|
|
||
|
// rename files
|
||
|
QHash<IndexedString, IndexedString>::const_iterator it = d->documentsRename.constBegin();
|
||
|
for(; it != d->documentsRename.constEnd(); ++it) {
|
||
|
KUrl url = it.key().toUrl();
|
||
|
IProject* p = ICore::self()->projectController()->findProjectForUrl(url);
|
||
|
if(p) {
|
||
|
QList<ProjectFileItem*> files = p->filesForPath(it.key());
|
||
|
if(!files.isEmpty()) {
|
||
|
ProjectBaseItem::RenameStatus renamed = files.first()->rename(it.value().str());
|
||
|
if(renamed == ProjectBaseItem::RenameOk) {
|
||
|
const KUrl newUrl(url.upUrl(), it.value().str());
|
||
|
if (url == oldActiveDoc) {
|
||
|
oldActiveDoc = newUrl;
|
||
|
}
|
||
|
IndexedString idxNewDoc(newUrl);
|
||
|
|
||
|
// ensure changes operate on new file name
|
||
|
ChangesHash::iterator iter = d->changes.find(it.key());
|
||
|
if (iter != d->changes.end()) {
|
||
|
// copy changes
|
||
|
ChangesList value = iter.value();
|
||
|
// remove old entry
|
||
|
d->changes.erase(iter);
|
||
|
// adapt to new url
|
||
|
ChangesList::iterator itChange = value.begin();
|
||
|
ChangesList::iterator itEnd = value.end();
|
||
|
for(; itChange != itEnd; ++itChange) {
|
||
|
(*itChange)->m_document = idxNewDoc;
|
||
|
}
|
||
|
d->changes[idxNewDoc] = value;
|
||
|
}
|
||
|
} else {
|
||
|
///FIXME: share code with project manager for the error code string representation
|
||
|
return ChangeResult(i18n("Could not rename '%1' to '%2'", url.pathOrUrl(), it.value().str()));
|
||
|
}
|
||
|
} else {
|
||
|
//TODO: do it outside the project management?
|
||
|
kWarning() << "tried to rename file not tracked by project - not implemented";
|
||
|
}
|
||
|
} else {
|
||
|
kWarning() << "tried to rename a file outside of a project - not implemented";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QMap<IndexedString, CodeRepresentation::Ptr> codeRepresentations;
|
||
|
QMap<IndexedString, QString> newTexts;
|
||
|
ChangesHash filteredSortedChanges;
|
||
|
ChangeResult result(true);
|
||
|
|
||
|
QList<IndexedString> files(d->changes.keys());
|
||
|
|
||
|
foreach(const IndexedString &file, files) {
|
||
|
CodeRepresentation::Ptr repr = createCodeRepresentation(file);
|
||
|
if(!repr) {
|
||
|
return ChangeResult(QString("Could not create a Representation for %1").arg(file.str()));
|
||
|
}
|
||
|
|
||
|
codeRepresentations[file] = repr;
|
||
|
|
||
|
QList<DocumentChangePointer>& sortedChangesList(filteredSortedChanges[file]);
|
||
|
{
|
||
|
result = d->removeDuplicates(file, sortedChangesList);
|
||
|
if(!result)
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
{
|
||
|
result = d->generateNewText(file, sortedChangesList, repr.data(), newTexts[file]);
|
||
|
if(!result)
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QMap<IndexedString, QString> oldTexts;
|
||
|
|
||
|
//Apply the changes to the files
|
||
|
foreach(const IndexedString &file, files) {
|
||
|
oldTexts[file] = codeRepresentations[file]->text();
|
||
|
|
||
|
result = d->replaceOldText(codeRepresentations[file].data(), newTexts[file], filteredSortedChanges[file]);
|
||
|
if(!result && d->replacePolicy == StopOnFailedChange) {
|
||
|
//Revert all files
|
||
|
foreach(const IndexedString &revertFile, oldTexts.keys()) {
|
||
|
codeRepresentations[revertFile]->setText(oldTexts[revertFile]);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
d->updateFiles();
|
||
|
|
||
|
if(d->activationPolicy == Activate) {
|
||
|
foreach(const IndexedString& file, files) {
|
||
|
ICore::self()->documentController()->openDocument(file.toUrl());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// ensure the old document is still activated
|
||
|
if (oldActiveDoc.isValid()) {
|
||
|
ICore::self()->documentController()->openDocument(oldActiveDoc);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOldText(CodeRepresentation* repr,
|
||
|
const QString& newText,
|
||
|
const ChangesList& sortedChangesList)
|
||
|
{
|
||
|
DynamicCodeRepresentation* dynamic = dynamic_cast<DynamicCodeRepresentation*>(repr);
|
||
|
if(dynamic) {
|
||
|
dynamic->startEdit();
|
||
|
//Replay the changes one by one
|
||
|
|
||
|
for(int pos = sortedChangesList.size()-1; pos >= 0; --pos) {
|
||
|
const DocumentChange& change(*sortedChangesList[pos]);
|
||
|
if(!dynamic->replace(change.m_range.textRange(), change.m_oldText, change.m_newText, change.m_ignoreOldText))
|
||
|
{
|
||
|
QString warningString = QString("Inconsistent change in %1 at %2:%3 -> %4:%5 = %6(encountered \"%7\") -> \"%8\"")
|
||
|
.arg(change.m_document.str())
|
||
|
.arg(change.m_range.start.line)
|
||
|
.arg(change.m_range.start.column)
|
||
|
.arg(change.m_range.end.line)
|
||
|
.arg(change.m_range.end.column)
|
||
|
.arg(change.m_oldText)
|
||
|
.arg(dynamic->rangeText(change.m_range.textRange()))
|
||
|
.arg(change.m_newText);
|
||
|
|
||
|
if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) {
|
||
|
kWarning() << warningString;
|
||
|
} else if(replacePolicy == DocumentChangeSet::StopOnFailedChange) {
|
||
|
dynamic->endEdit();
|
||
|
return DocumentChangeSet::ChangeResult(warningString);
|
||
|
}
|
||
|
//If set to ignore failed changes just continue with the others
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dynamic->endEdit();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//For files on disk
|
||
|
if (!repr->setText(newText)) {
|
||
|
QString warningString = QString("Could not replace text in the document: %1")
|
||
|
.arg(sortedChangesList.begin()->data()->m_document.str());
|
||
|
if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) {
|
||
|
kWarning() << warningString;
|
||
|
}
|
||
|
|
||
|
return DocumentChangeSet::ChangeResult(warningString);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::generateNewText(const IndexedString & file,
|
||
|
ChangesList& sortedChanges,
|
||
|
const CodeRepresentation * repr,
|
||
|
QString & output)
|
||
|
{
|
||
|
|
||
|
ISourceFormatter* formatter = 0;
|
||
|
if(ICore::self()) {
|
||
|
formatter = ICore::self()->sourceFormatterController()->formatterForUrl(file.toUrl());
|
||
|
}
|
||
|
|
||
|
//Create the actual new modified file
|
||
|
QStringList textLines = repr->text().split('\n');
|
||
|
|
||
|
KUrl url = file.toUrl();
|
||
|
|
||
|
KMimeType::Ptr mime = KMimeType::findByUrl(url);
|
||
|
QVector<int> removedLines;
|
||
|
|
||
|
for(int pos = sortedChanges.size()-1; pos >= 0; --pos) {
|
||
|
DocumentChange& change(*sortedChanges[pos]);
|
||
|
QString encountered;
|
||
|
if(changeIsValid(change, textLines) && //We demand this, although it should be fixed
|
||
|
((encountered = rangeText(change.m_range, textLines)) == change.m_oldText || change.m_ignoreOldText))
|
||
|
{
|
||
|
///Problem: This does not work if the other changes significantly alter the context @todo Use the changed context
|
||
|
QString leftContext = QStringList(textLines.mid(0, change.m_range.start.line+1)).join("\n");
|
||
|
leftContext.chop(textLines[change.m_range.start.line].length() - change.m_range.start.column);
|
||
|
|
||
|
QString rightContext = QStringList(textLines.mid(change.m_range.end.line)).join("\n").mid(change.m_range.end.column);
|
||
|
|
||
|
if(formatter && (formatPolicy == DocumentChangeSet::AutoFormatChanges
|
||
|
|| formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation))
|
||
|
{
|
||
|
QString oldNewText = change.m_newText;
|
||
|
change.m_newText = formatter->formatSource(change.m_newText, url, mime, leftContext, rightContext);
|
||
|
|
||
|
if(formatPolicy == DocumentChangeSet::AutoFormatChangesKeepIndentation) {
|
||
|
// Reproduce the previous indentation
|
||
|
QStringList oldLines = oldNewText.split('\n');
|
||
|
QStringList newLines = change.m_newText.split('\n');
|
||
|
|
||
|
if(oldLines.size() == newLines.size()) {
|
||
|
for(int line = 0; line < newLines.size(); ++line) {
|
||
|
// Keep the previous indentation
|
||
|
QString oldIndentation;
|
||
|
for (int a = 0; a < oldLines[line].size(); ++a) {
|
||
|
if (oldLines[line][a].isSpace()) {
|
||
|
oldIndentation.append(oldLines[line][a]);
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int newIndentationLength = 0;
|
||
|
|
||
|
for(int a = 0; a < newLines[line].size(); ++a) {
|
||
|
if(newLines[line][a].isSpace()) {
|
||
|
newIndentationLength = a;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
newLines[line].replace(0, newIndentationLength, oldIndentation);
|
||
|
}
|
||
|
change.m_newText = newLines.join("\n");
|
||
|
} else {
|
||
|
kDebug() << "Cannot keep the indentation because the line count has changed" << oldNewText;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
QString& line = textLines[change.m_range.start.line];
|
||
|
if (change.m_range.start.line == change.m_range.end.line) {
|
||
|
// simply replace existing line content
|
||
|
line.replace(change.m_range.start.column,
|
||
|
change.m_range.end.column-change.m_range.start.column,
|
||
|
change.m_newText);
|
||
|
} else {
|
||
|
// replace first line contents
|
||
|
line.replace(change.m_range.start.column, line.length() - change.m_range.start.column,
|
||
|
change.m_newText);
|
||
|
// null other lines and remember for deletion
|
||
|
for(int i = change.m_range.start.line + 1; i <= change.m_range.end.line; ++i) {
|
||
|
textLines[i].clear();
|
||
|
removedLines << i;
|
||
|
}
|
||
|
}
|
||
|
}else{
|
||
|
QString warningString = QString("Inconsistent change in %1 at %2:%3 -> %4:%5"
|
||
|
" = \"%6\"(encountered \"%7\") -> \"%8\"")
|
||
|
.arg(file.str())
|
||
|
.arg(change.m_range.start.line)
|
||
|
.arg(change.m_range.start.column)
|
||
|
.arg(change.m_range.end.line)
|
||
|
.arg(change.m_range.end.column)
|
||
|
.arg(change.m_oldText)
|
||
|
.arg(encountered)
|
||
|
.arg(change.m_newText);
|
||
|
|
||
|
if(replacePolicy == DocumentChangeSet::IgnoreFailedChange) {
|
||
|
//Just don't do the replacement
|
||
|
} else if(replacePolicy == DocumentChangeSet::WarnOnFailedChange) {
|
||
|
kWarning() << warningString;
|
||
|
} else {
|
||
|
return DocumentChangeSet::ChangeResult(warningString, sortedChanges[pos]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!removedLines.isEmpty()) {
|
||
|
int offset = 0;
|
||
|
qSort(removedLines);
|
||
|
foreach(int l, removedLines) {
|
||
|
textLines.removeAt(l - offset);
|
||
|
++offset;
|
||
|
}
|
||
|
}
|
||
|
output = textLines.join("\n");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
//Removes all duplicate changes for a single file, and then returns (via filteredChanges) the filtered duplicates
|
||
|
DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::removeDuplicates(const IndexedString& file,
|
||
|
ChangesList& filteredChanges)
|
||
|
{
|
||
|
typedef QMultiMap<SimpleCursor, DocumentChangePointer> ChangesMap;
|
||
|
ChangesMap sortedChanges;
|
||
|
|
||
|
foreach(const DocumentChangePointer &change, changes[file]) {
|
||
|
sortedChanges.insert(change->m_range.end, change);
|
||
|
}
|
||
|
|
||
|
//Remove duplicates
|
||
|
ChangesMap::iterator previous = sortedChanges.begin();
|
||
|
for(ChangesMap::iterator it = ++sortedChanges.begin(); it != sortedChanges.end(); ) {
|
||
|
if(( *previous ) && ( *previous )->m_range.end > (*it)->m_range.start) {
|
||
|
//intersection
|
||
|
if(duplicateChanges(( *previous ), *it)) {
|
||
|
//duplicate, remove one
|
||
|
it = sortedChanges.erase(it);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//When two changes contain each other, and the container change is set to ignore old text, then it should be safe to
|
||
|
//just ignore the contained change, and apply the bigger change
|
||
|
else if((*it)->m_range.contains(( *previous )->m_range) && (*it)->m_ignoreOldText ) {
|
||
|
kDebug() << "Removing change: " << ( *previous )->m_oldText << "->" << ( *previous )->m_newText
|
||
|
<< ", because it is contained by change: " << (*it)->m_oldText << "->" << (*it)->m_newText;
|
||
|
sortedChanges.erase(previous);
|
||
|
}
|
||
|
//This case is for when both have the same end, either of them could be the containing range
|
||
|
else if((*previous)->m_range.contains((*it)->m_range) && (*previous)->m_ignoreOldText ) {
|
||
|
kDebug() << "Removing change: " << (*it)->m_oldText << "->" << (*it)->m_newText
|
||
|
<< ", because it is contained by change: " << ( *previous )->m_oldText
|
||
|
<< "->" << ( *previous )->m_newText;
|
||
|
it = sortedChanges.erase(it);
|
||
|
continue;
|
||
|
} else {
|
||
|
return DocumentChangeSet::ChangeResult(
|
||
|
QString("Inconsistent change-request at %1; "
|
||
|
"intersecting changes: "
|
||
|
"\"%2\"->\"%3\"@%4:%5->%6:%7 & \"%8\"->\"%9\"@%10:%11->%12:%13 ")
|
||
|
.arg(file.str(), ( *previous )->m_oldText, ( *previous )->m_newText)
|
||
|
.arg(( *previous )->m_range.start.line)
|
||
|
.arg(( *previous )->m_range.start.column)
|
||
|
.arg(( *previous )->m_range.end.line)
|
||
|
.arg(( *previous )->m_range.end.column)
|
||
|
.arg((*it)->m_oldText, (*it)->m_newText)
|
||
|
.arg((*it)->m_range.start.line)
|
||
|
.arg((*it)->m_range.start.column)
|
||
|
.arg((*it)->m_range.end.line)
|
||
|
.arg((*it)->m_range.end.column));
|
||
|
}
|
||
|
|
||
|
}
|
||
|
previous = it;
|
||
|
++it;
|
||
|
}
|
||
|
|
||
|
filteredChanges = sortedChanges.values();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void DocumentChangeSetPrivate::updateFiles()
|
||
|
{
|
||
|
ModificationRevisionSet::clearCache();
|
||
|
foreach(const IndexedString& file, changes.keys()) {
|
||
|
ModificationRevision::clearModificationCache(file);
|
||
|
}
|
||
|
|
||
|
if(updatePolicy != DocumentChangeSet::NoUpdate && ICore::self())
|
||
|
{
|
||
|
// The active document should be updated first, so that the user sees the results instantly
|
||
|
if(IDocument* activeDoc = ICore::self()->documentController()->activeDocument()) {
|
||
|
ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(activeDoc->url()));
|
||
|
}
|
||
|
|
||
|
// If there are currently open documents that now need an update, update them too
|
||
|
foreach(const IndexedString& doc, ICore::self()->languageController()->backgroundParser()->managedDocuments()) {
|
||
|
DUChainReadLocker lock(DUChain::lock());
|
||
|
TopDUContext* top = DUChainUtils::standardContextForUrl(doc.toUrl(), true);
|
||
|
if((top && top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->needsUpdate()) || !top) {
|
||
|
lock.unlock();
|
||
|
ICore::self()->languageController()->backgroundParser()->addDocument(doc);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Eventually update _all_ affected files
|
||
|
foreach(const IndexedString &file, changes.keys()) {
|
||
|
if(!file.toUrl().isValid()) {
|
||
|
kWarning() << "Trying to apply changes to an invalid document";
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ICore::self()->languageController()->backgroundParser()->addDocument(file);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|