OSDN Git Service

theApp is unnecessary because these methods are a static methods
[winmerge-jp/winmerge-jp.git] / Src / MergeDoc.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /** 
8  * @file  MergeDoc.cpp
9  *
10  * @brief Implementation file for CMergeDoc
11  *
12  */
13
14 #include "StdAfx.h"
15 #include "MergeDoc.h"
16 #include <io.h>
17 #include <Poco/Timestamp.h>
18 #include "UnicodeString.h"
19 #include "Merge.h"
20 #include "MainFrm.h"
21 #include "DiffTextBuffer.h"
22 #include "Environment.h"
23 #include "MovedLines.h"
24 #include "MergeEditView.h"
25 #include "MergeEditFrm.h"
26 #include "DirDoc.h"
27 #include "files.h"
28 #include "FileTransform.h"
29 #include "unicoder.h"
30 #include "UniFile.h"
31 #include "OptionsDef.h"
32 #include "DiffFileInfo.h"
33 #include "SaveClosingDlg.h"
34 #include "OpenTableDlg.h"
35 #include "DiffList.h"
36 #include "paths.h"
37 #include "OptionsMgr.h"
38 #include "OptionsDiffOptions.h"
39 #include "MergeLineFlags.h"
40 #include "FileOrFolderSelect.h"
41 #include "LineFiltersList.h"
42 #include "SubstitutionFiltersList.h"
43 #include "TempFile.h"
44 #include "codepage_detect.h"
45 #include "SelectUnpackerDlg.h"
46 #include "EncodingErrorBar.h"
47 #include "MergeCmdLineInfo.h"
48 #include "TFile.h"
49 #include "Constants.h"
50 #include "Merge7zFormatMergePluginImpl.h"
51 #include "7zCommon.h"
52 #include "PatchTool.h"
53 #include "charsets.h"
54 #include "markdown.h"
55 #include "stringdiffs.h"
56
57 #ifdef _DEBUG
58 #define new DEBUG_NEW
59 #endif
60
61 using std::swap;
62
63 int CMergeDoc::m_nBuffersTemp = 2;
64
65 static void SaveBuffForDiff(CDiffTextBuffer & buf, const String& filepath, int nStartLine = 0, int nLines = -1);
66
67 /////////////////////////////////////////////////////////////////////////////
68 // CMergeDoc
69
70 IMPLEMENT_DYNCREATE(CMergeDoc, CDocument)
71
72 BEGIN_MESSAGE_MAP(CMergeDoc, CDocument)
73         //{{AFX_MSG_MAP(CMergeDoc)
74         ON_COMMAND(ID_FILE_SAVE, OnFileSave)
75         ON_COMMAND(ID_FILE_SAVE_LEFT, OnFileSaveLeft)
76         ON_COMMAND(ID_FILE_SAVE_MIDDLE, OnFileSaveMiddle)
77         ON_COMMAND(ID_FILE_SAVE_RIGHT, OnFileSaveRight)
78         ON_COMMAND(ID_FILE_SAVEAS_LEFT, OnFileSaveAsLeft)
79         ON_UPDATE_COMMAND_UI(ID_FILE_SAVEAS_MIDDLE, OnUpdateFileSaveAsMiddle)
80         ON_COMMAND(ID_FILE_SAVEAS_MIDDLE, OnFileSaveAsMiddle)
81         ON_COMMAND(ID_FILE_SAVEAS_RIGHT, OnFileSaveAsRight)
82         ON_UPDATE_COMMAND_UI(ID_STATUS_DIFFNUM, OnUpdateStatusNum)
83         ON_UPDATE_COMMAND_UI(ID_STATUS_PLUGIN, OnUpdatePluginName)
84         ON_COMMAND(ID_TOOLS_GENERATEREPORT, OnToolsGenerateReport)
85         ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
86         ON_COMMAND(ID_RESCAN, OnFileReload)
87         ON_COMMAND(ID_FILE_ENCODING, OnFileEncoding)
88         ON_COMMAND_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_INVERT, OnDiffContext)
89         ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_INVERT, OnUpdateDiffContext)
90         ON_COMMAND(ID_POPUP_OPEN_WITH_UNPACKER, OnCtxtOpenWithUnpacker)
91         ON_BN_CLICKED(IDC_FILEENCODING, OnBnClickedFileEncoding)
92         ON_BN_CLICKED(IDC_PLUGIN, OnBnClickedPlugin)
93         ON_BN_CLICKED(IDC_HEXVIEW, OnBnClickedHexView)
94         ON_COMMAND(IDOK, OnOK)
95         ON_COMMAND(ID_MERGE_COMPARE_TEXT, OnFileRecompareAsText)
96         ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_TEXT, OnUpdateFileRecompareAsText)
97         ON_COMMAND(ID_MERGE_COMPARE_TABLE, OnFileRecompareAsTable)
98         ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_TABLE, OnUpdateFileRecompareAsTable)
99         ON_COMMAND(ID_MERGE_COMPARE_XML, OnFileRecompareAsXML)
100         ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_XML, OnUpdateFileRecompareAsXML)
101         ON_COMMAND_RANGE(ID_MERGE_COMPARE_HEX, ID_MERGE_COMPARE_IMAGE, OnFileRecompareAs)
102         ON_UPDATE_COMMAND_UI_RANGE(ID_SWAPPANES_SWAP23, ID_SWAPPANES_SWAP13, OnUpdateSwapContext)
103         //}}AFX_MSG_MAP
104 END_MESSAGE_MAP()
105
106 /////////////////////////////////////////////////////////////////////////////
107 // CMergeDoc construction/destruction
108
109 /**
110  * @brief Constructor.
111  */
112 CMergeDoc::CMergeDoc()
113 : m_bEnableRescan(true)
114 , m_nCurDiff(-1)
115 , m_CurWordDiff{ -1, static_cast<size_t>(-1), -1 }
116 , m_pDirDoc(nullptr)
117 , m_bMixedEol(false)
118 , m_pInfoUnpacker(new PackingInfo)
119 , m_pEncodingErrorBar(nullptr)
120 , m_bHasSyncPoints(false)
121 , m_bAutoMerged(false)
122 , m_nGroups(0)
123 , m_pView{nullptr}
124 , m_bAutomaticRescan(false)
125 {
126         DIFFOPTIONS options = {0};
127
128         m_nBuffers = m_nBuffersTemp;
129         m_filePaths.SetSize(m_nBuffers);
130
131         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
132         {
133                 m_ptBuf[nBuffer].reset(new CDiffTextBuffer(this, nBuffer));
134                 m_pSaveFileInfo[nBuffer].reset(new DiffFileInfo());
135                 m_pRescanFileInfo[nBuffer].reset(new DiffFileInfo());
136                 m_nBufferType[nBuffer] = BUFFERTYPE::NORMAL;
137                 m_bEditAfterRescan[nBuffer] = false;
138         }
139
140         m_bEnableRescan = true;
141         m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
142
143         // COleDateTime m_LastRescan
144         curUndo = undoTgt.begin();
145         m_nDiffContext = GetOptionsMgr()->GetInt(OPT_DIFF_CONTEXT);
146         m_bInvertDiffContext = GetOptionsMgr()->GetBool(OPT_INVERT_DIFF_CONTEXT);
147
148         m_diffWrapper.SetDetectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
149         Options::DiffOptions::Load(GetOptionsMgr(), options);
150
151         m_diffWrapper.SetOptions(&options);
152         m_diffWrapper.SetPrediffer(nullptr);
153 }
154
155 /**
156  * @brief Destructor.
157  *
158  * Informs associated dirdoc that mergedoc is closing.
159  */
160 CMergeDoc::~CMergeDoc()
161 {       
162         if (m_pDirDoc != nullptr)
163         {
164                 m_pDirDoc->MergeDocClosing(this);
165                 m_pDirDoc = nullptr;
166         }
167 }
168
169 /**
170  * @brief Deleted data associated with doc before closing.
171  */
172 void CMergeDoc::DeleteContents ()
173 {
174         CDocument::DeleteContents ();
175         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
176         {
177                 m_ptBuf[nBuffer]->FreeAll ();
178                 m_tempFiles[nBuffer].Delete();
179         }
180 }
181
182 /**
183  * @brief Called when new document is created.
184  *
185  * Initialises buffers.
186  */
187 BOOL CMergeDoc::OnNewDocument()
188 {
189         if (!CDocument::OnNewDocument())
190                 return false;
191
192         SetTitle(_("File Comparison").c_str());
193         
194         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
195                 m_ptBuf[nBuffer]->InitNew ();
196         return true;
197 }
198
199 /**
200  * @brief Return active merge edit view (or left one if neither active)
201  */
202 CMergeEditView * CMergeDoc::GetActiveMergeView()
203 {
204         CView * pActiveView = GetParentFrame()->GetActiveView();
205         CMergeEditView * pMergeEditView = dynamic_cast<CMergeEditView *>(pActiveView);
206         if (pMergeEditView == nullptr)
207                 pMergeEditView = GetView(0, 0); // default to left view (in case some location or detail view active)
208         return pMergeEditView;
209 }
210
211 CMergeEditView * CMergeDoc::GetActiveMergeGroupView(int nBuffer)
212 {
213         return m_pView[GetActiveMergeView()->m_nThisGroup][nBuffer];
214 }
215
216 void CMergeDoc::SetUnpacker(const PackingInfo * infoNewHandler)
217 {
218         if (infoNewHandler != nullptr)
219         {
220                 *m_pInfoUnpacker = *infoNewHandler;
221         }
222 }
223
224 void CMergeDoc::SetPrediffer(const PrediffingInfo * infoPrediffer)
225 {
226         m_diffWrapper.SetPrediffer(infoPrediffer);
227 }
228 void CMergeDoc::GetPrediffer(PrediffingInfo * infoPrediffer)
229 {
230         m_diffWrapper.GetPrediffer(infoPrediffer);
231 }
232
233 /////////////////////////////////////////////////////////////////////////////
234 // CMergeDoc serialization
235
236 void CMergeDoc::Serialize(CArchive& ar)
237 {
238         ASSERT(false); // we do not use CDocument serialization
239 }
240
241 /////////////////////////////////////////////////////////////////////////////
242 // CMergeDoc commands
243
244 /**
245  * @brief Save an editor text buffer to a file for prediffing (make UCS-2LE if appropriate)
246  *
247  * @note 
248  * original file is Ansi : 
249  *   buffer  -> save as Ansi -> Ansi plugins -> diffutils
250  * original file is Unicode (UCS2-LE, UCS2-BE, UTF-8) :
251  *   buffer  -> save as UTF-8 -> Unicode plugins -> convert to UTF-8 -> diffutils
252  * (the plugins are optional, not the conversion)
253  * @todo Show SaveToFile() errors?
254  */
255 static void SaveBuffForDiff(CDiffTextBuffer & buf, const String& filepath, int nStartLine, int nLines)
256 {
257         // and we don't repack the file
258         PackingInfo * tempPacker = nullptr;
259
260         // write buffer out to temporary file
261         String sError;
262         int retVal = buf.SaveToFile(filepath, true, sError, tempPacker,
263                 CRLFSTYLE::AUTOMATIC, false, nStartLine, nLines);
264 }
265
266 /**
267  * @brief Save files to temp files & compare again.
268  *
269  * @param bBinary [in,out] [in] If true, compare two binary files
270  * [out] If true binary file was detected.
271  * @param bIdentical [out] If true files were identical
272  * @param bForced [in] If true, suppressing is ignored and rescan
273  * is done always
274  * @return Tells if rescan was successfully, was suppressed, or
275  * error happened
276  * If this code is OK, Rescan has detached the views temporarily
277  * (positions of cursors have been lost)
278  * @note Rescan() ALWAYS compares temp files. Actual user files are not
279  * touched by Rescan().
280  * @sa CDiffWrapper::RunFileDiff()
281  */
282 int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
283                 bool bForced /* =false */)
284 {
285         DIFFOPTIONS diffOptions = {0};
286         DiffFileInfo fileInfo;
287         bool diffSuccess = false;
288         int nResult = RESCAN_OK;
289         FileChange Changed[3] = {FileChange::NoChange, FileChange::NoChange, FileChange::NoChange};
290         int nBuffer;
291
292         if (!bForced)
293         {
294                 if (!m_bEnableRescan)
295                         return RESCAN_SUPPRESSED;
296         }
297
298         ClearWordDiffCache();
299
300         if (GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED))
301         {
302                 m_diffWrapper.SetFilterList(theApp.m_pLineFilters->GetAsString());
303         }
304         else
305         {
306                 m_diffWrapper.SetFilterList(_T(""));
307         }
308
309         if (theApp.m_pSubstitutionFiltersList && theApp.m_pSubstitutionFiltersList->GetEnabled())
310         {
311                 m_diffWrapper.SetSubstitutionList(theApp.m_pSubstitutionFiltersList->MakeSubstitutionList());
312         }
313         else
314         {
315                 m_diffWrapper.SetSubstitutionList(nullptr);
316         }
317
318         if (GetView(0, 0)->m_CurSourceDef->type != 0)
319                 m_diffWrapper.SetFilterCommentsSourceDef(GetView(0, 0)->m_CurSourceDef);
320         else
321                 m_diffWrapper.SetFilterCommentsSourceDef(GetFileExt(m_filePaths[0].c_str(), m_strDesc[0].c_str()));
322
323         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
324         {
325                 // Check if files have been modified since last rescan
326                 // Ignore checking in case of scratchpads (empty filenames)
327                 if (!m_filePaths[nBuffer].empty())
328                 {
329                         Changed[nBuffer] = IsFileChangedOnDisk(m_filePaths[nBuffer].c_str(),
330                                         fileInfo, false, nBuffer);
331                 }
332         }
333         m_LastRescan = COleDateTime::GetCurrentTime();
334
335         LPCTSTR tnames[] = {_T("t0_wmdoc"), _T("t1_wmdoc"), _T("t2_wmdoc")};
336         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
337         {
338                 if (Changed[nBuffer] == FileChange::Removed)
339                 {
340                         String msg = strutils::format_string1(_("The file\n%1\nhas disappeared. Please save a copy of the file to continue."), m_filePaths[nBuffer]);
341                         ShowMessageBox(msg, MB_ICONWARNING);
342                         bool bSaveResult = false;
343                         bool ok = DoSaveAs(m_filePaths[nBuffer].c_str(), bSaveResult, nBuffer);
344                         if (!ok || !bSaveResult)
345                         {
346                                 return RESCAN_FILE_ERR;
347                         }
348                 }
349
350                 String temp = m_tempFiles[nBuffer].GetPath();
351                 if (temp.empty())
352                         temp = m_tempFiles[nBuffer].Create(tnames[nBuffer]);
353                 if (temp.empty())
354                         return RESCAN_TEMP_ERR;
355         }
356
357         CheckFileChanged();
358
359         String tempPath = env::GetTemporaryPath();
360
361         // Set up DiffWrapper
362         m_diffWrapper.GetOptions(&diffOptions);
363
364         // Clear diff list
365         m_diffList.Clear();
366         m_nCurDiff = -1;
367         m_CurWordDiff = { -1, static_cast<size_t>(-1), -1 };
368         // Clear moved lines lists
369         if (m_diffWrapper.GetDetectMovedBlocks())
370         {
371                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
372                         m_diffWrapper.GetMovedLines(nBuffer)->Clear();
373         }
374
375         // Set paths for diffing and run diff
376         m_diffWrapper.EnablePlugins(GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED));
377         if (m_nBuffers < 3)
378                 m_diffWrapper.SetPaths(PathContext(m_tempFiles[0].GetPath(), m_tempFiles[1].GetPath()), true);
379         else
380                 m_diffWrapper.SetPaths(PathContext(m_tempFiles[0].GetPath(), m_tempFiles[1].GetPath(), m_tempFiles[2].GetPath()), true);
381         m_diffWrapper.SetCompareFiles(m_filePaths);
382
383         DIFFSTATUS status;
384
385         if (!HasSyncPoints())
386         {
387                 // Save text buffer to file
388                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
389                 {
390                         m_ptBuf[nBuffer]->SetTempPath(tempPath);
391                         SaveBuffForDiff(*m_ptBuf[nBuffer], m_tempFiles[nBuffer].GetPath());
392                 }
393
394                 m_diffWrapper.SetCreateDiffList(&m_diffList);
395                 diffSuccess = m_diffWrapper.RunFileDiff();
396
397                 // Read diff-status
398                 m_diffWrapper.GetDiffStatus(&status);
399                 if (bBinary) // believe caller if we were told these are binaries
400                         status.bBinaries = true;
401         }
402         else
403         {
404                 const std::vector<std::vector<int> > syncpoints = GetSyncPointList();   
405                 int nStartLine[3] = {0};
406                 int nLines[3], nRealLine[3];
407                 for (size_t i = 0; i <= syncpoints.size(); ++i)
408                 {
409                         // Save text buffer to file
410                         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
411                         {
412                                 nLines[nBuffer] = (i >= syncpoints.size()) ? -1 : syncpoints[i][nBuffer] - nStartLine[nBuffer];
413                                 m_ptBuf[nBuffer]->SetTempPath(tempPath);
414                                 SaveBuffForDiff(*m_ptBuf[nBuffer], m_tempFiles[nBuffer].GetPath(), 
415                                         nStartLine[nBuffer], nLines[nBuffer]);
416                         }
417                         DiffList templist;
418                         templist.Clear();
419                         m_diffWrapper.SetCreateDiffList(&templist);
420                         diffSuccess = m_diffWrapper.RunFileDiff();
421                         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
422                                 nRealLine[nBuffer] = m_ptBuf[nBuffer]->ComputeRealLine(nStartLine[nBuffer]);
423
424                         // Correct the comparison results made by diffutils if the first file separated by the sync point is an empty file.
425                         if (i == 0 && templist.GetSize() > 0)
426                                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
427                                         if (nStartLine[nBuffer] == 0)
428                                         {
429                                                 bool isEmptyFile = true;
430                                                 for (int j = 0; j < nLines[nBuffer]; j++)
431                                                 {
432                                                         if (!(m_ptBuf[nBuffer]->GetLineFlags(nStartLine[nBuffer] + j) & LF_GHOST))
433                                                         {
434                                                                 isEmptyFile = false;
435                                                                 break;
436                                                         }
437                                                 }
438                                                 if (isEmptyFile)
439                                                 {
440                                                         DIFFRANGE di;
441                                                         templist.GetDiff(0, di);
442                                                         if (di.begin[nBuffer] == 0 && di.end[nBuffer] == 0)
443                                                         {
444                                                                 di.end[nBuffer] = -1;
445                                                                 templist.SetDiff(0, di);
446                                                         }
447                                                 }
448                                         }
449
450                         m_diffList.AppendDiffList(templist, nRealLine);
451                         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
452                                 nStartLine[nBuffer] += nLines[nBuffer];
453
454                         // Read diff-status
455                         DIFFSTATUS status_part;
456                         m_diffWrapper.GetDiffStatus(&status_part);
457                         if (bBinary) // believe caller if we were told these are binaries
458                                 status.bBinaries = true;
459                         status.MergeStatus(status_part);
460                 }
461                 m_diffWrapper.SetCreateDiffList(&m_diffList);
462         }
463
464         // If one file has EOL before EOF and other not...
465         if (std::count(status.bMissingNL, status.bMissingNL + m_nBuffers, status.bMissingNL[0]) < m_nBuffers)
466         {
467                 // ..last DIFFRANGE of file which has EOL must be
468                 // fixed to contain last line too
469                 int lineCount[3] = { 0,0,0 };
470                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
471                         lineCount[nBuffer] = m_ptBuf[nBuffer]->GetLineCount();
472                 m_diffWrapper.FixLastDiffRange(m_nBuffers, lineCount, status.bMissingNL, diffOptions.bIgnoreBlankLines);
473         }
474
475         // set identical/diff result as recorded by diffutils
476         identical = status.Identical;
477
478         // Determine errors and binary file compares
479         if (!diffSuccess)
480                 nResult = RESCAN_FILE_ERR;
481         else if (status.bBinaries)
482         {
483                 bBinary = true;
484         }
485         else
486         {
487                 // Now update views and buffers for ghost lines
488
489                 // Prevent displaying views during this update 
490                 // BTW, this solves the problem of double asserts
491                 // (during the display of an assert message box, a second assert in one of the 
492                 //  display functions happens, and hides the first assert)
493                 ForEachView([](auto& pView) { pView->DetachFromBuffer(); });
494
495                 // Remove blank lines and clear winmerge flags
496                 // this operation does not change the modified flag
497                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
498                         m_ptBuf[nBuffer]->prepareForRescan();
499
500                 // Divide diff blocks to match lines.
501                 if (GetOptionsMgr()->GetBool(OPT_CMP_MATCH_SIMILAR_LINES) && m_nBuffers < 3)
502                         AdjustDiffBlocks();
503
504                 // Analyse diff-list (updating real line-numbers)
505                 // this operation does not change the modified flag
506                 PrimeTextBuffers();
507
508                 // Hide identical lines if diff-context is not 'All'
509                 HideLines();
510
511                 // Apply flags to lines that are trivial
512                 PrediffingInfo infoPrediffer;
513                 GetPrediffer(&infoPrediffer);
514                 if (!infoPrediffer.m_PluginName.empty())
515                         FlagTrivialLines();
516                 
517                 // Apply flags to lines that moved, to differentiate from appeared/disappeared lines
518                 if (m_diffWrapper.GetDetectMovedBlocks())
519                         FlagMovedLines();
520                 
521                 // After PrimeTextBuffers() we know amount of real diffs
522                 // (m_nDiffs) and trivial diffs (m_nTrivialDiffs)
523
524                 // Identical files are also updated
525                 if (!m_diffList.HasSignificantDiffs())
526                         identical = IDENTLEVEL::ALL;
527
528                 ForEachView([](auto& pView) {
529                         // just apply some options to the views
530                         pView->PrimeListWithFile();
531                         // Now buffers data are valid
532                         pView->ReAttachToBuffer();
533                 });
534                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
535                 {
536                         m_bEditAfterRescan[nBuffer] = false;
537                 }
538         }
539
540         if (!GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE) &&
541                 identical == IDENTLEVEL::ALL &&
542                 std::any_of(m_ptBuf, m_ptBuf + m_nBuffers,
543                         [&](std::unique_ptr<CDiffTextBuffer>& buf) { return buf->getEncoding() != m_ptBuf[0]->getEncoding(); }))
544                 identical = IDENTLEVEL::NONE;
545
546         GetParentFrame()->SetLastCompareResult(identical != IDENTLEVEL::ALL ? 1 : 0);
547
548         return nResult;
549 }
550
551 void CMergeDoc::CheckFileChanged(void)
552 {
553         int nBuffer;
554         DiffFileInfo fileInfo;
555         FileChange FileChange[3];
556
557         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
558         {
559                 FileChange[nBuffer] = IsFileChangedOnDisk(m_filePaths[nBuffer].c_str(), fileInfo,
560                         false, nBuffer);
561
562                 m_pRescanFileInfo[nBuffer]->Update((LPCTSTR)m_filePaths[nBuffer].c_str());
563         }
564
565         bool bDoReload = false;
566         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
567         {
568                 if (FileChange[nBuffer] == FileChange::Changed)
569                 {
570                         String msg = strutils::format_string1(_("Another application has updated file\n%1\nsince WinMerge scanned it last time.\n\nDo you want to reload the file?"), m_filePaths[nBuffer]);
571                         if (ShowMessageBox(msg, MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_FILECHANGED_RESCAN) == IDYES)
572                                 bDoReload = true;
573                         break;
574                 }
575         }
576         if (bDoReload)
577         {
578                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
579                 {
580                         if (FileChange[nBuffer] == FileChange::Changed)
581                         {
582                                 CPoint pt = GetView(0, nBuffer)->GetCursorPos();
583                                 ChangeFile(nBuffer, m_filePaths[nBuffer], pt.y);
584                         }
585                 }
586         }
587 }
588
589 /** @brief Apply flags to lines that are trivial */
590 void CMergeDoc::FlagTrivialLines(void)
591 {
592         for (int i = 0; i < m_ptBuf[0]->GetLineCount(); ++i)
593         {
594                 if ((m_ptBuf[0]->GetLineFlags(i) & LF_NONTRIVIAL_DIFF) == 0)
595                 {
596                         String str[3];
597                         for (int file = 0; file < m_nBuffers; ++file)
598                         {
599                                 const TCHAR *p = m_ptBuf[file]->GetLineChars(i);
600                                 str[file] = p ? p : _T("");
601                         }
602
603                         if (std::count(str + 1, str + m_nBuffers, str[0]) != m_nBuffers - 1)
604                         {
605                                 DIFFOPTIONS diffOptions = {0};
606                                 m_diffWrapper.GetOptions(&diffOptions);
607
608                                 // Make the call to stringdiffs, which does all the hard & tedious computations
609                                 std::vector<strdiff::wdiff> worddiffs = strdiff::ComputeWordDiffs(m_nBuffers, str,
610                                         !diffOptions.bIgnoreCase,
611                                         !diffOptions.bIgnoreEol,
612                                         diffOptions.nIgnoreWhitespace,
613                                         GetBreakType(), // whitespace only or include punctuation
614                                         GetByteColoringOption());
615                                 if (!worddiffs.empty())
616                                 {
617                                         for (int file = 0; file < m_nBuffers; ++file)
618                                                 m_ptBuf[file]->SetLineFlag(i, LF_TRIVIAL, true, false, false);
619                                 }
620                         }
621                 }
622         }
623 }
624
625 /** @brief Adjust all different lines that were detected as actually matching moved lines */
626 void CMergeDoc::FlagMovedLines(void)
627 {
628         int i;
629         MovedLines *pMovedLines;
630
631         pMovedLines = m_diffWrapper.GetMovedLines(0);
632         for (i = 0; i < m_ptBuf[0]->GetLineCount(); ++i)
633         {
634                 int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::RIGHT);
635                 if (j != -1)
636                 {
637                         TRACE(_T("%d->%d\n"), i, j);
638                         ASSERT(j>=0);
639                         // We only flag lines that are already marked as being different
640                         int apparent = m_ptBuf[0]->ComputeApparentLine(i);
641                         if (m_ptBuf[0]->FlagIsSet(apparent, LF_DIFF))
642                         {
643                                 m_ptBuf[0]->SetLineFlag(apparent, LF_MOVED, true, false, false);
644                                 if (m_ptBuf[1]->FlagIsSet(apparent, LF_GHOST))
645                                 {
646                                         int apparentJ = m_ptBuf[1]->ComputeApparentLine(j);
647                                         if (m_ptBuf[0]->FlagIsSet(apparentJ, LF_GHOST))
648                                                 m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
649                                 }
650                         }
651                 }
652         }
653
654         pMovedLines = m_diffWrapper.GetMovedLines(1);
655         for (i=0; i<m_ptBuf[1]->GetLineCount(); ++i)
656         {
657                 int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::LEFT);
658                 if (j != -1)
659                 {
660                         TRACE(_T("%d->%d\n"), i, j);
661                         ASSERT(j>=0);
662                         // We only flag lines that are already marked as being different
663                         int apparent = m_ptBuf[1]->ComputeApparentLine(i);
664                         if (m_ptBuf[1]->FlagIsSet(apparent, LF_DIFF))
665                         {
666                                 m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
667                                 if (m_ptBuf[0]->FlagIsSet(apparent, LF_GHOST))
668                                 {
669                                         int apparentJ = m_ptBuf[0]->ComputeApparentLine(j);
670                                         if (m_ptBuf[1]->FlagIsSet(apparentJ, LF_GHOST))
671                                                 m_ptBuf[0]->SetLineFlag(apparent, LF_MOVED, true, false, false);
672                                 }
673                         }
674                 }
675         }
676
677         if (m_nBuffers < 3)
678                 return;
679
680         pMovedLines = m_diffWrapper.GetMovedLines(1);
681         for (i=0; i<m_ptBuf[1]->GetLineCount(); ++i)
682         {
683                 int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::RIGHT);
684                 if (j != -1)
685                 {
686                         TRACE(_T("%d->%d\n"), i, j);
687                         ASSERT(j>=0);
688                         // We only flag lines that are already marked as being different
689                         int apparent = m_ptBuf[1]->ComputeApparentLine(i);
690                         if (m_ptBuf[1]->FlagIsSet(apparent, LF_DIFF))
691                         {
692                                 m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
693                                 if (m_ptBuf[2]->FlagIsSet(apparent, LF_GHOST))
694                                 {
695                                         int apparentJ = m_ptBuf[2]->ComputeApparentLine(j);
696                                         if (m_ptBuf[1]->FlagIsSet(apparentJ, LF_GHOST))
697                                                 m_ptBuf[2]->SetLineFlag(apparent, LF_MOVED, true, false, false);
698                                 }
699                         }
700                 }
701         }
702
703         pMovedLines = m_diffWrapper.GetMovedLines(2);
704         for (i=0; i<m_ptBuf[2]->GetLineCount(); ++i)
705         {
706                 int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::LEFT);
707                 if (j != -1)
708                 {
709                         TRACE(_T("%d->%d\n"), i, j);
710                         ASSERT(j>=0);
711                         // We only flag lines that are already marked as being different
712                         int apparent = m_ptBuf[2]->ComputeApparentLine(i);
713                         if (m_ptBuf[2]->FlagIsSet(apparent, LF_DIFF))
714                         {
715                                 m_ptBuf[2]->SetLineFlag(apparent, LF_MOVED, true, false, false);
716                                 if (m_ptBuf[1]->FlagIsSet(apparent, LF_GHOST))
717                                 {
718                                         int apparentJ = m_ptBuf[1]->ComputeApparentLine(j);
719                                         if (m_ptBuf[2]->FlagIsSet(apparentJ, LF_GHOST))
720                                                 m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
721                                 }
722                         }
723                 }
724         }
725
726         // todo: Need to record actual moved information
727 }
728
729 int CMergeDoc::ShowMessageBox(const String& sText, unsigned nType, unsigned nIDHelp)
730 {
731         if (m_pView[0][0] && m_pView[0][0]->IsTextBufferInitialized() && !GetParentFrame()->IsActivated())
732         {
733                 GetParentFrame()->InitialUpdateFrame(this, true);
734                 GetParentFrame()->SendMessageToDescendants(WM_IDLEUPDATECMDUI, static_cast<WPARAM>(true), 0, true, true);
735         }
736         return AfxMessageBox(sText.c_str(), nType, nIDHelp);
737 }
738
739 /**
740  * @brief Prints (error) message by rescan status.
741  *
742  * @param nRescanResult [in] Resultcocode from rescan().
743  * @param bIdentical [in] Were files identical?.
744  * @sa CMergeDoc::Rescan()
745  */
746 void CMergeDoc::ShowRescanError(int nRescanResult, IDENTLEVEL identical)
747 {
748         // Rescan was suppressed, there is no sensible status
749         if (nRescanResult == RESCAN_SUPPRESSED)
750                 return;
751
752         String s;
753
754         if (nRescanResult == RESCAN_FILE_ERR)
755         {
756                 s = _("An error occurred while comparing the files.");
757                 LogErrorString(s);
758                 ShowMessageBox(s, MB_ICONSTOP);
759                 return;
760         }
761
762         if (nRescanResult == RESCAN_TEMP_ERR)
763         {
764                 s = _("Temporary files could not be created. Check your temporary path settings.");
765                 LogErrorString(s);
766                 ShowMessageBox(s, MB_ICONSTOP);
767                 return;
768         }
769
770         // Files are not binaries, but they are identical
771         if (identical != IDENTLEVEL::NONE)
772         {
773                 CMergeFrameCommon::ShowIdenticalMessage(m_filePaths, identical == IDENTLEVEL::ALL,
774                         [this](LPCTSTR msg, UINT flags, UINT id) -> int { return ShowMessageBox(msg, flags, id); });
775         }
776 }
777
778 bool CMergeDoc::Undo()
779 {
780         return false;
781 }
782
783 /**
784  * @brief An instance of RescanSuppress prevents rescan during its lifetime
785  * (or until its Clear method is called, which ends its effect).
786  */
787 class RescanSuppress
788 {
789 public:
790         explicit RescanSuppress(CMergeDoc & doc) : m_doc(doc)
791         {
792                 m_bSuppress = true;
793                 m_bPrev = doc.m_bEnableRescan;
794                 m_doc.m_bEnableRescan = false;
795         }
796         void Clear() 
797         {
798                 if (m_bSuppress)
799                 {
800                         m_bSuppress = false;
801                         m_doc.m_bEnableRescan = m_bPrev;
802                 }
803         }
804         ~RescanSuppress()
805         {
806                 Clear();
807         }
808 private:
809         CMergeDoc & m_doc;
810         bool m_bPrev;
811         bool m_bSuppress;
812 };
813
814 /**
815  * @brief Copy all diffs from one side to side.
816  * @param [in] srcPane Source side from which diff is copied
817  * @param [in] dstPane Destination side
818  */
819 void CMergeDoc::CopyAllList(int srcPane, int dstPane)
820 {
821         CopyMultipleList(srcPane, dstPane, 0, m_diffList.GetSize() - 1);
822 }
823
824 /**
825  * @brief Copy range of diffs from one side to side.
826  * This function copies given range of differences from side to another.
827  * Ignored differences are skipped, and not copied.
828  * @param [in] srcPane Source side from which diff is copied
829  * @param [in] dstPane Destination side
830  * @param [in] firstDiff First diff copied (0-based index)
831  * @param [in] lastDiff Last diff copied (0-based index)
832  */
833 void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int lastDiff, int firstWordDiff, int lastWordDiff)
834 {
835 #ifdef _DEBUG
836         if (firstDiff > lastDiff)
837                 _RPTF0(_CRT_ERROR, "Invalid diff range (firstDiff > lastDiff)!");
838         if (firstDiff < 0)
839                 _RPTF0(_CRT_ERROR, "Invalid diff range (firstDiff < 0)!");
840         if (lastDiff > m_diffList.GetSize() - 1)
841                 _RPTF0(_CRT_ERROR, "Invalid diff range (lastDiff < diffcount)!");
842 #endif
843
844         lastDiff = min(m_diffList.GetSize() - 1, lastDiff);
845         firstDiff = max(0, firstDiff);
846         if (firstDiff > lastDiff)
847                 return;
848         
849         RescanSuppress suppressRescan(*this);
850
851         // Note we don't care about m_nDiffs count to become zero,
852         // because we don't rescan() so it does not change
853
854         SetCurrentDiff(lastDiff);
855
856         bool bGroupWithPrevious = false;
857         if (firstWordDiff <= 0 && lastWordDiff == -1)
858         {
859                 if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, true))
860                         return; // sync failure
861         }
862         else
863         {
864                 if (!WordListCopy(srcPane, dstPane, lastDiff, 
865                         (firstDiff == lastDiff) ? firstWordDiff : 0, lastWordDiff, nullptr, bGroupWithPrevious, true))
866                         return; // sync failure
867         }
868
869         SetEditedAfterRescan(dstPane);
870
871         int nGroup = GetActiveMergeView()->m_nThisGroup;
872         CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
873         CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
874         CPoint currentPosSrc = pViewSrc->GetCursorPos();
875         currentPosSrc.x = 0;
876         CPoint currentPosDst = pViewDst->GetCursorPos();
877         currentPosDst.x = 0;
878
879         CPoint pt(0, 0);
880         pViewDst->SetCursorPos(pt);
881         pViewDst->SetNewSelection(pt, pt, false);
882         pViewDst->SetNewAnchor(pt);
883
884         // copy from bottom up is more efficient
885         for (int i = lastDiff - 1; i >= firstDiff; --i)
886         {
887                 if (m_diffList.IsDiffSignificant(i))
888                 {
889                         SetCurrentDiff(i);
890                         const DIFFRANGE *pdi = m_diffList.DiffRangeAt(i);
891                         if (currentPosDst.y > pdi->dend)
892                         {
893                                 if (pdi->blank[dstPane] >= 0)
894                                         currentPosDst.y -= pdi->dend - pdi->blank[dstPane] + 1;
895                                 else if (pdi->blank[srcPane] >= 0)
896                                         currentPosDst.y -= pdi->dend - pdi->blank[srcPane] + 1;
897                         }                       
898                         // Group merge with previous (merge undo data to one action)
899                         bGroupWithPrevious = true;
900                         if (i > firstDiff || firstWordDiff <= 0)
901                         {
902                                 if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false))
903                                         break; // sync failure
904                         }
905                         else
906                         {
907                                 if (!WordListCopy(srcPane, dstPane, firstDiff, firstWordDiff, -1, nullptr, bGroupWithPrevious, false))
908                                         break; // sync failure
909                         }
910                 }
911         }
912
913         ForEachView(dstPane, [currentPosDst](auto& pView) {
914                 pView->SetCursorPos(currentPosDst);
915                 pView->SetNewSelection(currentPosDst, currentPosDst, false);
916                 pView->SetNewAnchor(currentPosDst);
917         });
918
919         suppressRescan.Clear(); // done suppress Rescan
920         FlushAndRescan();
921 }
922
923 void CMergeDoc::CopyMultiplePartialList(int srcPane, int dstPane, int firstDiff, int lastDiff,
924         int firstLineDiff, int lastLineDiff)
925 {
926         lastDiff = min(m_diffList.GetSize() - 1, lastDiff);
927         firstDiff = max(0, firstDiff);
928         if (firstDiff > lastDiff)
929                 return;
930         
931         RescanSuppress suppressRescan(*this);
932
933         bool bGroupWithPrevious = false;
934         if (firstLineDiff <= 0 && lastLineDiff == -1)
935         {
936                 if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, true))
937                         return; // sync failure
938         }
939         else
940         {
941                 if (!PartialListCopy(srcPane, dstPane, lastDiff, 
942                         (firstDiff == lastDiff) ? firstLineDiff : 0, lastLineDiff, bGroupWithPrevious, true))
943                         return; // sync failure
944         }
945
946
947         SetEditedAfterRescan(dstPane);
948
949         int nGroup = GetActiveMergeView()->m_nThisGroup;
950         CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
951         CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
952         CPoint currentPosSrc = pViewSrc->GetCursorPos();
953         currentPosSrc.x = 0;
954         CPoint currentPosDst = pViewDst->GetCursorPos();
955         currentPosDst.x = 0;
956
957         CPoint pt(0, 0);
958         pViewDst->SetCursorPos(pt);
959         pViewDst->SetNewSelection(pt, pt, false);
960         pViewDst->SetNewAnchor(pt);
961
962         // copy from bottom up is more efficient
963         for (int i = lastDiff - 1; i >= firstDiff; --i)
964         {
965                 if (m_diffList.IsDiffSignificant(i))
966                 {
967                         SetCurrentDiff(i);
968                         const DIFFRANGE *pdi = m_diffList.DiffRangeAt(i);
969                         if (currentPosDst.y > pdi->dend)
970                         {
971                                 if (pdi->blank[dstPane] >= 0)
972                                         currentPosDst.y -= pdi->dend - pdi->blank[dstPane] + 1;
973                                 else if (pdi->blank[srcPane] >= 0)
974                                         currentPosDst.y -= pdi->dend - pdi->blank[srcPane] + 1;
975                         }                       
976                         // Group merge with previous (merge undo data to one action)
977                         bGroupWithPrevious = true;
978                         if (i > firstDiff || firstLineDiff <= 0)
979                         {
980                                 if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false))
981                                         break; // sync failure
982                         }
983                         else
984                         {
985                                 if (!PartialListCopy(srcPane, dstPane, firstDiff, firstLineDiff, -1, bGroupWithPrevious, false))
986                                         break; // sync failure
987                         }
988                 }
989         }
990
991         ForEachView(dstPane, [currentPosDst](auto& pView) {
992                 pView->SetCursorPos(currentPosDst);
993                 pView->SetNewSelection(currentPosDst, currentPosDst, false);
994                 pView->SetNewAnchor(currentPosDst);
995         });
996
997         suppressRescan.Clear(); // done suppress Rescan
998         FlushAndRescan();
999 }
1000
1001 enum MergeResult { NoMergeNeeded, Merged, Conflict };
1002
1003 template<class Type>
1004 static std::pair<MergeResult, Type> DoMergeValue(Type left, Type middle, Type right, int dstPane)
1005 {
1006         bool equal_all = middle == left && middle == right && left == right;
1007         if (equal_all)
1008                 return std::pair<MergeResult, Type>(NoMergeNeeded, left);
1009         bool conflict = middle != left && middle != right && left != right;
1010         if (conflict)
1011                 return std::pair<MergeResult, Type>(Conflict, left);
1012         switch (dstPane)
1013         {
1014         case 0:
1015                 if (left == right)
1016                         return std::pair<MergeResult, Type>(Merged, middle);
1017                 break;
1018         case 1:
1019                 if (left == middle)
1020                         return std::pair<MergeResult, Type>(Merged, right);
1021                 else
1022                         return std::pair<MergeResult, Type>(Merged, left);
1023                 break;
1024         case 2:
1025                 if (left == right)
1026                         return std::pair<MergeResult, Type>(Merged, middle);
1027                 break;
1028         }
1029         return std::pair<MergeResult, Type>(NoMergeNeeded, left);
1030 }
1031
1032 /**
1033  * @brief Do auto-merge.
1034  * @param [in] dstPane Destination side
1035  */
1036 void CMergeDoc::DoAutoMerge(int dstPane)
1037 {
1038         const int lastDiff = m_diffList.GetSize() - 1;
1039         const int firstDiff = 0;
1040         bool bGroupWithPrevious = false;
1041         int autoMergedCount = 0;
1042         int unresolvedConflictCount = 0;
1043
1044         std::pair<MergeResult, FileTextEncoding> mergedEncoding = 
1045                 DoMergeValue(m_ptBuf[0]->getEncoding(), m_ptBuf[1]->getEncoding(), m_ptBuf[2]->getEncoding(), dstPane);
1046         if (mergedEncoding.first == Merged)
1047         {
1048                 ShowMessageBox(_("The change of codepage has been merged."), MB_ICONINFORMATION);
1049                 m_ptBuf[dstPane]->setEncoding(mergedEncoding.second);
1050         }
1051         else if (mergedEncoding.first == Conflict)
1052                 ShowMessageBox(_("The changes of codepage are conflicting."), MB_ICONINFORMATION);
1053
1054         std::pair<MergeResult, CRLFSTYLE> mergedEOLStyle = 
1055                 DoMergeValue(m_ptBuf[0]->GetCRLFMode(), m_ptBuf[1]->GetCRLFMode(), m_ptBuf[2]->GetCRLFMode(), dstPane);
1056         if (mergedEOLStyle.first == Merged)
1057         {
1058                 ShowMessageBox(_("The change of EOL has been merged."), MB_ICONINFORMATION);
1059                 m_ptBuf[dstPane]->SetCRLFMode(mergedEOLStyle.second);
1060         }
1061         else if (mergedEOLStyle.first == Conflict)
1062                 ShowMessageBox(_("The changes of EOL are conflicting."), MB_ICONINFORMATION);
1063
1064         RescanSuppress suppressRescan(*this);
1065
1066         // Note we don't care about m_nDiffs count to become zero,
1067         // because we don't rescan() so it does not change
1068
1069         SetCurrentDiff(lastDiff);
1070
1071         SetEditedAfterRescan(dstPane);
1072
1073         int nGroup = GetActiveMergeView()->m_nThisGroup;
1074         CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
1075         CPoint currentPosDst = pViewDst->GetCursorPos();
1076         currentPosDst.x = 0;
1077
1078         CPoint pt(0, 0);
1079         pViewDst->SetCursorPos(pt);
1080         pViewDst->SetNewSelection(pt, pt, false);
1081         pViewDst->SetNewAnchor(pt);
1082
1083         // copy from bottom up is more efficient
1084         for (int i = lastDiff; i >= firstDiff; --i)
1085         {
1086                 const DIFFRANGE *pdi = m_diffList.DiffRangeAt(i);
1087                 const int srcPane = m_diffList.GetMergeableSrcIndex(i, dstPane);
1088                 if (srcPane != -1)
1089                 {
1090                         SetCurrentDiff(i);
1091                         if (currentPosDst.y > pdi->dend)
1092                         {
1093                                 if (pdi->blank[dstPane] >= 0)
1094                                         currentPosDst.y -= pdi->dend - pdi->blank[dstPane] + 1;
1095                                 else if (pdi->blank[srcPane] >= 0)
1096                                         currentPosDst.y -= pdi->dend - pdi->blank[srcPane] + 1;
1097                         }                       
1098                         // Group merge with previous (merge undo data to one action)
1099                         if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false))
1100                                 break; // sync failure
1101                         if (!bGroupWithPrevious)
1102                                 bGroupWithPrevious = true;
1103                         ++autoMergedCount;
1104                 }
1105                 if (pdi->op == OP_DIFF)
1106                         ++unresolvedConflictCount;
1107         }
1108
1109         ForEachView(dstPane, [currentPosDst](auto& pView) {
1110                 pView->SetCursorPos(currentPosDst);
1111                 pView->SetNewSelection(currentPosDst, currentPosDst, false);
1112                 pView->SetNewAnchor(currentPosDst);
1113         });
1114
1115         suppressRescan.Clear(); // done suppress Rescan
1116         FlushAndRescan();
1117         UpdateHeaderPath(dstPane);
1118
1119         if (autoMergedCount > 0)
1120                 m_bAutoMerged = true;
1121
1122         // move to first conflict 
1123         const int nDiff = m_diffList.FirstSignificant3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1124         if (nDiff != -1)
1125                 pViewDst->SelectDiff(nDiff, true, false);
1126
1127         ShowMessageBox(
1128                 strutils::format_string2(
1129                         _("The number of automatically merged changes: %1\nThe number of unresolved conflicts: %2"), 
1130                         strutils::format(_T("%d"), autoMergedCount),
1131                         strutils::format(_T("%d"), unresolvedConflictCount)),
1132                 MB_ICONINFORMATION);
1133 }
1134
1135 /**
1136  * @brief Sanity check difference.
1137  *
1138  * Checks that lines in difference are inside difference in both files.
1139  * If file is edited, lines added or removed diff lines get out of sync and
1140  * merging fails miserably.
1141  *
1142  * @param [in] dr Difference to check.
1143  * @return true if difference lines match, false otherwise.
1144  */
1145 bool CMergeDoc::SanityCheckDiff(DIFFRANGE dr) const
1146 {
1147         const int cd_dbegin = dr.dbegin;
1148         const int cd_dend = dr.dend;
1149
1150         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
1151         {
1152                 // Must ensure line number is in range before getting line flags
1153                 if (cd_dend >= m_ptBuf[nBuffer]->GetLineCount())
1154                         return false;
1155
1156                 // Optimization - check last line first so we don't need to
1157                 // check whole diff for obvious cases
1158                 DWORD dwFlags = m_ptBuf[nBuffer]->GetLineFlags(cd_dend);
1159                 if (!(dwFlags & LF_WINMERGE_FLAGS))
1160                         return false;
1161         }
1162
1163         for (int line = cd_dbegin; line < cd_dend; line++)
1164         {
1165                 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
1166                 {
1167                         DWORD dwFlags = m_ptBuf[nBuffer]->GetLineFlags(cd_dend);
1168                         if (!(dwFlags & LF_WINMERGE_FLAGS))
1169                                 return false;
1170                 }
1171         }
1172         return true;
1173 }
1174
1175 /**
1176  * @brief Copy selected (=current) difference from from side to side.
1177  * @param [in] srcPane Source side from which diff is copied
1178  * @param [in] dstPane Destination side
1179  * @param [in] nDiff Diff to copy, if -1 function determines it.
1180  * @param [in] bGroupWithPrevious Adds diff to same undo group with
1181  * @return true if ok, false if sync failure & need to abort copy
1182  * previous action (allows one undo for copy all)
1183  */
1184 bool CMergeDoc::ListCopy(int srcPane, int dstPane, int nDiff /* = -1*/,
1185                 bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/)
1186 {
1187         int nGroup = GetActiveMergeView()->m_nThisGroup;
1188         CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
1189         CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
1190         CCrystalTextView *pSource = bUpdateView ? pViewDst : nullptr;
1191
1192         // suppress Rescan during this method
1193         // (Not only do we not want to rescan a lot of times, but
1194         // it will wreck the line status array to rescan as we merge)
1195         RescanSuppress suppressRescan(*this);
1196
1197         // If diff-number not given, determine it from active view
1198         if (nDiff == -1)
1199         {
1200                 nDiff = GetCurrentDiff();
1201
1202                 // No current diff, but maybe cursor is in diff?
1203                 if (nDiff == -1 && (pViewSrc->IsCursorInDiff() ||
1204                         pViewDst->IsCursorInDiff()))
1205                 {
1206                         // Find out diff under cursor
1207                         CPoint ptCursor = GetActiveMergeView()->GetCursorPos();
1208                         nDiff = m_diffList.LineToDiff(ptCursor.y);
1209                 }
1210         }
1211
1212         if (nDiff != -1)
1213         {
1214                 DIFFRANGE cd;
1215                 VERIFY(m_diffList.GetDiff(nDiff, cd));
1216                 CDiffTextBuffer& sbuf = *m_ptBuf[srcPane];
1217                 CDiffTextBuffer& dbuf = *m_ptBuf[dstPane];
1218                 bool bSrcWasMod = sbuf.IsModified();
1219                 const int cd_dbegin = cd.dbegin;
1220                 const int cd_dend = cd.dend;
1221                 const int cd_blank = cd.blank[srcPane];
1222                 bool bInSync = SanityCheckDiff(cd);
1223
1224                 if (!bInSync)
1225                 {
1226                         LangMessageBox(IDS_VIEWS_OUTOFSYNC, MB_ICONSTOP);
1227                         return false; // abort copying
1228                 }
1229
1230                 // If we remove whole diff from current view, we must fix cursor
1231                 // position first. Normally we would move to end of previous line,
1232                 // but we want to move to begin of that line for usability.
1233                 if (bUpdateView)
1234                 {
1235                         CPoint currentPos = pViewDst->GetCursorPos();
1236                         currentPos.x = 0;
1237                         if (currentPos.y > cd_dend)
1238                         {
1239                                 if (cd.blank[dstPane] >= 0)
1240                                         currentPos.y -= cd_dend - cd.blank[dstPane] + 1;
1241                                 else if (cd.blank[srcPane] >= 0)
1242                                         currentPos.y -= cd_dend - cd.blank[srcPane] + 1;
1243                         }
1244                         ForEachView(dstPane, [currentPos](auto& pView) { pView->SetCursorPos(currentPos); });
1245                 }
1246
1247                 // if the current diff contains missing lines, remove them from both sides
1248                 int limit = cd_dend;
1249
1250                 // curView is the view which is changed, so the opposite of the source view
1251                 dbuf.BeginUndoGroup(bGroupWithPrevious);
1252                 if (cd_blank>=0)
1253                 {
1254                         // text was missing, so delete rest of lines on both sides
1255                         // delete only on destination side since rescan will clear the other side
1256                         if (cd_dend + 1 < dbuf.GetLineCount())
1257                         {
1258                                 dbuf.DeleteText(pSource, cd_blank, 0, cd_dend+1, 0, CE_ACTION_MERGE);
1259                         }
1260                         else
1261                         {
1262                                 // To removing EOL chars of last line, deletes from the end of the line (cd_blank - 1).
1263                                 ASSERT(cd_blank > 0);
1264                                 dbuf.DeleteText(pSource, cd_blank-1, dbuf.GetLineLength(cd_blank-1), cd_dend, dbuf.GetLineLength(cd_dend), CE_ACTION_MERGE);
1265                         }
1266
1267                         limit=cd_blank-1;
1268                         dbuf.FlushUndoGroup(pSource);
1269                         dbuf.BeginUndoGroup(true);
1270                 }
1271
1272
1273                 // copy the selected text over
1274                 if (cd_dbegin <= limit)
1275                 {
1276                         // text exists on left side, so just replace
1277                         dbuf.ReplaceFullLines(dbuf, sbuf, pSource, cd_dbegin, limit, CE_ACTION_MERGE);
1278                         dbuf.FlushUndoGroup(pSource);
1279                         dbuf.BeginUndoGroup(true);
1280                 }
1281                 dbuf.FlushUndoGroup(pSource);
1282
1283                 // remove the diff
1284                 SetCurrentDiff(-1);
1285
1286                 // reset the mod status of the source view because we do make some
1287                 // changes, but none that concern the source text
1288                 sbuf.SetModified(bSrcWasMod);
1289         }
1290
1291         suppressRescan.Clear(); // done suppress Rescan
1292         FlushAndRescan();
1293         return true;
1294 }
1295
1296 bool CMergeDoc::PartialListCopy(int srcPane, int dstPane, int nDiff, int firstLine, int lastLine /*= -1*/,
1297         bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/)
1298 {
1299         int nGroup = GetActiveMergeView()->m_nThisGroup;
1300         CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
1301         CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
1302         CCrystalTextView *pSource = bUpdateView ? pViewDst : nullptr;
1303
1304         // suppress Rescan during this method
1305         // (Not only do we not want to rescan a lot of times, but
1306         // it will wreck the line status array to rescan as we merge)
1307         RescanSuppress suppressRescan(*this);
1308
1309         DIFFRANGE cd;
1310         VERIFY(m_diffList.GetDiff(nDiff, cd));
1311         CDiffTextBuffer& sbuf = *m_ptBuf[srcPane];
1312         CDiffTextBuffer& dbuf = *m_ptBuf[dstPane];
1313         bool bSrcWasMod = sbuf.IsModified();
1314         const int cd_dbegin = (firstLine > cd.dbegin) ? firstLine : cd.dbegin;
1315         const int cd_dend = cd.dend;
1316         const int cd_blank = cd.blank[srcPane];
1317         bool bInSync = SanityCheckDiff(cd);
1318
1319         if (!bInSync)
1320         {
1321                 LangMessageBox(IDS_VIEWS_OUTOFSYNC, MB_ICONSTOP);
1322                 return false; // abort copying
1323         }
1324
1325         // If we remove whole diff from current view, we must fix cursor
1326         // position first. Normally we would move to end of previous line,
1327         // but we want to move to begin of that line for usability.
1328         if (bUpdateView)
1329         {
1330                 CPoint currentPos = pViewDst->GetCursorPos();
1331                 currentPos.x = 0;
1332                 if (currentPos.y > cd_dend)
1333                 {
1334                         if (cd.blank[dstPane] >= 0)
1335                                 currentPos.y -= cd_dend - cd.blank[dstPane] + 1;
1336                         else if (cd.blank[srcPane] >= 0)
1337                                 currentPos.y -= cd_dend - cd.blank[srcPane] + 1;
1338                 }
1339                 ForEachView(dstPane, [currentPos](auto& pView) { pView->SetCursorPos(currentPos); });
1340         }
1341
1342         // if the current diff contains missing lines, remove them from both sides
1343         int limit = ((lastLine < 0) || (lastLine > cd_dend)) ? cd_dend : lastLine;
1344
1345         // curView is the view which is changed, so the opposite of the source view
1346         dbuf.BeginUndoGroup(bGroupWithPrevious);
1347         if ((cd_blank >= 0) && (cd_dbegin >= cd_blank))
1348         {
1349                 // text was missing, so delete rest of lines on both sides
1350                 // delete only on destination side since rescan will clear the other side
1351                 if (limit+1 < dbuf.GetLineCount())
1352                 {
1353                         dbuf.DeleteText(pSource, cd_dbegin, 0, limit+1, 0, CE_ACTION_MERGE);
1354                 }
1355                 else
1356                 {
1357                         // To removing EOL chars of last line, deletes from the end of the line (cd_blank - 1).
1358                         ASSERT(cd_dbegin > 0);
1359                         dbuf.DeleteText(pSource, cd_dbegin-1, dbuf.GetLineLength(cd_dbegin-1), limit, dbuf.GetLineLength(limit), CE_ACTION_MERGE);
1360                 }
1361
1362                 limit = cd_dbegin-1;
1363                 dbuf.FlushUndoGroup(pSource);
1364                 dbuf.BeginUndoGroup(true);
1365         }
1366
1367         // copy the selected text over
1368         if (cd_dbegin <= limit)
1369         {
1370                 // text exists on left side, so just replace
1371                 dbuf.ReplaceFullLines(dbuf, sbuf, pSource, cd_dbegin, limit, CE_ACTION_MERGE);
1372                 dbuf.FlushUndoGroup(pSource);
1373                 dbuf.BeginUndoGroup(true);
1374         }
1375         dbuf.FlushUndoGroup(pSource);
1376
1377         // remove the diff
1378         SetCurrentDiff(-1);
1379
1380         // reset the mod status of the source view because we do make some
1381         // changes, but none that concern the source text
1382         sbuf.SetModified(bSrcWasMod);
1383
1384         suppressRescan.Clear(); // done suppress Rescan
1385         FlushAndRescan();
1386         return true;
1387 }
1388
1389 bool CMergeDoc::WordListCopy(int srcPane, int dstPane, int nDiff, int firstWordDiff, int lastWordDiff,
1390                 const std::vector<int> *pWordDiffIndice, bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/)
1391 {
1392         int nGroup = GetActiveMergeView()->m_nThisGroup;
1393         CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
1394         CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
1395         CCrystalTextView *pSource = bUpdateView ? pViewDst : nullptr;
1396
1397         // suppress Rescan during this method
1398         // (Not only do we not want to rescan a lot of times, but
1399         // it will wreck the line status array to rescan as we merge)
1400         RescanSuppress suppressRescan(*this);
1401
1402         DIFFRANGE cd;
1403         VERIFY(m_diffList.GetDiff(nDiff, cd));
1404         CDiffTextBuffer& sbuf = *m_ptBuf[srcPane];
1405         CDiffTextBuffer& dbuf = *m_ptBuf[dstPane];
1406         bool bSrcWasMod = sbuf.IsModified();
1407         const int cd_dbegin = cd.dbegin;
1408         const int cd_dend = cd.dend;
1409         const int cd_blank = cd.blank[srcPane];
1410         bool bInSync = SanityCheckDiff(cd);
1411
1412         if (!bInSync)
1413         {
1414                 LangMessageBox(IDS_VIEWS_OUTOFSYNC, MB_ICONSTOP);
1415                 return false; // abort copying
1416         }
1417
1418         std::vector<WordDiff> worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
1419
1420         if (worddiffs.empty())
1421                 return false;
1422
1423         if (cd.end[srcPane] < cd.begin[srcPane])
1424                 return ListCopy(srcPane, dstPane, nDiff, bGroupWithPrevious, bUpdateView);
1425
1426         if (firstWordDiff == -1)
1427                 firstWordDiff = 0;
1428         if (lastWordDiff == -1)
1429                 lastWordDiff = static_cast<int>(worddiffs.size() - 1);
1430
1431         // If we remove whole diff from current view, we must fix cursor
1432         // position first. Normally we would move to end of previous line,
1433         // but we want to move to begin of that line for usability.
1434         if (bUpdateView)
1435         {
1436                 CPoint currentPos = pViewDst->GetCursorPos();
1437                 currentPos.x = 0;
1438                 if (currentPos.y > cd_dend)
1439                 {
1440                         if (cd.blank[dstPane] >= 0)
1441                                 currentPos.y -= cd_dend - cd.blank[dstPane] + 1;
1442                         else if (cd.blank[srcPane] >= 0)
1443                                 currentPos.y -= cd_dend - cd.blank[srcPane] + 1;
1444                 }
1445                 ForEachView(dstPane, [currentPos](auto& pView) { pView->SetCursorPos(currentPos); });
1446         }
1447
1448         // if the current diff contains missing lines, remove them from both sides
1449         int limit = cd_dend;
1450
1451         // curView is the view which is changed, so the opposite of the source view
1452         dbuf.BeginUndoGroup(bGroupWithPrevious);
1453
1454         CString srcText, dstText;
1455         CPoint ptDstStart, ptDstEnd;
1456         CPoint ptSrcStart, ptSrcEnd;
1457
1458         ptDstStart.x = worddiffs[firstWordDiff].begin[dstPane];
1459         ptDstStart.y = worddiffs[firstWordDiff].beginline[dstPane];
1460         ptDstEnd.x = worddiffs[lastWordDiff].end[dstPane];
1461         ptDstEnd.y = worddiffs[lastWordDiff].endline[dstPane];
1462         ptSrcStart.x = worddiffs[firstWordDiff].begin[srcPane];
1463         ptSrcStart.y = worddiffs[firstWordDiff].beginline[srcPane];
1464         ptSrcEnd.x = worddiffs[lastWordDiff].end[srcPane];
1465         ptSrcEnd.y = worddiffs[lastWordDiff].endline[srcPane];
1466
1467         std::vector<int> nDstOffsets(ptDstEnd.y - ptDstStart.y + 2);
1468         std::vector<int> nSrcOffsets(ptSrcEnd.y - ptSrcStart.y + 2);
1469
1470         dbuf.GetTextWithoutEmptys(ptDstStart.y, ptDstStart.x, ptDstEnd.y, ptDstEnd.x, dstText);
1471         sbuf.GetTextWithoutEmptys(ptSrcStart.y, ptSrcStart.x, ptSrcEnd.y, ptSrcEnd.x, srcText);
1472
1473         nDstOffsets[0] = 0;
1474         for (int nLine = ptDstStart.y; nLine <= ptDstEnd.y; nLine++)
1475                 nDstOffsets[nLine-ptDstStart.y+1] = nDstOffsets[nLine-ptDstStart.y] + dbuf.GetFullLineLength(nLine);
1476         nSrcOffsets[0] = 0;
1477         for (int nLine = ptSrcStart.y; nLine <= ptSrcEnd.y; nLine++)
1478                 nSrcOffsets[nLine-ptSrcStart.y+1] = nSrcOffsets[nLine-ptSrcStart.y] + sbuf.GetFullLineLength(nLine);
1479
1480         for (int i = lastWordDiff; i != firstWordDiff-1; --i)
1481         {
1482                 if (pWordDiffIndice && std::find(pWordDiffIndice->begin(), pWordDiffIndice->end(), i) == pWordDiffIndice->end())
1483                         continue;
1484                 int srcBegin = nSrcOffsets[worddiffs[i].beginline[srcPane] - ptSrcStart.y] + worddiffs[i].begin[srcPane];
1485                 int srcEnd   = nSrcOffsets[worddiffs[i].endline[srcPane] - ptSrcStart.y] + worddiffs[i].end[srcPane];
1486                 int dstBegin = nDstOffsets[worddiffs[i].beginline[dstPane] - ptDstStart.y] + worddiffs[i].begin[dstPane];
1487                 int dstEnd   = nDstOffsets[worddiffs[i].endline[dstPane] - ptDstStart.y] + worddiffs[i].end[dstPane];
1488                 dstText = dstText.Mid(0, dstBegin - ptDstStart.x)
1489                         + srcText.Mid(srcBegin - ptSrcStart.x, srcEnd - srcBegin)
1490                         + dstText.Mid(dstEnd - ptDstStart.x);
1491         }
1492
1493         dbuf.DeleteText(pSource, ptDstStart.y, ptDstStart.x, ptDstEnd.y, ptDstEnd.x, CE_ACTION_MERGE);
1494
1495         int endl,endc;
1496         dbuf.InsertText(pSource, ptDstStart.y, ptDstStart.x, dstText, dstText.GetLength(), endl, endc, CE_ACTION_MERGE);
1497
1498         dbuf.FlushUndoGroup(pSource);
1499
1500         // reset the mod status of the source view because we do make some
1501         // changes, but none that concern the source text
1502         sbuf.SetModified(bSrcWasMod);
1503
1504         suppressRescan.Clear(); // done suppress Rescan
1505         FlushAndRescan();
1506
1507         return true;
1508 }
1509
1510 /**
1511  * @brief Save file with new filename.
1512  *
1513  * This function is called by CMergeDoc::DoSave() or CMergeDoc::DoSAveAs()
1514  * to save file with new filename. CMergeDoc::DoSave() calls if saving with
1515  * normal filename fails, to let user choose another filename/location.
1516  * Also, if file is unnamed file (e.g. scratchpad) then it must be saved
1517  * using this function.
1518  * @param [in, out] strPath 
1519  * - [in] : Initial path shown to user
1520  * - [out] : Path to new filename if saving succeeds
1521  * @param [in, out] nSaveResult 
1522  * - [in] : Statuscode telling why we ended up here. Maybe the result of
1523  * previous save.
1524  * - [out] : Statuscode of this saving try
1525  * @param [in, out] sError Error string from lower level saving code
1526  * @param [in] nBuffer Buffer we are saving
1527  * @return false as long as the user is not satisfied. Calling function
1528  * should not continue until true is returned.
1529  * @sa CMergeDoc::DoSave()
1530  * @sa CMergeDoc::DoSaveAs()
1531  * @sa CMergeDoc::CDiffTextBuffer::SaveToFile()
1532  */
1533 bool CMergeDoc::TrySaveAs(String &strPath, int &nSaveResult, String & sError,
1534         int nBuffer, PackingInfo * pInfoTempUnpacker)
1535 {
1536         String s;
1537         String str;
1538         String strSavePath; // New path for next saving try
1539         String title;
1540         bool result = true;
1541         int answer = IDOK; // Set default we use for scratchpads
1542
1543         // We shouldn't get here if saving is succeed before
1544         ASSERT(nSaveResult != SAVE_DONE);
1545
1546         // Select message based on reason function called
1547         if (nSaveResult == SAVE_PACK_FAILED)
1548         {
1549                 if (m_nBuffers == 3)
1550                 {
1551                         str = strutils::format_string2(
1552                                 nBuffer == 0 ? 
1553                                         _("Plugin '%2' cannot pack your changes to the left file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?")
1554                                         : (nBuffer == 1 ? 
1555                                         _("Plugin '%2' cannot pack your changes to the middle file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?"): 
1556                                         _("Plugin '%2' cannot pack your changes to the right file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?")),
1557                                 strPath, pInfoTempUnpacker->m_PluginName);
1558                 }
1559                 else
1560                 {
1561                         str = strutils::format_string2(nBuffer == 0 ? _("Plugin '%2' cannot pack your changes to the left file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?") : 
1562                                 _("Plugin '%2' cannot pack your changes to the right file back into '%1'.\n\nThe original file will not be changed.\n\nDo you want to save the unpacked version to another file?"),
1563                                 strPath, pInfoTempUnpacker->m_PluginName);
1564                 }
1565                 // replace the unpacker with a "do nothing" unpacker
1566                 pInfoTempUnpacker->Initialize(PLUGIN_MODE::PLUGIN_MANUAL);
1567         }
1568         else
1569         {
1570                 str = strutils::format_string2(_("Saving file failed.\n%1\n%2\nDo you want to:\n\t- use a different filename (Press OK)\n\t- abort the current operation (Press Cancel)?"), strPath, sError);
1571         }
1572
1573         // SAVE_NO_FILENAME is temporarily used for scratchpad.
1574         // So don't ask about saving in that case.
1575         if (nSaveResult != SAVE_NO_FILENAME)
1576                 answer = ShowMessageBox(str, MB_OKCANCEL | MB_ICONWARNING);
1577
1578         switch (answer)
1579         {
1580         case IDOK:
1581                 if (nBuffer == 0)
1582                         title = _("Save Left File As");
1583                 else if (nBuffer == m_nBuffers - 1)
1584                         title = _("Save Right File As");
1585                 else
1586                         title = _("Save Middle File As");
1587
1588                 if (SelectFile(GetActiveMergeView()->GetSafeHwnd(), s, false, strPath.c_str(), title))
1589                 {
1590                         CDiffTextBuffer *pBuffer = m_ptBuf[nBuffer].get();
1591                         strSavePath = s;
1592                         nSaveResult = pBuffer->SaveToFile(strSavePath, false, sError,
1593                                 pInfoTempUnpacker);
1594
1595                         if (nSaveResult == SAVE_DONE)
1596                         {
1597                                 // We are saving scratchpad (unnamed file)
1598                                 if (strPath.empty())
1599                                 {
1600                                         m_nBufferType[nBuffer] = BUFFERTYPE::UNNAMED_SAVED;
1601                                         m_strDesc[nBuffer].erase();
1602                                 }
1603                                         
1604                                 strPath = strSavePath;
1605                                 UpdateHeaderPath(nBuffer);
1606                         }
1607                         else
1608                                 result = false;
1609                 }
1610                 else
1611                         nSaveResult = SAVE_CANCELLED;
1612                 break;
1613
1614         case IDCANCEL:
1615                 nSaveResult = SAVE_CANCELLED;
1616                 break;
1617         }
1618         return result;
1619 }
1620
1621 /**
1622  * @brief Save file creating backups etc.
1623  *
1624  * Safe top-level file saving function. Checks validity of given path.
1625  * Creates backup file if wanted to. And if saving to given path fails,
1626  * allows user to select new location/name for file.
1627  * @param [in] szPath Path where to save including filename. Can be
1628  * empty/`nullptr` if new file is created (scratchpad) without filename.
1629  * @param [out] bSaveSuccess Will contain information about save success with
1630  * the original name (to determine if file statuses should be changed)
1631  * @param [in] nBuffer Index (0-based) of buffer to save
1632  * @return Tells if caller can continue (no errors happened)
1633  * @note Return value does not tell if SAVING succeeded. Caller must
1634  * Check value of bSaveSuccess parameter after calling this function!
1635  * @note If CMainFrame::m_strSaveAsPath is non-empty, file is saved
1636  * to directory it points to. If m_strSaveAsPath contains filename,
1637  * that filename is used.
1638  * @sa CMergeDoc::TrySaveAs()
1639  * @sa CMainFrame::CheckSavePath()
1640  * @sa CMergeDoc::CDiffTextBuffer::SaveToFile()
1641  */
1642 bool CMergeDoc::DoSave(LPCTSTR szPath, bool &bSaveSuccess, int nBuffer)
1643 {
1644         DiffFileInfo fileInfo;
1645         String strSavePath(szPath);
1646         FileChange fileChanged;
1647         bool bApplyToAll = false;       
1648         int nRetVal = -1;
1649
1650         fileChanged = IsFileChangedOnDisk(szPath, fileInfo, true, nBuffer);
1651         if (fileChanged == FileChange::Changed)
1652         {
1653                 String msg = strutils::format_string1(_("Another application has updated file\n%1\nsince WinMerge loaded it.\n\nOverwrite changed file?"), szPath);
1654                 if (ShowMessageBox(msg, MB_ICONWARNING | MB_YESNO) == IDNO)
1655                 {
1656                         bSaveSuccess = true;
1657                         return true;
1658                 }               
1659         }
1660
1661         // use a temp packer
1662         // first copy the m_pInfoUnpacker
1663         // if an error arises during packing, change and take a "do nothing" packer
1664         PackingInfo infoTempUnpacker = *m_pInfoUnpacker;
1665
1666         bSaveSuccess = false;
1667         
1668         // Check third arg possibly given from command-line
1669         if (!theApp.m_strSaveAsPath.empty())
1670         {
1671                 if (paths::DoesPathExist(theApp.m_strSaveAsPath) == paths::IS_EXISTING_DIR)
1672                 {
1673                         // third arg was a directory, so get append the filename
1674                         String sname;
1675                         paths::SplitFilename(szPath, 0, &sname, 0);
1676                         strSavePath = theApp.m_strSaveAsPath;
1677                         strSavePath = paths::ConcatPath(strSavePath, sname);
1678                 }
1679                 else
1680                         strSavePath = theApp.m_strSaveAsPath;   
1681         }
1682
1683         nRetVal = CMergeApp::HandleReadonlySave(strSavePath, false, bApplyToAll);
1684         if (nRetVal == IDCANCEL)
1685                 return false;
1686
1687         if (!CMergeApp::CreateBackup(false, strSavePath))
1688                 return false;
1689
1690         // false as long as the user is not satisfied
1691         // true if saving succeeds, even with another filename, or if the user cancels
1692         bool result = false;
1693         // the error code from the latest save operation, 
1694         // or SAVE_DONE when the save succeeds
1695         // TODO: Shall we return this code in addition to bSaveSuccess ?
1696         int nSaveErrorCode = SAVE_DONE;
1697         CDiffTextBuffer *pBuffer = m_ptBuf[nBuffer].get();
1698
1699         // Assume empty filename means Scratchpad (unnamed file)
1700         // Todo: This is not needed? - buffer type check should be enough
1701         if (strSavePath.empty())
1702                 nSaveErrorCode = SAVE_NO_FILENAME;
1703
1704         // Handle unnamed buffers
1705         if (m_nBufferType[nBuffer] == BUFFERTYPE::UNNAMED)
1706                 nSaveErrorCode = SAVE_NO_FILENAME;
1707
1708         String sError;
1709         if (nSaveErrorCode == SAVE_DONE)
1710                 // We have a filename, just try to save
1711                 nSaveErrorCode = pBuffer->SaveToFile(strSavePath, false, sError, &infoTempUnpacker);
1712
1713         if (nSaveErrorCode != SAVE_DONE)
1714         {
1715                 // Saving failed, user may save to another location if wants to
1716                 do
1717                         result = TrySaveAs(strSavePath, nSaveErrorCode, sError, nBuffer, &infoTempUnpacker);
1718                 while (!result);
1719         }
1720
1721         // Saving succeeded with given/selected filename
1722         if (nSaveErrorCode == SAVE_DONE)
1723         {
1724                 // Preserve file times if user wants to
1725                 if (GetOptionsMgr()->GetBool(OPT_PRESERVE_FILETIMES))
1726                 {
1727                         fileInfo.SetFile(strSavePath);
1728                         try
1729                         {
1730                                 TFile file(strSavePath);
1731                                 file.setLastModified(fileInfo.mtime);
1732                         }
1733                         catch (...)
1734                         {
1735                         }
1736                 }
1737
1738                 m_ptBuf[nBuffer]->SetModified(false);
1739                 m_pSaveFileInfo[nBuffer]->Update(strSavePath.c_str());
1740                 m_filePaths[nBuffer] = strSavePath;
1741                 m_pRescanFileInfo[nBuffer]->Update(m_filePaths[nBuffer].c_str());
1742                 UpdateHeaderPath(nBuffer);
1743                 bSaveSuccess = true;
1744                 result = true;
1745         }
1746         else if (nSaveErrorCode == SAVE_CANCELLED)
1747         {
1748                 // User cancelled current operation, lets do what user wanted to do
1749                 result = false;
1750         }
1751         return result;
1752 }
1753
1754 /**
1755  * @brief Save file with different filename.
1756  *
1757  * Safe top-level file saving function. Asks user to select filename
1758  * and path. Does not create backups.
1759  * @param [in] szPath Path where to save including filename. Can be
1760  * empty/`nullptr` if new file is created (scratchpad) without filename.
1761  * @param [out] bSaveSuccess Will contain information about save success with
1762  * the original name (to determine if file statuses should be changed)
1763  * @param [in] nBuffer Index (0-based) of buffer to save
1764  * @return Tells if caller can continue (no errors happened)
1765  * @note Return value does not tell if SAVING succeeded. Caller must
1766  * Check value of bSaveSuccess parameter after calling this function!
1767  * @sa CMergeDoc::TrySaveAs()
1768  * @sa CMainFrame::CheckSavePath()
1769  * @sa CMergeDoc::CDiffTextBuffer::SaveToFile()
1770  */
1771 bool CMergeDoc::DoSaveAs(LPCTSTR szPath, bool &bSaveSuccess, int nBuffer)
1772 {
1773         String strSavePath(szPath);
1774
1775         // use a temp packer
1776         // first copy the m_pInfoUnpacker
1777         // if an error arises during packing, change and take a "do nothing" packer
1778         PackingInfo infoTempUnpacker = *m_pInfoUnpacker;
1779
1780         bSaveSuccess = false;
1781         // false as long as the user is not satisfied
1782         // true if saving succeeds, even with another filename, or if the user cancels
1783         bool result = false;
1784         // the error code from the latest save operation, 
1785         // or SAVE_DONE when the save succeeds
1786         // TODO: Shall we return this code in addition to bSaveSuccess ?
1787         int nSaveErrorCode = SAVE_DONE;
1788
1789         // Use SAVE_NO_FILENAME to prevent asking about error
1790         nSaveErrorCode = SAVE_NO_FILENAME;
1791
1792         // Loop until user succeeds saving or cancels
1793         String sError;
1794         do
1795                 result = TrySaveAs(strSavePath, nSaveErrorCode, sError, nBuffer, &infoTempUnpacker);
1796         while (!result);
1797
1798         // Saving succeeded with given/selected filename
1799         if (nSaveErrorCode == SAVE_DONE)
1800         {
1801                 m_pSaveFileInfo[nBuffer]->Update(strSavePath);
1802                 m_filePaths[nBuffer] = strSavePath;
1803                 m_pRescanFileInfo[nBuffer]->Update(m_filePaths[nBuffer]);
1804                 UpdateHeaderPath(nBuffer);
1805                 bSaveSuccess = true;
1806                 result = true;
1807         }
1808         return result;
1809 }
1810
1811 /**
1812  * @brief Get left->right info for a moved line (apparent line number)
1813  */
1814 int CMergeDoc::RightLineInMovedBlock(int nBuffer, int apparentLeftLine)
1815 {
1816         if (!(m_ptBuf[nBuffer]->GetLineFlags(apparentLeftLine) & LF_MOVED))
1817                 return -1;
1818
1819         int realLeftLine = m_ptBuf[nBuffer]->ComputeRealLine(apparentLeftLine);
1820         int realRightLine = -1;
1821         if (m_diffWrapper.GetDetectMovedBlocks())
1822         {
1823                 realRightLine = m_diffWrapper.GetMovedLines(nBuffer)->LineInBlock(realLeftLine,
1824                                 MovedLines::SIDE::RIGHT);
1825         }
1826         if (realRightLine != -1)
1827                 return m_ptBuf[nBuffer + 1]->ComputeApparentLine(realRightLine);
1828         else
1829                 return -1;
1830 }
1831
1832 /**
1833  * @brief Get right->left info for a moved line (apparent line number)
1834  */
1835 int CMergeDoc::LeftLineInMovedBlock(int nBuffer, int apparentRightLine)
1836 {
1837         if (!(m_ptBuf[nBuffer]->GetLineFlags(apparentRightLine) & LF_MOVED))
1838                 return -1;
1839
1840         int realRightLine = m_ptBuf[nBuffer]->ComputeRealLine(apparentRightLine);
1841         int realLeftLine = -1;
1842         if (m_diffWrapper.GetDetectMovedBlocks())
1843         {
1844                 realLeftLine = m_diffWrapper.GetMovedLines(nBuffer)->LineInBlock(realRightLine,
1845                                 MovedLines::SIDE::LEFT);
1846         }
1847         if (realLeftLine != -1)
1848                 return m_ptBuf[nBuffer - 1]->ComputeApparentLine(realLeftLine);
1849         else
1850                 return -1;
1851 }
1852
1853 /**
1854  * @brief Save modified documents.
1855  * This function asks if user wants to save modified documents. We also
1856  * allow user to cancel the closing.
1857  *
1858  * There is a special trick avoiding showing two save-dialogs, as MFC framework
1859  * sometimes calls this function twice. We use static counter for these calls
1860  * and if we already have saving in progress (counter == 1) we skip the new
1861  * saving dialog.
1862  *
1863  * @return true if docs are closed, false if closing is cancelled.
1864  */
1865 BOOL CMergeDoc::SaveModified()
1866 {
1867         static int counter;
1868         ++counter;
1869         if (counter > 1)
1870                 return false;
1871
1872         if (PromptAndSaveIfNeeded(true))
1873         {
1874                 counter = 0;
1875                 return true;
1876         }
1877         else
1878         {
1879                 counter = 0;
1880                 return false;
1881         }
1882 }
1883
1884 /**
1885  * @brief Sets the current difference.
1886  * @param [in] nDiff Difference to set as current difference.
1887  */
1888 void CMergeDoc::SetCurrentDiff(int nDiff)
1889 {
1890         if (nDiff >= 0 && nDiff <= m_diffList.LastSignificantDiff())
1891                 m_nCurDiff = nDiff;
1892         else
1893                 m_nCurDiff = -1;
1894 }
1895
1896 /**
1897  * @brief Take care of rescanning document.
1898  * 
1899  * Update view and restore cursor and scroll position after
1900  * rescanning document.
1901  * @param [in] bForced If true rescan cannot be suppressed
1902  */
1903 void CMergeDoc::FlushAndRescan(bool bForced /* =false */)
1904 {
1905         // Ignore suppressing when forced rescan
1906         if (!bForced)
1907                 if (!m_bEnableRescan) return;
1908
1909         CWaitCursor waitstatus;
1910
1911         CMergeEditView *pActiveView = GetActiveMergeView();
1912
1913         // store cursors and hide caret
1914         ForEachView([](auto& pView) { pView->PushCursors(); });
1915         pActiveView->HideCursor();
1916
1917         bool bBinary = false;
1918         IDENTLEVEL identical = IDENTLEVEL::NONE;
1919         int nRescanResult = Rescan(bBinary, identical, bForced);
1920
1921         // restore cursors and caret
1922         ForEachView([](auto& pView) { pView->PopCursors(); });
1923         pActiveView->ShowCursor();
1924
1925         ForEachView(pActiveView->m_nThisPane, [](auto& pView) {
1926                 // because of ghostlines, m_nTopLine may differ just after Rescan
1927                 // scroll both views to the same top line
1928                 pView->UpdateSiblingScrollPos(false);
1929         });
1930         // make sure we see the cursor from the curent view
1931         pActiveView->EnsureVisible(pActiveView->GetCursorPos());
1932
1933         // Refresh display
1934         UpdateAllViews(nullptr);
1935
1936         // Show possible error after updating screen
1937         if (nRescanResult != RESCAN_SUPPRESSED)
1938                 ShowRescanError(nRescanResult, identical);
1939         m_LastRescan = COleDateTime::GetCurrentTime();
1940 }
1941
1942 /**
1943  * @brief Saves both files
1944  */
1945 void CMergeDoc::OnFileSave() 
1946 {
1947         // We will need to know if either of the originals actually changed
1948         // so we know whether to update the diff status
1949         bool bChangedOriginal = false;
1950
1951         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
1952         {
1953                 if (m_ptBuf[nBuffer]->IsModified() && !m_ptBuf[nBuffer]->GetReadOnly())
1954                 {
1955                         // (why we don't use return value of DoSave)
1956                         // DoSave will return true if it wrote to something successfully
1957                         // but we have to know if it overwrote the original file
1958                         bool bSaveOriginal = false;
1959                         DoSave(m_filePaths[nBuffer].c_str(), bSaveOriginal, nBuffer );
1960                         if (bSaveOriginal)
1961                                 bChangedOriginal = true;
1962                 }
1963         }
1964
1965         // If either of the actual source files being compared was changed
1966         // we need to update status in the dir view
1967         if (bChangedOriginal)
1968         {
1969                 // If DirDoc contains diffs
1970                 if (m_pDirDoc != nullptr && m_pDirDoc->HasDiffs())
1971                 {
1972                         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
1973                         {
1974                                 if (m_bEditAfterRescan[nBuffer])
1975                                 {
1976                                         FlushAndRescan(false);
1977                                         break;
1978                                 }
1979                         }
1980
1981                         bool bIdentical = !m_diffList.HasSignificantDiffs(); // True if status should be set to identical
1982                         m_pDirDoc->UpdateChangedItem(m_filePaths, m_diffList.GetSignificantDiffs(),
1983                                         m_nTrivialDiffs, bIdentical);
1984                 }
1985         }
1986 }
1987
1988 void CMergeDoc::DoFileSave(int nBuffer)
1989 {
1990         bool bSaveSuccess = false;
1991         bool bModified = false;
1992
1993         if (m_ptBuf[nBuffer]->IsModified() && !m_ptBuf[nBuffer]->GetReadOnly())
1994         {
1995                 bModified = true;
1996                 DoSave(m_filePaths[nBuffer].c_str(), bSaveSuccess, nBuffer );
1997         }
1998
1999         // If file were modified and saving succeeded,
2000         // update status on dir view
2001         if (bModified && bSaveSuccess)
2002         {
2003                 // If DirDoc contains compare results
2004                 if (m_pDirDoc != nullptr && m_pDirDoc->HasDiffs())
2005                 {
2006                         for (int nBuffer1 = 0; nBuffer1 < m_nBuffers; nBuffer1++)
2007                         {
2008                                 if (m_bEditAfterRescan[nBuffer1])
2009                                 {
2010                                         FlushAndRescan(false);
2011                                         break;
2012                                 }
2013                         }
2014
2015                         bool bIdentical = !m_diffList.HasSignificantDiffs(); // True if status should be set to identical
2016                         m_pDirDoc->UpdateChangedItem(m_filePaths, m_diffList.GetSignificantDiffs(),
2017                                         m_nTrivialDiffs, bIdentical);
2018                 }
2019         }
2020 }
2021
2022 /**
2023  * @brief Saves left-side file
2024  */
2025 void CMergeDoc::OnFileSaveLeft()
2026 {
2027         DoFileSave(0);
2028 }
2029
2030 /**
2031  * @brief Saves middle-side file
2032  */
2033 void CMergeDoc::OnFileSaveMiddle()
2034 {
2035         DoFileSave(1);
2036 }
2037
2038 /**
2039  * @brief Saves right-side file
2040  */
2041 void CMergeDoc::OnFileSaveRight()
2042 {
2043         DoFileSave(m_nBuffers - 1);
2044 }
2045
2046 /**
2047  * @brief Saves left-side file with name asked
2048  */
2049 void CMergeDoc::OnFileSaveAsLeft()
2050 {
2051         bool bSaveResult = false;
2052         DoSaveAs(m_filePaths.GetLeft().c_str(), bSaveResult, 0);
2053 }
2054
2055 /**
2056  * @brief Called when "Save middle (as...)" item is updated
2057  */
2058 void CMergeDoc::OnUpdateFileSaveAsMiddle(CCmdUI* pCmdUI)
2059 {
2060         pCmdUI->Enable(m_nBuffers == 3);
2061 }
2062
2063 /**
2064  * @brief Saves right-side file with name asked
2065  */
2066 void CMergeDoc::OnFileSaveAsMiddle()
2067 {
2068         bool bSaveResult = false;
2069         DoSaveAs(m_filePaths.GetMiddle().c_str(), bSaveResult, 1);
2070 }
2071
2072 /**
2073  * @brief Saves right-side file with name asked
2074  */
2075 void CMergeDoc::OnFileSaveAsRight()
2076 {
2077         bool bSaveResult = false;
2078         DoSaveAs(m_filePaths.GetRight().c_str(), bSaveResult, m_nBuffers - 1);
2079 }
2080
2081 /**
2082  * @brief Update diff-number pane text in file compare.
2083  * The diff number pane shows selected difference/amount of differences when
2084  * there is difference selected. If there is no difference selected, then
2085  * the panel shows amount of differences. If there are ignored differences,
2086  * those are not count into numbers.
2087  * @param [in] pCmdUI UI component to update.
2088  */
2089 void CMergeDoc::OnUpdateStatusNum(CCmdUI* pCmdUI) 
2090 {
2091         TCHAR sIdx[32] = { 0 };
2092         TCHAR sCnt[32] = { 0 };
2093         String s;
2094         const int nDiffs = m_diffList.GetSignificantDiffs();
2095         
2096         // Files are identical - show text "Identical"
2097         if (nDiffs <= 0)
2098                 s = _("Identical");
2099         
2100         // There are differences, but no selected diff
2101         // - show amount of diffs
2102         else if (GetCurrentDiff() < 0)
2103         {
2104                 s = nDiffs == 1 ? _("1 Difference Found") : _("%1 Differences Found");
2105                 _itot_s(nDiffs, sCnt, 10);
2106                 strutils::replace(s, _T("%1"), sCnt);
2107         }
2108         
2109         // There are differences and diff selected
2110         // - show diff number and amount of diffs
2111         else
2112         {
2113                 s = _("Difference %1 of %2");
2114                 const int signInd = m_diffList.GetSignificantIndex(GetCurrentDiff());
2115                 _itot_s(signInd + 1, sIdx, 10);
2116                 strutils::replace(s, _T("%1"), sIdx);
2117                 _itot_s(nDiffs, sCnt, 10);
2118                 strutils::replace(s, _T("%2"), sCnt);
2119         }
2120         pCmdUI->SetText(s.c_str());
2121 }
2122
2123 /**
2124  * @brief Update plugin name
2125  * @param [in] pCmdUI UI component to update.
2126  */
2127 void CMergeDoc::OnUpdatePluginName(CCmdUI* pCmdUI)
2128 {
2129         String pluginNames;
2130         if (m_pInfoUnpacker && !m_pInfoUnpacker->m_PluginName.empty())
2131                 pluginNames += m_pInfoUnpacker->m_PluginName + _T("&");
2132         PrediffingInfo prediffer;
2133         GetPrediffer(&prediffer);
2134         if (!prediffer.m_PluginName.empty())
2135                 pluginNames += prediffer.m_PluginName + _T("&");
2136         pCmdUI->SetText(pluginNames.substr(0, pluginNames.length() - 1).c_str());
2137 }
2138
2139 /**
2140  * @brief Change number of diff context lines
2141  */
2142 void CMergeDoc::OnDiffContext(UINT nID)
2143 {
2144         switch (nID)
2145         {
2146         case ID_VIEW_DIFFCONTEXT_0:
2147                 m_nDiffContext = 0; break;
2148         case ID_VIEW_DIFFCONTEXT_1:
2149                 m_nDiffContext = 1; break;
2150         case ID_VIEW_DIFFCONTEXT_3:
2151                 m_nDiffContext = 3; break;
2152         case ID_VIEW_DIFFCONTEXT_5:
2153                 m_nDiffContext = 5; break;
2154         case ID_VIEW_DIFFCONTEXT_7:
2155                 m_nDiffContext = 7; break;
2156         case ID_VIEW_DIFFCONTEXT_9:
2157                 m_nDiffContext = 9; break;
2158         case ID_VIEW_DIFFCONTEXT_TOGGLE:
2159                 m_nDiffContext = -m_nDiffContext - 1; break;
2160         case ID_VIEW_DIFFCONTEXT_ALL:
2161                 if (m_nDiffContext >= 0)
2162                         m_nDiffContext = -m_nDiffContext - 1;
2163                 break;
2164         case ID_VIEW_DIFFCONTEXT_INVERT:
2165                 m_bInvertDiffContext = !m_bInvertDiffContext;
2166                 break;
2167         }
2168         GetOptionsMgr()->SaveOption(OPT_DIFF_CONTEXT, m_nDiffContext);
2169         GetOptionsMgr()->SaveOption(OPT_INVERT_DIFF_CONTEXT, m_bInvertDiffContext);
2170         FlushAndRescan(true);
2171 }
2172
2173 /**
2174  * @brief Swap context enable for 3 file compares 
2175  */
2176 void CMergeDoc::OnUpdateSwapContext(CCmdUI* pCmdUI)
2177 {
2178         if (m_nBuffers > 2)
2179         {
2180                 pCmdUI->Enable(true);
2181         }
2182         else
2183         {
2184                 pCmdUI->Enable(false);
2185         }
2186 }
2187
2188 /**
2189  * @brief Update number of diff context lines
2190  */
2191 void CMergeDoc::OnUpdateDiffContext(CCmdUI* pCmdUI)
2192 {
2193         bool bCheck;
2194         switch (pCmdUI->m_nID)
2195         {
2196         case ID_VIEW_DIFFCONTEXT_0:
2197                 bCheck = (m_nDiffContext == 0); break;
2198         case ID_VIEW_DIFFCONTEXT_1:
2199                 bCheck = (m_nDiffContext == 1); break;
2200         case ID_VIEW_DIFFCONTEXT_3:
2201                 bCheck = (m_nDiffContext == 3); break;
2202         case ID_VIEW_DIFFCONTEXT_5:
2203                 bCheck = (m_nDiffContext == 5); break;
2204         case ID_VIEW_DIFFCONTEXT_7:
2205                 bCheck = (m_nDiffContext == 7); break;
2206         case ID_VIEW_DIFFCONTEXT_9:
2207                 bCheck = (m_nDiffContext == 9); break;
2208         case ID_VIEW_DIFFCONTEXT_TOGGLE:
2209                 bCheck = false; break;
2210         case ID_VIEW_DIFFCONTEXT_INVERT:
2211                 bCheck = m_bInvertDiffContext; break;
2212         default:
2213                 bCheck = (m_nDiffContext < 0); break;
2214         }
2215         pCmdUI->SetCheck(bCheck);
2216         pCmdUI->Enable(!(pCmdUI->m_nID == ID_VIEW_DIFFCONTEXT_INVERT && (m_nDiffContext < 0)));
2217 }
2218
2219 /**
2220  * @brief Build the diff array and prepare buffers accordingly (insert ghost lines, set WinMerge flags)
2221  *
2222  * @note After PrimeTextBuffers(), all buffers should have the same length.
2223  */
2224 void CMergeDoc::PrimeTextBuffers()
2225 {
2226         SetCurrentDiff(-1);
2227         m_nTrivialDiffs = 0;
2228         int nDiff;
2229         int nDiffCount = m_diffList.GetSize();
2230         int file;
2231
2232         // walk the diff list and calculate numbers of extra lines to add
2233         int extras[3] = {0, 0, 0};   // extra lines added to each view
2234         m_diffList.GetExtraLinesCounts(m_nBuffers, extras);
2235
2236         // resize m_aLines once for each view
2237         UINT lcount[3] = {0, 0, 0};
2238         UINT lcountnew[3] = {0, 0, 0};
2239         UINT lcountmax = 0;
2240         
2241         for (file = 0; file < m_nBuffers; file++)
2242         {
2243                 lcount[file] = m_ptBuf[file]->GetLineCount();
2244                 lcountnew[file] = lcount[file] + extras[file];
2245                 lcountmax = max(lcountmax, lcountnew[file]);
2246         }
2247         for (file = 0; file < m_nBuffers; file++)
2248         {
2249                 m_ptBuf[file]->m_aLines.resize(lcountmax);
2250         }
2251
2252         // walk the diff list backward, move existing lines to proper place,
2253         // add ghost lines, and set flags
2254         for (nDiff = nDiffCount - 1; nDiff >= 0; nDiff --)
2255         {
2256                 DIFFRANGE curDiff;
2257                 VERIFY(m_diffList.GetDiff(nDiff, curDiff));
2258
2259                 // move matched lines after curDiff
2260                 int nline[3] = { 0, 0, 0 };
2261                 for (file = 0; file < m_nBuffers; file++)
2262                         nline[file] = lcount[file] - curDiff.end[file] - 1; // #lines on left/middle/right after current diff
2263                 // Matched lines should really match...
2264                 // But matched lines after last diff may differ because of empty last line (see function's note)
2265                 if (nDiff < nDiffCount - 1)
2266                         ASSERT(nline[0] == nline[1]);
2267
2268                 int nmaxline = 0;
2269                 for (file = 0; file < m_nBuffers; file++)
2270                 {
2271                         // Move all lines after current diff down as far as needed
2272                         // for any ghost lines we're about to insert
2273                         m_ptBuf[file]->MoveLine(curDiff.end[file]+1, lcount[file]-1, lcountnew[file]-nline[file]);
2274                         lcountnew[file] -= nline[file];
2275                         lcount[file] -= nline[file];
2276                         // move unmatched lines and add ghost lines
2277                         nline[file] = curDiff.end[file] - curDiff.begin[file] + 1; // #lines in diff on left/middle/right
2278                         nmaxline = max(nmaxline, nline[file]);
2279                 }
2280
2281                 for (file = 0; file < m_nBuffers; file++)
2282                 {
2283                         DWORD dflag = LF_GHOST;
2284                         if ((file == 0 && curDiff.op == OP_3RDONLY) || (file == 2 && curDiff.op == OP_1STONLY))
2285                                 dflag |= LF_SNP;
2286                         m_ptBuf[file]->MoveLine(curDiff.begin[file], curDiff.end[file], lcountnew[file]-nmaxline);
2287                         int nextra = nmaxline - nline[file];
2288                         if (nextra > 0)
2289                         {
2290                                 m_ptBuf[file]->SetEmptyLine(lcountnew[file] - nextra, nextra);
2291                                 for (int i = 1; i <= nextra; i++)
2292                                         m_ptBuf[file]->SetLineFlag(lcountnew[file]-i, dflag, true, false, false);
2293                         }
2294                         lcountnew[file] -= nmaxline;
2295
2296                         lcount[file] -= nline[file];
2297
2298                 }
2299                 // set dbegin, dend, blank, and line flags
2300                 curDiff.dbegin = lcountnew[0];
2301
2302                 switch (curDiff.op)
2303                 {
2304                 case OP_TRIVIAL:
2305                         ++m_nTrivialDiffs;
2306                         [[fallthrough]];
2307                 case OP_DIFF:
2308                 case OP_1STONLY:
2309                 case OP_2NDONLY:
2310                 case OP_3RDONLY:
2311                         // set curdiff
2312                         {
2313                                 curDiff.dend = lcountnew[0]+nmaxline-1;
2314                                 for (file = 0; file < m_nBuffers; file++)
2315                                 {
2316                                         curDiff.blank[file] = -1;
2317                                         int nextra = nmaxline - nline[file];
2318                                         if (nmaxline > nline[file])
2319                                         {
2320                                                 // more lines on left, ghost lines on right side
2321                                                 curDiff.blank[file] = curDiff.dend + 1 - nextra;
2322                                         }
2323                                 }
2324                         }
2325                         // flag lines
2326                         {
2327                                 for (file = 0; file < m_nBuffers; file++)
2328                                 {
2329                                         // left side
2330                                         int i;
2331                                         for (i = curDiff.dbegin; i <= curDiff.dend; i++)
2332                                         {
2333                                                 if (curDiff.blank[file] == -1 || (int)i < curDiff.blank[file])
2334                                                 {
2335                                                         // set diff or trivial flag
2336                                                         DWORD dflag = (curDiff.op == OP_TRIVIAL) ? LF_TRIVIAL : LF_DIFF;
2337                                                         if ((file == 0 && curDiff.op == OP_3RDONLY) || (file == 2 && curDiff.op == OP_1STONLY))
2338                                                                 dflag |= LF_SNP;
2339                                                         m_ptBuf[file]->SetLineFlag(i, dflag, true, false, false);
2340                                                         m_ptBuf[file]->SetLineFlag(i, LF_INVISIBLE, false, false, false);
2341                                                 }
2342                                                 else
2343                                                 {
2344                                                         // ghost lines are already inserted (and flagged)
2345                                                         // ghost lines opposite to trivial lines are ghost and trivial
2346                                                         if (curDiff.op == OP_TRIVIAL)
2347                                                                 m_ptBuf[file]->SetLineFlag(i, LF_TRIVIAL, true, false, false);
2348                                                 }
2349                                         }
2350                                 }
2351                         }
2352                         break;
2353                 }           // switch (curDiff.op)
2354                 VERIFY(m_diffList.SetDiff(nDiff, curDiff));
2355         }             // for (nDiff = nDiffCount; nDiff-- > 0; )
2356
2357         m_diffList.ConstructSignificantChain();
2358
2359 #ifdef _DEBUG
2360         // Note: By this point all `m_ptBuf[]` buffers must have the same  
2361         //              number of line entries; eventual buffer processing typically
2362         //              uses the line count from `m_ptBuf[0]` for all buffer processing.
2363
2364         for (file = 0; file < m_nBuffers; file++)
2365         {
2366                 ASSERT(m_ptBuf[0]->GetLineCount() == m_ptBuf[file]->GetLineCount());
2367         }
2368 #endif
2369
2370         for (file = 0; file < m_nBuffers; file++)
2371                 m_ptBuf[file]->FinishLoading();
2372 }
2373
2374 /**
2375  * @brief Checks if file has changed since last update (save or rescan).
2376  * @param [in] szPath File to check
2377  * @param [in] dfi Previous fileinfo of file
2378  * @param [in] bSave If true Compare to last save-info, else to rescan-info
2379  * @param [in] nBuffer Index (0-based) of buffer
2380  * @return true if file is changed.
2381  */
2382 CMergeDoc::FileChange CMergeDoc::IsFileChangedOnDisk(LPCTSTR szPath, DiffFileInfo &dfi,
2383         bool bSave, int nBuffer)
2384 {
2385         DiffFileInfo *fileInfo = nullptr;
2386         bool bFileChanged = false;
2387         bool bIgnoreSmallDiff = GetOptionsMgr()->GetBool(OPT_IGNORE_SMALL_FILETIME);
2388         int tolerance = 0;
2389         if (bIgnoreSmallDiff)
2390                 tolerance = SmallTimeDiff; // From MainFrm.h
2391
2392         if (bSave)
2393                 fileInfo = m_pSaveFileInfo[nBuffer].get();
2394         else
2395                 fileInfo = m_pRescanFileInfo[nBuffer].get();
2396
2397         // We assume file existed, so disappearing means removal
2398         if (!dfi.Update(szPath))
2399                 return FileChange::Removed;
2400
2401         int64_t timeDiff = dfi.mtime - fileInfo->mtime;
2402         if (timeDiff < 0) timeDiff = -timeDiff;
2403         if ((timeDiff > tolerance * Poco::Timestamp::resolution()) || (dfi.size != fileInfo->size))
2404         {
2405                 bFileChanged = true;
2406         }
2407
2408         if (bFileChanged)
2409                 return FileChange::Changed;
2410         else
2411                 return FileChange::NoChange;
2412 }
2413
2414 void CMergeDoc::HideLines()
2415 {
2416         int nLine;
2417         int file;
2418
2419         if (m_nDiffContext < 0)
2420         {
2421                 ForEachView([](auto& pView) { pView->SetEnableHideLines(false); });
2422                 return;
2423         }
2424
2425         int nLineCount = 0x7fffffff;
2426         for (file = 0; file < m_nBuffers; file++)
2427         {
2428                 if (nLineCount > m_ptBuf[file]->GetLineCount())
2429                         nLineCount = m_ptBuf[file]->GetLineCount();
2430         }
2431
2432         for (nLine =  0; nLine < nLineCount;)
2433         {
2434                 bool diff = !!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST));
2435                 if ((!m_bInvertDiffContext && !diff) || (m_bInvertDiffContext && diff))
2436                 {
2437                         for (file = 0; file < m_nBuffers; file++)
2438                                 m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, true, false, false);
2439                         nLine++;
2440                 }
2441                 else
2442                 {
2443                         int nLine2 = (nLine - m_nDiffContext < 0) ? 0 : (nLine - m_nDiffContext);
2444                         for (; nLine2 < nLine; nLine2++)
2445                         {
2446                                 for (file = 0; file < m_nBuffers; file++)
2447                                         m_ptBuf[file]->SetLineFlag(nLine2, LF_INVISIBLE, false, false, false);
2448                         }
2449                 
2450                         for (; nLine < nLineCount; nLine++)
2451                         {
2452                                 diff = !!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST));
2453                                 if ((!m_bInvertDiffContext && !diff) || (m_bInvertDiffContext && diff))
2454                                         break;
2455                                 for (file = 0; file < m_nBuffers; file++)
2456                                         m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, false, false, false);
2457                         }
2458
2459                         int nLineEnd2 = (nLine + m_nDiffContext >= nLineCount) ? nLineCount-1 : (nLine + m_nDiffContext);
2460                         for (; nLine < nLineEnd2; nLine++)
2461                         {
2462                                 for (file = 0; file < m_nBuffers; file++)
2463                                         m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, false, false, false);
2464                                 diff = !!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST));
2465                                 if ((!m_bInvertDiffContext && diff) || (m_bInvertDiffContext && !diff))
2466                                         nLineEnd2 = (nLine + 1 + m_nDiffContext >= nLineCount) ? nLineCount-1 : (nLine + 1 + m_nDiffContext);
2467                         }
2468                 }
2469         }
2470
2471         ForEachView([](auto& pView) { pView->SetEnableHideLines(true); });
2472 }
2473
2474 /**
2475  * @brief Asks and then saves modified files.
2476  *
2477  * This function saves modified files. Dialog is shown for user to select
2478  * modified file(s) one wants to save or discard changed. Cancelling of
2479  * save operation is allowed unless denied by parameter. After successfully
2480  * save operation file statuses are updated to directory compare.
2481  * @param [in] bAllowCancel If false "Cancel" button is disabled.
2482  * @return true if user selected "OK" so next operation can be
2483  * executed. If false user choosed "Cancel".
2484  * @note If filename is empty, we assume scratchpads are saved,
2485  * so instead of filename, description is shown.
2486  * @todo If we have filename and description for file, what should
2487  * we do after saving to different filename? Empty description?
2488  * @todo Parameter @p bAllowCancel is always true in callers - can be removed.
2489  */
2490 bool CMergeDoc::PromptAndSaveIfNeeded(bool bAllowCancel)
2491 {
2492         bool bLModified = false, bMModified = false, bRModified = false;
2493         bool result = true;
2494         bool bLSaveSuccess = false, bMSaveSuccess = false, bRSaveSuccess = false;
2495
2496         if (m_nBuffers == 3)
2497         {
2498                 bLModified = m_ptBuf[0]->IsModified();
2499                 bMModified = m_ptBuf[1]->IsModified();
2500                 bRModified = m_ptBuf[2]->IsModified();
2501         }
2502         else
2503         {
2504                 bLModified = m_ptBuf[0]->IsModified();
2505                 bRModified = m_ptBuf[1]->IsModified();
2506         }
2507         if (!bLModified && !bMModified && !bRModified)
2508                  return true;
2509
2510         SaveClosingDlg dlg;
2511         dlg.DoAskFor(bLModified, bMModified, bRModified);
2512         if (!bAllowCancel)
2513                 dlg.m_bDisableCancel = true;
2514         if (!m_filePaths.GetLeft().empty())
2515         {
2516                 if (theApp.m_strSaveAsPath.empty())
2517                         dlg.m_sLeftFile = m_filePaths.GetLeft();
2518                 else
2519                         dlg.m_sLeftFile = theApp.m_strSaveAsPath;
2520         }
2521         else
2522                 dlg.m_sLeftFile = m_strDesc[0];
2523         if (m_nBuffers == 3)
2524         {
2525                 if (!m_filePaths.GetMiddle().empty())
2526                 {
2527                         if (theApp.m_strSaveAsPath.empty())
2528                                 dlg.m_sMiddleFile = m_filePaths.GetMiddle();
2529                         else
2530                                 dlg.m_sMiddleFile = theApp.m_strSaveAsPath;
2531                 }
2532                 else
2533                         dlg.m_sMiddleFile = m_strDesc[1];
2534         }
2535         if (!m_filePaths.GetRight().empty())
2536         {
2537                 if (theApp.m_strSaveAsPath.empty())
2538                         dlg.m_sRightFile = m_filePaths.GetRight();
2539                 else
2540                         dlg.m_sRightFile = theApp.m_strSaveAsPath;
2541         }
2542         else
2543                 dlg.m_sRightFile = m_strDesc[m_nBuffers - 1];
2544
2545         if (dlg.DoModal() == IDOK)
2546         {
2547                 if (bLModified && dlg.m_leftSave == SaveClosingDlg::SAVECLOSING_SAVE)
2548                 {
2549                         if (!DoSave(m_filePaths.GetLeft().c_str(), bLSaveSuccess, 0))
2550                                 result = false;
2551                 }
2552
2553                 if (bMModified && dlg.m_middleSave == SaveClosingDlg::SAVECLOSING_SAVE)
2554                 {
2555                         if (!DoSave(m_filePaths.GetMiddle().c_str(), bMSaveSuccess, 1))
2556                                 result = false;
2557                 }
2558
2559                 if (bRModified && dlg.m_rightSave == SaveClosingDlg::SAVECLOSING_SAVE)
2560                 {
2561                         if (!DoSave(m_filePaths.GetRight().c_str(), bRSaveSuccess, m_nBuffers - 1))
2562                                 result = false;
2563                 }
2564         }
2565         else
2566         {       
2567                 result = false;
2568         }
2569
2570         // If file were modified and saving was successfull,
2571         // update status on dir view
2572         if ((bLModified && bLSaveSuccess) || 
2573              (bMModified && bMSaveSuccess) ||
2574                  (bRModified && bRSaveSuccess))
2575         {
2576                 // If directory compare has results
2577                 if (m_pDirDoc != nullptr && m_pDirDoc->HasDiffs())
2578                 {
2579                         if (m_bEditAfterRescan[0] || m_bEditAfterRescan[1] || (m_nBuffers == 3 && m_bEditAfterRescan[2]))
2580                                 FlushAndRescan(false);
2581
2582                         bool bIdentical = !m_diffList.HasSignificantDiffs(); // True if status should be set to identical
2583                         m_pDirDoc->UpdateChangedItem(m_filePaths, m_diffList.GetSignificantDiffs(),
2584                                         m_nTrivialDiffs, bIdentical);
2585                 }
2586         }
2587
2588         return result;
2589 }
2590
2591 /** Rescan only if we did not Rescan during the last timeOutInSecond seconds*/
2592 void CMergeDoc::RescanIfNeeded(float timeOutInSecond)
2593 {
2594         // if we did not rescan during the request timeOut, Rescan
2595         // else we did Rescan after the request, so do nothing
2596         COleDateTimeSpan elapsed = COleDateTime::GetCurrentTime() - m_LastRescan;
2597         if (elapsed.GetTotalSeconds() >= timeOutInSecond)
2598                 // (laoran 08-01-2003) maybe should be FlushAndRescan(true) ??
2599                 FlushAndRescan();
2600 }
2601
2602 /**
2603  * @brief We have two child views (left & right), so we keep pointers directly
2604  * at them (the MFC view list doesn't have them both)
2605  */
2606 void CMergeDoc::AddMergeViews(CMergeEditView *pView[3])
2607 {
2608
2609         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2610                 m_pView[m_nGroups][nBuffer] = pView[nBuffer];
2611         ++m_nGroups;
2612 }
2613
2614 void CMergeDoc::RemoveMergeViews(int nGroup)
2615 {
2616
2617         for (; nGroup < m_nGroups - 1; nGroup++)
2618         {
2619                 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2620                 {
2621                         m_pView[nGroup][nBuffer] = m_pView[nGroup + 1][nBuffer];
2622                         m_pView[nGroup][nBuffer]->m_nThisGroup = nGroup;
2623                 }
2624         }
2625         --m_nGroups;
2626 }
2627
2628 /**
2629  * @brief DirDoc gives us its identity just after it creates us
2630  */
2631 void CMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
2632 {
2633         ASSERT(pDirDoc != nullptr && m_pDirDoc == nullptr);
2634         m_pDirDoc = pDirDoc;
2635 }
2636
2637 /**
2638  * @brief Return pointer to parent frame
2639  */
2640 CMergeEditFrame * CMergeDoc::GetParentFrame() 
2641 {
2642         return dynamic_cast<CMergeEditFrame *>(m_pView[0][0]->GetParentFrame()); 
2643 }
2644
2645 /**
2646  * @brief DirDoc is closing
2647  */
2648 void CMergeDoc::DirDocClosing(CDirDoc * pDirDoc)
2649 {
2650         ASSERT(m_pDirDoc == pDirDoc);
2651         m_pDirDoc = nullptr;
2652         // TODO (Perry 2003-03-30): perhaps merge doc should close now ?
2653 }
2654
2655 /**
2656  * @brief DirDoc commanding us to close
2657  */
2658 bool CMergeDoc::CloseNow()
2659 {
2660         // Allow user to cancel closing
2661         if (!PromptAndSaveIfNeeded(true))
2662                 return false;
2663
2664         GetParentFrame()->CloseNow();
2665         return true;
2666 }
2667
2668 /**
2669  * @brief Loads file to buffer and shows load-errors
2670  * @param [in] sFileName File to open
2671  * @param [in] nBuffer Index (0-based) of buffer to load
2672  * @param [out] readOnly whether file is read-only
2673  * @param [in] encoding encoding used
2674  * @return Tells if files were loaded successfully
2675  * @sa CMergeDoc::OpenDocs()
2676  **/
2677 int CMergeDoc::LoadFile(CString sFileName, int nBuffer, bool & readOnly, const FileTextEncoding & encoding)
2678 {
2679         String sError;
2680         DWORD retVal = FileLoadResult::FRESULT_ERROR;
2681
2682         CDiffTextBuffer *pBuf = m_ptBuf[nBuffer].get();
2683         m_filePaths[nBuffer] = sFileName;
2684
2685         CRLFSTYLE nCrlfStyle = CRLFSTYLE::AUTOMATIC;
2686         CString sOpenError;
2687         retVal = pBuf->LoadFromFile(sFileName, m_pInfoUnpacker.get(),
2688                 m_strBothFilenames.c_str(), readOnly, nCrlfStyle, encoding, sOpenError);
2689
2690         // if CMergeDoc::CDiffTextBuffer::LoadFromFile failed,
2691         // it left the pBuf in a valid (but empty) state via a call to InitNew
2692
2693         if (FileLoadResult::IsOkImpure(retVal))
2694         {
2695                 // File loaded, and multiple EOL types in this file
2696                 FileLoadResult::SetMainOk(retVal);
2697
2698                 // If mixed EOLs are not enabled, enable them for this doc.
2699                 if (!GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL))
2700                 {
2701                         pBuf->SetMixedEOL(true);
2702                 }
2703         }
2704
2705         if (FileLoadResult::IsError(retVal))
2706         {
2707                 // Error from Unifile/system
2708                 if (!sOpenError.IsEmpty())
2709                         sError = strutils::format_string2(_("Cannot open file\n%1\n\n%2"), (LPCTSTR)sFileName, (LPCTSTR)sOpenError);
2710                 else
2711                         sError = strutils::format_string1(_("File not found: %1"), (LPCTSTR)sFileName);
2712                 ShowMessageBox(sError, MB_OK | MB_ICONSTOP | MB_MODELESS);
2713         }
2714         else if (FileLoadResult::IsErrorUnpack(retVal))
2715         {
2716                 sError = strutils::format_string1(_("File not unpacked: %1"), (LPCTSTR)sFileName);
2717                 ShowMessageBox(sError, MB_OK | MB_ICONSTOP | MB_MODELESS);
2718         }
2719         return retVal;
2720 }
2721
2722 /**
2723  * @brief Check if specified codepage number is valid for WinMerge Editor.
2724  * @param [in] cp Codepage number to check.
2725  * @return true if codepage is valid, false otherwise.
2726  */
2727 bool CMergeDoc::IsValidCodepageForMergeEditor(unsigned cp) const
2728 {
2729         if (cp == 0) // 0 is our signal value for invalid
2730                 return false;
2731         return GetEncodingNameFromCodePage(cp) != nullptr;
2732 }
2733
2734 /**
2735  * @brief Sanity check file's specified codepage.
2736  * This function checks if file's specified codepage is valid for WinMerge
2737  * editor and if not resets the codepage to default.
2738  * @param [in,out] fileinfo Class containing file's codepage.
2739  */
2740 void CMergeDoc::SanityCheckCodepage(FileLocation & fileinfo)
2741 {
2742         if (fileinfo.encoding.m_unicoding == ucr::NONE
2743                 && !IsValidCodepageForMergeEditor(fileinfo.encoding.m_codepage))
2744         {
2745                 int cp = ucr::getDefaultCodepage();
2746                 if (!IsValidCodepageForMergeEditor(cp))
2747                         cp = CP_ACP;
2748                 fileinfo.encoding.SetCodepage(cp);
2749         }
2750 }
2751
2752 /**
2753  * @brief Loads one file from disk and updates file infos.
2754  * @param [in] index Index of file in internal buffers.
2755  * @param [in] filename File's name.
2756  * @param [in] readOnly Is file read-only?
2757  * @param [in] encoding File's encoding.
2758  * @return One of FileLoadResult values.
2759  */
2760 DWORD CMergeDoc::LoadOneFile(int index, String filename, bool readOnly, const String& strDesc, 
2761                 const FileTextEncoding & encoding)
2762 {
2763         DWORD loadSuccess = FileLoadResult::FRESULT_ERROR;;
2764         
2765         m_strDesc[index] = strDesc;
2766         if (!filename.empty())
2767         {
2768                 if (strDesc.empty())
2769                         m_nBufferType[index] = BUFFERTYPE::NORMAL;
2770                 else
2771                         m_nBufferType[index] = BUFFERTYPE::NORMAL_NAMED;
2772                 m_pSaveFileInfo[index]->Update(filename);
2773                 m_pRescanFileInfo[index]->Update(filename);
2774
2775                 loadSuccess = LoadFile(filename.c_str(), index, readOnly, encoding);
2776                 if (FileLoadResult::IsLossy(loadSuccess))
2777                 {
2778                         // Determine the file encoding by looking at all the contents of the file, not just part of it
2779                         FileTextEncoding encodingNew = codepage_detect::Guess(filename, GetOptionsMgr()->GetInt(OPT_CP_DETECT), -1);
2780                         if (encoding != encodingNew)
2781                         {
2782                                 m_ptBuf[index]->FreeAll();
2783                                 loadSuccess = LoadFile(filename.c_str(), index, readOnly, encodingNew);
2784                         }
2785                 }
2786         }
2787         else
2788         {
2789                 m_nBufferType[index] = BUFFERTYPE::UNNAMED;
2790                 m_ptBuf[index]->InitNew();
2791                 m_ptBuf[index]->m_encoding = encoding;
2792                 m_ptBuf[index]->FinishLoading(); // should clear GGhostTextBuffer::m_RealityBlock when reloading unnamed buffer 
2793                 loadSuccess = FileLoadResult::FRESULT_OK;
2794         }
2795         return loadSuccess;
2796 }
2797
2798 void CMergeDoc::SetTableProperties()
2799 {
2800         struct TableProps { bool istable; TCHAR delimiter; TCHAR quote; bool allowNewlinesInQuotes; };
2801         auto getTablePropsByFileName = [](const String& path, const std::optional<bool>& enableTableEditing)-> TableProps
2802         {
2803                 const TCHAR quote = GetOptionsMgr()->GetString(OPT_CMP_TBL_QUOTE_CHAR).c_str()[0];
2804                 FileFilterHelper filterCSV, filterTSV, filterDSV;
2805                 bool allowNewlineIQuotes = GetOptionsMgr()->GetBool(OPT_CMP_TBL_ALLOW_NEWLINES_IN_QUOTES);
2806                 const String csvFilePattern = GetOptionsMgr()->GetString(OPT_CMP_CSV_FILEPATTERNS);
2807                 if (!csvFilePattern.empty())
2808                 {
2809                         filterCSV.UseMask(true);
2810                         filterCSV.SetMask(csvFilePattern);
2811                         if (filterCSV.includeFile(path))
2812                                 return { true, ',', quote, allowNewlineIQuotes };
2813                 }
2814                 const String tsvFilePattern = GetOptionsMgr()->GetString(OPT_CMP_TSV_FILEPATTERNS);
2815                 if (!tsvFilePattern.empty())
2816                 {
2817                         filterTSV.UseMask(true);
2818                         filterTSV.SetMask(tsvFilePattern);
2819                         if (filterTSV.includeFile(path))
2820                                 return { true, '\t', quote, allowNewlineIQuotes };
2821                 }
2822                 const String dsvFilePattern = GetOptionsMgr()->GetString(OPT_CMP_DSV_FILEPATTERNS);
2823                 if (!dsvFilePattern.empty())
2824                 {
2825                         filterDSV.UseMask(true);
2826                         filterDSV.SetMask(dsvFilePattern);
2827                         if (filterDSV.includeFile(path))
2828                                 return { true, GetOptionsMgr()->GetString(OPT_CMP_DSV_DELIM_CHAR).c_str()[0], quote };
2829                 }
2830                 if (enableTableEditing.value_or(false))
2831                 {
2832                         COpenTableDlg dlg;
2833                         if (dlg.DoModal() == IDOK)
2834                                 return { true, dlg.m_sDelimiterChar.c_str()[0], dlg.m_sQuoteChar.c_str()[0], dlg.m_bAllowNewlinesInQuotes };
2835                 }
2836                 return { false, 0, 0, false };
2837         };
2838
2839         TableProps tableProps[3] = {};
2840         int nTableFileIndex = -1;
2841         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2842         {
2843                 if (nBuffer == 0 ||
2844                         paths::FindExtension(m_ptBuf[nBuffer - 1]->GetTempFileName()) != paths::FindExtension(m_ptBuf[nBuffer]->GetTempFileName()))
2845                         tableProps[nBuffer] = getTablePropsByFileName(m_ptBuf[nBuffer]->GetTempFileName(), m_bEnableTableEditing);
2846                 else
2847                         tableProps[nBuffer] = tableProps[nBuffer - 1];
2848                 if (tableProps[nBuffer].istable)
2849                         nTableFileIndex = nBuffer;
2850         }
2851         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2852         {
2853                 if (m_bEnableTableEditing.value_or(true) && nTableFileIndex >= 0)
2854                 {
2855                         int i = tableProps[nBuffer].istable ? nBuffer : nTableFileIndex;
2856                         m_ptBuf[nBuffer]->SetTableEditing(true);
2857                         m_ptBuf[nBuffer]->ShareColumnWidths(*m_ptBuf[0]);
2858                         m_ptBuf[nBuffer]->SetAllowNewlinesInQuotes(tableProps[i].allowNewlinesInQuotes);
2859                         m_ptBuf[nBuffer]->SetFieldDelimiter(tableProps[i].delimiter);
2860                         m_ptBuf[nBuffer]->SetFieldEnclosure(tableProps[i].quote);
2861                         m_ptBuf[nBuffer]->JoinLinesForTableEditingMode();
2862                 }
2863                 else
2864                 {
2865                         m_ptBuf[nBuffer]->SetTableEditing(false);
2866                 }
2867         }
2868 }
2869
2870 /**
2871  * @brief Loads files and does initial rescan.
2872  * @param fileloc [in] File to open to left/middle/right side (path & encoding info)
2873  * @param bRO [in] Is left/middle/right file read-only
2874  * @return Success/Failure/Binary (failure) per typedef enum OpenDocsResult_TYPE
2875  * @todo Options are still read from CMainFrame, this will change
2876  * @sa CMainFrame::ShowTextMergeDoc()
2877  */
2878 bool CMergeDoc::OpenDocs(int nFiles, const FileLocation ifileloc[],
2879                 const bool bRO[], const String strDesc[])
2880 {
2881         IDENTLEVEL identical = IDENTLEVEL::NONE;
2882         int nRescanResult = RESCAN_OK;
2883         int nBuffer;
2884         FileLocation fileloc[3];
2885
2886         std::copy_n(ifileloc, 3, fileloc);
2887
2888         // Filter out invalid codepages, or editor will display all blank
2889         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2890                 SanityCheckCodepage(fileloc[nBuffer]);
2891
2892         // clear undo stack
2893         undoTgt.clear();
2894         curUndo = undoTgt.begin();
2895
2896         // Prevent displaying views during LoadFile
2897         // Note : attach buffer again only if both loads succeed
2898         m_strBothFilenames.erase();
2899
2900         ForEachView([](auto& pView) { pView->DetachFromBuffer(); });
2901
2902         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2903         {
2904                 // clear undo buffers
2905                 m_ptBuf[nBuffer]->m_aUndoBuf.clear();
2906
2907                 // free the buffers
2908                 m_ptBuf[nBuffer]->FreeAll ();
2909
2910                 // build the text being filtered, "|" separates files as it is forbidden in filenames
2911                 m_strBothFilenames += fileloc[nBuffer].filepath + _T("|");
2912         }
2913         m_strBothFilenames.erase(m_strBothFilenames.length() - 1);
2914
2915         // Load files
2916         DWORD nSuccess[3] = { FileLoadResult::FRESULT_ERROR,  FileLoadResult::FRESULT_ERROR,  FileLoadResult::FRESULT_ERROR };
2917         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2918         {
2919                 nSuccess[nBuffer] = LoadOneFile(nBuffer, fileloc[nBuffer].filepath, bRO[nBuffer], strDesc ? strDesc[nBuffer] : _T(""),
2920                         fileloc[nBuffer].encoding);
2921                 if (!FileLoadResult::IsOk(nSuccess[nBuffer]))
2922                 {
2923                         CMergeEditFrame* pFrame = GetParentFrame();
2924                         if (pFrame != nullptr)
2925                         {
2926                                 // Use verify macro to trap possible error in debug.
2927                                 VERIFY(pFrame->DestroyWindow());
2928                         }
2929                         return false;
2930                 }
2931         }
2932
2933         SetTableProperties();
2934
2935         const bool bFiltersEnabled = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED);
2936
2937         // scratchpad : we don't call LoadFile, so
2938         // we need to initialize the unpacker as a "do nothing" one
2939         if (bFiltersEnabled)
2940         { 
2941                 if (std::count(m_nBufferType, m_nBufferType + m_nBuffers, BUFFERTYPE::UNNAMED) == m_nBuffers)
2942                 {
2943                         m_pInfoUnpacker->Initialize(PLUGIN_MODE::PLUGIN_MANUAL);
2944                 }
2945         }
2946
2947         // Warn user if file load was lossy (bad encoding)
2948         int idres=0;
2949         int nLossyBuffers = 0;
2950         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2951         {
2952                 if (FileLoadResult::IsLossy(nSuccess[nBuffer]))
2953                 {
2954                         // TODO: It would be nice to report how many lines were lossy
2955                         // we did calculate those numbers when we loaded the files, in the text stats
2956         
2957                         idres = IDS_LOSSY_TRANSCODING_FIRST + nBuffer;
2958                         nLossyBuffers++;
2959                 }
2960         }
2961         if (nLossyBuffers > 1)
2962                 idres = IDS_LOSSY_TRANSCODING_BOTH; /* FIXEME */
2963         
2964         if (nLossyBuffers > 0)
2965         {
2966                 if (m_pEncodingErrorBar == nullptr)
2967                 {
2968                         m_pEncodingErrorBar.reset(new CEncodingErrorBar());
2969                         m_pEncodingErrorBar->Create(this->m_pView[0][0]->GetParentFrame());
2970                 }
2971                 m_pEncodingErrorBar->SetText(LoadResString(idres));
2972                 m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), TRUE, FALSE);
2973         }
2974
2975         ForEachView([](auto& pView) {
2976                 // Now buffers data are valid
2977                 pView->AttachToBuffer();
2978                 // Currently there is only one set of syntax colors, which all documents & views share
2979                 pView->SetColorContext(theApp.GetMainSyntaxColors());
2980                 // Currently there is only one set of markers, which all documents & views share
2981                 pView->SetMarkersContext(theApp.GetMainMarkers());
2982         });
2983         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
2984         {
2985                 // Set read-only statuses
2986                 m_ptBuf[nBuffer]->SetReadOnly(bRO[nBuffer]);
2987         }
2988
2989         // Check the EOL sensitivity option (do it before Rescan)
2990         DIFFOPTIONS diffOptions = {0};
2991         m_diffWrapper.GetOptions(&diffOptions);
2992         if (!GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) && !diffOptions.bIgnoreEol)
2993         {
2994                 for (nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
2995                         if (m_ptBuf[0]->GetCRLFMode() != m_ptBuf[nBuffer]->GetCRLFMode())
2996                                 break;
2997
2998                 if (nBuffer < m_nBuffers)
2999                 {
3000                         // Options and files not are not compatible :
3001                         // Sensitive to EOL on, allow mixing EOL off, and files have a different EOL style.
3002                         // All lines will differ, that is not very interesting and probably not wanted.
3003                         // Propose to turn off the option 'sensitive to EOL'
3004                         String s = theApp.LoadString(IDS_SUGGEST_IGNOREEOL);
3005                         if (ShowMessageBox(s, MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_SUGGEST_IGNOREEOL) == IDYES)
3006                         {
3007                                 diffOptions.bIgnoreEol = true;
3008                                 m_diffWrapper.SetOptions(&diffOptions);
3009
3010                                 CMessageBoxDialog dlg(nullptr, s.c_str(), _T(""), 0, IDS_SUGGEST_IGNOREEOL);
3011                                 const int nFormerResult = dlg.GetFormerResult();
3012                                 if (nFormerResult != -1)
3013                                 {
3014                                         // "Don't ask this question again" checkbox is checked
3015                                         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, true);
3016                                 }
3017                         }
3018                 }
3019         }
3020
3021         // Define the prediffer
3022         PackingInfo * infoUnpacker = nullptr;
3023         PrediffingInfo * infoPrediffer = nullptr;
3024         if (bFiltersEnabled && m_pDirDoc != nullptr)
3025         {
3026                 m_pDirDoc->GetPluginManager().FetchPluginInfos(m_strBothFilenames, &infoUnpacker, &infoPrediffer);
3027                 m_diffWrapper.SetPrediffer(infoPrediffer);
3028                 m_diffWrapper.SetTextForAutomaticPrediff(m_strBothFilenames);
3029         }
3030
3031         bool bBinary = false;
3032         nRescanResult = Rescan(bBinary, identical);
3033
3034         // Open filed if rescan succeed and files are not binaries
3035         if (nRescanResult == RESCAN_OK)
3036         {
3037                 // set the document types
3038                 // Warning : it is the first thing to do (must be done before UpdateView,
3039                 // or any function that calls UpdateView, like SelectDiff)
3040                 // Note: If option enabled, and another side type is not recognized,
3041                 // we use recognized type for unrecognized side too.
3042                 String sext[3];
3043                 bool bTyped[3];
3044                 int paneTyped = 0;
3045
3046                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3047                 {
3048                         if (bFiltersEnabled && m_pInfoUnpacker->m_textType.length())
3049                                 sext[nBuffer] = m_pInfoUnpacker->m_textType;
3050                         else
3051                                 sext[nBuffer] = GetFileExt(fileloc[nBuffer].filepath.c_str(), m_strDesc[nBuffer].c_str());
3052                         ForEachView(nBuffer, [&](auto& pView) {
3053                                 bTyped[nBuffer] = pView->SetTextType(sext[nBuffer].c_str());
3054                                 if (bTyped[nBuffer])
3055                                         paneTyped = nBuffer;
3056                         });
3057                 }
3058
3059                 for (nBuffer = 1; nBuffer < m_nBuffers; nBuffer++)
3060                 {
3061                         if (bTyped[0] != bTyped[nBuffer])
3062                                 break;
3063                 }
3064
3065                 bool syntaxHLEnabled = GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT);
3066                 if (syntaxHLEnabled && nBuffer < m_nBuffers)
3067                 {
3068                         if (std::count(bTyped, bTyped + m_nBuffers, false) == m_nBuffers)
3069                         {
3070                                 CString sFirstLine;
3071                                 m_ptBuf[0]->GetLine(0, sFirstLine);
3072                                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3073                                 {
3074                                         bTyped[nBuffer] = GetView(0, nBuffer)->SetTextTypeByContent(sFirstLine);
3075                                 }
3076                         }
3077                 }
3078
3079                 if (syntaxHLEnabled)
3080                 {
3081                         CrystalLineParser::TextDefinition *enuType = CrystalLineParser::GetTextType(sext[paneTyped].c_str());
3082                         ForEachView([&bTyped, enuType](auto& pView) {
3083                                 if (!bTyped[pView->m_nThisPane])
3084                                         pView->SetTextType(enuType);
3085                         });
3086                 }
3087
3088                 int nNormalBuffer = 0;
3089                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3090                 {
3091                         // set the frame window header
3092                         UpdateHeaderPath(nBuffer);
3093
3094                         ForEachView(nBuffer, [](auto& pView) { pView->DocumentsLoaded(); });
3095                         
3096                         if ((m_nBufferType[nBuffer] == BUFFERTYPE::NORMAL) ||
3097                             (m_nBufferType[nBuffer] == BUFFERTYPE::NORMAL_NAMED))
3098                         {
3099                                 nNormalBuffer++;
3100                         }
3101                         
3102                 }
3103
3104                 // Inform user that files are identical
3105                 // Don't show message if new buffers created
3106                 if (identical == IDENTLEVEL::ALL && nNormalBuffer > 0)
3107                 {
3108                         ShowRescanError(nRescanResult, identical);
3109                 }
3110
3111                 // Exit if files are identical should only work for the first
3112                 // comparison and must be disabled afterward.
3113                 theApp.m_bExitIfNoDiff = MergeCmdLineInfo::Disabled;
3114         }
3115         else
3116         {
3117                 // CMergeDoc::Rescan fails if files do not exist on both sides 
3118                 // or the really arcane case that the temp files couldn't be created, 
3119                 // which is too obscure to bother reporting if you can't write to 
3120                 // your temp directory, doing nothing is graceful enough for that).
3121                 ShowRescanError(nRescanResult, identical);
3122                 GetParentFrame()->DestroyWindow();
3123                 return false;
3124         }
3125
3126         // Force repaint of location pane to update it in case we had some warning
3127         // dialog visible and it got painted before files were loaded
3128         if (m_pView[0][0] != nullptr)
3129                 m_pView[0][0]->RepaintLocationPane();
3130
3131         return true;
3132 }
3133
3134 void CMergeDoc::MoveOnLoad(int nPane, int nLineIndex)
3135 {
3136         if (nPane < 0)
3137         {
3138                 nPane = GetOptionsMgr()->GetInt(OPT_ACTIVE_PANE);
3139                 if (nPane < 0 || nPane >= m_nBuffers)
3140                         nPane = 0;
3141         }
3142         if (nLineIndex == -1)
3143         {
3144                 // scroll to first diff
3145                 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST) &&
3146                         m_diffList.HasSignificantDiffs())
3147                 {
3148                         int nDiff = m_diffList.FirstSignificantDiff();
3149                         if (nDiff != -1)
3150                                 m_pView[0][nPane]->SelectDiff(nDiff, true, false);
3151                         m_pView[0][nPane]->SetActivePane();
3152                         return;
3153                 }
3154         }
3155         m_pView[0][nPane]->GotoLine(nLineIndex < 0 ? 0 : nLineIndex, false, nPane);
3156 }
3157
3158 void CMergeDoc::ChangeFile(int nBuffer, const String& path, int nLineIndex)
3159 {
3160         if (!PromptAndSaveIfNeeded(true))
3161                 return;
3162
3163         FileLocation fileloc[3];
3164         String strDesc[3];
3165         bool bRO[3];
3166         for (int pane = 0; pane < m_nBuffers; pane++)
3167         {
3168                 bRO[pane] = m_ptBuf[pane]->GetReadOnly();
3169                 fileloc[pane].encoding.m_unicoding = m_ptBuf[pane]->getUnicoding();
3170                 fileloc[pane].encoding.m_codepage = m_ptBuf[pane]->getCodepage();
3171                 fileloc[pane].setPath(m_filePaths[pane]);
3172         }
3173         std::copy_n(m_strDesc, m_nBuffers, strDesc);
3174
3175         strDesc[nBuffer] = _T("");
3176         fileloc[nBuffer].setPath(path);
3177         fileloc[nBuffer].encoding = codepage_detect::Guess(path, GetOptionsMgr()->GetInt(OPT_CP_DETECT));
3178         
3179         if (OpenDocs(m_nBuffers, fileloc, bRO, strDesc))
3180                 MoveOnLoad(nBuffer, nLineIndex);
3181 }
3182
3183 /**
3184  * @brief Re-load a document.
3185  * This methods re-loads the file compare document. The re-loaded document is
3186  * one side of the file compare.
3187  * @param [in] index The document to re-load.
3188  * @return Open result code.
3189  */
3190 void CMergeDoc::RefreshOptions()
3191 {
3192         DIFFOPTIONS options = {0};
3193         
3194         m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3195
3196         m_diffWrapper.SetDetectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
3197         Options::DiffOptions::Load(GetOptionsMgr(), options);
3198
3199         m_diffWrapper.SetOptions(&options);
3200
3201         // Refresh view options
3202         ForEachView([](auto& pView) { pView->RefreshOptions(); });
3203 }
3204
3205 /**
3206  * @brief Write path and filename to headerbar
3207  * @note SetText() does not repaint unchanged text
3208  */
3209 void CMergeDoc::UpdateHeaderPath(int pane)
3210 {
3211         CMergeEditFrame *pf = GetParentFrame();
3212         ASSERT(pf != nullptr);
3213         String sText;
3214         bool bChanges = false;
3215
3216         if (m_nBufferType[pane] == BUFFERTYPE::UNNAMED ||
3217                 m_nBufferType[pane] == BUFFERTYPE::NORMAL_NAMED)
3218         {
3219                 sText = m_strDesc[pane];
3220         }
3221         else
3222         {
3223                 sText = m_filePaths[pane];
3224                 if (m_pDirDoc != nullptr)
3225                 {
3226                         m_pDirDoc->ApplyDisplayRoot(pane, sText);
3227                 }
3228         }
3229         bChanges = m_ptBuf[pane]->IsModified();
3230
3231         if (bChanges)
3232                 sText.insert(0, _T("* "));
3233
3234         pf->GetHeaderInterface()->SetText(pane, sText);
3235
3236         SetTitle(nullptr);
3237 }
3238
3239 /**
3240  * @brief Paint differently the headerbar of the active view
3241  */
3242 void CMergeDoc::UpdateHeaderActivity(int pane, bool bActivate)
3243 {
3244         CMergeEditFrame *pf = GetParentFrame();
3245         ASSERT(pf != nullptr);
3246         pf->GetHeaderInterface()->SetActive(pane, bActivate);
3247 }
3248
3249 /**
3250  * @brief Set detect/not detect Moved Blocks
3251  */
3252 void CMergeDoc::SetDetectMovedBlocks(bool bDetectMovedBlocks)
3253 {
3254         if (bDetectMovedBlocks == m_diffWrapper.GetDetectMovedBlocks())
3255                 return;
3256
3257         GetOptionsMgr()->SaveOption(OPT_CMP_MOVED_BLOCKS, bDetectMovedBlocks);
3258         m_diffWrapper.SetDetectMovedBlocks(bDetectMovedBlocks);
3259         FlushAndRescan();
3260 }
3261
3262 /**
3263  * @brief Check if given buffer has mixed EOL style.
3264  * @param [in] nBuffer Buffer to check.
3265  * @return true if buffer's EOL style is mixed, false otherwise.
3266  */
3267 bool CMergeDoc::IsMixedEOL(int nBuffer) const
3268 {
3269         CDiffTextBuffer *pBuf = m_ptBuf[nBuffer].get();
3270         return pBuf->IsMixedEOL();
3271 }
3272
3273 void CMergeDoc::SetEditedAfterRescan(int nBuffer)
3274 {
3275         m_bEditAfterRescan[nBuffer] = true;
3276 }
3277
3278 bool CMergeDoc::IsEditedAfterRescan(int nBuffer) const
3279 {
3280         if (nBuffer >= 0 && nBuffer < m_nBuffers)
3281                 return m_bEditAfterRescan[nBuffer];
3282
3283         for (nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3284         {
3285                 if (m_bEditAfterRescan[nBuffer])
3286                         return true;
3287         }
3288
3289         return false;
3290 }
3291
3292 /**
3293  * @brief Update document filenames to title
3294  */
3295 void CMergeDoc::SetTitle(LPCTSTR lpszTitle)
3296 {
3297         String sTitle = (lpszTitle != nullptr) ? lpszTitle : CMergeFrameCommon::GetTitleString(m_filePaths, m_strDesc);
3298         CDocument::SetTitle(sTitle.c_str());
3299 }
3300
3301 /**
3302  * @brief Update any resources necessary after a GUI language change
3303  */
3304 void CMergeDoc::UpdateResources()
3305 {
3306         if (m_nBufferType[0] == BUFFERTYPE::UNNAMED)
3307                 m_strDesc[0] = _("Untitled left");
3308         if (m_nBufferType[m_nBuffers - 1] == BUFFERTYPE::UNNAMED)
3309                 m_strDesc[m_nBuffers - 1] = _("Untitled right");
3310         if (m_nBuffers == 3 && m_nBufferType[1] == BUFFERTYPE::UNNAMED)
3311                 m_strDesc[1] = _("Untitled middle");
3312         for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3313                 UpdateHeaderPath(nBuffer);
3314
3315         GetParentFrame()->UpdateResources();
3316         ForEachView([](auto& pView) { pView->UpdateResources(); });
3317 }
3318
3319 // Return current word breaking break type setting (whitespace only or include punctuation)
3320 bool CMergeDoc::GetBreakType() const
3321 {
3322         bool breakType = !!GetOptionsMgr()->GetInt(OPT_BREAK_TYPE);
3323         return breakType;
3324 }
3325
3326 // Return true to do line diff colors at the byte level (false to do them at word level)
3327 bool CMergeDoc::GetByteColoringOption() const
3328 {
3329         // color at byte level if 'break_on_words' option not set
3330         bool breakWords = GetOptionsMgr()->GetBool(OPT_BREAK_ON_WORDS);
3331         return !breakWords;
3332 }
3333
3334 /// Swap files and update views
3335 void CMergeDoc::SwapFiles(int nFromIndex, int nToIndex)
3336 {
3337         if ((nFromIndex >= 0 && nFromIndex < m_nBuffers) && (nToIndex >= 0 && nToIndex < m_nBuffers))
3338         {
3339                 // Swap views
3340                 for (int nGroup = 0; nGroup < m_nGroups; ++nGroup)
3341                 {
3342                         int nLeftViewId = m_pView[nGroup][nFromIndex]->GetDlgCtrlID();
3343                         int nRightViewId = m_pView[nGroup][nToIndex]->GetDlgCtrlID();
3344                         m_pView[nGroup][nFromIndex]->SetDlgCtrlID(nRightViewId);
3345                         m_pView[nGroup][nToIndex]->SetDlgCtrlID(nLeftViewId);
3346                 }
3347
3348
3349                 // Swap buffers and so on
3350                 std::swap(m_ptBuf[nFromIndex], m_ptBuf[nToIndex]);
3351                 for (int nGroup = 0; nGroup < m_nGroups; ++nGroup)
3352                         std::swap(m_pView[nGroup][nFromIndex], m_pView[nGroup][nToIndex]);
3353                 std::swap(m_pSaveFileInfo[nFromIndex], m_pSaveFileInfo[nToIndex]);
3354                 std::swap(m_pRescanFileInfo[nFromIndex], m_pRescanFileInfo[nToIndex]);
3355                 std::swap(m_nBufferType[nFromIndex], m_nBufferType[nToIndex]);
3356                 std::swap(m_bEditAfterRescan[nFromIndex], m_bEditAfterRescan[nToIndex]);
3357                 std::swap(m_strDesc[nFromIndex], m_strDesc[nToIndex]);
3358
3359                 m_filePaths.Swap(nFromIndex, nToIndex);
3360                 m_diffList.Swap(nFromIndex, nToIndex);
3361                 for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
3362                         swap(m_pView[nGroup][nFromIndex]->m_piMergeEditStatus, m_pView[nGroup][nToIndex]->m_piMergeEditStatus);
3363
3364                 ClearWordDiffCache();
3365
3366                 for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3367                 {
3368                         m_ptBuf[nBuffer]->m_nThisPane = nBuffer;
3369                         for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
3370                                 m_pView[nGroup][nBuffer]->m_nThisPane = nBuffer;
3371
3372                         // Update views
3373                         UpdateHeaderPath(nBuffer);
3374                 }
3375                 GetParentFrame()->UpdateSplitter();
3376                 ForEachView([](auto& pView) { pView->UpdateStatusbar(); });
3377
3378                 UpdateAllViews(nullptr);
3379         }
3380 }
3381
3382 /**
3383  * @brief Display unpacker dialog to user & handle user's choices
3384  */
3385 bool CMergeDoc::OpenWithUnpackerDialog()
3386 {
3387         // let the user choose a handler
3388         CSelectUnpackerDlg dlg(m_filePaths[0], nullptr);
3389         // create now a new infoUnpacker to initialize the manual/automatic flag
3390         PackingInfo infoUnpacker(PLUGIN_MODE::PLUGIN_AUTO);
3391         dlg.SetInitialInfoHandler(&infoUnpacker);
3392
3393         if (dlg.DoModal() == IDOK)
3394         {
3395                 infoUnpacker = dlg.GetInfoHandler();
3396                 Merge7zFormatMergePluginScope scope(&infoUnpacker);
3397                 if (HasZipSupport() && std::count_if(m_filePaths.begin(), m_filePaths.end(), ArchiveGuessFormat) == m_nBuffers)
3398                 {
3399                         DWORD dwFlags[3] = {FFILEOPEN_NOMRU, FFILEOPEN_NOMRU, FFILEOPEN_NOMRU};
3400                         GetMainFrame()->DoFileOpen(&m_filePaths, dwFlags, m_strDesc, _T(""), 
3401                                 GetOptionsMgr()->GetBool(OPT_CMP_INCLUDE_SUBDIRS), nullptr, _T(""), &infoUnpacker);
3402                         CloseNow();
3403                 }
3404                 else
3405                 {
3406                         SetUnpacker(&infoUnpacker);
3407                         OnFileReload();
3408                 }
3409                 return true;
3410         }
3411         else
3412         {
3413                 return false;
3414         }
3415 }
3416
3417 /**
3418  * @brief Reloads the opened files
3419  */
3420 void CMergeDoc::OnFileReload()
3421 {
3422         if (!PromptAndSaveIfNeeded(true))
3423                 return;
3424         
3425         FileLocation fileloc[3];
3426         bool bRO[3];
3427         for (int pane = 0; pane < m_nBuffers; pane++)
3428         {
3429                 bRO[pane] = m_ptBuf[pane]->GetReadOnly();
3430                 fileloc[pane].encoding.m_unicoding = m_ptBuf[pane]->getUnicoding();
3431                 fileloc[pane].encoding.m_codepage = m_ptBuf[pane]->getCodepage();
3432                 fileloc[pane].setPath(m_filePaths[pane]);
3433         }
3434         CPoint pt = GetActiveMergeView()->GetCursorPos();
3435         if (OpenDocs(m_nBuffers, fileloc, bRO, m_strDesc))
3436                 MoveOnLoad(GetActiveMergeView()->m_nThisPane, pt.y);
3437 }
3438
3439 /**
3440  * @brief Display encodings to user
3441  */
3442 void CMergeDoc::OnFileEncoding()
3443 {
3444         DoFileEncodingDialog();
3445 }
3446
3447 void CMergeDoc::OnCtxtOpenWithUnpacker() 
3448 {
3449         OpenWithUnpackerDialog();
3450 }
3451
3452 void CMergeDoc::OnBnClickedFileEncoding()
3453 {
3454         if (m_pEncodingErrorBar == nullptr || m_pView[0][0] == nullptr)
3455                 return;
3456         m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
3457         DoFileEncodingDialog();
3458 }
3459
3460 void CMergeDoc::OnBnClickedPlugin()
3461 {
3462         if (m_pEncodingErrorBar == nullptr || m_pView[0][0] == nullptr)
3463                 return;
3464         m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
3465         OpenWithUnpackerDialog();
3466 }
3467
3468 void CMergeDoc::OnBnClickedHexView()
3469 {
3470         OnFileRecompareAs(ID_MERGE_COMPARE_HEX);
3471 }
3472
3473 void CMergeDoc::OnOK()
3474 {
3475         if (m_pEncodingErrorBar == nullptr || m_pView[0][0] == nullptr)
3476                 return;
3477         m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
3478 }
3479
3480 void CMergeDoc::OnFileRecompareAsText()
3481 {
3482         m_bEnableTableEditing = false;
3483         PackingInfo infoUnpacker;
3484         SetUnpacker(&infoUnpacker);
3485         OnFileReload();
3486 }
3487
3488 void CMergeDoc::OnUpdateFileRecompareAsText(CCmdUI *pCmdUI)
3489 {
3490         pCmdUI->Enable(m_pInfoUnpacker->m_PluginOrPredifferMode == PLUGIN_MODE::PLUGIN_BUILTIN_XML ||
3491                 m_ptBuf[0]->GetTableEditing());
3492 }
3493
3494 void CMergeDoc::OnFileRecompareAsTable()
3495 {
3496         m_bEnableTableEditing = true;
3497         PackingInfo infoUnpacker;
3498         SetUnpacker(&infoUnpacker);
3499         OnFileReload();
3500 }
3501
3502 void CMergeDoc::OnUpdateFileRecompareAsTable(CCmdUI *pCmdUI)
3503 {
3504         pCmdUI->Enable(!m_ptBuf[0]->GetTableEditing());
3505 }
3506
3507 void CMergeDoc::OnFileRecompareAsXML()
3508 {
3509         m_bEnableTableEditing = false;
3510         PackingInfo infoUnpacker(PLUGIN_MODE::PLUGIN_BUILTIN_XML);
3511         SetUnpacker(&infoUnpacker);
3512         OnFileReload();
3513 }
3514
3515 void CMergeDoc::OnUpdateFileRecompareAsXML(CCmdUI *pCmdUI)
3516 {
3517         pCmdUI->Enable(m_pInfoUnpacker->m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_BUILTIN_XML);
3518 }
3519
3520 void CMergeDoc::OnFileRecompareAs(UINT nID)
3521 {
3522         DWORD dwFlags[3] = { 0 };
3523         FileLocation fileloc[3];
3524         for (int pane = 0; pane < m_nBuffers; pane++)
3525         {
3526                 fileloc[pane].setPath(m_filePaths[pane]);
3527                 dwFlags[pane] |= FFILEOPEN_NOMRU | (m_ptBuf[pane]->GetReadOnly() ? FFILEOPEN_READONLY : 0);
3528         }
3529         if (m_pEncodingErrorBar!=nullptr && m_pEncodingErrorBar->IsWindowVisible())
3530                 m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
3531         GetMainFrame()->ShowMergeDoc(nID, m_pDirDoc, m_nBuffers, fileloc, dwFlags, m_strDesc);
3532         GetParentFrame()->ShowWindow(SW_RESTORE);
3533         GetParentFrame()->DestroyWindow();
3534 }
3535
3536 // Return file extension either from file name 
3537 String CMergeDoc::GetFileExt(LPCTSTR sFileName, LPCTSTR sDescription) const
3538 {
3539         String sExt;
3540         paths::SplitFilename(sFileName, nullptr, nullptr, &sExt);
3541         return sExt;
3542 }
3543
3544 /**
3545  * @brief Generate report from file compare results.
3546  */
3547 bool CMergeDoc::GenerateReport(const String& sFileName) const
3548 {
3549         // calculate HTML font size
3550         LOGFONT lf;
3551         CDC dc;
3552         dc.CreateDC(_T("DISPLAY"), nullptr, nullptr, nullptr);
3553         m_pView[0][0]->GetFont(lf);
3554         int nFontSize = -MulDiv (lf.lfHeight, 72, dc.GetDeviceCaps (LOGPIXELSY));
3555
3556         // create HTML report
3557         UniStdioFile file;
3558         if (!file.Open(sFileName, _T("wt")))
3559         {
3560                 String errMsg = GetSysError(GetLastError());
3561                 String msg = strutils::format_string1(
3562                         _("Error creating the report:\n%1"), errMsg);
3563                 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
3564                 return false;
3565         }
3566
3567         file.SetCodepage(ucr::CP_UTF_8);
3568
3569         CString headerText =
3570                 _T("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n")
3571                 _T("\t\"http://www.w3.org/TR/html4/loose.dtd\">\n")
3572                 _T("<html>\n")
3573                 _T("<head>\n")
3574                 _T("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n")
3575                 _T("<title>WinMerge File Compare Report</title>\n")
3576                 _T("<style type=\"text/css\">\n")
3577                 _T("<!--\n")
3578                 _T("table {margin: 0; border: 1px solid #a0a0a0; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.15);}\n")
3579                 _T("td,th {word-break: break-all; font-size: %dpt;padding: 0 3px;}\n")
3580                 _T("tr { vertical-align: top; }\n")
3581                 _T(".title {color: white; background-color: blue; vertical-align: top; padding: 4px 4px; background: linear-gradient(mediumblue, darkblue);}\n")
3582                 _T("%s")
3583                 _T("-->\n")
3584                 _T("</style>\n")
3585                 _T("</head>\n")
3586                 _T("<body>\n")
3587                 _T("<table cellspacing=\"0\" cellpadding=\"0\" style=\"width:100%%;\">\n")
3588                 _T("<thead>\n")
3589                 _T("<tr>\n");
3590         String header = 
3591                 strutils::format((LPCTSTR)headerText, nFontSize, (LPCTSTR)m_pView[0][0]->GetHTMLStyles());
3592         file.WriteString(header);
3593
3594         // Get paths
3595         // If archive, use archive path + folder + filename inside archive
3596         // If desc text given, use it
3597         PathContext paths = m_filePaths;
3598         if (m_pDirDoc != nullptr && m_pDirDoc->IsArchiveFolders())
3599         {
3600                 for (int i = 0; i < paths.GetSize(); i++)
3601                         m_pDirDoc->ApplyDisplayRoot(i, paths[i]);
3602         }
3603         else
3604         {
3605                 for (int i = 0; i < paths.GetSize(); i++)
3606                 {
3607                         if (!m_strDesc[i].empty())
3608                                 paths[i] = m_strDesc[i];
3609                 }
3610         }
3611
3612         // titles
3613         int nBuffer;
3614         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3615         {
3616                 String data = strutils::format(_T("<th colspan=\"2\" class=\"title\" style=\"width:%f%%\">"),
3617                         (double)100 / m_nBuffers);
3618                 file.WriteString(data);
3619                 file.WriteString(ucr::toTString(CMarkdown::Entities(ucr::toUTF8(paths[nBuffer]))));
3620                 file.WriteString(_T("</th>\n"));
3621         }
3622         file.WriteString(
3623                 _T("</tr>\n")
3624                 _T("</thead>\n")
3625                 _T("<tbody>\n"));
3626
3627         // write the body of the report
3628         int idx[3] = {0};
3629         int nLineCount[3] = {0};
3630         int nDiff = 0;
3631         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3632                 nLineCount[nBuffer] = m_ptBuf[nBuffer]->GetLineCount();
3633
3634         for (;;)
3635         {
3636                 file.WriteString(_T("<tr>\n"));
3637                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3638                 {
3639                         for (; idx[nBuffer] < nLineCount[nBuffer]; idx[nBuffer]++)
3640                         {
3641                                 if (m_pView[0][nBuffer]->GetLineVisible(idx[nBuffer]))
3642                                         break;
3643                         }
3644                                 
3645                         if (idx[nBuffer] < nLineCount[nBuffer])
3646                         {
3647                                 // line number
3648                                 int iVisibleLineNumber = 0;
3649                                 String tdtag = _T("<td class=\"ln\">");
3650                                 DWORD dwFlags = m_ptBuf[nBuffer]->GetLineFlags(idx[nBuffer]);
3651                                 if ((dwFlags & LF_GHOST) == 0 && m_pView[0][nBuffer]->GetViewLineNumbers())
3652                                 {
3653                                         iVisibleLineNumber = m_ptBuf[nBuffer]->ComputeRealLine(idx[nBuffer]) + 1;
3654                                 }
3655                                 if (nBuffer == 0 &&
3656                                         (dwFlags & (LF_DIFF | LF_GHOST)) != 0 && (idx[nBuffer] == 0 ||
3657                                         (m_ptBuf[nBuffer]->GetLineFlags(idx[nBuffer] - 1) & (LF_DIFF | LF_GHOST)) == 0))
3658                                 {
3659                                         ++nDiff;
3660                                         if (iVisibleLineNumber > 0)
3661                                         {
3662                                                 tdtag += strutils::format(_T("<a name=\"d%d\" href=\"#d%d\">%d</a>"), nDiff, nDiff, iVisibleLineNumber);
3663                                                 iVisibleLineNumber = 0;
3664                                         }
3665                                         else
3666                                                 tdtag += strutils::format(_T("<a name=\"d%d\" href=\"#d%d\">.</a>"), nDiff, nDiff);
3667                                 }
3668                                 if (iVisibleLineNumber > 0)
3669                                         tdtag += strutils::format(_T("%d</td>"), iVisibleLineNumber);
3670                                 else
3671                                         tdtag += _T("</td>");
3672                                 file.WriteString(tdtag);
3673                                 // line content
3674                                 file.WriteString((LPCTSTR)m_pView[0][nBuffer]->GetHTMLLine(idx[nBuffer], _T("td")));
3675                                 idx[nBuffer]++;
3676                         }
3677                         else
3678                                 file.WriteString(_T("<td class=\"ln\"></td><td></td>"));
3679                         file.WriteString(_T("\n"));
3680                 }
3681                 file.WriteString(_T("</tr>\n"));
3682
3683                 bool bBorderLine = false;
3684                 for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3685                 {
3686                         if (idx[nBuffer] < nLineCount[nBuffer] && !m_pView[0][nBuffer]->GetLineVisible(idx[nBuffer]))
3687                                 bBorderLine = true;
3688                 }
3689
3690                 if (bBorderLine)
3691                 {
3692                         file.WriteString(_T("<tr height=1>"));
3693                         for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
3694                         {
3695                                 if (idx[nBuffer] < nLineCount[nBuffer] && !m_pView[0][nBuffer]->GetLineVisible(idx[nBuffer]))
3696                                         file.WriteString(_T("<td style=\"background-color: black\"></td><td style=\"background-color: black\"></td>"));
3697                                 else
3698                                         file.WriteString(_T("<td></td><td></td>"));
3699                         }
3700                         file.WriteString(_T("</tr>\n"));
3701                 }
3702
3703                 if (idx[0] >= nLineCount[0] && idx[1] >= nLineCount[1] && (m_nBuffers < 3 || idx[2] >= nLineCount[2]))
3704                         break;
3705         }
3706         file.WriteString(
3707                 _T("</tbody>\n")
3708                 _T("</table>\n")
3709                 _T("</body>\n")
3710                 _T("</html>\n"));
3711
3712         file.Close();
3713
3714         return true;
3715 }
3716
3717 /**
3718  * @brief Generate report from file compare results.
3719  */
3720 void CMergeDoc::OnToolsGenerateReport()
3721 {
3722         String s;
3723         CString folder;
3724
3725         if (!SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, false, folder, _T(""), _("HTML Files (*.htm,*.html)|*.htm;*.html|All Files (*.*)|*.*||"), _T("htm")))
3726                 return;
3727
3728         if (GenerateReport(s.c_str()))
3729                 LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION);
3730 }
3731
3732 /**
3733  * @brief Generate patch from files selected.
3734  *
3735  * Creates a patch from selected files in active directory compare, or
3736  * active file compare. Files in file compare must be saved before
3737  * creating a patch.
3738  */
3739 void CMergeDoc::OnToolsGeneratePatch()
3740 {
3741         // If there are changes in files, tell user to save them first
3742         if (IsModified())
3743         {
3744                 LangMessageBox(IDS_SAVEFILES_FORPATCH, MB_ICONSTOP);
3745                 return;
3746         }
3747
3748         CPatchTool patcher;
3749         patcher.AddFiles(m_filePaths.GetLeft(),
3750                         m_filePaths.GetRight());
3751         patcher.CreatePatch();
3752 }
3753
3754 /**
3755  * @brief Add synchronization point
3756  */
3757 void CMergeDoc::AddSyncPoint()
3758 {
3759         int nLine[3];
3760         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3761         {
3762                 int tmp = m_pView[0][nBuffer]->GetCursorPos().y;
3763                 nLine[nBuffer] = m_ptBuf[nBuffer]->ComputeApparentLine(m_ptBuf[nBuffer]->ComputeRealLine(tmp));
3764         }
3765
3766         // If adding a sync point by selecting a ghost line that is after the last block, Cancel the process adding a sync point.
3767         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3768                 if (nLine[nBuffer] >= m_ptBuf[nBuffer]->GetLineCount())
3769                 {
3770                         LangMessageBox(IDS_SYNCPOINT_LASTBLOCK, MB_ICONSTOP);
3771                         return;
3772                 }
3773
3774         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3775                 if (m_ptBuf[nBuffer]->GetLineFlags(nLine[nBuffer]) & LF_INVALID_BREAKPOINT)
3776                         DeleteSyncPoint(nBuffer, nLine[nBuffer], false);
3777
3778         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3779                 m_ptBuf[nBuffer]->SetLineFlag(nLine[nBuffer], LF_INVALID_BREAKPOINT, true, false);
3780
3781         m_bHasSyncPoints = true;
3782
3783         ForEachView([](auto& pView) { pView->SetSelectionMargin(true); });
3784
3785         FlushAndRescan(true);
3786 }
3787
3788 /**
3789  * @brief Delete a synchronization point
3790  */
3791 bool CMergeDoc::DeleteSyncPoint(int pane, int nLine, bool bRescan)
3792 {
3793         const auto syncpoints = GetSyncPointList();     
3794         for (auto syncpnt : syncpoints)
3795         {
3796                 if (syncpnt[pane] == nLine)
3797                 {
3798                         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3799                                 m_ptBuf[nBuffer]->SetLineFlag(syncpnt[nBuffer], LF_INVALID_BREAKPOINT, false, false);
3800                 }
3801         }
3802
3803         if (syncpoints.size() == 1)
3804                 m_bHasSyncPoints = false;
3805
3806         if (bRescan)
3807                 FlushAndRescan(true);
3808         return true;
3809 }
3810
3811 /**
3812  * @brief Clear Synchronization points
3813  */
3814 void CMergeDoc::ClearSyncPoints()
3815 {
3816         if (!m_bHasSyncPoints)
3817                 return;
3818
3819         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3820         {
3821                 int nLineCount = m_ptBuf[nBuffer]->GetLineCount();
3822                 for (int nLine = 0; nLine < nLineCount; ++nLine)
3823                 {
3824                         if (m_ptBuf[nBuffer]->GetLineFlags(nLine) & LF_INVALID_BREAKPOINT)
3825                                 m_ptBuf[nBuffer]->SetLineFlag(nLine, LF_INVALID_BREAKPOINT, false, false);
3826                 }
3827         }
3828         
3829         m_bHasSyncPoints = false;
3830
3831         FlushAndRescan(true);
3832 }
3833
3834 std::vector<std::vector<int> > CMergeDoc::GetSyncPointList()
3835 {
3836         std::vector<std::vector<int> > list;
3837         if (!m_bHasSyncPoints)
3838                 return list;
3839         int idx[3] = {-1, -1, -1};
3840         std::vector<int> points(m_nBuffers);
3841         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3842                 points[nBuffer] = m_ptBuf[nBuffer]->GetLineCount() - 1;
3843         for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
3844         {
3845                 int nLineCount = m_ptBuf[nBuffer]->GetLineCount();
3846                 for (int nLine = 0; nLine < nLineCount; ++nLine)
3847                 {
3848                         if (m_ptBuf[nBuffer]->GetLineFlags(nLine) & LF_INVALID_BREAKPOINT)
3849                         {
3850                                 idx[nBuffer]++;
3851                                 if (static_cast<int>(list.size()) <= idx[nBuffer])
3852                                         list.push_back(points);
3853                                 list[idx[nBuffer]][nBuffer] = nLine;
3854                         }
3855                 }
3856         }
3857         return list;
3858 }
3859