OSDN Git Service

MergeDoc.cpp: Avoid unpacking the file on the right if the Unpacker plugin fails...
[winmerge-jp/winmerge-jp.git] / Src / MergeDoc.cpp
index 61dbaeb..b8928cc 100644 (file)
@@ -2,21 +2,7 @@
 //    WinMerge:  an interactive diff/merge utility
 //    Copyright (C) 1997-2000  Thingamahoochie Software
 //    Author: Dean Grimm
-//
-//    This program is free software; you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation; either version 2 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program; if not, write to the Free Software
-//    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-//
+//    SPDX-License-Identifier: GPL-2.0-or-later
 /////////////////////////////////////////////////////////////////////////////
 /** 
  * @file  MergeDoc.cpp
@@ -36,7 +22,7 @@
 #include "Environment.h"
 #include "MovedLines.h"
 #include "MergeEditView.h"
-#include "ChildFrm.h"
+#include "MergeEditFrm.h"
 #include "DirDoc.h"
 #include "files.h"
 #include "FileTransform.h"
@@ -45,6 +31,7 @@
 #include "OptionsDef.h"
 #include "DiffFileInfo.h"
 #include "SaveClosingDlg.h"
+#include "OpenTableDlg.h"
 #include "DiffList.h"
 #include "paths.h"
 #include "OptionsMgr.h"
@@ -52,6 +39,7 @@
 #include "MergeLineFlags.h"
 #include "FileOrFolderSelect.h"
 #include "LineFiltersList.h"
+#include "SubstitutionFiltersList.h"
 #include "TempFile.h"
 #include "codepage_detect.h"
 #include "SelectUnpackerDlg.h"
 
 using std::swap;
 
-/** @brief Max len of path in caption. */
-static const UINT CAPTION_PATH_MAX = 50;
-
 int CMergeDoc::m_nBuffersTemp = 2;
 
-/** @brief EOL types */
-static LPCTSTR crlfs[] =
-{
-       _T ("\x0d\x0a"), //  DOS/Windows style
-       _T ("\x0a"),     //  UNIX style
-       _T ("\x0d")      //  Macintosh style
-};
-
-static void SaveBuffForDiff(CDiffTextBuffer & buf, const String& filepath, bool bForceUTF8, int nStartLine = 0, int nLines = -1);
+static void SaveBuffForDiff(CDiffTextBuffer & buf, const String& filepath, int nStartLine = 0, int nLines = -1);
 
 /////////////////////////////////////////////////////////////////////////////
 // CMergeDoc
@@ -108,15 +85,21 @@ BEGIN_MESSAGE_MAP(CMergeDoc, CDocument)
        ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
        ON_COMMAND(ID_RESCAN, OnFileReload)
        ON_COMMAND(ID_FILE_ENCODING, OnFileEncoding)
-       ON_COMMAND_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_TOGGLE, OnDiffContext)
-       ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_TOGGLE, OnUpdateDiffContext)
+       ON_COMMAND_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_INVERT, OnDiffContext)
+       ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_DIFFCONTEXT_ALL, ID_VIEW_DIFFCONTEXT_INVERT, OnUpdateDiffContext)
        ON_COMMAND(ID_POPUP_OPEN_WITH_UNPACKER, OnCtxtOpenWithUnpacker)
        ON_BN_CLICKED(IDC_FILEENCODING, OnBnClickedFileEncoding)
        ON_BN_CLICKED(IDC_PLUGIN, OnBnClickedPlugin)
        ON_BN_CLICKED(IDC_HEXVIEW, OnBnClickedHexView)
        ON_COMMAND(IDOK, OnOK)
+       ON_COMMAND(ID_MERGE_COMPARE_TEXT, OnFileRecompareAsText)
+       ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_TEXT, OnUpdateFileRecompareAsText)
+       ON_COMMAND(ID_MERGE_COMPARE_TABLE, OnFileRecompareAsTable)
+       ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_TABLE, OnUpdateFileRecompareAsTable)
        ON_COMMAND(ID_MERGE_COMPARE_XML, OnFileRecompareAsXML)
+       ON_UPDATE_COMMAND_UI(ID_MERGE_COMPARE_XML, OnUpdateFileRecompareAsXML)
        ON_COMMAND_RANGE(ID_MERGE_COMPARE_HEX, ID_MERGE_COMPARE_IMAGE, OnFileRecompareAs)
+       ON_UPDATE_COMMAND_UI_RANGE(ID_SWAPPANES_SWAP23, ID_SWAPPANES_SWAP13, OnUpdateSwapContext)
        //}}AFX_MSG_MAP
 END_MESSAGE_MAP()
 
@@ -129,6 +112,7 @@ END_MESSAGE_MAP()
 CMergeDoc::CMergeDoc()
 : m_bEnableRescan(true)
 , m_nCurDiff(-1)
+, m_CurWordDiff{ -1, static_cast<size_t>(-1), -1 }
 , m_pDirDoc(nullptr)
 , m_bMixedEol(false)
 , m_pInfoUnpacker(new PackingInfo)
@@ -137,6 +121,7 @@ CMergeDoc::CMergeDoc()
 , m_bAutoMerged(false)
 , m_nGroups(0)
 , m_pView{nullptr}
+, m_bAutomaticRescan(false)
 {
        DIFFOPTIONS options = {0};
 
@@ -148,15 +133,17 @@ CMergeDoc::CMergeDoc()
                m_ptBuf[nBuffer].reset(new CDiffTextBuffer(this, nBuffer));
                m_pSaveFileInfo[nBuffer].reset(new DiffFileInfo());
                m_pRescanFileInfo[nBuffer].reset(new DiffFileInfo());
-               m_nBufferType[nBuffer] = BUFFER_NORMAL;
+               m_nBufferType[nBuffer] = BUFFERTYPE::NORMAL;
                m_bEditAfterRescan[nBuffer] = false;
        }
 
-       m_nCurDiff=-1;
        m_bEnableRescan = true;
+       m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
+
        // COleDateTime m_LastRescan
        curUndo = undoTgt.begin();
        m_nDiffContext = GetOptionsMgr()->GetInt(OPT_DIFF_CONTEXT);
+       m_bInvertDiffContext = GetOptionsMgr()->GetBool(OPT_INVERT_DIFF_CONTEXT);
 
        m_diffWrapper.SetDetectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
        Options::DiffOptions::Load(GetOptionsMgr(), options);
@@ -265,34 +252,15 @@ void CMergeDoc::Serialize(CArchive& ar)
  * (the plugins are optional, not the conversion)
  * @todo Show SaveToFile() errors?
  */
-static void SaveBuffForDiff(CDiffTextBuffer & buf, const String& filepath, bool bForceUTF8, int nStartLine, int nLines)
+static void SaveBuffForDiff(CDiffTextBuffer & buf, const String& filepath, int nStartLine, int nLines)
 {
-       ASSERT(buf.m_nSourceEncoding == buf.m_nDefaultEncoding);  
-       int orig_codepage = buf.getCodepage();
-       ucr::UNICODESET orig_unicoding = buf.getUnicoding();
-       bool orig_bHasBOM = buf.getHasBom();
-
-       // If file was in Unicode
-       if (orig_unicoding != ucr::NONE || bForceUTF8)
-       {
-       // we subvert the buffer's memory of the original file encoding
-               buf.setUnicoding(ucr::UTF8);  // write as UTF-8 (for preprocessing)
-               buf.setCodepage(ucr::CP_UTF_8); // should not matter
-               buf.setHasBom(false);
-       }
-
        // and we don't repack the file
        PackingInfo * tempPacker = nullptr;
 
        // write buffer out to temporary file
        String sError;
        int retVal = buf.SaveToFile(filepath, true, sError, tempPacker,
-               CRLF_STYLE_AUTOMATIC, false, nStartLine, nLines);
-
-       // restore memory of encoding of original file
-       buf.setUnicoding(orig_unicoding);
-       buf.setCodepage(orig_codepage);
-       buf.setHasBom(orig_bHasBOM);
+               CRLFSTYLE::AUTOMATIC, false, nStartLine, nLines);
 }
 
 /**
@@ -318,7 +286,7 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
        DiffFileInfo fileInfo;
        bool diffSuccess = false;
        int nResult = RESCAN_OK;
-       FileChange FileChanged[3] = {FileNoChange, FileNoChange, FileNoChange};
+       FileChange Changed[3] = {FileChange::NoChange, FileChange::NoChange, FileChange::NoChange};
        int nBuffer;
 
        if (!bForced)
@@ -337,7 +305,20 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
        {
                m_diffWrapper.SetFilterList(_T(""));
        }
-       m_diffWrapper.SetFilterCommentsManager(theApp.m_pFilterCommentsManager.get());
+
+       if (theApp.m_pSubstitutionFiltersList && theApp.m_pSubstitutionFiltersList->GetEnabled())
+       {
+               m_diffWrapper.SetSubstitutionList(theApp.m_pSubstitutionFiltersList->MakeSubstitutionList());
+       }
+       else
+       {
+               m_diffWrapper.SetSubstitutionList(nullptr);
+       }
+
+       if (GetView(0, 0)->m_CurSourceDef->type != 0)
+               m_diffWrapper.SetFilterCommentsSourceDef(GetView(0, 0)->m_CurSourceDef);
+       else
+               m_diffWrapper.SetFilterCommentsSourceDef(GetFileExt(m_filePaths[0].c_str(), m_strDesc[0].c_str()));
 
        for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
        {
@@ -345,7 +326,7 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
                // Ignore checking in case of scratchpads (empty filenames)
                if (!m_filePaths[nBuffer].empty())
                {
-                       FileChanged[nBuffer] = IsFileChangedOnDisk(m_filePaths[nBuffer].c_str(),
+                       Changed[nBuffer] = IsFileChangedOnDisk(m_filePaths[nBuffer].c_str(),
                                        fileInfo, false, nBuffer);
                }
        }
@@ -354,7 +335,7 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
        LPCTSTR tnames[] = {_T("t0_wmdoc"), _T("t1_wmdoc"), _T("t2_wmdoc")};
        for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
        {
-               if (FileChanged[nBuffer] == FileRemoved)
+               if (Changed[nBuffer] == FileChange::Removed)
                {
                        String msg = strutils::format_string1(_("The file\n%1\nhas disappeared. Please save a copy of the file to continue."), m_filePaths[nBuffer]);
                        ShowMessageBox(msg, MB_ICONWARNING);
@@ -368,10 +349,7 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
 
                String temp = m_tempFiles[nBuffer].GetPath();
                if (temp.empty())
-               {
-                       temp = m_tempFiles[nBuffer].CreateFromFile(m_filePaths.GetPath(nBuffer),
-                               tnames[nBuffer]);
-               }
+                       temp = m_tempFiles[nBuffer].Create(tnames[nBuffer]);
                if (temp.empty())
                        return RESCAN_TEMP_ERR;
        }
@@ -383,16 +361,10 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
        // Set up DiffWrapper
        m_diffWrapper.GetOptions(&diffOptions);
 
-       bool bForceUTF8 = diffOptions.bIgnoreCase;
-       IF_IS_TRUE_ALL (
-               m_ptBuf[0]->getCodepage() == m_ptBuf[nBuffer]->getCodepage() && m_ptBuf[nBuffer]->getUnicoding() == ucr::NONE,
-               nBuffer, m_nBuffers) {}
-       else
-               bForceUTF8 = true;
-
        // Clear diff list
        m_diffList.Clear();
        m_nCurDiff = -1;
+       m_CurWordDiff = { -1, static_cast<size_t>(-1), -1 };
        // Clear moved lines lists
        if (m_diffWrapper.GetDetectMovedBlocks())
        {
@@ -407,9 +379,6 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
        else
                m_diffWrapper.SetPaths(PathContext(m_tempFiles[0].GetPath(), m_tempFiles[1].GetPath(), m_tempFiles[2].GetPath()), true);
        m_diffWrapper.SetCompareFiles(m_filePaths);
-       m_diffWrapper.SetCodepage(bForceUTF8 ? ucr::CP_UTF_8 : (m_ptBuf[0]->m_encoding.m_unicoding ? CP_UTF8 : m_ptBuf[0]->m_encoding.m_codepage));
-       m_diffWrapper.SetCodepage(m_ptBuf[0]->m_encoding.m_unicoding ?
-                       CP_UTF8 : m_ptBuf[0]->m_encoding.m_codepage);
 
        DIFFSTATUS status;
 
@@ -419,7 +388,7 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
                for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
                {
                        m_ptBuf[nBuffer]->SetTempPath(tempPath);
-                       SaveBuffForDiff(*m_ptBuf[nBuffer], m_tempFiles[nBuffer].GetPath(), bForceUTF8);
+                       SaveBuffForDiff(*m_ptBuf[nBuffer], m_tempFiles[nBuffer].GetPath());
                }
 
                m_diffWrapper.SetCreateDiffList(&m_diffList);
@@ -442,7 +411,7 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
                        {
                                nLines[nBuffer] = (i >= syncpoints.size()) ? -1 : syncpoints[i][nBuffer] - nStartLine[nBuffer];
                                m_ptBuf[nBuffer]->SetTempPath(tempPath);
-                               SaveBuffForDiff(*m_ptBuf[nBuffer], m_tempFiles[nBuffer].GetPath(), bForceUTF8,
+                               SaveBuffForDiff(*m_ptBuf[nBuffer], m_tempFiles[nBuffer].GetPath(), 
                                        nStartLine[nBuffer], nLines[nBuffer]);
                        }
                        DiffList templist;
@@ -451,6 +420,33 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
                        diffSuccess = m_diffWrapper.RunFileDiff();
                        for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
                                nRealLine[nBuffer] = m_ptBuf[nBuffer]->ComputeRealLine(nStartLine[nBuffer]);
+
+                       // Correct the comparison results made by diffutils if the first file separated by the sync point is an empty file.
+                       if (i == 0 && templist.GetSize() > 0)
+                               for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
+                                       if (nStartLine[nBuffer] == 0)
+                                       {
+                                               bool isEmptyFile = true;
+                                               for (int j = 0; j < nLines[nBuffer]; j++)
+                                               {
+                                                       if (!(m_ptBuf[nBuffer]->GetLineFlags(nStartLine[nBuffer] + j) & LF_GHOST))
+                                                       {
+                                                               isEmptyFile = false;
+                                                               break;
+                                                       }
+                                               }
+                                               if (isEmptyFile)
+                                               {
+                                                       DIFFRANGE di;
+                                                       templist.GetDiff(0, di);
+                                                       if (di.begin[nBuffer] == 0 && di.end[nBuffer] == 0)
+                                                       {
+                                                               di.end[nBuffer] = -1;
+                                                               templist.SetDiff(0, di);
+                                                       }
+                                               }
+                                       }
+
                        m_diffList.AppendDiffList(templist, nRealLine);
                        for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
                                nStartLine[nBuffer] += nLines[nBuffer];
@@ -527,7 +523,7 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
 
                // Identical files are also updated
                if (!m_diffList.HasSignificantDiffs())
-                       identical = IDENTLEVEL_ALL;
+                       identical = IDENTLEVEL::ALL;
 
                ForEachView([](auto& pView) {
                        // just apply some options to the views
@@ -542,12 +538,12 @@ int CMergeDoc::Rescan(bool &bBinary, IDENTLEVEL &identical,
        }
 
        if (!GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CODEPAGE) &&
-               identical == IDENTLEVEL_ALL &&
+               identical == IDENTLEVEL::ALL &&
                std::any_of(m_ptBuf, m_ptBuf + m_nBuffers,
                        [&](std::unique_ptr<CDiffTextBuffer>& buf) { return buf->getEncoding() != m_ptBuf[0]->getEncoding(); }))
-               identical = IDENTLEVEL_NONE;
+               identical = IDENTLEVEL::NONE;
 
-       GetParentFrame()->SetLastCompareResult(identical != IDENTLEVEL_ALL ? 1 : 0);
+       GetParentFrame()->SetLastCompareResult(identical != IDENTLEVEL::ALL ? 1 : 0);
 
        return nResult;
 }
@@ -566,16 +562,26 @@ void CMergeDoc::CheckFileChanged(void)
                m_pRescanFileInfo[nBuffer]->Update((LPCTSTR)m_filePaths[nBuffer].c_str());
        }
 
+       bool bDoReload = false;
        for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
        {
-               if (FileChange[nBuffer] == FileChanged)
+               if (FileChange[nBuffer] == FileChange::Changed)
                {
                        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]);
                        if (ShowMessageBox(msg, MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_FILECHANGED_RESCAN) == IDYES)
+                               bDoReload = true;
+                       break;
+               }
+       }
+       if (bDoReload)
+       {
+               for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
+               {
+                       if (FileChange[nBuffer] == FileChange::Changed)
                        {
-                               OnFileReload();
+                               CPoint pt = GetView(0, nBuffer)->GetCursorPos();
+                               ChangeFile(nBuffer, m_filePaths[nBuffer], pt.y);
                        }
-                       break;
                }
        }
 }
@@ -599,14 +605,13 @@ void CMergeDoc::FlagTrivialLines(void)
                                DIFFOPTIONS diffOptions = {0};
                                m_diffWrapper.GetOptions(&diffOptions);
 
-                               std::vector<strdiff::wdiff> worddiffs;
                                // Make the call to stringdiffs, which does all the hard & tedious computations
-                               strdiff::ComputeWordDiffs(m_nBuffers, str,
+                               std::vector<strdiff::wdiff> worddiffs = strdiff::ComputeWordDiffs(m_nBuffers, str,
                                        !diffOptions.bIgnoreCase,
+                                       !diffOptions.bIgnoreEol,
                                        diffOptions.nIgnoreWhitespace,
                                        GetBreakType(), // whitespace only or include punctuation
-                                       GetByteColoringOption(),
-                                       &worddiffs);
+                                       GetByteColoringOption());
                                if (!worddiffs.empty())
                                {
                                        for (int file = 0; file < m_nBuffers; ++file)
@@ -626,7 +631,7 @@ void CMergeDoc::FlagMovedLines(void)
        pMovedLines = m_diffWrapper.GetMovedLines(0);
        for (i = 0; i < m_ptBuf[0]->GetLineCount(); ++i)
        {
-               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_RIGHT);
+               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::RIGHT);
                if (j != -1)
                {
                        TRACE(_T("%d->%d\n"), i, j);
@@ -636,6 +641,12 @@ void CMergeDoc::FlagMovedLines(void)
                        if (m_ptBuf[0]->FlagIsSet(apparent, LF_DIFF))
                        {
                                m_ptBuf[0]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               if (m_ptBuf[1]->FlagIsSet(apparent, LF_GHOST))
+                               {
+                                       int apparentJ = m_ptBuf[1]->ComputeApparentLine(j);
+                                       if (m_ptBuf[0]->FlagIsSet(apparentJ, LF_GHOST))
+                                               m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               }
                        }
                }
        }
@@ -643,7 +654,7 @@ void CMergeDoc::FlagMovedLines(void)
        pMovedLines = m_diffWrapper.GetMovedLines(1);
        for (i=0; i<m_ptBuf[1]->GetLineCount(); ++i)
        {
-               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_LEFT);
+               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::LEFT);
                if (j != -1)
                {
                        TRACE(_T("%d->%d\n"), i, j);
@@ -653,6 +664,12 @@ void CMergeDoc::FlagMovedLines(void)
                        if (m_ptBuf[1]->FlagIsSet(apparent, LF_DIFF))
                        {
                                m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               if (m_ptBuf[0]->FlagIsSet(apparent, LF_GHOST))
+                               {
+                                       int apparentJ = m_ptBuf[0]->ComputeApparentLine(j);
+                                       if (m_ptBuf[1]->FlagIsSet(apparentJ, LF_GHOST))
+                                               m_ptBuf[0]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               }
                        }
                }
        }
@@ -663,7 +680,7 @@ void CMergeDoc::FlagMovedLines(void)
        pMovedLines = m_diffWrapper.GetMovedLines(1);
        for (i=0; i<m_ptBuf[1]->GetLineCount(); ++i)
        {
-               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_RIGHT);
+               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::RIGHT);
                if (j != -1)
                {
                        TRACE(_T("%d->%d\n"), i, j);
@@ -673,6 +690,12 @@ void CMergeDoc::FlagMovedLines(void)
                        if (m_ptBuf[1]->FlagIsSet(apparent, LF_DIFF))
                        {
                                m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               if (m_ptBuf[2]->FlagIsSet(apparent, LF_GHOST))
+                               {
+                                       int apparentJ = m_ptBuf[2]->ComputeApparentLine(j);
+                                       if (m_ptBuf[1]->FlagIsSet(apparentJ, LF_GHOST))
+                                               m_ptBuf[2]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               }
                        }
                }
        }
@@ -680,7 +703,7 @@ void CMergeDoc::FlagMovedLines(void)
        pMovedLines = m_diffWrapper.GetMovedLines(2);
        for (i=0; i<m_ptBuf[2]->GetLineCount(); ++i)
        {
-               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE_LEFT);
+               int j = pMovedLines->LineInBlock(i, MovedLines::SIDE::LEFT);
                if (j != -1)
                {
                        TRACE(_T("%d->%d\n"), i, j);
@@ -690,6 +713,12 @@ void CMergeDoc::FlagMovedLines(void)
                        if (m_ptBuf[2]->FlagIsSet(apparent, LF_DIFF))
                        {
                                m_ptBuf[2]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               if (m_ptBuf[1]->FlagIsSet(apparent, LF_GHOST))
+                               {
+                                       int apparentJ = m_ptBuf[1]->ComputeApparentLine(j);
+                                       if (m_ptBuf[2]->FlagIsSet(apparentJ, LF_GHOST))
+                                               m_ptBuf[1]->SetLineFlag(apparent, LF_MOVED, true, false, false);
+                               }
                        }
                }
        }
@@ -699,7 +728,7 @@ void CMergeDoc::FlagMovedLines(void)
 
 int CMergeDoc::ShowMessageBox(const String& sText, unsigned nType, unsigned nIDHelp)
 {
-       if (!GetParentFrame()->IsActivated())
+       if (m_pView[0][0] && m_pView[0][0]->IsTextBufferInitialized() && !GetParentFrame()->IsActivated())
        {
                GetParentFrame()->InitialUpdateFrame(this, true);
                GetParentFrame()->SendMessageToDescendants(WM_IDLEUPDATECMDUI, static_cast<WPARAM>(true), 0, true, true);
@@ -739,40 +768,10 @@ void CMergeDoc::ShowRescanError(int nRescanResult, IDENTLEVEL identical)
        }
 
        // Files are not binaries, but they are identical
-       if (identical != IDENTLEVEL_NONE)
+       if (identical != IDENTLEVEL::NONE)
        {
-               if (!m_filePaths.GetLeft().empty() && !m_filePaths.GetMiddle().empty() && !m_filePaths.GetRight().empty() && 
-                       m_filePaths.GetLeft() == m_filePaths.GetRight() && m_filePaths.GetMiddle() == m_filePaths.GetRight())
-               {
-                       // compare file to itself, a custom message so user may hide the message in this case only
-                       s = _("The same file is opened in both panels.");
-                       ShowMessageBox(s, MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN, IDS_FILE_TO_ITSELF);
-               }
-               else if (identical == IDENTLEVEL_ALL)
-               {
-                       UINT nFlags = MB_ICONINFORMATION | MB_DONT_DISPLAY_AGAIN;
-
-                       if (theApp.m_bExitIfNoDiff == MergeCmdLineInfo::Exit)
-                       {
-                               // Show the "files are identical" for basic "exit no diff" flag
-                               // If user don't want to see the message one uses the quiet version
-                               // of the "exit no diff".
-                               nFlags &= ~MB_DONT_DISPLAY_AGAIN;
-                       }
-
-                       if (theApp.m_bExitIfNoDiff != MergeCmdLineInfo::ExitQuiet)
-                       {
-                               s = _("The selected files are identical.");
-                               ShowMessageBox(s, nFlags, IDS_FILESSAME);
-                       }
-
-                       // Exit application if files are identical.
-                       if (theApp.m_bExitIfNoDiff == MergeCmdLineInfo::Exit ||
-                               theApp.m_bExitIfNoDiff == MergeCmdLineInfo::ExitQuiet)
-                       {
-                               AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_APP_EXIT);
-                       }
-               }
+               CMergeFrameCommon::ShowIdenticalMessage(m_filePaths, identical == IDENTLEVEL::ALL,
+                       [this](LPCTSTR msg, UINT flags, UINT id) -> int { return ShowMessageBox(msg, flags, id); });
        }
 }
 
@@ -862,7 +861,8 @@ void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int la
        }
        else
        {
-               if (!WordListCopy(srcPane, dstPane, lastDiff, firstWordDiff, lastWordDiff, nullptr, bGroupWithPrevious, true))
+               if (!WordListCopy(srcPane, dstPane, lastDiff, 
+                       (firstDiff == lastDiff) ? firstWordDiff : 0, lastWordDiff, nullptr, bGroupWithPrevious, true))
                        return; // sync failure
        }
 
@@ -897,7 +897,7 @@ void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int la
                        }                       
                        // Group merge with previous (merge undo data to one action)
                        bGroupWithPrevious = true;
-                       if (i > firstDiff)
+                       if (i > firstDiff || firstWordDiff <= 0)
                        {
                                if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false))
                                        break; // sync failure
@@ -920,6 +920,84 @@ void CMergeDoc::CopyMultipleList(int srcPane, int dstPane, int firstDiff, int la
        FlushAndRescan();
 }
 
+void CMergeDoc::CopyMultiplePartialList(int srcPane, int dstPane, int firstDiff, int lastDiff,
+       int firstLineDiff, int lastLineDiff)
+{
+       lastDiff = min(m_diffList.GetSize() - 1, lastDiff);
+       firstDiff = max(0, firstDiff);
+       if (firstDiff > lastDiff)
+               return;
+       
+       RescanSuppress suppressRescan(*this);
+
+       bool bGroupWithPrevious = false;
+       if (firstLineDiff <= 0 && lastLineDiff == -1)
+       {
+               if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, true))
+                       return; // sync failure
+       }
+       else
+       {
+               if (!PartialListCopy(srcPane, dstPane, lastDiff, 
+                       (firstDiff == lastDiff) ? firstLineDiff : 0, lastLineDiff, bGroupWithPrevious, true))
+                       return; // sync failure
+       }
+
+
+       SetEditedAfterRescan(dstPane);
+
+       int nGroup = GetActiveMergeView()->m_nThisGroup;
+       CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
+       CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
+       CPoint currentPosSrc = pViewSrc->GetCursorPos();
+       currentPosSrc.x = 0;
+       CPoint currentPosDst = pViewDst->GetCursorPos();
+       currentPosDst.x = 0;
+
+       CPoint pt(0, 0);
+       pViewDst->SetCursorPos(pt);
+       pViewDst->SetNewSelection(pt, pt, false);
+       pViewDst->SetNewAnchor(pt);
+
+       // copy from bottom up is more efficient
+       for (int i = lastDiff - 1; i >= firstDiff; --i)
+       {
+               if (m_diffList.IsDiffSignificant(i))
+               {
+                       SetCurrentDiff(i);
+                       const DIFFRANGE *pdi = m_diffList.DiffRangeAt(i);
+                       if (currentPosDst.y > pdi->dend)
+                       {
+                               if (pdi->blank[dstPane] >= 0)
+                                       currentPosDst.y -= pdi->dend - pdi->blank[dstPane] + 1;
+                               else if (pdi->blank[srcPane] >= 0)
+                                       currentPosDst.y -= pdi->dend - pdi->blank[srcPane] + 1;
+                       }                       
+                       // Group merge with previous (merge undo data to one action)
+                       bGroupWithPrevious = true;
+                       if (i > firstDiff || firstLineDiff <= 0)
+                       {
+                               if (!ListCopy(srcPane, dstPane, -1, bGroupWithPrevious, false))
+                                       break; // sync failure
+                       }
+                       else
+                       {
+                               if (!PartialListCopy(srcPane, dstPane, firstDiff, firstLineDiff, -1, bGroupWithPrevious, false))
+                                       break; // sync failure
+                       }
+               }
+       }
+
+       ForEachView(dstPane, [currentPosDst](auto& pView) {
+               pView->SetCursorPos(currentPosDst);
+               pView->SetNewSelection(currentPosDst, currentPosDst, false);
+               pView->SetNewAnchor(currentPosDst);
+       });
+
+       suppressRescan.Clear(); // done suppress Rescan
+       FlushAndRescan();
+}
+
 enum MergeResult { NoMergeNeeded, Merged, Conflict };
 
 template<class Type>
@@ -967,21 +1045,21 @@ void CMergeDoc::DoAutoMerge(int dstPane)
                DoMergeValue(m_ptBuf[0]->getEncoding(), m_ptBuf[1]->getEncoding(), m_ptBuf[2]->getEncoding(), dstPane);
        if (mergedEncoding.first == Merged)
        {
-               ShowMessageBox(_("The change of codepage has been merged"), MB_ICONINFORMATION);
+               ShowMessageBox(_("The change of codepage has been merged."), MB_ICONINFORMATION);
                m_ptBuf[dstPane]->setEncoding(mergedEncoding.second);
        }
        else if (mergedEncoding.first == Conflict)
-               ShowMessageBox(_("The changes of codepage are conflicting"), MB_ICONINFORMATION);
+               ShowMessageBox(_("The changes of codepage are conflicting."), MB_ICONINFORMATION);
 
        std::pair<MergeResult, CRLFSTYLE> mergedEOLStyle = 
                DoMergeValue(m_ptBuf[0]->GetCRLFMode(), m_ptBuf[1]->GetCRLFMode(), m_ptBuf[2]->GetCRLFMode(), dstPane);
        if (mergedEOLStyle.first == Merged)
        {
-               ShowMessageBox(_("The change of EOL has been merged"), MB_ICONINFORMATION);
+               ShowMessageBox(_("The change of EOL has been merged."), MB_ICONINFORMATION);
                m_ptBuf[dstPane]->SetCRLFMode(mergedEOLStyle.second);
        }
        else if (mergedEOLStyle.first == Conflict)
-               ShowMessageBox(_("The changes of EOL are conflicting"), MB_ICONINFORMATION);
+               ShowMessageBox(_("The changes of EOL are conflicting."), MB_ICONINFORMATION);
 
        RescanSuppress suppressRescan(*this);
 
@@ -1048,7 +1126,7 @@ void CMergeDoc::DoAutoMerge(int dstPane)
 
        ShowMessageBox(
                strutils::format_string2(
-                       _T("The number of automatically merged changes: %1\nThe number of unresolved conflicts: %2"), 
+                       _("The number of automatically merged changes: %1\nThe number of unresolved conflicts: %2"), 
                        strutils::format(_T("%d"), autoMergedCount),
                        strutils::format(_T("%d"), unresolvedConflictCount)),
                MB_ICONINFORMATION);
@@ -1215,8 +1293,101 @@ bool CMergeDoc::ListCopy(int srcPane, int dstPane, int nDiff /* = -1*/,
        return true;
 }
 
+bool CMergeDoc::PartialListCopy(int srcPane, int dstPane, int nDiff, int firstLine, int lastLine /*= -1*/,
+       bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/)
+{
+       int nGroup = GetActiveMergeView()->m_nThisGroup;
+       CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
+       CMergeEditView *pViewDst = m_pView[nGroup][dstPane];
+       CCrystalTextView *pSource = bUpdateView ? pViewDst : nullptr;
+
+       // suppress Rescan during this method
+       // (Not only do we not want to rescan a lot of times, but
+       // it will wreck the line status array to rescan as we merge)
+       RescanSuppress suppressRescan(*this);
+
+       DIFFRANGE cd;
+       VERIFY(m_diffList.GetDiff(nDiff, cd));
+       CDiffTextBuffer& sbuf = *m_ptBuf[srcPane];
+       CDiffTextBuffer& dbuf = *m_ptBuf[dstPane];
+       bool bSrcWasMod = sbuf.IsModified();
+       const int cd_dbegin = (firstLine > cd.dbegin) ? firstLine : cd.dbegin;
+       const int cd_dend = cd.dend;
+       const int cd_blank = cd.blank[srcPane];
+       bool bInSync = SanityCheckDiff(cd);
+
+       if (!bInSync)
+       {
+               LangMessageBox(IDS_VIEWS_OUTOFSYNC, MB_ICONSTOP);
+               return false; // abort copying
+       }
+
+       // If we remove whole diff from current view, we must fix cursor
+       // position first. Normally we would move to end of previous line,
+       // but we want to move to begin of that line for usability.
+       if (bUpdateView)
+       {
+               CPoint currentPos = pViewDst->GetCursorPos();
+               currentPos.x = 0;
+               if (currentPos.y > cd_dend)
+               {
+                       if (cd.blank[dstPane] >= 0)
+                               currentPos.y -= cd_dend - cd.blank[dstPane] + 1;
+                       else if (cd.blank[srcPane] >= 0)
+                               currentPos.y -= cd_dend - cd.blank[srcPane] + 1;
+               }
+               ForEachView(dstPane, [currentPos](auto& pView) { pView->SetCursorPos(currentPos); });
+       }
+
+       // if the current diff contains missing lines, remove them from both sides
+       int limit = ((lastLine < 0) || (lastLine > cd_dend)) ? cd_dend : lastLine;
+
+       // curView is the view which is changed, so the opposite of the source view
+       dbuf.BeginUndoGroup(bGroupWithPrevious);
+       if ((cd_blank >= 0) && (cd_dbegin >= cd_blank))
+       {
+               // text was missing, so delete rest of lines on both sides
+               // delete only on destination side since rescan will clear the other side
+               if (limit+1 < dbuf.GetLineCount())
+               {
+                       dbuf.DeleteText(pSource, cd_dbegin, 0, limit+1, 0, CE_ACTION_MERGE);
+               }
+               else
+               {
+                       // To removing EOL chars of last line, deletes from the end of the line (cd_blank - 1).
+                       ASSERT(cd_dbegin > 0);
+                       dbuf.DeleteText(pSource, cd_dbegin-1, dbuf.GetLineLength(cd_dbegin-1), limit, dbuf.GetLineLength(limit), CE_ACTION_MERGE);
+               }
+
+               limit = cd_dbegin-1;
+               dbuf.FlushUndoGroup(pSource);
+               dbuf.BeginUndoGroup(true);
+       }
+
+       // copy the selected text over
+       if (cd_dbegin <= limit)
+       {
+               // text exists on left side, so just replace
+               dbuf.ReplaceFullLines(dbuf, sbuf, pSource, cd_dbegin, limit, CE_ACTION_MERGE);
+               dbuf.FlushUndoGroup(pSource);
+               dbuf.BeginUndoGroup(true);
+       }
+       dbuf.FlushUndoGroup(pSource);
+
+       // remove the diff
+       SetCurrentDiff(-1);
+
+       // reset the mod status of the source view because we do make some
+       // changes, but none that concern the source text
+       sbuf.SetModified(bSrcWasMod);
+
+       suppressRescan.Clear(); // done suppress Rescan
+       FlushAndRescan();
+       return true;
+}
+
 bool CMergeDoc::WordListCopy(int srcPane, int dstPane, int nDiff, int firstWordDiff, int lastWordDiff,
-               std::vector<int> *pWordDiffIndice, bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/)
+               const std::vector<int> *pWordDiffIndice, bool bGroupWithPrevious /*= false*/, bool bUpdateView /*= true*/)
 {
        int nGroup = GetActiveMergeView()->m_nThisGroup;
        CMergeEditView *pViewSrc = m_pView[nGroup][srcPane];
@@ -1244,12 +1415,14 @@ bool CMergeDoc::WordListCopy(int srcPane, int dstPane, int nDiff, int firstWordD
                return false; // abort copying
        }
 
-       std::vector<WordDiff> worddiffs;
-       GetWordDiffArray(cd_dbegin, &worddiffs);
+       std::vector<WordDiff> worddiffs = GetWordDiffArrayInDiffBlock(nDiff);
 
        if (worddiffs.empty())
                return false;
 
+       if (cd.end[srcPane] < cd.begin[srcPane])
+               return ListCopy(srcPane, dstPane, nDiff, bGroupWithPrevious, bUpdateView);
+
        if (firstWordDiff == -1)
                firstWordDiff = 0;
        if (lastWordDiff == -1)
@@ -1312,9 +1485,9 @@ bool CMergeDoc::WordListCopy(int srcPane, int dstPane, int nDiff, int firstWordD
                int srcEnd   = nSrcOffsets[worddiffs[i].endline[srcPane] - ptSrcStart.y] + worddiffs[i].end[srcPane];
                int dstBegin = nDstOffsets[worddiffs[i].beginline[dstPane] - ptDstStart.y] + worddiffs[i].begin[dstPane];
                int dstEnd   = nDstOffsets[worddiffs[i].endline[dstPane] - ptDstStart.y] + worddiffs[i].end[dstPane];
-               CString text = srcText.Mid(srcBegin - ptSrcStart.x, srcEnd - srcBegin);
-               dstText.Delete(dstBegin - ptDstStart.x, dstEnd - dstBegin);
-               dstText.Insert(dstBegin - ptDstStart.x, text);
+               dstText = dstText.Mid(0, dstBegin - ptDstStart.x)
+                       + srcText.Mid(srcBegin - ptSrcStart.x, srcEnd - srcBegin)
+                       + dstText.Mid(dstEnd - ptDstStart.x);
        }
 
        dbuf.DeleteText(pSource, ptDstStart.y, ptDstStart.x, ptDstEnd.y, ptDstEnd.x, CE_ACTION_MERGE);
@@ -1390,11 +1563,11 @@ bool CMergeDoc::TrySaveAs(String &strPath, int &nSaveResult, String & sError,
                                strPath, pInfoTempUnpacker->m_PluginName);
                }
                // replace the unpacker with a "do nothing" unpacker
-               pInfoTempUnpacker->Initialize(PLUGIN_MANUAL);
+               pInfoTempUnpacker->Initialize(PLUGIN_MODE::PLUGIN_MANUAL);
        }
        else
        {
-               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);
+               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);
        }
 
        // SAVE_NO_FILENAME is temporarily used for scratchpad.
@@ -1424,7 +1597,7 @@ bool CMergeDoc::TrySaveAs(String &strPath, int &nSaveResult, String & sError,
                                // We are saving scratchpad (unnamed file)
                                if (strPath.empty())
                                {
-                                       m_nBufferType[nBuffer] = BUFFER_UNNAMED_SAVED;
+                                       m_nBufferType[nBuffer] = BUFFERTYPE::UNNAMED_SAVED;
                                        m_strDesc[nBuffer].erase();
                                }
                                        
@@ -1475,7 +1648,7 @@ bool CMergeDoc::DoSave(LPCTSTR szPath, bool &bSaveSuccess, int nBuffer)
        int nRetVal = -1;
 
        fileChanged = IsFileChangedOnDisk(szPath, fileInfo, true, nBuffer);
-       if (fileChanged == FileChanged)
+       if (fileChanged == FileChange::Changed)
        {
                String msg = strutils::format_string1(_("Another application has updated file\n%1\nsince WinMerge loaded it.\n\nOverwrite changed file?"), szPath);
                if (ShowMessageBox(msg, MB_ICONWARNING | MB_YESNO) == IDNO)
@@ -1529,7 +1702,7 @@ bool CMergeDoc::DoSave(LPCTSTR szPath, bool &bSaveSuccess, int nBuffer)
                nSaveErrorCode = SAVE_NO_FILENAME;
 
        // Handle unnamed buffers
-       if (m_nBufferType[nBuffer] == BUFFER_UNNAMED)
+       if (m_nBufferType[nBuffer] == BUFFERTYPE::UNNAMED)
                nSaveErrorCode = SAVE_NO_FILENAME;
 
        String sError;
@@ -1648,7 +1821,7 @@ int CMergeDoc::RightLineInMovedBlock(int nBuffer, int apparentLeftLine)
        if (m_diffWrapper.GetDetectMovedBlocks())
        {
                realRightLine = m_diffWrapper.GetMovedLines(nBuffer)->LineInBlock(realLeftLine,
-                               MovedLines::SIDE_RIGHT);
+                               MovedLines::SIDE::RIGHT);
        }
        if (realRightLine != -1)
                return m_ptBuf[nBuffer + 1]->ComputeApparentLine(realRightLine);
@@ -1669,7 +1842,7 @@ int CMergeDoc::LeftLineInMovedBlock(int nBuffer, int apparentRightLine)
        if (m_diffWrapper.GetDetectMovedBlocks())
        {
                realLeftLine = m_diffWrapper.GetMovedLines(nBuffer)->LineInBlock(realRightLine,
-                               MovedLines::SIDE_LEFT);
+                               MovedLines::SIDE::LEFT);
        }
        if (realLeftLine != -1)
                return m_ptBuf[nBuffer - 1]->ComputeApparentLine(realLeftLine);
@@ -1742,7 +1915,7 @@ void CMergeDoc::FlushAndRescan(bool bForced /* =false */)
        pActiveView->HideCursor();
 
        bool bBinary = false;
-       IDENTLEVEL identical = IDENTLEVEL_NONE;
+       IDENTLEVEL identical = IDENTLEVEL::NONE;
        int nRescanResult = Rescan(bBinary, identical, bForced);
 
        // restore cursors and caret
@@ -1753,10 +1926,9 @@ void CMergeDoc::FlushAndRescan(bool bForced /* =false */)
                // because of ghostlines, m_nTopLine may differ just after Rescan
                // scroll both views to the same top line
                pView->UpdateSiblingScrollPos(false);
-
-               // make sure we see the cursor from the curent view
-               pView->EnsureVisible(pView->GetCursorPos());
        });
+       // make sure we see the cursor from the curent view
+       pActiveView->EnsureVisible(pActiveView->GetCursorPos());
 
        // Refresh display
        UpdateAllViews(nullptr);
@@ -1989,12 +2161,31 @@ void CMergeDoc::OnDiffContext(UINT nID)
                if (m_nDiffContext >= 0)
                        m_nDiffContext = -m_nDiffContext - 1;
                break;
+       case ID_VIEW_DIFFCONTEXT_INVERT:
+               m_bInvertDiffContext = !m_bInvertDiffContext;
+               break;
        }
        GetOptionsMgr()->SaveOption(OPT_DIFF_CONTEXT, m_nDiffContext);
+       GetOptionsMgr()->SaveOption(OPT_INVERT_DIFF_CONTEXT, m_bInvertDiffContext);
        FlushAndRescan(true);
 }
 
 /**
+ * @brief Swap context enable for 3 file compares 
+ */
+void CMergeDoc::OnUpdateSwapContext(CCmdUI* pCmdUI)
+{
+       if (m_nBuffers > 2)
+       {
+               pCmdUI->Enable(true);
+       }
+       else
+       {
+               pCmdUI->Enable(false);
+       }
+}
+
+/**
  * @brief Update number of diff context lines
  */
 void CMergeDoc::OnUpdateDiffContext(CCmdUI* pCmdUI)
@@ -2016,11 +2207,13 @@ void CMergeDoc::OnUpdateDiffContext(CCmdUI* pCmdUI)
                bCheck = (m_nDiffContext == 9); break;
        case ID_VIEW_DIFFCONTEXT_TOGGLE:
                bCheck = false; break;
+       case ID_VIEW_DIFFCONTEXT_INVERT:
+               bCheck = m_bInvertDiffContext; break;
        default:
                bCheck = (m_nDiffContext < 0); break;
        }
        pCmdUI->SetCheck(bCheck);
-       pCmdUI->Enable(true);
+       pCmdUI->Enable(!(pCmdUI->m_nID == ID_VIEW_DIFFCONTEXT_INVERT && (m_nDiffContext < 0)));
 }
 
 /**
@@ -2110,7 +2303,7 @@ void CMergeDoc::PrimeTextBuffers()
                {
                case OP_TRIVIAL:
                        ++m_nTrivialDiffs;
-                       // fall through and handle as diff
+                       [[fallthrough]];
                case OP_DIFF:
                case OP_1STONLY:
                case OP_2NDONLY:
@@ -2203,7 +2396,7 @@ CMergeDoc::FileChange CMergeDoc::IsFileChangedOnDisk(LPCTSTR szPath, DiffFileInf
 
        // We assume file existed, so disappearing means removal
        if (!dfi.Update(szPath))
-               return FileRemoved;
+               return FileChange::Removed;
 
        int64_t timeDiff = dfi.mtime - fileInfo->mtime;
        if (timeDiff < 0) timeDiff = -timeDiff;
@@ -2213,9 +2406,9 @@ CMergeDoc::FileChange CMergeDoc::IsFileChangedOnDisk(LPCTSTR szPath, DiffFileInf
        }
 
        if (bFileChanged)
-               return FileChanged;
+               return FileChange::Changed;
        else
-               return FileNoChange;
+               return FileChange::NoChange;
 }
 
 void CMergeDoc::HideLines()
@@ -2238,7 +2431,8 @@ void CMergeDoc::HideLines()
 
        for (nLine =  0; nLine < nLineCount;)
        {
-               if (!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST)))
+               bool diff = !!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST));
+               if ((!m_bInvertDiffContext && !diff) || (m_bInvertDiffContext && diff))
                {
                        for (file = 0; file < m_nBuffers; file++)
                                m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, true, false, false);
@@ -2255,7 +2449,8 @@ void CMergeDoc::HideLines()
                
                        for (; nLine < nLineCount; nLine++)
                        {
-                               if (!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST)))
+                               diff = !!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST));
+                               if ((!m_bInvertDiffContext && !diff) || (m_bInvertDiffContext && diff))
                                        break;
                                for (file = 0; file < m_nBuffers; file++)
                                        m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, false, false, false);
@@ -2266,7 +2461,8 @@ void CMergeDoc::HideLines()
                        {
                                for (file = 0; file < m_nBuffers; file++)
                                        m_ptBuf[file]->SetLineFlag(nLine, LF_INVISIBLE, false, false, false);
-                               if (m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST))
+                               diff = !!(m_ptBuf[0]->GetLineFlags(nLine) & (LF_DIFF | LF_GHOST));
+                               if ((!m_bInvertDiffContext && diff) || (m_bInvertDiffContext && !diff))
                                        nLineEnd2 = (nLine + 1 + m_nDiffContext >= nLineCount) ? nLineCount-1 : (nLine + 1 + m_nDiffContext);
                        }
                }
@@ -2441,9 +2637,9 @@ void CMergeDoc::SetDirDoc(CDirDoc * pDirDoc)
 /**
  * @brief Return pointer to parent frame
  */
-CChildFrame * CMergeDoc::GetParentFrame() 
+CMergeEditFrame * CMergeDoc::GetParentFrame() 
 {
-       return dynamic_cast<CChildFrame *>(m_pView[0][0]->GetParentFrame()); 
+       return dynamic_cast<CMergeEditFrame *>(m_pView[0][0]->GetParentFrame()); 
 }
 
 /**
@@ -2486,7 +2682,7 @@ int CMergeDoc::LoadFile(CString sFileName, int nBuffer, bool & readOnly, const F
        CDiffTextBuffer *pBuf = m_ptBuf[nBuffer].get();
        m_filePaths[nBuffer] = sFileName;
 
-       CRLFSTYLE nCrlfStyle = CRLF_STYLE_AUTOMATIC;
+       CRLFSTYLE nCrlfStyle = CRLFSTYLE::AUTOMATIC;
        CString sOpenError;
        retVal = pBuf->LoadFromFile(sFileName, m_pInfoUnpacker.get(),
                m_strBothFilenames.c_str(), readOnly, nCrlfStyle, encoding, sOpenError);
@@ -2570,9 +2766,9 @@ DWORD CMergeDoc::LoadOneFile(int index, String filename, bool readOnly, const St
        if (!filename.empty())
        {
                if (strDesc.empty())
-                       m_nBufferType[index] = BUFFER_NORMAL;
+                       m_nBufferType[index] = BUFFERTYPE::NORMAL;
                else
-                       m_nBufferType[index] = BUFFER_NORMAL_NAMED;
+                       m_nBufferType[index] = BUFFERTYPE::NORMAL_NAMED;
                m_pSaveFileInfo[index]->Update(filename);
                m_pRescanFileInfo[index]->Update(filename);
 
@@ -2581,12 +2777,12 @@ DWORD CMergeDoc::LoadOneFile(int index, String filename, bool readOnly, const St
                {
                        m_ptBuf[index]->FreeAll();
                        loadSuccess = LoadFile(filename.c_str(), index, readOnly,
-                               GuessCodepageEncoding(filename, GetOptionsMgr()->GetInt(OPT_CP_DETECT), -1));
+                               codepage_detect::Guess(filename, GetOptionsMgr()->GetInt(OPT_CP_DETECT), -1));
                }
        }
        else
        {
-               m_nBufferType[index] = BUFFER_UNNAMED;
+               m_nBufferType[index] = BUFFERTYPE::UNNAMED;
                m_ptBuf[index]->InitNew();
                m_ptBuf[index]->m_encoding = encoding;
                m_ptBuf[index]->FinishLoading(); // should clear GGhostTextBuffer::m_RealityBlock when reloading unnamed buffer 
@@ -2595,18 +2791,90 @@ DWORD CMergeDoc::LoadOneFile(int index, String filename, bool readOnly, const St
        return loadSuccess;
 }
 
+void CMergeDoc::SetTableProperties()
+{
+       struct TableProps { bool istable; TCHAR delimiter; TCHAR quote; bool allowNewlinesInQuotes; };
+       auto getTablePropsByFileName = [](const String& path, const std::optional<bool>& enableTableEditing)-> TableProps
+       {
+               const TCHAR quote = GetOptionsMgr()->GetString(OPT_CMP_TBL_QUOTE_CHAR).c_str()[0];
+               FileFilterHelper filterCSV, filterTSV, filterDSV;
+               bool allowNewlineIQuotes = GetOptionsMgr()->GetBool(OPT_CMP_TBL_ALLOW_NEWLINES_IN_QUOTES);
+               const String csvFilePattern = GetOptionsMgr()->GetString(OPT_CMP_CSV_FILEPATTERNS);
+               if (!csvFilePattern.empty())
+               {
+                       filterCSV.UseMask(true);
+                       filterCSV.SetMask(csvFilePattern);
+                       if (filterCSV.includeFile(path))
+                               return { true, ',', quote, allowNewlineIQuotes };
+               }
+               const String tsvFilePattern = GetOptionsMgr()->GetString(OPT_CMP_TSV_FILEPATTERNS);
+               if (!tsvFilePattern.empty())
+               {
+                       filterTSV.UseMask(true);
+                       filterTSV.SetMask(tsvFilePattern);
+                       if (filterTSV.includeFile(path))
+                               return { true, '\t', quote, allowNewlineIQuotes };
+               }
+               const String dsvFilePattern = GetOptionsMgr()->GetString(OPT_CMP_DSV_FILEPATTERNS);
+               if (!dsvFilePattern.empty())
+               {
+                       filterDSV.UseMask(true);
+                       filterDSV.SetMask(dsvFilePattern);
+                       if (filterDSV.includeFile(path))
+                               return { true, GetOptionsMgr()->GetString(OPT_CMP_DSV_DELIM_CHAR).c_str()[0], quote };
+               }
+               if (enableTableEditing.value_or(false))
+               {
+                       COpenTableDlg dlg;
+                       if (dlg.DoModal() == IDOK)
+                               return { true, dlg.m_sDelimiterChar.c_str()[0], dlg.m_sQuoteChar.c_str()[0], dlg.m_bAllowNewlinesInQuotes };
+               }
+               return { false, 0, 0, false };
+       };
+
+       TableProps tableProps[3] = {};
+       int nTableFileIndex = -1;
+       for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
+       {
+               if (nBuffer == 0 ||
+                       paths::FindExtension(m_ptBuf[nBuffer - 1]->GetTempFileName()) != paths::FindExtension(m_ptBuf[nBuffer]->GetTempFileName()))
+                       tableProps[nBuffer] = getTablePropsByFileName(m_ptBuf[nBuffer]->GetTempFileName(), m_bEnableTableEditing);
+               else
+                       tableProps[nBuffer] = tableProps[nBuffer - 1];
+               if (tableProps[nBuffer].istable)
+                       nTableFileIndex = nBuffer;
+       }
+       for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
+       {
+               if (m_bEnableTableEditing.value_or(true) && nTableFileIndex >= 0)
+               {
+                       int i = tableProps[nBuffer].istable ? nBuffer : nTableFileIndex;
+                       m_ptBuf[nBuffer]->SetTableEditing(true);
+                       m_ptBuf[nBuffer]->ShareColumnWidths(*m_ptBuf[0]);
+                       m_ptBuf[nBuffer]->SetAllowNewlinesInQuotes(tableProps[i].allowNewlinesInQuotes);
+                       m_ptBuf[nBuffer]->SetFieldDelimiter(tableProps[i].delimiter);
+                       m_ptBuf[nBuffer]->SetFieldEnclosure(tableProps[i].quote);
+                       m_ptBuf[nBuffer]->JoinLinesForTableEditingMode();
+               }
+               else
+               {
+                       m_ptBuf[nBuffer]->SetTableEditing(false);
+               }
+       }
+}
+
 /**
  * @brief Loads files and does initial rescan.
  * @param fileloc [in] File to open to left/middle/right side (path & encoding info)
  * @param bRO [in] Is left/middle/right file read-only
  * @return Success/Failure/Binary (failure) per typedef enum OpenDocsResult_TYPE
  * @todo Options are still read from CMainFrame, this will change
- * @sa CMainFrame::ShowMergeDoc()
+ * @sa CMainFrame::ShowTextMergeDoc()
  */
 bool CMergeDoc::OpenDocs(int nFiles, const FileLocation ifileloc[],
                const bool bRO[], const String strDesc[])
 {
-       IDENTLEVEL identical = IDENTLEVEL_NONE;
+       IDENTLEVEL identical = IDENTLEVEL::NONE;
        int nRescanResult = RESCAN_OK;
        int nBuffer;
        FileLocation fileloc[3];
@@ -2641,34 +2909,35 @@ bool CMergeDoc::OpenDocs(int nFiles, const FileLocation ifileloc[],
        m_strBothFilenames.erase(m_strBothFilenames.length() - 1);
 
        // Load files
-       DWORD nSuccess[3];
+       DWORD nSuccess[3] = { FileLoadResult::FRESULT_ERROR,  FileLoadResult::FRESULT_ERROR,  FileLoadResult::FRESULT_ERROR };
        for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
        {
                nSuccess[nBuffer] = LoadOneFile(nBuffer, fileloc[nBuffer].filepath, bRO[nBuffer], strDesc ? strDesc[nBuffer] : _T(""),
                        fileloc[nBuffer].encoding);
+               if (!FileLoadResult::IsOk(nSuccess[nBuffer]))
+               {
+                       CMergeEditFrame* pFrame = GetParentFrame();
+                       if (pFrame != nullptr)
+                       {
+                               // Use verify macro to trap possible error in debug.
+                               VERIFY(pFrame->DestroyWindow());
+                       }
+                       return false;
+               }
        }
+
+       SetTableProperties();
+
        const bool bFiltersEnabled = GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED);
 
        // scratchpad : we don't call LoadFile, so
        // we need to initialize the unpacker as a "do nothing" one
        if (bFiltersEnabled)
        { 
-               if (std::count(m_nBufferType, m_nBufferType + m_nBuffers, BUFFER_UNNAMED) == m_nBuffers)
-               {
-                       m_pInfoUnpacker->Initialize(PLUGIN_MANUAL);
-               }
-       }
-
-       // Bail out if either side failed
-       if (std::find_if(nSuccess, nSuccess + m_nBuffers, [](DWORD d){return !FileLoadResult::IsOk(d);} ) != nSuccess + m_nBuffers)
-       {
-               CChildFrame *pFrame = GetParentFrame();
-               if (pFrame != nullptr)
+               if (std::count(m_nBufferType, m_nBufferType + m_nBuffers, BUFFERTYPE::UNNAMED) == m_nBuffers)
                {
-                       // Use verify macro to trap possible error in debug.
-                       VERIFY(pFrame->DestroyWindow());
+                       m_pInfoUnpacker->Initialize(PLUGIN_MODE::PLUGIN_MANUAL);
                }
-               return false;
        }
 
        // Warn user if file load was lossy (bad encoding)
@@ -2729,10 +2998,18 @@ bool CMergeDoc::OpenDocs(int nFiles, const FileLocation ifileloc[],
                        // All lines will differ, that is not very interesting and probably not wanted.
                        // Propose to turn off the option 'sensitive to EOL'
                        String s = theApp.LoadString(IDS_SUGGEST_IGNOREEOL);
-                       if (ShowMessageBox(s, MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN | MB_IGNORE_IF_SILENCED, IDS_SUGGEST_IGNOREEOL) == IDYES)
+                       if (ShowMessageBox(s, MB_YESNO | MB_ICONWARNING | MB_DONT_ASK_AGAIN, IDS_SUGGEST_IGNOREEOL) == IDYES)
                        {
                                diffOptions.bIgnoreEol = true;
                                m_diffWrapper.SetOptions(&diffOptions);
+
+                               CMessageBoxDialog dlg(nullptr, s.c_str(), _T(""), 0, IDS_SUGGEST_IGNOREEOL);
+                               const int nFormerResult = dlg.GetFormerResult();
+                               if (nFormerResult != -1)
+                               {
+                                       // "Don't ask this question again" checkbox is checked
+                                       GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, true);
+                               }
                        }
                }
        }
@@ -2797,7 +3074,7 @@ bool CMergeDoc::OpenDocs(int nFiles, const FileLocation ifileloc[],
 
                if (syntaxHLEnabled)
                {
-                       CCrystalTextView::TextDefinition *enuType = GetView(0, paneTyped)->GetTextType(sext[paneTyped].c_str());
+                       CrystalLineParser::TextDefinition *enuType = CrystalLineParser::GetTextType(sext[paneTyped].c_str());
                        ForEachView([&bTyped, enuType](auto& pView) {
                                if (!bTyped[pView->m_nThisPane])
                                        pView->SetTextType(enuType);
@@ -2812,8 +3089,8 @@ bool CMergeDoc::OpenDocs(int nFiles, const FileLocation ifileloc[],
 
                        ForEachView(nBuffer, [](auto& pView) { pView->DocumentsLoaded(); });
                        
-                       if ((m_nBufferType[nBuffer] == BUFFER_NORMAL) ||
-                           (m_nBufferType[nBuffer] == BUFFER_NORMAL_NAMED))
+                       if ((m_nBufferType[nBuffer] == BUFFERTYPE::NORMAL) ||
+                           (m_nBufferType[nBuffer] == BUFFERTYPE::NORMAL_NAMED))
                        {
                                nNormalBuffer++;
                        }
@@ -2822,7 +3099,7 @@ bool CMergeDoc::OpenDocs(int nFiles, const FileLocation ifileloc[],
 
                // Inform user that files are identical
                // Don't show message if new buffers created
-               if (identical == IDENTLEVEL_ALL && nNormalBuffer > 0)
+               if (identical == IDENTLEVEL::ALL && nNormalBuffer > 0)
                {
                        ShowRescanError(nRescanResult, identical);
                }
@@ -2865,18 +3142,16 @@ void CMergeDoc::MoveOnLoad(int nPane, int nLineIndex)
                        m_diffList.HasSignificantDiffs())
                {
                        int nDiff = m_diffList.FirstSignificantDiff();
-                       m_pView[0][nPane]->SelectDiff(nDiff, true, false);
-                       nLineIndex = m_pView[0][nPane]->GetCursorPos().y;
-               }
-               else
-               {
-                       nLineIndex = 0;
+                       if (nDiff != -1)
+                               m_pView[0][nPane]->SelectDiff(nDiff, true, false);
+                       m_pView[0][nPane]->SetActivePane();
+                       return;
                }
        }
-       m_pView[0][nPane]->GotoLine(nLineIndex, false, nPane);
+       m_pView[0][nPane]->GotoLine(nLineIndex < 0 ? 0 : nLineIndex, false, nPane);
 }
 
-void CMergeDoc::ChangeFile(int nBuffer, const String& path)
+void CMergeDoc::ChangeFile(int nBuffer, const String& path, int nLineIndex)
 {
        if (!PromptAndSaveIfNeeded(true))
                return;
@@ -2895,10 +3170,10 @@ void CMergeDoc::ChangeFile(int nBuffer, const String& path)
 
        strDesc[nBuffer] = _T("");
        fileloc[nBuffer].setPath(path);
-       fileloc[nBuffer].encoding = GuessCodepageEncoding(path, GetOptionsMgr()->GetInt(OPT_CP_DETECT));
+       fileloc[nBuffer].encoding = codepage_detect::Guess(path, GetOptionsMgr()->GetInt(OPT_CP_DETECT));
        
-       OpenDocs(m_nBuffers, fileloc, bRO, strDesc);
-       MoveOnLoad(nBuffer, 0);
+       if (OpenDocs(m_nBuffers, fileloc, bRO, strDesc))
+               MoveOnLoad(nBuffer, nLineIndex);
 }
 
 /**
@@ -2912,6 +3187,8 @@ void CMergeDoc::RefreshOptions()
 {
        DIFFOPTIONS options = {0};
        
+       m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
+
        m_diffWrapper.SetDetectMovedBlocks(GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS));
        Options::DiffOptions::Load(GetOptionsMgr(), options);
 
@@ -2927,13 +3204,13 @@ void CMergeDoc::RefreshOptions()
  */
 void CMergeDoc::UpdateHeaderPath(int pane)
 {
-       CChildFrame *pf = GetParentFrame();
+       CMergeEditFrame *pf = GetParentFrame();
        ASSERT(pf != nullptr);
        String sText;
        bool bChanges = false;
 
-       if (m_nBufferType[pane] == BUFFER_UNNAMED ||
-               m_nBufferType[pane] == BUFFER_NORMAL_NAMED)
+       if (m_nBufferType[pane] == BUFFERTYPE::UNNAMED ||
+               m_nBufferType[pane] == BUFFERTYPE::NORMAL_NAMED)
        {
                sText = m_strDesc[pane];
        }
@@ -2960,7 +3237,7 @@ void CMergeDoc::UpdateHeaderPath(int pane)
  */
 void CMergeDoc::UpdateHeaderActivity(int pane, bool bActivate)
 {
-       CChildFrame *pf = GetParentFrame();
+       CMergeEditFrame *pf = GetParentFrame();
        ASSERT(pf != nullptr);
        pf->GetHeaderInterface()->SetActive(pane, bActivate);
 }
@@ -2994,25 +3271,26 @@ void CMergeDoc::SetEditedAfterRescan(int nBuffer)
        m_bEditAfterRescan[nBuffer] = true;
 }
 
+bool CMergeDoc::IsEditedAfterRescan(int nBuffer) const
+{
+       if (nBuffer >= 0 && nBuffer < m_nBuffers)
+               return m_bEditAfterRescan[nBuffer];
+
+       for (nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
+       {
+               if (m_bEditAfterRescan[nBuffer])
+                       return true;
+       }
+
+       return false;
+}
+
 /**
  * @brief Update document filenames to title
  */
 void CMergeDoc::SetTitle(LPCTSTR lpszTitle)
 {
-       String sTitle;
-       String sFileName[3];
-
-       if (lpszTitle != nullptr)
-               sTitle = lpszTitle;
-       else
-       {
-               for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
-                       sFileName[nBuffer] = !m_strDesc[nBuffer].empty() ? m_strDesc[nBuffer] : paths::FindFileName(m_filePaths[nBuffer]);
-               if (std::count(&sFileName[0], &sFileName[0] + m_nBuffers, sFileName[0]) == m_nBuffers)
-                       sTitle = sFileName[0] + strutils::format(_T(" x %d"), m_nBuffers);
-               else
-                       sTitle = strutils::join(&sFileName[0], &sFileName[0] + m_nBuffers, _T(" - "));
-       }
+       String sTitle = (lpszTitle != nullptr) ? lpszTitle : CMergeFrameCommon::GetTitleString(m_filePaths, m_strDesc);
        CDocument::SetTitle(sTitle.c_str());
 }
 
@@ -3021,14 +3299,13 @@ void CMergeDoc::SetTitle(LPCTSTR lpszTitle)
  */
 void CMergeDoc::UpdateResources()
 {
-       CString str;
-       int nBuffer;
-
-       m_strDesc[0] = _("Untitled left");
-       m_strDesc[m_nBuffers - 1] = _("Untitled right");
-       if (m_nBuffers == 3)
+       if (m_nBufferType[0] == BUFFERTYPE::UNNAMED)
+               m_strDesc[0] = _("Untitled left");
+       if (m_nBufferType[m_nBuffers - 1] == BUFFERTYPE::UNNAMED)
+               m_strDesc[m_nBuffers - 1] = _("Untitled right");
+       if (m_nBuffers == 3 && m_nBufferType[1] == BUFFERTYPE::UNNAMED)
                m_strDesc[1] = _("Untitled middle");
-       for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
+       for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
                UpdateHeaderPath(nBuffer);
 
        GetParentFrame()->UpdateResources();
@@ -3051,48 +3328,51 @@ bool CMergeDoc::GetByteColoringOption() const
 }
 
 /// Swap files and update views
-void CMergeDoc::SwapFiles()
+void CMergeDoc::SwapFiles(int nFromIndex, int nToIndex)
 {
-       // Swap views
-       for (int nGroup = 0; nGroup < m_nGroups; ++nGroup)
+       if ((nFromIndex >= 0 && nFromIndex < m_nBuffers) && (nToIndex >= 0 && nToIndex < m_nBuffers))
        {
-               int nLeftViewId = m_pView[nGroup][0]->GetDlgCtrlID();
-               int nRightViewId = m_pView[nGroup][m_nBuffers - 1]->GetDlgCtrlID();
-               m_pView[nGroup][0]->SetDlgCtrlID(nRightViewId);
-               m_pView[nGroup][m_nBuffers - 1]->SetDlgCtrlID(nLeftViewId);
-       }
+               // Swap views
+               for (int nGroup = 0; nGroup < m_nGroups; ++nGroup)
+               {
+                       int nLeftViewId = m_pView[nGroup][nFromIndex]->GetDlgCtrlID();
+                       int nRightViewId = m_pView[nGroup][nToIndex]->GetDlgCtrlID();
+                       m_pView[nGroup][nFromIndex]->SetDlgCtrlID(nRightViewId);
+                       m_pView[nGroup][nToIndex]->SetDlgCtrlID(nLeftViewId);
+               }
 
 
-       // Swap buffers and so on
-       std::swap(m_ptBuf[0], m_ptBuf[m_nBuffers - 1]);
-       for (int nGroup = 0; nGroup < m_nGroups; ++nGroup)
-               std::swap(m_pView[nGroup][0], m_pView[nGroup][m_nBuffers - 1]);
-       std::swap(m_pSaveFileInfo[0], m_pSaveFileInfo[m_nBuffers - 1]);
-       std::swap(m_pRescanFileInfo[0], m_pRescanFileInfo[m_nBuffers - 1]);
-       std::swap(m_nBufferType[0], m_nBufferType[m_nBuffers - 1]);
-       std::swap(m_bEditAfterRescan[0], m_bEditAfterRescan[m_nBuffers - 1]);
-       std::swap(m_strDesc[0], m_strDesc[m_nBuffers - 1]);
+               // Swap buffers and so on
+               std::swap(m_ptBuf[nFromIndex], m_ptBuf[nToIndex]);
+               for (int nGroup = 0; nGroup < m_nGroups; ++nGroup)
+                       std::swap(m_pView[nGroup][nFromIndex], m_pView[nGroup][nToIndex]);
+               std::swap(m_pSaveFileInfo[nFromIndex], m_pSaveFileInfo[nToIndex]);
+               std::swap(m_pRescanFileInfo[nFromIndex], m_pRescanFileInfo[nToIndex]);
+               std::swap(m_nBufferType[nFromIndex], m_nBufferType[nToIndex]);
+               std::swap(m_bEditAfterRescan[nFromIndex], m_bEditAfterRescan[nToIndex]);
+               std::swap(m_strDesc[nFromIndex], m_strDesc[nToIndex]);
 
-       m_filePaths.Swap();
-       m_diffList.Swap(0, m_nBuffers - 1);
-       for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
-               swap(m_pView[nGroup][0]->m_piMergeEditStatus, m_pView[nGroup][m_nBuffers - 1]->m_piMergeEditStatus);
+               m_filePaths.Swap(nFromIndex, nToIndex);
+               m_diffList.Swap(nFromIndex, nToIndex);
+               for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
+                       swap(m_pView[nGroup][nFromIndex]->m_piMergeEditStatus, m_pView[nGroup][nToIndex]->m_piMergeEditStatus);
 
-       ClearWordDiffCache();
+               ClearWordDiffCache();
 
-       for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
-       {
-               m_ptBuf[nBuffer]->m_nThisPane = nBuffer;
-               for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
-                       m_pView[nGroup][nBuffer]->m_nThisPane = nBuffer;
+               for (int nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
+               {
+                       m_ptBuf[nBuffer]->m_nThisPane = nBuffer;
+                       for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
+                               m_pView[nGroup][nBuffer]->m_nThisPane = nBuffer;
 
-               // Update views
-               UpdateHeaderPath(nBuffer);
-       }
-       GetParentFrame()->UpdateSplitter();
-       ForEachView([](auto& pView) { pView->UpdateStatusbar(); });
+                       // Update views
+                       UpdateHeaderPath(nBuffer);
+               }
+               GetParentFrame()->UpdateSplitter();
+               ForEachView([](auto& pView) { pView->UpdateStatusbar(); });
 
-       UpdateAllViews(nullptr);
+               UpdateAllViews(nullptr);
+       }
 }
 
 /**
@@ -3103,7 +3383,7 @@ bool CMergeDoc::OpenWithUnpackerDialog()
        // let the user choose a handler
        CSelectUnpackerDlg dlg(m_filePaths[0], nullptr);
        // create now a new infoUnpacker to initialize the manual/automatic flag
-       PackingInfo infoUnpacker(PLUGIN_AUTO);
+       PackingInfo infoUnpacker(PLUGIN_MODE::PLUGIN_AUTO);
        dlg.SetInitialInfoHandler(&infoUnpacker);
 
        if (dlg.DoModal() == IDOK)
@@ -3148,8 +3428,8 @@ void CMergeDoc::OnFileReload()
                fileloc[pane].setPath(m_filePaths[pane]);
        }
        CPoint pt = GetActiveMergeView()->GetCursorPos();
-       OpenDocs(m_nBuffers, fileloc, bRO, m_strDesc);
-       MoveOnLoad(GetActiveMergeView()->m_nThisPane, pt.y);
+       if (OpenDocs(m_nBuffers, fileloc, bRO, m_strDesc))
+               MoveOnLoad(GetActiveMergeView()->m_nThisPane, pt.y);
 }
 
 /**
@@ -3167,12 +3447,16 @@ void CMergeDoc::OnCtxtOpenWithUnpacker()
 
 void CMergeDoc::OnBnClickedFileEncoding()
 {
+       if (m_pEncodingErrorBar == nullptr || m_pView[0][0] == nullptr)
+               return;
        m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
        DoFileEncodingDialog();
 }
 
 void CMergeDoc::OnBnClickedPlugin()
 {
+       if (m_pEncodingErrorBar == nullptr || m_pView[0][0] == nullptr)
+               return;
        m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
        OpenWithUnpackerDialog();
 }
@@ -3184,16 +3468,51 @@ void CMergeDoc::OnBnClickedHexView()
 
 void CMergeDoc::OnOK()
 {
+       if (m_pEncodingErrorBar == nullptr || m_pView[0][0] == nullptr)
+               return;
        m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
 }
 
+void CMergeDoc::OnFileRecompareAsText()
+{
+       m_bEnableTableEditing = false;
+       PackingInfo infoUnpacker;
+       SetUnpacker(&infoUnpacker);
+       OnFileReload();
+}
+
+void CMergeDoc::OnUpdateFileRecompareAsText(CCmdUI *pCmdUI)
+{
+       pCmdUI->Enable(m_pInfoUnpacker->m_PluginOrPredifferMode == PLUGIN_MODE::PLUGIN_BUILTIN_XML ||
+               m_ptBuf[0]->GetTableEditing());
+}
+
+void CMergeDoc::OnFileRecompareAsTable()
+{
+       m_bEnableTableEditing = true;
+       PackingInfo infoUnpacker;
+       SetUnpacker(&infoUnpacker);
+       OnFileReload();
+}
+
+void CMergeDoc::OnUpdateFileRecompareAsTable(CCmdUI *pCmdUI)
+{
+       pCmdUI->Enable(!m_ptBuf[0]->GetTableEditing());
+}
+
 void CMergeDoc::OnFileRecompareAsXML()
 {
-       PackingInfo infoUnpacker(PLUGIN_BUILTIN_XML);
+       m_bEnableTableEditing = false;
+       PackingInfo infoUnpacker(PLUGIN_MODE::PLUGIN_BUILTIN_XML);
        SetUnpacker(&infoUnpacker);
        OnFileReload();
 }
 
+void CMergeDoc::OnUpdateFileRecompareAsXML(CCmdUI *pCmdUI)
+{
+       pCmdUI->Enable(m_pInfoUnpacker->m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_BUILTIN_XML);
+}
+
 void CMergeDoc::OnFileRecompareAs(UINT nID)
 {
        DWORD dwFlags[3] = { 0 };
@@ -3205,10 +3524,7 @@ void CMergeDoc::OnFileRecompareAs(UINT nID)
        }
        if (m_pEncodingErrorBar!=nullptr && m_pEncodingErrorBar->IsWindowVisible())
                m_pView[0][0]->GetParentFrame()->ShowControlBar(m_pEncodingErrorBar.get(), FALSE, FALSE);
-       if (nID == ID_MERGE_COMPARE_HEX)
-               GetMainFrame()->ShowHexMergeDoc(m_pDirDoc, m_nBuffers, fileloc, dwFlags, m_strDesc);
-       else
-               GetMainFrame()->ShowImgMergeDoc(m_pDirDoc, m_nBuffers, fileloc, dwFlags, m_strDesc);
+       GetMainFrame()->ShowMergeDoc(nID, m_pDirDoc, m_nBuffers, fileloc, dwFlags, m_strDesc);
        GetParentFrame()->ShowWindow(SW_RESTORE);
        GetParentFrame()->DestroyWindow();
 }
@@ -3255,18 +3571,16 @@ bool CMergeDoc::GenerateReport(const String& sFileName) const
                _T("<title>WinMerge File Compare Report</title>\n")
                _T("<style type=\"text/css\">\n")
                _T("<!--\n")
-               _T("td,th {word-break: break-all; font-size: %dpt;}\n")
+               _T("table {margin: 0; border: 1px solid #a0a0a0; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.15);}\n")
+               _T("td,th {word-break: break-all; font-size: %dpt;padding: 0 3px;}\n")
                _T("tr { vertical-align: top; }\n")
-               _T(".border { border-radius: 6px; border: 1px #a0a0a0 solid; box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.15); overflow: hidden; }\n")
-               _T(".ln {text-align: right; word-break: normal; background-color: lightgrey; box-shadow: inset 1px 0px 0px rgba(0, 0, 0, 0.10);}\n")
                _T(".title {color: white; background-color: blue; vertical-align: top; padding: 4px 4px; background: linear-gradient(mediumblue, darkblue);}\n")
                _T("%s")
                _T("-->\n")
                _T("</style>\n")
                _T("</head>\n")
                _T("<body>\n")
-               _T("<div class=\"border\">")
-               _T("<table cellspacing=\"0\" cellpadding=\"0\" style=\"width: 100%%; margin: 0; border: none;\">\n")
+               _T("<table cellspacing=\"0\" cellpadding=\"0\" style=\"width:100%%;\">\n")
                _T("<thead>\n")
                _T("<tr>\n");
        String header = 
@@ -3291,16 +3605,12 @@ bool CMergeDoc::GenerateReport(const String& sFileName) const
                }
        }
 
-       // left and right title
+       // titles
        int nBuffer;
        for (nBuffer = 0; nBuffer < m_nBuffers; nBuffer++)
        {
-               int nLineNumberColumnWidth = 1;
-               String data = strutils::format(_T("<th class=\"title\" style=\"width:%d%%\"></th>"), 
-                       nLineNumberColumnWidth);
-               file.WriteString(data);
-               data = strutils::format(_T("<th class=\"title\" style=\"width:%f%%\">"),
-                       (double)(100 - nLineNumberColumnWidth * m_nBuffers) / m_nBuffers);
+               String data = strutils::format(_T("<th colspan=\"2\" class=\"title\" style=\"width:%f%%\">"),
+                       (double)100 / m_nBuffers);
                file.WriteString(data);
                file.WriteString(ucr::toTString(CMarkdown::Entities(ucr::toUTF8(paths[nBuffer]))));
                file.WriteString(_T("</th>\n"));
@@ -3331,21 +3641,32 @@ bool CMergeDoc::GenerateReport(const String& sFileName) const
                        if (idx[nBuffer] < nLineCount[nBuffer])
                        {
                                // line number
+                               int iVisibleLineNumber = 0;
                                String tdtag = _T("<td class=\"ln\">");
                                DWORD dwFlags = m_ptBuf[nBuffer]->GetLineFlags(idx[nBuffer]);
-                               if (nBuffer == 0 && 
-                                    (dwFlags & (LF_DIFF | LF_GHOST))!=0 && (idx[nBuffer] == 0 || 
-                                   (m_ptBuf[nBuffer]->GetLineFlags(idx[nBuffer] - 1) & (LF_DIFF | LF_GHOST))==0 ))
+                               if ((dwFlags & LF_GHOST) == 0 && m_pView[0][nBuffer]->GetViewLineNumbers())
+                               {
+                                       iVisibleLineNumber = m_ptBuf[nBuffer]->ComputeRealLine(idx[nBuffer]) + 1;
+                               }
+                               if (nBuffer == 0 &&
+                                       (dwFlags & (LF_DIFF | LF_GHOST)) != 0 && (idx[nBuffer] == 0 ||
+                                       (m_ptBuf[nBuffer]->GetLineFlags(idx[nBuffer] - 1) & (LF_DIFF | LF_GHOST)) == 0))
                                {
                                        ++nDiff;
-                                       tdtag += strutils::format(_T("<a name=\"d%d\" href=\"#d%d\">.</a>"), nDiff, nDiff);
+                                       if (iVisibleLineNumber > 0)
+                                       {
+                                               tdtag += strutils::format(_T("<a name=\"d%d\" href=\"#d%d\">%d</a>"), nDiff, nDiff, iVisibleLineNumber);
+                                               iVisibleLineNumber = 0;
+                                       }
+                                       else
+                                               tdtag += strutils::format(_T("<a name=\"d%d\" href=\"#d%d\">.</a>"), nDiff, nDiff);
                                }
-                               if ((dwFlags & LF_GHOST)==0 && m_pView[0][nBuffer]->GetViewLineNumbers())
-                                       tdtag += strutils::format(_T("%d</td>"), m_ptBuf[nBuffer]->ComputeRealLine(idx[nBuffer]) + 1);
+                               if (iVisibleLineNumber > 0)
+                                       tdtag += strutils::format(_T("%d</td>"), iVisibleLineNumber);
                                else
                                        tdtag += _T("</td>");
                                file.WriteString(tdtag);
-                               // write a line on left/right side
+                               // line content
                                file.WriteString((LPCTSTR)m_pView[0][nBuffer]->GetHTMLLine(idx[nBuffer], _T("td")));
                                idx[nBuffer]++;
                        }
@@ -3381,7 +3702,6 @@ bool CMergeDoc::GenerateReport(const String& sFileName) const
        file.WriteString(
                _T("</tbody>\n")
                _T("</table>\n")
-               _T("</div>")
                _T("</body>\n")
                _T("</html>\n"));
 
@@ -3401,9 +3721,8 @@ void CMergeDoc::OnToolsGenerateReport()
        if (!SelectFile(AfxGetMainWnd()->GetSafeHwnd(), s, false, folder, _T(""), _("HTML Files (*.htm,*.html)|*.htm;*.html|All Files (*.*)|*.*||"), _T("htm")))
                return;
 
-       GenerateReport(s.c_str());
-
-       LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION);
+       if (GenerateReport(s.c_str()))
+               LangMessageBox(IDS_REPORT_SUCCESS, MB_OK | MB_ICONINFORMATION);
 }
 
 /**
@@ -3436,13 +3755,22 @@ void CMergeDoc::AddSyncPoint()
        int nLine[3];
        for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
        {
-                int tmp = m_pView[0][nBuffer]->GetCursorPos().y;
-                nLine[nBuffer] = m_ptBuf[nBuffer]->ComputeApparentLine(m_ptBuf[nBuffer]->ComputeRealLine(tmp));
+               int tmp = m_pView[0][nBuffer]->GetCursorPos().y;
+               nLine[nBuffer] = m_ptBuf[nBuffer]->ComputeApparentLine(m_ptBuf[nBuffer]->ComputeRealLine(tmp));
+       }
 
+       // If adding a sync point by selecting a ghost line that is after the last block, Cancel the process adding a sync point.
+       for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
+               if (nLine[nBuffer] >= m_ptBuf[nBuffer]->GetLineCount())
+               {
+                       LangMessageBox(IDS_SYNCPOINT_LASTBLOCK, MB_ICONSTOP);
+                       return;
+               }
+
+       for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
                if (m_ptBuf[nBuffer]->GetLineFlags(nLine[nBuffer]) & LF_INVALID_BREAKPOINT)
                        DeleteSyncPoint(nBuffer, nLine[nBuffer], false);
-       }
-       
+
        for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
                m_ptBuf[nBuffer]->SetLineFlag(nLine[nBuffer], LF_INVALID_BREAKPOINT, true, false);
 
@@ -3499,14 +3827,6 @@ void CMergeDoc::ClearSyncPoints()
        FlushAndRescan(true);
 }
 
-/**
- * @brief return true if there are synchronization points
- */
-bool CMergeDoc::HasSyncPoints()
-{
-       return m_bHasSyncPoints;
-}
-
 std::vector<std::vector<int> > CMergeDoc::GetSyncPointList()
 {
        std::vector<std::vector<int> > list;