OSDN Git Service

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