OSDN Git Service

Remove unused resource IDs
[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 CRect & rc)
30 {
31         if (rc.top == -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.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());
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         CRect rc[3];
54
55         Computelinediff(pView, rc, bReversed);
56
57         if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.top == -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         CRect rc[3];
76
77         Computelinediff(pView, rc, bReversed);
78
79         if (std::all_of(rc, rc + m_nBuffers, [](auto& rc) { return rc.top == -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, CRect rc[], bool bReversed)
139 {
140         int file;
141         for (file = 0; file < m_nBuffers; file++)
142                 rc[file].top = -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].left = 0;
272                                 rc[file].top = di.dbegin;
273                                 rc[file].right = 0;
274                                 if (di.dbegin < nLineCount - 1)
275                                         rc[file].bottom = di.dbegin + 1;
276                                 else
277                                         rc[file].bottom = 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].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];
292                 }
293         }
294
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;
302 }
303
304 void CMergeDoc::ClearWordDiffCache(int nDiff/* = -1 */)
305 {
306         if (nDiff == -1)
307         {
308                 m_cacheWordDiffs.clear();
309         }
310         else
311         {
312                 std::map<int, std::vector<WordDiff> >::iterator it = m_cacheWordDiffs.find(nDiff);
313                 if (it != m_cacheWordDiffs.end())
314                         m_cacheWordDiffs.erase(it);
315         }
316 }
317
318 std::vector<WordDiff> CMergeDoc::GetWordDiffArrayInDiffBlock(int nDiff)
319 {
320         DIFFRANGE cd;
321         m_diffList.GetDiff(nDiff, cd);
322
323         bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
324         if (!diffPerLine)
325                 return GetWordDiffArray(cd.dbegin);
326
327         std::vector<WordDiff> worddiffs;
328         for (int nLine = cd.dbegin; nLine <= cd.dend; ++nLine)
329         {
330                 std::vector<WordDiff> worddiffsPerLine = GetWordDiffArray(nLine);
331                 worddiffs.insert(worddiffs.end(), worddiffsPerLine.begin(), worddiffsPerLine.end());
332         }
333         return worddiffs;
334 }
335
336 std::vector<WordDiff>
337 CMergeDoc::GetWordDiffArrayInRange(const int begin[3], const int end[3], int pane1/*=-1*/, int pane2/*=-1*/)
338 {
339         DIFFOPTIONS diffOptions = {0};
340         m_diffWrapper.GetOptions(&diffOptions);
341         String str[3];
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 };
347         else
348                 panes = std::vector<int>{ pane1, pane2 };
349         for (size_t i = 0; i < panes.size(); ++i)
350         {
351                 int file = panes[i];
352                 int nLineBegin = begin[file];
353                 int nLineEnd = end[file];
354                 if (nLineEnd >= m_ptBuf[file]->GetLineCount())
355                         return worddiffs;
356                 nOffsets[file].reset(new int[nLineEnd - nLineBegin + 1]);
357                 CString strText;
358                 if (nLineBegin <= nLineEnd)
359                 {
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;
364                 }
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);
368         }
369
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();
376
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);
380
381         std::vector<strdiff::wdiff>::iterator it;
382         for (it = wdiffs.begin(); it != wdiffs.end(); ++it)
383         {
384                 WordDiff wd;
385                 for (size_t i = 0; i < panes.size(); ++i)
386                 {
387                         int file = panes[i];
388                         int nLineBegin = begin[file];
389                         int nLineEnd = end[file];
390                         int nLine;
391                         for (nLine = nLineBegin; nLine < nLineEnd; nLine++)
392                         {
393                                 if (it->begin[i] == nOffsets[file][nLine-nLineBegin] || it->begin[i] < nOffsets[file][nLine-nLineBegin+1])
394                                         break;
395                         }
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])
399                         {
400                                 if (wd.beginline[i] < m_ptBuf[file]->GetLineCount() - 1)
401                                 {
402                                         wd.begin[i] = 0;
403                                         wd.beginline[i]++;
404                                 }
405                                 else
406                                 {
407                                         wd.begin[i] = m_ptBuf[file]->GetLineLength(nLine);
408                                 }
409                         }
410
411                         for (; nLine < nLineEnd; nLine++)
412                         {
413                                 if (it->end[i] + 1 == nOffsets[file][nLine-nLineBegin] || it->end[i] + 1 < nOffsets[file][nLine-nLineBegin+1])
414                                         break;
415                         }
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])
419                         {
420                                 if (wd.endline[i] < m_ptBuf[file]->GetLineCount() - 1)
421                                 {
422                                         wd.end[i] = 0;
423                                         wd.endline[i]++;
424                                 }
425                                 else
426                                 {
427                                         wd.end[i] = m_ptBuf[file]->GetLineLength(nLine);
428                                 }
429                         }
430                 }
431                 wd.op = it->op;
432
433                 worddiffs.push_back(wd);
434         }
435         return worddiffs;
436 }
437
438 /**
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)
442  */
443 std::vector<WordDiff> CMergeDoc::GetWordDiffArray(int nLineIndex)
444 {
445         int file;
446         DIFFRANGE cd;
447         std::vector<WordDiff> worddiffs;
448
449         for (file = 0; file < m_nBuffers; file++)
450         {
451                 if (nLineIndex >= m_ptBuf[file]->GetLineCount())
452                         return worddiffs;
453         }
454
455         int nDiff = m_diffList.LineToDiff(nLineIndex);
456         if (nDiff == -1)
457                 return worddiffs;
458         std::map<int, std::vector<WordDiff> >::iterator itmap = m_cacheWordDiffs.find(nDiff);
459         if (itmap != m_cacheWordDiffs.end())
460         {
461                 worddiffs.resize((*itmap).second.size());
462                 std::copy((*itmap).second.begin(), (*itmap).second.end(), worddiffs.begin());
463                 return worddiffs;
464         }
465
466         m_diffList.GetDiff(nDiff, cd);
467
468         bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
469
470         int nLineBegin[3]{}, nLineEnd[3]{};
471         if (!diffPerLine)
472         {
473                 for (int pane = 0; pane < m_nBuffers; ++pane)
474                 {
475                         nLineBegin[pane] = cd.dbegin;
476                         nLineEnd[pane] = cd.dend;
477                 }
478         }
479         else
480         {
481                 for (int pane = 0; pane < m_nBuffers; ++pane)
482                 {
483                         nLineBegin[pane] = nLineEnd[pane] = nLineIndex;
484                 }
485         }
486
487         worddiffs = GetWordDiffArrayInRange(nLineBegin, nLineEnd);
488
489         if (!diffPerLine)
490         {
491                 m_cacheWordDiffs[nDiff].resize(worddiffs.size());
492                 std::copy(worddiffs.begin(), worddiffs.end(), m_cacheWordDiffs[nDiff].begin());
493         }
494
495         return worddiffs;
496 }
497