OSDN Git Service

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