1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * @file DiffWrapper.cpp
5 * @brief Code for DiffWrapper class
7 * @date Created: 2003-08-22
12 #include "DiffWrapper.h"
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"
29 #include "MovedLines.h"
30 #include "FilterList.h"
33 #include "xdiff_gnudiff_compat.h"
34 #include "FileTransform.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"
44 #include "Exceptions.h"
45 #include "parsers/crystallineparser.h"
46 #include "SyntaxColors.h"
48 #include "SubstitutionList.h"
49 #include "codepage_detect.h"
54 using Poco::StringTokenizer;
55 using Poco::Exception;
59 extern "C" int is_blank_line(char const* pch, char const* limit);
61 static void CopyTextStats(const file_data * inf, FileTextStats * myTextStats);
62 static void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData);
64 constexpr char* FILTERED_LINE = "!" "c0d5089f" "-" "3d91" "-" "4d69" "-" "b406" "-" "dc5a5b51a4f8";
67 * @brief Default constructor.
68 * Initializes members.
70 CDiffWrapper::CDiffWrapper()
71 : m_pFilterCommentsDef(nullptr)
72 , m_bCreatePatchFile(false)
73 , m_bUseDiffList(false)
75 , m_bAppendFiles(false)
77 , m_infoPrediffer(nullptr)
78 , m_pDiffList(nullptr)
79 , m_bPathsAreTemp(false)
80 , m_pFilterList(nullptr)
81 , m_pSubstitutionList{nullptr}
82 , m_bPluginsEnabled(false)
84 , m_codepage(ucr::CP_UTF_8)
87 // character that ends a line. Currently this is always `\n'
94 CDiffWrapper::~CDiffWrapper() = default;
97 * @brief Enables/disables patch-file creation and sets filename.
98 * This function enables or disables patch file creation. When
99 * @p filename is empty, patch files are disabled.
100 * @param [in] filename Filename for patch file, or empty string.
102 void CDiffWrapper::SetCreatePatchFile(const String &filename)
104 if (filename.empty())
106 m_bCreatePatchFile = false;
107 m_sPatchFile.clear();
111 m_bCreatePatchFile = true;
112 m_sPatchFile = filename;
113 strutils::replace(m_sPatchFile, _T("/"), _T("\\"));
118 * @brief Enables/disabled DiffList creation ands sets DiffList.
119 * This function enables or disables DiffList creation. When
120 * @p diffList is `nullptr`, a difflist was not created. When valid
121 * DiffList pointer is given, compare results are stored into it.
122 * @param [in] diffList Pointer to DiffList getting compare results.
124 void CDiffWrapper::SetCreateDiffList(DiffList *diffList)
126 if (diffList == nullptr)
128 m_bUseDiffList = false;
129 m_pDiffList = nullptr;
133 m_bUseDiffList = true;
134 m_pDiffList = diffList;
139 * @brief Returns current set of options used by diff-engine.
140 * This function converts internally used diff-options to
141 * format used outside CDiffWrapper and returns them.
142 * @param [in,out] options Pointer to structure getting used options.
144 void CDiffWrapper::GetOptions(DIFFOPTIONS *options) const
146 assert(options != nullptr);
147 DIFFOPTIONS tmpOptions = {0};
148 m_options.GetAsDiffOptions(tmpOptions);
149 *options = tmpOptions;
153 * @brief Set options for Diff-engine.
154 * This function converts given options to format CDiffWrapper uses
155 * internally and stores them.
156 * @param [in] options Pointer to structure having new options.
158 void CDiffWrapper::SetOptions(const DIFFOPTIONS *options)
160 assert(options != nullptr);
161 m_options.SetFromDiffOptions(*options);
162 m_xdlFlags = make_xdl_flags(m_options);
165 void CDiffWrapper::SetPrediffer(const PrediffingInfo * prediffer /*= nullptr*/)
167 // all flags are set correctly during the construction
168 m_infoPrediffer.reset(new PrediffingInfo);
170 if (prediffer != nullptr)
171 *m_infoPrediffer = *prediffer;
175 * @brief Set options used for patch-file creation.
176 * @param [in] options Pointer to structure having new options.
178 void CDiffWrapper::SetPatchOptions(const PATCHOPTIONS *options)
180 assert(options != nullptr);
181 m_options.m_contextLines = options->nContext;
183 switch (options->outputStyle)
186 m_options.m_outputStyle = DIFF_OUTPUT_NORMAL;
189 m_options.m_outputStyle = DIFF_OUTPUT_CONTEXT;
192 m_options.m_outputStyle = DIFF_OUTPUT_UNIFIED;
195 m_options.m_outputStyle = DIFF_OUTPUT_HTML;
198 throw "Unknown output style!";
202 m_bAddCmdLine = options->bAddCommandline;
206 * @brief Enables/disables moved block detection.
207 * @param [in] bDetectMovedBlocks If true moved blocks are detected.
209 void CDiffWrapper::SetDetectMovedBlocks(bool bDetectMovedBlocks)
211 if (bDetectMovedBlocks)
213 if (m_pMovedLines[0] == nullptr)
215 m_pMovedLines[0].reset(new MovedLines);
216 m_pMovedLines[1].reset(new MovedLines);
217 m_pMovedLines[2].reset(new MovedLines);
222 m_pMovedLines[0].reset();
223 m_pMovedLines[1].reset();
224 m_pMovedLines[2].reset();
228 static String convertToTString(const char* start, const char* end)
230 if (!ucr::CheckForInvalidUtf8(start, end - start))
232 return ucr::toTString(std::string(start, end));
238 ucr::maketstring(text, start, end - start, -1, &lossy);
243 static unsigned GetLastLineCookie(unsigned dwCookie, int startLine, int endLine, const char **linbuf, CrystalLineParser::TextDefinition* enuType)
247 for (int i = startLine; i <= endLine; ++i)
249 String text = convertToTString(linbuf[i], linbuf[i + 1]);
250 int nActualItems = 0;
251 std::vector<CrystalLineParser::TEXTBLOCK> blocks(text.length());
252 dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), static_cast<int>(text.length()), blocks.data(), nActualItems);
257 static unsigned GetCommentsFilteredText(unsigned dwCookie, int startLine, int endLine, const char **linbuf, std::string& filtered, CrystalLineParser::TextDefinition* enuType)
260 for (int i = startLine; i <= endLine; ++i)
262 String text = convertToTString(linbuf[i], linbuf[i + 1]);
263 unsigned textlen = static_cast<unsigned>(text.size());
270 int nActualItems = 0;
271 std::vector<CrystalLineParser::TEXTBLOCK> blocks(textlen);
272 dwCookie = enuType->ParseLineX(dwCookie, text.c_str(), textlen, blocks.data(), nActualItems);
274 if (nActualItems == 0)
280 for (int j = 0; j < nActualItems; ++j)
282 CrystalLineParser::TEXTBLOCK& block = blocks[j];
283 if (block.m_nColorIndex != COLORINDEX_COMMENT)
285 unsigned blocklen = (j < nActualItems - 1) ? (blocks[j + 1].m_nCharPos - block.m_nCharPos) : textlen - block.m_nCharPos;
286 filteredT.append(text.c_str() + block.m_nCharPos, blocklen);
290 if (blocks[nActualItems - 1].m_nColorIndex == COLORINDEX_COMMENT)
292 // If there is an inline comment, the EOL for that line will be deleted, so add the EOL.
293 size_t fullLen = linbuf[i + 1] - linbuf[i];
294 size_t len = linelen(linbuf[i], fullLen);
295 for (size_t j = len; j < fullLen; ++j)
296 filteredT += linbuf[i][j];
302 filtered = ucr::toUTF8(filteredT);
308 * @brief Replace a string inside a string with another string.
309 * This function searches for a string inside another string an if found,
310 * replaces it with another string. Function can replace several instances
311 * of the string inside one string.
312 * @param [in,out] target A string containing another string to replace.
313 * @param [in] find A string to search and replace with another (@p replace).
314 * @param [in] replace A string used to replace original (@p find).
316 void Replace(std::string &target, const std::string &find, const std::string &replace)
318 const std::string::size_type find_len = find.length();
319 const std::string::size_type replace_len = replace.length();
320 std::string::size_type pos = 0;
321 while ((pos = target.find(find, pos)) != std::string::npos)
323 target.replace(pos, find_len, replace);
329 * @brief Replace the characters that matche characters specified in its arguments
330 * @param [in,out] str - A string containing another string to replace.
331 * @param [in] chars - characters to search for
332 * @param [in] rep - String to replace
334 static void ReplaceChars(std::string & str, const char* chars, const char *rep)
336 std::string::size_type pos = 0;
337 size_t replen = strlen(rep);
338 while ((pos = str.find_first_of(chars, pos)) != std::string::npos)
340 std::string::size_type posend = str.find_first_not_of(chars, pos);
341 if (posend != String::npos)
342 str.replace(pos, posend - pos, rep);
344 str.replace(pos, str.length() - pos, rep);
350 * @brief The main entry for post filtering. Performs post-filtering, by setting comment blocks to trivial
351 * @param [in, out] thisob Current change
352 * @return Number of trivial diffs inserted
354 int CDiffWrapper::PostFilter(PostFilterContext& ctxt, change* thisob, const file_data *file_data_ary) const
356 const int first0 = thisob->line0;
357 const int first1 = thisob->line1;
358 const int last0 = first0 + thisob->deleted - 1;
359 const int last1 = first1 + thisob->inserted - 1;
360 int trans_a0 = 0, trans_b0 = 0, trans_a1 = 0, trans_b1 = 0;
361 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
362 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
363 const int qtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
364 const int qtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
365 const int lineNumberLeft = trans_a0 - 1;
366 const int lineNumberRight = trans_a1 - 1;
368 std::string lineDataLeft, lineDataRight;
370 if (m_options.m_filterCommentsLines)
372 ctxt.dwCookieLeft = GetLastLineCookie(ctxt.dwCookieLeft,
373 ctxt.nParsedLineEndLeft + 1, lineNumberLeft - 1, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, m_pFilterCommentsDef);
374 ctxt.dwCookieRight = GetLastLineCookie(ctxt.dwCookieRight,
375 ctxt.nParsedLineEndRight + 1, lineNumberRight - 1, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, m_pFilterCommentsDef);
377 ctxt.nParsedLineEndLeft = lineNumberLeft + qtyLinesLeft - 1;
378 ctxt.nParsedLineEndRight = lineNumberRight + qtyLinesRight - 1;;
380 ctxt.dwCookieLeft = GetCommentsFilteredText(ctxt.dwCookieLeft,
381 lineNumberLeft, ctxt.nParsedLineEndLeft, file_data_ary[0].linbuf + file_data_ary[0].linbuf_base, lineDataLeft, m_pFilterCommentsDef);
382 ctxt.dwCookieRight = GetCommentsFilteredText(ctxt.dwCookieRight,
383 lineNumberRight, ctxt.nParsedLineEndRight, file_data_ary[1].linbuf + file_data_ary[1].linbuf_base, lineDataRight, m_pFilterCommentsDef);
387 lineDataLeft.assign(file_data_ary[0].linbuf[lineNumberLeft + file_data_ary[0].linbuf_base],
388 file_data_ary[0].linbuf[lineNumberLeft + qtyLinesLeft + file_data_ary[0].linbuf_base]
389 - file_data_ary[0].linbuf[lineNumberLeft + file_data_ary[0].linbuf_base]);
390 lineDataRight.assign(file_data_ary[1].linbuf[lineNumberRight + file_data_ary[1].linbuf_base],
391 file_data_ary[1].linbuf[lineNumberRight + qtyLinesRight + file_data_ary[1].linbuf_base]
392 - file_data_ary[1].linbuf[lineNumberRight + file_data_ary[1].linbuf_base]);
395 if (m_pFilterList != nullptr && m_pFilterList->HasRegExps())
397 // Match lines against regular expression filters
398 // Our strategy is that every line in both sides must
399 // match regexp before we mark difference as ignored.
400 bool match1 = RegExpFilter(lineDataLeft);
401 bool match2 = RegExpFilter(lineDataRight);
402 if (match1 && match2)
409 if (m_pSubstitutionList)
411 lineDataLeft = m_pSubstitutionList->Subst(lineDataLeft, m_codepage);
412 lineDataRight = m_pSubstitutionList->Subst(lineDataRight, m_codepage);
415 if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_ALL)
417 //Ignore character case
418 ReplaceChars(lineDataLeft, " \t", "");
419 ReplaceChars(lineDataRight, " \t", "");
421 else if (m_options.m_ignoreWhitespace == WHITESPACE_IGNORE_CHANGE)
423 //Ignore change in whitespace char count
424 ReplaceChars(lineDataLeft, " \t", " ");
425 ReplaceChars(lineDataRight, " \t", " ");
428 if (m_options.m_bIgnoreNumbers)
430 //Ignore number character case
431 ReplaceChars(lineDataLeft, "0123456789", "");
432 ReplaceChars(lineDataRight, "0123456789", "");
434 if (m_options.m_bIgnoreCase)
437 for (std::string::iterator pb = lineDataLeft.begin(), pe = lineDataLeft.end(); pb != pe; ++pb)
438 *pb = static_cast<char>(::toupper(*pb));
439 for (std::string::iterator pb = lineDataRight.begin(), pe = lineDataRight.end(); pb != pe; ++pb)
440 *pb = static_cast<char>(::toupper(*pb));
442 if (m_options.m_bIgnoreEOLDifference)
444 Replace(lineDataLeft, "\r\n", "\n");
445 Replace(lineDataLeft, "\r", "\n");
446 Replace(lineDataRight, "\r\n", "\n");
447 Replace(lineDataRight, "\r", "\n");
450 // If both match after filtering, mark this diff hunk as trivial and return.
451 if (lineDataLeft == lineDataRight)
453 //only difference is trival
458 auto SplitLines = [](const std::string& lines) -> std::vector<std::string_view>
460 std::vector<std::string_view> result;
461 const char* line = lines.c_str();
462 for (size_t i = 0; i < lines.length(); ++i)
467 if (i + 1 < lines.length() && lines[i + 1] == '\n')
469 result.emplace_back(line, lines.c_str() + i + 1 - line);
470 line = lines.c_str() + i + 1;
474 result.emplace_back(line, lines.c_str() + i + 1 - line);
475 line = lines.c_str() + i + 1;
478 if (!lines.empty() && (lines.back() != '\r' && lines.back() != '\n'))
479 result.emplace_back(line, lines.c_str() + lines.length() - line);
483 std::vector<std::string_view> leftLines = SplitLines(lineDataLeft);
484 std::vector<std::string_view> rightLines = SplitLines(lineDataRight);
486 if (qtyLinesLeft != leftLines.size() || qtyLinesRight != rightLines.size())
489 // If both do not match as a result of filtering, some lines may match,
490 // so diff calculation is performed again using the filtered lines.
491 change* script = diff_2_buffers_xdiff(
492 lineDataLeft.c_str(), lineDataLeft.length(),
493 lineDataRight.c_str(), lineDataRight.length(), m_xdlFlags);
495 auto TranslateLineNumbers = [](change* thisob, change* script)
497 assert(thisob && script);
498 for (change* cur = script; cur; cur = cur->link)
500 cur->line0 += thisob->line0;
501 cur->line1 += thisob->line1;
505 // Insert lines with no differences as trivial diffs after filtering
506 auto InsertTrivialChanges = [](change* thisob, change* script) -> int
508 assert(thisob && script);
509 int l0 = thisob->line0;
510 int l1 = thisob->line1;
511 change* first = script;
512 change* prev = nullptr;
513 int nTrivialInserts = 0;
514 for (change* cur = script; cur; cur = cur->link)
516 if (l0 < cur->line0 || l1 < cur->line1)
519 change *newob = (change *)xmalloc(sizeof (change));
522 newob->deleted = cur->line0 - l0;
523 newob->inserted = cur->line1 - l1;
529 std::swap(newob->line0, cur->line0);
530 std::swap(newob->line1, cur->line1);
531 std::swap(newob->deleted, cur->deleted);
532 std::swap(newob->inserted, cur->inserted);
533 std::swap(newob->trivial, cur->trivial);
534 std::swap(newob->match0, cur->match0);
535 std::swap(newob->match1, cur->match1);
536 newob->link = cur->link;
545 l0 = cur->line0 + cur->deleted;
546 l1 = cur->line1 + cur->inserted;
549 if (l0 < thisob->line0 + thisob->deleted || l1 < thisob->line1 + thisob->inserted)
552 change *newob = (change *)xmalloc(sizeof (change));
556 newob->deleted = thisob->line0 + thisob->deleted - l0;
557 newob->inserted = thisob->line1 + thisob->inserted - l1;
561 newob->link = nullptr;
563 return nTrivialInserts;
566 // Insert blank lines or filtered lines that are only on one side as trivial diffs.
567 auto InsertTrivialChanges2 =
568 [](change* thisob, change* script, bool ignoreBlankLines,
569 const std::vector<std::string_view>& leftLines,
570 const std::vector<std::string_view>& rightLines) -> int
572 assert(thisob && script);
573 auto IsBlankLine = [](const std::string_view& line)
577 if (!std::isspace(static_cast<unsigned char>(c)))
582 int nTrivialInserts = 0;
583 for (change* cur = script; cur; cur = cur->link)
585 if (!cur->trivial && cur->deleted != cur->inserted)
587 bool ignorable = true;
588 if (cur->deleted > cur->inserted)
590 for (int i = cur->line0 + cur->inserted - thisob->line0; i < cur->line0 + cur->deleted - thisob->line0; ++i)
592 if (!(ignoreBlankLines && IsBlankLine(leftLines[i])) && leftLines[i] != FILTERED_LINE)
597 if (cur->inserted == 0)
604 change* newob = (change*)xmalloc(sizeof(change));
605 newob->line0 = cur->line0 + cur->inserted;
606 newob->line1 = cur->line1 + cur->inserted;
607 newob->deleted = cur->deleted - cur->inserted;
612 newob->link = cur->link;
614 cur->deleted = cur->inserted;
620 for (int i = cur->line1 + cur->deleted - thisob->line1; i < cur->line1 + cur->inserted - thisob->line1; ++i)
622 if (!(ignoreBlankLines && IsBlankLine(rightLines[i])) && rightLines[i] != FILTERED_LINE)
627 if (cur->deleted == 0)
634 change* newob = (change*)xmalloc(sizeof(change));
635 newob->line0 = cur->line0 + cur->deleted;
636 newob->line1 = cur->line1 + cur->deleted;
638 newob->inserted = cur->inserted - cur->deleted;
642 newob->link = cur->link;
644 cur->inserted = cur->deleted;
650 return nTrivialInserts;
653 auto ReplaceChanges = [](change* thisob, change* script)
655 assert(thisob && script);
656 change* last = nullptr;
657 for (change* cur = script; cur; cur = cur->link)
659 last->link = thisob->link;
660 thisob->link = script->link;
661 thisob->line0 = script->line0;
662 thisob->line1 = script->line1;
663 thisob->deleted = script->deleted;
664 thisob->inserted = script->inserted;
665 thisob->trivial = script->trivial;
666 thisob->match0 = script->match0;
667 thisob->match1 = script->match1;
671 TranslateLineNumbers(thisob, script);
672 int nTrivialInserts = InsertTrivialChanges(thisob, script);
673 nTrivialInserts += InsertTrivialChanges2(thisob, script, m_options.m_bIgnoreBlankLines, leftLines, rightLines);
674 ReplaceChanges(thisob, script);
675 return nTrivialInserts;
679 * @brief Set source paths for diffing two files.
680 * Sets full paths to two files we are diffing. Paths can be actual user files
681 * or temporary copies of user files. Parameter @p tempPaths tells if paths
682 * are temporary paths that can be deleted.
683 * @param [in] files Files to compare
684 * @param [in] tempPaths Are given paths temporary (can be deleted)?.
686 void CDiffWrapper::SetPaths(const PathContext &tFiles,
690 m_bPathsAreTemp = tempPaths;
694 * @brief Runs diff-engine.
696 bool CDiffWrapper::RunFileDiff()
698 PathContext aFiles = m_files;
700 for (file = 0; file < m_files.GetSize(); file++)
701 aFiles[file] = paths::ToWindowsPath(aFiles[file]);
704 String strFileTemp[3];
705 std::copy(m_files.begin(), m_files.end(), strFileTemp);
707 m_options.SetToDiffUtils();
710 m_nDiffs = m_pDiffList->GetSize();
712 for (file = 0; file < aFiles.GetSize(); file++)
714 if (m_bPluginsEnabled)
716 // Do the preprocessing now, overwrite the temp files
717 // NOTE: FileTransform_UCS2ToUTF8() may create new temp
718 // files and return new names, those created temp files
719 // are deleted in end of function.
721 // this can only fail if the data can not be saved back (no more
722 // place on disk ???) What to do then ??
723 if (m_infoPrediffer && !m_infoPrediffer->Prediffing(strFileTemp[file], m_sToFindPrediffer, m_bPathsAreTemp, { strFileTemp[file] }))
725 // display a message box
726 String sError = strutils::format_string2(
727 _("An error occurred while prediffing the file '%1' with the plugin '%2'. The prediffing is not applied any more."),
728 strFileTemp[file].c_str(),
729 m_infoPrediffer->GetPluginPipeline().c_str());
730 AppErrorMessageBox(sError);
731 // don't use any more this prediffer
732 m_infoPrediffer->ClearPluginPipeline();
737 struct change *script = nullptr;
738 struct change *script10 = nullptr;
739 struct change *script12 = nullptr;
740 DiffFileData diffdata, diffdata10, diffdata12;
741 int bin_flag = 0, bin_flag10 = 0, bin_flag12 = 0;
743 if (aFiles.GetSize() == 2)
745 diffdata.SetDisplayFilepaths(aFiles[0], aFiles[1]); // store true names for diff utils patch file
746 // This opens & fstats both files (if it succeeds)
747 if (!diffdata.OpenFiles(strFileTemp[0], strFileTemp[1]))
752 // Compare the files, if no error was found.
753 // Last param (bin_file) is `nullptr` since we don't
754 // (yet) need info about binary sides.
755 bRet = Diff2Files(&script, &diffdata, &bin_flag, nullptr);
757 // We don't anymore create diff-files for every rescan.
758 // User can create patch-file whenever one wants to.
759 // We don't need to waste time. But lets keep this as
760 // debugging aid. Sometimes it is very useful to see
761 // what differences diff-engine sees!
763 // throw the diff into a temp file
764 String sTempPath = env::GetTemporaryPath(); // get path to Temp folder
765 String path = paths::ConcatPath(sTempPath, _T("Diff.txt"));
767 if (cio::tfopen_s(&outfile, path, _T("w+")) == 0)
769 print_normal_script(script);
777 diffdata10.SetDisplayFilepaths(aFiles[1], aFiles[0]); // store true names for diff utils patch file
778 diffdata12.SetDisplayFilepaths(aFiles[1], aFiles[2]); // store true names for diff utils patch file
780 if (!diffdata10.OpenFiles(strFileTemp[1], strFileTemp[0]))
785 bRet = Diff2Files(&script10, &diffdata10, &bin_flag10, nullptr);
787 if (!diffdata12.OpenFiles(strFileTemp[1], strFileTemp[2]))
792 bRet = Diff2Files(&script12, &diffdata12, &bin_flag12, nullptr);
795 // First determine what happened during comparison
796 // If there were errors or files were binaries, don't bother
797 // creating diff-lists or patches
799 // diff_2_files set bin_flag to -1 if different binary
800 // diff_2_files set bin_flag to +1 if same binary
802 file_data * inf = diffdata.m_inf;
803 file_data * inf10 = diffdata10.m_inf;
804 file_data * inf12 = diffdata12.m_inf;
806 if (aFiles.GetSize() == 2)
810 m_status.bBinaries = true;
812 m_status.Identical = IDENTLEVEL::ALL;
814 m_status.Identical = IDENTLEVEL::NONE;
817 { // text files according to diffutils, so change script exists
818 m_status.Identical = (script == 0) ? IDENTLEVEL::ALL : IDENTLEVEL::NONE;
819 m_status.bBinaries = false;
821 m_status.bMissingNL[0] = !!inf[0].missing_newline;
822 m_status.bMissingNL[1] = !!inf[1].missing_newline;
826 m_status.Identical = IDENTLEVEL::NONE;
827 if (bin_flag10 != 0 || bin_flag12 != 0)
829 m_status.bBinaries = true;
830 if (bin_flag10 != -1 && bin_flag12 != -1)
831 m_status.Identical = IDENTLEVEL::ALL;
832 else if (bin_flag10 != -1)
833 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
834 else if (bin_flag12 != -1)
835 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
837 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
840 { // text files according to diffutils, so change script exists
841 m_status.bBinaries = false;
842 if (script10 == nullptr && script12 == nullptr)
843 m_status.Identical = IDENTLEVEL::ALL;
844 else if (script10 == nullptr)
845 m_status.Identical = IDENTLEVEL::EXCEPTRIGHT;
846 else if (script12 == nullptr)
847 m_status.Identical = IDENTLEVEL::EXCEPTLEFT;
849 m_status.Identical = IDENTLEVEL::EXCEPTMIDDLE;
851 m_status.bMissingNL[0] = !!inf10[1].missing_newline;
852 m_status.bMissingNL[1] = !!inf12[0].missing_newline;
853 m_status.bMissingNL[2] = !!inf12[1].missing_newline;
858 if (!m_status.bBinaries && m_bCreatePatchFile && aFiles.GetSize() == 2)
860 WritePatchFile(script, &inf[0]);
863 // Go through diffs adding them to WinMerge's diff list
864 // This is done on every WinMerge's doc rescan!
865 if (!m_status.bBinaries && m_bUseDiffList)
867 if (aFiles.GetSize() == 2)
868 LoadWinMergeDiffsFromDiffUtilsScript(script, diffdata.m_inf);
870 LoadWinMergeDiffsFromDiffUtilsScript3(
872 diffdata10.m_inf, diffdata12.m_inf);
875 // cleanup the script
876 if (aFiles.GetSize() == 2)
877 FreeDiffUtilsScript(script);
880 FreeDiffUtilsScript(script10);
881 FreeDiffUtilsScript(script12);
884 // Done with diffutils filedata
885 if (aFiles.GetSize() == 2)
895 if (m_bPluginsEnabled)
897 // Delete temp files transformation functions possibly created
898 for (file = 0; file < aFiles.GetSize(); file++)
900 if (strutils::compare_nocase(aFiles[file], strFileTemp[file]) != 0)
904 TFile(strFileTemp[file]).remove();
908 LogErrorStringUTF8(e.displayText());
910 strFileTemp[file].erase();
918 * @brief Add diff to external diff-list
920 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, unsigned begin0, unsigned end0, unsigned begin1, unsigned end1, OP_TYPE op)
925 dr.begin[0] = begin0;
927 dr.begin[1] = begin1;
932 dr.blank[0] = dr.blank[1] = dr.blank[2] = -1;
933 pDiffList->AddDiff(dr);
935 catch (std::exception& e)
937 AppErrorMessageBox(ucr::toTString(e.what()));
941 void CDiffWrapper::AddDiffRange(DiffList *pDiffList, DIFFRANGE &dr)
945 pDiffList->AddDiff(dr);
947 catch (std::exception& e)
949 AppErrorMessageBox(ucr::toTString(e.what()));
954 * @brief Expand last DIFFRANGE of file by one line to contain last line after EOL.
955 * @param [in] leftBufferLines size of array pane left
956 * @param [in] rightBufferLines size of array pane right
957 * @param [in] left on whitch side we have to insert
958 * @param [in] bIgnoreBlankLines, if true we always add a new diff and mark as trivial
960 void CDiffWrapper::FixLastDiffRange(int nFiles, int bufferLines[], bool bMissingNL[], bool bIgnoreBlankLines)
963 const int count = m_pDiffList->GetSize();
966 m_pDiffList->GetDiff(count - 1, dr);
968 for (int file = 0; file < nFiles; file++)
970 if (!bMissingNL[file])
974 m_pDiffList->SetDiff(count - 1, dr);
978 // we have to create the DIFF
979 for (int file = 0; file < nFiles; file++)
981 dr.end[file] = bufferLines[file] - 1;
982 if (bMissingNL[file])
983 dr.begin[file] = dr.end[file];
985 dr.begin[file] = dr.end[file] + 1;
987 assert(dr.begin[0] == dr.begin[file]);
989 if (bIgnoreBlankLines)
992 AddDiffRange(m_pDiffList, dr);
997 * @brief Returns status-data from diff-engine last run
999 void CDiffWrapper::GetDiffStatus(DIFFSTATUS *status) const
1001 std::memcpy(status, &m_status, sizeof(DIFFSTATUS));
1005 * @brief Formats command-line for diff-engine last run (like it was called from command-line)
1007 String CDiffWrapper::FormatSwitchString() const
1011 switch (m_options.m_outputStyle)
1013 case DIFF_OUTPUT_NORMAL:
1016 case DIFF_OUTPUT_CONTEXT:
1017 switches = (m_options.m_contextLines > 0) ? _T(" -C ") : _T(" -c");
1019 case DIFF_OUTPUT_UNIFIED:
1020 switches = (m_options.m_contextLines > 0) ? _T(" -U ") : _T(" -u");
1023 case DIFF_OUTPUT_ED:
1024 switches = _T(" e");
1026 case DIFF_OUTPUT_FORWARD_ED:
1027 switches = _T(" f");
1029 case DIFF_OUTPUT_RCS:
1030 switches = _T(" n");
1032 case DIFF_OUTPUT_IFDEF:
1033 switches = _T(" D");
1035 case DIFF_OUTPUT_SDIFF:
1036 switches = _T(" y");
1041 if ((m_options.m_outputStyle == DIFF_OUTPUT_CONTEXT || m_options.m_outputStyle == DIFF_OUTPUT_UNIFIED) &&
1042 m_options.m_contextLines > 0)
1043 switches += strutils::to_str(m_options.m_contextLines);
1045 if (ignore_all_space_flag > 0)
1046 switches += _T(" -w");
1048 if (ignore_blank_lines_flag > 0)
1049 switches += _T(" -B");
1051 if (ignore_case_flag > 0)
1052 switches += _T(" -i");
1054 if (ignore_space_change_flag > 0)
1055 switches += _T(" -b");
1061 * @brief Enables/disables patch-file appending.
1062 * If the file for patch already exists then the patch will be appended to
1064 * @param [in] bAppendFiles If true patch will be appended to existing file.
1066 void CDiffWrapper::SetAppendFiles(bool bAppendFiles)
1068 m_bAppendFiles = bAppendFiles;
1072 * @brief Compare two files using diffutils.
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 `nullptr` 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.
1088 bool CDiffWrapper::Diff2Files(struct change ** diffs, DiffFileData *diffData,
1089 int * bin_status, int * bin_file) const
1095 if (m_options.m_diffAlgorithm != DIFF_ALGORITHM_DEFAULT)
1097 const unsigned xdl_flags = make_xdl_flags(m_options);
1098 *diffs = diff_2_files_xdiff(diffData->m_inf, bin_status,
1099 (m_pMovedLines[0] != nullptr), bin_file, xdl_flags);
1100 files[0] = diffData->m_inf[0];
1101 files[1] = diffData->m_inf[1];
1105 // Diff files. depth is zero because we are not comparing dirs
1106 *diffs = diff_2_files(diffData->m_inf, 0, bin_status,
1107 (m_pMovedLines[0] != nullptr), bin_file);
1109 CopyDiffutilTextStats(diffData->m_inf, diffData);
1111 catch (SE_Exception&)
1120 * @brief Free script (the diffutils linked list of differences)
1123 CDiffWrapper::FreeDiffUtilsScript(struct change * & script)
1125 if (script == nullptr) return;
1126 struct change *e=nullptr, *p=nullptr;
1127 // cleanup the script
1128 for (e = script; e != nullptr; e = p)
1137 * @brief Match regular expression list against given difference.
1138 * This function matches the regular expression list against the difference
1139 * (given as start line and end line). Matching the diff requires that all
1140 * lines in difference match.
1141 * @param [in] StartPos First line of the difference.
1142 * @param [in] endPos Last line of the difference.
1143 * @param [in] FileNo File to match.
1144 * return true if any of the expressions matches.
1146 bool CDiffWrapper::RegExpFilter(std::string& lines) const
1148 if (m_pFilterList == nullptr)
1150 throw "CDiffWrapper::RegExpFilter() called when "
1151 "filterlist doesn't exist (=nullptr)";
1154 bool linesMatch = true; // set to false when non-matching line is found.
1156 std::string replaced;
1157 replaced.reserve(lines.length());
1159 while (pos < lines.length())
1161 const char* string = lines.c_str() + pos;
1162 while (pos < lines.length() && (lines[pos] != '\r' && lines[pos] != '\n'))
1164 size_t stringlen = lines.c_str() + pos - string;
1165 std::string line = std::string(string, stringlen);
1166 if (!m_pFilterList->Match(line, m_codepage))
1173 replaced += FILTERED_LINE;
1176 while (pos < lines.length() && (lines[pos] == '\r' || lines[pos] == '\n'))
1177 eol += lines[pos++];
1185 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1188 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript(struct change * script, const file_data * file_data_ary)
1190 //Logic needed for Ignore comment option
1191 PostFilterContext ctxt;
1193 struct change *next = script;
1195 const bool usefilters = m_options.m_filterCommentsLines ||
1196 (m_pFilterList && m_pFilterList->HasRegExps()) ||
1197 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps());
1199 while (next != nullptr)
1201 /* Find a set of changes that belong together. */
1202 struct change *thisob = next;
1203 struct change *end = find_change(next);
1205 /* Disconnect them from the rest of the changes,
1206 making them a hunk, and remember the rest for next iteration. */
1208 end->link = nullptr;
1210 debug_script(thisob);
1213 /* Print thisob hunk. */
1214 //(*printfun) (thisob);
1216 /* Determine range of line numbers involved in each file. */
1217 int first0=0, last0=0, first1=0, last1=0, deletes=0, inserts=0;
1218 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, file_data_ary);
1220 /* Reconnect the script so it will all be freed properly. */
1223 if (deletes || inserts || thisob->trivial)
1225 OP_TYPE op = OP_NONE;
1226 if (deletes && inserts)
1228 else if (deletes || inserts)
1233 /* Print the lines that the first file has. */
1234 int trans_a0=0, trans_b0=0, trans_a1=0, trans_b1=0;
1235 translate_range(&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
1236 translate_range(&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
1238 // Store information about these blocks in moved line info
1239 if (GetDetectMovedBlocks())
1241 if (thisob->match0>=0)
1243 assert(thisob->inserted > 0);
1244 for (int i=0; i<thisob->inserted; ++i)
1246 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1247 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1248 GetMovedLines(1)->Add(MovedLines::SIDE::LEFT, line1, line0);
1251 if (thisob->match1>=0)
1253 assert(thisob->deleted > 0);
1254 for (int i=0; i<thisob->deleted; ++i)
1256 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1257 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1258 GetMovedLines(0)->Add(MovedLines::SIDE::RIGHT, line0, line1);
1262 int nTrivialInserts = 0;
1263 if (op != OP_TRIVIAL && usefilters)
1264 nTrivialInserts = PostFilter(ctxt, thisob, file_data_ary);
1265 if (nTrivialInserts > 0)
1267 while (thisob != next)
1269 op = (thisob->trivial) ? OP_TRIVIAL : OP_DIFF;
1270 first0 = thisob->line0;
1271 first1 = thisob->line1;
1272 last0 = first0 + thisob->deleted - 1;
1273 last1 = first1 + thisob->inserted - 1;
1274 translate_range (&file_data_ary[0], first0, last0, &trans_a0, &trans_b0);
1275 translate_range (&file_data_ary[1], first1, last1, &trans_a1, &trans_b1);
1276 const int qtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1277 const int qtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1279 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces)
1281 if (qtyLinesLeft == qtyLinesRight)
1287 trans_a0 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1288 trans_a1 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1292 AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1294 thisob = thisob->link;
1299 if (thisob->trivial)
1301 const int qtyLinesLeft = (trans_b0 - trans_a0) + 1; //Determine quantity of lines in this block for left side
1302 const int qtyLinesRight = (trans_b1 - trans_a1) + 1;//Determine quantity of lines in this block for right side
1303 if (op == OP_TRIVIAL && m_options.m_bCompletelyBlankOutIgnoredDiffereneces)
1305 if (qtyLinesLeft == qtyLinesRight)
1311 trans_a0 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1312 trans_a1 += qtyLinesLeft < qtyLinesRight ? qtyLinesLeft : qtyLinesRight;
1316 AddDiffRange(m_pDiffList, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1323 struct Comp02Functor
1325 Comp02Functor(const file_data * inf10, const file_data * inf12) :
1326 inf10_(inf10), inf12_(inf12)
1329 bool operator()(const DiffRangeInfo &dr3)
1331 int line0 = dr3.begin[0];
1332 int line2 = dr3.begin[2];
1333 int line0end = dr3.end[0];
1334 int line2end = dr3.end[2];
1335 if (line0end - line0 != line2end - line2)
1337 const char **linbuf0 = inf10_[1].linbuf + inf10_[1].linbuf_base;
1338 const char **linbuf2 = inf12_[1].linbuf + inf12_[1].linbuf_base;
1339 for (int i = 0; i < line0end - line0 + 1; ++i)
1341 const size_t line0len = linbuf0[line0 + i + 1] - linbuf0[line0 + i];
1342 const size_t line2len = linbuf2[line2 + i + 1] - linbuf2[line2 + i];
1343 if (line_cmp(linbuf0[line0 + i], line0len, linbuf2[line2 + i], line2len) != 0)
1348 const file_data *inf10_;
1349 const file_data *inf12_;
1353 * @brief Walk the diff utils change script, building the WinMerge list of diff blocks
1356 CDiffWrapper::LoadWinMergeDiffsFromDiffUtilsScript3(
1357 struct change * script10,
1358 struct change * script12,
1359 const file_data * inf10,
1360 const file_data * inf12)
1362 DiffList diff10, diff12;
1366 const bool usefilters = m_options.m_filterCommentsLines ||
1367 (m_pFilterList && m_pFilterList->HasRegExps()) ||
1368 (m_pSubstitutionList && m_pSubstitutionList->HasRegExps());
1370 for (int file = 0; file < 2; file++)
1372 struct change *next = nullptr;
1373 int trans_a0, trans_b0, trans_a1, trans_b1;
1374 int first0, last0, first1, last1, deletes, inserts;
1376 const file_data *pinf = nullptr;
1377 DiffList *pdiff = nullptr;
1378 PostFilterContext ctxt;
1382 case 0: next = script10; pdiff = &diff10; pinf = inf10; break;
1383 case 1: next = script12; pdiff = &diff12; pinf = inf12; break;
1386 while (next != nullptr)
1388 /* Find a set of changes that belong together. */
1389 struct change *thisob = next;
1390 struct change *end = find_change(next);
1392 /* Disconnect them from the rest of the changes,
1393 making them a hunk, and remember the rest for next iteration. */
1395 end->link = nullptr;
1397 debug_script(thisob);
1400 /* Print thisob hunk. */
1401 //(*printfun) (thisob);
1403 /* Determine range of line numbers involved in each file. */
1404 analyze_hunk (thisob, &first0, &last0, &first1, &last1, &deletes, &inserts, pinf);
1406 /* Reconnect the script so it will all be freed properly. */
1409 if (deletes || inserts || thisob->trivial)
1411 if (deletes && inserts)
1413 else if (deletes || inserts)
1418 /* Print the lines that the first file has. */
1419 translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1420 translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1422 // Store information about these blocks in moved line info
1423 if (GetDetectMovedBlocks())
1425 int index1 = 0; // defaults for (file == 0 /* diff10 */)
1427 MovedLines::SIDE side1 = MovedLines::SIDE::RIGHT;
1428 MovedLines::SIDE side2 = MovedLines::SIDE::LEFT;
1429 if (file == 1 /* diff12 */)
1433 side1 = MovedLines::SIDE::LEFT;
1434 side2 = MovedLines::SIDE::RIGHT;
1436 if (index1 != -1 && index2 != -1)
1438 if (thisob->match0>=0)
1440 assert(thisob->inserted > 0);
1441 for (int i=0; i<thisob->inserted; ++i)
1443 int line0 = i+thisob->match0 + (trans_a0-first0-1);
1444 int line1 = i+thisob->line1 + (trans_a1-first1-1);
1445 GetMovedLines(index1)->Add(side1, line1, line0);
1448 if (thisob->match1>=0)
1450 assert(thisob->deleted > 0);
1451 for (int i=0; i<thisob->deleted; ++i)
1453 int line0 = i+thisob->line0 + (trans_a0-first0-1);
1454 int line1 = i+thisob->match1 + (trans_a1-first1-1);
1455 GetMovedLines(index2)->Add(side2, line0, line1);
1461 int nTrivialInserts = 0;
1462 if (op != OP_TRIVIAL && usefilters)
1463 nTrivialInserts = PostFilter(ctxt, thisob, pinf);
1464 if (nTrivialInserts)
1466 while (thisob != next)
1468 op = (thisob->trivial) ? OP_TRIVIAL : OP_DIFF;
1469 first0 = thisob->line0;
1470 first1 = thisob->line1;
1471 last0 = first0 + thisob->deleted - 1;
1472 last1 = first1 + thisob->inserted - 1;
1473 translate_range (&pinf[0], first0, last0, &trans_a0, &trans_b0);
1474 translate_range (&pinf[1], first1, last1, &trans_a1, &trans_b1);
1476 AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1477 thisob = thisob->link;
1482 if (thisob->trivial)
1484 AddDiffRange(pdiff, trans_a0-1, trans_b0-1, trans_a1-1, trans_b1-1, op);
1491 Make3wayDiff(m_pDiffList->GetDiffRangeInfoVector(), diff10.GetDiffRangeInfoVector(), diff12.GetDiffRangeInfoVector(),
1492 Comp02Functor(inf10, inf12),
1493 (m_pFilterList != nullptr && m_pFilterList->HasRegExps()) || m_options.m_bIgnoreBlankLines || m_options.m_filterCommentsLines);
1496 void CDiffWrapper::WritePatchFileHeader(enum output_style tOutput_style, bool bAppendFiles)
1499 if (!m_sPatchFile.empty())
1501 const tchar_t *mode = (bAppendFiles ? _T("a+") : _T("w+"));
1502 if (cio::tfopen_s(&outfile, m_sPatchFile, mode) != 0)
1506 if (outfile == nullptr)
1508 m_status.bPatchFileFailed = true;
1513 switch (tOutput_style)
1516 case OUTPUT_CONTEXT:
1517 case OUTPUT_UNIFIED:
1520 case OUTPUT_FORWARD_ED:
1527 print_html_header();
1535 void CDiffWrapper::WritePatchFileTerminator(enum output_style tOutput_style)
1538 if (!m_sPatchFile.empty())
1540 if (cio::tfopen_s(&outfile, m_sPatchFile, _T("a+")) != 0)
1544 if (outfile == nullptr)
1546 m_status.bPatchFileFailed = true;
1551 switch (tOutput_style)
1554 case OUTPUT_CONTEXT:
1555 case OUTPUT_UNIFIED:
1558 case OUTPUT_FORWARD_ED:
1565 print_html_terminator();
1574 * @brief Write out a patch file.
1575 * Writes patch file using already computed diffutils script. Converts path
1576 * delimiters from \ to / since we want to keep compatibility with patch-tools.
1577 * @param [in] script list of changes.
1578 * @param [in] inf file_data table containing filenames
1580 void CDiffWrapper::WritePatchFile(struct change * script, file_data * inf)
1582 file_data inf_patch[2] = { inf[0], inf[1] };
1584 // Get paths, primarily use alternative paths, only if they are empty
1585 // use full filepaths
1586 String path1(m_alternativePaths[0]);
1587 String path2(m_alternativePaths[1]);
1592 path1 = paths::ToUnixPath(path1);
1593 path2 = paths::ToUnixPath(path2);
1594 auto strdupPath = [](const String& path, const void *buffer, size_t buffered_chars) -> char*
1596 FileTextEncoding encoding = codepage_detect::Guess(_T(""), buffer, buffered_chars, 1);
1597 if (encoding.m_unicoding != ucr::NONE)
1598 encoding.SetUnicoding(ucr::UTF8);
1599 ucr::buffer buf(256);
1600 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);
1601 return strdup(reinterpret_cast<const char *>(buf.ptr));
1603 inf_patch[0].name = strdupPath(path1, inf_patch[0].buffer, inf_patch[0].buffered_chars);
1604 inf_patch[1].name = strdupPath(path2, inf_patch[1].buffer, inf_patch[1].buffered_chars);
1606 // If paths in m_s1File and m_s2File point to original files, then we can use
1607 // them to fix potentially meaningless stats from potentially temporary files,
1608 // resulting from whatever transforms may have taken place.
1609 // If not, then we can't help it, and hence assert that this won't happen.
1610 if (!m_bPathsAreTemp)
1612 mywstat(m_files[0].c_str(), &inf_patch[0].stat);
1613 mywstat(m_files[1].c_str(), &inf_patch[1].stat);
1621 if (!m_sPatchFile.empty())
1623 const tchar_t *mode = (m_bAppendFiles ? _T("a+") : _T("w+"));
1624 if (cio::tfopen_s(&outfile, m_sPatchFile, mode) != 0)
1628 if (outfile == nullptr)
1630 m_status.bPatchFileFailed = true;
1634 if (strcmp(inf[0].name, "NUL") == 0)
1636 free((void *)inf_patch[0].name);
1637 inf_patch[0].name = strdup("/dev/null");
1639 if (strcmp(inf[1].name, "NUL") == 0)
1641 free((void *)inf_patch[1].name);
1642 inf_patch[1].name = strdup("/dev/null");
1645 // Print "command line"
1646 if (m_bAddCmdLine && output_style != OUTPUT_HTML)
1648 String switches = FormatSwitchString();
1649 fprintf(outfile, "diff%S %s %s\n",
1650 switches.c_str(), inf_patch[0].name, inf_patch[1].name);
1654 switch (output_style)
1657 print_normal_script(script);
1659 case OUTPUT_CONTEXT:
1660 print_context_header(inf_patch, 0);
1661 print_context_script(script, 0);
1663 case OUTPUT_UNIFIED:
1664 print_context_header(inf_patch, 1);
1665 print_context_script(script, 1);
1669 print_ed_script(script);
1671 case OUTPUT_FORWARD_ED:
1672 pr_forward_ed_script(script);
1675 print_rcs_script(script);
1678 print_ifdef_script(script);
1681 print_sdiff_script(script);
1685 print_html_diff_header(inf_patch);
1686 print_html_script(script);
1687 print_html_diff_terminator();
1693 free((void *)inf_patch[0].name);
1694 free((void *)inf_patch[1].name);
1698 * @brief Set line filters, given as one string.
1699 * @param [in] filterStr Filters.
1701 void CDiffWrapper::SetFilterList(const String& filterStr)
1703 // Remove filterlist if new filter is empty
1704 if (filterStr.empty())
1706 m_pFilterList.reset();
1710 // Adding new filter without previous filter
1711 if (m_pFilterList == nullptr)
1713 m_pFilterList.reset(new FilterList);
1716 m_pFilterList->RemoveAllFilters();
1718 std::string regexp_str = ucr::toUTF8(filterStr);
1720 // Add every "line" of regexps to regexp list
1721 StringTokenizer tokens(regexp_str, "\r\n");
1722 for (StringTokenizer::Iterator it = tokens.begin(); it != tokens.end(); ++it)
1723 m_pFilterList->AddRegExp(*it);
1726 const FilterList* CDiffWrapper::GetFilterList() const
1728 return m_pFilterList.get();
1731 void CDiffWrapper::SetFilterList(std::shared_ptr<FilterList> pFilterList)
1733 m_pFilterList = std::move(pFilterList);
1736 const SubstitutionList* CDiffWrapper::GetSubstitutionList() const
1738 return m_pSubstitutionList.get();
1741 void CDiffWrapper::SetSubstitutionList(std::shared_ptr<SubstitutionList> pSubstitutionList)
1743 m_pSubstitutionList = std::move(pSubstitutionList);
1746 void CDiffWrapper::SetFilterCommentsSourceDef(const String& ext)
1748 m_pFilterCommentsDef = CrystalLineParser::GetTextType(ext.c_str());
1752 * @brief Copy text stat results from diffutils back into the FileTextStats structure
1754 void CopyTextStats(const file_data * inf, FileTextStats * myTextStats)
1756 myTextStats->ncrlfs = inf->count_crlfs;
1757 myTextStats->ncrs = inf->count_crs;
1758 myTextStats->nlfs = inf->count_lfs;
1759 myTextStats->nzeros = inf->count_zeros;
1763 * @brief Copy both left & right text stats results back into the DiffFileData text stats
1765 void CopyDiffutilTextStats(file_data *inf, DiffFileData * diffData)
1767 CopyTextStats(&inf[0], &diffData->m_textStats[0]);
1768 CopyTextStats(&inf[1], &diffData->m_textStats[1]);