2 * @file MergeDocLineDiffs.cpp
4 * @brief Implementation file for word diff highlighting (F4) for merge edit & detail views
12 #include "MergeEditView.h"
13 #include "DiffTextBuffer.h"
14 #include "stringdiffs.h"
15 #include "UnicodeString.h"
16 #include "SubstitutionFiltersList.h"
26 * @brief Display the line/word difference highlight in edit view
29 HighlightDiffRect(CMergeEditView * pView, const CRect & rc)
33 // Should we remove existing selection ?
38 // with anchor at left and caret at right
39 // this seems to be conventional behavior in Windows editors
40 pView->SelectArea(rc.TopLeft(), rc.BottomRight());
41 pView->SetCursorPos(rc.BottomRight());
42 pView->SetNewAnchor(rc.TopLeft());
43 // try to ensure that selected area is visible
44 pView->EnsureVisible(rc.TopLeft(), rc.BottomRight());
49 * @brief Highlight difference in current line (left & right panes)
51 void CMergeDoc::Showlinediff(CMergeEditView *pView, bool bReversed)
55 Computelinediff(pView, rc, bReversed);
57 if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.top == -1; }))
59 String caption = _("Line difference");
60 String msg = _("No differences to select found");
61 MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK);
65 // Actually display selection areas on screen in both edit panels
66 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
67 HighlightDiffRect(m_pView[pView->m_nThisGroup][nBuffer], rc[nBuffer]);
70 void CMergeDoc::AddToSubstitutionFilters(CMergeEditView* pView, bool bReversed)
73 return; /// Not clear what to do for a 3-way merge
77 Computelinediff(pView, rc, bReversed);
79 if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.top == -1; }))
81 String caption = _("Line difference");
82 String msg = _("No differences found to add as substitution filter");
83 MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK);
87 // Actually display selection areas on screen in both edit panels
88 String selectedText[3];
89 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
91 HighlightDiffRect(m_pView[pView->m_nThisGroup][nBuffer], rc[nBuffer]);
92 selectedText[nBuffer] = String(m_pView[pView->m_nThisGroup][nBuffer]->GetSelectedText());
95 if (selectedText[0].empty())
101 /// Check whether the pair is already registered with Substitution Filters
102 SubstitutionFiltersList &SubstitutionFiltersList = *theApp.m_pSubstitutionFiltersList.get();
103 for (int f = 0; f < SubstitutionFiltersList.GetCount(); f++)
105 String str0 = SubstitutionFiltersList.GetAt(f).pattern;
106 String str1 = SubstitutionFiltersList.GetAt(f).replacement;
107 if ( str0 == selectedText[0] && str1 == selectedText[1])
109 String caption = _("The pair is already present in the list of Substitution Filters");
110 String msg = strutils::format(_T("\"%s\" <-> \"%s\""), selectedText[0], selectedText[1]);
111 MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_OK);
112 return; /// The substitution pair is already registered
116 String caption = _("Add this change to Substitution Filters?");
117 String msg = strutils::format(_T("\"%s\" <-> \"%s\""), selectedText[0], selectedText[1]);
118 if (MessageBox(pView->GetSafeHwnd(), msg.c_str(), caption.c_str(), MB_YESNO) == IDYES)
120 SubstitutionFiltersList.Add(selectedText[0], selectedText[1], false, true, false, true);
121 SubstitutionFiltersList.SetEnabled(true);
122 SubstitutionFiltersList.SaveFilters();
123 FlushAndRescan(true);
129 static inline bool IsDiffPerLine(bool bTableEditing, const DIFFRANGE& cd)
131 const int LineLimit = 20;
132 return (bTableEditing || (cd.dend - cd.dbegin > LineLimit)) ? true : false;
136 * @brief Returns rectangles to highlight in both views (to show differences in line specified)
138 void CMergeDoc::Computelinediff(CMergeEditView *pView, CRect rc[], bool bReversed)
141 for (file = 0; file < m_nBuffers; file++)
144 const int nActivePane = pView->m_nThisPane;
145 if (m_diffList.GetSize() == 0 || IsEditedAfterRescan())
148 auto [ptStart, ptEnd] = pView->GetSelection();
150 const int nLineCount = m_ptBuf[nActivePane]->GetLineCount();
151 size_t nWordDiff = 0;
153 vector<WordDiff> worddiffs;
154 int nDiff = m_diffList.LineToDiff(ptStart.y);
156 worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
158 if (!worddiffs.empty())
160 bool bGoToNextWordDiff = true;
161 if (EqualCurrentWordDiff(nActivePane, ptStart, ptEnd))
163 nWordDiff = m_CurWordDiff.nWordDiff;
169 for (nWordDiff = 0; nWordDiff < worddiffs.size(); ++nWordDiff)
171 auto& worddiff = worddiffs[nWordDiff];
172 if (worddiff.beginline[nActivePane] <= ptStart.y && ptStart.y <= worddiff.endline[nActivePane])
174 int begin = (worddiff.beginline[nActivePane] < ptStart.y) ? 0 : worddiff.begin[nActivePane];
175 int end = (worddiff.endline[nActivePane] > ptStart.y) ? m_ptBuf[nActivePane]->GetLineLength(ptStart.y) : worddiff.end[nActivePane];
176 if (ptStart.x <= begin || (begin <= ptStart.x && ptStart.x <= end))
178 bGoToNextWordDiff = false;
186 for (nWordDiff = worddiffs.size() - 1; nWordDiff != static_cast<size_t>(-1); --nWordDiff)
188 auto& worddiff = worddiffs[nWordDiff];
189 if (worddiff.beginline[nActivePane] <= ptStart.y && ptStart.y <= worddiff.endline[nActivePane])
191 int begin = (worddiff.beginline[nActivePane] < ptStart.y) ? 0 : worddiff.begin[nActivePane];
192 int end = (worddiff.endline[nActivePane] > ptStart.y) ? m_ptBuf[nActivePane]->GetLineLength(ptStart.y) : worddiff.end[nActivePane];
193 if (ptStart.x >= end || (begin <= ptStart.x && ptStart.x <= end))
195 bGoToNextWordDiff = false;
202 if (bGoToNextWordDiff)
206 if (nWordDiff < worddiffs.size())
209 if (nWordDiff == worddiffs.size())
211 m_diffList.GetDiff(nDiff, di);
218 if (nWordDiff == 0 || nWordDiff == static_cast<size_t>(-1))
220 m_diffList.GetDiff(nDiff, di);
221 ptStart.y = di.dbegin;
229 if (worddiffs.empty())
233 nDiff = m_diffList.LineToDiff(ptStart.y);
236 m_diffList.GetNextDiff(ptStart.y, nDiff);
242 nDiff = (nDiff + 1) % m_diffList.GetSize();
244 m_diffList.GetDiff(nDiff, di);
245 worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
250 nDiff = m_diffList.LineToDiff(ptStart.y);
253 m_diffList.GetPrevDiff(ptStart.y, nDiff);
255 nDiff = m_diffList.GetSize() - 1;
259 nDiff = (nDiff + m_diffList.GetSize() - 1) % m_diffList.GetSize();
261 m_diffList.GetDiff(nDiff, di);
262 worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
263 nWordDiff = worddiffs.size() - 1;
265 if (worddiffs.empty())
269 for (file = 0; file < m_nBuffers; file++)
272 rc[file].top = di.dbegin;
274 if (di.dbegin < nLineCount - 1)
275 rc[file].bottom = di.dbegin + 1;
277 rc[file].bottom = di.dbegin;
279 nWordDiff = static_cast<size_t>(-1);
283 if (nWordDiff != static_cast<size_t>(-1))
285 auto& worddiff = worddiffs[nWordDiff];
286 for (file = 0; file < m_nBuffers; file++)
288 rc[file].left = worddiff.begin[file];
289 rc[file].top = worddiff.beginline[file];
290 rc[file].right = worddiff.end[file];
291 rc[file].bottom = worddiff.endline[file];
295 m_CurWordDiff.nPane = nActivePane;
296 m_CurWordDiff.ptStart.x = rc[nActivePane].left;
297 m_CurWordDiff.ptStart.y = rc[nActivePane].top;
298 m_CurWordDiff.ptEnd.x = rc[nActivePane].right;
299 m_CurWordDiff.ptEnd.y = rc[nActivePane].bottom;
300 m_CurWordDiff.nDiff = nDiff;
301 m_CurWordDiff.nWordDiff = nWordDiff;
304 void CMergeDoc::ClearWordDiffCache(int nDiff/* = -1 */)
308 m_cacheWordDiffs.clear();
312 std::map<int, std::vector<WordDiff> >::iterator it = m_cacheWordDiffs.find(nDiff);
313 if (it != m_cacheWordDiffs.end())
314 m_cacheWordDiffs.erase(it);
318 std::vector<WordDiff> CMergeDoc::GetWordDiffArrayInDiffBlock(int nDiff)
321 m_diffList.GetDiff(nDiff, cd);
323 bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
325 return GetWordDiffArray(cd.dbegin);
327 std::vector<WordDiff> worddiffs;
328 for (int nLine = cd.dbegin; nLine <= cd.dend; ++nLine)
330 std::vector<WordDiff> worddiffsPerLine = GetWordDiffArray(nLine);
331 worddiffs.insert(worddiffs.end(), worddiffsPerLine.begin(), worddiffsPerLine.end());
336 std::vector<WordDiff>
337 CMergeDoc::GetWordDiffArrayInRange(const int begin[3], const int end[3], int pane1/*=-1*/, int pane2/*=-1*/)
339 DIFFOPTIONS diffOptions = {0};
340 m_diffWrapper.GetOptions(&diffOptions);
342 std::unique_ptr<int[]> nOffsets[3];
343 std::vector<WordDiff> worddiffs;
344 std::vector<int> panes;
345 if (pane1 == -1 && pane2 == -1)
346 panes = (m_nBuffers == 2) ? std::vector<int>{0, 1} : std::vector<int>{ 0, 1, 2 };
348 panes = std::vector<int>{ pane1, pane2 };
349 for (size_t i = 0; i < panes.size(); ++i)
352 int nLineBegin = begin[file];
353 int nLineEnd = end[file];
354 if (nLineEnd >= m_ptBuf[file]->GetLineCount())
356 nOffsets[file].reset(new int[nLineEnd - nLineBegin + 1]);
358 if (nLineBegin <= nLineEnd)
360 if (nLineBegin != nLineEnd || m_ptBuf[file]->GetLineLength(nLineEnd) > 0)
361 m_ptBuf[file]->GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, m_ptBuf[file]->GetLineLength(nLineEnd), strText);
362 strText += m_ptBuf[file]->GetLineEol(nLineEnd);
363 nOffsets[file][0] = 0;
365 str[i].assign(strText, strText.GetLength());
366 for (int nLine = nLineBegin; nLine < nLineEnd; nLine++)
367 nOffsets[file][nLine-nLineBegin+1] = nOffsets[file][nLine-nLineBegin] + m_ptBuf[file]->GetFullLineLength(nLine);
370 // Options that affect comparison
371 bool casitive = !diffOptions.bIgnoreCase;
372 bool eolSensitive = !diffOptions.bIgnoreEol;
373 int xwhite = diffOptions.nIgnoreWhitespace;
374 int breakType = GetBreakType(); // whitespace only or include punctuation
375 bool byteColoring = GetByteColoringOption();
377 // Make the call to stringdiffs, which does all the hard & tedious computations
378 std::vector<strdiff::wdiff> wdiffs =
379 strdiff::ComputeWordDiffs(static_cast<int>(panes.size()), str, casitive, eolSensitive, xwhite, diffOptions.bIgnoreNumbers, breakType, byteColoring);
381 std::vector<strdiff::wdiff>::iterator it;
382 for (it = wdiffs.begin(); it != wdiffs.end(); ++it)
385 for (size_t i = 0; i < panes.size(); ++i)
388 int nLineBegin = begin[file];
389 int nLineEnd = end[file];
391 for (nLine = nLineBegin; nLine < nLineEnd; nLine++)
393 if (it->begin[i] == nOffsets[file][nLine-nLineBegin] || it->begin[i] < nOffsets[file][nLine-nLineBegin+1])
396 wd.beginline[i] = nLine;
397 wd.begin[i] = it->begin[i] - nOffsets[file][nLine-nLineBegin];
398 if (m_ptBuf[file]->GetLineLength(nLine) < wd.begin[i])
400 if (wd.beginline[i] < m_ptBuf[file]->GetLineCount() - 1)
407 wd.begin[i] = m_ptBuf[file]->GetLineLength(nLine);
411 for (; nLine < nLineEnd; nLine++)
413 if (it->end[i] + 1 == nOffsets[file][nLine-nLineBegin] || it->end[i] + 1 < nOffsets[file][nLine-nLineBegin+1])
416 wd.endline[i] = nLine;
417 wd.end[i] = it->end[i] + 1 - nOffsets[file][nLine-nLineBegin];
418 if (m_ptBuf[file]->GetLineLength(nLine) < wd.end[i])
420 if (wd.endline[i] < m_ptBuf[file]->GetLineCount() - 1)
427 wd.end[i] = m_ptBuf[file]->GetLineLength(nLine);
433 worddiffs.push_back(wd);
439 * @brief Return array of differences in specified line
440 * This is used by algorithm for line diff coloring
441 * (Line diff coloring is distinct from the selection highlight code)
443 std::vector<WordDiff> CMergeDoc::GetWordDiffArray(int nLineIndex)
447 std::vector<WordDiff> worddiffs;
449 for (file = 0; file < m_nBuffers; file++)
451 if (nLineIndex >= m_ptBuf[file]->GetLineCount())
455 int nDiff = m_diffList.LineToDiff(nLineIndex);
458 std::map<int, std::vector<WordDiff> >::iterator itmap = m_cacheWordDiffs.find(nDiff);
459 if (itmap != m_cacheWordDiffs.end())
461 worddiffs.resize((*itmap).second.size());
462 std::copy((*itmap).second.begin(), (*itmap).second.end(), worddiffs.begin());
466 m_diffList.GetDiff(nDiff, cd);
468 bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
470 int nLineBegin[3]{}, nLineEnd[3]{};
473 for (int pane = 0; pane < m_nBuffers; ++pane)
475 nLineBegin[pane] = cd.dbegin;
476 nLineEnd[pane] = cd.dend;
481 for (int pane = 0; pane < m_nBuffers; ++pane)
483 nLineBegin[pane] = nLineEnd[pane] = nLineIndex;
487 worddiffs = GetWordDiffArrayInRange(nLineBegin, nLineEnd);
491 m_cacheWordDiffs[nDiff].resize(worddiffs.size());
492 std::copy(worddiffs.begin(), worddiffs.end(), m_cacheWordDiffs[nDiff].begin());