From 17668329181be771669c10baa0e322e0192fecdb Mon Sep 17 00:00:00 2001 From: Andre Hartmann Date: Wed, 29 Nov 2017 21:36:30 +0100 Subject: [PATCH] DiffEditor: Stage and unstage selected lines for Git MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: QTCREATORBUG-19071 Change-Id: I560ba208e68e477ea865e499847d819cfdfeb6f3 Reviewed-by: Orgad Shaneh Reviewed-by: André Hartmann --- .../diffeditor/diffeditorcontroller.cpp | 8 +- src/plugins/diffeditor/diffeditorcontroller.h | 11 +- src/plugins/diffeditor/diffeditordocument.cpp | 34 ++++- src/plugins/diffeditor/diffeditordocument.h | 5 +- src/plugins/diffeditor/diffeditorplugin.cpp | 135 ++++++++++++++++++ src/plugins/diffeditor/diffeditorplugin.h | 2 + .../diffeditor/diffeditorwidgetcontroller.cpp | 33 ++++- .../diffeditor/diffeditorwidgetcontroller.h | 6 +- src/plugins/diffeditor/diffutils.h | 9 ++ .../diffeditor/sidebysidediffeditorwidget.cpp | 80 ++++++++++- .../diffeditor/sidebysidediffeditorwidget.h | 4 +- .../diffeditor/unifieddiffeditorwidget.cpp | 94 +++++++++--- .../diffeditor/unifieddiffeditorwidget.h | 14 +- src/plugins/git/gitclient.cpp | 40 ++++-- src/plugins/git/gitclient.h | 4 +- 15 files changed, 422 insertions(+), 57 deletions(-) diff --git a/src/plugins/diffeditor/diffeditorcontroller.cpp b/src/plugins/diffeditor/diffeditorcontroller.cpp index 0a82110bbe8..7ca1a4694b2 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.cpp +++ b/src/plugins/diffeditor/diffeditorcontroller.cpp @@ -71,9 +71,10 @@ bool DiffEditorController::ignoreWhitespace() const } QString DiffEditorController::makePatch(int fileIndex, int chunkIndex, + const ChunkSelection &selection, PatchOptions options) const { - return m_document->makePatch(fileIndex, chunkIndex, + return m_document->makePatch(fileIndex, chunkIndex, selection, options & Revert, options & AddPrefix); } @@ -143,9 +144,10 @@ void DiffEditorController::reloadFinished(bool success) 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 diff --git a/src/plugins/diffeditor/diffeditorcontroller.h b/src/plugins/diffeditor/diffeditorcontroller.h index b32537c0e45..ef8f2092543 100644 --- a/src/plugins/diffeditor/diffeditorcontroller.h +++ b/src/plugins/diffeditor/diffeditorcontroller.h @@ -38,6 +38,8 @@ namespace DiffEditor { namespace Internal { class DiffEditorDocument; } +class ChunkSelection; + class DIFFEDITOR_EXPORT DiffEditorController : public QObject { Q_OBJECT @@ -58,18 +60,21 @@ public: AddPrefix = 2 }; 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, const QString &displayName); 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; Core::IDocument *document() const; signals: - void chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex); + void chunkActionsRequested(QMenu *menu, int fileIndex, int chunkIndex, + const ChunkSelection &selection); protected: // reloadFinished() should be called diff --git a/src/plugins/diffeditor/diffeditordocument.cpp b/src/plugins/diffeditor/diffeditordocument.cpp index b38235adbe6..529dac382e8 100644 --- a/src/plugins/diffeditor/diffeditordocument.cpp +++ b/src/plugins/diffeditor/diffeditordocument.cpp @@ -76,7 +76,39 @@ DiffEditorController *DiffEditorDocument::controller() const 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, + const ChunkSelection &selection, bool revert, bool addPrefix, const QString &overriddenFileName) const { @@ -90,7 +122,7 @@ QString DiffEditorDocument::makePatch(int fileIndex, int chunkIndex, if (chunkIndex >= fileData.chunks.count()) 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 QString fileName = !overriddenFileName.isEmpty() diff --git a/src/plugins/diffeditor/diffeditordocument.h b/src/plugins/diffeditor/diffeditordocument.h index 42657e43f38..dce11062604 100644 --- a/src/plugins/diffeditor/diffeditordocument.h +++ b/src/plugins/diffeditor/diffeditordocument.h @@ -34,6 +34,7 @@ QT_FORWARD_DECLARE_CLASS(QMenu) namespace DiffEditor { class DiffEditorController; +class ChunkSelection; namespace Internal { @@ -52,7 +53,9 @@ public: 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, const QString &overriddenFileName = QString()) const; diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp index 0c52b55a9e1..652c6e9d14f 100644 --- a/src/plugins/diffeditor/diffeditorplugin.cpp +++ b/src/plugins/diffeditor/diffeditorplugin.cpp @@ -610,6 +610,7 @@ void DiffEditorPlugin::diffExternalFiles() Q_DECLARE_METATYPE(DiffEditor::ChunkData) Q_DECLARE_METATYPE(DiffEditor::FileData) +Q_DECLARE_METATYPE(DiffEditor::ChunkSelection) 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("chunk"); + QTest::addColumn("rightLines"); + QTest::addColumn("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 #include "diffeditorplugin.moc" diff --git a/src/plugins/diffeditor/diffeditorplugin.h b/src/plugins/diffeditor/diffeditorplugin.h index 84d642b9f37..7ec3e2d392e 100644 --- a/src/plugins/diffeditor/diffeditorplugin.h +++ b/src/plugins/diffeditor/diffeditorplugin.h @@ -73,6 +73,8 @@ private slots: void testMakePatch(); void testReadPatch_data(); void testReadPatch(); + void testFilterPatch_data(); + void testFilterPatch(); #endif // WITH_TESTS }; diff --git a/src/plugins/diffeditor/diffeditorwidgetcontroller.cpp b/src/plugins/diffeditor/diffeditorwidgetcontroller.cpp index e1c656d5c74..c4f0ab5cb53 100644 --- a/src/plugins/diffeditor/diffeditorwidgetcontroller.cpp +++ b/src/plugins/diffeditor/diffeditorwidgetcontroller.cpp @@ -158,7 +158,7 @@ void DiffEditorWidgetController::patch(bool revert, int fileIndex, int chunkInde if (patchBehaviour == DiffFileInfo::PatchFile) { 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()) return; @@ -183,8 +183,9 @@ void DiffEditorWidgetController::patch(bool revert, int fileIndex, int chunkInde const QString contentsCopyFileName = contentsCopy.fileName(); const QString contentsCopyDir = QFileInfo(contentsCopyFileName).absolutePath(); - const QString patch = m_document->makePatch(fileIndex, - chunkIndex, revert, false, QFileInfo(contentsCopyFileName).fileName()); + const QString patch = m_document->makePatch(fileIndex, chunkIndex, + ChunkSelection(), revert, false, + QFileInfo(contentsCopyFileName).fileName()); if (patch.isEmpty()) return; @@ -244,6 +245,24 @@ bool DiffEditorWidgetController::chunkExists(int fileIndex, int chunkIndex) cons 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 { const FileData fileData = m_contextFileData.at(fileIndex); @@ -268,10 +287,11 @@ void DiffEditorWidgetController::addRevertAction(QMenu *menu, int fileIndex, int 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()) - controller->requestChunkActions(menu, fileIndex, chunkIndex); + controller->requestChunkActions(menu, fileIndex, chunkIndex, selection); } void DiffEditorWidgetController::sendChunkToCodePaster(int fileIndex, int chunkIndex) @@ -283,7 +303,8 @@ void DiffEditorWidgetController::sendChunkToCodePaster(int fileIndex, int chunkI auto pasteService = ExtensionSystem::PluginManager::getObject(); 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()) return; diff --git a/src/plugins/diffeditor/diffeditorwidgetcontroller.h b/src/plugins/diffeditor/diffeditorwidgetcontroller.h index c0c1be95486..4951ec1cb44 100644 --- a/src/plugins/diffeditor/diffeditorwidgetcontroller.h +++ b/src/plugins/diffeditor/diffeditorwidgetcontroller.h @@ -39,6 +39,8 @@ namespace Utils { class ProgressIndicator; } namespace DiffEditor { +class ChunkSelection; + namespace Internal { class DiffEditorDocument; @@ -58,7 +60,9 @@ public: void addCodePasterAction(QMenu *menu, int fileIndex, int chunkIndex); void addApplyAction(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; QList m_contextFileData; // ultimate data to be shown diff --git a/src/plugins/diffeditor/diffutils.h b/src/plugins/diffeditor/diffutils.h index 605266a0efe..47ae8c126ac 100644 --- a/src/plugins/diffeditor/diffutils.h +++ b/src/plugins/diffeditor/diffutils.h @@ -99,6 +99,15 @@ public: 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 { public: enum FileOperation { diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp index a2799e7ca06..ffe4b2bc14f 100644 --- a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp +++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp @@ -79,6 +79,8 @@ public: int blockNumberForFileIndex(int fileIndex) const; int fileIndexForBlockNumber(int blockNumber) const; int chunkIndexForBlockNumber(int blockNumber) const; + int chunkRowForBlockNumber(int blockNumber) const; + int chunkRowsCountForBlockNumber(int blockNumber) const; bool isChunkLine(int blockNumber) const { return m_skippedLines.contains(blockNumber); } @@ -97,7 +99,8 @@ signals: int columnNumber); void contextMenuRequested(QMenu *menu, int diffFileIndex, - int chunkIndex); + int chunkIndex, + const ChunkSelection &selection); void foldChanged(int blockNumber, bool folded); void gotDisplaySettings(); void gotFocus(); @@ -355,6 +358,40 @@ int SideDiffEditorWidget::chunkIndexForBlockNumber(int blockNumber) const 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) { setBlockSelection(false); @@ -446,11 +483,40 @@ void SideDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e) { QPointer 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()); 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), - chunkIndexForBlockNumber(blockNumber)); + chunkIndexForBlockNumber(blockNumber), + selection); connect(this, &SideDiffEditorWidget::destroyed, menu.data(), &QMenu::deleteLater); menu->exec(e->globalPos()); @@ -1067,24 +1133,26 @@ void SideBySideDiffEditorWidget::slotRightJumpToOriginalFileRequested( void SideBySideDiffEditorWidget::slotLeftContextMenuRequested(QMenu *menu, int fileIndex, - int chunkIndex) + int chunkIndex, + const ChunkSelection &selection) { menu->addSeparator(); m_controller.addCodePasterAction(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, int fileIndex, - int chunkIndex) + int chunkIndex, + const ChunkSelection &selection) { menu->addSeparator(); m_controller.addCodePasterAction(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() diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.h b/src/plugins/diffeditor/sidebysidediffeditorwidget.h index 5fbd253e7c3..14795cf0853 100644 --- a/src/plugins/diffeditor/sidebysidediffeditorwidget.h +++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.h @@ -85,9 +85,9 @@ private: void slotRightJumpToOriginalFileRequested(int diffFileIndex, int lineNumber, int columnNumber); void slotLeftContextMenuRequested(QMenu *menu, int fileIndex, - int chunkIndex); + int chunkIndex, const ChunkSelection &selection); void slotRightContextMenuRequested(QMenu *menu, int fileIndex, - int chunkIndex); + int chunkIndex, const ChunkSelection &selection); void leftVSliderChanged(); void rightVSliderChanged(); void leftHSliderChanged(); diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp index 83767e0a728..a383a55c15f 100644 --- a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp +++ b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp @@ -178,11 +178,57 @@ void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e) { QPointer 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()); 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), - chunkIndexForBlockNumber(blockNumber)); + chunkIndexForBlockNumber(blockNumber), selection); connect(this, &UnifiedDiffEditorWidget::destroyed, menu.data(), &QMenu::deleteLater); menu->exec(e->globalPos()); @@ -191,14 +237,15 @@ void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e) void UnifiedDiffEditorWidget::addContextMenuActions(QMenu *menu, int fileIndex, - int chunkIndex) + int chunkIndex, + const ChunkSelection &selection) { menu->addSeparator(); m_controller.addCodePasterAction(menu, fileIndex, chunkIndex); m_controller.addApplyAction(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) @@ -228,7 +275,7 @@ QString UnifiedDiffEditorWidget::lineNumber(int blockNumber) const if (leftLineExists || rightLineExists) { const QString leftLine = leftLineExists - ? QString::number(m_leftLineNumbers.value(blockNumber)) + ? QString::number(m_leftLineNumbers.value(blockNumber).first) : QString(); lineNumberString += QString(m_leftLineNumberDigits - leftLine.count(), ' ') + leftLine; @@ -236,7 +283,7 @@ QString UnifiedDiffEditorWidget::lineNumber(int blockNumber) const lineNumberString += '|'; const QString rightLine = rightLineExists - ? QString::number(m_rightLineNumbers.value(blockNumber)) + ? QString::number(m_rightLineNumbers.value(blockNumber).first) : QString(); lineNumberString += QString(m_rightLineNumberDigits - rightLine.count(), ' ') + rightLine; @@ -249,18 +296,20 @@ int UnifiedDiffEditorWidget::lineNumberDigits() const 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); - m_leftLineNumbers.insert(blockNumber, lineNumber); + m_leftLineNumbers.insert(blockNumber, qMakePair(lineNumber, rowNumberInChunk)); m_leftLineNumberDigits = qMax(m_leftLineNumberDigits, lineNumberString.count()); } -void UnifiedDiffEditorWidget::setRightLineNumber(int blockNumber, int lineNumber) +void UnifiedDiffEditorWidget::setRightLineNumber(int blockNumber, int lineNumber, + int rowNumberInChunk) { const QString lineNumberString = QString::number(lineNumber); - m_rightLineNumbers.insert(blockNumber, lineNumber); + m_rightLineNumbers.insert(blockNumber, qMakePair(lineNumber, rowNumberInChunk)); m_rightLineNumberDigits = qMax(m_rightLineNumberDigits, lineNumberString.count()); } @@ -307,6 +356,7 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, int blockCount = 0; int charCount = 0; QList leftBuffer, rightBuffer; + QList leftRowsBuffer, rightRowsBuffer; (*selections)[*blockNumber].append(DiffSelection(&m_controller.m_chunkLineFormat)); @@ -356,7 +406,8 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, if (!line.isEmpty()) { setLeftLineNumber(*blockNumber + blockCount + 1, chunkData.leftStartingLineNumber - + leftLineCount + 1); + + leftLineCount + 1, + leftRowsBuffer.at(j)); blockCount += blockDelta; ++leftLineCount; } @@ -366,6 +417,7 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, charCount += line.count(); } leftBuffer.clear(); + leftRowsBuffer.clear(); } if (rightBuffer.count()) { for (int j = 0; j < rightBuffer.count(); j++) { @@ -396,7 +448,8 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, if (!line.isEmpty()) { setRightLineNumber(*blockNumber + blockCount + 1, chunkData.rightStartingLineNumber - + rightLineCount + 1); + + rightLineCount + 1, + rightRowsBuffer.at(j)); blockCount += blockDelta; ++rightLineCount; } @@ -406,6 +459,7 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, charCount += line.count(); } rightBuffer.clear(); + rightRowsBuffer.clear(); } if (i < chunkData.rows.count()) { const QString line = DiffUtils::makePatchLine(' ', @@ -416,10 +470,12 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, if (!line.isEmpty()) { setLeftLineNumber(*blockNumber + blockCount + 1, chunkData.leftStartingLineNumber - + leftLineCount + 1); + + leftLineCount + 1, + i); setRightLineNumber(*blockNumber + blockCount + 1, chunkData.rightStartingLineNumber - + rightLineCount + 1); + + rightLineCount + 1, + i); blockCount += line.count('\n'); ++leftLineCount; ++rightLineCount; @@ -430,10 +486,14 @@ QString UnifiedDiffEditorWidget::showChunk(const ChunkData &chunkData, charCount += line.count(); } } else { - if (rowData.leftLine.textLineType == TextLineData::TextLine) + if (rowData.leftLine.textLineType == TextLineData::TextLine) { leftBuffer.append(rowData.leftLine); - if (rowData.rightLine.textLineType == TextLineData::TextLine) + leftRowsBuffer.append(i); + } + if (rowData.rightLine.textLineType == TextLineData::TextLine) { 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 rightLineNumber = m_rightLineNumbers.value(blockNumber, -1); + const int rightLineNumber = m_rightLineNumbers.value(blockNumber, qMakePair(-1, 0)).first; if (rightLineNumber >= 0) { m_controller.jumpToOriginalFile(rightFileName, rightLineNumber, columnNumber); return; } - const int leftLineNumber = m_leftLineNumbers.value(blockNumber, -1); + const int leftLineNumber = m_leftLineNumbers.value(blockNumber, qMakePair(-1, 0)).first; if (leftLineNumber >= 0) { if (leftFileName == rightFileName) { for (const ChunkData &chunkData : fileData.chunks) { diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.h b/src/plugins/diffeditor/unifieddiffeditorwidget.h index 087426e94e0..993d6cba20a 100644 --- a/src/plugins/diffeditor/unifieddiffeditorwidget.h +++ b/src/plugins/diffeditor/unifieddiffeditorwidget.h @@ -39,6 +39,7 @@ namespace DiffEditor { class ChunkData; class FileData; +class ChunkSelection; namespace Internal { @@ -79,8 +80,8 @@ private: void slotCursorPositionChangedInEditor(); - void setLeftLineNumber(int blockNumber, int lineNumber); - void setRightLineNumber(int blockNumber, int lineNumber); + void setLeftLineNumber(int blockNumber, int lineNumber, int rowNumberInChunk); + void setRightLineNumber(int blockNumber, int lineNumber, int rowNumberInChunk); void setFileInfo(int blockNumber, const DiffFileInfo &leftFileInfo, const DiffFileInfo &rightFileInfo); @@ -97,11 +98,12 @@ private: void jumpToOriginalFile(const QTextCursor &cursor); void addContextMenuActions(QMenu *menu, int fileIndex, - int chunkIndex); + int chunkIndex, + const ChunkSelection &selection); - // block number, visual line number. - QMap m_leftLineNumbers; - QMap m_rightLineNumbers; + // block number, visual line number, chunk row number + QMap > m_leftLineNumbers; + QMap > m_rightLineNumbers; DiffEditorWidgetController m_controller; diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index d06ac0e03b2..be0dc57ff5e 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -852,38 +852,58 @@ QTextCodec *GitClient::codecFor(GitClient::CodecType codecType, const QString &s 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 diffController = qobject_cast(sender()); auto stageChunk = [this](QPointer diffController, - int fileIndex, int chunkIndex, bool revert) { + int fileIndex, int chunkIndex, DiffEditorController::PatchOptions options, + const DiffEditor::ChunkSelection &selection) { if (diffController.isNull()) return; - DiffEditorController::PatchOptions options = DiffEditorController::AddPrefix; - if (revert) - options |= DiffEditorController::Revert; - const QString patch = diffController->makePatch(fileIndex, chunkIndex, options); - stage(diffController, patch, revert); + options |= DiffEditorController::AddPrefix; + const QString patch = diffController->makePatch(fileIndex, chunkIndex, selection, options); + stage(diffController, patch, options & Revert); }; menu->addSeparator(); QAction *stageChunkAction = menu->addAction(tr("Stage Chunk")); connect(stageChunkAction, &QAction::triggered, this, [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")); connect(unstageChunkAction, &QAction::triggered, this, [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)) { stageChunkAction->setEnabled(false); + stageLinesAction->setEnabled(false); unstageChunkAction->setEnabled(false); + unstageLinesAction->setEnabled(false); } } diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h index f23ac7dd652..f1495495ea9 100644 --- a/src/plugins/git/gitclient.h +++ b/src/plugins/git/gitclient.h @@ -54,6 +54,7 @@ namespace VcsBase { } namespace DiffEditor { +class ChunkSelection; class DiffEditorController; } @@ -333,7 +334,8 @@ public: private: 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, const QString &patch, bool revert);