OSDN Git Service

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