From: Takashi Sawanaka Date: Sat, 6 Apr 2019 04:02:17 +0000 (+0900) Subject: Word-level merging did not work correctly if the number of lines in the diff block... X-Git-Tag: 2.16.4+-jp-10~121 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=783c1474bb36345e80cdbbe301cdbe36505b95d2;p=winmerge-jp%2Fwinmerge-jp.git Word-level merging did not work correctly if the number of lines in the diff block exceeded 20. --HG-- branch : jp --- diff --git a/Src/MergeDoc.cpp b/Src/MergeDoc.cpp index 1da38631c..9259792ff 100644 --- a/Src/MergeDoc.cpp +++ b/Src/MergeDoc.cpp @@ -800,7 +800,7 @@ void CMergeDoc::CopyAllList(int srcPane, int dstPane) * @param [in] firstDiff First diff copied (0-based index) * @param [in] lastDiff Last diff copied (0-based index) */ -void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int lastDiff) +void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int lastDiff, int firstWordDiff, int lastWordDiff) { #ifdef _DEBUG if (firstDiff > lastDiff) @@ -824,8 +824,16 @@ void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int la SetCurrentDiff(lastDiff); bool bGroupWithPrevious = false; - if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, true)) - return; // sync failure + if (firstWordDiff <= 0 && lastWordDiff == -1) + { + if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, true)) + return; // sync failure + } + else + { + if (!WordListCopy(srcPane, dstPane, lastDiff, firstWordDiff, lastWordDiff, nullptr, bGroupWithPrevious, true)) + return; // sync failure + } SetEditedAfterRescan(dstPane); @@ -858,8 +866,16 @@ void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int la } // Group merge with previous (merge undo data to one action) bGroupWithPrevious = true; - if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false)) - break; // sync failure + if (i > firstDiff || firstWordDiff <= 0) + { + if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false)) + break; // sync failure + } + else + { + if (!WordListCopy(srcPane, dstPane, firstDiff, firstWordDiff, -1, nullptr, bGroupWithPrevious, false)) + break; // sync failure + } } } @@ -1168,6 +1184,127 @@ bool CMergeDoc::ListCopy(int srcPane, int dstPane, int nDiff /* = -1*/, return true; } +bool CMergeDoc::WordListCopy(int srcPane, int dstPane, int nDiff, int firstWordDiff, int lastWordDiff, + const std::vector *pWordDiffIndice, bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/) +{ + int nGroup = GetActiveMergeView()->m_nThisGroup; + CMergeEditView *pViewSrc = m_pView[nGroup][srcPane]; + CMergeEditView *pViewDst = m_pView[nGroup][dstPane]; + CCrystalTextView *pSource = bUpdateView ? pViewDst : nullptr; + + // suppress Rescan during this method + // (Not only do we not want to rescan a lot of times, but + // it will wreck the line status array to rescan as we merge) + RescanSuppress suppressRescan(*this); + + DIFFRANGE cd; + VERIFY(m_diffList.GetDiff(nDiff, cd)); + CDiffTextBuffer& sbuf = *m_ptBuf[srcPane]; + CDiffTextBuffer& dbuf = *m_ptBuf[dstPane]; + bool bSrcWasMod = sbuf.IsModified(); + const int cd_dbegin = cd.dbegin; + const int cd_dend = cd.dend; + const int cd_blank = cd.blank[srcPane]; + bool bInSync = SanityCheckDiff(cd); + + if (!bInSync) + { + LangMessageBox(IDS_VIEWS_OUTOFSYNC, MB_ICONSTOP); + return false; // abort copying + } + + std::vector worddiffs = GetWordDiffArrayInDiffBlock(nDiff); + + if (worddiffs.empty()) + return false; + + if (cd.end[srcPane] < cd.begin[srcPane]) + return ListCopy(srcPane, dstPane, nDiff, bGroupWithPrevious, bUpdateView); + + if (firstWordDiff == -1) + firstWordDiff = 0; + if (lastWordDiff == -1) + lastWordDiff = static_cast(worddiffs.size() - 1); + + // If we remove whole diff from current view, we must fix cursor + // position first. Normally we would move to end of previous line, + // but we want to move to begin of that line for usability. + if (bUpdateView) + { + CPoint currentPos = pViewDst->GetCursorPos(); + currentPos.x = 0; + if (currentPos.y > cd_dend) + { + if (cd.blank[dstPane] >= 0) + currentPos.y -= cd_dend - cd.blank[dstPane] + 1; + else if (cd.blank[srcPane] >= 0) + currentPos.y -= cd_dend - cd.blank[srcPane] + 1; + } + ForEachView(dstPane, [currentPos](auto& pView) { pView->SetCursorPos(currentPos); }); + } + + // if the current diff contains missing lines, remove them from both sides + int limit = cd_dend; + + // curView is the view which is changed, so the opposite of the source view + dbuf.BeginUndoGroup(bGroupWithPrevious); + + CString srcText, dstText; + CPoint ptDstStart, ptDstEnd; + CPoint ptSrcStart, ptSrcEnd; + + ptDstStart.x = worddiffs[firstWordDiff].begin[dstPane]; + ptDstStart.y = worddiffs[firstWordDiff].beginline[dstPane]; + ptDstEnd.x = worddiffs[lastWordDiff].end[dstPane]; + ptDstEnd.y = worddiffs[lastWordDiff].endline[dstPane]; + ptSrcStart.x = worddiffs[firstWordDiff].begin[srcPane]; + ptSrcStart.y = worddiffs[firstWordDiff].beginline[srcPane]; + ptSrcEnd.x = worddiffs[lastWordDiff].end[srcPane]; + ptSrcEnd.y = worddiffs[lastWordDiff].endline[srcPane]; + + std::vector nDstOffsets(ptDstEnd.y - ptDstStart.y + 2); + std::vector nSrcOffsets(ptSrcEnd.y - ptSrcStart.y + 2); + + dbuf.GetTextWithoutEmptys(ptDstStart.y, ptDstStart.x, ptDstEnd.y, ptDstEnd.x, dstText); + sbuf.GetTextWithoutEmptys(ptSrcStart.y, ptSrcStart.x, ptSrcEnd.y, ptSrcEnd.x, srcText); + + nDstOffsets[0] = 0; + for (int nLine = ptDstStart.y; nLine <= ptDstEnd.y; nLine++) + nDstOffsets[nLine-ptDstStart.y+1] = nDstOffsets[nLine-ptDstStart.y] + dbuf.GetFullLineLength(nLine); + nSrcOffsets[0] = 0; + for (int nLine = ptSrcStart.y; nLine <= ptSrcEnd.y; nLine++) + nSrcOffsets[nLine-ptSrcStart.y+1] = nSrcOffsets[nLine-ptSrcStart.y] + sbuf.GetFullLineLength(nLine); + + for (int i = lastWordDiff; i != firstWordDiff-1; --i) + { + if (pWordDiffIndice && std::find(pWordDiffIndice->begin(), pWordDiffIndice->end(), i) == pWordDiffIndice->end()) + continue; + int srcBegin = nSrcOffsets[worddiffs[i].beginline[srcPane] - ptSrcStart.y] + worddiffs[i].begin[srcPane]; + int srcEnd = nSrcOffsets[worddiffs[i].endline[srcPane] - ptSrcStart.y] + worddiffs[i].end[srcPane]; + int dstBegin = nDstOffsets[worddiffs[i].beginline[dstPane] - ptDstStart.y] + worddiffs[i].begin[dstPane]; + int dstEnd = nDstOffsets[worddiffs[i].endline[dstPane] - ptDstStart.y] + worddiffs[i].end[dstPane]; + dstText = dstText.Mid(0, dstBegin - ptDstStart.x) + + srcText.Mid(srcBegin - ptSrcStart.x, srcEnd - srcBegin) + + dstText.Mid(dstEnd - ptDstStart.x); + } + + dbuf.DeleteText(pSource, ptDstStart.y, ptDstStart.x, ptDstEnd.y, ptDstEnd.x, CE_ACTION_MERGE); + + int endl,endc; + dbuf.InsertText(pSource, ptDstStart.y, ptDstStart.x, dstText, dstText.GetLength(), endl, endc, CE_ACTION_MERGE); + + dbuf.FlushUndoGroup(pSource); + + // reset the mod status of the source view because we do make some + // changes, but none that concern the source text + sbuf.SetModified(bSrcWasMod); + + suppressRescan.Clear(); // done suppress Rescan + FlushAndRescan(); + + return true; +} + /** * @brief Save file with new filename. * diff --git a/Src/MergeDoc.h b/Src/MergeDoc.h index aa75593ea..0dc69ed0c 100644 --- a/Src/MergeDoc.h +++ b/Src/MergeDoc.h @@ -190,9 +190,10 @@ public: void ShowRescanError(int nRescanResult, IDENTLEVEL identical); bool Undo(); void CopyAllList(int srcPane, int dstPane); - void CopyMultipleList(int srcPane, int dstPane, int firstDiff, int lastDiff); + void CopyMultipleList(int srcPane, int dstPane, int firstDiff, int lastDiff, int firstWordDiff = -1, int lastWordDiff = -1); void DoAutoMerge(int dstPane); bool SanityCheckDiff(DIFFRANGE dr) const; + bool WordListCopy(int srcPane, int dstPane, int nDiff, int nFirstWordDiff, int nLastWordDiff, const std::vector *pWordDiffIndice, bool bGroupWithPrevious = false, bool bUpdateView = true); bool ListCopy(int srcPane, int dstPane, int nDiff = -1, bool bGroupWithPrevious = false, bool bUpdateView = true); bool TrySaveAs(String& strPath, int &nLastErrorCode, String & sError, int nBuffer, PackingInfo * pInfoTempUnpacker); @@ -277,6 +278,7 @@ public: public: typedef enum { BYTEDIFF, WORDDIFF } DIFFLEVEL; void Showlinediff(CMergeEditView *pView, bool bReversed = false); + std::vector GetWordDiffArrayInDiffBlock(int nDiff); std::vector GetWordDiffArray(int nLineIndex); void ClearWordDiffCache(int nDiff = -1); private: diff --git a/Src/MergeDocLineDiffs.cpp b/Src/MergeDocLineDiffs.cpp index e831ce4b4..7035bc095 100644 --- a/Src/MergeDocLineDiffs.cpp +++ b/Src/MergeDocLineDiffs.cpp @@ -137,6 +137,25 @@ void CMergeDoc::ClearWordDiffCache(int nDiff/* = -1 */) } } +std::vector CMergeDoc::GetWordDiffArrayInDiffBlock(int nDiff) +{ + DIFFRANGE cd; + const int LineLimit = 20; + m_diffList.GetDiff(nDiff, cd); + + bool diffPerLine = (cd.dend - cd.dbegin > LineLimit) ? true : false; + if (!diffPerLine) + return GetWordDiffArray(cd.dbegin); + + std::vector worddiffs; + for (int nLine = cd.dbegin; nLine <= cd.dend; ++nLine) + { + std::vector worddiffsPerLine = GetWordDiffArray(nLine); + worddiffs.insert(worddiffs.end(), worddiffsPerLine.begin(), worddiffsPerLine.end()); + } + return worddiffs; +} + /** * @brief Return array of differences in specified line * This is used by algorithm for line diff coloring diff --git a/Src/MergeEditView.cpp b/Src/MergeEditView.cpp index c313c1ccd..0cee83b50 100644 --- a/Src/MergeEditView.cpp +++ b/Src/MergeEditView.cpp @@ -296,14 +296,18 @@ CString CMergeEditView::GetSelectedText() * @brief Get diffs inside selection. * @param [out] firstDiff First diff inside selection * @param [out] lastDiff Last diff inside selection + * @param [out] firstWordDiff First word level diff inside selection + * @param [out] lastWordDiff Last word level diff inside selection * @note -1 is returned in parameters if diffs cannot be determined * @todo This shouldn't be called when there is no diffs, so replace * first 'if' with ASSERT()? */ -void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff) +void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd) { firstDiff = -1; lastDiff = -1; + firstWordDiff = -1; + lastWordDiff = -1; CMergeDoc *pd = GetDocument(); const int nDiffs = pd->m_diffList.GetSignificantDiffs(); @@ -311,39 +315,140 @@ void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff) return; int firstLine, lastLine; - GetFullySelectedLines(firstLine, lastLine); - if (lastLine < firstLine) + CPoint ptStart, ptEnd; + GetSelection(ptStart, ptEnd); + if (pptStart != nullptr) + ptStart = *pptStart; + if (pptEnd != nullptr) + ptEnd = *pptEnd; + firstLine = ptStart.y; + lastLine = ptEnd.y; + + firstDiff = pd->m_diffList.LineToDiff(firstLine); + if (firstDiff == -1) + { + firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine); + if (firstDiff == -1) + return; + firstWordDiff = 0; + } + lastDiff = pd->m_diffList.LineToDiff(lastLine); + if (lastDiff == -1) + lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine); + if (lastDiff < firstDiff) + { + firstDiff = -1; + firstWordDiff = -1; return; + } - firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine); - lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine); if (firstDiff != -1 && lastDiff != -1) { DIFFRANGE di; - // Check that first selected line is first diff's first line or above it - VERIFY(pd->m_diffList.GetDiff(firstDiff, di)); - if ((int)di.dbegin < firstLine) + if (ptStart != ptEnd) { - if (firstDiff < lastDiff) - ++firstDiff; - } + if (firstWordDiff == -1) + { + VERIFY(pd->m_diffList.GetDiff(firstDiff, di)); + vector worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff); + for (size_t i = 0; i < worddiffs.size(); ++i) + { + if (worddiffs[i].endline[m_nThisPane] > firstLine || + (firstLine == worddiffs[i].endline[m_nThisPane] && worddiffs[i].end[m_nThisPane] - 1 >= ptStart.x)) + { + firstWordDiff = static_cast(i); + break; + } + } - // Check that last selected line is last diff's last line or below it - VERIFY(pd->m_diffList.GetDiff(lastDiff, di)); - if ((int)di.dend > lastLine) - { - if (firstDiff < lastDiff) + if (firstLine >= di.dbegin && firstWordDiff == -1) + { + ++firstDiff; + firstWordDiff = 0; + } + } + + VERIFY(pd->m_diffList.GetDiff(lastDiff, di)); + vector worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff); + for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i) + { + if (worddiffs[i].beginline[m_nThisPane] < lastLine || + (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x)) + { + lastWordDiff = static_cast(i); + break; + } + } + + if (lastLine <= di.dend && lastWordDiff == -1) --lastDiff; - } - // Special case: one-line diff is not selected if cursor is in it - if (firstLine == lastLine) + if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff)) + { + firstDiff = -1; + lastDiff = -1; + firstWordDiff = -1; + lastWordDiff = -1; + } + else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1)) + { + firstDiff = -1; + lastDiff = -1; + firstWordDiff = -1; + lastWordDiff = -1; + } + } + else { firstDiff = -1; lastDiff = -1; + firstWordDiff = -1; + lastWordDiff = -1; } } + + ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true); + ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true); + ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true); +} + +std::map> CMergeEditView::GetColumnSelectedWordDiffIndice() +{ + CMergeDoc *pDoc = GetDocument(); + std::map> ret; + std::map *> list; + CPoint ptStart, ptEnd; + GetSelection(ptStart, ptEnd); + for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine) + { + if (pDoc->m_diffList.LineToDiff(nLine) != -1) + { + int firstDiff, lastDiff, firstWordDiff, lastWordDiff; + int nLeft, nRight; + GetColumnSelection(nLine, nLeft, nRight); + CPoint ptStart2, ptEnd2; + ptStart2.x = nLeft; + ptEnd2.x = nRight; + ptStart2.y = ptEnd2.y = nLine; + GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2); + if (firstDiff != -1 && lastDiff != -1) + { + std::vector *pWordDiffs; + if (list.find(firstDiff) == list.end()) + list.insert(std::pair *>(firstDiff, new std::vector())); + pWordDiffs = list[firstDiff]; + for (int i = firstWordDiff; i <= lastWordDiff; ++i) + { + if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1]) + pWordDiffs->push_back(i); + } + } + } + } + for (auto& it : list) + ret.insert(std::pair>(it.first, *it.second)); + return ret; } void CMergeEditView::OnInitialUpdate() @@ -1669,16 +1774,28 @@ void CMergeEditView::OnX2Y(int srcPane, int dstPane) } } - int firstDiff, lastDiff; - GetFullySelectedDiffs(firstDiff, lastDiff); - - if (firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff)) + if (IsSelection()) { - CWaitCursor waitstatus; - if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff) && !IsSelection()) - pDoc->ListCopy(srcPane, dstPane, currentDiff); + int firstDiff, lastDiff, firstWordDiff, lastWordDiff; + if (!m_bColumnSelection) + { + GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff); + if (firstDiff != -1 && lastDiff != -1) + { + CWaitCursor waitstatus; + pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff); + } + } else - pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff); + { + CWaitCursor waitstatus; + auto wordDiffs = GetColumnSelectedWordDiffIndice(); + int i = 0; + std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) { + pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0); + ++i; + }); + } } else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff)) { @@ -1692,14 +1809,16 @@ void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI) // Check that right side is not readonly if (!IsReadOnly(dstPane)) { - int firstDiff, lastDiff; - GetFullySelectedDiffs(firstDiff, lastDiff); - // If one or more diffs inside selection OR // there is an active diff OR // cursor is inside diff - if (firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff)) - pCmdUI->Enable(true); + if (IsSelection()) + { + int firstDiff, lastDiff, firstWordDiff, lastWordDiff; + GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff); + + pCmdUI->Enable(firstDiff != -1 && lastDiff != -1); + } else { const int currDiff = GetDocument()->GetCurrentDiff(); diff --git a/Src/MergeEditView.h b/Src/MergeEditView.h index 2f45c1a4c..1d6f7f644 100644 --- a/Src/MergeEditView.h +++ b/Src/MergeEditView.h @@ -123,7 +123,8 @@ public: void SelectDiff(int nDiff, bool bScroll = true, bool bSelectText = true); virtual CCrystalTextBuffer *LocateTextBuffer (); const CCrystalTextBuffer *LocateTextBuffer () const { return const_cast(this)->LocateTextBuffer(); }; - void GetFullySelectedDiffs(int & firstDiff, int & lastDiff); + void GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart = nullptr, const CPoint *ppEnd = nullptr); + std::map> GetColumnSelectedWordDiffIndice(); CString GetSelectedText(); CString GetLineText(int idx); CMergeDoc* GetDocument();