/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "diffeditordocument.h" #include "diffeditorconstants.h" #include "diffeditorcontroller.h" #include "diffutils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; namespace DiffEditor { namespace Internal { DiffEditorDocument::DiffEditorDocument() : Core::BaseTextDocument() { setId(Constants::DIFF_EDITOR_ID); setMimeType(Constants::DIFF_EDITOR_MIMETYPE); setTemporary(true); } /** * @brief Set a controller for a document * @param controller The controller to set. * * This method takes ownership of the controller and will delete it after it is done with it. */ void DiffEditorDocument::setController(DiffEditorController *controller) { if (m_controller == controller) return; QTC_ASSERT(isTemporary(), return); if (m_controller) m_controller->deleteLater(); m_controller = controller; } DiffEditorController *DiffEditorDocument::controller() const { return m_controller; } static void appendRow(ChunkData *chunk, const RowData &row) { const bool isSeparator = row.leftLine.textLineType == TextLineData::Separator && row.rightLine.textLineType == TextLineData::Separator; if (!isSeparator) chunk->rows.append(row); } ChunkData DiffEditorDocument::filterChunk(const ChunkData &data, const ChunkSelection &selection, bool revert) { if (selection.isNull()) return data; ChunkData chunk(data); chunk.rows.clear(); for (int i = 0; i < data.rows.count(); ++i) { RowData row = data.rows[i]; const bool isLeftSelected = selection.leftSelection.contains(i); const bool isRightSelected = selection.rightSelection.contains(i); if (isLeftSelected || isRightSelected) { if (row.equal || (isLeftSelected && isRightSelected)) { appendRow(&chunk, row); } else if (isLeftSelected) { RowData newRow = row; row.rightLine = TextLineData(TextLineData::Separator); appendRow(&chunk, row); if (revert) { newRow.leftLine = newRow.rightLine; newRow.equal = true; appendRow(&chunk, newRow); } } else { // isRightSelected if (!revert) { RowData newRow = row; newRow.rightLine = newRow.leftLine; newRow.equal = true; appendRow(&chunk, newRow); } row.leftLine = TextLineData(TextLineData::Separator); appendRow(&chunk, row); } } else { if (revert) row.leftLine = row.rightLine; else row.rightLine = row.leftLine; row.equal = true; appendRow(&chunk, row); } } return chunk; } QString DiffEditorDocument::makePatch(int fileIndex, int chunkIndex, const ChunkSelection &selection, bool revert, bool addPrefix, const QString &overriddenFileName) const { if (fileIndex < 0 || chunkIndex < 0) return QString(); if (fileIndex >= m_diffFiles.count()) return QString(); const FileData &fileData = m_diffFiles.at(fileIndex); if (chunkIndex >= fileData.chunks.count()) return QString(); const ChunkData chunkData = filterChunk(fileData.chunks.at(chunkIndex), selection, revert); const bool lastChunk = (chunkIndex == fileData.chunks.count() - 1); const QString fileName = !overriddenFileName.isEmpty() ? overriddenFileName : revert ? fileData.rightFileInfo.fileName : fileData.leftFileInfo.fileName; QString leftPrefix, rightPrefix; if (addPrefix) { leftPrefix = "a/"; rightPrefix = "b/"; } return DiffUtils::makePatch(chunkData, leftPrefix + fileName, rightPrefix + fileName, lastChunk && fileData.lastChunkAtTheEndOfFile); } void DiffEditorDocument::setDiffFiles(const QList &data, const QString &directory, const QString &startupFile) { m_diffFiles = data; if (!directory.isEmpty()) m_baseDirectory = directory; m_startupFile = startupFile; emit documentChanged(); } QList DiffEditorDocument::diffFiles() const { return m_diffFiles; } QString DiffEditorDocument::baseDirectory() const { return m_baseDirectory; } void DiffEditorDocument::setBaseDirectory(const QString &directory) { m_baseDirectory = directory; } QString DiffEditorDocument::startupFile() const { return m_startupFile; } void DiffEditorDocument::setDescription(const QString &description) { if (m_description == description) return; m_description = description; emit descriptionChanged(); } QString DiffEditorDocument::description() const { return m_description; } void DiffEditorDocument::setContextLineCount(int lines) { QTC_ASSERT(!m_isContextLineCountForced, return); m_contextLineCount = lines; } int DiffEditorDocument::contextLineCount() const { return m_contextLineCount; } void DiffEditorDocument::forceContextLineCount(int lines) { m_contextLineCount = lines; m_isContextLineCountForced = true; } bool DiffEditorDocument::isContextLineCountForced() const { return m_isContextLineCountForced; } void DiffEditorDocument::setIgnoreWhitespace(bool ignore) { m_ignoreWhitespace = ignore; } bool DiffEditorDocument::ignoreWhitespace() const { return m_ignoreWhitespace; } bool DiffEditorDocument::setContents(const QByteArray &contents) { Q_UNUSED(contents) return true; } QString DiffEditorDocument::fallbackSaveAsPath() const { if (!m_baseDirectory.isEmpty()) return m_baseDirectory; return QDir::homePath(); } bool DiffEditorDocument::isSaveAsAllowed() const { return state() == LoadOK; } bool DiffEditorDocument::save(QString *errorString, const QString &fileName, bool autoSave) { Q_UNUSED(errorString) Q_UNUSED(autoSave) if (state() != LoadOK) return false; const bool ok = write(fileName, format(), plainText(), errorString); if (!ok) return false; setController(nullptr); setDescription(QString()); Core::EditorManager::clearUniqueId(this); const QFileInfo fi(fileName); setTemporary(false); setFilePath(FilePath::fromString(fi.absoluteFilePath())); setPreferredDisplayName(QString()); emit temporaryStateChanged(); return true; } void DiffEditorDocument::reload() { if (m_controller) { m_controller->requestReload(); } else { QString errorMessage; reload(&errorMessage, Core::IDocument::FlagReload, Core::IDocument::TypeContents); } } bool DiffEditorDocument::reload(QString *errorString, ReloadFlag flag, ChangeType type) { Q_UNUSED(type) if (flag == FlagIgnore) return true; return open(errorString, filePath().toString(), filePath().toString()) == OpenResult::Success; } Core::IDocument::OpenResult DiffEditorDocument::open(QString *errorString, const QString &fileName, const QString &realFileName) { QTC_CHECK(fileName == realFileName); // does not support autosave beginReload(); QString patch; ReadResult readResult = read(fileName, &patch, errorString); if (readResult == TextFileFormat::ReadIOError || readResult == TextFileFormat::ReadMemoryAllocationError) { return OpenResult::ReadError; } bool ok = false; QList fileDataList = DiffUtils::readPatch(patch, &ok); if (!ok) { *errorString = tr("Could not parse patch file \"%1\". " "The content is not of unified diff format.") .arg(fileName); } else { const QFileInfo fi(fileName); setTemporary(false); emit temporaryStateChanged(); setFilePath(FilePath::fromString(fi.absoluteFilePath())); setDiffFiles(fileDataList, fi.absolutePath()); } endReload(ok); if (!ok && readResult == TextFileFormat::ReadEncodingError) ok = selectEncoding(); return ok ? OpenResult::Success : OpenResult::CannotHandle; } bool DiffEditorDocument::selectEncoding() { Core::CodecSelector codecSelector(Core::ICore::dialogParent(), this); switch (codecSelector.exec()) { case Core::CodecSelector::Reload: { setCodec(codecSelector.selectedCodec()); QString errorMessage; return reload(&errorMessage, Core::IDocument::FlagReload, Core::IDocument::TypeContents); } case Core::CodecSelector::Save: setCodec(codecSelector.selectedCodec()); return Core::EditorManager::saveDocument(this); case Core::CodecSelector::Cancel: break; } return false; } QString DiffEditorDocument::fallbackSaveAsFileName() const { const int maxSubjectLength = 50; const QString desc = description(); if (!desc.isEmpty()) { QString name = QString::fromLatin1("0001-%1").arg(desc.left(desc.indexOf('\n'))); name = FileUtils::fileSystemFriendlyName(name); name.truncate(maxSubjectLength); name.append(".patch"); return name; } return QStringLiteral("0001.patch"); } // ### fixme: git-specific handling should be done in the git plugin: // Remove unexpanded branches and follows-tag, clear indentation // and create E-mail static void formatGitDescription(QString *description) { QString result; result.reserve(description->size()); const auto descriptionList = description->split('\n'); for (QString line : descriptionList) { if (line.startsWith("commit ") || line.startsWith("Branches: ")) continue; if (line.startsWith("Author: ")) line.replace(0, 8, "From: "); else if (line.startsWith(" ")) line.remove(0, 4); result.append(line); result.append('\n'); } *description = result; } QString DiffEditorDocument::plainText() const { QString result = description(); const int formattingOptions = DiffUtils::GitFormat; if (formattingOptions & DiffUtils::GitFormat) formatGitDescription(&result); const QString diff = DiffUtils::makePatch(diffFiles(), formattingOptions); if (!diff.isEmpty()) { if (!result.isEmpty()) result += '\n'; result += diff; } return result; } void DiffEditorDocument::beginReload() { emit aboutToReload(); m_state = Reloading; emit changed(); QSignalBlocker blocker(this); setDiffFiles(QList(), QString()); setDescription(QString()); } void DiffEditorDocument::endReload(bool success) { m_state = success ? LoadOK : LoadFailed; emit changed(); emit reloadFinished(success); } } // namespace Internal } // namespace DiffEditor