diff --git a/language/codegen/codegenerator.cpp b/language/codegen/codegenerator.cpp index 23fdc1d99e..4f62746e68 100644 --- a/language/codegen/codegenerator.cpp +++ b/language/codegen/codegenerator.cpp @@ -1,238 +1,239 @@ /* Copyright 2008 Hamish Rodda Copyright 2009 Ramon Zarazua 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 "codegenerator.h" #include "documentchangeset.h" #include "duchainchangeset.h" #include #include #include #include #include #include "applychangeswidget.h" namespace KDevelop { struct CodeGeneratorPrivate { CodeGeneratorPrivate() : autoGen(false), context(0) {} QMap duchainChanges; DocumentChangeSet documentChanges; bool autoGen; DUContext * context; DocumentRange range; QString error; }; CodeGeneratorBase::CodeGeneratorBase() : d(new CodeGeneratorPrivate) { } CodeGeneratorBase::~CodeGeneratorBase() { clearChangeSets(); delete d; } void CodeGeneratorBase::autoGenerate ( DUContext* context, const KDevelop::DocumentRange* range ) { d->autoGen = true; d->context = context; d->range = *range; } void CodeGeneratorBase::addChangeSet(DUChainChangeSet * duchainChange) { IndexedString file = duchainChange->topDuContext().data()->url() ; QMap::iterator it = d->duchainChanges.find(file); //if we already have an entry for this file, merge it if(it !=d->duchainChanges.end()) { **it << *duchainChange; delete duchainChange; } else d->duchainChanges.insert(file, duchainChange); } void CodeGeneratorBase::addChangeSet(DocumentChangeSet & docChangeSet) { d->documentChanges << docChangeSet; } DocumentChangeSet & CodeGeneratorBase::documentChangeSet() { return d->documentChanges; } const QString & CodeGeneratorBase::errorText() const { return d->error; } bool CodeGeneratorBase::autoGeneration() const { return d->autoGen; } void CodeGeneratorBase::setErrorText(const QString & errorText) { d->error = errorText; } void CodeGeneratorBase::clearChangeSets(void) { kDebug() << "Cleaning up all the changesets registered by the generator"; foreach(DUChainChangeSet * changeSet, d->duchainChanges) delete changeSet; d->duchainChanges.clear(); d->documentChanges.clear(); } bool CodeGeneratorBase::execute() { kDebug() << "Checking Preconditions for the codegenerator"; //Shouldn't there be a method in iDocument to get a DocumentRange as well? KUrl document; if(!d->autoGen) { if( !ICore::self()->documentController()->activeDocument() ) { setErrorText( i18n("Could not find an open document" ) ); return false; } document = ICore::self()->documentController()->activeDocument()->url(); if(d->range.isEmpty()) { DUChainReadLocker lock(DUChain::lock()); d->range = DocumentRange(document.url(), ICore::self()->documentController()->activeDocument()->textSelection()); } } if(!d->context) { DUChainReadLocker lock(DUChain::lock()); TopDUContext * documentChain = DUChain::self()->chainForDocument(document); if(!documentChain) { setErrorText(QString("Could not find chain for selected document: %1").arg(document.url())); return false; } d->context = documentChain->findContextIncluding(d->range); if(!d->context) { //Attempt to get the context again QList contexts = DUChain::self()->chainsForDocument(document); foreach(TopDUContext * top, contexts) { kDebug() << "Checking top context with range: " << top->range().textRange() << " for a context"; if((d->context = top->findContextIncluding(d->range))) break; } } } if(!d->context) { setErrorText("Error finding context for selection range"); return false; } if(!checkPreconditions(d->context,d->range)) { setErrorText("Error checking conditions to generate code: " + errorText()); return false; } if(!d->autoGen) { kDebug() << "Gathering user information for the codegenerator"; if(!gatherInformation()) { setErrorText("Error Gathering user information: " + errorText()); return false; } } kDebug() << "Generating code"; if(!process()) { setErrorText("Error generating code: " + errorText()); return false; } if(!d->autoGen) { kDebug() << "Submitting to the user for review"; return displayChanges(); } //If it is autogenerated, it shouldn't need to apply changes, instead return them to client that my be another generator DocumentChangeSet::ChangeResult result(true); if(!d->autoGen && !(result = d->documentChanges.applyAllChanges())) { setErrorText(result.m_failureReason); return false; } return true; } bool CodeGeneratorBase::displayChanges() { DocumentChangeSet::ChangeResult result = d->documentChanges.applyAllToTemp(); if(!result) { setErrorText(result.m_failureReason); return false; } ApplyChangesWidget widget; //TODO: Find some good information to put widget.setInformation("Info?"); - typedef QPair NameList; - foreach(const NameList & namePair, d->documentChanges.tempNamesForAll()) - widget.addDocuments(namePair.first , namePair.second); + QMap temps = d->documentChanges.tempNamesForAll(); + for(QMap::iterator it = temps.begin(); + it != temps.end(); ++it) + widget.addDocuments(it.key() , it.value(), "Info?"); if(widget.exec()) return widget.applyAllChanges(); else return false; } } diff --git a/language/codegen/documentchangeset.cpp b/language/codegen/documentchangeset.cpp index 14a8f49175..1d8fe32a9f 100644 --- a/language/codegen/documentchangeset.cpp +++ b/language/codegen/documentchangeset.cpp @@ -1,805 +1,804 @@ /* Copyright 2008 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 "documentchangeset.h" #include "coderepresentation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KDevelop { struct DocumentChangeSetPrivate { DocumentChangeSet::ReplacementPolicy replacePolicy; DocumentChangeSet::FormatPolicy formatPolicy; DocumentChangeSet::DUChainUpdateHandling updatePolicy; typedef QPair TempPair; QMap< IndexedString, TempPair > tempFiles; QMap< IndexedString, QList > changes; QMap< IndexedString, IndexedString > tempToOriginal; DocumentChangeSet::ChangeResult addChange(DocumentChangePointer change); DocumentChangeSet::ChangeResult replaceOldText(CodeRepresentation * repr, const QString & newText, const QList & sortedChangesList); DocumentChangeSet::ChangeResult generateNewText(const KDevelop::IndexedString & file, QList< KDevelop::DocumentChangePointer > & sortedChanges, const KDevelop::CodeRepresentation* repr, QString& output); DocumentChangeSet::ChangeResult removeDuplicates(const IndexedString & file, QList & filteredChanges); void formatChanges(); void updateFiles(); void addFileToProject(IndexedString file); void addTempFile(IndexedString originalName, const QString & text); void adjustChangeToTemp(DocumentChangePointer newChange); QList withoutOriginals(); DocumentChangeSet::ChangeResult replaceOriginalsWithTemps(); }; //Simple helper 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() && change.m_range.start.line == change.m_range.end.line; } inline bool duplicateChanges(DocumentChangePointer previous, 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)); } } DocumentChangeSet::DocumentChangeSet() : d(new DocumentChangeSetPrivate) { d->replacePolicy = StopOnFailedChange; d->formatPolicy = AutoFormatChanges; d->updatePolicy = SimpleUpdate; } DocumentChangeSet::DocumentChangeSet(const DocumentChangeSet & rhs) : d(new DocumentChangeSetPrivate(*rhs.d)) { } DocumentChangeSet& DocumentChangeSet::operator=(const KDevelop::DocumentChangeSet& rhs) { *d = *rhs.d; return *this; } DocumentChangeSet::~DocumentChangeSet() { delete d; } KDevelop::DocumentChangeSet::ChangeResult DocumentChangeSet::addChange(const KDevelop::DocumentChange& change) { return d->addChange(DocumentChangePointer(new DocumentChange(change))); } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::addChange(DocumentChangePointer change) { if(change->m_range.start.line != change->m_range.end.line) return DocumentChangeSet::ChangeResult("Multi-line ranges are not supported"); if(tempFiles.contains(change->m_document)) adjustChangeToTemp(change); changes[change->m_document].append(change); return true; } /* * Merges the changes from both DocumentChangeSets. * Merging changes from two changesets that have each applied temp changes is not supported * When temp files have been created in one change set, but not the other, all the changes from the * original file need to be adjusted, so they correspond to the temp version. */ DocumentChangeSet & DocumentChangeSet::operator<<(DocumentChangeSet & rhs) { #ifndef NDEBUG kDebug() << "Merging DocumentChangeSets, tempFiles.\nTemp Files in lhs:"; for(QMap ::iterator it = d->tempFiles.begin(); it != d->tempFiles.end(); ++it) kDebug() << it.key().str() << " " << it->first.str(); kDebug() << "Temp Files in rhs: "; for(QMap ::iterator it = rhs.d->tempFiles.begin(); it != rhs.d->tempFiles.end(); ++it) kDebug() << it.key().str() << " " << it->first.str(); #endif //NDEBUG //If a temp has been created on the rhs changeset, then the changes in this one must be adjusted //This is not an efficient method of adjusting them, however it is simpler to manage foreach(const IndexedString & file, rhs.d->tempFiles.keys()) if(d->changes.contains(file)) { foreach(const DocumentChangePointer & change, d->changes[file]) rhs.d->addChange(change); d->changes.remove(file); } /// @todo Possibly check for duplicates, since it could create a lot of bloat when big changes are merged foreach(const QList & changeList, rhs.d->changes.values()) foreach(const DocumentChangePointer & change, changeList) d->addChange(change); /// @todo Fix for a possibility of two different temporaries created for the same fileName d->tempFiles.unite(rhs.d->tempFiles); Q_ASSERT(d->tempFiles.uniqueKeys().size() == d->tempFiles.size()); d->tempToOriginal.unite(rhs.d->tempToOriginal); rhs.clear(); #ifndef NDEBUG kDebug() << "Merged Changes: "; for(QMap >::iterator it = d->changes.begin(); it != d->changes.end(); ++it) { kDebug() << it.key().str(); foreach(DocumentChangePointer p, *it) kDebug() << p->m_newText; } #endif //NDEBUG return *this; } void DocumentChangeSet::clear () { d->tempFiles.clear(); d->changes.clear(); d->tempToOriginal.clear(); } 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; } IndexedString DocumentChangeSet::tempNameForFile ( IndexedString file ) const { if(d->tempFiles.contains(file)) file = d->tempFiles[file].first; return file; } -QList > DocumentChangeSet::tempNamesForAll() const +QMap DocumentChangeSet::tempNamesForAll() const { - QList > names; + QMap names; for(QMap< IndexedString, DocumentChangeSetPrivate::TempPair >::Iterator it = d->tempFiles.begin(); it != d->tempFiles.end(); ++it) - names << qMakePair(it.key(), it.value().first); + names[it.key()] = it.value().first; return names; } DocumentChangeSet::ChangeResult DocumentChangeSet::applyToTemp(IndexedString fileName) { //If there is a temporary version already, then use that one IndexedString tempFile = tempNameForFile(fileName); if(!d->changes.contains(tempFile)) return ChangeResult(i18n("Trying to apply changes to a temporary that is not in this change set: %1", fileName.str())); CodeRepresentation::Ptr repr = createCodeRepresentation(tempFile); ChangeResult result(true); QList sortedChanges; QString newText; { result = d->removeDuplicates(tempFile, sortedChanges); if(!result) return result; result = d->generateNewText(tempFile, sortedChanges, repr.data(), newText); if(!result) return result; } //If a previous temp did not exist, create one if(!d->tempToOriginal.contains(tempFile)) d->addTempFile(fileName, newText); else { result = d->replaceOldText(repr.data(), newText, sortedChanges); //On success the changes' documents should be changed to the original to prevent double application if(result) { IndexedString original = d->tempToOriginal[tempFile]; d->changes.erase(d->changes.find(tempFile)); foreach(DocumentChangePointer p, sortedChanges) { p->m_document = original; d->changes[original] << p; } } } return result; } DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllToTemp() { QMap codeRepresentations; QMap newTexts; QMap > filteredSortedChanges; QList files(d->withoutOriginals()); foreach(const IndexedString &file, files) { kDebug() << "Processing changes- for file: " << file.str(); CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr) return ChangeResult(QString("Could not create a Representation for %1").arg(file.str())); codeRepresentations[file] = repr; QList& sortedChangesList(filteredSortedChanges[file]); { ChangeResult result(d->removeDuplicates(file, sortedChangesList)); if(!result) return result; } { ChangeResult result(d->generateNewText(file, sortedChangesList, repr.data(), newTexts[file])); if(!result) return result; } } QMap oldTexts; //! @todo apply correct formatting, and call it //d->autoformatChanges() //Apply the changes to the files foreach(const IndexedString & file, files) { if(!d->tempToOriginal.contains(file)) { kDebug() << "Adding a new Temp file from original with text: " << newTexts[file]; d->addTempFile(file, newTexts[file]); } else { oldTexts[file] = codeRepresentations[file]->text(); kDebug() << "Applying to already created temp: " << file.str() << ". Old text: " << oldTexts[file]; DocumentChangeSet::ChangeResult 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; } //On success the changes' documents should be changed to the original to prevent double application d->changes.erase(d->changes.find(file)); foreach(DocumentChangePointer p, filteredSortedChanges[file]) { p->m_document = d->tempToOriginal[file]; d->changes[p->m_document] << p; } kDebug() << "Applied new text: " << newTexts[file]; } } ModificationRevisionSet::clearCache(); d->updateFiles(); return DocumentChangeSet::ChangeResult(true); } DocumentChangeSet::ChangeResult DocumentChangeSet::applyAllChanges() { QMap codeRepresentations; QMap newTexts; QMap > filteredSortedChanges; ChangeResult result(true); QList files(d->withoutOriginals()); 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& 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 oldTexts; //! @todo apply correct formatting, and call it //d->autoformatChanges() //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; } } result = d->replaceOriginalsWithTemps(); ModificationRevisionSet::clearCache(); d->updateFiles(); return result;; } DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOldText(CodeRepresentation * repr, const QString & newText, const QList & sortedChangesList) { DynamicCodeRepresentation* dynamic = dynamic_cast(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 for file in disk, or artificial code: %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, QList & 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'); 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 = textLines[change.m_range.start.line].mid(change.m_range.start.column, change.m_range.end.column-change.m_range.start.column)) == 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) change.m_newText = formatter->formatSource(change.m_newText, KMimeType::findByUrl(file.toUrl()), leftContext, rightContext); textLines[change.m_range.start.line].replace(change.m_range.start.column, change.m_range.end.column-change.m_range.start.column, change.m_newText); }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]); } } 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, QList & filteredChanges) { QMultiMap sortedChanges; foreach(const DocumentChangePointer &change, changes[file]) sortedChanges.insert(change->m_range.end, change); //Remove duplicates QMultiMap::iterator previous = sortedChanges.begin(); for(QMultiMap::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() { if(updatePolicy != DocumentChangeSet::NoUpdate && ICore::self()) foreach(const IndexedString &file, changes.keys()) { ICore::self()->languageController()->backgroundParser()->addDocument(file.toUrl()); foreach(KUrl doc, ICore::self()->languageController()->backgroundParser()->managedDocuments()) { DUChainReadLocker lock(DUChain::lock()); TopDUContext* top = DUChainUtils::standardContextForUrl(doc); if((top && top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->needsUpdate()) || !top) { lock.unlock(); ICore::self()->languageController()->backgroundParser()->addDocument(doc); } } } } void DocumentChangeSetPrivate::formatChanges() { #if 0 if(formatChanges() == DocumentChangeSet::AutoFormatChanges) { foreach(const IndexedString &file, d->changes.keys()) { ISourceFormatter* formatter = ICore::self()->sourceFormatterController()->formatterForUrl(file.toUrl()); if(!formatter) continue; filteredSortedChanges[file].clear(); //We create a new list of changes QStringList fileNewText = newTexts[file].split("\n"); QList< SimpleRange > changes = changedRanges[file].keys(); for(QList< SimpleRange >::const_iterator it = changes.end(); it != changes.begin(); ) { --it; QString leftContext = QStringList(fileNewText.mid(0, (*it).start.line+1)).join("\n"); leftContext.chop((*it).start.column); QString text = fileNewText[(*it).start.line].mid((*it).start.column); if((*it).start.line == (*it).end.line) text = text.left((*it).end.column - (*it).start.column); QString rightContext = QStringList(fileNewText.mid((*it).end.line)).join("\n").mid((*it).end.column); /* --it; int end = *it+1; int start = *it; while(it != changes.begin() && *(it-1) == start-1) { --start; --it; } QString leftContext = QStringList(fileNewText.mid(0, start)).join("\n"); QString sourceToFormat = QStringList(fileNewText.mid(start, end-start)).join("\n"); QString rightContext = QStringList(fileNewText.mid(end)).join("\n"); QString formattedSource = formatter->formatSource(sourceToFormat, KMimeType::findByUrl(file.toUrl()), leftContext + "\n", "\n" + rightContext); fileNewText.erase(fileNewText.begin()+start, fileNewText.begin()+end); int insertAt = start; foreach(QString insertString, formattedSource.split("\n")) { fileNewText.insert(insertAt, insertString); ++insertAt; } //Create new changes that replace the whole lines at the same time filteredSortedChanges[file].prepend(DocumentChangePointer(new DocumentChange(file, SimpleRange(start, 0, end, 0), sourceToFormat+"\n", formattedSource+"\n"))); */} newTexts[file] = fileNewText.join("\n"); } } #endif } void DocumentChangeSetPrivate::addFileToProject(IndexedString file) { Q_UNUSED(file); #if 0 //Pick the folder Item that should contain the new class IProject * p; QList folderList = p->foldersForUrl(newClassWizard.implementationUrl().upUrl()); if(folderList.isEmpty()) return; ProjectFolderItem* folder = folderList.first(); //Add new files into the project if(!item) item=folder; ProjectFileItem* projectFile=p->buildSystemManager()->addFile(newClassWizard.implementationUrl(), folder); //Add them as targets if(item->target()) { p->buildSystemManager()->addFileToTarget(file, item->target()); p->buildSystemManager()->addFileToTarget(header, item->target()); } else if(item->project()->buildSystemManager() && item->project()->buildSystemManager()->features() & IBuildSystemManager::Targets) { QList t=folder->targetList(); for(QStandardItem* it=folder; it && t.isEmpty(); it=it->parent()) { KDevelop::ProjectBaseItem* bit=static_cast(it); t=bit->targetList(); } if(t.count()==1) //Just choose this one p->buildSystemManager()->addFileToTarget(file, t.first()); else { KDialog d; QWidget *w=new QWidget(&d); w->setLayout(new QVBoxLayout); w->layout()->addWidget(new QLabel("Choose one target to add the file or cancel if you do not want to do so.")); QListWidget* targetsWidget=new QListWidget(w); targetsWidget->setSelectionMode(QAbstractItemView::SingleSelection); foreach(ProjectTargetItem* it, t) { targetsWidget->addItem(it->text()); } w->layout()->addWidget(targetsWidget); targetsWidget->setCurrentRow(0); d.setButtons( KDialog::Ok | KDialog::Cancel); d.enableButtonOk(true); d.setMainWidget(w); if(d.exec()==QDialog::Accepted) { if(targetsWidget->selectedItems().isEmpty()) QMessageBox::warning(0, QString(), i18n("Did not select anything, not adding to a target.")); else p->buildSystemManager()->addFileToTarget(file, t[targetsWidget->currentRow()]); } } } #endif } void DocumentChangeSetPrivate::addTempFile(IndexedString originalName, const QString & text) { QString name = originalName.str(); KUrl url = CodeRepresentation::artificialUrl(name); unsigned int counter = 0; //Search for a unique new url for this file while(artificialCodeRepresentationExists(IndexedString(url))) { //Increment a number reference before the period name.insert(name.lastIndexOf("."), QString::number(counter++)); url = CodeRepresentation::artificialUrl(name); } IndexedString tempFile(url); tempFiles[originalName] = qMakePair(tempFile, InsertArtificialCodeRepresentationPointer(new InsertArtificialCodeRepresentation(tempFile, text))); tempToOriginal[tempFile] = originalName; changes.erase(changes.find(tempFile)); } void DocumentChangeSetPrivate::adjustChangeToTemp(DocumentChangePointer newChange) { kDebug() << "Adjusting change to temp for change: " << newChange->m_newText << "With range: " << newChange->m_range.textRange(); //Adjust the range of the new change according to temporaries already applied //Sort the changes to the original file QList sortedChanges; removeDuplicates(newChange->m_document, sortedChanges ); std::reverse(sortedChanges.begin(), sortedChanges.end()); - //foreach changed foreach(DocumentChangePointer originalChange, sortedChanges) { if(originalChange->m_range.textRange() < newChange->m_range.textRange() ) { if(originalChange->m_range.end.line == newChange->m_range.start.line) { //Apply column offset int offset; if(originalChange->m_newText.count('\n')) offset = originalChange->m_range.start.column - (originalChange->m_newText.lastIndexOf('\n') - originalChange->m_newText.size() + 1); else offset = originalChange->m_newText.size() - (originalChange->m_range.end.column - originalChange->m_range.start.column); newChange->m_range.start.column += offset; newChange->m_range.end.column += offset; } //Get the line difference that the change poduced, and apply it to the new one int lineDifference = originalChange->m_newText.count('\n') - originalChange->m_range.textRange().numberOfLines(); if(lineDifference) { newChange->m_range.start.line += lineDifference; newChange->m_range.end.line += lineDifference; } } } newChange->m_document = tempFiles[newChange->m_document].first; kDebug() << "New range: " << newChange->m_range.textRange(); } // Return a list of the files that are logically unapplied changes QList DocumentChangeSetPrivate::withoutOriginals() { QList files(changes.keys()); QList::iterator it = files.begin(); while(it != files.end()) { if(tempFiles.contains(*it)) it = files.erase(it); else ++it; } return files; } //Replace original files with temporaries DocumentChangeSet::ChangeResult DocumentChangeSetPrivate::replaceOriginalsWithTemps() { //These two are helpers for backtracking changes in case an error replacing text is encountered QMap oldText; QMap originalRepresentations; QMap< IndexedString, TempPair >::iterator it = tempFiles.begin(); while(it != tempFiles.end()) { CodeRepresentation::Ptr original = originalRepresentations[it.key()] = createCodeRepresentation(it.key()); oldText[it.key()] = original->text(); CodeRepresentation::Ptr temp = createCodeRepresentation(it->first); if(!original->setText(temp->text())) { foreach(const IndexedString & file, originalRepresentations.keys()) originalRepresentations[file]->setText(oldText[file]); return DocumentChangeSet::ChangeResult(QString("Error trying to write changes to file: %1").arg(it.key().str())); } ++it; } return true; } } diff --git a/language/codegen/documentchangeset.h b/language/codegen/documentchangeset.h index febb27c19f..56b7949530 100644 --- a/language/codegen/documentchangeset.h +++ b/language/codegen/documentchangeset.h @@ -1,129 +1,129 @@ /* Copyright 2008 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. */ #ifndef DOCUMENTCHANGESET_H #define DOCUMENTCHANGESET_H #include #include #include #include namespace KDevelop { struct DocumentChangeSetPrivate; struct KDEVPLATFORMLANGUAGE_EXPORT DocumentChange : public QSharedData { DocumentChange(const IndexedString& document, const SimpleRange& range, const QString& oldText, const QString& newText) : m_document(document), m_range(range), m_oldText(oldText), m_newText(newText), m_ignoreOldText(false) { //Clean the URL, so we don't get the same file be stored as a different one KUrl url(m_document.toUrl()); url.cleanPath(); m_document = IndexedString(url); } IndexedString m_document; SimpleRange m_range; QString m_oldText; QString m_newText; bool m_ignoreOldText; //Set this to disable the verification of m_oldText. This can be used to overwrite arbitrary text, but is dangerous! }; typedef KSharedPtr DocumentChangePointer; class KDEVPLATFORMLANGUAGE_EXPORT DocumentChangeSet { public: DocumentChangeSet(); DocumentChangeSet(const DocumentChangeSet & rhs); ~DocumentChangeSet(); DocumentChangeSet& operator=(const DocumentChangeSet& rhs); //Returns true on success struct ChangeResult { ChangeResult(bool success) : m_success(success) { } ChangeResult(QString failureReason, DocumentChangePointer reasonChange = DocumentChangePointer()) : m_failureReason(failureReason), m_reasonChange(reasonChange), m_success(false) { } operator bool() const { return m_success; } QString m_failureReason; DocumentChangePointer m_reasonChange; bool m_success; }; ///If the change has multiple lines, a problem will be returned. these don't work at he moment. ChangeResult addChange(const DocumentChange& change); ///Get rid of all the current changes void clear(); ///Merge two document change sets DocumentChangeSet & operator<<(DocumentChangeSet &); enum ReplacementPolicy { IgnoreFailedChange,///If this is given, all changes that could not be applied are simply ignored WarnOnFailedChange,///If this is given to applyAllChanges, a warning is given when a change could not be applied, ///but following changes are applied, and success is returned. StopOnFailedChange ///If this is given to applyAllChanges, then all replacements are reverted and an error returned on problems }; ///@param policy What should be done when a change could not be applied? void setReplacementPolicy(ReplacementPolicy policy); enum FormatPolicy { NoAutoFormat, ///If this option is given, no automatic formatting is applied AutoFormatChanges ///If this option is given, all changes are automatically reformatted using the formatter plugin for the mime type }; ///@param policy How the changed text should be formatted void setFormatPolicy(FormatPolicy policy); enum DUChainUpdateHandling { NoUpdate, ///No updates will be scheduled SimpleUpdate ///The changed documents will be added to the background parser, plus all documents that are currently open and recursively import those documetns //FullUpdate ///All documents in all open projects that recursively import any of the changed documents will be updated }; ///@param policy Whether a duchain update should be triggered for all affected documents void setUpdateHandling(DUChainUpdateHandling policy); ///Apply all the registered changes for @p file, and keep them as temporary in memory ///@note For multiple passes if the original file is given, then the previous temporary will be merged ChangeResult applyToTemp(IndexedString file); ///Apply all changes for all files, and keep them as temporary in memory ChangeResult applyAllToTemp(); /// @return The name for the latest temporary version of @p file if it exists, returns @p file otherwise IndexedString tempNameForFile(IndexedString file) const; /// @return The mapping between all original file names and their temporaries /// @note If a file has not been applied to temp, it won't appear in the mapping - QList > tempNamesForAll() const; + QMap tempNamesForAll() const; /// Apply all the changes registered in this changeset ChangeResult applyAllChanges(); private: DocumentChangeSetPrivate * d; }; } #endif