DiffEditor: Stage and unstage selected lines for Git

Fixes: QTCREATORBUG-19071
Change-Id: I560ba208e68e477ea865e499847d819cfdfeb6f3
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: André Hartmann <aha_1980@gmx.de>
This commit is contained in:
Andre Hartmann
2017-11-29 21:36:30 +01:00
committed by Jarek Kobus
parent 2758682723
commit 1766832918
15 changed files with 422 additions and 57 deletions

View File

@@ -71,9 +71,10 @@ bool DiffEditorController::ignoreWhitespace() const
} }
QString DiffEditorController::makePatch(int fileIndex, int chunkIndex, QString DiffEditorController::makePatch(int fileIndex, int chunkIndex,
const ChunkSelection &selection,
PatchOptions options) const PatchOptions options) const
{ {
return m_document->makePatch(fileIndex, chunkIndex, return m_document->makePatch(fileIndex, chunkIndex, selection,
options & Revert, options & AddPrefix); options & Revert, options & AddPrefix);
} }
@@ -143,9 +144,10 @@ void DiffEditorController::reloadFinished(bool success)
m_isReloading = false; m_isReloading = false;
} }
void DiffEditorController::requestChunkActions(QMenu *menu, int fileIndex, int chunkIndex) void DiffEditorController::requestChunkActions(QMenu *menu, int fileIndex, int chunkIndex,
const ChunkSelection &selection)
{ {
emit chunkActionsRequested(menu, fileIndex, chunkIndex); emit chunkActionsRequested(menu, fileIndex, chunkIndex, selection);
} }
bool DiffEditorController::chunkExists(int fileIndex, int chunkIndex) const bool DiffEditorController::chunkExists(int fileIndex, int chunkIndex) const

View File

@@ -38,6 +38,8 @@ namespace DiffEditor {
namespace Internal { class DiffEditorDocument; } namespace Internal { class DiffEditorDocument; }
class ChunkSelection;
class DIFFEDITOR_EXPORT DiffEditorController : public QObject class DIFFEDITOR_EXPORT DiffEditorController : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -58,18 +60,21 @@ public:
AddPrefix = 2 AddPrefix = 2
}; };
Q_DECLARE_FLAGS(PatchOptions, PatchOption) Q_DECLARE_FLAGS(PatchOptions, PatchOption)
QString makePatch(int fileIndex, int chunkIndex, PatchOptions options) const; QString makePatch(int fileIndex, int chunkIndex, const ChunkSelection &selection,
PatchOptions options) const;
static Core::IDocument *findOrCreateDocument(const QString &vcsId, static Core::IDocument *findOrCreateDocument(const QString &vcsId,
const QString &displayName); const QString &displayName);
static DiffEditorController *controller(Core::IDocument *document); static DiffEditorController *controller(Core::IDocument *document);
void requestChunkActions(QMenu *menu, int fileIndex, int chunkIndex); void requestChunkActions(QMenu *menu, int fileIndex, int chunkIndex,
const ChunkSelection &selection);
bool chunkExists(int fileIndex, int chunkIndex) const; bool chunkExists(int fileIndex, int chunkIndex) const;
Core::IDocument *document() const; Core::IDocument *document() const;
signals: signals:
void chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex); void chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex,
const ChunkSelection &selection);
protected: protected:
// reloadFinished() should be called // reloadFinished() should be called

View File

@@ -76,7 +76,39 @@ DiffEditorController *DiffEditorDocument::controller() const
return m_controller; return m_controller;
} }
ChunkData DiffEditorDocument::filterChunk(const ChunkData &data,
const ChunkSelection &selection, bool revert)
{
if (selection.isNull())
return data;
ChunkData chunk(data);
for (int i = 0; i < chunk.rows.count(); ++i) {
RowData &row = chunk.rows[i];
if (i < selection.startRow || i >= selection.startRow + selection.selectedRowsCount) {
if (revert)
row.leftLine = row.rightLine;
else
row.rightLine = row.leftLine;
row.equal = true;
}
}
for (int i = 0; i < chunk.rows.count(); ) {
const RowData &row = chunk.rows[i];
const bool isSeparator = row.leftLine.textLineType == TextLineData::Separator
&& row.rightLine.textLineType == TextLineData::Separator;
if (isSeparator)
chunk.rows.removeAt(i);
else
++i;
}
return chunk;
}
QString DiffEditorDocument::makePatch(int fileIndex, int chunkIndex, QString DiffEditorDocument::makePatch(int fileIndex, int chunkIndex,
const ChunkSelection &selection,
bool revert, bool addPrefix, bool revert, bool addPrefix,
const QString &overriddenFileName) const const QString &overriddenFileName) const
{ {
@@ -90,7 +122,7 @@ QString DiffEditorDocument::makePatch(int fileIndex, int chunkIndex,
if (chunkIndex >= fileData.chunks.count()) if (chunkIndex >= fileData.chunks.count())
return QString(); return QString();
const ChunkData &chunkData = fileData.chunks.at(chunkIndex); const ChunkData chunkData = filterChunk(fileData.chunks.at(chunkIndex), selection, revert);
const bool lastChunk = (chunkIndex == fileData.chunks.count() - 1); const bool lastChunk = (chunkIndex == fileData.chunks.count() - 1);
const QString fileName = !overriddenFileName.isEmpty() const QString fileName = !overriddenFileName.isEmpty()

View File

@@ -34,6 +34,7 @@ QT_FORWARD_DECLARE_CLASS(QMenu)
namespace DiffEditor { namespace DiffEditor {
class DiffEditorController; class DiffEditorController;
class ChunkSelection;
namespace Internal { namespace Internal {
@@ -52,7 +53,9 @@ public:
LoadFailed LoadFailed
}; };
QString makePatch(int fileIndex, int chunkIndex, static ChunkData filterChunk(const ChunkData &data,
const ChunkSelection &selection, bool revert);
QString makePatch(int fileIndex, int chunkIndex, const ChunkSelection &selection,
bool revert, bool addPrefix = false, bool revert, bool addPrefix = false,
const QString &overriddenFileName = QString()) const; const QString &overriddenFileName = QString()) const;

View File

@@ -610,6 +610,7 @@ void DiffEditorPlugin::diffExternalFiles()
Q_DECLARE_METATYPE(DiffEditor::ChunkData) Q_DECLARE_METATYPE(DiffEditor::ChunkData)
Q_DECLARE_METATYPE(DiffEditor::FileData) Q_DECLARE_METATYPE(DiffEditor::FileData)
Q_DECLARE_METATYPE(DiffEditor::ChunkSelection)
static inline QString _(const char *string) { return QString::fromLatin1(string); } static inline QString _(const char *string) { return QString::fromLatin1(string); }
@@ -1430,6 +1431,140 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch()
} }
} }
void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data()
{
QTest::addColumn<ChunkData>("chunk");
QTest::addColumn<QStringList>("rightLines");
QTest::addColumn<ChunkSelection>("selection");
auto createChunk = []() {
ChunkData chunk;
chunk.contextInfo = "void DiffEditor::ctor()";
chunk.contextChunk = false;
chunk.leftStartingLineNumber = 49;
chunk.rightStartingLineNumber = 49;
return chunk;
};
auto appendRow = [](ChunkData *chunk, const QString &left, const QString &right) {
RowData row;
row.equal = (left == right);
row.leftLine.text = left;
row.leftLine.textLineType = left.isEmpty() ? TextLineData::Separator : TextLineData::TextLine;
row.rightLine.text = right;
row.rightLine.textLineType = right.isEmpty() ? TextLineData::Separator : TextLineData::TextLine;
chunk->rows.append(row);
};
ChunkData chunk;
QStringList rightLines;
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "", "B"); // 51 +
appendRow(&chunk, "C", "C"); // 52
rightLines = QStringList {
"A",
"B",
"C"
};
QTest::newRow("one added") << chunk << rightLines << ChunkSelection();
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", ""); // 51 -
appendRow(&chunk, "C", "C"); // 52
rightLines = QStringList {
"A",
"",
"C"
};
QTest::newRow("one removed") << chunk << rightLines << ChunkSelection();
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "", "B"); // 51
appendRow(&chunk, "", "C"); // 52 +
appendRow(&chunk, "", "D"); // 53 +
appendRow(&chunk, "", "E"); // 54
appendRow(&chunk, "F", "F"); // 55
rightLines = QStringList {
"A",
"C",
"D",
"F",
};
QTest::newRow("stage selected added") << chunk << rightLines << ChunkSelection(2, 2);
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "", "B"); // 51 +
appendRow(&chunk, "C", "D"); // 52
appendRow(&chunk, "E", "E"); // 53
rightLines = QStringList {
"A",
"B",
"C",
"E",
};
QTest::newRow("stage selected added keep changed") << chunk << rightLines << ChunkSelection(1, 1);
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", ""); // 51
appendRow(&chunk, "C", ""); // 52 -
appendRow(&chunk, "D", ""); // 53 -
appendRow(&chunk, "E", ""); // 54
appendRow(&chunk, "F", "F"); // 55
rightLines = QStringList {
"A",
"B",
"",
"",
"E",
"F",
};
QTest::newRow("stage selected removed") << chunk << rightLines << ChunkSelection(2, 2);
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", ""); // 51
appendRow(&chunk, "C", ""); // 52 -
appendRow(&chunk, "", "D"); // 53 +
appendRow(&chunk, "", "E"); // 54
appendRow(&chunk, "F", "F"); // 55
rightLines = QStringList {
"A",
"B",
"",
"D",
"F",
};
QTest::newRow("stage selected added/removed") << chunk << rightLines << ChunkSelection(2, 2);
chunk = createChunk();
appendRow(&chunk, "A", "A"); // 50
appendRow(&chunk, "B", "C"); // 51 -/+
appendRow(&chunk, "D", "D"); // 52
rightLines = QStringList {
"A",
"C",
"D",
};
QTest::newRow("stage modified row") << chunk << rightLines << ChunkSelection(1, 1);
}
void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch()
{
QFETCH(ChunkData, chunk);
QFETCH(QStringList, rightLines);
QFETCH(ChunkSelection, selection);
ChunkData result = DiffEditorDocument::filterChunk(chunk, selection, false);
QCOMPARE(result.rows.size(), rightLines.size());
for (int i = 0; i < rightLines.size(); ++i) {
QCOMPARE(result.rows.at(i).rightLine.text, rightLines.at(i));
}
}
#endif // WITH_TESTS #endif // WITH_TESTS
#include "diffeditorplugin.moc" #include "diffeditorplugin.moc"

View File

@@ -73,6 +73,8 @@ private slots:
void testMakePatch(); void testMakePatch();
void testReadPatch_data(); void testReadPatch_data();
void testReadPatch(); void testReadPatch();
void testFilterPatch_data();
void testFilterPatch();
#endif // WITH_TESTS #endif // WITH_TESTS
}; };

View File

@@ -158,7 +158,7 @@ void DiffEditorWidgetController::patch(bool revert, int fileIndex, int chunkInde
if (patchBehaviour == DiffFileInfo::PatchFile) { if (patchBehaviour == DiffFileInfo::PatchFile) {
const int strip = m_document->baseDirectory().isEmpty() ? -1 : 0; const int strip = m_document->baseDirectory().isEmpty() ? -1 : 0;
const QString patch = m_document->makePatch(fileIndex, chunkIndex, revert); const QString patch = m_document->makePatch(fileIndex, chunkIndex, ChunkSelection(), revert);
if (patch.isEmpty()) if (patch.isEmpty())
return; return;
@@ -183,8 +183,9 @@ void DiffEditorWidgetController::patch(bool revert, int fileIndex, int chunkInde
const QString contentsCopyFileName = contentsCopy.fileName(); const QString contentsCopyFileName = contentsCopy.fileName();
const QString contentsCopyDir = QFileInfo(contentsCopyFileName).absolutePath(); const QString contentsCopyDir = QFileInfo(contentsCopyFileName).absolutePath();
const QString patch = m_document->makePatch(fileIndex, const QString patch = m_document->makePatch(fileIndex, chunkIndex,
chunkIndex, revert, false, QFileInfo(contentsCopyFileName).fileName()); ChunkSelection(), revert, false,
QFileInfo(contentsCopyFileName).fileName());
if (patch.isEmpty()) if (patch.isEmpty())
return; return;
@@ -244,6 +245,24 @@ bool DiffEditorWidgetController::chunkExists(int fileIndex, int chunkIndex) cons
return false; return false;
} }
ChunkData DiffEditorWidgetController::chunkData(int fileIndex, int chunkIndex) const
{
if (!m_document)
return ChunkData();
if (fileIndex < 0 || chunkIndex < 0)
return ChunkData();
if (fileIndex >= m_contextFileData.count())
return ChunkData();
const FileData fileData = m_contextFileData.at(fileIndex);
if (chunkIndex >= fileData.chunks.count())
return ChunkData();
return fileData.chunks.at(chunkIndex);
}
bool DiffEditorWidgetController::fileNamesAreDifferent(int fileIndex) const bool DiffEditorWidgetController::fileNamesAreDifferent(int fileIndex) const
{ {
const FileData fileData = m_contextFileData.at(fileIndex); const FileData fileData = m_contextFileData.at(fileIndex);
@@ -268,10 +287,11 @@ void DiffEditorWidgetController::addRevertAction(QMenu *menu, int fileIndex, int
revertAction->setEnabled(chunkExists(fileIndex, chunkIndex)); revertAction->setEnabled(chunkExists(fileIndex, chunkIndex));
} }
void DiffEditorWidgetController::addExtraActions(QMenu *menu, int fileIndex, int chunkIndex) void DiffEditorWidgetController::addExtraActions(QMenu *menu, int fileIndex, int chunkIndex,
const ChunkSelection &selection)
{ {
if (DiffEditorController *controller = m_document->controller()) if (DiffEditorController *controller = m_document->controller())
controller->requestChunkActions(menu, fileIndex, chunkIndex); controller->requestChunkActions(menu, fileIndex, chunkIndex, selection);
} }
void DiffEditorWidgetController::sendChunkToCodePaster(int fileIndex, int chunkIndex) void DiffEditorWidgetController::sendChunkToCodePaster(int fileIndex, int chunkIndex)
@@ -283,7 +303,8 @@ void DiffEditorWidgetController::sendChunkToCodePaster(int fileIndex, int chunkI
auto pasteService = ExtensionSystem::PluginManager::getObject<CodePaster::Service>(); auto pasteService = ExtensionSystem::PluginManager::getObject<CodePaster::Service>();
QTC_ASSERT(pasteService, return); QTC_ASSERT(pasteService, return);
const QString patch = m_document->makePatch(fileIndex, chunkIndex, false); const QString patch = m_document->makePatch(fileIndex, chunkIndex,
ChunkSelection(), false);
if (patch.isEmpty()) if (patch.isEmpty())
return; return;

View File

@@ -39,6 +39,8 @@ namespace Utils { class ProgressIndicator; }
namespace DiffEditor { namespace DiffEditor {
class ChunkSelection;
namespace Internal { namespace Internal {
class DiffEditorDocument; class DiffEditorDocument;
@@ -58,7 +60,9 @@ public:
void addCodePasterAction(QMenu *menu, int fileIndex, int chunkIndex); void addCodePasterAction(QMenu *menu, int fileIndex, int chunkIndex);
void addApplyAction(QMenu *menu, int fileIndex, int chunkIndex); void addApplyAction(QMenu *menu, int fileIndex, int chunkIndex);
void addRevertAction(QMenu *menu, int fileIndex, int chunkIndex); void addRevertAction(QMenu *menu, int fileIndex, int chunkIndex);
void addExtraActions(QMenu *menu, int fileIndex, int chunkIndex); void addExtraActions(QMenu *menu, int fileIndex, int chunkIndex, const ChunkSelection &selection);
ChunkData chunkData(int fileIndex, int chunkIndex) const;
bool m_ignoreCurrentIndexChange = false; bool m_ignoreCurrentIndexChange = false;
QList<FileData> m_contextFileData; // ultimate data to be shown QList<FileData> m_contextFileData; // ultimate data to be shown

View File

@@ -99,6 +99,15 @@ public:
bool contextChunk = false; bool contextChunk = false;
}; };
class DIFFEDITOR_EXPORT ChunkSelection {
public:
ChunkSelection() {}
ChunkSelection(int s, int c) : startRow(s), selectedRowsCount(c) {}
bool isNull() const { return selectedRowsCount <= 0; }
int startRow = -1;
int selectedRowsCount = 0;
};
class DIFFEDITOR_EXPORT FileData { class DIFFEDITOR_EXPORT FileData {
public: public:
enum FileOperation { enum FileOperation {

View File

@@ -79,6 +79,8 @@ public:
int blockNumberForFileIndex(int fileIndex) const; int blockNumberForFileIndex(int fileIndex) const;
int fileIndexForBlockNumber(int blockNumber) const; int fileIndexForBlockNumber(int blockNumber) const;
int chunkIndexForBlockNumber(int blockNumber) const; int chunkIndexForBlockNumber(int blockNumber) const;
int chunkRowForBlockNumber(int blockNumber) const;
int chunkRowsCountForBlockNumber(int blockNumber) const;
bool isChunkLine(int blockNumber) const { bool isChunkLine(int blockNumber) const {
return m_skippedLines.contains(blockNumber); return m_skippedLines.contains(blockNumber);
} }
@@ -97,7 +99,8 @@ signals:
int columnNumber); int columnNumber);
void contextMenuRequested(QMenu *menu, void contextMenuRequested(QMenu *menu,
int diffFileIndex, int diffFileIndex,
int chunkIndex); int chunkIndex,
const ChunkSelection &selection);
void foldChanged(int blockNumber, bool folded); void foldChanged(int blockNumber, bool folded);
void gotDisplaySettings(); void gotDisplaySettings();
void gotFocus(); void gotFocus();
@@ -355,6 +358,40 @@ int SideDiffEditorWidget::chunkIndexForBlockNumber(int blockNumber) const
return -1; return -1;
} }
int SideDiffEditorWidget::chunkRowForBlockNumber(int blockNumber) const
{
if (m_chunkInfo.isEmpty())
return -1;
auto it = m_chunkInfo.upperBound(blockNumber);
if (it == m_chunkInfo.constBegin())
return -1;
--it;
if (blockNumber < it.key() + it.value().first)
return blockNumber - it.key();
return -1;
}
int SideDiffEditorWidget::chunkRowsCountForBlockNumber(int blockNumber) const
{
if (m_chunkInfo.isEmpty())
return -1;
auto it = m_chunkInfo.upperBound(blockNumber);
if (it == m_chunkInfo.constBegin())
return -1;
--it;
if (blockNumber < it.key() + it.value().first)
return it.value().first;
return -1;
}
void SideDiffEditorWidget::clearAll(const QString &message) void SideDiffEditorWidget::clearAll(const QString &message)
{ {
setBlockSelection(false); setBlockSelection(false);
@@ -446,11 +483,40 @@ void SideDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
{ {
QPointer<QMenu> menu = createStandardContextMenu(); QPointer<QMenu> menu = createStandardContextMenu();
const QTextCursor tc = textCursor();
QTextCursor start = tc;
start.setPosition(tc.selectionStart());
QTextCursor end = tc;
end.setPosition(tc.selectionEnd());
const int startBlockNumber = start.blockNumber();
const int endBlockNumber = end.blockNumber();
QTextCursor cursor = cursorForPosition(e->pos()); QTextCursor cursor = cursorForPosition(e->pos());
const int blockNumber = cursor.blockNumber(); const int blockNumber = cursor.blockNumber();
const int fileIndex = fileIndexForBlockNumber(blockNumber);
const int chunkIndex = chunkIndexForBlockNumber(blockNumber);
const int selectionStartFileIndex = fileIndexForBlockNumber(startBlockNumber);
const int selectionStartChunkIndex = chunkIndexForBlockNumber(startBlockNumber);
const int selectionEndFileIndex = fileIndexForBlockNumber(endBlockNumber);
const int selectionEndChunkIndex = chunkIndexForBlockNumber(endBlockNumber);
const int selectionStart = selectionStartFileIndex == fileIndex
&& selectionStartChunkIndex == chunkIndex
? chunkRowForBlockNumber(startBlockNumber)
: 0;
const int selectionEnd = selectionEndFileIndex == fileIndex
&& selectionEndChunkIndex == chunkIndex
? chunkRowForBlockNumber(endBlockNumber)
: chunkRowsCountForBlockNumber(blockNumber);
const ChunkSelection selection(selectionStart, selectionEnd - selectionStart + 1);
emit contextMenuRequested(menu, fileIndexForBlockNumber(blockNumber), emit contextMenuRequested(menu, fileIndexForBlockNumber(blockNumber),
chunkIndexForBlockNumber(blockNumber)); chunkIndexForBlockNumber(blockNumber),
selection);
connect(this, &SideDiffEditorWidget::destroyed, menu.data(), &QMenu::deleteLater); connect(this, &SideDiffEditorWidget::destroyed, menu.data(), &QMenu::deleteLater);
menu->exec(e->globalPos()); menu->exec(e->globalPos());
@@ -1067,24 +1133,26 @@ void SideBySideDiffEditorWidget::slotRightJumpToOriginalFileRequested(
void SideBySideDiffEditorWidget::slotLeftContextMenuRequested(QMenu *menu, void SideBySideDiffEditorWidget::slotLeftContextMenuRequested(QMenu *menu,
int fileIndex, int fileIndex,
int chunkIndex) int chunkIndex,
const ChunkSelection &selection)
{ {
menu->addSeparator(); menu->addSeparator();
m_controller.addCodePasterAction(menu, fileIndex, chunkIndex); m_controller.addCodePasterAction(menu, fileIndex, chunkIndex);
m_controller.addApplyAction(menu, fileIndex, chunkIndex); m_controller.addApplyAction(menu, fileIndex, chunkIndex);
m_controller.addExtraActions(menu, fileIndex, chunkIndex); m_controller.addExtraActions(menu, fileIndex, chunkIndex, selection);
} }
void SideBySideDiffEditorWidget::slotRightContextMenuRequested(QMenu *menu, void SideBySideDiffEditorWidget::slotRightContextMenuRequested(QMenu *menu,
int fileIndex, int fileIndex,
int chunkIndex) int chunkIndex,
const ChunkSelection &selection)
{ {
menu->addSeparator(); menu->addSeparator();
m_controller.addCodePasterAction(menu, fileIndex, chunkIndex); m_controller.addCodePasterAction(menu, fileIndex, chunkIndex);
m_controller.addRevertAction(menu, fileIndex, chunkIndex); m_controller.addRevertAction(menu, fileIndex, chunkIndex);
m_controller.addExtraActions(menu, fileIndex, chunkIndex); m_controller.addExtraActions(menu, fileIndex, chunkIndex, selection);
} }
void SideBySideDiffEditorWidget::leftVSliderChanged() void SideBySideDiffEditorWidget::leftVSliderChanged()

View File

@@ -85,9 +85,9 @@ private:
void slotRightJumpToOriginalFileRequested(int diffFileIndex, void slotRightJumpToOriginalFileRequested(int diffFileIndex,
int lineNumber, int columnNumber); int lineNumber, int columnNumber);
void slotLeftContextMenuRequested(QMenu *menu, int fileIndex, void slotLeftContextMenuRequested(QMenu *menu, int fileIndex,
int chunkIndex); int chunkIndex, const ChunkSelection &selection);
void slotRightContextMenuRequested(QMenu *menu, int fileIndex, void slotRightContextMenuRequested(QMenu *menu, int fileIndex,
int chunkIndex); int chunkIndex, const ChunkSelection &selection);
void leftVSliderChanged(); void leftVSliderChanged();
void rightVSliderChanged(); void rightVSliderChanged();
void leftHSliderChanged(); void leftHSliderChanged();

View File

@@ -178,11 +178,57 @@ void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
{ {
QPointer<QMenu> menu = createStandardContextMenu(); QPointer<QMenu> menu = createStandardContextMenu();
const QTextCursor tc = textCursor();
QTextCursor start = tc;
start.setPosition(tc.selectionStart());
QTextCursor end = tc;
end.setPosition(tc.selectionEnd());
const int startBlockNumber = start.blockNumber();
const int endBlockNumber = end.blockNumber();
QTextCursor cursor = cursorForPosition(e->pos()); QTextCursor cursor = cursorForPosition(e->pos());
const int blockNumber = cursor.blockNumber(); const int blockNumber = cursor.blockNumber();
const int fileIndex = fileIndexForBlockNumber(blockNumber);
const int chunkIndex = chunkIndexForBlockNumber(blockNumber);
const ChunkData chunkData = m_controller.chunkData(fileIndex, chunkIndex);
int selectionStart = -1;
int selectionEnd = -1;
for (int i = startBlockNumber; i <= endBlockNumber; ++i) {
const int currentFileIndex = fileIndexForBlockNumber(i);
if (currentFileIndex < fileIndex)
continue;
if (currentFileIndex > fileIndex)
break;
const int currentChunkIndex = chunkIndexForBlockNumber(i);
if (currentChunkIndex < chunkIndex)
continue;
if (currentChunkIndex > chunkIndex)
break;
const int leftRow = m_leftLineNumbers.value(i, qMakePair(-1, -1)).second;
const int rightRow = m_rightLineNumbers.value(i, qMakePair(-1, -1)).second;
const int row = leftRow >= 0 ? leftRow : rightRow;
if (row < 0)
continue;
if (selectionStart < 0 || selectionStart > row)
selectionStart = row;
if (selectionEnd < 0 || selectionEnd < row)
selectionEnd = row;
}
const ChunkSelection selection(selectionStart, selectionEnd - selectionStart + 1);
addContextMenuActions(menu, fileIndexForBlockNumber(blockNumber), addContextMenuActions(menu, fileIndexForBlockNumber(blockNumber),
chunkIndexForBlockNumber(blockNumber)); chunkIndexForBlockNumber(blockNumber), selection);
connect(this, &UnifiedDiffEditorWidget::destroyed, menu.data(), &QMenu::deleteLater); connect(this, &UnifiedDiffEditorWidget::destroyed, menu.data(), &QMenu::deleteLater);
menu->exec(e->globalPos()); menu->exec(e->globalPos());
@@ -191,14 +237,15 @@ void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
void UnifiedDiffEditorWidget::addContextMenuActions(QMenu *menu, void UnifiedDiffEditorWidget::addContextMenuActions(QMenu *menu,
int fileIndex, int fileIndex,
int chunkIndex) int chunkIndex,
const ChunkSelection &selection)
{ {
menu->addSeparator(); menu->addSeparator();
m_controller.addCodePasterAction(menu, fileIndex, chunkIndex); m_controller.addCodePasterAction(menu, fileIndex, chunkIndex);
m_controller.addApplyAction(menu, fileIndex, chunkIndex); m_controller.addApplyAction(menu, fileIndex, chunkIndex);
m_controller.addRevertAction(menu, fileIndex, chunkIndex); m_controller.addRevertAction(menu, fileIndex, chunkIndex);
m_controller.addExtraActions(menu, fileIndex, chunkIndex); m_controller.addExtraActions(menu, fileIndex, chunkIndex, selection);
} }
void UnifiedDiffEditorWidget::clear(const QString &message) void UnifiedDiffEditorWidget::clear(const QString &message)
@@ -228,7 +275,7 @@ QString UnifiedDiffEditorWidget::lineNumber(int blockNumber) const
if (leftLineExists || rightLineExists) { if (leftLineExists || rightLineExists) {
const QString leftLine = leftLineExists const QString leftLine = leftLineExists
? QString::number(m_leftLineNumbers.value(blockNumber)) ? QString::number(m_leftLineNumbers.value(blockNumber).first)
: QString(); : QString();
lineNumberString += QString(m_leftLineNumberDigits - leftLine.count(), lineNumberString += QString(m_leftLineNumberDigits - leftLine.count(),
' ') + leftLine; ' ') + leftLine;
@@ -236,7 +283,7 @@ QString UnifiedDiffEditorWidget::lineNumber(int blockNumber) const
lineNumberString += '|'; lineNumberString += '|';
const QString rightLine = rightLineExists const QString rightLine = rightLineExists
? QString::number(m_rightLineNumbers.value(blockNumber)) ? QString::number(m_rightLineNumbers.value(blockNumber).first)
: QString(); : QString();
lineNumberString += QString(m_rightLineNumberDigits - rightLine.count(), lineNumberString += QString(m_rightLineNumberDigits - rightLine.count(),
' ') + rightLine; ' ') + rightLine;
@@ -249,18 +296,20 @@ int UnifiedDiffEditorWidget::lineNumberDigits() const
return m_leftLineNumberDigits + m_rightLineNumberDigits + 1; return m_leftLineNumberDigits + m_rightLineNumberDigits + 1;
} }
void UnifiedDiffEditorWidget::setLeftLineNumber(int blockNumber, int lineNumber) void UnifiedDiffEditorWidget::setLeftLineNumber(int blockNumber, int lineNumber,
int rowNumberInChunk)
{ {
const QString lineNumberString = QString::number(lineNumber); const QString lineNumberString = QString::number(lineNumber);
m_leftLineNumbers.insert(blockNumber, lineNumber); m_leftLineNumbers.insert(blockNumber, qMakePair(lineNumber, rowNumberInChunk));
m_leftLineNumberDigits = qMax(m_leftLineNumberDigits, m_leftLineNumberDigits = qMax(m_leftLineNumberDigits,
lineNumberString.count()); lineNumberString.count());
} }
void UnifiedDiffEditorWidget::setRightLineNumber(int blockNumber, int lineNumber) void UnifiedDiffEditorWidget::setRightLineNumber(int blockNumber, int lineNumber,
int rowNumberInChunk)
{ {
const QString lineNumberString = QString::number(lineNumber); const QString lineNumberString = QString::number(lineNumber);
m_rightLineNumbers.insert(blockNumber, lineNumber); m_rightLineNumbers.insert(blockNumber, qMakePair(lineNumber, rowNumberInChunk));
m_rightLineNumberDigits = qMax(m_rightLineNumberDigits, m_rightLineNumberDigits = qMax(m_rightLineNumberDigits,
lineNumberString.count()); lineNumberString.count());
} }
@@ -307,6 +356,7 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
int blockCount = 0; int blockCount = 0;
int charCount = 0; int charCount = 0;
QList<TextLineData> leftBuffer, rightBuffer; QList<TextLineData> leftBuffer, rightBuffer;
QList<int> leftRowsBuffer, rightRowsBuffer;
(*selections)[*blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat)); (*selections)[*blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat));
@@ -356,7 +406,8 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
if (!line.isEmpty()) { if (!line.isEmpty()) {
setLeftLineNumber(*blockNumber + blockCount + 1, setLeftLineNumber(*blockNumber + blockCount + 1,
chunkData.leftStartingLineNumber chunkData.leftStartingLineNumber
+ leftLineCount + 1); + leftLineCount + 1,
leftRowsBuffer.at(j));
blockCount += blockDelta; blockCount += blockDelta;
++leftLineCount; ++leftLineCount;
} }
@@ -366,6 +417,7 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
charCount += line.count(); charCount += line.count();
} }
leftBuffer.clear(); leftBuffer.clear();
leftRowsBuffer.clear();
} }
if (rightBuffer.count()) { if (rightBuffer.count()) {
for (int j = 0; j < rightBuffer.count(); j++) { for (int j = 0; j < rightBuffer.count(); j++) {
@@ -396,7 +448,8 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
if (!line.isEmpty()) { if (!line.isEmpty()) {
setRightLineNumber(*blockNumber + blockCount + 1, setRightLineNumber(*blockNumber + blockCount + 1,
chunkData.rightStartingLineNumber chunkData.rightStartingLineNumber
+ rightLineCount + 1); + rightLineCount + 1,
rightRowsBuffer.at(j));
blockCount += blockDelta; blockCount += blockDelta;
++rightLineCount; ++rightLineCount;
} }
@@ -406,6 +459,7 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
charCount += line.count(); charCount += line.count();
} }
rightBuffer.clear(); rightBuffer.clear();
rightRowsBuffer.clear();
} }
if (i < chunkData.rows.count()) { if (i < chunkData.rows.count()) {
const QString line = DiffUtils::makePatchLine(' ', const QString line = DiffUtils::makePatchLine(' ',
@@ -416,10 +470,12 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
if (!line.isEmpty()) { if (!line.isEmpty()) {
setLeftLineNumber(*blockNumber + blockCount + 1, setLeftLineNumber(*blockNumber + blockCount + 1,
chunkData.leftStartingLineNumber chunkData.leftStartingLineNumber
+ leftLineCount + 1); + leftLineCount + 1,
i);
setRightLineNumber(*blockNumber + blockCount + 1, setRightLineNumber(*blockNumber + blockCount + 1,
chunkData.rightStartingLineNumber chunkData.rightStartingLineNumber
+ rightLineCount + 1); + rightLineCount + 1,
i);
blockCount += line.count('\n'); blockCount += line.count('\n');
++leftLineCount; ++leftLineCount;
++rightLineCount; ++rightLineCount;
@@ -430,10 +486,14 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData,
charCount += line.count(); charCount += line.count();
} }
} else { } else {
if (rowData.leftLine.textLineType == TextLineData::TextLine) if (rowData.leftLine.textLineType == TextLineData::TextLine) {
leftBuffer.append(rowData.leftLine); leftBuffer.append(rowData.leftLine);
if (rowData.rightLine.textLineType == TextLineData::TextLine) leftRowsBuffer.append(i);
}
if (rowData.rightLine.textLineType == TextLineData::TextLine) {
rightBuffer.append(rowData.rightLine); rightBuffer.append(rowData.rightLine);
rightRowsBuffer.append(i);
}
} }
} }
@@ -584,13 +644,13 @@ void UnifiedDiffEditorWidget::jumpToOriginalFile(const QTextCursor &cursor)
const int columnNumber = cursor.positionInBlock() - 1; // -1 for the first character in line const int columnNumber = cursor.positionInBlock() - 1; // -1 for the first character in line
const int rightLineNumber = m_rightLineNumbers.value(blockNumber, -1); const int rightLineNumber = m_rightLineNumbers.value(blockNumber, qMakePair(-1, 0)).first;
if (rightLineNumber >= 0) { if (rightLineNumber >= 0) {
m_controller.jumpToOriginalFile(rightFileName, rightLineNumber, columnNumber); m_controller.jumpToOriginalFile(rightFileName, rightLineNumber, columnNumber);
return; return;
} }
const int leftLineNumber = m_leftLineNumbers.value(blockNumber, -1); const int leftLineNumber = m_leftLineNumbers.value(blockNumber, qMakePair(-1, 0)).first;
if (leftLineNumber >= 0) { if (leftLineNumber >= 0) {
if (leftFileName == rightFileName) { if (leftFileName == rightFileName) {
for (const ChunkData &chunkData : fileData.chunks) { for (const ChunkData &chunkData : fileData.chunks) {

View File

@@ -39,6 +39,7 @@ namespace DiffEditor {
class ChunkData; class ChunkData;
class FileData; class FileData;
class ChunkSelection;
namespace Internal { namespace Internal {
@@ -79,8 +80,8 @@ private:
void slotCursorPositionChangedInEditor(); void slotCursorPositionChangedInEditor();
void setLeftLineNumber(int blockNumber, int lineNumber); void setLeftLineNumber(int blockNumber, int lineNumber, int rowNumberInChunk);
void setRightLineNumber(int blockNumber, int lineNumber); void setRightLineNumber(int blockNumber, int lineNumber, int rowNumberInChunk);
void setFileInfo(int blockNumber, void setFileInfo(int blockNumber,
const DiffFileInfo &leftFileInfo, const DiffFileInfo &leftFileInfo,
const DiffFileInfo &rightFileInfo); const DiffFileInfo &rightFileInfo);
@@ -97,11 +98,12 @@ private:
void jumpToOriginalFile(const QTextCursor &cursor); void jumpToOriginalFile(const QTextCursor &cursor);
void addContextMenuActions(QMenu *menu, void addContextMenuActions(QMenu *menu,
int fileIndex, int fileIndex,
int chunkIndex); int chunkIndex,
const ChunkSelection &selection);
// block number, visual line number. // block number, visual line number, chunk row number
QMap<int, int> m_leftLineNumbers; QMap<int, QPair<int, int> > m_leftLineNumbers;
QMap<int, int> m_rightLineNumbers; QMap<int, QPair<int, int> > m_rightLineNumbers;
DiffEditorWidgetController m_controller; DiffEditorWidgetController m_controller;

View File

@@ -852,38 +852,58 @@ QTextCodec *GitClient::codecFor(GitClient::CodecType codecType, const QString &s
return nullptr; return nullptr;
} }
void GitClient::chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex) void GitClient::chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex,
const DiffEditor::ChunkSelection &selection)
{ {
QPointer<DiffEditor::DiffEditorController> diffController QPointer<DiffEditor::DiffEditorController> diffController
= qobject_cast<DiffEditorController *>(sender()); = qobject_cast<DiffEditorController *>(sender());
auto stageChunk = [this](QPointer<DiffEditor::DiffEditorController> diffController, auto stageChunk = [this](QPointer<DiffEditor::DiffEditorController> diffController,
int fileIndex, int chunkIndex, bool revert) { int fileIndex, int chunkIndex, DiffEditorController::PatchOptions options,
const DiffEditor::ChunkSelection &selection) {
if (diffController.isNull()) if (diffController.isNull())
return; return;
DiffEditorController::PatchOptions options = DiffEditorController::AddPrefix; options |= DiffEditorController::AddPrefix;
if (revert) const QString patch = diffController->makePatch(fileIndex, chunkIndex, selection, options);
options |= DiffEditorController::Revert; stage(diffController, patch, options & Revert);
const QString patch = diffController->makePatch(fileIndex, chunkIndex, options);
stage(diffController, patch, revert);
}; };
menu->addSeparator(); menu->addSeparator();
QAction *stageChunkAction = menu->addAction(tr("Stage Chunk")); QAction *stageChunkAction = menu->addAction(tr("Stage Chunk"));
connect(stageChunkAction, &QAction::triggered, this, connect(stageChunkAction, &QAction::triggered, this,
[stageChunk, diffController, fileIndex, chunkIndex]() { [stageChunk, diffController, fileIndex, chunkIndex]() {
stageChunk(diffController, fileIndex, chunkIndex, false); stageChunk(diffController, fileIndex, chunkIndex,
DiffEditorController::NoOption, DiffEditor::ChunkSelection());
});
QAction *stageLinesAction = menu->addAction(tr("Stage %n Line(s)", "", selection.selectedRowsCount));
connect(stageLinesAction, &QAction::triggered, this,
[stageChunk, diffController, fileIndex, chunkIndex, selection]() {
stageChunk(diffController, fileIndex, chunkIndex,
DiffEditorController::NoOption, selection);
}); });
QAction *unstageChunkAction = menu->addAction(tr("Unstage Chunk")); QAction *unstageChunkAction = menu->addAction(tr("Unstage Chunk"));
connect(unstageChunkAction, &QAction::triggered, this, connect(unstageChunkAction, &QAction::triggered, this,
[stageChunk, diffController, fileIndex, chunkIndex]() { [stageChunk, diffController, fileIndex, chunkIndex]() {
stageChunk(diffController, fileIndex, chunkIndex, true); stageChunk(diffController, fileIndex, chunkIndex,
DiffEditorController::Revert, DiffEditor::ChunkSelection());
}); });
QAction *unstageLinesAction = menu->addAction(tr("Unstage %n Line(s)", "", selection.selectedRowsCount));
connect(unstageLinesAction, &QAction::triggered, this,
[stageChunk, diffController, fileIndex, chunkIndex, selection]() {
stageChunk(diffController, fileIndex, chunkIndex,
DiffEditorController::Revert,
selection);
});
if (selection.isNull()) {
stageLinesAction->setVisible(false);
unstageLinesAction->setVisible(false);
}
if (!diffController || !diffController->chunkExists(fileIndex, chunkIndex)) { if (!diffController || !diffController->chunkExists(fileIndex, chunkIndex)) {
stageChunkAction->setEnabled(false); stageChunkAction->setEnabled(false);
stageLinesAction->setEnabled(false);
unstageChunkAction->setEnabled(false); unstageChunkAction->setEnabled(false);
unstageLinesAction->setEnabled(false);
} }
} }

View File

@@ -54,6 +54,7 @@ namespace VcsBase {
} }
namespace DiffEditor { namespace DiffEditor {
class ChunkSelection;
class DiffEditorController; class DiffEditorController;
} }
@@ -333,7 +334,8 @@ public:
private: private:
void finishSubmoduleUpdate(); void finishSubmoduleUpdate();
void chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex); void chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex,
const DiffEditor::ChunkSelection &selection);
void stage(DiffEditor::DiffEditorController *diffController, void stage(DiffEditor::DiffEditorController *diffController,
const QString &patch, bool revert); const QString &patch, bool revert);