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);
212 ptStart.y = (di.dend + 1) % nLineCount;
220 m_diffList.GetDiff(nDiff, di);
221 ptStart.y = (di.dbegin - 1) % nLineCount;
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 worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
249 nDiff = m_diffList.LineToDiff(ptStart.y);
252 m_diffList.GetPrevDiff(ptStart.y, nDiff);
254 nDiff = m_diffList.GetSize() - 1;
258 nDiff = (nDiff - 1) % m_diffList.GetSize();
260 worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
261 nWordDiff = worddiffs.size() - 1;
263 if (worddiffs.empty())
267 for (file = 0; file < m_nBuffers; file++)
270 rc[file].top = di.dbegin;
272 if (di.dbegin < nLineCount - 1)
273 rc[file].bottom = di.dbegin + 1;
275 rc[file].bottom = di.dbegin;
277 nWordDiff = static_cast<size_t>(-1);
281 if (nWordDiff != static_cast<size_t>(-1))
283 auto& worddiff = worddiffs[nWordDiff];
284 for (file = 0; file < m_nBuffers; file++)
286 rc[file].left = worddiff.begin[file];
287 rc[file].top = worddiff.beginline[file];
288 rc[file].right = worddiff.end[file];
289 rc[file].bottom = worddiff.endline[file];
293 m_CurWordDiff.nPane = nActivePane;
294 m_CurWordDiff.ptStart.x = rc[nActivePane].left;
295 m_CurWordDiff.ptStart.y = rc[nActivePane].top;
296 m_CurWordDiff.ptEnd.x = rc[nActivePane].right;
297 m_CurWordDiff.ptEnd.y = rc[nActivePane].bottom;
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());
335 * @brief Return array of differences in specified line
336 * This is used by algorithm for line diff coloring
337 * (Line diff coloring is distinct from the selection highlight code)
339 std::vector<WordDiff> CMergeDoc::GetWordDiffArray(int nLineIndex)
343 std::vector<WordDiff> worddiffs;
345 for (file = 0; file < m_nBuffers; file++)
347 if (nLineIndex >= m_ptBuf[file]->GetLineCount())
351 int nDiff = m_diffList.LineToDiff(nLineIndex);
354 std::map<int, std::vector<WordDiff> >::iterator itmap = m_cacheWordDiffs.find(nDiff);
355 if (itmap != m_cacheWordDiffs.end())
357 worddiffs.resize((*itmap).second.size());
358 std::copy((*itmap).second.begin(), (*itmap).second.end(), worddiffs.begin());
362 m_diffList.GetDiff(nDiff, cd);
364 DIFFOPTIONS diffOptions = {0};
365 m_diffWrapper.GetOptions(&diffOptions);
367 std::unique_ptr<int[]> nOffsets[3];
368 bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
370 int nLineBegin, nLineEnd;
373 nLineBegin = cd.dbegin;
378 nLineBegin = nLineEnd = nLineIndex;
381 for (file = 0; file < m_nBuffers; file++)
383 if (nLineEnd >= m_ptBuf[file]->GetLineCount())
385 nOffsets[file].reset(new int[nLineEnd - nLineBegin + 1]);
387 if (nLineBegin != nLineEnd || m_ptBuf[file]->GetLineLength(nLineEnd) > 0)
388 m_ptBuf[file]->GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, m_ptBuf[file]->GetLineLength(nLineEnd), strText);
389 strText += m_ptBuf[file]->GetLineEol(nLineEnd);
390 str[file].assign(strText, strText.GetLength());
392 nOffsets[file][0] = 0;
393 for (int nLine = nLineBegin; nLine < nLineEnd; nLine++)
394 nOffsets[file][nLine-nLineBegin+1] = nOffsets[file][nLine-nLineBegin] + m_ptBuf[file]->GetFullLineLength(nLine);
397 // Options that affect comparison
398 bool casitive = !diffOptions.bIgnoreCase;
399 bool eolSensitive = !diffOptions.bIgnoreEol;
400 int xwhite = diffOptions.nIgnoreWhitespace;
401 int breakType = GetBreakType(); // whitespace only or include punctuation
402 bool byteColoring = GetByteColoringOption();
404 // Make the call to stringdiffs, which does all the hard & tedious computations
405 std::vector<strdiff::wdiff> wdiffs = strdiff::ComputeWordDiffs(m_nBuffers, str, casitive, eolSensitive, xwhite, breakType, byteColoring);
408 std::vector<strdiff::wdiff>::iterator it;
409 for (i = 0, it = wdiffs.begin(); it != wdiffs.end(); ++i, ++it)
412 for (file = 0; file < m_nBuffers; file++)
415 for (nLine = nLineBegin; nLine < nLineEnd; nLine++)
417 if (it->begin[file] == nOffsets[file][nLine-nLineBegin] || it->begin[file] < nOffsets[file][nLine-nLineBegin+1])
420 wd.beginline[file] = nLine;
421 wd.begin[file] = it->begin[file] - nOffsets[file][nLine-nLineBegin];
422 if (m_ptBuf[file]->GetLineLength(nLine) < wd.begin[file])
424 if (wd.beginline[file] < m_ptBuf[file]->GetLineCount() - 1)
427 wd.beginline[file]++;
431 wd.begin[file] = m_ptBuf[file]->GetLineLength(nLine);
435 for (; nLine < nLineEnd; nLine++)
437 if (it->end[file] + 1 == nOffsets[file][nLine-nLineBegin] || it->end[file] + 1 < nOffsets[file][nLine-nLineBegin+1])
440 wd.endline[file] = nLine;
441 wd.end[file] = it->end[file] + 1 - nOffsets[file][nLine-nLineBegin];
442 if (m_ptBuf[file]->GetLineLength(nLine) < wd.end[file])
444 if (wd.endline[file] < m_ptBuf[file]->GetLineCount() - 1)
451 wd.end[file] = m_ptBuf[file]->GetLineLength(nLine);
457 worddiffs.push_back(wd);
462 m_cacheWordDiffs[nDiff].resize(worddiffs.size());
463 std::copy(worddiffs.begin(), worddiffs.end(), m_cacheWordDiffs[nDiff].begin());