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 std::pair<CEPoint, CEPoint> & 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.first, rc.second);
41 pView->SetCursorPos(rc.second);
42 pView->SetNewAnchor(rc.first);
43 // try to ensure that selected area is visible
44 pView->EnsureVisible(rc.first, rc.second);
49 * @brief Highlight difference in current line (left & right panes)
51 void CMergeDoc::Showlinediff(CMergeEditView *pView, bool bReversed)
53 std::pair<CEPoint, CEPoint> rc[3];
55 Computelinediff(pView, rc, bReversed);
57 if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.first.y == -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
75 std::pair<CEPoint, CEPoint> rc[3];
77 Computelinediff(pView, rc, bReversed);
79 if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.first.y == -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, std::pair<CEPoint, CEPoint> rc[], bool bReversed)
141 for (file = 0; file < m_nBuffers; file++)
142 rc[file].first.y = -1;
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++)
271 rc[file].first.x = 0;
272 rc[file].first.y = di.dbegin;
273 rc[file].second.x = 0;
274 if (di.dbegin < nLineCount - 1)
275 rc[file].second.y = di.dbegin + 1;
277 rc[file].second.y = 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].first.x = worddiff.begin[file];
289 rc[file].first.y = worddiff.beginline[file];
290 rc[file].second.x = worddiff.end[file];
291 rc[file].second.y = worddiff.endline[file];
295 m_CurWordDiff.nPane = nActivePane;
296 m_CurWordDiff.ptStart = rc[nActivePane].first;
297 m_CurWordDiff.ptEnd = rc[nActivePane].second;
298 m_CurWordDiff.nDiff = nDiff;
299 m_CurWordDiff.nWordDiff = nWordDiff;
302 void CMergeDoc::ClearWordDiffCache(int nDiff/* = -1 */)
306 m_cacheWordDiffs.clear();
310 std::map<int, std::vector<WordDiff> >::iterator it = m_cacheWordDiffs.find(nDiff);
311 if (it != m_cacheWordDiffs.end())
312 m_cacheWordDiffs.erase(it);
316 std::vector<WordDiff> CMergeDoc::GetWordDiffArrayInDiffBlock(int nDiff)
319 m_diffList.GetDiff(nDiff, cd);
321 bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
323 return GetWordDiffArray(cd.dbegin);
325 std::vector<WordDiff> worddiffs;
326 for (int nLine = cd.dbegin; nLine <= cd.dend; ++nLine)
328 std::vector<WordDiff> worddiffsPerLine = GetWordDiffArray(nLine);
329 worddiffs.insert(worddiffs.end(), worddiffsPerLine.begin(), worddiffsPerLine.end());
334 std::vector<WordDiff>
335 CMergeDoc::GetWordDiffArrayInRange(const int begin[3], const int end[3], int pane1/*=-1*/, int pane2/*=-1*/)
337 DIFFOPTIONS diffOptions = {0};
338 m_diffWrapper.GetOptions(&diffOptions);
340 std::unique_ptr<int[]> nOffsets[3];
341 std::vector<WordDiff> worddiffs;
342 std::vector<int> panes;
343 if (pane1 == -1 && pane2 == -1)
344 panes = (m_nBuffers == 2) ? std::vector<int>{0, 1} : std::vector<int>{ 0, 1, 2 };
346 panes = std::vector<int>{ pane1, pane2 };
347 for (size_t i = 0; i < panes.size(); ++i)
350 int nLineBegin = begin[file];
351 int nLineEnd = end[file];
352 if (nLineEnd >= m_ptBuf[file]->GetLineCount())
354 nOffsets[file].reset(new int[nLineEnd - nLineBegin + 1]);
356 if (nLineBegin <= nLineEnd)
358 if (nLineBegin != nLineEnd || m_ptBuf[file]->GetLineLength(nLineEnd) > 0)
359 m_ptBuf[file]->GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, m_ptBuf[file]->GetLineLength(nLineEnd), strText);
360 strText += m_ptBuf[file]->GetLineEol(nLineEnd);
361 nOffsets[file][0] = 0;
363 str[i] = std::move(strText);
364 for (int nLine = nLineBegin; nLine < nLineEnd; nLine++)
365 nOffsets[file][nLine-nLineBegin+1] = nOffsets[file][nLine-nLineBegin] + m_ptBuf[file]->GetFullLineLength(nLine);
368 // Options that affect comparison
369 bool casitive = !diffOptions.bIgnoreCase;
370 bool eolSensitive = !diffOptions.bIgnoreEol;
371 int xwhite = diffOptions.nIgnoreWhitespace;
372 int breakType = GetBreakType(); // whitespace only or include punctuation
373 bool byteColoring = GetByteColoringOption();
375 // Make the call to stringdiffs, which does all the hard & tedious computations
376 std::vector<strdiff::wdiff> wdiffs =
377 strdiff::ComputeWordDiffs(static_cast<int>(panes.size()), str, casitive, eolSensitive, xwhite, diffOptions.bIgnoreNumbers, breakType, byteColoring);
379 std::vector<strdiff::wdiff>::iterator it;
380 for (it = wdiffs.begin(); it != wdiffs.end(); ++it)
383 for (size_t i = 0; i < panes.size(); ++i)
386 int nLineBegin = begin[file];
387 int nLineEnd = end[file];
389 for (nLine = nLineBegin; nLine < nLineEnd; nLine++)
391 if (it->begin[i] == nOffsets[file][nLine-nLineBegin] || it->begin[i] < nOffsets[file][nLine-nLineBegin+1])
394 wd.beginline[i] = nLine;
395 wd.begin[i] = it->begin[i] - nOffsets[file][nLine-nLineBegin];
396 if (m_ptBuf[file]->GetLineLength(nLine) < wd.begin[i])
398 if (wd.beginline[i] < m_ptBuf[file]->GetLineCount() - 1)
405 wd.begin[i] = m_ptBuf[file]->GetLineLength(nLine);
409 for (; nLine < nLineEnd; nLine++)
411 if (it->end[i] + 1 == nOffsets[file][nLine-nLineBegin] || it->end[i] + 1 < nOffsets[file][nLine-nLineBegin+1])
414 wd.endline[i] = nLine;
415 wd.end[i] = it->end[i] + 1 - nOffsets[file][nLine-nLineBegin];
416 if (m_ptBuf[file]->GetLineLength(nLine) < wd.end[i])
418 if (wd.endline[i] < m_ptBuf[file]->GetLineCount() - 1)
425 wd.end[i] = m_ptBuf[file]->GetLineLength(nLine);
431 worddiffs.push_back(wd);
437 * @brief Return array of differences in specified line
438 * This is used by algorithm for line diff coloring
439 * (Line diff coloring is distinct from the selection highlight code)
441 std::vector<WordDiff> CMergeDoc::GetWordDiffArray(int nLineIndex)
445 std::vector<WordDiff> worddiffs;
447 for (file = 0; file < m_nBuffers; file++)
449 if (nLineIndex >= m_ptBuf[file]->GetLineCount())
453 int nDiff = m_diffList.LineToDiff(nLineIndex);
456 std::map<int, std::vector<WordDiff> >::iterator itmap = m_cacheWordDiffs.find(nDiff);
457 if (itmap != m_cacheWordDiffs.end())
459 worddiffs.resize((*itmap).second.size());
460 std::copy((*itmap).second.begin(), (*itmap).second.end(), worddiffs.begin());
464 m_diffList.GetDiff(nDiff, cd);
466 bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
468 int nLineBegin[3]{}, nLineEnd[3]{};
471 for (int pane = 0; pane < m_nBuffers; ++pane)
473 nLineBegin[pane] = cd.dbegin;
474 nLineEnd[pane] = cd.dend;
479 for (int pane = 0; pane < m_nBuffers; ++pane)
481 nLineBegin[pane] = nLineEnd[pane] = nLineIndex;
485 worddiffs = GetWordDiffArrayInRange(nLineBegin, nLineEnd);
489 m_cacheWordDiffs[nDiff].resize(worddiffs.size());
490 std::copy(worddiffs.begin(), worddiffs.end(), m_cacheWordDiffs[nDiff].begin());