OSDN Git Service

Update TranslationsStatus.*
[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 + 1) % nLineCount;
213                                         worddiffs.clear();
214                                 }
215                         }
216                         else
217                         {
218                                 if (nWordDiff == 0)
219                                 {
220                                         m_diffList.GetDiff(nDiff, di);
221                                         ptStart.y = (di.dbegin - 1) % nLineCount;
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                         worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
245                         nWordDiff = 0;
246                 }
247                 else
248                 {
249                         nDiff = m_diffList.LineToDiff(ptStart.y);
250                         if (nDiff == -1)
251                         {
252                                 m_diffList.GetPrevDiff(ptStart.y, nDiff);
253                                 if (nDiff == -1)
254                                         nDiff = m_diffList.GetSize() - 1;
255                         }
256                         else
257                         {
258                                 nDiff = (nDiff - 1) % m_diffList.GetSize();
259                         }
260                         worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
261                         nWordDiff = worddiffs.size() - 1;
262                 }
263                 if (worddiffs.empty())
264                 {
265                         if (nDiff == -1)
266                                 return;
267                         for (file = 0; file < m_nBuffers; file++)
268                         {
269                                 rc[file].left = 0;
270                                 rc[file].top = di.dbegin;
271                                 rc[file].right = 0;
272                                 if (di.dbegin < nLineCount - 1)
273                                         rc[file].bottom = di.dbegin + 1;
274                                 else
275                                         rc[file].bottom = di.dbegin;
276                         }
277                         nWordDiff = static_cast<size_t>(-1);
278                 }
279         }
280
281         if (nWordDiff != static_cast<size_t>(-1))
282         {
283                 auto& worddiff = worddiffs[nWordDiff];
284                 for (file = 0; file < m_nBuffers; file++)
285                 {
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];
290                 }
291         }
292
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;
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 /**
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)
338  */
339 std::vector<WordDiff> CMergeDoc::GetWordDiffArray(int nLineIndex)
340 {
341         int file;
342         DIFFRANGE cd;
343         std::vector<WordDiff> worddiffs;
344
345         for (file = 0; file < m_nBuffers; file++)
346         {
347                 if (nLineIndex >= m_ptBuf[file]->GetLineCount())
348                         return worddiffs;
349         }
350
351         int nDiff = m_diffList.LineToDiff(nLineIndex);
352         if (nDiff == -1)
353                 return worddiffs;
354         std::map<int, std::vector<WordDiff> >::iterator itmap = m_cacheWordDiffs.find(nDiff);
355         if (itmap != m_cacheWordDiffs.end())
356         {
357                 worddiffs.resize((*itmap).second.size());
358                 std::copy((*itmap).second.begin(), (*itmap).second.end(), worddiffs.begin());
359                 return worddiffs;
360         }
361
362         m_diffList.GetDiff(nDiff, cd);
363
364         DIFFOPTIONS diffOptions = {0};
365         m_diffWrapper.GetOptions(&diffOptions);
366         String str[3];
367         std::unique_ptr<int[]> nOffsets[3];
368         bool diffPerLine = IsDiffPerLine(m_ptBuf[0]->GetTableEditing(), cd);
369
370         int nLineBegin, nLineEnd;
371         if (!diffPerLine)
372         {
373                 nLineBegin = cd.dbegin;
374                 nLineEnd = cd.dend;
375         }
376         else
377         {
378                 nLineBegin = nLineEnd = nLineIndex;
379         }
380
381         for (file = 0; file < m_nBuffers; file++)
382         {
383                 if (nLineEnd >= m_ptBuf[file]->GetLineCount())
384                         return worddiffs;
385                 nOffsets[file].reset(new int[nLineEnd - nLineBegin + 1]);
386                 CString strText;
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());
391
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);
395         }
396
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();
403
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);
406
407         int i;
408         std::vector<strdiff::wdiff>::iterator it;
409         for (i = 0, it = wdiffs.begin(); it != wdiffs.end(); ++i, ++it)
410         {
411                 WordDiff wd;
412                 for (file = 0; file < m_nBuffers; file++)
413                 {
414                         int nLine;
415                         for (nLine = nLineBegin; nLine < nLineEnd; nLine++)
416                         {
417                                 if (it->begin[file] == nOffsets[file][nLine-nLineBegin] || it->begin[file] < nOffsets[file][nLine-nLineBegin+1])
418                                         break;
419                         }
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])
423                         {
424                                 if (wd.beginline[file] < m_ptBuf[file]->GetLineCount() - 1)
425                                 {
426                                         wd.begin[file] = 0;
427                                         wd.beginline[file]++;
428                                 }
429                                 else
430                                 {
431                                         wd.begin[file] = m_ptBuf[file]->GetLineLength(nLine);
432                                 }
433                         }
434
435                         for (; nLine < nLineEnd; nLine++)
436                         {
437                                 if (it->end[file] + 1 == nOffsets[file][nLine-nLineBegin] || it->end[file] + 1 < nOffsets[file][nLine-nLineBegin+1])
438                                         break;
439                         }
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])
443                         {
444                                 if (wd.endline[file] < m_ptBuf[file]->GetLineCount() - 1)
445                                 {
446                                         wd.end[file] = 0;
447                                         wd.endline[file]++;
448                                 }
449                                 else
450                                 {
451                                         wd.end[file] = m_ptBuf[file]->GetLineLength(nLine);
452                                 }
453                         }
454                 }
455                 wd.op = it->op;
456
457                 worddiffs.push_back(wd);
458         }
459
460         if (!diffPerLine)
461         {
462                 m_cacheWordDiffs[nDiff].resize(worddiffs.size());
463                 std::copy(worddiffs.begin(), worddiffs.end(), m_cacheWordDiffs[nDiff].begin());
464         }
465
466         return worddiffs;
467 }
468