OSDN Git Service

refactor
[winmerge-jp/winmerge-jp.git] / Src / MergeDocLineDiffs.cpp
1 /** 
2  * @file  MergeDocLineDiffs.cpp
3  *
4  * @brief Implementation file for word diff highlighting (F4) for merge edit & detail views
5  *
6  */
7
8 #include "StdAfx.h"
9 #include "MergeDoc.h"
10 #include <vector>
11 #include <memory>
12 #include "MergeEditView.h"
13 #include "DiffTextBuffer.h"
14 #include "stringdiffs.h"
15 #include "UnicodeString.h"
16 #include "SubstitutionFiltersList.h"
17 #include "Merge.h"
18
19 #ifdef _DEBUG
20 #define new DEBUG_NEW
21 #endif
22
23 using std::vector;
24
25 /**
26  * @brief Display the line/word difference highlight in edit view
27  */
28 static void
29 HighlightDiffRect(CMergeEditView * pView, const std::pair<CEPoint, CEPoint> & rc)
30 {
31         if (rc.first.y == -1)
32         {
33                 // Should we remove existing selection ?
34         }
35         else
36         {
37                 // select the area
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);
45         }
46 }
47
48 /**
49  * @brief Highlight difference in current line (left & right panes)
50  */
51 void CMergeDoc::Showlinediff(CMergeEditView *pView, bool bReversed)
52 {
53         std::pair<CEPoint, CEPoint> rc[3];
54
55         Computelinediff(pView, rc, bReversed);
56
57         if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.first.y == -1; }))
58         {
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);
62                 return;
63         }
64
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]);
68 }
69
70 void CMergeDoc::AddToSubstitutionFilters(CMergeEditView* pView, bool bReversed)
71 {
72         if (m_nBuffers != 2)
73                 return; /// Not clear what to do for a 3-way merge
74
75         std::pair<CEPoint, CEPoint> rc[3];
76
77         Computelinediff(pView, rc, bReversed);
78
79         if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.first.y == -1; }))
80         {
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);
84                 return;
85         }
86
87         // Actually display selection areas on screen in both edit panels
88         String selectedText[3];
89         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
90         {
91                 HighlightDiffRect(m_pView[pView->m_nThisGroup][nBuffer], rc[nBuffer]);
92                 selectedText[nBuffer] = String(m_pView[pView->m_nThisGroup][nBuffer]->GetSelectedText());
93         }
94
95         if (selectedText[0].empty())
96         {
97                 return;
98         }
99
100
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++)
104         {
105                 String str0 = SubstitutionFiltersList.GetAt(f).pattern;
106                 String str1 = SubstitutionFiltersList.GetAt(f).replacement;
107                 if ( str0 == selectedText[0] && str1 == selectedText[1])
108                 {
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
113                 }
114         }
115
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)
119         {
120                 SubstitutionFiltersList.Add(selectedText[0], selectedText[1], false, true, false, true);
121                 SubstitutionFiltersList.SetEnabled(true);
122                 SubstitutionFiltersList.SaveFilters();
123                 FlushAndRescan(true);
124                 //Rescan();
125         }
126         return;
127 }
128
129 static inline bool IsDiffPerLine(bool bTableEditing, const DIFFRANGE& cd)
130 {
131         const int LineLimit = 20;
132         return (bTableEditing || (cd.dend - cd.dbegin > LineLimit)) ? true : false;
133 }
134
135 /**
136  * @brief Returns rectangles to highlight in both views (to show differences in line specified)
137  */
138 void CMergeDoc::Computelinediff(CMergeEditView *pView, std::pair<CEPoint, CEPoint> rc[], bool bReversed)
139 {
140         int file;
141         for (file = 0; file < m_nBuffers; file++)
142                 rc[file].first.y = -1;
143
144         const int nActivePane = pView->m_nThisPane;
145         if (m_diffList.GetSize() == 0 || IsEditedAfterRescan())
146                 return;
147
148         auto [ptStart, ptEnd] = pView->GetSelection();
149
150         const int nLineCount = m_ptBuf[nActivePane]->GetLineCount();
151         size_t nWordDiff = 0;
152         DIFFRANGE di;
153         vector<WordDiff> worddiffs;
154         int nDiff = m_diffList.LineToDiff(ptStart.y);
155         if (nDiff != -1)
156                 worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
157
158         if (!worddiffs.empty())
159         {
160                 bool bGoToNextWordDiff = true;
161                 if (EqualCurrentWordDiff(nActivePane, ptStart, ptEnd))
162                 {
163                         nWordDiff = m_CurWordDiff.nWordDiff;
164                 }
165                 else
166                 {
167                         if (!bReversed)
168                         {
169                                 for (nWordDiff = 0; nWordDiff < worddiffs.size(); ++nWordDiff)
170                                 {
171                                         auto& worddiff = worddiffs[nWordDiff];
172                                         if (worddiff.beginline[nActivePane] <= ptStart.y && ptStart.y <= worddiff.endline[nActivePane])
173                                         {
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))
177                                                 {
178                                                         bGoToNextWordDiff = false;
179                                                         break;
180                                                 }
181                                         }
182                                 }
183                         }
184                         else
185                         {
186                                 for (nWordDiff = worddiffs.size() - 1; nWordDiff != static_cast<size_t>(-1); --nWordDiff)
187                                 {
188                                         auto& worddiff = worddiffs[nWordDiff];
189                                         if (worddiff.beginline[nActivePane] <= ptStart.y && ptStart.y <= worddiff.endline[nActivePane])
190                                         {
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))
194                                                 {
195                                                         bGoToNextWordDiff = false;
196                                                         break;
197                                                 }
198                                         }
199                                 }
200                         }
201                 }
202                 if (bGoToNextWordDiff)
203                 {
204                         if (!bReversed)
205                         {
206                                 if (nWordDiff < worddiffs.size())
207                                         ++nWordDiff;
208
209                                 if (nWordDiff == worddiffs.size())
210                                 {
211                                         m_diffList.GetDiff(nDiff, di);
212                                         ptStart.y = di.dend;
213                                         worddiffs.clear();
214                                 }
215                         }
216                         else
217                         {
218                                 if (nWordDiff == 0 || nWordDiff == static_cast<size_t>(-1))
219                                 {
220                                         m_diffList.GetDiff(nDiff, di);
221                                         ptStart.y = di.dbegin;
222                                         worddiffs.clear();
223                                 }
224                                 else
225                                         --nWordDiff;
226                         }
227                 }
228         }
229         if (worddiffs.empty())
230         {
231                 if (!bReversed)
232                 {
233                         nDiff = m_diffList.LineToDiff(ptStart.y);
234                         if (nDiff == -1)
235                         {
236                                 m_diffList.GetNextDiff(ptStart.y, nDiff);
237                                 if (nDiff == -1)
238                                         nDiff = 0;
239                         }
240                         else
241                         {
242                                 nDiff = (nDiff + 1) % m_diffList.GetSize();
243                         }
244                         m_diffList.GetDiff(nDiff, di);
245                         worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
246                         nWordDiff = 0;
247                 }
248                 else
249                 {
250                         nDiff = m_diffList.LineToDiff(ptStart.y);
251                         if (nDiff == -1)
252                         {
253                                 m_diffList.GetPrevDiff(ptStart.y, nDiff);
254                                 if (nDiff == -1)
255                                         nDiff = m_diffList.GetSize() - 1;
256                         }
257                         else
258                         {
259                                 nDiff = (nDiff + m_diffList.GetSize() - 1) % m_diffList.GetSize();
260                         }
261                         m_diffList.GetDiff(nDiff, di);
262                         worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
263                         nWordDiff = worddiffs.size() - 1;
264                 }
265                 if (worddiffs.empty())
266                 {
267                         if (nDiff == -1)
268                                 return;
269                         for (file = 0; file < m_nBuffers; file++)
270                         {
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;
276                                 else
277                                         rc[file].second.y = di.dbegin;
278                         }
279                         nWordDiff = static_cast<size_t>(-1);
280                 }
281         }
282
283         if (nWordDiff != static_cast<size_t>(-1))
284         {
285                 auto& worddiff = worddiffs[nWordDiff];
286                 for (file = 0; file < m_nBuffers; file++)
287                 {
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];
292                 }
293         }
294
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;
300 }
301
302 void CMergeDoc::ClearWordDiffCache(int nDiff/* = -1 */)
303 {
304         if (nDiff == -1)
305         {
306                 m_cacheWordDiffs.clear();
307         }
308         else
309         {
310                 std::map<int, std::vector<WordDiff> >::iterator it = m_cacheWordDiffs.find(nDiff);
311                 if (it != m_cacheWordDiffs.end())
312                         m_cacheWordDiffs.erase(it);
313         }
314 }
315
316 std::vector<WordDiff> CMergeDoc::GetWordDiffArrayInDiffBlock(int nDiff)
317 {
318         DIFFRANGE cd;
319         m_diffList.GetDiff(nDiff, cd);
320
321         bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
322         if (!diffPerLine)
323                 return GetWordDiffArray(cd.dbegin);
324
325         std::vector<WordDiff> worddiffs;
326         for (int nLine = cd.dbegin; nLine <= cd.dend; ++nLine)
327         {
328                 std::vector<WordDiff> worddiffsPerLine = GetWordDiffArray(nLine);
329                 worddiffs.insert(worddiffs.end(), worddiffsPerLine.begin(), worddiffsPerLine.end());
330         }
331         return worddiffs;
332 }
333
334 std::vector<WordDiff>
335 CMergeDoc::GetWordDiffArrayInRange(const int begin[3], const int end[3], int pane1/*=-1*/, int pane2/*=-1*/)
336 {
337         DIFFOPTIONS diffOptions = {0};
338         m_diffWrapper.GetOptions(&diffOptions);
339         String str[3];
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 };
345         else
346                 panes = std::vector<int>{ pane1, pane2 };
347         for (size_t i = 0; i < panes.size(); ++i)
348         {
349                 int file = panes[i];
350                 int nLineBegin = begin[file];
351                 int nLineEnd = end[file];
352                 if (nLineEnd >= m_ptBuf[file]->GetLineCount())
353                         return worddiffs;
354                 nOffsets[file].reset(new int[nLineEnd - nLineBegin + 1]);
355                 String strText;
356                 if (nLineBegin <= nLineEnd)
357                 {
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;
362                 }
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);
366         }
367
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();
374
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);
378
379         std::vector<strdiff::wdiff>::iterator it;
380         for (it = wdiffs.begin(); it != wdiffs.end(); ++it)
381         {
382                 WordDiff wd;
383                 for (size_t i = 0; i < panes.size(); ++i)
384                 {
385                         int file = panes[i];
386                         int nLineBegin = begin[file];
387                         int nLineEnd = end[file];
388                         int nLine;
389                         for (nLine = nLineBegin; nLine < nLineEnd; nLine++)
390                         {
391                                 if (it->begin[i] == nOffsets[file][nLine-nLineBegin] || it->begin[i] < nOffsets[file][nLine-nLineBegin+1])
392                                         break;
393                         }
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])
397                         {
398                                 if (wd.beginline[i] < m_ptBuf[file]->GetLineCount() - 1)
399                                 {
400                                         wd.begin[i] = 0;
401                                         wd.beginline[i]++;
402                                 }
403                                 else
404                                 {
405                                         wd.begin[i] = m_ptBuf[file]->GetLineLength(nLine);
406                                 }
407                         }
408
409                         for (; nLine < nLineEnd; nLine++)
410                         {
411                                 if (it->end[i] + 1 == nOffsets[file][nLine-nLineBegin] || it->end[i] + 1 < nOffsets[file][nLine-nLineBegin+1])
412                                         break;
413                         }
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])
417                         {
418                                 if (wd.endline[i] < m_ptBuf[file]->GetLineCount() - 1)
419                                 {
420                                         wd.end[i] = 0;
421                                         wd.endline[i]++;
422                                 }
423                                 else
424                                 {
425                                         wd.end[i] = m_ptBuf[file]->GetLineLength(nLine);
426                                 }
427                         }
428                 }
429                 wd.op = it->op;
430
431                 worddiffs.push_back(wd);
432         }
433         return worddiffs;
434 }
435
436 /**
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)
440  */
441 std::vector<WordDiff> CMergeDoc::GetWordDiffArray(int nLineIndex)
442 {
443         int file;
444         DIFFRANGE cd;
445         std::vector<WordDiff> worddiffs;
446
447         for (file = 0; file < m_nBuffers; file++)
448         {
449                 if (nLineIndex >= m_ptBuf[file]->GetLineCount())
450                         return worddiffs;
451         }
452
453         int nDiff = m_diffList.LineToDiff(nLineIndex);
454         if (nDiff == -1)
455                 return worddiffs;
456         std::map<int, std::vector<WordDiff> >::iterator itmap = m_cacheWordDiffs.find(nDiff);
457         if (itmap != m_cacheWordDiffs.end())
458         {
459                 worddiffs.resize((*itmap).second.size());
460                 std::copy((*itmap).second.begin(), (*itmap).second.end(), worddiffs.begin());
461                 return worddiffs;
462         }
463
464         m_diffList.GetDiff(nDiff, cd);
465
466         bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
467
468         int nLineBegin[3]{}, nLineEnd[3]{};
469         if (!diffPerLine)
470         {
471                 for (int pane = 0; pane < m_nBuffers; ++pane)
472                 {
473                         nLineBegin[pane] = cd.dbegin;
474                         nLineEnd[pane] = cd.dend;
475                 }
476         }
477         else
478         {
479                 for (int pane = 0; pane < m_nBuffers; ++pane)
480                 {
481                         nLineBegin[pane] = nLineEnd[pane] = nLineIndex;
482                 }
483         }
484
485         worddiffs = GetWordDiffArrayInRange(nLineBegin, nLineEnd);
486
487         if (!diffPerLine)
488         {
489                 m_cacheWordDiffs[nDiff].resize(worddiffs.size());
490                 std::copy(worddiffs.begin(), worddiffs.end(), m_cacheWordDiffs[nDiff].begin());
491         }
492
493         return worddiffs;
494 }
495