OSDN Git Service

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