OSDN Git Service

Merge remote-tracking branch 'upstream/master' into jp
[winmerge-jp/winmerge-jp.git] / Src / DiffWrapper.cpp
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** 
3  * @file  DiffWrapper.cpp
4  *
5  * @brief Code for DiffWrapper class
6  *
7  * @date  Created: 2003-08-22
8  */
9
10 #include "pch.h"
11 #define NOMINMAX
12 #include "DiffWrapper.h"
13 #include <algorithm>
14 #include <string>
15 #include <cctype>
16 #include <cwctype>
17 #include <map>
18 #include <cassert>
19 #include <exception>
20 #include <vector>
21 #include <list>
22 #include <Poco/Format.h>
23 #include <Poco/Debugger.h>
24 #include <Poco/StringTokenizer.h>
25 #include <Poco/Exception.h>
26 #include "DiffContext.h"
27 #include "coretools.h"
28 #include "DiffList.h"
29 #include "MovedLines.h"
30 #include "FilterList.h"
31 #include "diff.h"
32 #include "Diff3.h"
33 #include "xdiff_gnudiff_compat.h"
34 #include "FileTransform.h"
35 #include "paths.h"
36 #include "CompareOptions.h"
37 #include "FileTextStats.h"
38 #include "FolderCmp.h"
39 #include "Environment.h"
40 #include "PatchHTML.h"
41 #include "UnicodeString.h"
42 #include "unicoder.h"
43 #include "TFile.h"
44 #include "Exceptions.h"
45 #include "parsers/crystallineparser.h"
46 #include "SyntaxColors.h"
47 #include "MergeApp.h"
48 #include "SubstitutionList.h"
49 #include "codepage_detect.h"
50 #include "cio.h"
51
52 using Poco::Debugger;
53 using Poco::format;
54 using Poco::StringTokenizer;
55 using Poco::Exception;
56
57 extern int recursive;
58
59 extern "C" int is_blank_line(char const* pch, char const* limit);
60
61 static void CopyTextStats(const file_data * inf, FileTextStats * myTextStats);
62 static void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData);
63
64 constexpr char* FILTERED_LINE = "!" "c0d5089f" "-" "3d91" "-" "4d69" "-" "b406" "-" "dc5a5b51a4f8";
65
66 /**
67  * @brief Default constructor.
68  * Initializes members.
69  */
70 CDiffWrapper::CDiffWrapper()
71 : m_pFilterCommentsDef(nullptr)
72 , m_bCreatePatchFile(false)
73 , m_bUseDiffList(false)
74 , m_bAddCmdLine(true)
75 , m_bAppendFiles(false)
76 , m_nDiffs(0)
77 , m_infoPrediffer(nullptr)
78 , m_pDiffList(nullptr)
79 , m_bPathsAreTemp(false)
80 , m_pFilterList(nullptr)
81 , m_pSubstitutionList{nullptr}
82 , m_bPluginsEnabled(false)
83 , m_status()
84 , m_codepage(ucr::CP_UTF_8)
85 , m_xdlFlags(0)
86 {
87         // character that ends a line.  Currently this is always `\n'
88         line_end_char = '\n';
89 }
90
91 /**
92  * @brief Destructor.
93  */
94 CDiffWrapper::~CDiffWrapper() = default;
95
96 /**
97  * @brief Enables/disables patch-file creation and sets filename.
98  * This function enables or disables patch file creation. When
99  * @p filename is empty, patch files are disabled.
100  * @param [in] filename Filename for patch file, or empty string.
101  */
102 void CDiffWrapper::SetCreatePatchFile(const String &filename)
103 {
104         if (filename.empty())
105         {
106                 m_bCreatePatchFile = false;
107                 m_sPatchFile.clear();
108         }
109         else
110         {
111                 m_bCreatePatchFile = true;
112                 m_sPatchFile = filename;
113                 strutils::replace(m_sPatchFile, _T("/"), _T("\\"));
114         }
115 }
116
117 /**
118  * @brief Enables/disabled DiffList creation ands sets DiffList.
119  * This function enables or disables DiffList creation. When
120  * @p diffList is `nullptr`, a difflist was not created. When valid 
121  * DiffList pointer is given, compare results are stored into it.
122  * @param [in] diffList Pointer to DiffList getting compare results.
123  */
124 void CDiffWrapper::SetCreateDiffList(DiffList *diffList)
125 {
126         if (diffList == nullptr)
127         {
128                 m_bUseDiffList = false;
129                 m_pDiffList = nullptr;
130         }
131         else
132         {
133                 m_bUseDiffList = true;
134                 m_pDiffList = diffList;
135         }
136 }
137
138 /**
139  * @brief Returns current set of options used by diff-engine.
140  * This function converts internally used diff-options to
141  * format used outside CDiffWrapper and returns them.
142  * @param [in,out] options Pointer to structure getting used options.
143  */
144 void CDiffWrapper::GetOptions(DIFFOPTIONS *options) const
145 {
146         assert(options != nullptr);
147         DIFFOPTIONS tmpOptions = {0};
148         m_options.GetAsDiffOptions(tmpOptions);
149         *options = tmpOptions;
150 }
151
152 /**
153  * @brief Set options for Diff-engine.
154  * This function converts given options to format CDiffWrapper uses
155  * internally and stores them.
156  * @param [in] options Pointer to structure having new options.
157  */
158 void CDiffWrapper::SetOptions(const DIFFOPTIONS *options)
159 {
160         assert(options != nullptr);
161         m_options.SetFromDiffOptions(*options);
162         m_xdlFlags = make_xdl_flags(m_options);
163 }
164
165 void CDiffWrapper::SetPrediffer(const PrediffingInfo * prediffer /*= nullptr*/)
166 {
167         // all flags are set correctly during the construction
168         m_infoPrediffer.reset(new PrediffingInfo);
169
170         if (prediffer != nullptr)
171                 *m_infoPrediffer = *prediffer;
172 }
173
174 /**
175  * @brief Set options used for patch-file creation.
176  * @param [in] options Pointer to structure having new options.
177  */
178 void CDiffWrapper::SetPatchOptions(const PATCHOPTIONS *options)
179 {
180         assert(options != nullptr);
181         m_options.m_contextLines = options->nContext;
182
183         switch (options->outputStyle)
184         {
185         case OUTPUT_NORMAL:
186                 m_options.m_outputStyle = DIFF_OUTPUT_NORMAL;
187                 break;
188         case OUTPUT_CONTEXT:
189                 m_options.m_outputStyle = DIFF_OUTPUT_CONTEXT;
190                 break;
191         case OUTPUT_UNIFIED:
192                 m_options.m_outputStyle = DIFF_OUTPUT_UNIFIED;
193                 break;
194         case OUTPUT_HTML:
195                 m_options.m_outputStyle = DIFF_OUTPUT_HTML;
196                 break;
197         default:
198                 throw "Unknown output style!";
199                 break;
200         }
201
202         m_bAddCmdLine = options->bAddCommandline;
203 }
204
205 /**
206  * @brief Enables/disables moved block detection.
207  * @param [in] bDetectMovedBlocks If true moved blocks are detected.
208  */
209 void CDiffWrapper::SetDetectMovedBlocks(bool bDetectMovedBlocks)
210 {
211         if (bDetectMovedBlocks)
212         {
213                 if (m_pMovedLines[0] == nullptr)
214                 {
215                         m_pMovedLines[0].reset(new MovedLines);
216                         m_pMovedLines[1].reset(new MovedLines);
217                         m_pMovedLines[2].reset(new MovedLines);
218                 }
219         }
220         else
221         {
222                 m_pMovedLines[0].reset();
223                 m_pMovedLines[1].reset();
224                 m_pMovedLines[2].reset();
225         }
226 }
227
228 static String convertToTString(const char* start, const char* end)
229 {
230         if (!ucr::CheckForInvalidUtf8(start, end - start))
231         {
232                 return ucr::toTString(std::string(start, end));
233         }
234         else
235         {
236                 bool lossy = false;
237                 String text;
238                 ucr::maketstring(text, start, end - start, -1, &lossy);
239                 return text;
240         }
241 }
242
243 static unsigned GetLastLineCookie(unsigned dwCookie, int startLine, int endLine, const char **linbuf, CrystalLineParser::TextDefinition* enuType)
244 {
245         if (!enuType)
246                 return dwCookie;
247         for (int i = startLine; i <= endLine; ++i)
248         {
249                 String text = convertToTString(linbuf[i], linbuf[i + 1]);
250                 int nActualItems = 0;
251                 std::vector<CrystalLineParser::TEXTBLOCK> blocks(text.length());
252                 dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), static_cast<int>(text.length()), blocks.data(), nActualItems);
253         }
254         return dwCookie;
255 }
256
257 static unsigned GetCommentsFilteredText(unsigned dwCookie, int startLine, int endLine, const char **linbuf, std::string& filtered, CrystalLineParser::TextDefinition* enuType)
258 {
259         String filteredT;
260         for (int i = startLine; i <= endLine; ++i)
261         {
262                 String text = convertToTString(linbuf[i], linbuf[i + 1]);
263                 unsigned textlen = static_cast<unsigned>(text.size());
264                 if (!enuType)
265                 {
266                         filteredT += text;
267                 }
268                 else
269                 {
270                         int nActualItems = 0;
271                         std::vector<CrystalLineParser::TEXTBLOCK> blocks(textlen);
272                         dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), textlen, blocks.data(), nActualItems);
273
274                         if (nActualItems == 0)
275                         {
276                                 filteredT += text;
277                         }
278                         else
279                         {
280                                 for (int j = 0; j < nActualItems; ++j)
281                                 {
282                                         CrystalLineParser::TEXTBLOCK& block = blocks[j];
283                                         if (block.m_nColorIndex != COLORINDEX_COMMENT)
284                                         {
285                                                 unsigned blocklen = (j < nActualItems - 1) ? (blocks[j + 1].m_nCharPos - block.m_nCharPos) : textlen - block.m_nCharPos;
286                                                 filteredT.append(text.c_str() + block.m_nCharPos, blocklen);
287                                         }
288                                 }
289
290                                 if (blocks[nActualItems - 1].m_nColorIndex == COLORINDEX_COMMENT)
291                                 {
292                                         // If there is an inline comment, the EOL for that line will be deleted, so add the EOL.
293                                         size_t fullLen = linbuf[i + 1] - linbuf[i];
294                                         size_t len = linelen(linbuf[i], fullLen);
295                                         for (size_t j = len; j < fullLen; ++j)
296                                                 filteredT += linbuf[i][j];
297                                 }
298                         }
299                 }
300         }
301
302         filtered = ucr::toUTF8(filteredT);
303
304         return dwCookie;
305 }
306
307 /**
308  * @brief Replace a string inside a string with another string.
309  * This function searches for a string inside another string an if found,
310  * replaces it with another string. Function can replace several instances
311  * of the string inside one string.
312  * @param [in,out] target A string containing another string to replace.
313  * @param [in] find A string to search and replace with another (@p replace).
314  * @param [in] replace A string used to replace original (@p find).
315  */
316 void Replace(std::string &target, const std::string &find, const std::string &replace)
317 {
318         const std::string::size_type find_len = find.length();
319         const std::string::size_type replace_len = replace.length();
320         std::string::size_type pos = 0;
321         while ((pos = target.find(find, pos)) != std::string::npos)
322         {
323                 target.replace(pos, find_len, replace);
324                 pos += replace_len;
325         }
326 }
327
328 /**
329  * @brief Replace the characters that matche characters specified in its arguments
330  * @param [in,out] str - A string containing another string to replace.
331  * @param [in] chars - characters to search for
332  * @param [in] rep - String to replace
333  */
334 static void ReplaceChars(std::string & str, const char* chars, const char *rep)
335 {
336         std::string::size_type pos = 0;
337         size_t replen = strlen(rep);
338         while ((pos = str.find_first_of(chars, pos)) != std::string::npos)
339         {
340                 std::string::size_type posend = str.find_first_not_of(chars, pos);
341                 if (posend != String::npos)
342                         str.replace(pos, posend - pos, rep);
343                 else
344                         str.replace(pos, str.length() - pos, rep);
345                 pos += replen;
346         }
347 }
348
349 /**
350  * @brief The main entry for post filtering.  Performs post-filtering, by setting comment blocks to trivial
351  * @param [in, out]  thisob     Current change
352  * @return Number of trivial diffs inserted
353  */
354 int CDiffWrapper::PostFilter(PostFilterContext& ctxt, change* thisob, const file_data *file_data_ary) const
355 {
356         const int first0 = thisob->line0;
357         const int first1 = thisob->line1;
358         const int last0 = first0 + thisob->deleted - 1;
359         const int last1 = first1 + thisob->inserted - 1;
360         int trans_a0 = 0, trans_b0 = 0, trans_a1 = 0, trans_b1 = 0;
361         translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
362         translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
363         const int qtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
364         const int qtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
365         const int lineNumberLeft = trans_a0 - 1;
366         const int lineNumberRight = trans_a1 - 1;
367         
368         std::string lineDataLeft, lineDataRight;
369
370         if (m_options.m_filterCommentsLines)
371         {
372                 ctxt.dwCookieLeft = GetLastLineCookie(ctxt.dwCookieLeft,
373                         ctxt.nParsedLineEndLeft + 1, lineNumberLeft - 1, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, m_pFilterCommentsDef);
374                 ctxt.dwCookieRight = GetLastLineCookie(ctxt.dwCookieRight,
375                         ctxt.nParsedLineEndRight + 1, lineNumberRight - 1, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, m_pFilterCommentsDef);
376
377                 ctxt.nParsedLineEndLeft = lineNumberLeft + qtyLinesLeft - 1;
378                 ctxt.nParsedLineEndRight = lineNumberRight + qtyLinesRight - 1;;
379
380                 ctxt.dwCookieLeft = GetCommentsFilteredText(ctxt.dwCookieLeft,
381                         lineNumberLeft, ctxt.nParsedLineEndLeft, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, lineDataLeft, m_pFilterCommentsDef);
382                 ctxt.dwCookieRight = GetCommentsFilteredText(ctxt.dwCookieRight,
383                         lineNumberRight, ctxt.nParsedLineEndRight, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, lineDataRight, m_pFilterCommentsDef);
384         }
385         else
386         {
387                 lineDataLeft.assign(file_data_ary[0].linbuf[lineNumberLeft + file_data_ary[0].linbuf_base],
388                         file_data_ary[0].linbuf[lineNumberLeft + qtyLinesLeft + file_data_ary[0].linbuf_base]
389                         - file_data_ary[0].linbuf[lineNumberLeft + file_data_ary[0].linbuf_base]);
390                 lineDataRight.assign(file_data_ary[1].linbuf[lineNumberRight + file_data_ary[1].linbuf_base],
391                         file_data_ary[1].linbuf[lineNumberRight + qtyLinesRight + file_data_ary[1].linbuf_base]
392                         - file_data_ary[1].linbuf[lineNumberRight + file_data_ary[1].linbuf_base]);
393         }
394
395         if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
396         {
397                 // Match lines against regular expression filters
398                 // Our strategy is that every line in both sides must
399                 // match regexp before we mark difference as ignored.
400                 bool match1 = RegExpFilter(lineDataLeft);
401                 bool match2 = RegExpFilter(lineDataRight);
402                 if (match1 && match2)
403                 {
404                         thisob->trivial = 1;
405                         return 0;
406                 }
407         }
408
409         if (m_pSubstitutionList)
410         {
411                 lineDataLeft = m_pSubstitutionList->Subst(lineDataLeft, m_codepage);
412                 lineDataRight = m_pSubstitutionList->Subst(lineDataRight, m_codepage);
413         }
414
415         if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_ALL)
416         {
417                 //Ignore character case
418                 ReplaceChars(lineDataLeft, " \t", "");
419                 ReplaceChars(lineDataRight, " \t", "");
420         }
421         else if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_CHANGE)
422         {
423                 //Ignore change in whitespace char count
424                 ReplaceChars(lineDataLeft, " \t", " ");
425                 ReplaceChars(lineDataRight, " \t", " ");
426         }
427
428         if (m_options.m_bIgnoreNumbers)
429         {
430                 //Ignore number character case
431                 ReplaceChars(lineDataLeft, "0123456789", "");
432                 ReplaceChars(lineDataRight, "0123456789", "");
433         }
434         if (m_options.m_bIgnoreCase)
435         {
436                 //ignore case
437                 for (std::string::iterator pb = lineDataLeft.begin(), pe = lineDataLeft.end(); pb != pe; ++pb) 
438                         *pb = static_cast<char>(::toupper(*pb));
439                 for (std::string::iterator pb = lineDataRight.begin(), pe = lineDataRight.end(); pb != pe; ++pb) 
440                         *pb = static_cast<char>(::toupper(*pb));
441         }
442         if (m_options.m_bIgnoreEOLDifference)
443         {
444                 Replace(lineDataLeft, "\r\n", "\n");
445                 Replace(lineDataLeft, "\r", "\n");
446                 Replace(lineDataRight, "\r\n", "\n");
447                 Replace(lineDataRight, "\r", "\n");
448         }
449
450         // If both match after filtering, mark this diff hunk as trivial and return.
451         if (lineDataLeft == lineDataRight)
452         {
453                 //only difference is trival
454                 thisob->trivial = 1;
455                 return 0;
456         }
457
458         auto SplitLines = [](const std::string& lines) -> std::vector<std::string_view>
459                 {
460                         std::vector<std::string_view> result;
461                         const char* line = lines.c_str();
462                         for (size_t i = 0; i < lines.length(); ++i)
463                         {
464                                 char c = lines[i];
465                                 if (c == '\r')
466                                 {
467                                         if (i + 1 < lines.length() && lines[i + 1] == '\n')
468                                                 i++;
469                                         result.emplace_back(line, lines.c_str() + i + 1 - line);
470                                         line = lines.c_str() + i + 1;
471                                 }
472                                 else if (c == '\n')
473                                 {
474                                         result.emplace_back(line, lines.c_str() + i + 1 - line);
475                                         line = lines.c_str() + i + 1;
476                                 }
477                         }
478                         if (!lines.empty() && (lines.back() != '\r' && lines.back() != '\n'))
479                                 result.emplace_back(line, lines.c_str() + lines.length() - line);
480                         return result; 
481                 };
482
483         std::vector<std::string_view> leftLines = SplitLines(lineDataLeft);
484         std::vector<std::string_view> rightLines = SplitLines(lineDataRight);
485
486         if (qtyLinesLeft != leftLines.size() || qtyLinesRight != rightLines.size())
487                 return 0;
488
489         // If both do not match as a result of filtering, some lines may match,
490         // so diff calculation is performed again using the filtered lines.
491         change* script = diff_2_buffers_xdiff(
492                 lineDataLeft.c_str(), lineDataLeft.length(),
493                 lineDataRight.c_str(), lineDataRight.length(), m_xdlFlags);
494
495         auto TranslateLineNumbers = [](change* thisob, change* script)
496                 {
497                         assert(thisob && script);
498                         for (change* cur = script; cur; cur = cur->link)
499                         {
500                                 cur->line0 += thisob->line0;
501                                 cur->line1 += thisob->line1;
502                         }
503                 };
504
505         // Insert lines with no differences as trivial diffs after filtering
506         auto InsertTrivialChanges = [](change* thisob, change* script) -> int
507                 {
508                         assert(thisob && script);
509                         int l0 = thisob->line0;
510                         int l1 = thisob->line1;
511                         change* first = script;
512                         change* prev = nullptr;
513                         int nTrivialInserts = 0;
514                         for (change* cur = script; cur; cur = cur->link)
515                         {
516                                 if (l0 < cur->line0 || l1 < cur->line1)
517                                 {
518                                         nTrivialInserts++;
519                                         change *newob = (change *)xmalloc(sizeof (change));
520                                         newob->line0 = l0;
521                                         newob->line1 = l1;
522                                         newob->deleted = cur->line0 - l0;
523                                         newob->inserted = cur->line1 - l1;
524                                         newob->trivial = 1;
525                                         newob->match0 = -1;
526                                         newob->match1 = -1;
527                                         if (cur == first)
528                                         {
529                                                 std::swap(newob->line0, cur->line0);
530                                                 std::swap(newob->line1, cur->line1);
531                                                 std::swap(newob->deleted, cur->deleted);
532                                                 std::swap(newob->inserted, cur->inserted);
533                                                 std::swap(newob->trivial, cur->trivial);
534                                                 std::swap(newob->match0, cur->match0);
535                                                 std::swap(newob->match1, cur->match1);
536                                                 newob->link = cur->link;
537                                                 cur->link = newob;
538                                         }
539                                         else
540                                         {
541                                                 prev->link = newob;
542                                                 newob->link = cur;
543                                         }
544                                 }
545                                 l0 = cur->line0 + cur->deleted;
546                                 l1 = cur->line1 + cur->inserted;
547                                 prev = cur;
548                         }
549                         if (l0 < thisob->line0 + thisob->deleted || l1 < thisob->line1 + thisob->inserted)
550                         {
551                                 nTrivialInserts++;
552                                 change *newob = (change *)xmalloc(sizeof (change));
553                                 prev->link = newob;
554                                 newob->line0 = l0;
555                                 newob->line1 = l1;
556                                 newob->deleted = thisob->line0 + thisob->deleted - l0;
557                                 newob->inserted = thisob->line1 + thisob->inserted - l1;
558                                 newob->trivial = 1;
559                                 newob->match0 = -1;
560                                 newob->match1 = -1;
561                                 newob->link = nullptr;
562                         }
563                         return nTrivialInserts;
564                 };
565
566         // Insert blank lines or filtered lines that are only on one side as trivial diffs. 
567         auto InsertTrivialChanges2 =
568                 [](change* thisob, change* script, bool ignoreBlankLines,
569                    const std::vector<std::string_view>& leftLines,
570                    const std::vector<std::string_view>& rightLines) -> int
571                 {
572                         assert(thisob && script);
573                         auto IsBlankLine = [](const std::string_view& line)
574                                 {
575                                         for (char c : line)
576                                         {
577                                                 if (!std::isspace(static_cast<unsigned char>(c)))
578                                                         return false;
579                                         }
580                                         return true;
581                                 };
582                         int nTrivialInserts = 0;
583                         for (change* cur = script; cur; cur = cur->link)
584                         {
585                                 if (!cur->trivial && cur->deleted != cur->inserted)
586                                 {
587                                         bool ignorable = true;
588                                         if (cur->deleted > cur->inserted)
589                                         {
590                                                 for (int i = cur->line0 + cur->inserted - thisob->line0; i < cur->line0 + cur->deleted - thisob->line0; ++i)
591                                                 {
592                                                         if (!(ignoreBlankLines && IsBlankLine(leftLines[i])) && leftLines[i] != FILTERED_LINE)
593                                                                 ignorable = false;
594                                                 }
595                                                 if (ignorable)
596                                                 {
597                                                         if (cur->inserted == 0)
598                                                         {
599                                                                 cur->trivial = 1;
600                                                         }
601                                                         else
602                                                         {
603                                                                 nTrivialInserts++;
604                                                                 change* newob = (change*)xmalloc(sizeof(change));
605                                                                 newob->line0 = cur->line0 + cur->inserted;
606                                                                 newob->line1 = cur->line1 + cur->inserted;
607                                                                 newob->deleted = cur->deleted - cur->inserted;
608                                                                 newob->inserted = 0;
609                                                                 newob->trivial = 1;
610                                                                 newob->match0 = -1;
611                                                                 newob->match1 = -1;
612                                                                 newob->link = cur->link;
613                                                                 cur->link = newob;
614                                                                 cur->deleted = cur->inserted;
615                                                         }
616                                                 }
617                                         }
618                                         else
619                                         {
620                                                 for (int i = cur->line1 + cur->deleted - thisob->line1; i < cur->line1 + cur->inserted - thisob->line1; ++i)
621                                                 {
622                                                         if (!(ignoreBlankLines && IsBlankLine(rightLines[i])) && rightLines[i] != FILTERED_LINE)
623                                                                 ignorable = false;
624                                                 }
625                                                 if (ignorable)
626                                                 {
627                                                         if (cur->deleted == 0)
628                                                         {
629                                                                 cur->trivial = 1;
630                                                         }
631                                                         else
632                                                         {
633                                                                 nTrivialInserts++;
634                                                                 change* newob = (change*)xmalloc(sizeof(change));
635                                                                 newob->line0 = cur->line0 + cur->deleted;
636                                                                 newob->line1 = cur->line1 + cur->deleted;
637                                                                 newob->deleted = 0;
638                                                                 newob->inserted = cur->inserted - cur->deleted;
639                                                                 newob->trivial = 1;
640                                                                 newob->match0 = -1;
641                                                                 newob->match1 = -1;
642                                                                 newob->link = cur->link;
643                                                                 cur->link = newob;
644                                                                 cur->inserted = cur->deleted;
645                                                         }
646                                                 }
647                                         }
648                                 }
649                         }
650                         return nTrivialInserts;
651                 };
652
653         auto ReplaceChanges = [](change* thisob, change* script)
654                 {
655                         assert(thisob && script);
656                         change* last = nullptr;
657                         for (change* cur = script; cur; cur = cur->link)
658                                 last = cur;
659                         last->link = thisob->link;
660                         thisob->link = script->link;
661                         thisob->line0 = script->line0;
662                         thisob->line1 = script->line1;
663                         thisob->deleted = script->deleted;
664                         thisob->inserted = script->inserted;
665                         thisob->trivial = script->trivial;
666                         thisob->match0 = script->match0;
667                         thisob->match1 = script->match1;
668                         free(script);
669                 };
670
671         TranslateLineNumbers(thisob, script);
672         int nTrivialInserts = InsertTrivialChanges(thisob, script);
673         nTrivialInserts += InsertTrivialChanges2(thisob, script, m_options.m_bIgnoreBlankLines, leftLines, rightLines);
674         ReplaceChanges(thisob, script);
675         return nTrivialInserts;
676 }
677
678 /**
679  * @brief Set source paths for diffing two files.
680  * Sets full paths to two files we are diffing. Paths can be actual user files
681  * or temporary copies of user files. Parameter @p tempPaths tells if paths
682  * are temporary paths that can be deleted.
683  * @param [in] files Files to compare
684  * @param [in] tempPaths Are given paths temporary (can be deleted)?.
685  */
686 void CDiffWrapper::SetPaths(const PathContext &tFiles,
687                 bool tempPaths)
688 {
689         m_files = tFiles;
690         m_bPathsAreTemp = tempPaths;
691 }
692
693 /**
694  * @brief Runs diff-engine.
695  */
696 bool CDiffWrapper::RunFileDiff()
697 {
698         PathContext aFiles = m_files;
699         int file;
700         for (file = 0; file < m_files.GetSize(); file++)
701                 aFiles[file] = paths::ToWindowsPath(aFiles[file]);
702
703         bool bRet = true;
704         String strFileTemp[3];
705         std::copy(m_files.begin(), m_files.end(), strFileTemp);
706         
707         m_options.SetToDiffUtils();
708
709         if (m_bUseDiffList)
710                 m_nDiffs = m_pDiffList->GetSize();
711
712         for (file = 0; file < aFiles.GetSize(); file++)
713         {
714                 if (m_bPluginsEnabled)
715                 {
716                         // Do the preprocessing now, overwrite the temp files
717                         // NOTE: FileTransform_UCS2ToUTF8() may create new temp
718                         // files and return new names, those created temp files
719                         // are deleted in end of function.
720
721                         // this can only fail if the data can not be saved back (no more
722                         // place on disk ???) What to do then ??
723                         if (m_infoPrediffer && !m_infoPrediffer->Prediffing(strFileTemp[file], m_sToFindPrediffer, m_bPathsAreTemp, { strFileTemp[file] }))
724                         {
725                                 // display a message box
726                                 String sError = strutils::format_string2(
727                                         _("An error occurred while prediffing the file '%1' with the plugin '%2'. The prediffing is not applied any more."),
728                                         strFileTemp[file].c_str(),
729                                         m_infoPrediffer->GetPluginPipeline().c_str());
730                                 AppErrorMessageBox(sError);
731                                 // don't use any more this prediffer
732                                 m_infoPrediffer->ClearPluginPipeline();
733                         }
734                 }
735         }
736
737         struct change *script = nullptr;
738         struct change *script10 = nullptr;
739         struct change *script12 = nullptr;
740         DiffFileData diffdata, diffdata10, diffdata12;
741         int bin_flag = 0, bin_flag10 = 0, bin_flag12 = 0;
742
743         if (aFiles.GetSize() == 2)
744         {
745                 diffdata.SetDisplayFilepaths(aFiles[0], aFiles[1]); // store true names for diff utils patch file
746                 // This opens & fstats both files (if it succeeds)
747                 if (!diffdata.OpenFiles(strFileTemp[0], strFileTemp[1]))
748                 {
749                         return false;
750                 }
751
752                 // Compare the files, if no error was found.
753                 // Last param (bin_file) is `nullptr` since we don't
754                 // (yet) need info about binary sides.
755                 bRet = Diff2Files(&script, &diffdata, &bin_flag, nullptr);
756
757                 // We don't anymore create diff-files for every rescan.
758                 // User can create patch-file whenever one wants to.
759                 // We don't need to waste time. But lets keep this as
760                 // debugging aid. Sometimes it is very useful to see
761                 // what differences diff-engine sees!
762 #ifdef _DEBUG
763                 // throw the diff into a temp file
764                 String sTempPath = env::GetTemporaryPath(); // get path to Temp folder
765                 String path = paths::ConcatPath(sTempPath, _T("Diff.txt"));
766
767                 if (cio::tfopen_s(&outfile, path, _T("w+")) == 0)
768                 {
769                         print_normal_script(script);
770                         fclose(outfile);
771                         outfile = nullptr;
772                 }
773 #endif
774         }
775         else
776         {
777                 diffdata10.SetDisplayFilepaths(aFiles[1], aFiles[0]); // store true names for diff utils patch file
778                 diffdata12.SetDisplayFilepaths(aFiles[1], aFiles[2]); // store true names for diff utils patch file
779
780                 if (!diffdata10.OpenFiles(strFileTemp[1], strFileTemp[0]))
781                 {
782                         return false;
783                 }
784
785                 bRet = Diff2Files(&script10, &diffdata10, &bin_flag10, nullptr);
786
787                 if (!diffdata12.OpenFiles(strFileTemp[1], strFileTemp[2]))
788                 {
789                         return false;
790                 }
791
792                 bRet = Diff2Files(&script12, &diffdata12, &bin_flag12, nullptr);
793         }
794
795         // First determine what happened during comparison
796         // If there were errors or files were binaries, don't bother
797         // creating diff-lists or patches
798         
799         // diff_2_files set bin_flag to -1 if different binary
800         // diff_2_files set bin_flag to +1 if same binary
801
802         file_data * inf = diffdata.m_inf;
803         file_data * inf10 = diffdata10.m_inf;
804         file_data * inf12 = diffdata12.m_inf;
805
806         if (aFiles.GetSize() == 2)
807         {
808                 if (bin_flag != 0)
809                 {
810                         m_status.bBinaries = true;
811                         if (bin_flag != -1)
812                                 m_status.Identical = IDENTLEVEL::ALL;
813                         else
814                                 m_status.Identical = IDENTLEVEL::NONE;
815                 }
816                 else
817                 { // text files according to diffutils, so change script exists
818                         m_status.Identical = (script == 0) ? IDENTLEVEL::ALL : IDENTLEVEL::NONE;
819                         m_status.bBinaries = false;
820                 }
821                 m_status.bMissingNL[0] = !!inf[0].missing_newline;
822                 m_status.bMissingNL[1] = !!inf[1].missing_newline;
823         }
824         else
825         {
826                 m_status.Identical = IDENTLEVEL::NONE;
827                 if (bin_flag10 != 0 || bin_flag12 != 0)
828                 {
829                         m_status.bBinaries = true;
830                         if (bin_flag10 != -1 && bin_flag12 != -1)
831                                 m_status.Identical = IDENTLEVEL::ALL;
832                         else if (bin_flag10 != -1)
833                                 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
834                         else if (bin_flag12 != -1)
835                                 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
836                         else
837                                 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
838                 }
839                 else
840                 { // text files according to diffutils, so change script exists
841                         m_status.bBinaries = false;
842                         if (script10 == nullptr && script12 == nullptr)
843                                 m_status.Identical = IDENTLEVEL::ALL;
844                         else if (script10 == nullptr)
845                                 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
846                         else if (script12 == nullptr)
847                                 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
848                         else
849                                 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
850                 }
851                 m_status.bMissingNL[0] = !!inf10[1].missing_newline;
852                 m_status.bMissingNL[1] = !!inf12[0].missing_newline;
853                 m_status.bMissingNL[2] = !!inf12[1].missing_newline;
854         }
855
856
857         // Create patch file
858         if (!m_status.bBinaries && m_bCreatePatchFile && aFiles.GetSize() == 2)
859         {
860                 WritePatchFile(script, &inf[0]);
861         }
862         
863         // Go through diffs adding them to WinMerge's diff list
864         // This is done on every WinMerge's doc rescan!
865         if (!m_status.bBinaries && m_bUseDiffList)
866         {
867                 if (aFiles.GetSize() == 2)
868                         LoadWinMergeDiffsFromDiffUtilsScript(script, diffdata.m_inf);
869                 else
870                         LoadWinMergeDiffsFromDiffUtilsScript3(
871                                 script10, script12,
872                                 diffdata10.m_inf, diffdata12.m_inf);
873         }                       
874
875         // cleanup the script
876         if (aFiles.GetSize() == 2)
877                 FreeDiffUtilsScript(script);
878         else
879         {
880                 FreeDiffUtilsScript(script10);
881                 FreeDiffUtilsScript(script12);
882         }
883
884         // Done with diffutils filedata
885         if (aFiles.GetSize() == 2)
886         {
887                 diffdata.Close();
888         }
889         else
890         {
891                 diffdata10.Close();
892                 diffdata12.Close();
893         }
894
895         if (m_bPluginsEnabled)
896         {
897                 // Delete temp files transformation functions possibly created
898                 for (file = 0; file < aFiles.GetSize(); file++)
899                 {
900                         if (strutils::compare_nocase(aFiles[file], strFileTemp[file]) != 0)
901                         {
902                                 try
903                                 {
904                                         TFile(strFileTemp[file]).remove();
905                                 }
906                                 catch (Exception& e)
907                                 {
908                                         LogErrorStringUTF8(e.displayText());
909                                 }
910                                 strFileTemp[file].erase();
911                         }
912                 }
913         }
914         return bRet;
915 }
916
917 /**
918  * @brief Add diff to external diff-list
919  */
920 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, unsigned begin0, unsigned end0, unsigned begin1, unsigned end1, OP_TYPE op)
921 {
922         try
923         {
924                 DIFFRANGE dr;
925                 dr.begin[0] = begin0;
926                 dr.end[0] = end0;
927                 dr.begin[1] = begin1;
928                 dr.end[1] = end1;
929                 dr.begin[2] = -1;
930                 dr.end[2] = -1;
931                 dr.op = op;
932                 dr.blank[0] = dr.blank[1] = dr.blank[2] = -1;
933                 pDiffList->AddDiff(dr);
934         }
935         catch (std::exception& e)
936         {
937                 AppErrorMessageBox(ucr::toTString(e.what()));
938         }
939 }
940
941 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, DIFFRANGE &dr)
942 {
943         try
944         {
945                 pDiffList->AddDiff(dr);
946         }
947         catch (std::exception& e)
948         {
949                 AppErrorMessageBox(ucr::toTString(e.what()));
950         }
951 }
952
953 /**
954  * @brief Expand last DIFFRANGE of file by one line to contain last line after EOL.
955  * @param [in] leftBufferLines size of array pane left
956  * @param [in] rightBufferLines size of array pane right
957  * @param [in] left on whitch side we have to insert
958  * @param [in] bIgnoreBlankLines, if true we always add a new diff and mark as trivial
959  */
960 void CDiffWrapper::FixLastDiffRange(int nFiles, int bufferLines[], bool bMissingNL[], bool bIgnoreBlankLines)
961 {
962         DIFFRANGE dr;
963         const int count = m_pDiffList->GetSize();
964         if (count > 0)
965         {
966                 m_pDiffList->GetDiff(count - 1, dr);
967
968                 for (int file = 0; file < nFiles; file++)
969                 {
970                         if (!bMissingNL[file])
971                                 dr.end[file]++;
972                 }
973
974                 m_pDiffList->SetDiff(count - 1, dr);
975         }
976         else 
977         {
978                 // we have to create the DIFF
979                 for (int file = 0; file < nFiles; file++)
980                 {
981                         dr.end[file] = bufferLines[file] - 1;
982                         if (bMissingNL[file])
983                                 dr.begin[file] = dr.end[file];
984                         else
985                                 dr.begin[file] = dr.end[file] + 1;
986                         dr.op = OP_DIFF;
987                         assert(dr.begin[0] == dr.begin[file]);
988                 }
989                 if (bIgnoreBlankLines)
990                         dr.op = OP_TRIVIAL;
991
992                 AddDiffRange(m_pDiffList, dr); 
993         }
994 }
995
996 /**
997  * @brief Returns status-data from diff-engine last run
998  */
999 void CDiffWrapper::GetDiffStatus(DIFFSTATUS *status) const
1000 {
1001         std::memcpy(status, &m_status, sizeof(DIFFSTATUS));
1002 }
1003
1004 /**
1005  * @brief Formats command-line for diff-engine last run (like it was called from command-line)
1006  */
1007 String CDiffWrapper::FormatSwitchString() const
1008 {
1009         String switches;
1010         
1011         switch (m_options.m_outputStyle)
1012         {
1013         case DIFF_OUTPUT_NORMAL:
1014                 switches = _T(" ");
1015                 break;
1016         case DIFF_OUTPUT_CONTEXT:
1017                 switches = (m_options.m_contextLines > 0) ? _T(" -C ") : _T(" -c");
1018                 break;
1019         case DIFF_OUTPUT_UNIFIED:
1020                 switches = (m_options.m_contextLines > 0) ? _T(" -U ") : _T(" -u");
1021                 break;
1022 #if 0
1023         case DIFF_OUTPUT_ED:
1024                 switches = _T(" e");
1025                 break;
1026         case DIFF_OUTPUT_FORWARD_ED:
1027                 switches = _T(" f");
1028                 break;
1029         case DIFF_OUTPUT_RCS:
1030                 switches = _T(" n");
1031                 break;
1032         case DIFF_OUTPUT_IFDEF:
1033                 switches = _T(" D");
1034                 break;
1035         case DIFF_OUTPUT_SDIFF:
1036                 switches = _T(" y");
1037                 break;
1038 #endif
1039         }
1040
1041         if ((m_options.m_outputStyle == DIFF_OUTPUT_CONTEXT || m_options.m_outputStyle == DIFF_OUTPUT_UNIFIED) &&
1042                 m_options.m_contextLines > 0)
1043                 switches += strutils::to_str(m_options.m_contextLines);
1044
1045         if (ignore_all_space_flag > 0)
1046                 switches += _T(" -w");
1047
1048         if (ignore_blank_lines_flag > 0)
1049                 switches += _T(" -B");
1050
1051         if (ignore_case_flag > 0)
1052                 switches += _T(" -i");
1053
1054         if (ignore_space_change_flag > 0)
1055                 switches += _T(" -b");
1056
1057         return switches;
1058 }
1059
1060 /**
1061  * @brief Enables/disables patch-file appending.
1062  * If the file for patch already exists then the patch will be appended to
1063  * existing file.
1064  * @param [in] bAppendFiles If true patch will be appended to existing file.
1065  */
1066 void CDiffWrapper::SetAppendFiles(bool bAppendFiles)
1067 {
1068         m_bAppendFiles = bAppendFiles;
1069 }
1070
1071 /**
1072  * @brief Compare two files using diffutils.
1073  *
1074  * Compare two files (in DiffFileData param) using diffutils. Run diffutils
1075  * inside SEH so we can trap possible error and exceptions. If error or
1076  * execption is trapped, return compare failure.
1077  * @param [out] diffs Pointer to list of change structs where diffdata is stored.
1078  * @param [in] diffData files to compare.
1079  * @param [out] bin_status used to return binary status from compare.
1080  * @param [out] bin_file Returns which file was binary file as bitmap.
1081     So if first file is binary, first bit is set etc. Can be `nullptr` if binary file
1082     info is not needed (faster compare since diffutils don't bother checking
1083     second file if first is binary).
1084  * @return true when compare succeeds, false if error happened during compare.
1085  * @note This function is used in file compare, not folder compare. Similar
1086  * folder compare function is in DiffFileData.cpp.
1087  */
1088 bool CDiffWrapper::Diff2Files(struct change ** diffs, DiffFileData *diffData,
1089         int * bin_status, int * bin_file) const
1090 {
1091         bool bRet = true;
1092         SE_Handler seh;
1093         try
1094         {
1095                 if (m_options.m_diffAlgorithm != DIFF_ALGORITHM_DEFAULT)
1096                 {
1097                         const unsigned xdl_flags = make_xdl_flags(m_options);
1098                         *diffs = diff_2_files_xdiff(diffData->m_inf, bin_status,
1099                                 (m_pMovedLines[0] != nullptr), bin_file, xdl_flags);
1100                         files[0] = diffData->m_inf[0];
1101                         files[1] = diffData->m_inf[1];
1102                 }
1103                 else
1104                 {
1105                         // Diff files. depth is zero because we are not comparing dirs
1106                         *diffs = diff_2_files(diffData->m_inf, 0, bin_status,
1107                                 (m_pMovedLines[0] != nullptr), bin_file);
1108                 }
1109                 CopyDiffutilTextStats(diffData->m_inf, diffData);
1110         }
1111         catch (SE_Exception&)
1112         {
1113                 *diffs = nullptr;
1114                 bRet = false;
1115         }
1116         return bRet;
1117 }
1118
1119 /**
1120  * @brief Free script (the diffutils linked list of differences)
1121  */
1122 void
1123 CDiffWrapper::FreeDiffUtilsScript(struct change * & script)
1124 {
1125         if (script == nullptr) return;
1126         struct change *e=nullptr, *p=nullptr;
1127         // cleanup the script
1128         for (e = script; e != nullptr; e = p)
1129         {
1130                 p = e->link;
1131                 free(e);
1132         }
1133         script = nullptr;
1134 }
1135
1136 /**
1137  * @brief Match regular expression list against given difference.
1138  * This function matches the regular expression list against the difference
1139  * (given as start line and end line). Matching the diff requires that all
1140  * lines in difference match.
1141  * @param [in] StartPos First line of the difference.
1142  * @param [in] endPos Last line of the difference.
1143  * @param [in] FileNo File to match.
1144  * return true if any of the expressions matches.
1145  */
1146 bool CDiffWrapper::RegExpFilter(std::string& lines) const
1147 {
1148         if (m_pFilterList == nullptr)
1149         {       
1150                 throw "CDiffWrapper::RegExpFilter() called when "
1151                         "filterlist doesn't exist (=nullptr)";
1152         }
1153
1154         bool linesMatch = true; // set to false when non-matching line is found.
1155
1156         std::string replaced;
1157         replaced.reserve(lines.length());
1158         size_t pos = 0;
1159         while (pos < lines.length())
1160         {
1161                 const char* string = lines.c_str() + pos;
1162                 while (pos < lines.length() && (lines[pos] != '\r' && lines[pos] != '\n'))
1163                         pos++;
1164                 size_t stringlen = lines.c_str() + pos - string;
1165                 std::string line = std::string(string, stringlen);
1166                 if (!m_pFilterList->Match(line, m_codepage))
1167                 {
1168                         linesMatch = false;
1169                         replaced += line;
1170                 }
1171                 else
1172                 {
1173                         replaced += FILTERED_LINE;
1174                 }
1175                 std::string eol;
1176                 while (pos < lines.length() && (lines[pos] == '\r' || lines[pos] == '\n'))
1177                         eol += lines[pos++];
1178                 replaced += eol;
1179         }
1180         lines = replaced;
1181         return linesMatch;
1182 }
1183
1184 /**
1185  * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1186  */
1187 void
1188 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const file_data * file_data_ary)
1189 {
1190         //Logic needed for Ignore comment option
1191         PostFilterContext ctxt;
1192
1193         struct change *next = script;
1194
1195         const bool usefilters = m_options.m_filterCommentsLines ||
1196                 (m_pFilterList && m_pFilterList->HasRegExps()) ||
1197                 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps());
1198         
1199         while (next != nullptr)
1200         {
1201                 /* Find a set of changes that belong together.  */
1202                 struct change *thisob = next;
1203                 struct change *end = find_change(next);
1204                 
1205                 /* Disconnect them from the rest of the changes,
1206                 making them a hunk, and remember the rest for next iteration.  */
1207                 next = end->link;
1208                 end->link = nullptr;
1209 #ifdef DEBUG
1210                 debug_script(thisob);
1211 #endif
1212
1213                 /* Print thisob hunk.  */
1214                 //(*printfun) (thisob);
1215                 {                                       
1216                         /* Determine range of line numbers involved in each file.  */
1217                         int first0=0, last0=0, first1=0, last1=0, deletes=0, inserts=0;
1218                         analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, file_data_ary);
1219                 
1220                         /* Reconnect the script so it will all be freed properly.  */
1221                         end->link = next;
1222
1223                         if (deletes || inserts || thisob->trivial)
1224                         {
1225                                 OP_TYPE op = OP_NONE;
1226                                 if (deletes && inserts)
1227                                         op = OP_DIFF;
1228                                 else if (deletes || inserts)
1229                                         op = OP_DIFF;
1230                                 else
1231                                         op = OP_TRIVIAL;
1232                                 
1233                                 /* Print the lines that the first file has.  */
1234                                 int trans_a0=0, trans_b0=0, trans_a1=0, trans_b1=0;
1235                                 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
1236                                 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
1237
1238                                 // Store information about these blocks in moved line info
1239                                 if (GetDetectMovedBlocks())
1240                                 {
1241                                         if (thisob->match0>=0)
1242                                         {
1243                                                 assert(thisob->inserted > 0);
1244                                                 for (int i=0; i<thisob->inserted; ++i)
1245                                                 {
1246                                                         int line0 = i+thisob->match0 + (trans_a0-first0-1);
1247                                                         int line1 = i+thisob->line1 + (trans_a1-first1-1);
1248                                                         GetMovedLines(1)->Add(MovedLines::SIDE::LEFT, line1, line0);
1249                                                 }
1250                                         }
1251                                         if (thisob->match1>=0)
1252                                         {
1253                                                 assert(thisob->deleted > 0);
1254                                                 for (int i=0; i<thisob->deleted; ++i)
1255                                                 {
1256                                                         int line0 = i+thisob->line0 + (trans_a0-first0-1);
1257                                                         int line1 = i+thisob->match1 + (trans_a1-first1-1);
1258                                                         GetMovedLines(0)->Add(MovedLines::SIDE::RIGHT, line0, line1);
1259                                                 }
1260                                         }
1261                                 }
1262                                 int nTrivialInserts = 0;
1263                                 if (op != OP_TRIVIAL && usefilters)
1264                                         nTrivialInserts = PostFilter(ctxt, thisob, file_data_ary);
1265                                 if (nTrivialInserts > 0)
1266                                 {
1267                                         while (thisob != next)
1268                                         {
1269                                                 op = (thisob->trivial) ? OP_TRIVIAL : OP_DIFF;
1270                                                 first0 = thisob->line0;
1271                                                 first1 = thisob->line1;
1272                                                 last0 = first0 + thisob->deleted - 1;
1273                                                 last1 = first1 + thisob->inserted - 1;
1274                                                 translate_range (&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
1275                                                 translate_range (&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
1276                                                 const int qtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1277                                                 const int qtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1278
1279                                                 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces)
1280                                                 {
1281                                                         if (qtyLinesLeft == qtyLinesRight)
1282                                                         {
1283                                                                 op = OP_NONE;
1284                                                         }
1285                                                         else
1286                                                         {
1287                                                                 trans_a0 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1288                                                                 trans_a1 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1289                                                         }
1290                                                 }
1291                                                 if (op != OP_NONE)
1292                                                         AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1293
1294                                                 thisob = thisob->link;
1295                                         }
1296                                 }
1297                                 else
1298                                 {
1299                                         if (thisob->trivial)
1300                                                 op = OP_TRIVIAL;
1301                                         const int qtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1302                                         const int qtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1303                                         if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces)
1304                                         {
1305                                                 if (qtyLinesLeft == qtyLinesRight)
1306                                                 {
1307                                                         op = OP_NONE;
1308                                                 }
1309                                                 else
1310                                                 {
1311                                                         trans_a0 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1312                                                         trans_a1 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1313                                                 }
1314                                         }
1315                                         if (op != OP_NONE)
1316                                                 AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1317                                 }
1318                         }
1319                 }
1320         }
1321 }
1322
1323 struct Comp02Functor
1324 {
1325         Comp02Functor(const file_data * inf10, const file_data * inf12) :
1326                 inf10_(inf10), inf12_(inf12)
1327         {
1328         }
1329         bool operator()(const DiffRangeInfo &dr3)
1330         {
1331                 int line0 = dr3.begin[0];
1332                 int line2 = dr3.begin[2];
1333                 int line0end = dr3.end[0];
1334                 int line2end = dr3.end[2];
1335                 if (line0end - line0 != line2end - line2)
1336                         return false;
1337                 const char **linbuf0 = inf10_[1].linbuf + inf10_[1].linbuf_base;
1338                 const char **linbuf2 = inf12_[1].linbuf + inf12_[1].linbuf_base;
1339                 for (int i = 0; i < line0end - line0 + 1; ++i)
1340                 {
1341                         const size_t line0len = linbuf0[line0 + i + 1] - linbuf0[line0 + i];
1342                         const size_t line2len = linbuf2[line2 + i + 1] - linbuf2[line2 + i];
1343                         if (line_cmp(linbuf0[line0 + i], line0len, linbuf2[line2 + i], line2len) != 0)
1344                                 return false;
1345                 }
1346                 return true;
1347         }
1348         const file_data *inf10_;
1349         const file_data *inf12_;
1350 };
1351
1352 /**
1353  * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1354  */
1355 void
1356 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript3(
1357         struct change * script10, 
1358         struct change * script12,  
1359         const file_data * inf10, 
1360         const file_data * inf12)
1361 {
1362         DiffList diff10, diff12;
1363         diff10.Clear();
1364         diff12.Clear();
1365
1366         const bool usefilters = m_options.m_filterCommentsLines ||
1367                 (m_pFilterList && m_pFilterList->HasRegExps()) ||
1368                 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps());
1369         
1370         for (int file = 0; file < 2; file++)
1371         {
1372                 struct change *next = nullptr;
1373                 int trans_a0, trans_b0, trans_a1, trans_b1;
1374                 int first0, last0, first1, last1, deletes, inserts;
1375                 OP_TYPE op;
1376                 const file_data *pinf = nullptr;
1377                 DiffList *pdiff = nullptr;
1378                 PostFilterContext ctxt;
1379
1380                 switch (file)
1381                 {
1382                 case 0: next = script10; pdiff = &diff10; pinf = inf10; break;
1383                 case 1: next = script12; pdiff = &diff12; pinf = inf12; break;
1384                 }
1385
1386                 while (next != nullptr)
1387                 {
1388                         /* Find a set of changes that belong together.  */
1389                         struct change *thisob = next;
1390                         struct change *end = find_change(next);
1391                         
1392                         /* Disconnect them from the rest of the changes,
1393                         making them a hunk, and remember the rest for next iteration.  */
1394                         next = end->link;
1395                         end->link = nullptr;
1396 #ifdef DEBUG
1397                         debug_script(thisob);
1398 #endif
1399
1400                         /* Print thisob hunk.  */
1401                         //(*printfun) (thisob);
1402                         {                                       
1403                                 /* Determine range of line numbers involved in each file.  */
1404                                 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, pinf);
1405                         
1406                                 /* Reconnect the script so it will all be freed properly.  */
1407                                 end->link = next;
1408
1409                                 if (deletes || inserts || thisob->trivial)
1410                                 {
1411                                         if (deletes && inserts)
1412                                                 op = OP_DIFF;
1413                                         else if (deletes || inserts)
1414                                                 op = OP_DIFF;
1415                                         else
1416                                                 op = OP_TRIVIAL;
1417                                         
1418                                         /* Print the lines that the first file has.  */
1419                                         translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1420                                         translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1421
1422                                         // Store information about these blocks in moved line info
1423                                         if (GetDetectMovedBlocks())
1424                                         {
1425                                                 int index1 = 0;  // defaults for (file == 0 /* diff10 */)
1426                                                 int index2 = 1;
1427                                                 MovedLines::SIDE side1 = MovedLines::SIDE::RIGHT;
1428                                                 MovedLines::SIDE side2 = MovedLines::SIDE::LEFT;
1429                                                 if (file == 1 /* diff12 */)
1430                                                 {
1431                                                         index1 = 2;
1432                                                         index2 = 1;
1433                                                         side1 = MovedLines::SIDE::LEFT;
1434                                                         side2 = MovedLines::SIDE::RIGHT;
1435                                                 }
1436                                                 if (index1 != -1 && index2 != -1)
1437                                                 {
1438                                                         if (thisob->match0>=0)
1439                                                         {
1440                                                                 assert(thisob->inserted > 0);
1441                                                                 for (int i=0; i<thisob->inserted; ++i)
1442                                                                 {
1443                                                                         int line0 = i+thisob->match0 + (trans_a0-first0-1);
1444                                                                         int line1 = i+thisob->line1 + (trans_a1-first1-1);
1445                                                                         GetMovedLines(index1)->Add(side1, line1, line0);
1446                                                                 }
1447                                                         }
1448                                                         if (thisob->match1>=0)
1449                                                         {
1450                                                                 assert(thisob->deleted > 0);
1451                                                                 for (int i=0; i<thisob->deleted; ++i)
1452                                                                 {
1453                                                                         int line0 = i+thisob->line0 + (trans_a0-first0-1);
1454                                                                         int line1 = i+thisob->match1 + (trans_a1-first1-1);
1455                                                                         GetMovedLines(index2)->Add(side2, line0, line1);
1456                                                                 }
1457                                                         }
1458                                                 }
1459                                         }
1460
1461                                         int nTrivialInserts = 0;
1462                                         if (op != OP_TRIVIAL && usefilters)
1463                                                 nTrivialInserts = PostFilter(ctxt, thisob, pinf);
1464                                         if (nTrivialInserts)
1465                                         {
1466                                                 while (thisob != next)
1467                                                 {
1468                                                         op = (thisob->trivial) ? OP_TRIVIAL : OP_DIFF;
1469                                                         first0 = thisob->line0;
1470                                                         first1 = thisob->line1;
1471                                                         last0 = first0 + thisob->deleted - 1;
1472                                                         last1 = first1 + thisob->inserted - 1;
1473                                                         translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1474                                                         translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1475
1476                                                         AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1477                                                         thisob = thisob->link;
1478                                                 }
1479                                         }
1480                                         else
1481                                         {
1482                                                 if (thisob->trivial)
1483                                                         op = OP_TRIVIAL;
1484                                                 AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1485                                         }
1486                                 }
1487                         }
1488                 }
1489         }
1490
1491         Make3wayDiff(m_pDiffList->GetDiffRangeInfoVector(), diff10.GetDiffRangeInfoVector(), diff12.GetDiffRangeInfoVector(), 
1492                 Comp02Functor(inf10, inf12), 
1493                 (m_pFilterList != nullptr && m_pFilterList->HasRegExps()) || m_options.m_bIgnoreBlankLines || m_options.m_filterCommentsLines);
1494 }
1495
1496 void CDiffWrapper::WritePatchFileHeader(enum output_style tOutput_style, bool bAppendFiles)
1497 {
1498         outfile = nullptr;
1499         if (!m_sPatchFile.empty())
1500         {
1501                 const tchar_t *mode = (bAppendFiles ? _T("a+") : _T("w+"));
1502                 if (cio::tfopen_s(&outfile, m_sPatchFile, mode) != 0)
1503                         outfile = nullptr;
1504         }
1505
1506         if (outfile == nullptr)
1507         {
1508                 m_status.bPatchFileFailed = true;
1509                 return;
1510         }
1511
1512         // Output patchfile
1513         switch (tOutput_style)
1514         {
1515         case OUTPUT_NORMAL:
1516         case OUTPUT_CONTEXT:
1517         case OUTPUT_UNIFIED:
1518 #if 0
1519         case OUTPUT_ED:
1520         case OUTPUT_FORWARD_ED:
1521         case OUTPUT_RCS:
1522         case OUTPUT_IFDEF:
1523         case OUTPUT_SDIFF:
1524 #endif
1525                 break;
1526         case OUTPUT_HTML:
1527                 print_html_header();
1528                 break;
1529         }
1530         
1531         fclose(outfile);
1532         outfile = nullptr;
1533 }
1534
1535 void CDiffWrapper::WritePatchFileTerminator(enum output_style tOutput_style)
1536 {
1537         outfile = nullptr;
1538         if (!m_sPatchFile.empty())
1539         {
1540                 if (cio::tfopen_s(&outfile, m_sPatchFile, _T("a+")) != 0)
1541                         outfile = nullptr;
1542         }
1543
1544         if (outfile == nullptr)
1545         {
1546                 m_status.bPatchFileFailed = true;
1547                 return;
1548         }
1549
1550         // Output patchfile
1551         switch (tOutput_style)
1552         {
1553         case OUTPUT_NORMAL:
1554         case OUTPUT_CONTEXT:
1555         case OUTPUT_UNIFIED:
1556 #if 0
1557         case OUTPUT_ED:
1558         case OUTPUT_FORWARD_ED:
1559         case OUTPUT_RCS:
1560         case OUTPUT_IFDEF:
1561         case OUTPUT_SDIFF:
1562 #endif
1563                 break;
1564         case OUTPUT_HTML:
1565                 print_html_terminator();
1566                 break;
1567         }
1568         
1569         fclose(outfile);
1570         outfile = nullptr;
1571 }
1572
1573 /**
1574  * @brief Write out a patch file.
1575  * Writes patch file using already computed diffutils script. Converts path
1576  * delimiters from \ to / since we want to keep compatibility with patch-tools.
1577  * @param [in] script list of changes.
1578  * @param [in] inf file_data table containing filenames
1579  */
1580 void CDiffWrapper::WritePatchFile(struct change * script, file_data * inf)
1581 {
1582         file_data inf_patch[2] = { inf[0], inf[1] };
1583
1584         // Get paths, primarily use alternative paths, only if they are empty
1585         // use full filepaths
1586         String path1(m_alternativePaths[0]);
1587         String path2(m_alternativePaths[1]);
1588         if (path1.empty())
1589                 path1 = m_files[0];
1590         if (path2.empty())
1591                 path2 = m_files[1];
1592         path1 = paths::ToUnixPath(path1);
1593         path2 = paths::ToUnixPath(path2);
1594         auto strdupPath = [](const String& path, const void *buffer, size_t buffered_chars) -> char*
1595         {
1596                 FileTextEncoding encoding = codepage_detect::Guess(_T(""), buffer, buffered_chars, 1);
1597                 if (encoding.m_unicoding != ucr::NONE)
1598                         encoding.SetUnicoding(ucr::UTF8);
1599                 ucr::buffer buf(256);
1600                 ucr::convert(ucr::CP_TCHAR, reinterpret_cast<const unsigned char *>(path.c_str()), static_cast<int>(path.size() * sizeof(tchar_t)), encoding.m_codepage, &buf);
1601                 return strdup(reinterpret_cast<const char *>(buf.ptr));
1602         };
1603         inf_patch[0].name = strdupPath(path1, inf_patch[0].buffer, inf_patch[0].buffered_chars);
1604         inf_patch[1].name = strdupPath(path2, inf_patch[1].buffer, inf_patch[1].buffered_chars);
1605
1606         // If paths in m_s1File and m_s2File point to original files, then we can use
1607         // them to fix potentially meaningless stats from potentially temporary files,
1608         // resulting from whatever transforms may have taken place.
1609         // If not, then we can't help it, and hence assert that this won't happen.
1610         if (!m_bPathsAreTemp)
1611         {
1612                 mywstat(m_files[0].c_str(), &inf_patch[0].stat);
1613                 mywstat(m_files[1].c_str(), &inf_patch[1].stat);
1614         }
1615         else
1616         {
1617                 assert(false);
1618         }
1619
1620         outfile = nullptr;
1621         if (!m_sPatchFile.empty())
1622         {
1623                 const tchar_t *mode = (m_bAppendFiles ? _T("a+") : _T("w+"));
1624                 if (cio::tfopen_s(&outfile, m_sPatchFile, mode) != 0)
1625                         outfile = nullptr;
1626         }
1627
1628         if (outfile == nullptr)
1629         {
1630                 m_status.bPatchFileFailed = true;
1631                 return;
1632         }
1633
1634         if (strcmp(inf[0].name, "NUL") == 0)
1635         {
1636                 free((void *)inf_patch[0].name);
1637                 inf_patch[0].name = strdup("/dev/null");
1638         }
1639         if (strcmp(inf[1].name, "NUL") == 0)
1640         {
1641                 free((void *)inf_patch[1].name);
1642                 inf_patch[1].name = strdup("/dev/null");
1643         }
1644
1645         // Print "command line"
1646         if (m_bAddCmdLine && output_style != OUTPUT_HTML)
1647         {
1648                 String switches = FormatSwitchString();
1649                 fprintf(outfile, "diff%S %s %s\n",
1650                         switches.c_str(), inf_patch[0].name, inf_patch[1].name);
1651         }
1652
1653         // Output patchfile
1654         switch (output_style)
1655         {
1656         case OUTPUT_NORMAL:
1657                 print_normal_script(script);
1658                 break;
1659         case OUTPUT_CONTEXT:
1660                 print_context_header(inf_patch, 0);
1661                 print_context_script(script, 0);
1662                 break;
1663         case OUTPUT_UNIFIED:
1664                 print_context_header(inf_patch, 1);
1665                 print_context_script(script, 1);
1666                 break;
1667 #if 0
1668         case OUTPUT_ED:
1669                 print_ed_script(script);
1670                 break;
1671         case OUTPUT_FORWARD_ED:
1672                 pr_forward_ed_script(script);
1673                 break;
1674         case OUTPUT_RCS:
1675                 print_rcs_script(script);
1676                 break;
1677         case OUTPUT_IFDEF:
1678                 print_ifdef_script(script);
1679                 break;
1680         case OUTPUT_SDIFF:
1681                 print_sdiff_script(script);
1682                 break;
1683 #endif
1684         case OUTPUT_HTML:
1685                 print_html_diff_header(inf_patch);
1686                 print_html_script(script);
1687                 print_html_diff_terminator();
1688         }
1689         
1690         fclose(outfile);
1691         outfile = nullptr;
1692
1693         free((void *)inf_patch[0].name);
1694         free((void *)inf_patch[1].name);
1695 }
1696
1697 /**
1698  * @brief Set line filters, given as one string.
1699  * @param [in] filterStr Filters.
1700  */
1701 void CDiffWrapper::SetFilterList(const String& filterStr)
1702 {
1703         // Remove filterlist if new filter is empty
1704         if (filterStr.empty())
1705         {
1706                 m_pFilterList.reset();
1707                 return;
1708         }
1709
1710         // Adding new filter without previous filter
1711         if (m_pFilterList == nullptr)
1712         {
1713                 m_pFilterList.reset(new FilterList);
1714         }
1715
1716         m_pFilterList->RemoveAllFilters();
1717
1718         std::string regexp_str = ucr::toUTF8(filterStr);
1719
1720         // Add every "line" of regexps to regexp list
1721         StringTokenizer tokens(regexp_str, "\r\n");
1722         for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
1723                 m_pFilterList->AddRegExp(*it);
1724 }
1725
1726 const FilterList* CDiffWrapper::GetFilterList() const
1727 {
1728         return m_pFilterList.get();
1729 }
1730
1731 void CDiffWrapper::SetFilterList(std::shared_ptr<FilterList> pFilterList)
1732 {
1733         m_pFilterList = std::move(pFilterList);
1734 }
1735
1736 const SubstitutionList* CDiffWrapper::GetSubstitutionList() const
1737 {
1738         return m_pSubstitutionList.get();
1739 }
1740
1741 void CDiffWrapper::SetSubstitutionList(std::shared_ptr<SubstitutionList> pSubstitutionList)
1742 {
1743         m_pSubstitutionList = std::move(pSubstitutionList);
1744 }
1745
1746 void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext)
1747 {
1748         m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str());
1749 }
1750
1751 /**
1752  * @brief Copy text stat results from diffutils back into the FileTextStats structure
1753  */
1754 void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
1755 {
1756         myTextStats->ncrlfs = inf->count_crlfs;
1757         myTextStats->ncrs = inf->count_crs;
1758         myTextStats->nlfs = inf->count_lfs;
1759         myTextStats->nzeros = inf->count_zeros;
1760 }
1761
1762 /**
1763  * @brief Copy both left & right text stats results back into the DiffFileData text stats
1764  */
1765 void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData)
1766 {
1767         CopyTextStats(&inf[0], &diffData->m_textStats[0]);
1768         CopyTextStats(&inf[1], &diffData->m_textStats[1]);
1769 }