OSDN Git Service

Fix the problem that the BS key does not work at the beginning of the line after...
[winmerge-jp/winmerge-jp.git] / Src / MergeEditView.cpp
index 63228a2..b6fc755 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  MergeEditView.cpp
 #include "DropHandler.h"
 #include "DirDoc.h"
 #include "ShellContextMenu.h"
+#include "editcmd.h"
+#include "Shell.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #endif
 
+#ifndef WM_MOUSEHWHEEL
+#  define WM_MOUSEHWHEEL 0x20e
+#endif
+
 using std::vector;
 using CrystalLineParser::TEXTBLOCK;
 
@@ -72,11 +64,11 @@ CMergeEditView::CMergeEditView()
 , m_nThisGroup(0)
 , m_bDetailView(false)
 , m_piMergeEditStatus(nullptr)
-, m_bAutomaticRescan(false)
 , fTimerWaitingForIdle(0)
 , m_lineBegin(0)
 , m_lineEnd(-1)
 , m_CurrentPredifferID(0)
+, m_bChangedSchemeManually(false)
 {
        SetParser(&m_xParser);
        
@@ -137,6 +129,7 @@ BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
        ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
        ON_WM_LBUTTONDBLCLK()
        ON_WM_LBUTTONUP()
+       ON_WM_RBUTTONDOWN()
        ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
        ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
        ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
@@ -145,12 +138,20 @@ BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
        ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
        ON_COMMAND(ID_L2R, OnL2r)
        ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
+       ON_COMMAND(ID_LINES_L2R, OnLinesL2r)
+       ON_UPDATE_COMMAND_UI(ID_LINES_L2R, OnUpdateLinesL2r)
        ON_COMMAND(ID_R2L, OnR2l)
        ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
+       ON_COMMAND(ID_LINES_R2L, OnLinesR2l)
+       ON_UPDATE_COMMAND_UI(ID_LINES_R2L, OnUpdateLinesR2l)
        ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
        ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
+       ON_COMMAND(ID_COPY_LINES_FROM_LEFT, OnCopyLinesFromLeft)
+       ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_LEFT, OnUpdateCopyLinesFromLeft)
        ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
        ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
+       ON_COMMAND(ID_COPY_LINES_FROM_RIGHT, OnCopyLinesFromRight)
+       ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_RIGHT, OnUpdateCopyLinesFromRight)
        ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
        ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
        ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
@@ -164,9 +165,11 @@ BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
        ON_COMMAND(ID_REFRESH, OnRefresh)
        ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
        ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
-       ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
+       ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
        ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
        ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
+       ON_COMMAND(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnAddToSubstitutionFilters)
+       ON_UPDATE_COMMAND_UI(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnUpdateAddToSubstitutionFilters)
        ON_WM_CONTEXTMENU()
        ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
        ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
@@ -190,6 +193,10 @@ BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
        ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
        ON_COMMAND(ID_NEXT_PANE, OnChangePane)
        ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
+       ON_COMMAND(ID_GOTO_MOVED_LINE_LM, OnGotoMovedLineLM)
+       ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_LM, OnUpdateGotoMovedLineLM)
+       ON_COMMAND(ID_GOTO_MOVED_LINE_MR, OnGotoMovedLineMR)
+       ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_MR, OnUpdateGotoMovedLineMR)
        ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
        ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
        ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
@@ -209,25 +216,32 @@ BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
        ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
        ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
        ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
+       ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
+       ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
        ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
        ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
        ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
-       ON_COMMAND(ID_VIEW_SWAPPANES, OnViewSwapPanes)
+       ON_COMMAND(ID_FILE_OPEN_PARENT_FOLDER, OnOpenParentFolder)
+       ON_COMMAND(ID_SWAPPANES_SWAP12, OnViewSwapPanes12)
+       ON_COMMAND(ID_SWAPPANES_SWAP23, OnViewSwapPanes23)
+       ON_COMMAND(ID_SWAPPANES_SWAP13, OnViewSwapPanes13)
        ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
        ON_WM_SIZE()
        ON_WM_MOVE()
        ON_COMMAND(ID_HELP, OnHelp)
-       ON_COMMAND(ID_VIEW_FILEMARGIN, OnViewMargin)
-       ON_UPDATE_COMMAND_UI(ID_VIEW_FILEMARGIN, OnUpdateViewMargin)
+       ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
+       ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
        ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
        ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
        ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
        ON_WM_MOUSEWHEEL()
+       ON_WM_MOUSEHWHEEL()
        ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
        ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
        ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
        ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
        ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
+       ON_NOTIFY(NM_DBLCLK, AFX_IDW_STATUS_BAR, OnStatusBarDblClick)
        //}}AFX_MSG_MAP
 END_MESSAGE_MAP()
 
@@ -255,6 +269,15 @@ CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
        return GetDocument()->m_ptBuf[m_nThisPane].get();
 }
 
+void CMergeEditView::CopyProperties(CCrystalTextView* pSource)
+{
+       __super::CopyProperties(pSource);
+       auto pSourceEditView = dynamic_cast<decltype(this)>(pSource);
+       if (!pSourceEditView)
+               return;
+       m_bChangedSchemeManually = pSourceEditView->m_bChangedSchemeManually;
+}
+
 /**
  * @brief Update any resources necessary after a GUI language change
  */
@@ -287,15 +310,98 @@ CString CMergeEditView::GetLineText(int idx)
  */
 CString CMergeEditView::GetSelectedText()
 {
-       CPoint ptStart, ptEnd;
        CString strText;
-       GetSelection(ptStart, ptEnd);
+       auto [ptStart, ptEnd] = GetSelection();
        if (ptStart != ptEnd)
                GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
        return strText;
 }
 
 /**
+ * @brief Return number of selected characters
+ */
+std::pair<int, int> CMergeEditView::GetSelectedLineAndCharacterCount()
+{
+       auto [ptStart, ptEnd] = GetSelection();
+       int nCharsOrColumns =0;
+       int nSelectedLines = 0;
+       for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
+       {
+               if ((GetLineFlags(nLine) & (LF_GHOST | LF_INVISIBLE)) == 0)
+               {
+                       int nLineLength = GetLineLength(nLine) + (m_pTextBuffer->GetLineEol(nLine)[0] ? 1 : 0);
+                       nCharsOrColumns += (nLine == ptEnd.y) ? ptEnd.x : nLineLength;
+                       if (nLine == ptStart.y)
+                               nCharsOrColumns -= ptStart.x;
+                       if (nLine < ptEnd.y || (ptStart != ptEnd && ptEnd.x > 0))
+                               ++nSelectedLines;
+               }
+       }
+       if (m_bRectangularSelection)
+       {
+               int nStartLeft, nStartRight, nEndLeft, nEndRight;
+               GetColumnSelection(ptStart.y, nStartLeft, nStartRight);
+               GetColumnSelection(ptEnd.y, nEndLeft, nEndRight);
+               nCharsOrColumns = (std::max)(nStartRight, nEndRight) - (std::min)(nStartLeft, nEndLeft);
+       }
+       return { nSelectedLines, nCharsOrColumns };
+}
+
+/**
+ * @brief Get diffs inside selection.
+ * @param [out] firstDiff First diff inside selection
+ * @param [out] lastDiff Last diff inside selection
+ * @note -1 is returned in parameters if diffs cannot be determined
+ * @todo This shouldn't be called when there is no diffs, so replace
+ * first 'if' with ASSERT()?
+ */
+void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
+{
+       firstDiff = -1;
+       lastDiff = -1;
+
+       CMergeDoc *pd = GetDocument();
+       const int nDiffs = pd->m_diffList.GetSignificantDiffs();
+       if (nDiffs == 0)
+               return;
+
+       int firstLine, lastLine;
+       GetFullySelectedLines(firstLine, lastLine);
+       if (lastLine < firstLine)
+               return;
+
+       firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
+       lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
+       if (firstDiff != -1 && lastDiff != -1)
+       {
+               DIFFRANGE di;
+
+               // Check that first selected line is first diff's first line or above it
+               VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
+               if ((int)di.dbegin < firstLine)
+               {
+                       if (firstDiff < lastDiff)
+                               ++firstDiff;
+               }
+
+               // Check that last selected line is last diff's last line or below it
+               VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
+               if ((int)di.dend > lastLine)
+               {
+                       if (firstDiff < lastDiff)
+                               --lastDiff;
+               }
+
+               // Special case: one-line diff is not selected if cursor is in it
+               if (firstLine == lastLine)
+               {
+                       firstDiff = -1;
+                       lastDiff = -1;
+               }
+       }
+}
+
+/**
  * @brief Get diffs inside selection.
  * @param [out] firstDiff First diff inside selection
  * @param [out] lastDiff Last diff inside selection
@@ -318,8 +424,7 @@ void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int
                return;
 
        int firstLine, lastLine;
-       CPoint ptStart, ptEnd;
-       GetSelection(ptStart, ptEnd);
+       auto [ptStart, ptEnd] = GetSelection();
        if (pptStart != nullptr)
                ptStart = *pptStart;
        if (pptEnd != nullptr)
@@ -328,6 +433,7 @@ void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int
        lastLine = ptEnd.y;
 
        firstDiff = pd->m_diffList.LineToDiff(firstLine);
+       bool firstLineIsNotInDiff = firstDiff == -1;
        if (firstDiff == -1)
        {
                firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
@@ -336,6 +442,7 @@ void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int
                firstWordDiff = 0;
        }
        lastDiff = pd->m_diffList.LineToDiff(lastLine);
+       bool lastLineIsNotInDiff = lastDiff == -1;      
        if (lastDiff == -1)
                lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
        if (lastDiff < firstDiff)
@@ -349,16 +456,28 @@ void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int
        {
                DIFFRANGE di;
                
-               if (ptStart != ptEnd)
+               if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
+               {
+                       firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
+               }
+               else if (ptStart != ptEnd)
                {
+                       VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
+                       if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
+                       {
+                               firstWordDiff = -1;
+                               return;
+                       }
+
                        if (firstWordDiff == -1)
                        {
-                               VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
                                vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
                                for (size_t i = 0; i < worddiffs.size(); ++i)
                                {
+                                       int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
                                        if (worddiffs[i].endline[m_nThisPane] > firstLine ||
-                                               (firstLine == worddiffs[i].endline[m_nThisPane] && worddiffs[i].end[m_nThisPane] - 1 >= ptStart.x))
+                                               (firstLine == worddiffs[i].endline[m_nThisPane] && 
+                                                worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
                                        {
                                                firstWordDiff = static_cast<int>(i);
                                                break;
@@ -416,13 +535,47 @@ void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int
        ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
 }
 
+void CMergeEditView::GetSelectedDiffs(int & firstDiff, int & lastDiff)
+{
+       firstDiff = -1;
+       lastDiff = -1;
+
+       CMergeDoc *pd = GetDocument();
+       const int nDiffs = pd->m_diffList.GetSignificantDiffs();
+       if (nDiffs == 0)
+               return;
+
+       int firstLine, lastLine;
+       auto [ptStart, ptEnd] = GetSelection();
+       firstLine = ptStart.y;
+       lastLine = ptEnd.y;
+
+       firstDiff = pd->m_diffList.LineToDiff(firstLine);
+       if (firstDiff == -1)
+       {
+               firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
+               if (firstDiff == -1)
+                       return;
+       }
+       lastDiff = pd->m_diffList.LineToDiff(lastLine);
+       if (lastDiff == -1)
+               lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
+       if (lastDiff < firstDiff)
+       {
+               firstDiff = -1;
+               return;
+       }
+
+       ASSERT(firstDiff == -1 ? (lastDiff  == -1) : true);
+       ASSERT(lastDiff  == -1 ? (firstDiff == -1) : true);
+}
+
 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
 {
        CMergeDoc *pDoc = GetDocument();
        std::map<int, std::vector<int>> ret;
        std::map<int, std::vector<int> *> list;
-       CPoint ptStart, ptEnd;
-       GetSelection(ptStart, ptEnd);
+       auto [ptStart, ptEnd] = GetSelection();
        for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
        {
                if (pDoc->m_diffList.LineToDiff(nLine) != -1)
@@ -474,6 +627,16 @@ void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView*
        pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
 }
 
+std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
+{
+       if (m_bDetailView)
+       {
+               if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
+                       return std::vector<CrystalLineParser::TEXTBLOCK>();
+       }
+       return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
+}
+
 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
 {
        static const std::vector<TEXTBLOCK> emptyBlocks;
@@ -571,9 +734,9 @@ std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
        return blocks;
 }
 
-COLORREF CMergeEditView::GetColor(int nColorIndex)
+COLORREF CMergeEditView::GetColor(int nColorIndex) const
 {
-       switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
+       switch (nColorIndex & ~COLORINDEX_MASK)
        {
        case COLORINDEX_HIGHLIGHTBKGND1:
                return m_cachedColors.clrSelWordDiff;
@@ -696,10 +859,7 @@ void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF
                        {
                                if (dwLineFlags & LF_MOVED)
                                {
-                                       if (dwLineFlags & LF_GHOST)
-                                               crBkgnd = m_cachedColors.clrSelMovedDeleted;
-                                       else
-                                               crBkgnd = m_cachedColors.clrSelMoved;
+                                       crBkgnd = m_cachedColors.clrSelMoved;
                                        crText = m_cachedColors.clrSelMovedText;
                                }
                                else
@@ -713,10 +873,7 @@ void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF
                        {
                                if (dwLineFlags & LF_MOVED)
                                {
-                                       if (dwLineFlags & LF_GHOST)
-                                               crBkgnd = m_cachedColors.clrMovedDeleted;
-                                       else
-                                               crBkgnd = m_cachedColors.clrMoved;
+                                       crBkgnd = m_cachedColors.clrMoved;
                                        crText = m_cachedColors.clrMovedText;
                                }
                                else
@@ -741,9 +898,19 @@ void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF
                else if (dwLineFlags & LF_GHOST)
                {
                        if (lineInCurrentDiff)
-                               crBkgnd = m_cachedColors.clrSelDiffDeleted;
+                       {
+                               if (dwLineFlags & LF_MOVED)
+                                       crBkgnd = m_cachedColors.clrSelMovedDeleted;
+                               else
+                                       crBkgnd = m_cachedColors.clrSelDiffDeleted;
+                       }
                        else
-                               crBkgnd = m_cachedColors.clrDiffDeleted;
+                       {
+                               if (dwLineFlags & LF_MOVED)
+                                       crBkgnd = m_cachedColors.clrMovedDeleted;
+                               else
+                                       crBkgnd = m_cachedColors.clrDiffDeleted;
+                       }
                        return;
                }
        }
@@ -873,32 +1040,28 @@ void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
                newlineEnd = curDiff.dend;
        }
 
-       if (newlineBegin == m_lineBegin && newlineEnd == m_lineEnd)
-               return;
        m_lineBegin = newlineBegin;
        m_lineEnd = newlineEnd;
 
-       // scroll to the first line of the diff
-       ScrollToLine(m_lineBegin);
+       int nLineCount = GetLineCount();
+       if (m_lineBegin > nLineCount)
+               m_lineBegin = nLineCount - 1;
+       if (m_lineEnd > nLineCount)
+               m_lineEnd = nLineCount - 1;
 
-       // tell the others views about this diff (no need to call UpdateSiblingScrollPos)
-       CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
+       if (m_nTopLine == newlineBegin)
+               return;
 
-       // pSplitterWnd is `nullptr` if WinMerge started minimized.
-       if (pSplitterWnd != nullptr)
-       {
-               int nRows = pSplitterWnd->GetRowCount ();
-               int nCols = pSplitterWnd->GetColumnCount ();
-               for (int nRow = 0; nRow < nRows; nRow++)
-               {
-                       for (int nCol = 0; nCol < nCols; nCol++)
-                       {
-                               CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
-                               if (pSiblingView != nullptr)
-                                       pSiblingView->OnDisplayDiff(nDiff);
-                       }
-               }
-       }
+       // scroll to the first line of the diff
+       vector<WordDiff> worddiffs;
+       if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
+               worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
+       CPoint pt = worddiffs.size() > 0 ?
+               CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } : 
+               CPoint{ 0, m_lineBegin };
+       ScrollToLine(m_lineBegin);
+       if (pt.x > 0)
+               EnsureVisible(pt);
 
        // update the width of the horizontal scrollbar
        RecalcHorzScrollBar();
@@ -931,7 +1094,24 @@ void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelect
        UpdateSiblingScrollPos(false);
 
        // notify either side, as it will notify the other one
-       pd->ForEachView (0, [&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
+       pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
+}
+
+void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
+{
+       CMergeDoc *pd = GetDocument();
+       // If we have a selected diff, deselect it
+       int nCurrentDiff = pd->GetCurrentDiff();
+       if (nCurrentDiff != -1)
+       {
+               CPoint pos = GetCursorPos();
+               if (!IsLineInCurrentDiff(pos.y))
+               {
+                       pd->SetCurrentDiff(-1);
+                       Invalidate();
+                       pd->UpdateAllViews(this);
+               }
+       }
 }
 
 /**
@@ -989,8 +1169,7 @@ void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
 void CMergeEditView::OnEditCopy()
 {
        CMergeDoc * pDoc = GetDocument();
-       CPoint ptSelStart, ptSelEnd;
-       GetSelection(ptSelStart, ptSelEnd);
+       auto [ptSelStart, ptSelEnd] = GetSelection();
 
        // Nothing selected
        if (ptSelStart == ptSelEnd)
@@ -998,7 +1177,7 @@ void CMergeEditView::OnEditCopy()
 
        CString text;
 
-       if (!m_bColumnSelection)
+       if (!m_bRectangularSelection)
        {
                CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
 
@@ -1008,7 +1187,7 @@ void CMergeEditView::OnEditCopy()
        else
                GetTextWithoutEmptysInColumnSelection(text);
 
-       PutToClipboard(text, text.GetLength(), m_bColumnSelection);
+       PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
 }
 
 /**
@@ -1024,27 +1203,26 @@ void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
  */
 void CMergeEditView::OnEditCut()
 {
-       if (IsReadOnly(m_nThisPane))
+       if (!QueryEditable())
                return;
 
-       CPoint ptSelStart, ptSelEnd;
        CMergeDoc * pDoc = GetDocument();
-       GetSelection(ptSelStart, ptSelEnd);
+       auto [ptSelStart, ptSelEnd] = GetSelection();
 
        // Nothing selected
        if (ptSelStart == ptSelEnd)
                return;
 
        CString text;
-       if (!m_bColumnSelection)
+       if (!m_bRectangularSelection)
                pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
                        ptSelEnd.y, ptSelEnd.x, text);
        else
                GetTextWithoutEmptysInColumnSelection(text);
 
-       PutToClipboard(text, text.GetLength(), m_bColumnSelection);
+       PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
 
-       if (!m_bColumnSelection)
+       if (!m_bRectangularSelection)
        {
                CPoint ptCursorPos = ptSelStart;
                ASSERT_VALIDTEXTPOS(ptCursorPos);
@@ -1067,7 +1245,7 @@ void CMergeEditView::OnEditCut()
  */
 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
 {
-       if (!IsReadOnly(m_nThisPane))
+       if (QueryEditable())
                CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
        else
                pCmdUI->Enable(false);
@@ -1078,7 +1256,7 @@ void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
  */
 void CMergeEditView::OnEditPaste()
 {
-       if (IsReadOnly(m_nThisPane))
+       if (!QueryEditable())
                return;
 
        CCrystalEditViewEx::Paste();
@@ -1090,7 +1268,7 @@ void CMergeEditView::OnEditPaste()
  */
 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
 {
-       if (!IsReadOnly(m_nThisPane))
+       if (QueryEditable())
                CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
        else
                pCmdUI->Enable(false);
@@ -1106,7 +1284,7 @@ void CMergeEditView::OnEditUndo()
        CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
        if(tgt==this)
        {
-               if (IsReadOnly(m_nThisPane))
+               if (!QueryEditable())
                        return;
 
                GetParentFrame()->SetActiveView(this, true);
@@ -1740,24 +1918,23 @@ void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
  */
 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
 {
-       CMergeDoc *pd = GetDocument();
        CCrystalEditViewEx::OnLButtonUp(nFlags, point);
+       DeselectDiffIfCursorNotInCurrentDiff();
+}
 
-       // If we have a selected diff, deselect it
-       int nCurrentDiff = pd->GetCurrentDiff();
-       if (nCurrentDiff != -1)
-       {
-               CPoint pos = GetCursorPos();
-               if (!IsLineInCurrentDiff(pos.y))
-               {
-                       pd->SetCurrentDiff(-1);
-                       Invalidate();
-                       pd->UpdateAllViews(this);
-               }
-       }
+/**
+ * @brief Called when mouse right button is pressed.
+ *
+ * If right button is pressed outside diffs, current diff
+ * is deselected.
+ */
+void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
+{
+       CCrystalEditViewEx::OnRButtonDown(nFlags, point);
+       DeselectDiffIfCursorNotInCurrentDiff();
 }
 
-void CMergeEditView::OnX2Y(int srcPane, int dstPane)
+void CMergeEditView::OnX2Y(int srcPane, int dstPane, bool selectedLineOnly)
 {
        // Check that right side is not readonly
        if (IsReadOnly(dstPane))
@@ -1777,16 +1954,42 @@ void CMergeEditView::OnX2Y(int srcPane, int dstPane)
                }
        }
 
-       if (IsSelection())
+       auto [ptStart, ptEnd] = GetSelection();
+       if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
        {
-               int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
-               if (!m_bColumnSelection)
+               if (!m_bRectangularSelection)
                {
-                       GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
-                       if (firstDiff != -1 && lastDiff != -1)
+                       if (selectedLineOnly)
                        {
-                               CWaitCursor waitstatus;
-                               pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
+                               int firstDiff, lastDiff;
+                               GetSelectedDiffs(firstDiff, lastDiff);
+                               if (firstDiff != -1 && lastDiff != -1)
+                               {
+                                       CWaitCursor waitstatus;
+                                       pDoc->CopyMultiplePartialList(srcPane, dstPane, firstDiff, lastDiff, ptStart.y, ptEnd.y);
+                               }
+                       }
+                       else
+                       {
+                               int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
+                               GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
+                               if (firstDiff != -1 && lastDiff != -1)
+                               {
+                                       CWaitCursor waitstatus;
+                                       
+                                       // Setting CopyFullLine (OPT_COPY_FULL_LINE)
+                                       // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
+                                       if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
+                                       {
+                                               // old behaviour: copy full line
+                                               pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
+                                       }
+                                       else
+                                       {
+                                               // new behaviour: copy selected text only
+                                               pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
+                                       }
+                               }
                        }
                }
                else
@@ -1802,8 +2005,16 @@ void CMergeEditView::OnX2Y(int srcPane, int dstPane)
        }
        else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
        {
-               CWaitCursor waitstatus;
-               pDoc->ListCopy(srcPane, dstPane, currentDiff);
+               if (selectedLineOnly)
+               {
+                       CWaitCursor waitstatus;
+                       pDoc->PartialListCopy(srcPane, dstPane, currentDiff, ptStart.y, ptEnd.y);
+               }
+               else
+               {
+                       CWaitCursor waitstatus;
+                       pDoc->ListCopy(srcPane, dstPane, currentDiff);
+               }
        }
 }
 
@@ -1815,20 +2026,25 @@ void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
                // If one or more diffs inside selection OR
                // there is an active diff OR
                // cursor is inside diff
-               if (IsSelection())
+               auto [ptStart, ptEnd] = GetSelection();
+               if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
                {
-                       int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
-                       GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
+                       if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
+                       {
+                               pCmdUI->Enable(true);
+                       }
+                       else
+                       {
+                               int firstDiff, lastDiff;
+                               GetFullySelectedDiffs(firstDiff, lastDiff);
 
-                       pCmdUI->Enable(firstDiff != -1 && lastDiff != -1);
+                               pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
+                       }
                }
                else
                {
                        const int currDiff = GetDocument()->GetCurrentDiff();
-                       if (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff))
-                               pCmdUI->Enable(true);
-                       else
-                               pCmdUI->Enable(m_bCurrentLineIsDiff);
+                       pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
                }
        }
        else
@@ -1861,6 +2077,18 @@ void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
        OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
 }
 
+void CMergeEditView::OnLinesL2r()
+{
+       int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
+       int srcPane = dstPane - 1;
+       OnX2Y(srcPane, dstPane, true);
+}
+
+void CMergeEditView::OnUpdateLinesL2r(CCmdUI* pCmdUI)
+{
+       OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
+}
+
 /**
  * @brief Copy diff from right pane to left pane
  *
@@ -1887,6 +2115,18 @@ void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
        OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
 }
 
+void CMergeEditView::OnLinesR2l()
+{
+       int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
+       int srcPane = dstPane + 1;
+       OnX2Y(srcPane, dstPane, true);
+}
+
+void CMergeEditView::OnUpdateLinesR2l(CCmdUI* pCmdUI)
+{
+       OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
+}
+
 void CMergeEditView::OnCopyFromLeft()
 {
        int dstPane = m_nThisPane;
@@ -1906,6 +2146,25 @@ void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
                OnUpdateX2Y(dstPane, pCmdUI);
 }
 
+void CMergeEditView::OnCopyLinesFromLeft()
+{
+       int dstPane = m_nThisPane;
+       int srcPane = dstPane - 1;
+       if (srcPane < 0)
+               return;
+       OnX2Y(srcPane, dstPane, true);
+}
+
+void CMergeEditView::OnUpdateCopyLinesFromLeft(CCmdUI* pCmdUI)
+{
+       int dstPane = m_nThisPane;
+       int srcPane = dstPane - 1;
+       if (srcPane < 0)
+               pCmdUI->Enable(false);
+       else
+               OnUpdateX2Y(dstPane, pCmdUI);
+}
+
 void CMergeEditView::OnCopyFromRight()
 {
        int dstPane = m_nThisPane;
@@ -1925,6 +2184,25 @@ void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
                OnUpdateX2Y(dstPane, pCmdUI);
 }
 
+void CMergeEditView::OnCopyLinesFromRight()
+{
+       int dstPane = m_nThisPane;
+       int srcPane = dstPane + 1;
+       if (srcPane >= GetDocument()->m_nBuffers)
+               return;
+       OnX2Y(srcPane, dstPane, true);
+}
+
+void CMergeEditView::OnUpdateCopyLinesFromRight(CCmdUI* pCmdUI)
+{
+       int dstPane = m_nThisPane;
+       int srcPane = dstPane + 1;
+       if (srcPane >= GetDocument()->m_nBuffers)
+               pCmdUI->Enable(false);
+       else
+               OnUpdateX2Y(dstPane, pCmdUI);
+}
+
 /**
  * @brief Copy all diffs from right pane to left pane
  */
@@ -1988,7 +2266,7 @@ void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
 void CMergeEditView::OnAutoMerge()
 {
        // Check current pane is not readonly
-       if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || IsReadOnly(m_nThisPane))
+       if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
                return;
 
        CWaitCursor waitstatus;
@@ -2004,7 +2282,7 @@ void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
        pCmdUI->Enable(GetDocument()->m_nBuffers == 3 && 
                !GetDocument()->IsModified() && 
                !GetDocument()->GetAutoMerged() && 
-               !IsReadOnly(m_nThisPane));
+               QueryEditable());
 }
 
 /**
@@ -2040,7 +2318,7 @@ void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
  */
 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
 {
-       if (IsReadOnly(m_nThisPane))
+       if (!QueryEditable())
        {
                // We must not arrive here, and assert helps detect troubles
                ASSERT(false);
@@ -2069,7 +2347,7 @@ void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchTex
        pDoc->UpdateHeaderPath(m_nThisPane);
 
        // If automatic rescan enabled, rescan after edit events
-       if (m_bAutomaticRescan)
+       if (pDoc->GetAutomaticRescan())
        {
                // keep document up to date     
                // (Re)start timer to rescan only when user edits text
@@ -2116,7 +2394,7 @@ void CMergeEditView::OnEditRedo()
        CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
        if(tgt==this)
        {
-               if (IsReadOnly(m_nThisPane))
+               if (!QueryEditable())
                        return;
 
                GetParentFrame()->SetActiveView(this, true);
@@ -2182,7 +2460,7 @@ void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
                ptEnd.x = 0;
                ptEnd.y = curDiff.dend;
 
-               if (bScroll)
+               if (bScroll && !m_bDetailView)
                {
                        if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
                        {
@@ -2203,16 +2481,29 @@ void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
                                                GetGroupView(nPane)->ScrollToSubLine(nLine);
                                }
                        }
-                       GetGroupView(m_nThisPane)->SetCursorPos(ptStart);
-                       GetGroupView(m_nThisPane)->SetAnchor(ptStart);
-                       GetGroupView(m_nThisPane)->SetSelection(ptStart, ptStart);
+
+                       vector<WordDiff> worddiffs;
+                       if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
+                               worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
+                       CPoint pt = worddiffs.size() > 0 ?
+                               CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
+                               ptStart;
+                       GetGroupView(m_nThisPane)->SetCursorPos(pt);
+                       GetGroupView(m_nThisPane)->SetAnchor(pt);
+                       GetGroupView(m_nThisPane)->SetSelection(pt, pt);
+                       GetGroupView(m_nThisPane)->EnsureVisible(pt);
                        for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
                        {
                                if (nPane != m_nThisPane)
                                {
-                                       GetGroupView(nPane)->SetCursorPos(ptStart);
-                                       GetGroupView(nPane)->SetAnchor(ptStart);
-                                       GetGroupView(nPane)->SetSelection(ptStart, ptStart);
+                                       if (worddiffs.size() > 0)
+                                       {
+                                               pt.x = worddiffs[0].begin[nPane];
+                                               pt.y = worddiffs[0].beginline[nPane];
+                                       }
+                                       GetGroupView(nPane)->SetCursorPos(pt);
+                                       GetGroupView(nPane)->SetAnchor(pt);
+                                       GetGroupView(nPane)->SetSelection(pt, pt);
                                }
                        }
                }
@@ -2270,7 +2561,7 @@ void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
  */
 bool CMergeEditView::IsReadOnly(int pane) const
 {
-       return GetDocument()->m_ptBuf[pane]->GetReadOnly() != false;
+       return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
 }
 
 /**
@@ -2312,16 +2603,6 @@ void CMergeEditView::OnRefresh()
 }
 
 /**
- * @brief Enable/Disable automatic rescanning
- */
-bool CMergeEditView::EnableRescan(bool bEnable)
-{
-       bool bOldValue = m_bAutomaticRescan;
-       m_bAutomaticRescan = bEnable;
-       return bOldValue;
-}
-
-/**
  * @brief Handle some keys when in merging mode
  */
 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
@@ -2386,8 +2667,8 @@ BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
                // Close window if user has allowed it from options
                if (pMsg->wParam == VK_ESCAPE)
                {
-                       bool bCloseWithEsc = GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC);
-                       if (bCloseWithEsc)
+                       int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
+                       if (nCloseWithEsc != 0)
                                GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
                        return false;
                }
@@ -2520,6 +2801,7 @@ void CMergeEditView::OnUpdateCaret()
        int column = -1;
        int columns = -1;
        int curChar = -1;
+       auto [selectedLines, selectedChars] = GetSelectedLineAndCharacterCount();
        DWORD dwLineFlags = 0;
 
        dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
@@ -2548,7 +2830,8 @@ void CMergeEditView::OnUpdateCaret()
                        sEol = _T("hidden");
        }
        m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
-               curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
+               curChar, chars, selectedLines, selectedChars,
+               sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
 
        // Is cursor inside difference?
        if (dwLineFlags & LF_NONTRIVIAL_DIFF)
@@ -2556,7 +2839,9 @@ void CMergeEditView::OnUpdateCaret()
        else
                m_bCurrentLineIsDiff = false;
 
-       UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
+       CWnd* pWnd = GetFocus();
+       if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
+               UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
 }
 /**
  * @brief Select linedifference in the current line.
@@ -2574,11 +2859,18 @@ void CMergeEditView::OnSelectLineDiff()
 /// Enable select difference menuitem if current line is inside difference.
 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
 {
-       int line = GetCursorPos().y;
-       bool enable = ((GetLineFlags(line) & (LF_DIFF | LF_GHOST)) != 0);
-       if (GetDocument()->IsEditedAfterRescan(m_nThisPane))
-               enable = false;
-       pCmdUI->Enable(enable);
+       pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
+}
+
+void CMergeEditView::OnAddToSubstitutionFilters()
+{
+       // Pass this to the document, to compare this file to other
+       GetDocument()->AddToSubstitutionFilters(this, false);
+}
+
+void CMergeEditView::OnUpdateAddToSubstitutionFilters(CCmdUI* pCmdUI)
+{
+       pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
 }
 
 /**
@@ -2607,8 +2899,7 @@ void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
 {
        // get scripts list
-       std::vector<String> functionNamesList;
-       FileTransform::GetFreeFunctionsInScripts(functionNamesList, L"EDITOR_SCRIPT");
+       std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
 
        // empty the menu
        size_t i = GetMenuItemCount(hMenu);
@@ -2719,7 +3010,7 @@ HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
        PrediffingInfo prediffer;
        pd->GetPrediffer(&prediffer);
 
-       if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
+       if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
                m_CurrentPredifferID = 0;
        else if (prediffer.m_PluginName.empty())
                m_CurrentPredifferID = ID_NO_PREDIFFER;
@@ -2758,13 +3049,26 @@ void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
        {
                menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
                menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
+               menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
+               menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
        }
        if (m_nThisPane == GetDocument()->m_nBuffers - 1)
        {
                menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
                menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
+               menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
+               menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
        }
 
+       // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
+       // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
+       // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
+       int nBuffers = GetDocument()->m_nBuffers;
+       if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
+               menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
+       else if (nBuffers == 3 && m_nThisPane == 2)
+               menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
+
        VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
        theApp.TranslateMenu(menu.m_hMenu);
 
@@ -2800,17 +3104,17 @@ void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
  */
 void CMergeEditView::OnConvertEolTo(UINT nID )
 {
-       CRLFSTYLE nStyle = CRLF_STYLE_AUTOMATIC;;
+       CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;;
        switch (nID)
        {
                case ID_EOL_TO_DOS:
-                       nStyle = CRLF_STYLE_DOS;
+                       nStyle = CRLFSTYLE::DOS;
                        break;
                case ID_EOL_TO_UNIX:
-                       nStyle = CRLF_STYLE_UNIX;
+                       nStyle = CRLFSTYLE::UNIX;
                        break;
                case ID_EOL_TO_MAC:
-                       nStyle = CRLF_STYLE_MAC;
+                       nStyle = CRLFSTYLE::MAC;
                        break;
                default:
                        // Catch errors
@@ -2834,17 +3138,17 @@ void CMergeEditView::OnConvertEolTo(UINT nID )
  */
 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
 {
-       int nStyle = CRLF_STYLE_AUTOMATIC;
+       CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;
        switch (pCmdUI->m_nID)
        {
                case ID_EOL_TO_DOS:
-                       nStyle = CRLF_STYLE_DOS;
+                       nStyle = CRLFSTYLE::DOS;
                        break;
                case ID_EOL_TO_UNIX:
-                       nStyle = CRLF_STYLE_UNIX;
+                       nStyle = CRLFSTYLE::UNIX;
                        break;
                case ID_EOL_TO_MAC:
-                       nStyle = CRLF_STYLE_MAC;
+                       nStyle = CRLFSTYLE::MAC;
                        break;
                default:
                        // Catch errors
@@ -2859,7 +3163,7 @@ void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
                pCmdUI->SetRadio(false);
 
                // Don't allow selecting other EOL style for protected pane
-               if (IsReadOnly(m_nThisPane))
+               if (!QueryEditable())
                        pCmdUI->Enable(false);
        }
        else
@@ -2983,7 +3287,8 @@ void CMergeEditView::OnWMGoto()
                        if (nRealLine1 > nLastLine)
                                nRealLine1 = nLastLine;
 
-                       GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
+                       bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
+                       GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile, !bShift);
                }
                else
                {
@@ -2998,6 +3303,139 @@ void CMergeEditView::OnWMGoto()
        }
 }
 
+/**
+* @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
+* Go to moved line between the left and right panes when in 2-way file comparison.
+* Go to moved line between the left and middle panes when in 3-way file comparison.
+*/
+void CMergeEditView::OnGotoMovedLineLM()
+{
+       if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
+               return;
+
+       CMergeDoc* pDoc = GetDocument();
+       CPoint pos = GetCursorPos();
+
+       ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
+       ASSERT(pDoc != nullptr);
+       ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
+       ASSERT(pos.y >= 0);
+
+       if (m_nThisPane == 0)
+       {
+               int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
+               if (line >= 0)
+                       GotoLine(line, false, 1);
+       }
+       else if (m_nThisPane == 1)
+       {
+               int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
+               if (line >= 0)
+                       GotoLine(line, false, 0);
+       }
+}
+
+/**
+ * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
+ * @param [in] pCmdUI UI component to update.
+ * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
+ */
+void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
+{
+       CMergeDoc* pDoc = GetDocument();
+       CPoint pos = GetCursorPos();
+
+       ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
+       ASSERT(pCmdUI != nullptr);
+       ASSERT(pDoc != nullptr);
+       ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
+       ASSERT(pos.y >= 0);
+
+       if (pDoc->m_nBuffers == 2)
+               pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
+
+       if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
+       {
+               pCmdUI->Enable(false);
+               return;
+       }
+
+       if (m_nThisPane == 0)
+       {
+               bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
+               pCmdUI->Enable(bOn);
+       }
+       else if (m_nThisPane == 1)
+       {
+               bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
+               pCmdUI->Enable(bOn);
+       }
+}
+
+/**
+* @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
+* Go to moved line between the middle and right panes when in 3-way file comparison.
+*/
+void CMergeEditView::OnGotoMovedLineMR()
+{
+       if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
+               return;
+
+       CMergeDoc* pDoc = GetDocument();
+       CPoint pos = GetCursorPos();
+
+       ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
+       ASSERT(pDoc != nullptr);
+       ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
+       ASSERT(pos.y >= 0);
+
+       if (m_nThisPane == 1)
+       {
+               int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
+               if (line >= 0)
+                       GotoLine(line, false, 2);
+       }
+       else if (m_nThisPane == 2)
+       {
+               int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
+               if (line >= 0)
+                       GotoLine(line, false, 1);
+       }
+}
+
+/**
+ * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
+ * @param [in] pCmdUI UI component to update.
+ */
+void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
+{
+       CMergeDoc* pDoc = GetDocument();
+       CPoint pos = GetCursorPos();
+
+       ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
+       ASSERT(pCmdUI != nullptr);
+       ASSERT(pDoc != nullptr);
+       ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
+       ASSERT(pos.y >= 0);
+
+       if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
+       {
+               pCmdUI->Enable(false);
+               return;
+       }
+
+       if (m_nThisPane == 1)
+       {
+               bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
+               pCmdUI->Enable(bOn);
+       }
+       else if (m_nThisPane == 2)
+       {
+               bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
+               pCmdUI->Enable(bOn);
+       }
+}
+
 void CMergeEditView::OnShellMenu()
 {
        CFrameWnd *pFrame = GetTopLevelFrame();
@@ -3031,7 +3469,8 @@ void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
  */
 void CMergeEditView::RefreshOptions()
 { 
-       m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
+       RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
+       SetRenderingMode(nRenderingMode);
 
        if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
                SetInsertTabs(true);
@@ -3041,13 +3480,26 @@ void CMergeEditView::RefreshOptions()
        SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
 
        if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
-               SetTextType(CCrystalTextView::SRC_PLAIN);
+               SetTextType(CrystalLineParser::SRC_PLAIN);
+       else if (!m_bChangedSchemeManually)
+       {
+               // The syntax highlighting scheme should only be applied if it has not been manually changed.
+               String fileName = GetDocument()->m_filePaths[m_nThisPane];
+               String sExt;
+               paths::SplitFilename(fileName, nullptr, nullptr, &sExt);
+               CrystalLineParser::TextDefinition* def = CrystalLineParser::GetTextType(sExt.c_str());
+               if (def != nullptr)
+                       SetTextType(def->type);
+               else
+                       SetTextType(CrystalLineParser::SRC_PLAIN);
+               SetDisableBSAtSOL(false);
+       }
 
        SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
        SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
 
        SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
-       SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE),
+       SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
                GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
                GetDocument()->IsMixedEOL(m_nThisPane));
 
@@ -3072,7 +3524,7 @@ void CMergeEditView::OnScripts(UINT nID )
 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
 {
        // append the scripts submenu
-       HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
+       HMENU scriptsSubmenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu->m_hMenu : nullptr;
        if (scriptsSubmenu != nullptr)
                createScriptsSubmenu(scriptsSubmenu);
 
@@ -3091,7 +3543,7 @@ void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
        PrediffingInfo prediffer;
        pd->GetPrediffer(&prediffer);
 
-       if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
+       if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
        {
                pCmdUI->SetRadio(false);
                return;
@@ -3145,7 +3597,7 @@ void CMergeEditView::SetPredifferByMenu(UINT nID )
                m_CurrentPredifferID = nID;
                // All flags are set correctly during the construction
                PrediffingInfo *infoPrediffer = new PrediffingInfo;
-               infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MANUAL;
+               infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
                infoPrediffer->m_PluginName.clear();
                pd->SetPrediffer(infoPrediffer);
                pd->FlushAndRescan(true);
@@ -3160,12 +3612,11 @@ void CMergeEditView::SetPredifferByMenu(UINT nID )
 
        // build a PrediffingInfo structure fom the ID
        PrediffingInfo prediffer;
-       prediffer.m_PluginOrPredifferMode = PLUGIN_MANUAL;
+       prediffer.m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
 
        size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
        if (pluginNumber < piScriptArray->size())
        {
-               prediffer.m_bWithFile = true;
                const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
                prediffer.m_PluginName = plugin->m_name;
        }
@@ -3174,7 +3625,6 @@ void CMergeEditView::SetPredifferByMenu(UINT nID )
                pluginNumber -= piScriptArray->size();
                if (pluginNumber >= piScriptArray2->size())
                        return;
-               prediffer.m_bWithFile = false;
                const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
                prediffer.m_PluginName = plugin->m_name;
        }
@@ -3234,8 +3684,9 @@ bool CMergeEditView::SetPredifferByName(const CString & prediffer)
  * @param [in] bRealLine if true linenumber is real line, otherwise
  * it is apparent line (including deleted lines)
  * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
+ * @param [in] bMoveAnchor if true the anchor is moved to nLine
  */
-void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
+void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane, bool bMoveAnchor)
 {
        CMergeDoc *pDoc = GetDocument();
        CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
@@ -3267,34 +3718,33 @@ void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
 
        for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
        {
-               CMergeEditView *pView = GetGroupView(nPane);
+               int nGroup = m_bDetailView ? 0 : m_nThisGroup;
+               CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
                pView->ScrollToSubLine(nScrollLine);
                if (ptPos.y < pView->GetLineCount())
                {
                        pView->SetCursorPos(ptPos);
-                       pView->SetAnchor(ptPos);
+                       if (bMoveAnchor)
+                               pView->SetAnchor(ptPos);
+                       pView->SetSelection(pView->GetAnchor(), ptPos);
                }
                else
                {
                        CPoint ptPos1(0, pView->GetLineCount() - 1);
                        pView->SetCursorPos(ptPos1);
-                       pView->SetAnchor(ptPos1);
+                       if (bMoveAnchor)
+                               pView->SetAnchor(ptPos1);
+                       pView->SetSelection(pView->GetAnchor(), ptPos1);
                }
        }
 
        // If goto target is another view - activate another view.
        // This is done for user convenience as user probably wants to
        // work with goto target file.
-       if (GetGroupView(pane) != pCurrentView)
-       {
-               if (pSplitterWnd != nullptr)
-               {
-                       if (pSplitterWnd->GetColumnCount() > 1)
-                               pSplitterWnd->SetActivePane(0, pane);
-                       else
-                               pSplitterWnd->SetActivePane(pane, 0);
-               }
-       }
+       if (m_bDetailView)
+               GetDocument()->GetView(0, pane)->SetActivePane();
+       else if (GetGroupView(pane) != pCurrentView)
+               GetGroupView(pane)->SetActivePane();
 }
 
 /**
@@ -3359,14 +3809,12 @@ void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
  */
 void CMergeEditView::OnEditCopyLineNumbers()
 {
-       CPoint ptStart;
-       CPoint ptEnd;
        CString strText;
        CString strLine;
        CString strNumLine;
 
        CMergeDoc *pDoc = GetDocument();
-       GetSelection(ptStart, ptEnd);
+       auto [ptStart, ptEnd] = GetSelection();
 
        // Get last selected line (having widest linenumber)
        int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
@@ -3388,7 +3836,7 @@ void CMergeEditView::OnEditCopyLineNumbers()
                strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
                strText += strNumLine;
        }
-       PutToClipboard(strText, strText.GetLength(), m_bColumnSelection);
+       PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
 }
 
 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
@@ -3411,13 +3859,7 @@ void CMergeEditView::OnOpenFile()
        String sFileName = pDoc->m_filePaths[m_nThisPane];
        if (sFileName.empty())
                return;
-       HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
-                       0, 0, SW_SHOWNORMAL);
-       if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
-               rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
-                        0, 0, SW_SHOWNORMAL);
-       if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
-               OnOpenFileWith();
+       shell::Edit(sFileName.c_str());
 }
 
 /**
@@ -3431,14 +3873,7 @@ void CMergeEditView::OnOpenFileWith()
        String sFileName = pDoc->m_filePaths[m_nThisPane];
        if (sFileName.empty())
                return;
-
-       CString sysdir;
-       if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
-               return;
-       sysdir.ReleaseBuffer();
-       CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
-       ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
-                       sysdir, SW_SHOWNORMAL);
+       shell::OpenWith(sFileName.c_str());
 }
 
 /**
@@ -3454,7 +3889,22 @@ void CMergeEditView::OnOpenFileWithEditor()
                return;
 
        int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
-       theApp.OpenFileToExternalEditor(sFileName.c_str(), nRealLine);
+       theApp.OpenFileToExternalEditor(sFileName, nRealLine);
+}
+
+/**
+ * @brief Open parent folder of active file
+ */
+void CMergeEditView::OnOpenParentFolder()
+{
+       CMergeDoc * pDoc = GetDocument();
+       ASSERT(pDoc != nullptr);
+
+       String sFileName = pDoc->m_filePaths[m_nThisPane];
+       if (sFileName.empty())
+               return;
+
+       shell::OpenParentFolder(sFileName.c_str());
 }
 
 /**
@@ -3541,6 +3991,17 @@ void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
        pCmdUI->SetCheck(GetViewTabs());
 }
 
+void CMergeEditView::OnViewEOL() 
+{
+       GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
+       GetDocument()->RefreshOptions();
+}
+
+void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI) 
+{
+       pCmdUI->SetCheck(GetViewEols());
+}
+
 void CMergeEditView::OnSize(UINT nType, int cx, int cy) 
 {
        if (!IsInitialized())
@@ -3740,18 +4201,25 @@ void CMergeEditView::SetWordWrapping( bool bWordWrap )
 /**
  * @brief Swap the positions of the two panes
  */
-void CMergeEditView::OnViewSwapPanes()
+void CMergeEditView::OnViewSwapPanes12()
 {
-       GetDocument()->SwapFiles();
+       GetDocument()->SwapFiles(0, 1);
 }
 
 /**
- * @brief Check if cursor is inside difference.
- * @return true if cursor is inside difference.
+ * @brief Swap the positions of the two panes
  */
-bool CMergeEditView::IsCursorInDiff() const
+void CMergeEditView::OnViewSwapPanes23()
 {
-       return m_bCurrentLineIsDiff;
+       GetDocument()->SwapFiles(1, 2);
+}
+
+/**
+ * @brief Swap the positions of the two panes
+ */
+void CMergeEditView::OnViewSwapPanes13()
+{
+       GetDocument()->SwapFiles(0, 2);
 }
 
 /**
@@ -3810,15 +4278,23 @@ void CMergeEditView::OnHelp()
  */
 void CMergeEditView::DocumentsLoaded()
 {
-       // Enable/disable automatic rescan (rescanning after edit)
-       EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
+       if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
+       {
+               SetTopMargin(true);
+               if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
+                       AutoFitColumn();
+       }
+       else
+       {
+               SetTopMargin(false);
+       }
 
        // SetTextType will revert to language dependent defaults for tab
        SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
        SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
        const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
                GetDocument()->IsMixedEOL(m_nThisPane);
-       SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE), mixedEOLs);
+       SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
        SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
        SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
        SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
@@ -3921,19 +4397,21 @@ void CMergeEditView::OnChangeScheme(UINT nID)
        CMergeDoc *pDoc = GetDocument();
        ASSERT(pDoc != nullptr);
 
-       for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
-       {
-               CMergeEditView *pView = GetGroupView(nPane);
-               ASSERT(pView != nullptr);
-
-               if (pView != nullptr)
+       for (int nGroup = 0; nGroup < pDoc->m_nGroups; nGroup++)
+               for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
                {
-                       pView->SetTextType(CCrystalTextView::TextType(nID - ID_COLORSCHEME_FIRST));
-                       pView->SetDisableBSAtSOL(false);
+                       CMergeEditView *pView = pDoc->GetView(nGroup, nPane);
+                       ASSERT(pView != nullptr);
+
+                       if (pView != nullptr)
+                       {
+                               pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
+                               pView->SetDisableBSAtSOL(false);
+                               pView->m_bChangedSchemeManually = true;
+                       }
                }
-       }
 
-       pDoc->UpdateAllViews(nullptr);
+       OnRefresh();
 }
 
 /**
@@ -3986,6 +4464,30 @@ BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
 }
 
 /**
+ * @brief Called when mouse's horizontal wheel is scrolled.
+ */
+void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
+{
+       SCROLLINFO si = { sizeof SCROLLINFO };
+       si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
+
+       VERIFY(GetScrollInfo(SB_HORZ, &si));
+
+       // new horz pos
+       si.nPos += zDelta / 40;
+       if (si.nPos > si.nMax) si.nPos = si.nMax;
+       if (si.nPos < si.nMin) si.nPos = si.nMin;
+
+       SetScrollInfo(SB_HORZ, &si);
+
+       // for update
+       SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
+
+       // no default CCrystalTextView
+       CView::OnMouseHWheel(nFlags, zDelta, pt);
+}
+
+/**
  * @brief Change font size (zoom) in views.
  * @param [in] amount Amount of change/zoom, negative number makes
  *  font smaller, positive number bigger and 0 reset the font size.
@@ -4000,7 +4502,9 @@ void CMergeEditView::ZoomText(short amount)
 
        if ( amount == 0)
        {
-               nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
+               nPointSize = GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_POINTSIZE);
+               if (nPointSize ==  0)
+                       nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
        }
 
        nPointSize += amount;
@@ -4027,6 +4531,130 @@ void CMergeEditView::ZoomText(short amount)
        }
 }
 
+bool CMergeEditView::QueryEditable()
+{
+       return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
+}
+
+/**
+ * @brief Adjust the point to remain in the displayed diff
+ *
+ * @return Tells if the point has been changed
+ */
+bool CMergeEditView::EnsureInDiff(CPoint& pt)
+{
+       int nLineCount = GetLineCount();
+       if (m_lineBegin >= nLineCount)
+               m_lineBegin = nLineCount - 1;
+       if (m_lineEnd >= nLineCount)
+               m_lineEnd = nLineCount - 1;
+
+       int diffLength = m_lineEnd - m_lineBegin + 1;
+       // first get the degenerate case out of the way
+       // no diff ?
+       if (diffLength == 0)
+       {
+               if (pt.y == m_lineBegin && pt.x == 0)
+                       return false;
+               pt.y = m_lineBegin;
+               pt.x = 0;
+               return true;
+       }
+
+       // not above diff
+       if (pt.y < m_lineBegin)
+       {
+               pt.y = m_lineBegin;
+               pt.x = 0;
+               return true;
+       }
+       // diff is defined and not below diff
+       if (m_lineEnd > -1 && pt.y > m_lineEnd)
+       {
+               pt.y = m_lineEnd;
+               pt.x = GetLineLength(pt.y);
+               return true;
+       }
+       return false;
+}
+
+void CMergeEditView::EnsureVisible(CPoint pt)
+{
+       CPoint ptNew = pt;
+       if (m_bDetailView)
+       {
+               // ensure we remain in diff
+               if (EnsureInDiff(ptNew))
+                       SetCursorPos(ptNew);
+       }
+       CCrystalTextView::EnsureVisible(ptNew);
+}
+
+void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
+{
+       CCrystalTextView::EnsureVisible(ptStart, ptEnd);
+}
+
+void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
+{
+       CPoint ptStartNew = ptStart;
+       CPoint ptEndNew = ptEnd;
+       if (m_bDetailView)
+       {
+               // ensure we remain in diff
+               EnsureInDiff(ptStartNew);
+               EnsureInDiff(ptEndNew);
+       }
+       CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
+}
+
+void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
+{
+       if (m_bDetailView)
+       {
+               int nLineCount = GetLineCount();
+               if (m_lineBegin >= nLineCount)
+                       m_lineBegin = nLineCount - 1;
+               if (m_lineEnd >= nLineCount)
+                       m_lineEnd = nLineCount - 1;
+
+               // ensure we remain in diff
+               int sublineBegin = GetSubLineIndex(m_lineBegin);
+               int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
+               int diffLength = sublineEnd - sublineBegin + 1;
+               int displayLength = GetScreenLines();
+               if (diffLength <= displayLength)
+                       nNewTopLine = sublineBegin;
+               else
+               {
+                       if (nNewTopLine < sublineBegin)
+                               nNewTopLine = sublineBegin;
+                       if (nNewTopLine + displayLength - 1 > sublineEnd)
+                               nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
+               }
+
+               CPoint pt = GetCursorPos();
+               if (EnsureInDiff(pt))
+                       SetCursorPos(pt);
+
+               auto [ptSelStart, ptSelEnd] = GetSelection();
+               if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
+                       SetSelection(ptSelStart, ptSelEnd);
+       }
+       CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
+}
+
+void CMergeEditView::SetActivePane()
+{
+       auto* pwndSplitterChild = GetParentSplitter(this, false);
+       if (!pwndSplitterChild)
+               return;
+       if (pwndSplitterChild->GetColumnCount() > 1)
+               pwndSplitterChild->SetActivePane(0, m_nThisPane);
+       else
+               pwndSplitterChild->SetActivePane(m_nThisPane, 0);
+}
+
 /**
  * @brief Called when user selects View/Zoom In from menu.
  */
@@ -4079,16 +4707,49 @@ void CMergeEditView::OnWindowSplit()
        {
                wndSplitter.SetActivePane(0, 0);
                wndSplitter.DeleteRow(1);
-               if (pwndSplitterChild->GetColumnCount() > 1)
-                       pwndSplitterChild->SetActivePane(0, nBuffer);
-               else
-                       pwndSplitterChild->SetActivePane(nBuffer, 0);
+               pDoc->GetView(0, nBuffer)->SetActivePane();
        }
 }
 
 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
 {
-       pCmdUI->Enable(TRUE);
+       pCmdUI->Enable(!m_bDetailView);
        pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
 }
 
+void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
+{
+       *pResult = 0;
+       LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
+       const int pane = pNMItemActivate->iItem / 4;
+       CMergeDoc* pDoc = GetDocument();
+       if (pane >= pDoc->m_nBuffers || !GetParentFrame()->IsChild(CWnd::FromHandle(pNMItemActivate->hdr.hwndFrom)))
+               return;
+
+       switch (pNMItemActivate->iItem % 4)
+       {
+       case 0:
+               pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
+               break;
+       case 1:
+               pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
+               break;
+       case 2:
+       {
+               CPoint point;
+               ::GetCursorPos(&point);
+
+               BCMenu menu;
+               VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
+               theApp.TranslateMenu(menu.m_hMenu);
+               menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
+               break;
+       }
+       case 3:
+               pDoc->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());
+               break;
+       default:
+               break;
+       }
+}
+