OSDN Git Service

Fix issue #834: Incorrect comparison
[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 (m_infoPrediffer && !m_infoPrediffer->Prediffing(strFileTemp[file], m_sToFindPrediffer, m_bPathsAreTemp, { strFileTemp[file] }))
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->GetPluginPipeline().c_str());
440                                 AppErrorMessageBox(sError);
441                                 // don't use any more this prediffer
442                                 m_infoPrediffer->ClearPluginPipeline();
443                         }
444                 }
445         }
446
447         struct change *script = nullptr;
448         struct change *script10 = nullptr;
449         struct change *script12 = nullptr;
450         DiffFileData diffdata, diffdata10, diffdata12;
451         int bin_flag = 0, bin_flag10 = 0, bin_flag12 = 0;
452
453         if (aFiles.GetSize() == 2)
454         {
455                 diffdata.SetDisplayFilepaths(aFiles[0], aFiles[1]); // store true names for diff utils patch file
456                 // This opens & fstats both files (if it succeeds)
457                 if (!diffdata.OpenFiles(strFileTemp[0], strFileTemp[1]))
458                 {
459                         return false;
460                 }
461
462                 // Compare the files, if no error was found.
463                 // Last param (bin_file) is `nullptr` since we don't
464                 // (yet) need info about binary sides.
465                 bRet = Diff2Files(&script, &diffdata, &bin_flag, nullptr);
466
467                 // We don't anymore create diff-files for every rescan.
468                 // User can create patch-file whenever one wants to.
469                 // We don't need to waste time. But lets keep this as
470                 // debugging aid. Sometimes it is very useful to see
471                 // what differences diff-engine sees!
472 #ifdef _DEBUG
473                 // throw the diff into a temp file
474                 String sTempPath = env::GetTemporaryPath(); // get path to Temp folder
475                 String path = paths::ConcatPath(sTempPath, _T("Diff.txt"));
476
477                 if (_tfopen_s(&outfile, path.c_str(), _T("w+")) == 0)
478                 {
479                         print_normal_script(script);
480                         fclose(outfile);
481                         outfile = nullptr;
482                 }
483 #endif
484         }
485         else
486         {
487                 diffdata10.SetDisplayFilepaths(aFiles[1], aFiles[0]); // store true names for diff utils patch file
488                 diffdata12.SetDisplayFilepaths(aFiles[1], aFiles[2]); // store true names for diff utils patch file
489
490                 if (!diffdata10.OpenFiles(strFileTemp[1], strFileTemp[0]))
491                 {
492                         return false;
493                 }
494
495                 bRet = Diff2Files(&script10, &diffdata10, &bin_flag10, nullptr);
496
497                 if (!diffdata12.OpenFiles(strFileTemp[1], strFileTemp[2]))
498                 {
499                         return false;
500                 }
501
502                 bRet = Diff2Files(&script12, &diffdata12, &bin_flag12, nullptr);
503         }
504
505         // First determine what happened during comparison
506         // If there were errors or files were binaries, don't bother
507         // creating diff-lists or patches
508         
509         // diff_2_files set bin_flag to -1 if different binary
510         // diff_2_files set bin_flag to +1 if same binary
511
512         file_data * inf = diffdata.m_inf;
513         file_data * inf10 = diffdata10.m_inf;
514         file_data * inf12 = diffdata12.m_inf;
515
516         if (aFiles.GetSize() == 2)
517         {
518                 if (bin_flag != 0)
519                 {
520                         m_status.bBinaries = true;
521                         if (bin_flag != -1)
522                                 m_status.Identical = IDENTLEVEL::ALL;
523                         else
524                                 m_status.Identical = IDENTLEVEL::NONE;
525                 }
526                 else
527                 { // text files according to diffutils, so change script exists
528                         m_status.Identical = (script == 0) ? IDENTLEVEL::ALL : IDENTLEVEL::NONE;
529                         m_status.bBinaries = false;
530                 }
531                 m_status.bMissingNL[0] = !!inf[0].missing_newline;
532                 m_status.bMissingNL[1] = !!inf[1].missing_newline;
533         }
534         else
535         {
536                 m_status.Identical = IDENTLEVEL::NONE;
537                 if (bin_flag10 != 0 || bin_flag12 != 0)
538                 {
539                         m_status.bBinaries = true;
540                         if (bin_flag10 != -1 && bin_flag12 != -1)
541                                 m_status.Identical = IDENTLEVEL::ALL;
542                         else if (bin_flag10 != -1)
543                                 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
544                         else if (bin_flag12 != -1)
545                                 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
546                         else
547                                 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
548                 }
549                 else
550                 { // text files according to diffutils, so change script exists
551                         m_status.bBinaries = false;
552                         if (script10 == nullptr && script12 == nullptr)
553                                 m_status.Identical = IDENTLEVEL::ALL;
554                         else if (script10 == nullptr)
555                                 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
556                         else if (script12 == nullptr)
557                                 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
558                         else
559                                 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
560                 }
561                 m_status.bMissingNL[0] = !!inf10[1].missing_newline;
562                 m_status.bMissingNL[1] = !!inf12[0].missing_newline;
563                 m_status.bMissingNL[2] = !!inf12[1].missing_newline;
564         }
565
566
567         // Create patch file
568         if (!m_status.bBinaries && m_bCreatePatchFile && aFiles.GetSize() == 2)
569         {
570                 WritePatchFile(script, &inf[0]);
571         }
572         
573         // Go through diffs adding them to WinMerge's diff list
574         // This is done on every WinMerge's doc rescan!
575         if (!m_status.bBinaries && m_bUseDiffList)
576         {
577                 if (aFiles.GetSize() == 2)
578                         LoadWinMergeDiffsFromDiffUtilsScript(script, diffdata.m_inf);
579                 else
580                         LoadWinMergeDiffsFromDiffUtilsScript3(
581                                 script10, script12,
582                                 diffdata10.m_inf, diffdata12.m_inf);
583         }                       
584
585         // cleanup the script
586         if (aFiles.GetSize() == 2)
587                 FreeDiffUtilsScript(script);
588         else
589         {
590                 FreeDiffUtilsScript(script10);
591                 FreeDiffUtilsScript(script12);
592         }
593
594         // Done with diffutils filedata
595         if (aFiles.GetSize() == 2)
596         {
597                 diffdata.Close();
598         }
599         else
600         {
601                 diffdata10.Close();
602                 diffdata12.Close();
603         }
604
605         if (m_bPluginsEnabled)
606         {
607                 // Delete temp files transformation functions possibly created
608                 for (file = 0; file < aFiles.GetSize(); file++)
609                 {
610                         if (strutils::compare_nocase(aFiles[file], strFileTemp[file]) != 0)
611                         {
612                                 try
613                                 {
614                                         TFile(strFileTemp[file]).remove();
615                                 }
616                                 catch (Exception& e)
617                                 {
618                                         LogErrorStringUTF8(e.displayText());
619                                 }
620                                 strFileTemp[file].erase();
621                         }
622                 }
623         }
624         return bRet;
625 }
626
627 /**
628  * @brief Add diff to external diff-list
629  */
630 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, unsigned begin0, unsigned end0, unsigned begin1, unsigned end1, OP_TYPE op)
631 {
632         try
633         {
634                 DIFFRANGE dr;
635                 dr.begin[0] = begin0;
636                 dr.end[0] = end0;
637                 dr.begin[1] = begin1;
638                 dr.end[1] = end1;
639                 dr.begin[2] = -1;
640                 dr.end[2] = -1;
641                 dr.op = op;
642                 dr.blank[0] = dr.blank[1] = dr.blank[2] = -1;
643                 pDiffList->AddDiff(dr);
644         }
645         catch (std::exception& e)
646         {
647                 AppErrorMessageBox(ucr::toTString(e.what()));
648         }
649 }
650
651 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, DIFFRANGE &dr)
652 {
653         try
654         {
655                 pDiffList->AddDiff(dr);
656         }
657         catch (std::exception& e)
658         {
659                 AppErrorMessageBox(ucr::toTString(e.what()));
660         }
661 }
662
663 /**
664  * @brief Expand last DIFFRANGE of file by one line to contain last line after EOL.
665  * @param [in] leftBufferLines size of array pane left
666  * @param [in] rightBufferLines size of array pane right
667  * @param [in] left on whitch side we have to insert
668  * @param [in] bIgnoreBlankLines, if true we always add a new diff and mark as trivial
669  */
670 void CDiffWrapper::FixLastDiffRange(int nFiles, int bufferLines[], bool bMissingNL[], bool bIgnoreBlankLines)
671 {
672         DIFFRANGE dr;
673         const int count = m_pDiffList->GetSize();
674         if (count > 0)
675         {
676                 m_pDiffList->GetDiff(count - 1, dr);
677
678                 for (int file = 0; file < nFiles; file++)
679                 {
680                         if (!bMissingNL[file])
681                                 dr.end[file]++;
682                 }
683
684                 m_pDiffList->SetDiff(count - 1, dr);
685         }
686         else 
687         {
688                 // we have to create the DIFF
689                 for (int file = 0; file < nFiles; file++)
690                 {
691                         dr.end[file] = bufferLines[file] - 1;
692                         if (bMissingNL[file])
693                                 dr.begin[file] = dr.end[file];
694                         else
695                                 dr.begin[file] = dr.end[file] + 1;
696                         dr.op = OP_DIFF;
697                         assert(dr.begin[0] == dr.begin[file]);
698                 }
699                 if (bIgnoreBlankLines)
700                         dr.op = OP_TRIVIAL;
701
702                 AddDiffRange(m_pDiffList, dr); 
703         }
704 }
705
706 /**
707  * @brief Returns status-data from diff-engine last run
708  */
709 void CDiffWrapper::GetDiffStatus(DIFFSTATUS *status) const
710 {
711         std::memcpy(status, &m_status, sizeof(DIFFSTATUS));
712 }
713
714 /**
715  * @brief Formats command-line for diff-engine last run (like it was called from command-line)
716  */
717 String CDiffWrapper::FormatSwitchString() const
718 {
719         String switches;
720         
721         switch (m_options.m_outputStyle)
722         {
723         case DIFF_OUTPUT_NORMAL:
724                 switches = _T(" ");
725                 break;
726         case DIFF_OUTPUT_CONTEXT:
727                 switches = (m_options.m_contextLines > 0) ? _T(" -C ") : _T(" -c");
728                 break;
729         case DIFF_OUTPUT_UNIFIED:
730                 switches = (m_options.m_contextLines > 0) ? _T(" -U ") : _T(" -u");
731                 break;
732 #if 0
733         case DIFF_OUTPUT_ED:
734                 switches = _T(" e");
735                 break;
736         case DIFF_OUTPUT_FORWARD_ED:
737                 switches = _T(" f");
738                 break;
739         case DIFF_OUTPUT_RCS:
740                 switches = _T(" n");
741                 break;
742         case DIFF_OUTPUT_IFDEF:
743                 switches = _T(" D");
744                 break;
745         case DIFF_OUTPUT_SDIFF:
746                 switches = _T(" y");
747                 break;
748 #endif
749         }
750
751         if ((m_options.m_outputStyle == DIFF_OUTPUT_CONTEXT || m_options.m_outputStyle == DIFF_OUTPUT_UNIFIED) &&
752                 m_options.m_contextLines > 0)
753         {
754                 TCHAR tmpNum[5] = {0};
755                 _itot_s(m_options.m_contextLines, tmpNum, 10);
756                 switches += tmpNum;
757         }
758
759         if (ignore_all_space_flag > 0)
760                 switches += _T(" -w");
761
762         if (ignore_blank_lines_flag > 0)
763                 switches += _T(" -B");
764
765         if (ignore_case_flag > 0)
766                 switches += _T(" -i");
767
768         if (ignore_space_change_flag > 0)
769                 switches += _T(" -b");
770
771         return switches;
772 }
773
774 /**
775  * @brief Enables/disables patch-file appending.
776  * If the file for patch already exists then the patch will be appended to
777  * existing file.
778  * @param [in] bAppendFiles If true patch will be appended to existing file.
779  */
780 void CDiffWrapper::SetAppendFiles(bool bAppendFiles)
781 {
782         m_bAppendFiles = bAppendFiles;
783 }
784
785 /**
786  * @brief Compare two files using diffutils.
787  *
788  * Compare two files (in DiffFileData param) using diffutils. Run diffutils
789  * inside SEH so we can trap possible error and exceptions. If error or
790  * execption is trapped, return compare failure.
791  * @param [out] diffs Pointer to list of change structs where diffdata is stored.
792  * @param [in] diffData files to compare.
793  * @param [out] bin_status used to return binary status from compare.
794  * @param [out] bin_file Returns which file was binary file as bitmap.
795     So if first file is binary, first bit is set etc. Can be `nullptr` if binary file
796     info is not needed (faster compare since diffutils don't bother checking
797     second file if first is binary).
798  * @return true when compare succeeds, false if error happened during compare.
799  * @note This function is used in file compare, not folder compare. Similar
800  * folder compare function is in DiffFileData.cpp.
801  */
802 bool CDiffWrapper::Diff2Files(struct change ** diffs, DiffFileData *diffData,
803         int * bin_status, int * bin_file) const
804 {
805         bool bRet = true;
806         SE_Handler seh;
807         try
808         {
809                 if (m_options.m_diffAlgorithm != DIFF_ALGORITHM_DEFAULT)
810                 {
811                         unsigned xdl_flags = make_xdl_flags(m_options);
812                         *diffs = diff_2_files_xdiff(diffData->m_inf, (m_pMovedLines[0] != nullptr), xdl_flags);
813                         files[0] = diffData->m_inf[0];
814                         files[1] = diffData->m_inf[1];
815                 }
816                 else
817                 {
818                         // Diff files. depth is zero because we are not comparing dirs
819                         *diffs = diff_2_files(diffData->m_inf, 0, bin_status,
820                                 (m_pMovedLines[0] != nullptr), bin_file);
821                 }
822                 CopyDiffutilTextStats(diffData->m_inf, diffData);
823         }
824         catch (SE_Exception&)
825         {
826                 *diffs = nullptr;
827                 bRet = false;
828         }
829         return bRet;
830 }
831
832 /**
833  * @brief Free script (the diffutils linked list of differences)
834  */
835 void
836 CDiffWrapper::FreeDiffUtilsScript(struct change * & script)
837 {
838         if (script == nullptr) return;
839         struct change *e=nullptr, *p=nullptr;
840         // cleanup the script
841         for (e = script; e != nullptr; e = p)
842         {
843                 p = e->link;
844                 free(e);
845         }
846         script = nullptr;
847 }
848
849 /**
850  * @brief Match regular expression list against given difference.
851  * This function matches the regular expression list against the difference
852  * (given as start line and end line). Matching the diff requires that all
853  * lines in difference match.
854  * @param [in] StartPos First line of the difference.
855  * @param [in] endPos Last line of the difference.
856  * @param [in] FileNo File to match.
857  * return true if any of the expressions matches.
858  */
859 bool CDiffWrapper::RegExpFilter(int StartPos, int EndPos, const file_data *pinf) const
860 {
861         if (m_pFilterList == nullptr)
862         {       
863                 throw "CDiffWrapper::RegExpFilter() called when "
864                         "filterlist doesn't exist (=nullptr)";
865         }
866
867         bool linesMatch = true; // set to false when non-matching line is found.
868         int line = StartPos;
869
870         while (line <= EndPos && linesMatch)
871         {
872                 size_t len = pinf->linbuf[line + 1] - pinf->linbuf[line];
873                 const char *string = pinf->linbuf[line];
874                 size_t stringlen = linelen(string, len);
875                 if (!m_pFilterList->Match(std::string(string, stringlen)))
876
877                 {
878                         linesMatch = false;
879                 }
880                 ++line;
881         }
882         return linesMatch;
883 }
884
885 /**
886  * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
887  */
888 void
889 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const file_data * file_data_ary)
890 {
891         //Logic needed for Ignore comment option
892         PostFilterContext ctxt;
893
894         struct change *next = script;
895         
896         while (next != nullptr)
897         {
898                 /* Find a set of changes that belong together.  */
899                 struct change *thisob = next;
900                 struct change *end = find_change(next);
901                 
902                 /* Disconnect them from the rest of the changes,
903                 making them a hunk, and remember the rest for next iteration.  */
904                 next = end->link;
905                 end->link = nullptr;
906 #ifdef DEBUG
907                 debug_script(thisob);
908 #endif
909
910                 /* Print thisob hunk.  */
911                 //(*printfun) (thisob);
912                 {                                       
913                         /* Determine range of line numbers involved in each file.  */
914                         int first0=0, last0=0, first1=0, last1=0, deletes=0, inserts=0;
915                         analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, file_data_ary);
916                         if (deletes || inserts || thisob->trivial)
917                         {
918                                 OP_TYPE op = OP_NONE;
919                                 if (deletes && inserts)
920                                         op = OP_DIFF;
921                                 else if (deletes || inserts)
922                                         op = OP_DIFF;
923                                 else
924                                         op = OP_TRIVIAL;
925                                 
926                                 /* Print the lines that the first file has.  */
927                                 int trans_a0=0, trans_b0=0, trans_a1=0, trans_b1=0;
928                                 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
929                                 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
930
931                                 // Store information about these blocks in moved line info
932                                 if (GetDetectMovedBlocks())
933                                 {
934                                         if (thisob->match0>=0)
935                                         {
936                                                 assert(thisob->inserted > 0);
937                                                 for (int i=0; i<thisob->inserted; ++i)
938                                                 {
939                                                         int line0 = i+thisob->match0 + (trans_a0-first0-1);
940                                                         int line1 = i+thisob->line1 + (trans_a1-first1-1);
941                                                         GetMovedLines(1)->Add(MovedLines::SIDE::LEFT, line1, line0);
942                                                 }
943                                         }
944                                         if (thisob->match1>=0)
945                                         {
946                                                 assert(thisob->deleted > 0);
947                                                 for (int i=0; i<thisob->deleted; ++i)
948                                                 {
949                                                         int line0 = i+thisob->line0 + (trans_a0-first0-1);
950                                                         int line1 = i+thisob->match1 + (trans_a1-first1-1);
951                                                         GetMovedLines(0)->Add(MovedLines::SIDE::RIGHT, line0, line1);
952                                                 }
953                                         }
954                                 }
955                                 int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
956                                 int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
957
958                                 if (m_options.m_filterCommentsLines ||
959                                         (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
960                                         PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, file_data_ary);
961
962                                 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
963                                 {
964                                         // Match lines against regular expression filters
965                                         // Our strategy is that every line in both sides must
966                                         // match regexp before we mark difference as ignored.
967                                         bool match2 = false;
968                                         bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &file_data_ary[0]);
969                                         if (match1)
970                                                 match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &file_data_ary[1]);
971                                         if (match1 && match2)
972                                                 op = OP_TRIVIAL;
973                                 }
974
975                                 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces &&
976                                         QtyLinesLeft == QtyLinesRight)
977                                         op = OP_NONE;
978                                 if (op != OP_NONE)
979                                         AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
980                         }
981                 }
982                 
983                 /* Reconnect the script so it will all be freed properly.  */
984                 end->link = next;
985         }
986 }
987
988 struct Comp02Functor
989 {
990         Comp02Functor(const file_data * inf10, const file_data * inf12) :
991                 inf10_(inf10), inf12_(inf12)
992         {
993         }
994         bool operator()(const DiffRangeInfo &dr3)
995         {
996                 int line0 = dr3.begin[0];
997                 int line2 = dr3.begin[2];
998                 int line0end = dr3.end[0];
999                 int line2end = dr3.end[2];
1000                 if (line0end - line0 != line2end - line2)
1001                         return false;
1002                 const char **linbuf0 = inf10_[1].linbuf + inf10_[1].linbuf_base;
1003                 const char **linbuf2 = inf12_[1].linbuf + inf12_[1].linbuf_base;
1004                 for (int i = 0; i < line0end - line0 + 1; ++i)
1005                 {
1006                         const size_t line0len = linbuf0[line0 + i + 1] - linbuf0[line0 + i];
1007                         const size_t line2len = linbuf2[line2 + i + 1] - linbuf2[line2 + i];
1008                         if (line_cmp(linbuf0[line0 + i], line0len, linbuf2[line2 + i], line2len) != 0)
1009                                 return false;
1010                 }
1011                 return true;
1012         }
1013         const file_data *inf10_;
1014         const file_data *inf12_;
1015 };
1016
1017 /**
1018  * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1019  */
1020 void
1021 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript3(
1022         struct change * script10, 
1023         struct change * script12,  
1024         const file_data * inf10, 
1025         const file_data * inf12)
1026 {
1027         DiffList diff10, diff12;
1028         diff10.Clear();
1029         diff12.Clear();
1030
1031         for (int file = 0; file < 2; file++)
1032         {
1033                 struct change *next = nullptr;
1034                 int trans_a0, trans_b0, trans_a1, trans_b1;
1035                 int first0, last0, first1, last1, deletes, inserts;
1036                 OP_TYPE op;
1037                 const file_data *pinf = nullptr;
1038                 DiffList *pdiff = nullptr;
1039                 PostFilterContext ctxt;
1040
1041                 switch (file)
1042                 {
1043                 case 0: next = script10; pdiff = &diff10; pinf = inf10; break;
1044                 case 1: next = script12; pdiff = &diff12; pinf = inf12; break;
1045                 }
1046
1047                 while (next != nullptr)
1048                 {
1049                         /* Find a set of changes that belong together.  */
1050                         struct change *thisob = next;
1051                         struct change *end = find_change(next);
1052                         
1053                         /* Disconnect them from the rest of the changes,
1054                         making them a hunk, and remember the rest for next iteration.  */
1055                         next = end->link;
1056                         end->link = nullptr;
1057 #ifdef DEBUG
1058                         debug_script(thisob);
1059 #endif
1060
1061                         /* Print thisob hunk.  */
1062                         //(*printfun) (thisob);
1063                         {                                       
1064                                 /* Determine range of line numbers involved in each file.  */
1065                                 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, pinf);
1066                                 if (deletes || inserts || thisob->trivial)
1067                                 {
1068                                         if (deletes && inserts)
1069                                                 op = OP_DIFF;
1070                                         else if (deletes || inserts)
1071                                                 op = OP_DIFF;
1072                                         else
1073                                                 op = OP_TRIVIAL;
1074                                         
1075                                         /* Print the lines that the first file has.  */
1076                                         translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1077                                         translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1078
1079                                         // Store information about these blocks in moved line info
1080                                         if (GetDetectMovedBlocks())
1081                                         {
1082                                                 int index1 = 0;  // defaults for (file == 0 /* diff10 */)
1083                                                 int index2 = 1;
1084                                                 MovedLines::SIDE side1 = MovedLines::SIDE::RIGHT;
1085                                                 MovedLines::SIDE side2 = MovedLines::SIDE::LEFT;
1086                                                 if (file == 1 /* diff12 */)
1087                                                 {
1088                                                         index1 = 2;
1089                                                         index2 = 1;
1090                                                         side1 = MovedLines::SIDE::LEFT;
1091                                                         side2 = MovedLines::SIDE::RIGHT;
1092                                                 }
1093                                                 if (index1 != -1 && index2 != -1)
1094                                                 {
1095                                                         if (thisob->match0>=0)
1096                                                         {
1097                                                                 assert(thisob->inserted > 0);
1098                                                                 for (int i=0; i<thisob->inserted; ++i)
1099                                                                 {
1100                                                                         int line0 = i+thisob->match0 + (trans_a0-first0-1);
1101                                                                         int line1 = i+thisob->line1 + (trans_a1-first1-1);
1102                                                                         GetMovedLines(index1)->Add(side1, line1, line0);
1103                                                                 }
1104                                                         }
1105                                                         if (thisob->match1>=0)
1106                                                         {
1107                                                                 assert(thisob->deleted > 0);
1108                                                                 for (int i=0; i<thisob->deleted; ++i)
1109                                                                 {
1110                                                                         int line0 = i+thisob->line0 + (trans_a0-first0-1);
1111                                                                         int line1 = i+thisob->match1 + (trans_a1-first1-1);
1112                                                                         GetMovedLines(index2)->Add(side2, line0, line1);
1113                                                                 }
1114                                                         }
1115                                                 }
1116                                         }
1117
1118                                         int QtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1119                                         int QtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1120
1121                                         if (m_options.m_filterCommentsLines ||
1122                                                 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps()))
1123                                                 PostFilter(ctxt, trans_a0 - 1, QtyLinesLeft, trans_a1 - 1, QtyLinesRight, op, pinf);
1124
1125                                         if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
1126                                         {
1127                                                 // Match lines against regular expression filters
1128                                                 // Our strategy is that every line in both sides must
1129                                                 // match regexp before we mark difference as ignored.
1130                                                 bool match2 = false;
1131                                                 bool match1 = RegExpFilter(thisob->line0, thisob->line0 + QtyLinesLeft - 1, &pinf[0]);
1132                                                 if (match1)
1133                                                         match2 = RegExpFilter(thisob->line1, thisob->line1 + QtyLinesRight - 1, &pinf[1]);
1134                                                 if (match1 && match2)
1135                                                         op = OP_TRIVIAL;
1136                                         }
1137
1138                                         AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1139                                 }
1140                         }
1141                         
1142                         /* Reconnect the script so it will all be freed properly.  */
1143                         end->link = next;
1144                 }
1145         }
1146
1147         Make3wayDiff(m_pDiffList->GetDiffRangeInfoVector(), diff10.GetDiffRangeInfoVector(), diff12.GetDiffRangeInfoVector(), 
1148                 Comp02Functor(inf10, inf12), 
1149                 (m_pFilterList != nullptr && m_pFilterList->HasRegExps()) || m_options.m_bIgnoreBlankLines || m_options.m_filterCommentsLines);
1150 }
1151
1152 void CDiffWrapper::WritePatchFileHeader(enum output_style tOutput_style, bool bAppendFiles)
1153 {
1154         outfile = nullptr;
1155         if (!m_sPatchFile.empty())
1156         {
1157                 const TCHAR *mode = (bAppendFiles ? _T("a+") : _T("w+"));
1158                 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1159                         outfile = nullptr;
1160         }
1161
1162         if (outfile == nullptr)
1163         {
1164                 m_status.bPatchFileFailed = true;
1165                 return;
1166         }
1167
1168         // Output patchfile
1169         switch (tOutput_style)
1170         {
1171         case OUTPUT_NORMAL:
1172         case OUTPUT_CONTEXT:
1173         case OUTPUT_UNIFIED:
1174 #if 0
1175         case OUTPUT_ED:
1176         case OUTPUT_FORWARD_ED:
1177         case OUTPUT_RCS:
1178         case OUTPUT_IFDEF:
1179         case OUTPUT_SDIFF:
1180 #endif
1181                 break;
1182         case OUTPUT_HTML:
1183                 print_html_header();
1184                 break;
1185         }
1186         
1187         fclose(outfile);
1188         outfile = nullptr;
1189 }
1190
1191 void CDiffWrapper::WritePatchFileTerminator(enum output_style tOutput_style)
1192 {
1193         outfile = nullptr;
1194         if (!m_sPatchFile.empty())
1195         {
1196                 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), _T("a+")) != 0)
1197                         outfile = nullptr;
1198         }
1199
1200         if (outfile == nullptr)
1201         {
1202                 m_status.bPatchFileFailed = true;
1203                 return;
1204         }
1205
1206         // Output patchfile
1207         switch (tOutput_style)
1208         {
1209         case OUTPUT_NORMAL:
1210         case OUTPUT_CONTEXT:
1211         case OUTPUT_UNIFIED:
1212 #if 0
1213         case OUTPUT_ED:
1214         case OUTPUT_FORWARD_ED:
1215         case OUTPUT_RCS:
1216         case OUTPUT_IFDEF:
1217         case OUTPUT_SDIFF:
1218 #endif
1219                 break;
1220         case OUTPUT_HTML:
1221                 print_html_terminator();
1222                 break;
1223         }
1224         
1225         fclose(outfile);
1226         outfile = nullptr;
1227 }
1228
1229 /**
1230  * @brief Write out a patch file.
1231  * Writes patch file using already computed diffutils script. Converts path
1232  * delimiters from \ to / since we want to keep compatibility with patch-tools.
1233  * @param [in] script list of changes.
1234  * @param [in] inf file_data table containing filenames
1235  */
1236 void CDiffWrapper::WritePatchFile(struct change * script, file_data * inf)
1237 {
1238         file_data inf_patch[2] = { inf[0], inf[1] };
1239
1240         // Get paths, primarily use alternative paths, only if they are empty
1241         // use full filepaths
1242         String path1(m_alternativePaths[0]);
1243         String path2(m_alternativePaths[1]);
1244         if (path1.empty())
1245                 path1 = m_files[0];
1246         if (path2.empty())
1247                 path2 = m_files[1];
1248         path1 = paths::ToUnixPath(path1);
1249         path2 = paths::ToUnixPath(path2);
1250         if ((inf_patch[0].linbuf && ucr::CheckForInvalidUtf8(inf_patch[0].buffer, inf_patch[0].buffered_chars)) ||
1251                 (inf_patch[1].linbuf && ucr::CheckForInvalidUtf8(inf_patch[1].buffer, inf_patch[1].buffered_chars)))
1252         {
1253                 inf_patch[0].name = _strdup(ucr::toThreadCP(path1).c_str());
1254                 inf_patch[1].name = _strdup(ucr::toThreadCP(path2).c_str());
1255         }
1256         else
1257         {
1258                 inf_patch[0].name = _strdup(ucr::toUTF8(path1).c_str());
1259                 inf_patch[1].name = _strdup(ucr::toUTF8(path2).c_str());
1260         }
1261
1262         // If paths in m_s1File and m_s2File point to original files, then we can use
1263         // them to fix potentially meaningless stats from potentially temporary files,
1264         // resulting from whatever transforms may have taken place.
1265         // If not, then we can't help it, and hence assert that this won't happen.
1266         if (!m_bPathsAreTemp)
1267         {
1268                 mywstat(m_files[0].c_str(), &inf_patch[0].stat);
1269                 mywstat(m_files[1].c_str(), &inf_patch[1].stat);
1270         }
1271         else
1272         {
1273                 assert(false);
1274         }
1275
1276         outfile = nullptr;
1277         if (!m_sPatchFile.empty())
1278         {
1279                 const TCHAR *mode = (m_bAppendFiles ? _T("a+") : _T("w+"));
1280                 if (_tfopen_s(&outfile, m_sPatchFile.c_str(), mode) != 0)
1281                         outfile = nullptr;
1282         }
1283
1284         if (outfile == nullptr)
1285         {
1286                 m_status.bPatchFileFailed = true;
1287                 return;
1288         }
1289
1290         // Print "command line"
1291         if (m_bAddCmdLine && output_style != OUTPUT_HTML)
1292         {
1293                 String switches = FormatSwitchString();
1294                 _ftprintf(outfile, _T("diff%s %s %s\n"),
1295                         switches.c_str(), 
1296                         path1 == _T("NUL") ? _T("/dev/null") : path1.c_str(),
1297                         path2 == _T("NUL") ? _T("/dev/null") : path2.c_str());
1298         }
1299
1300         if (strcmp(inf[0].name, "NUL") == 0)
1301         {
1302                 free((void *)inf_patch[0].name);
1303                 inf_patch[0].name = _strdup("/dev/null");
1304         }
1305         if (strcmp(inf[1].name, "NUL") == 0)
1306         {
1307                 free((void *)inf_patch[1].name);
1308                 inf_patch[1].name = _strdup("/dev/null");
1309         }
1310
1311         // Output patchfile
1312         switch (output_style)
1313         {
1314         case OUTPUT_NORMAL:
1315                 print_normal_script(script);
1316                 break;
1317         case OUTPUT_CONTEXT:
1318                 print_context_header(inf_patch, 0);
1319                 print_context_script(script, 0);
1320                 break;
1321         case OUTPUT_UNIFIED:
1322                 print_context_header(inf_patch, 1);
1323                 print_context_script(script, 1);
1324                 break;
1325 #if 0
1326         case OUTPUT_ED:
1327                 print_ed_script(script);
1328                 break;
1329         case OUTPUT_FORWARD_ED:
1330                 pr_forward_ed_script(script);
1331                 break;
1332         case OUTPUT_RCS:
1333                 print_rcs_script(script);
1334                 break;
1335         case OUTPUT_IFDEF:
1336                 print_ifdef_script(script);
1337                 break;
1338         case OUTPUT_SDIFF:
1339                 print_sdiff_script(script);
1340                 break;
1341 #endif
1342         case OUTPUT_HTML:
1343                 print_html_diff_header(inf_patch);
1344                 print_html_script(script);
1345                 print_html_diff_terminator();
1346         }
1347         
1348         fclose(outfile);
1349         outfile = nullptr;
1350
1351         free((void *)inf_patch[0].name);
1352         free((void *)inf_patch[1].name);
1353 }
1354
1355 /**
1356  * @brief Set line filters, given as one string.
1357  * @param [in] filterStr Filters.
1358  */
1359 void CDiffWrapper::SetFilterList(const String& filterStr)
1360 {
1361         // Remove filterlist if new filter is empty
1362         if (filterStr.empty())
1363         {
1364                 m_pFilterList.reset();
1365                 return;
1366         }
1367
1368         // Adding new filter without previous filter
1369         if (m_pFilterList == nullptr)
1370         {
1371                 m_pFilterList.reset(new FilterList);
1372         }
1373
1374         m_pFilterList->RemoveAllFilters();
1375
1376         std::string regexp_str = ucr::toUTF8(filterStr);
1377
1378         // Add every "line" of regexps to regexp list
1379         StringTokenizer tokens(regexp_str, "\r\n");
1380         for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
1381                 m_pFilterList->AddRegExp(*it);
1382 }
1383
1384 void CDiffWrapper::SetFilterList(const FilterList* pFilterList)
1385 {
1386         if (!pFilterList)
1387                 m_pFilterList.reset();
1388         else
1389         {
1390                 m_pFilterList.reset(new FilterList());
1391                 *m_pFilterList = *pFilterList;
1392         }
1393 }
1394
1395 const SubstitutionList* CDiffWrapper::GetSubstitutionList() const
1396 {
1397         return m_pSubstitutionList.get();
1398 }
1399
1400 void CDiffWrapper::SetSubstitutionList(std::shared_ptr<SubstitutionList> pSubstitutionList)
1401 {
1402         m_pSubstitutionList = pSubstitutionList;
1403 }
1404
1405 void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext)
1406 {
1407         m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str());
1408 }
1409
1410 /**
1411  * @brief Copy text stat results from diffutils back into the FileTextStats structure
1412  */
1413 void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
1414 {
1415         myTextStats->ncrlfs = inf->count_crlfs;
1416         myTextStats->ncrs = inf->count_crs;
1417         myTextStats->nlfs = inf->count_lfs;
1418         myTextStats->nzeros = inf->count_zeros;
1419 }
1420
1421 /**
1422  * @brief Copy both left & right text stats results back into the DiffFileData text stats
1423  */
1424 void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData)
1425 {
1426         CopyTextStats(&inf[0], &diffData->m_textStats[0]);
1427         CopyTextStats(&inf[1], &diffData->m_textStats[1]);
1428 }