Files
qt-creator/src/plugins/diffeditor/diffeditordocument.cpp

406 lines
11 KiB
C++
Raw Normal View History

// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#include "diffeditordocument.h"
#include "diffeditorconstants.h"
#include "diffeditorcontroller.h"
#include "diffutils.h"
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <coreplugin/dialogs/codecselector.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <QCoreApplication>
#include <QFile>
#include <QDir>
#include <QMenu>
#include <QTextCodec>
#include <QUuid>
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.line[LeftSide].textLineType == TextLineData::Separator
&& row.line[RightSide].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.selection[LeftSide].contains(i);
const bool isRightSelected = selection.selection[RightSide].contains(i);
if (isLeftSelected || isRightSelected) {
if (row.equal || (isLeftSelected && isRightSelected)) {
appendRow(&chunk, row);
} else if (isLeftSelected) {
RowData newRow = row;
row.line[RightSide] = TextLineData(TextLineData::Separator);
appendRow(&chunk, row);
if (revert) {
newRow.line[LeftSide] = newRow.line[RightSide];
newRow.equal = true;
appendRow(&chunk, newRow);
}
} else { // isRightSelected
if (!revert) {
RowData newRow = row;
newRow.line[RightSide] = newRow.line[LeftSide];
newRow.equal = true;
appendRow(&chunk, newRow);
}
row.line[LeftSide] = TextLineData(TextLineData::Separator);
appendRow(&chunk, row);
}
} else {
if (revert)
row.line[LeftSide] = row.line[RightSide];
else
row.line[RightSide] = row.line[LeftSide];
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 || fileIndex >= m_diffFiles.count())
return {};
const FileData &fileData = m_diffFiles.at(fileIndex);
if (chunkIndex >= fileData.chunks.count())
return {};
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.fileInfo[RightSide].fileName
: fileData.fileInfo[LeftSide].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<FileData> &data, const FilePath &directory,
const QString &startupFile)
{
m_diffFiles = data;
if (!directory.isEmpty())
m_baseDirectory = directory;
m_startupFile = startupFile;
emit documentChanged();
}
QList<FileData> DiffEditorDocument::diffFiles() const
{
return m_diffFiles;
}
FilePath DiffEditorDocument::baseDirectory() const
{
return m_baseDirectory;
}
void DiffEditorDocument::setBaseDirectory(const FilePath &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;
}
FilePath DiffEditorDocument::fallbackSaveAsPath() const
{
if (!m_baseDirectory.isEmpty())
return m_baseDirectory;
return FileUtils::homePath();
}
bool DiffEditorDocument::isSaveAsAllowed() const
{
return state() == LoadOK;
}
bool DiffEditorDocument::save(QString *errorString, const FilePath &filePath, bool autoSave)
{
Q_UNUSED(errorString)
Q_UNUSED(autoSave)
if (state() != LoadOK)
return false;
const bool ok = write(filePath, format(), plainText(), errorString);
if (!ok)
return false;
setController(nullptr);
setDescription({});
Core::EditorManager::clearUniqueId(this);
setTemporary(false);
setFilePath(filePath.absoluteFilePath());
setPreferredDisplayName({});
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(), filePath()) == OpenResult::Success;
}
Core::IDocument::OpenResult DiffEditorDocument::open(QString *errorString, const FilePath &filePath,
const FilePath &realFilePath)
{
QTC_CHECK(filePath == realFilePath); // does not support autosave
beginReload();
QString patch;
ReadResult readResult = read(filePath, &patch, errorString);
if (readResult == TextFileFormat::ReadIOError
|| readResult == TextFileFormat::ReadMemoryAllocationError) {
return OpenResult::ReadError;
}
bool ok = false;
QList<FileData> fileDataList = DiffUtils::readPatch(patch, &ok);
if (!ok) {
*errorString = tr("Could not parse patch file \"%1\". "
"The content is not of unified diff format.")
.arg(filePath.toUserOutput());
} else {
setTemporary(false);
emit temporaryStateChanged();
setFilePath(filePath.absoluteFilePath());
setDiffFiles(fileDataList, filePath.absoluteFilePath());
}
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: <Expand>"))
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({}, {});
setDescription({});
}
void DiffEditorDocument::endReload(bool success)
{
m_state = success ? LoadOK : LoadFailed;
emit changed();
emit reloadFinished(success);
}
} // namespace Internal
} // namespace DiffEditor