OSDN Git Service

Add "Open Parent Folder" menu item to the file compare window's context menu
[winmerge-jp/winmerge-jp.git] / Src / MergeEditView.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //    SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
7 /**
8  * @file  MergeEditView.cpp
9  *
10  * @brief Implementation of the CMergeEditView class
11  */
12
13 #include "StdAfx.h"
14 #include "MergeEditView.h"
15 #include <vector>
16 #include "BCMenu.h"
17 #include "Merge.h"
18 #include "LocationView.h"
19 #include "MergeDoc.h"
20 #include "MainFrm.h"
21 #include "OptionsMgr.h"
22 #include "OptionsDiffColors.h"
23 #include "FileTransform.h"
24 #include "Plugins.h"
25 #include "WMGotoDlg.h"
26 #include "OptionsDef.h"
27 #include "SyntaxColors.h"
28 #include "MergeEditFrm.h"
29 #include "MergeLineFlags.h"
30 #include "paths.h"
31 #include "DropHandler.h"
32 #include "DirDoc.h"
33 #include "ShellContextMenu.h"
34 #include "editcmd.h"
35
36 #ifdef _DEBUG
37 #define new DEBUG_NEW
38 #endif
39
40 #ifndef WM_MOUSEHWHEEL
41 #  define WM_MOUSEHWHEEL 0x20e
42 #endif
43
44 using std::vector;
45 using CrystalLineParser::TEXTBLOCK;
46
47 /** @brief Timer ID for delayed rescan. */
48 const UINT IDT_RESCAN = 2;
49 /** @brief Timer timeout for delayed rescan. */
50 const UINT RESCAN_TIMEOUT = 1000;
51
52 /** @brief Location for file compare specific help to open. */
53 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
54
55 /////////////////////////////////////////////////////////////////////////////
56 // CMergeEditView
57
58 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
59
60 CMergeEditView::CMergeEditView()
61 : m_bCurrentLineIsDiff(false)
62 , m_nThisPane(0)
63 , m_nThisGroup(0)
64 , m_bDetailView(false)
65 , m_piMergeEditStatus(nullptr)
66 , m_bAutomaticRescan(false)
67 , fTimerWaitingForIdle(0)
68 , m_lineBegin(0)
69 , m_lineEnd(-1)
70 , m_CurrentPredifferID(0)
71 , m_bChangedSchemeManually(false)
72 {
73         SetParser(&m_xParser);
74         
75         Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
76 }
77
78 CMergeEditView::~CMergeEditView()
79 {
80 }
81
82
83 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
84         //{{AFX_MSG_MAP(CMergeEditView)
85         ON_COMMAND(ID_CURDIFF, OnCurdiff)
86         ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
87         ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
88         ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
89         ON_COMMAND(ID_EDIT_CUT, OnEditCut)
90         ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
91         ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
92         ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
93         ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
94         ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
95         ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
96         ON_COMMAND(ID_LASTDIFF, OnLastdiff)
97         ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
98         ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
99         ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
100         ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
101         ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
102         ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
103         ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
104         ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
105         ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
106         ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
107         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
108         ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
109         ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
110         ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
111         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
112         ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
113         ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
114         ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
115         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
116         ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
117         ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
118         ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
119         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
120         ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
121         ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
122         ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
123         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
124         ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
125         ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
126         ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
127         ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
128         ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
129         ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
130         ON_WM_LBUTTONDBLCLK()
131         ON_WM_LBUTTONUP()
132         ON_WM_RBUTTONDOWN()
133         ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
134         ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
135         ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
136         ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
137         ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
138         ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
139         ON_COMMAND(ID_L2R, OnL2r)
140         ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
141         ON_COMMAND(ID_LINES_L2R, OnLinesL2r)
142         ON_UPDATE_COMMAND_UI(ID_LINES_L2R, OnUpdateLinesL2r)
143         ON_COMMAND(ID_R2L, OnR2l)
144         ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
145         ON_COMMAND(ID_LINES_R2L, OnLinesR2l)
146         ON_UPDATE_COMMAND_UI(ID_LINES_R2L, OnUpdateLinesR2l)
147         ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
148         ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
149         ON_COMMAND(ID_COPY_LINES_FROM_LEFT, OnCopyLinesFromLeft)
150         ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_LEFT, OnUpdateCopyLinesFromLeft)
151         ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
152         ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
153         ON_COMMAND(ID_COPY_LINES_FROM_RIGHT, OnCopyLinesFromRight)
154         ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_RIGHT, OnUpdateCopyLinesFromRight)
155         ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
156         ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
157         ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
158         ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
159         ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
160         ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
161         ON_WM_TIMER()
162         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
163         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
164         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
165         ON_COMMAND(ID_REFRESH, OnRefresh)
166         ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
167         ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
168         ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
169         ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
170         ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
171         ON_COMMAND(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnAddToSubstitutionFilters)
172         ON_UPDATE_COMMAND_UI(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnUpdateAddToSubstitutionFilters)
173         ON_WM_CONTEXTMENU()
174         ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
175         ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
176         ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
177         ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
178         ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
179         ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
180         ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
181         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
182         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
183         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
184         ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
185         ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
186         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
187         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
188         ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
189         ON_COMMAND(ID_L2RNEXT, OnL2RNext)
190         ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
191         ON_COMMAND(ID_R2LNEXT, OnR2LNext)
192         ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
193         ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
194         ON_COMMAND(ID_NEXT_PANE, OnChangePane)
195         ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
196         ON_COMMAND(ID_GOTO_MOVED_LINE_LM, OnGotoMovedLineLM)
197         ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_LM, OnUpdateGotoMovedLineLM)
198         ON_COMMAND(ID_GOTO_MOVED_LINE_MR, OnGotoMovedLineMR)
199         ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_MR, OnUpdateGotoMovedLineMR)
200         ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
201         ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
202         ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
203         ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
204         ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
205         ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
206         ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
207         ON_WM_VSCROLL ()
208         ON_WM_HSCROLL ()
209         ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
210         ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
211         ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
212         ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
213         ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
214         ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
215         ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
216         ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
217         ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
218         ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
219         ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
220         ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
221         ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
222         ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
223         ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
224         ON_COMMAND(ID_FILE_OPEN_PARENT_FOLDER, OnOpenParentFolder)
225         ON_COMMAND(ID_SWAPPANES_SWAP12, OnViewSwapPanes12)
226         ON_COMMAND(ID_SWAPPANES_SWAP23, OnViewSwapPanes23)
227         ON_COMMAND(ID_SWAPPANES_SWAP13, OnViewSwapPanes13)
228         ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
229         ON_WM_SIZE()
230         ON_WM_MOVE()
231         ON_COMMAND(ID_HELP, OnHelp)
232         ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
233         ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
234         ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
235         ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
236         ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
237         ON_WM_MOUSEWHEEL()
238         ON_WM_MOUSEHWHEEL()
239         ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
240         ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
241         ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
242         ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
243         ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
244         ON_NOTIFY(NM_DBLCLK, AFX_IDW_STATUS_BAR, OnStatusBarDblClick)
245         //}}AFX_MSG_MAP
246 END_MESSAGE_MAP()
247
248
249 /////////////////////////////////////////////////////////////////////////////
250 // CMergeEditView diagnostics
251
252 #ifdef _DEBUG
253 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
254 {
255         ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
256         return (CMergeDoc*)m_pDocument;
257 }
258
259 #endif //_DEBUG
260
261 /////////////////////////////////////////////////////////////////////////////
262 // CMergeEditView message handlers
263
264 /**
265  * @brief Return text buffer for file in view
266  */
267 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
268 {
269         return GetDocument()->m_ptBuf[m_nThisPane].get();
270 }
271
272 /**
273  * @brief Update any resources necessary after a GUI language change
274  */
275 void CMergeEditView::UpdateResources()
276 {
277 }
278
279 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
280 {
281         return GetDocument()->GetView(m_nThisGroup, nBuffer);
282 }
283
284 void CMergeEditView::PrimeListWithFile()
285 {
286         // Set the tab size now, just in case the options change...
287         // We don't update it at the end of OnOptions,
288         // we can update it safely now
289         SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
290 }
291 /**
292  * @brief Return text from line given
293  */
294 CString CMergeEditView::GetLineText(int idx)
295 {
296         return GetLineChars(idx);
297 }
298
299 /**
300  * @brief Return text from selection
301  */
302 CString CMergeEditView::GetSelectedText()
303 {
304         CString strText;
305         auto [ptStart, ptEnd] = GetSelection();
306         if (ptStart != ptEnd)
307                 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
308         return strText;
309 }
310
311 /**
312  * @brief Return number of selected characters
313  */
314 std::pair<int, int> CMergeEditView::GetSelectedLineAndCharacterCount()
315 {
316         auto [ptStart, ptEnd] = GetSelection();
317         int nCharsOrColumns =0;
318         int nSelectedLines = 0;
319         for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
320         {
321                 if ((GetLineFlags(nLine) & (LF_GHOST | LF_INVISIBLE)) == 0)
322                 {
323                         int nLineLength = GetLineLength(nLine) + (m_pTextBuffer->GetLineEol(nLine)[0] ? 1 : 0);
324                         nCharsOrColumns += (nLine == ptEnd.y) ? ptEnd.x : nLineLength;
325                         if (nLine == ptStart.y)
326                                 nCharsOrColumns -= ptStart.x;
327                         if (nLine < ptEnd.y || (ptStart != ptEnd && ptEnd.x > 0))
328                                 ++nSelectedLines;
329                 }
330         }
331         if (m_bRectangularSelection)
332         {
333                 int nStartLeft, nStartRight, nEndLeft, nEndRight;
334                 GetColumnSelection(ptStart.y, nStartLeft, nStartRight);
335                 GetColumnSelection(ptEnd.y, nEndLeft, nEndRight);
336                 nCharsOrColumns = (std::max)(nStartRight, nEndRight) - (std::min)(nStartLeft, nEndLeft);
337         }
338         return { nSelectedLines, nCharsOrColumns };
339 }
340
341 /**
342  * @brief Get diffs inside selection.
343  * @param [out] firstDiff First diff inside selection
344  * @param [out] lastDiff Last diff inside selection
345  * @note -1 is returned in parameters if diffs cannot be determined
346  * @todo This shouldn't be called when there is no diffs, so replace
347  * first 'if' with ASSERT()?
348  */
349 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
350 {
351         firstDiff = -1;
352         lastDiff = -1;
353
354         CMergeDoc *pd = GetDocument();
355         const int nDiffs = pd->m_diffList.GetSignificantDiffs();
356         if (nDiffs == 0)
357                 return;
358
359         int firstLine, lastLine;
360         GetFullySelectedLines(firstLine, lastLine);
361         if (lastLine < firstLine)
362                 return;
363
364         firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
365         lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
366         if (firstDiff != -1 && lastDiff != -1)
367         {
368                 DIFFRANGE di;
369
370                 // Check that first selected line is first diff's first line or above it
371                 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
372                 if ((int)di.dbegin < firstLine)
373                 {
374                         if (firstDiff < lastDiff)
375                                 ++firstDiff;
376                 }
377
378                 // Check that last selected line is last diff's last line or below it
379                 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
380                 if ((int)di.dend > lastLine)
381                 {
382                         if (firstDiff < lastDiff)
383                                 --lastDiff;
384                 }
385
386                 // Special case: one-line diff is not selected if cursor is in it
387                 if (firstLine == lastLine)
388                 {
389                         firstDiff = -1;
390                         lastDiff = -1;
391                 }
392         }
393 }
394
395 /**
396  * @brief Get diffs inside selection.
397  * @param [out] firstDiff First diff inside selection
398  * @param [out] lastDiff Last diff inside selection
399  * @param [out] firstWordDiff First word level diff inside selection
400  * @param [out] lastWordDiff Last word level diff inside selection
401  * @note -1 is returned in parameters if diffs cannot be determined
402  * @todo This shouldn't be called when there is no diffs, so replace
403  * first 'if' with ASSERT()?
404  */
405 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
406 {
407         firstDiff = -1;
408         lastDiff = -1;
409         firstWordDiff = -1;
410         lastWordDiff = -1;
411
412         CMergeDoc *pd = GetDocument();
413         const int nDiffs = pd->m_diffList.GetSignificantDiffs();
414         if (nDiffs == 0)
415                 return;
416
417         int firstLine, lastLine;
418         auto [ptStart, ptEnd] = GetSelection();
419         if (pptStart != nullptr)
420                 ptStart = *pptStart;
421         if (pptEnd != nullptr)
422                 ptEnd = *pptEnd;
423         firstLine = ptStart.y;
424         lastLine = ptEnd.y;
425
426         firstDiff = pd->m_diffList.LineToDiff(firstLine);
427         bool firstLineIsNotInDiff = firstDiff == -1;
428         if (firstDiff == -1)
429         {
430                 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
431                 if (firstDiff == -1)
432                         return;
433                 firstWordDiff = 0;
434         }
435         lastDiff = pd->m_diffList.LineToDiff(lastLine);
436         bool lastLineIsNotInDiff = lastDiff == -1;      
437         if (lastDiff == -1)
438                 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
439         if (lastDiff < firstDiff)
440         {
441                 firstDiff = -1;
442                 firstWordDiff = -1;
443                 return;
444         }
445
446         if (firstDiff != -1 && lastDiff != -1)
447         {
448                 DIFFRANGE di;
449                 
450                 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
451                 {
452                         firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
453                 }
454                 else if (ptStart != ptEnd)
455                 {
456                         VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
457                         if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
458                         {
459                                 firstWordDiff = -1;
460                                 return;
461                         }
462
463                         if (firstWordDiff == -1)
464                         {
465                                 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
466                                 for (size_t i = 0; i < worddiffs.size(); ++i)
467                                 {
468                                         int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
469                                         if (worddiffs[i].endline[m_nThisPane] > firstLine ||
470                                                 (firstLine == worddiffs[i].endline[m_nThisPane] && 
471                                                  worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
472                                         {
473                                                 firstWordDiff = static_cast<int>(i);
474                                                 break;
475                                         }
476                                 }
477
478                                 if (firstLine >= di.dbegin && firstWordDiff == -1)
479                                 {
480                                         ++firstDiff;
481                                         firstWordDiff = 0;
482                                 }
483                         }
484
485                         VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
486                         vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
487                         for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
488                         {
489                                 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
490                                     (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
491                                 {
492                                         lastWordDiff = static_cast<int>(i);
493                                         break;
494                                 }
495                         }
496
497                         if (lastLine <= di.dend && lastWordDiff == -1)
498                                 --lastDiff;
499
500                         if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
501                         {
502                                 firstDiff = -1;
503                                 lastDiff = -1;
504                                 firstWordDiff = -1;
505                                 lastWordDiff = -1;
506                         }
507                         else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
508                         {
509                                 firstDiff = -1;
510                                 lastDiff = -1;
511                                 firstWordDiff = -1;
512                                 lastWordDiff = -1;
513                         }
514                 }
515                 else
516                 {
517                         firstDiff = -1;
518                         lastDiff = -1;
519                         firstWordDiff = -1;
520                         lastWordDiff = -1;
521                 }
522         }
523
524         ASSERT(firstDiff == -1 ? (lastDiff  == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
525         ASSERT(lastDiff  == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
526         ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
527 }
528
529 void CMergeEditView::GetSelectedDiffs(int & firstDiff, int & lastDiff)
530 {
531         firstDiff = -1;
532         lastDiff = -1;
533
534         CMergeDoc *pd = GetDocument();
535         const int nDiffs = pd->m_diffList.GetSignificantDiffs();
536         if (nDiffs == 0)
537                 return;
538
539         int firstLine, lastLine;
540         auto [ptStart, ptEnd] = GetSelection();
541         firstLine = ptStart.y;
542         lastLine = ptEnd.y;
543
544         firstDiff = pd->m_diffList.LineToDiff(firstLine);
545         if (firstDiff == -1)
546         {
547                 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
548                 if (firstDiff == -1)
549                         return;
550         }
551         lastDiff = pd->m_diffList.LineToDiff(lastLine);
552         if (lastDiff == -1)
553                 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
554         if (lastDiff < firstDiff)
555         {
556                 firstDiff = -1;
557                 return;
558         }
559
560         ASSERT(firstDiff == -1 ? (lastDiff  == -1) : true);
561         ASSERT(lastDiff  == -1 ? (firstDiff == -1) : true);
562 }
563
564 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
565 {
566         CMergeDoc *pDoc = GetDocument();
567         std::map<int, std::vector<int>> ret;
568         std::map<int, std::vector<int> *> list;
569         auto [ptStart, ptEnd] = GetSelection();
570         for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
571         {
572                 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
573                 {
574                         int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
575                         int nLeft, nRight;
576                         GetColumnSelection(nLine, nLeft, nRight);
577                         CPoint ptStart2, ptEnd2;
578                         ptStart2.x = nLeft;
579                         ptEnd2.x = nRight;
580                         ptStart2.y = ptEnd2.y = nLine;
581                         GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
582                         if (firstDiff != -1 && lastDiff != -1)
583                         {
584                                 std::vector<int> *pWordDiffs;
585                                 if (list.find(firstDiff) == list.end())
586                                         list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
587                                 pWordDiffs = list[firstDiff];
588                                 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
589                                 {
590                                         if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
591                                                 pWordDiffs->push_back(i);
592                                 }
593                         }
594                 }
595         }
596         for (auto& it : list)
597                 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
598         return ret;
599 }
600
601 void CMergeEditView::OnInitialUpdate()
602 {
603         PushCursors();
604         CCrystalEditViewEx::OnInitialUpdate();
605         PopCursors();
606         SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
607         SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
608
609         m_lineBegin = 0;
610         m_lineEnd = -1;
611 }
612
613 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
614 {
615         CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
616
617         CMergeDoc* pDoc = GetDocument();
618         pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
619 }
620
621 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
622 {
623         if (m_bDetailView)
624         {
625                 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
626                         return std::vector<CrystalLineParser::TEXTBLOCK>();
627         }
628         return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
629 }
630
631 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
632 {
633         static const std::vector<TEXTBLOCK> emptyBlocks;
634         if (m_bDetailView)
635         {
636                 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
637                         return emptyBlocks;
638         }
639
640         DWORD dwLineFlags = GetLineFlags(nLineIndex);
641         if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
642                 return emptyBlocks;
643
644         if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
645                 return emptyBlocks;
646
647         CMergeDoc *pDoc = GetDocument();
648         if (pDoc->IsEditedAfterRescan(m_nThisPane))
649                 return emptyBlocks;
650         
651         int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
652         if (nDiff == -1)
653                 return emptyBlocks;
654
655         DIFFRANGE cd;
656         pDoc->m_diffList.GetDiff(nDiff, cd);
657         int unemptyLineCount = 0;
658         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
659         {
660                 if (cd.begin[nPane] != cd.end[nPane] + 1)
661                         unemptyLineCount++;
662         }
663         if (unemptyLineCount < 2)
664                 return emptyBlocks;
665
666         vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
667         size_t nWordDiffs = worddiffs.size();
668
669         bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
670
671         std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
672         blocks[0].m_nCharPos = 0;
673         blocks[0].m_nColorIndex = COLORINDEX_NONE;
674         blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
675         size_t i, j;
676         for (i = 0, j = 1; i < nWordDiffs; i++)
677         {
678                 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
679                         continue;
680                 if (pDoc->m_nBuffers > 2)
681                 {
682                         if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
683                                 continue;
684                         else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
685                                 continue;
686                 }
687                 int begin[3], end[3];
688                 bool deleted = false;
689                 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
690                 {
691                         begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
692                         end[pane]   = (worddiffs[i].endline[pane]   > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
693                         if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
694                                 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
695                                 deleted = true;
696                 }
697                 blocks[j].m_nCharPos = begin[m_nThisPane];
698                 if (lineInCurrentDiff)
699                 {
700                         if (m_cachedColors.clrSelDiffText != CLR_NONE)
701                                 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
702                         else
703                                 blocks[j].m_nColorIndex = COLORINDEX_NONE;
704                         blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE | 
705                                 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
706                 }
707                 else
708                 {
709                         if (m_cachedColors.clrDiffText != CLR_NONE)
710                                 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
711                         else
712                                 blocks[j].m_nColorIndex = COLORINDEX_NONE;
713                         blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
714                                 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
715                 }
716                 j++;
717                 blocks[j].m_nCharPos = end[m_nThisPane];
718                 blocks[j].m_nColorIndex = COLORINDEX_NONE;
719                 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
720                 j++;
721         }
722
723         blocks.resize(j);
724
725         return blocks;
726 }
727
728 COLORREF CMergeEditView::GetColor(int nColorIndex) const
729 {
730         switch (nColorIndex & ~COLORINDEX_MASK)
731         {
732         case COLORINDEX_HIGHLIGHTBKGND1:
733                 return m_cachedColors.clrSelWordDiff;
734         case COLORINDEX_HIGHLIGHTTEXT1:
735                 return m_cachedColors.clrSelWordDiffText;
736         case COLORINDEX_HIGHLIGHTBKGND2:
737                 return m_cachedColors.clrWordDiff;
738         case COLORINDEX_HIGHLIGHTTEXT2:
739                 return m_cachedColors.clrWordDiffText;
740         case COLORINDEX_HIGHLIGHTBKGND3:
741                 return m_cachedColors.clrWordDiffDeleted;
742         case COLORINDEX_HIGHLIGHTBKGND4:
743                 return m_cachedColors.clrSelWordDiffDeleted;
744
745         default:
746                 return CCrystalTextView::GetColor(nColorIndex);
747         }
748 }
749
750 /**
751  * @brief Determine text and background color for line
752  * @param [in] nLineIndex Index of line in view (NOT line in file)
753  * @param [out] crBkgnd Backround color for line
754  * @param [out] crText Text color for line
755  */
756 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
757                                 COLORREF & crText, bool & bDrawWhitespace)
758 {
759         DWORD ignoreFlags = 0;
760         GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
761 }
762
763 /**
764  * @brief Determine text and background color for line
765  * @param [in] nLineIndex Index of line in view (NOT line in file)
766  * @param [in] ignoreFlags Flags that caller wishes ignored
767  * @param [out] crBkgnd Backround color for line
768  * @param [out] crText Text color for line
769  *
770  * This version allows caller to suppress particular flags
771  */
772 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
773                                 COLORREF & crText, bool & bDrawWhitespace)
774 {
775         if (GetLineCount() <= nLineIndex)
776                 return;
777
778         DWORD dwLineFlags = GetLineFlags(nLineIndex);
779
780         if (dwLineFlags & ignoreFlags)
781                 dwLineFlags &= (~ignoreFlags);
782
783         if (m_bDetailView)
784         {
785                 // Line with WinMerge flag, 
786                 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
787                 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
788                 {
789                         crText = m_cachedColors.clrDiffText;
790                         bDrawWhitespace = true;
791
792                         if (dwLineFlags & LF_GHOST)
793                         {
794                                 crBkgnd = m_cachedColors.clrDiffDeleted;
795                         }
796                 }
797                 else
798                 {
799                         // If no syntax hilighting
800                         if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
801                         {
802                                 crBkgnd = GetColor (COLORINDEX_BKGND);
803                                 crText = GetColor (COLORINDEX_NORMALTEXT);
804                                 bDrawWhitespace = false;
805                         }
806                         else
807                                 // Line not inside diff, get colors from CrystalEditor
808                                 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
809                                         crText, bDrawWhitespace);
810                 }
811                 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
812                 {
813                         crBkgnd = GetColor (COLORINDEX_WHITESPACE);
814                         crText = GetColor (COLORINDEX_WHITESPACE);
815                         bDrawWhitespace = false;
816                 }
817                 return;
818         }
819
820         // Line inside diff
821         if (dwLineFlags & LF_WINMERGE_FLAGS)
822         {
823                 crText = m_cachedColors.clrDiffText;
824                 bDrawWhitespace = true;
825                 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
826
827                 if (dwLineFlags & LF_SNP)
828                 {
829                         if (lineInCurrentDiff)
830                         {
831                                 if (dwLineFlags & LF_GHOST)
832                                         crBkgnd = m_cachedColors.clrSelSNPDeleted;
833                                 else
834                                         crBkgnd = m_cachedColors.clrSelSNP;
835                                 crText = m_cachedColors.clrSelSNPText;
836                         }
837                         else
838                         {
839                                 if (dwLineFlags & LF_GHOST)
840                                         crBkgnd = m_cachedColors.clrSNPDeleted;
841                                 else
842                                         crBkgnd = m_cachedColors.clrSNP;
843                                 crText = m_cachedColors.clrSNPText;
844                         }
845                         return;
846                 }
847                 else if (dwLineFlags & LF_DIFF)
848                 {
849                         if (lineInCurrentDiff)
850                         {
851                                 if (dwLineFlags & LF_MOVED)
852                                 {
853                                         crBkgnd = m_cachedColors.clrSelMoved;
854                                         crText = m_cachedColors.clrSelMovedText;
855                                 }
856                                 else
857                                 {
858                                         crBkgnd = m_cachedColors.clrSelDiff;
859                                         crText = m_cachedColors.clrSelDiffText;
860                                 }
861                         
862                         }
863                         else
864                         {
865                                 if (dwLineFlags & LF_MOVED)
866                                 {
867                                         crBkgnd = m_cachedColors.clrMoved;
868                                         crText = m_cachedColors.clrMovedText;
869                                 }
870                                 else
871                                 {
872                                         crBkgnd = m_cachedColors.clrDiff;
873                                         crText = m_cachedColors.clrDiffText;
874                                 }
875                         }
876                         return;
877                 }
878                 else if (dwLineFlags & LF_TRIVIAL)
879                 {
880                         // trivial diff can not be selected
881                         if (dwLineFlags & LF_GHOST)
882                                 // ghost lines in trivial diff has their own color
883                                 crBkgnd = m_cachedColors.clrTrivialDeleted;
884                         else
885                                 crBkgnd = m_cachedColors.clrTrivial;
886                         crText = m_cachedColors.clrTrivialText;
887                         return;
888                 }
889                 else if (dwLineFlags & LF_GHOST)
890                 {
891                         if (lineInCurrentDiff)
892                         {
893                                 if (dwLineFlags & LF_MOVED)
894                                         crBkgnd = m_cachedColors.clrSelMovedDeleted;
895                                 else
896                                         crBkgnd = m_cachedColors.clrSelDiffDeleted;
897                         }
898                         else
899                         {
900                                 if (dwLineFlags & LF_MOVED)
901                                         crBkgnd = m_cachedColors.clrMovedDeleted;
902                                 else
903                                         crBkgnd = m_cachedColors.clrDiffDeleted;
904                         }
905                         return;
906                 }
907         }
908         else
909         {
910                 // Line not inside diff,
911                 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
912                 {
913                         // If no syntax hilighting, get windows default colors
914                         crBkgnd = GetColor (COLORINDEX_BKGND);
915                         crText = GetColor (COLORINDEX_NORMALTEXT);
916                         bDrawWhitespace = false;
917                 }
918                 else
919                         // Syntax highlighting, get colors from CrystalEditor
920                         CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
921                                 crText, bDrawWhitespace);
922         }
923 }
924
925 /**
926  * @brief Sync other pane position
927  */
928 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
929 {
930         CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
931         if (pSplitterWnd != nullptr)
932         {
933                 //  See CSplitterWnd::IdFromRowCol() implementation for details
934                 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
935                 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
936                 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
937                 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
938
939                 // limit the TopLine : must be smaller than GetLineCount for all the panels
940                 int newTopSubLine = m_nTopSubLine;
941                 int nRows = pSplitterWnd->GetRowCount ();
942                 int nCols = pSplitterWnd->GetColumnCount ();
943                 int nRow=0;
944 //              for (nRow = 0; nRow < nRows; nRow++)
945 //              {
946 //                      for (int nCol = 0; nCol < nCols; nCol++)
947 //                      {
948 //                              CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
949 //                              if (pSiblingView != nullptr)
950 //                                      if (pSiblingView->GetSubLineCount() <= newTopSubLine)
951 //                                              newTopSubLine = pSiblingView->GetSubLineCount()-1;
952 //                      }
953 //              }
954                 if (m_nTopSubLine != newTopSubLine)
955                         ScrollToSubLine(newTopSubLine);
956
957                 for (nRow = 0; nRow < nRows; nRow++)
958                 {
959                         for (int nCol = 0; nCol < nCols; nCol++)
960                         {
961                                 if (!(nRow == nCurrentRow && nCol == nCurrentCol))  //  We don't need to update ourselves
962                                 {
963                                         CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
964                                         if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
965                                                 pSiblingView->OnUpdateSibling (this, bHorz);
966                                 }
967                         }
968                 }
969         }
970 }
971
972 /**
973  * @brief Update other panes
974  */
975 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
976 {
977         if (pUpdateSource != this)
978         {
979                 ASSERT (pUpdateSource != nullptr);
980                 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
981                 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
982                 if (!bHorz)  // changed this so bHorz works right
983                 {
984                         ASSERT (pSrcView->m_nTopSubLine >= 0);
985
986                         // This ASSERT is wrong: panes have different files and
987                         // different linecounts
988                         // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
989                         if (pSrcView->m_nTopSubLine != m_nTopSubLine)
990                         {
991                                 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
992                                 UpdateCaret ();
993                                 RecalcVertScrollBar(true);
994                                 RecalcHorzScrollBar();
995                         }
996                 }
997                 else
998                 {
999                         ASSERT (pSrcView->m_nOffsetChar >= 0);
1000
1001                         // This ASSERT is wrong: panes have different files and
1002                         // different linelengths
1003                         // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
1004                         if (pSrcView->m_nOffsetChar != m_nOffsetChar)
1005                         {
1006                                 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
1007                                 UpdateCaret ();
1008                                 RecalcHorzScrollBar(true);
1009                                 RecalcHorzScrollBar();
1010                         }
1011                 }
1012         }
1013 }
1014
1015 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
1016 {
1017         int newlineBegin, newlineEnd;
1018         CMergeDoc *pd = GetDocument();
1019         if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
1020         {
1021                 newlineBegin = 0;
1022                 newlineEnd = -1;
1023         }
1024         else
1025         {
1026                 DIFFRANGE curDiff;
1027                 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
1028
1029                 newlineBegin = curDiff.dbegin;
1030                 ASSERT (newlineBegin >= 0);
1031                 newlineEnd = curDiff.dend;
1032         }
1033
1034         m_lineBegin = newlineBegin;
1035         m_lineEnd = newlineEnd;
1036
1037         int nLineCount = GetLineCount();
1038         if (m_lineBegin > nLineCount)
1039                 m_lineBegin = nLineCount - 1;
1040         if (m_lineEnd > nLineCount)
1041                 m_lineEnd = nLineCount - 1;
1042
1043         if (m_nTopLine == newlineBegin)
1044                 return;
1045
1046         // scroll to the first line of the diff
1047         vector<WordDiff> worddiffs;
1048         if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
1049                 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
1050         CPoint pt = worddiffs.size() > 0 ?
1051                 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } : 
1052                 CPoint{ 0, m_lineBegin };
1053         ScrollToLine(m_lineBegin);
1054         if (pt.x > 0)
1055                 EnsureVisible(pt);
1056
1057         // update the width of the horizontal scrollbar
1058         RecalcHorzScrollBar();
1059 }
1060
1061 /**
1062  * @brief Selects diff by number and syncs other file
1063  * @param [in] nDiff Diff to select, must be >= 0
1064  * @param [in] bScroll Scroll diff to view
1065  * @param [in] bSelectText Select diff text
1066  * @sa CMergeEditView::ShowDiff()
1067  * @sa CMergeDoc::SetCurrentDiff()
1068  * @todo Parameter bSelectText is never used?
1069  */
1070 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
1071 {
1072         CMergeDoc *pd = GetDocument();
1073
1074         // Check that nDiff is valid
1075         if (nDiff < 0)
1076                 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
1077         if (nDiff >= pd->m_diffList.GetSize())
1078                 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
1079                         nDiff, pd->m_diffList.GetSize());
1080
1081         SelectNone();
1082         pd->SetCurrentDiff(nDiff);
1083         ShowDiff(bScroll, bSelectText);
1084         pd->UpdateAllViews(this);
1085         UpdateSiblingScrollPos(false);
1086
1087         // notify either side, as it will notify the other one
1088         pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1089 }
1090
1091 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1092 {
1093         CMergeDoc *pd = GetDocument();
1094         // If we have a selected diff, deselect it
1095         int nCurrentDiff = pd->GetCurrentDiff();
1096         if (nCurrentDiff != -1)
1097         {
1098                 CPoint pos = GetCursorPos();
1099                 if (!IsLineInCurrentDiff(pos.y))
1100                 {
1101                         pd->SetCurrentDiff(-1);
1102                         Invalidate();
1103                         pd->UpdateAllViews(this);
1104                 }
1105         }
1106 }
1107
1108 /**
1109  * @brief Called when user selects "Current Difference".
1110  * Goes to active diff. If no active diff, selects diff under cursor
1111  * @sa CMergeEditView::SelectDiff()
1112  * @sa CMergeDoc::GetCurrentDiff()
1113  * @sa CMergeDoc::LineToDiff()
1114  */
1115 void CMergeEditView::OnCurdiff()
1116 {
1117         CMergeDoc *pd = GetDocument();
1118
1119         // If no diffs, nothing to select
1120         if (!pd->m_diffList.HasSignificantDiffs())
1121                 return;
1122
1123         // GetCurrentDiff() returns -1 if no diff selected
1124         int nDiff = pd->GetCurrentDiff();
1125         if (nDiff != -1)
1126         {
1127                 // Scroll to the first line of the currently selected diff
1128                 SelectDiff(nDiff, true, false);
1129         }
1130         else
1131         {
1132                 // If cursor is inside diff, select that diff
1133                 CPoint pos = GetCursorPos();
1134                 nDiff = pd->m_diffList.LineToDiff(pos.y);
1135                 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1136                         SelectDiff(nDiff, true, false);
1137         }
1138 }
1139
1140 /**
1141  * @brief Called when "Current diff" item is updated
1142  */
1143 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1144 {
1145         CMergeDoc *pd = GetDocument();
1146         CPoint pos = GetCursorPos();
1147         int nCurrentDiff = pd->GetCurrentDiff();
1148         if (nCurrentDiff == -1)
1149         {
1150                 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1151                 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1152         }
1153         else
1154                 pCmdUI->Enable(true);
1155 }
1156
1157 /**
1158  * @brief Copy selected text to clipboard
1159  */
1160 void CMergeEditView::OnEditCopy()
1161 {
1162         CMergeDoc * pDoc = GetDocument();
1163         auto [ptSelStart, ptSelEnd] = GetSelection();
1164
1165         // Nothing selected
1166         if (ptSelStart == ptSelEnd)
1167                 return;
1168
1169         CString text;
1170
1171         if (!m_bRectangularSelection)
1172         {
1173                 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1174
1175                 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1176                         ptSelEnd.y, ptSelEnd.x, text);
1177         }
1178         else
1179                 GetTextWithoutEmptysInColumnSelection(text);
1180
1181         PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1182 }
1183
1184 /**
1185  * @brief Called when "Copy" item is updated
1186  */
1187 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1188 {
1189         CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1190 }
1191
1192 /**
1193  * @brief Cut current selection to clipboard
1194  */
1195 void CMergeEditView::OnEditCut()
1196 {
1197         if (!QueryEditable())
1198                 return;
1199
1200         CMergeDoc * pDoc = GetDocument();
1201         auto [ptSelStart, ptSelEnd] = GetSelection();
1202
1203         // Nothing selected
1204         if (ptSelStart == ptSelEnd)
1205                 return;
1206
1207         CString text;
1208         if (!m_bRectangularSelection)
1209                 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1210                         ptSelEnd.y, ptSelEnd.x, text);
1211         else
1212                 GetTextWithoutEmptysInColumnSelection(text);
1213
1214         PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1215
1216         if (!m_bRectangularSelection)
1217         {
1218                 CPoint ptCursorPos = ptSelStart;
1219                 ASSERT_VALIDTEXTPOS(ptCursorPos);
1220                 SetAnchor(ptCursorPos);
1221                 SetSelection(ptCursorPos, ptCursorPos);
1222                 SetCursorPos(ptCursorPos);
1223                 EnsureVisible(ptCursorPos);
1224
1225                 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1226                         ptSelEnd.x, CE_ACTION_CUT);
1227         }
1228         else
1229                 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1230
1231         m_pTextBuffer->SetModified(true);
1232 }
1233
1234 /**
1235  * @brief Called when "Cut" item is updated
1236  */
1237 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1238 {
1239         if (QueryEditable())
1240                 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1241         else
1242                 pCmdUI->Enable(false);
1243 }
1244
1245 /**
1246  * @brief Paste text from clipboard
1247  */
1248 void CMergeEditView::OnEditPaste()
1249 {
1250         if (!QueryEditable())
1251                 return;
1252
1253         CCrystalEditViewEx::Paste();
1254         m_pTextBuffer->SetModified(true);
1255 }
1256
1257 /**
1258  * @brief Called when "Paste" item is updated
1259  */
1260 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1261 {
1262         if (QueryEditable())
1263                 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1264         else
1265                 pCmdUI->Enable(false);
1266 }
1267
1268 /**
1269  * @brief Undo last action
1270  */
1271 void CMergeEditView::OnEditUndo()
1272 {
1273         CWaitCursor waitstatus;
1274         CMergeDoc* pDoc = GetDocument();
1275         CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1276         if(tgt==this)
1277         {
1278                 if (!QueryEditable())
1279                         return;
1280
1281                 GetParentFrame()->SetActiveView(this, true);
1282                 if(CCrystalEditViewEx::DoEditUndo())
1283                 {
1284                         --pDoc->curUndo;
1285                         pDoc->UpdateHeaderPath(m_nThisPane);
1286                         pDoc->FlushAndRescan();
1287
1288                         int nAction;
1289                         m_pTextBuffer->GetRedoActionCode(nAction);
1290                         if (nAction == CE_ACTION_MERGE)
1291                                 // select the diff so we may just merge it again
1292                                 OnCurdiff();
1293                 }
1294         }
1295         else
1296         {
1297                 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1298         }
1299         if (!pDoc->CanUndo())
1300                 pDoc->SetAutoMerged(false);
1301 }
1302
1303 /**
1304  * @brief Called when "Undo" item is updated
1305  */
1306 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1307 {
1308         CMergeDoc* pDoc = GetDocument();
1309         if (pDoc->curUndo!=pDoc->undoTgt.begin())
1310         {
1311                 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1312                 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1313         }
1314         else
1315                 pCmdUI->Enable(false);
1316 }
1317
1318 /**
1319  * @brief Go to first diff
1320  *
1321  * Called when user selects "First Difference"
1322  * @sa CMergeEditView::SelectDiff()
1323  */
1324 void CMergeEditView::OnFirstdiff()
1325 {
1326         CMergeDoc *pd = GetDocument();
1327         if (pd->m_diffList.HasSignificantDiffs())
1328         {
1329                 int nDiff = pd->m_diffList.FirstSignificantDiff();
1330                 SelectDiff(nDiff, true, false);
1331         }
1332 }
1333
1334 /**
1335  * @brief Update "First diff" UI items
1336  */
1337 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1338 {
1339         OnUpdatePrevdiff(pCmdUI);
1340 }
1341
1342 /**
1343  * @brief Go to last diff
1344  */
1345 void CMergeEditView::OnLastdiff()
1346 {
1347         CMergeDoc *pd = GetDocument();
1348         if (pd->m_diffList.HasSignificantDiffs())
1349         {
1350                 int nDiff = pd->m_diffList.LastSignificantDiff();
1351                 SelectDiff(nDiff, true, false);
1352         }
1353 }
1354
1355 /**
1356  * @brief Update "Last diff" UI items
1357  */
1358 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1359 {
1360         OnUpdateNextdiff(pCmdUI);
1361 }
1362
1363 /**
1364  * @brief Go to next diff and select it.
1365  *
1366  * Finds and selects next difference. There are several cases:
1367  * - if there is selected difference, and that difference is visible
1368  * on screen, next found difference is selected.
1369  * - if there is selected difference but it is not visible, next
1370  * difference from cursor position is selected. This is what user
1371  * expects to happen and is natural thing to do. Also reduces
1372  * needless scrolling.
1373  * - if there is no selected difference, next difference from cursor
1374  * position is selected.
1375  */
1376 void CMergeEditView::OnNextdiff()
1377 {
1378         CMergeDoc *pd = GetDocument();
1379         int cnt = pd->m_ptBuf[0]->GetLineCount();
1380         if (cnt <= 0)
1381                 return;
1382
1383         // Returns -1 if no diff selected
1384         int nextDiff = -1;
1385         int curDiff = pd->GetCurrentDiff();
1386         if (curDiff != -1)
1387         {
1388                 // We're on a diff
1389                 if (!IsDiffVisible(curDiff))
1390                 {
1391                         // Selected difference not visible, select next from cursor
1392                         int line = GetCursorPos().y;
1393                         // Make sure we aren't in the first line of the diff
1394                         ++line;
1395                         if (!IsValidTextPosY(CPoint(0, line)))
1396                                 line = m_nTopLine;
1397                         nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1398                 }
1399                 else
1400                 {
1401                         // Find out if there is a following significant diff
1402                         if (curDiff < pd->m_diffList.GetSize() - 1)
1403                         {
1404                                 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1405                         }
1406                 }
1407         }
1408         else
1409         {
1410                 // We don't have a selected difference,
1411                 // but cursor can be inside inactive diff
1412                 int line = GetCursorPos().y;
1413                 if (!IsValidTextPosY(CPoint(0, line)))
1414                         line = m_nTopLine;
1415                 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1416         }
1417
1418         int lastDiff = pd->m_diffList.LastSignificantDiff();
1419         if (nextDiff >= 0 && nextDiff <= lastDiff)
1420                 SelectDiff(nextDiff, true, false);
1421         else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1422         {
1423                 if (pDirDoc->MoveableToNextDiff())
1424                         pDirDoc->MoveToNextDiff(pd);
1425         }
1426 }
1427
1428 /**
1429  * @brief Update "Next diff" UI items
1430  */
1431 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1432 {
1433         CMergeDoc *pd = GetDocument();
1434         const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1435         bool enabled;
1436
1437         if (dfi == nullptr)
1438         {
1439                 // There aren't any significant differences
1440                 enabled = false;
1441         }
1442         else
1443         {
1444                 // Enable if the beginning of the last significant difference is after caret
1445                 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1446         }
1447
1448         if (!enabled && pd->GetDirDoc())
1449                 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1450
1451         pCmdUI->Enable(enabled);
1452 }
1453
1454 /**
1455  * @brief Go to previous diff and select it.
1456  *
1457  * Finds and selects previous difference. There are several cases:
1458  * - if there is selected difference, and that difference is visible
1459  * on screen, previous found difference is selected.
1460  * - if there is selected difference but it is not visible, previous
1461  * difference from cursor position is selected. This is what user
1462  * expects to happen and is natural thing to do. Also reduces
1463  * needless scrolling.
1464  * - if there is no selected difference, previous difference from cursor
1465  * position is selected.
1466  */
1467 void CMergeEditView::OnPrevdiff()
1468 {
1469         CMergeDoc *pd = GetDocument();
1470         int cnt = pd->m_ptBuf[0]->GetLineCount();
1471         if (cnt <= 0)
1472                 return;
1473
1474         // GetCurrentDiff() returns -1 if no diff selected
1475         int prevDiff = -1;
1476         int curDiff = pd->GetCurrentDiff();
1477         if (curDiff != -1)
1478         {
1479                 // We're on a diff
1480                 if (!IsDiffVisible(curDiff))
1481                 {
1482                         // Selected difference not visible, select previous from cursor
1483                         int line = GetCursorPos().y;
1484                         // Make sure we aren't in the last line of the diff
1485                         --line;
1486                         if (!IsValidTextPosY(CPoint(0, line)))
1487                                 line = m_nTopLine;
1488                         prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1489                 }
1490                 else
1491                 {
1492                         // Find out if there is a preceding significant diff
1493                         if (curDiff > 0)
1494                         {
1495                                 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1496                         }
1497                 }
1498         }
1499         else
1500         {
1501                 // We don't have a selected difference,
1502                 // but cursor can be inside inactive diff
1503                 int line = GetCursorPos().y;
1504                 if (!IsValidTextPosY(CPoint(0, line)))
1505                         line = m_nTopLine;
1506                 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1507         }
1508
1509         int firstDiff = pd->m_diffList.FirstSignificantDiff();
1510         if (prevDiff >= 0 && prevDiff >= firstDiff)
1511                 SelectDiff(prevDiff, true, false);
1512         else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1513         {
1514                 if (pDirDoc->MoveableToPrevDiff())
1515                         pDirDoc->MoveToPrevDiff(pd);
1516         }
1517 }
1518
1519 /**
1520  * @brief Update "Previous diff" UI items
1521  */
1522 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1523 {
1524         CMergeDoc *pd = GetDocument();
1525         const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1526         bool enabled;
1527
1528         if (dfi == nullptr)
1529         {
1530                 // There aren't any significant differences
1531                 enabled = false;
1532         }
1533         else
1534         {
1535                 // Enable if the end of the first significant difference is before caret
1536                 enabled = (GetCursorPos().y > (long)dfi->dend);
1537         }
1538
1539         if (!enabled && pd->GetDirDoc())
1540                 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1541
1542         pCmdUI->Enable(enabled);
1543 }
1544
1545 void CMergeEditView::OnNextConflict()
1546 {
1547         OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1548 }
1549
1550 /**
1551  * @brief Update "Next Conflict" UI items
1552  */
1553 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1554 {
1555         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1556 }
1557
1558 void CMergeEditView::OnPrevConflict()
1559 {
1560         OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1561 }
1562
1563 /**
1564  * @brief Update "Prev Conflict" UI items
1565  */
1566 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1567 {
1568         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1569 }
1570
1571 /**
1572  * @brief Go to next 3-way diff and select it.
1573  */
1574 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1575 {
1576         CMergeDoc *pd = GetDocument();
1577         int cnt = pd->m_ptBuf[0]->GetLineCount();
1578         if (cnt <= 0)
1579                 return;
1580
1581         // Returns -1 if no diff selected
1582         int curDiff = pd->GetCurrentDiff();
1583         if (curDiff != -1)
1584         {
1585                 // We're on a diff
1586                 int nextDiff = curDiff;
1587                 if (!IsDiffVisible(curDiff))
1588                 {
1589                         // Selected difference not visible, select next from cursor
1590                         int line = GetCursorPos().y;
1591                         // Make sure we aren't in the first line of the diff
1592                         ++line;
1593                         if (!IsValidTextPosY(CPoint(0, line)))
1594                                 line = m_nTopLine;
1595                         nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1596                 }
1597                 else
1598                 {
1599                         // Find out if there is a following significant diff
1600                         if (curDiff < pd->m_diffList.GetSize() - 1)
1601                         {
1602                                 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1603                         }
1604                 }
1605                 if (nextDiff == -1)
1606                         nextDiff = curDiff;
1607
1608                 // nextDiff is the next one if there is one, else it is the one we're on
1609                 SelectDiff(nextDiff, true, false);
1610         }
1611         else
1612         {
1613                 // We don't have a selected difference,
1614                 // but cursor can be inside inactive diff
1615                 int line = GetCursorPos().y;
1616                 if (!IsValidTextPosY(CPoint(0, line)))
1617                         line = m_nTopLine;
1618                 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1619                 if (curDiff >= 0)
1620                         SelectDiff(curDiff, true, false);
1621         }
1622 }
1623
1624 /**
1625  * @brief Update "Next 3-way diff" UI items
1626  */
1627 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1628 {
1629         CMergeDoc *pd = GetDocument();
1630
1631         if (pd->m_nBuffers < 3)
1632         {
1633                 pCmdUI->Enable(false);
1634                 return;
1635         }
1636
1637         const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1638
1639         if (dfi == nullptr)
1640         {
1641                 // There aren't any significant differences
1642                 pCmdUI->Enable(false);
1643         }
1644         else
1645         {
1646                 // Enable if the beginning of the last significant difference is after caret
1647                 CPoint pos = GetCursorPos();
1648                 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1649         }
1650 }
1651
1652 /**
1653  * @brief Go to previous 3-way diff and select it.
1654  */
1655 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1656 {
1657         CMergeDoc *pd = GetDocument();
1658
1659         int cnt = pd->m_ptBuf[0]->GetLineCount();
1660         if (cnt <= 0)
1661                 return;
1662
1663         // GetCurrentDiff() returns -1 if no diff selected
1664         int curDiff = pd->GetCurrentDiff();
1665         if (curDiff != -1)
1666         {
1667                 // We're on a diff
1668                 int prevDiff = curDiff;
1669                 if (!IsDiffVisible(curDiff))
1670                 {
1671                         // Selected difference not visible, select previous from cursor
1672                         int line = GetCursorPos().y;
1673                         // Make sure we aren't in the last line of the diff
1674                         --line;
1675                         if (!IsValidTextPosY(CPoint(0, line)))
1676                                 line = m_nTopLine;
1677                         prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1678                 }
1679                 else
1680                 {
1681                         // Find out if there is a preceding significant diff
1682                         if (curDiff > 0)
1683                         {
1684                                 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1685                         }
1686                 }
1687                 if (prevDiff == -1)
1688                         prevDiff = curDiff;
1689
1690                 // prevDiff is the preceding one if there is one, else it is the one we're on
1691                 SelectDiff(prevDiff, true, false);
1692         }
1693         else
1694         {
1695                 // We don't have a selected difference,
1696                 // but cursor can be inside inactive diff
1697                 int line = GetCursorPos().y;
1698                 if (!IsValidTextPosY(CPoint(0, line)))
1699                         line = m_nTopLine;
1700                 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1701                 if (curDiff >= 0)
1702                         SelectDiff(curDiff, true, false);
1703         }
1704 }
1705
1706 /**
1707  * @brief Update "Previous diff X and Y" UI items
1708  */
1709 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1710 {
1711         CMergeDoc *pd = GetDocument();
1712
1713         if (pd->m_nBuffers < 3)
1714         {
1715                 pCmdUI->Enable(false);
1716                 return;
1717         }
1718
1719         const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1720
1721         if (dfi == nullptr)
1722         {
1723                 // There aren't any significant differences
1724                 pCmdUI->Enable(false);
1725         }
1726         else
1727         {
1728                 // Enable if the end of the first significant difference is before caret
1729                 CPoint pos = GetCursorPos();
1730                 pCmdUI->Enable(pos.y > (long)dfi->dend);
1731         }
1732 }
1733
1734 void CMergeEditView::OnNextdiffLM()
1735 {
1736         OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1737 }
1738
1739 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1740 {
1741         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1742 }
1743
1744 void CMergeEditView::OnNextdiffLR()
1745 {
1746         OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1747 }
1748
1749 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1750 {
1751         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1752 }
1753
1754 void CMergeEditView::OnNextdiffMR()
1755 {
1756         OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1757 }
1758
1759 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1760 {
1761         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1762 }
1763
1764 void CMergeEditView::OnNextdiffLO()
1765 {
1766         OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1767 }
1768
1769 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1770 {
1771         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1772 }
1773
1774 void CMergeEditView::OnNextdiffMO()
1775 {
1776         OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1777 }
1778
1779 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1780 {
1781         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1782 }
1783
1784 void CMergeEditView::OnNextdiffRO()
1785 {
1786         OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1787 }
1788
1789 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1790 {
1791         OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1792 }
1793
1794 void CMergeEditView::OnPrevdiffLM()
1795 {
1796         OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1797 }
1798
1799 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1800 {
1801         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1802 }
1803
1804 void CMergeEditView::OnPrevdiffLR()
1805 {
1806         OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1807 }
1808
1809 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1810 {
1811         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1812 }
1813
1814 void CMergeEditView::OnPrevdiffMR()
1815 {
1816         OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1817 }
1818
1819 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1820 {
1821         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1822 }
1823
1824 void CMergeEditView::OnPrevdiffLO()
1825 {
1826         OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1827 }
1828
1829 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1830 {
1831         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1832 }
1833
1834 void CMergeEditView::OnPrevdiffMO()
1835 {
1836         OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1837 }
1838
1839 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1840 {
1841         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1842 }
1843
1844 void CMergeEditView::OnPrevdiffRO()
1845 {
1846         OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1847 }
1848
1849 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1850 {
1851         OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1852 }
1853
1854 /**
1855  * @brief Clear selection
1856  */
1857 void CMergeEditView::SelectNone()
1858 {
1859         SetSelection (GetCursorPos(), GetCursorPos());
1860         UpdateCaret();
1861 }
1862
1863 /**
1864  * @brief Check if line is inside currently selected diff
1865  * @param [in] nLine 0-based linenumber in view
1866  * @sa CMergeDoc::GetCurrentDiff()
1867  * @sa CMergeDoc::LineInDiff()
1868  */
1869 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1870 {
1871         // Check validity of nLine
1872 #ifdef _DEBUG
1873         if (nLine < 0)
1874                 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1875         int nLineCount = LocateTextBuffer()->GetLineCount();
1876         if (nLine >= nLineCount)
1877                 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1878 #endif
1879
1880         const CMergeDoc *pd = GetDocument();
1881         int curDiff = pd->GetCurrentDiff();
1882         if (curDiff == -1)
1883                 return false;
1884         return pd->m_diffList.LineInDiff(nLine, curDiff);
1885 }
1886
1887 /**
1888  * @brief Called when mouse left-button double-clicked
1889  *
1890  * Double-clicking mouse inside diff selects that diff
1891  */
1892 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1893 {
1894         CMergeDoc *pd = GetDocument();
1895         CPoint pos = GetCursorPos();
1896
1897         int diff = pd->m_diffList.LineToDiff(pos.y);
1898         if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1899                 SelectDiff(diff, false, false);
1900
1901         CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1902 }
1903
1904 /**
1905  * @brief Called when mouse left button is released.
1906  *
1907  * If button is released outside diffs, current diff
1908  * is deselected.
1909  */
1910 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1911 {
1912         CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1913         DeselectDiffIfCursorNotInCurrentDiff();
1914 }
1915
1916 /**
1917  * @brief Called when mouse right button is pressed.
1918  *
1919  * If right button is pressed outside diffs, current diff
1920  * is deselected.
1921  */
1922 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1923 {
1924         CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1925         DeselectDiffIfCursorNotInCurrentDiff();
1926 }
1927
1928 void CMergeEditView::OnX2Y(int srcPane, int dstPane, bool selectedLineOnly)
1929 {
1930         // Check that right side is not readonly
1931         if (IsReadOnly(dstPane))
1932                 return;
1933
1934         CMergeDoc *pDoc = GetDocument();
1935         int currentDiff = pDoc->GetCurrentDiff();
1936
1937         if (currentDiff == -1)
1938         {
1939                 // No selected diff
1940                 // If cursor is inside diff get number of that diff
1941                 if (m_bCurrentLineIsDiff)
1942                 {
1943                         CPoint pt = GetCursorPos();
1944                         currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1945                 }
1946         }
1947
1948         auto [ptStart, ptEnd] = GetSelection();
1949         if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1950         {
1951                 if (!m_bRectangularSelection)
1952                 {
1953                         if (selectedLineOnly)
1954                         {
1955                                 int firstDiff, lastDiff;
1956                                 GetSelectedDiffs(firstDiff, lastDiff);
1957                                 if (firstDiff != -1 && lastDiff != -1)
1958                                 {
1959                                         CWaitCursor waitstatus;
1960                                         pDoc->CopyMultiplePartialList(srcPane, dstPane, firstDiff, lastDiff, ptStart.y, ptEnd.y);
1961                                 }
1962                         }
1963                         else
1964                         {
1965                                 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1966                                 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1967                                 if (firstDiff != -1 && lastDiff != -1)
1968                                 {
1969                                         CWaitCursor waitstatus;
1970                                         
1971                                         // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1972                                         // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1973                                         if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1974                                         {
1975                                                 // old behaviour: copy full line
1976                                                 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1977                                         }
1978                                         else
1979                                         {
1980                                                 // new behaviour: copy selected text only
1981                                                 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1982                                         }
1983                                 }
1984                         }
1985                 }
1986                 else
1987                 {
1988                         CWaitCursor waitstatus;
1989                         auto wordDiffs = GetColumnSelectedWordDiffIndice();
1990                         int i = 0;
1991                         std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1992                                 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
1993                                 ++i;
1994                         });
1995                 }
1996         }
1997         else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1998         {
1999                 if (selectedLineOnly)
2000                 {
2001                         CWaitCursor waitstatus;
2002                         pDoc->PartialListCopy(srcPane, dstPane, currentDiff, ptStart.y, ptEnd.y);
2003                 }
2004                 else
2005                 {
2006                         CWaitCursor waitstatus;
2007                         pDoc->ListCopy(srcPane, dstPane, currentDiff);
2008                 }
2009         }
2010 }
2011
2012 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
2013 {
2014         // Check that right side is not readonly
2015         if (!IsReadOnly(dstPane))
2016         {
2017                 // If one or more diffs inside selection OR
2018                 // there is an active diff OR
2019                 // cursor is inside diff
2020                 auto [ptStart, ptEnd] = GetSelection();
2021                 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
2022                 {
2023                         if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
2024                         {
2025                                 pCmdUI->Enable(true);
2026                         }
2027                         else
2028                         {
2029                                 int firstDiff, lastDiff;
2030                                 GetFullySelectedDiffs(firstDiff, lastDiff);
2031
2032                                 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
2033                         }
2034                 }
2035                 else
2036                 {
2037                         const int currDiff = GetDocument()->GetCurrentDiff();
2038                         pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
2039                 }
2040         }
2041         else
2042                 pCmdUI->Enable(false);
2043 }
2044
2045 /**
2046  * @brief Copy diff from left pane to right pane
2047  *
2048  * Difference is copied from left to right when
2049  * - difference is selected
2050  * - difference is inside selection (allows merging multiple differences).
2051  * - cursor is inside diff
2052  *
2053  * If there is selected diff outside selection, we copy selected
2054  * difference only.
2055  */
2056 void CMergeEditView::OnL2r()
2057 {
2058         int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2059         int srcPane = dstPane - 1;
2060         OnX2Y(srcPane, dstPane);
2061 }
2062
2063 /**
2064  * @brief Called when "Copy to left" item is updated
2065  */
2066 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
2067 {
2068         OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2069 }
2070
2071 void CMergeEditView::OnLinesL2r()
2072 {
2073         int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2074         int srcPane = dstPane - 1;
2075         OnX2Y(srcPane, dstPane, true);
2076 }
2077
2078 void CMergeEditView::OnUpdateLinesL2r(CCmdUI* pCmdUI)
2079 {
2080         OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2081 }
2082
2083 /**
2084  * @brief Copy diff from right pane to left pane
2085  *
2086  * Difference is copied from left to right when
2087  * - difference is selected
2088  * - difference is inside selection (allows merging multiple differences).
2089  * - cursor is inside diff
2090  *
2091  * If there is selected diff outside selection, we copy selected
2092  * difference only.
2093  */
2094 void CMergeEditView::OnR2l()
2095 {
2096         int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2097         int srcPane = dstPane + 1;
2098         OnX2Y(srcPane, dstPane);
2099 }
2100
2101 /**
2102  * @brief Called when "Copy to right" item is updated
2103  */
2104 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
2105 {
2106         OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2107 }
2108
2109 void CMergeEditView::OnLinesR2l()
2110 {
2111         int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2112         int srcPane = dstPane + 1;
2113         OnX2Y(srcPane, dstPane, true);
2114 }
2115
2116 void CMergeEditView::OnUpdateLinesR2l(CCmdUI* pCmdUI)
2117 {
2118         OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2119 }
2120
2121 void CMergeEditView::OnCopyFromLeft()
2122 {
2123         int dstPane = m_nThisPane;
2124         int srcPane = dstPane - 1;
2125         if (srcPane < 0)
2126                 return;
2127         OnX2Y(srcPane, dstPane);
2128 }
2129
2130 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2131 {
2132         int dstPane = m_nThisPane;
2133         int srcPane = dstPane - 1;
2134         if (srcPane < 0)
2135                 pCmdUI->Enable(false);
2136         else
2137                 OnUpdateX2Y(dstPane, pCmdUI);
2138 }
2139
2140 void CMergeEditView::OnCopyLinesFromLeft()
2141 {
2142         int dstPane = m_nThisPane;
2143         int srcPane = dstPane - 1;
2144         if (srcPane < 0)
2145                 return;
2146         OnX2Y(srcPane, dstPane, true);
2147 }
2148
2149 void CMergeEditView::OnUpdateCopyLinesFromLeft(CCmdUI* pCmdUI)
2150 {
2151         int dstPane = m_nThisPane;
2152         int srcPane = dstPane - 1;
2153         if (srcPane < 0)
2154                 pCmdUI->Enable(false);
2155         else
2156                 OnUpdateX2Y(dstPane, pCmdUI);
2157 }
2158
2159 void CMergeEditView::OnCopyFromRight()
2160 {
2161         int dstPane = m_nThisPane;
2162         int srcPane = dstPane + 1;
2163         if (srcPane >= GetDocument()->m_nBuffers)
2164                 return;
2165         OnX2Y(srcPane, dstPane);
2166 }
2167
2168 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2169 {
2170         int dstPane = m_nThisPane;
2171         int srcPane = dstPane + 1;
2172         if (srcPane >= GetDocument()->m_nBuffers)
2173                 pCmdUI->Enable(false);
2174         else
2175                 OnUpdateX2Y(dstPane, pCmdUI);
2176 }
2177
2178 void CMergeEditView::OnCopyLinesFromRight()
2179 {
2180         int dstPane = m_nThisPane;
2181         int srcPane = dstPane + 1;
2182         if (srcPane >= GetDocument()->m_nBuffers)
2183                 return;
2184         OnX2Y(srcPane, dstPane, true);
2185 }
2186
2187 void CMergeEditView::OnUpdateCopyLinesFromRight(CCmdUI* pCmdUI)
2188 {
2189         int dstPane = m_nThisPane;
2190         int srcPane = dstPane + 1;
2191         if (srcPane >= GetDocument()->m_nBuffers)
2192                 pCmdUI->Enable(false);
2193         else
2194                 OnUpdateX2Y(dstPane, pCmdUI);
2195 }
2196
2197 /**
2198  * @brief Copy all diffs from right pane to left pane
2199  */
2200 void CMergeEditView::OnAllLeft()
2201 {
2202         // Check that left side is not readonly
2203         int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
2204         int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2205         if (IsReadOnly(dstPane))
2206                 return;
2207         CWaitCursor waitstatus;
2208
2209         GetDocument()->CopyAllList(srcPane, dstPane);
2210 }
2211
2212 /**
2213  * @brief Called when "Copy all to left" item is updated
2214  */
2215 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2216 {
2217         // Check that left side is not readonly
2218         int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2219         if (!IsReadOnly(dstPane))
2220                 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2221         else
2222                 pCmdUI->Enable(false);
2223 }
2224
2225 /**
2226  * @brief Copy all diffs from left pane to right pane
2227  */
2228 void CMergeEditView::OnAllRight()
2229 {
2230         // Check that right side is not readonly
2231         int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
2232         int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2233         if (IsReadOnly(dstPane))
2234                 return;
2235
2236         CWaitCursor waitstatus;
2237
2238         GetDocument()->CopyAllList(srcPane, dstPane);
2239 }
2240
2241 /**
2242  * @brief Called when "Copy all to right" item is updated
2243  */
2244 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2245 {
2246         // Check that right side is not readonly
2247         int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2248         if (!IsReadOnly(dstPane))
2249                 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2250         else
2251                 pCmdUI->Enable(false);
2252 }
2253
2254 /**
2255  * @brief Do Auto merge
2256  */
2257 void CMergeEditView::OnAutoMerge()
2258 {
2259         // Check current pane is not readonly
2260         if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2261                 return;
2262
2263         CWaitCursor waitstatus;
2264
2265         GetDocument()->DoAutoMerge(m_nThisPane);
2266 }
2267
2268 /**
2269  * @brief Called when "Auto Merge" item is updated
2270  */
2271 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2272 {
2273         pCmdUI->Enable(GetDocument()->m_nBuffers == 3 && 
2274                 !GetDocument()->IsModified() && 
2275                 !GetDocument()->GetAutoMerged() && 
2276                 QueryEditable());
2277 }
2278
2279 /**
2280  * @brief Add synchronization point
2281  */
2282 void CMergeEditView::OnAddSyncPoint()
2283 {
2284         GetDocument()->AddSyncPoint();
2285 }
2286
2287 /**
2288  * @brief Clear synchronization points
2289  */
2290 void CMergeEditView::OnClearSyncPoints()
2291 {
2292         GetDocument()->ClearSyncPoints();
2293 }
2294
2295 /**
2296  * @brief Called when "Clear Synchronization Points" item is updated
2297  */
2298 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2299 {
2300         pCmdUI->Enable(GetDocument()->HasSyncPoints());
2301 }
2302
2303 /**
2304  * @brief This function is called before other edit events.
2305  * @param [in] nAction Edit operation to do
2306  * @param [in] pszText Text to insert, delete etc
2307  * @sa CCrystalEditView::OnEditOperation()
2308  * @todo More edit-events for rescan delaying?
2309  */
2310 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2311 {
2312         if (!QueryEditable())
2313         {
2314                 // We must not arrive here, and assert helps detect troubles
2315                 ASSERT(false);
2316                 return;
2317         }
2318
2319         CMergeDoc* pDoc = GetDocument();
2320         pDoc->SetEditedAfterRescan(m_nThisPane);
2321
2322         // simple hook for multiplex undo operations
2323         // deleted by jtuc 2003-06-28
2324         // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2325         /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2326         {
2327                 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2328                 pDoc->undoTgt.push_back(this);
2329                 pDoc->curUndo = pDoc->undoTgt.end();
2330         }*/
2331
2332         // perform original function
2333         CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2334
2335         // augment with additional operations
2336
2337         // Change header to inform about changed doc
2338         pDoc->UpdateHeaderPath(m_nThisPane);
2339
2340         // If automatic rescan enabled, rescan after edit events
2341         if (m_bAutomaticRescan)
2342         {
2343                 // keep document up to date     
2344                 // (Re)start timer to rescan only when user edits text
2345                 // If timer starting fails, rescan immediately
2346                 if (nAction == CE_ACTION_TYPING ||
2347                         nAction == CE_ACTION_REPLACE ||
2348                         nAction == CE_ACTION_BACKSPACE ||
2349                         nAction == CE_ACTION_INDENT ||
2350                         nAction == CE_ACTION_PASTE ||
2351                         nAction == CE_ACTION_DELSEL ||
2352                         nAction == CE_ACTION_DELETE ||
2353                         nAction == CE_ACTION_CUT)
2354                 {
2355                         if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2356                                 pDoc->FlushAndRescan();
2357                 }
2358                 else
2359                         pDoc->FlushAndRescan();
2360         }
2361         else
2362         {
2363                 if (m_bWordWrap)
2364                 {
2365                         // Update other pane for sync line.
2366                         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2367                         {
2368                                 if (nPane == m_nThisPane)
2369                                         continue;
2370                                 CCrystalEditView *pView = GetGroupView(nPane);
2371                                 if (pView != nullptr)
2372                                         pView->Invalidate();
2373                         }
2374                 }
2375         }
2376 }
2377
2378 /**
2379  * @brief Redo last action
2380  */
2381 void CMergeEditView::OnEditRedo()
2382 {
2383         CWaitCursor waitstatus;
2384         CMergeDoc* pDoc = GetDocument();
2385         CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2386         if(tgt==this)
2387         {
2388                 if (!QueryEditable())
2389                         return;
2390
2391                 GetParentFrame()->SetActiveView(this, true);
2392                 if(CCrystalEditViewEx::DoEditRedo())
2393                 {
2394                         ++pDoc->curUndo;
2395                         pDoc->UpdateHeaderPath(m_nThisPane);
2396                         pDoc->FlushAndRescan();
2397                 }
2398         }
2399         else
2400         {
2401                 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2402         }
2403 }
2404
2405 /**
2406  * @brief Called when "Redo" item is updated
2407  */
2408 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2409 {
2410         CMergeDoc* pDoc = GetDocument();
2411         if (pDoc->curUndo!=pDoc->undoTgt.end())
2412         {
2413                 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2414                 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2415         }
2416         else
2417                 pCmdUI->Enable(false);
2418 }
2419
2420 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2421 {
2422         CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2423 }
2424
2425 /**
2426  * @brief Scrolls to current diff and/or selects diff text
2427  * @param [in] bScroll If true scroll diff to view
2428  * @param [in] bSelectText If true select diff text
2429  * @note If bScroll and bSelectText are false, this does nothing!
2430  * @todo This shouldn't be called when no diff is selected, so
2431  * somebody could try to ASSERT(nDiff > -1)...
2432  */
2433 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2434 {
2435         CMergeDoc *pd = GetDocument();
2436         const int nDiff = pd->GetCurrentDiff();
2437
2438         // Try to trap some errors
2439         if (nDiff >= pd->m_diffList.GetSize())
2440                 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2441                         nDiff, pd->m_diffList.GetSize());
2442
2443         if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2444         {
2445                 CPoint ptStart, ptEnd;
2446                 DIFFRANGE curDiff;
2447                 pd->m_diffList.GetDiff(nDiff, curDiff);
2448
2449                 ptStart.x = 0;
2450                 ptStart.y = curDiff.dbegin;
2451                 ptEnd.x = 0;
2452                 ptEnd.y = curDiff.dend;
2453
2454                 if (bScroll && !m_bDetailView)
2455                 {
2456                         if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2457                         {
2458                                 // Difference is not visible, scroll it so that max amount of
2459                                 // scrolling is done while keeping the diff in screen. So if
2460                                 // scrolling is downwards, scroll the diff to as up in screen
2461                                 // as possible. This usually brings next diff to the screen
2462                                 // and we don't need to scroll into it.
2463                                 int nLine = GetSubLineIndex(ptStart.y);
2464                                 if (nLine > CONTEXT_LINES_ABOVE)
2465                                 {
2466                                         nLine -= CONTEXT_LINES_ABOVE;
2467                                 }
2468                                 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2469                                 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2470                                 {
2471                                         if (nPane != m_nThisPane)
2472                                                 GetGroupView(nPane)->ScrollToSubLine(nLine);
2473                                 }
2474                         }
2475
2476                         vector<WordDiff> worddiffs;
2477                         if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
2478                                 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2479                         CPoint pt = worddiffs.size() > 0 ?
2480                                 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2481                                 ptStart;
2482                         GetGroupView(m_nThisPane)->SetCursorPos(pt);
2483                         GetGroupView(m_nThisPane)->SetAnchor(pt);
2484                         GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2485                         GetGroupView(m_nThisPane)->EnsureVisible(pt);
2486                         for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2487                         {
2488                                 if (nPane != m_nThisPane)
2489                                 {
2490                                         if (worddiffs.size() > 0)
2491                                         {
2492                                                 pt.x = worddiffs[0].begin[nPane];
2493                                                 pt.y = worddiffs[0].beginline[nPane];
2494                                         }
2495                                         GetGroupView(nPane)->SetCursorPos(pt);
2496                                         GetGroupView(nPane)->SetAnchor(pt);
2497                                         GetGroupView(nPane)->SetSelection(pt, pt);
2498                                 }
2499                         }
2500                 }
2501
2502                 if (bSelectText)
2503                 {
2504                         ptEnd.x = GetLineLength(ptEnd.y);
2505                         SetSelection(ptStart, ptEnd);
2506                         UpdateCaret();
2507                 }
2508                 else
2509                         Invalidate();
2510         }
2511 }
2512
2513
2514 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2515 {
2516         // Maybe we want theApp::OnIdle to proceed before processing a timer message
2517         // ...but for this the queue must be empty
2518         // The timer message is a low priority message but the queue is maybe not yet empty
2519         // So we set a flag, wait for OnIdle to proceed, then come back here...
2520         // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2521         // not with SetTimer so there is no delay)
2522
2523         // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2524
2525         // IDLE_TIMER is the false timer used to come back here after OnIdle
2526         // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2527         // (one normal timer = one flag = one command)
2528
2529         if (nIDEvent == IDT_RESCAN)
2530         {
2531                 KillTimer(IDT_RESCAN);
2532                 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2533                 // notify the app to come back after OnIdle
2534                 theApp.SetNeedIdleTimer();
2535         }
2536
2537         if (nIDEvent == IDLE_TIMER)
2538         {
2539                 // not a real timer, just come back after OnIdle
2540                 // look to flags to know what to do
2541                 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2542                         GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2543                 fTimerWaitingForIdle = 0;
2544         }
2545
2546         CCrystalEditViewEx::OnTimer(nIDEvent);
2547 }
2548
2549 /**
2550  * @brief Returns if buffer is read-only
2551  * @note This has no any relation to file being read-only!
2552  */
2553 bool CMergeEditView::IsReadOnly(int pane) const
2554 {
2555         return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2556 }
2557
2558 /**
2559  * @brief Called when "Save left (as...)" item is updated
2560  */
2561 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2562 {
2563         CMergeDoc *pd = GetDocument();
2564         pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2565 }
2566
2567 /**
2568  * @brief Called when "Save middle (as...)" item is updated
2569  */
2570 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2571 {
2572         CMergeDoc *pd = GetDocument();
2573         pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2574 }
2575
2576 /**
2577  * @brief Called when "Save right (as...)" item is updated
2578  */
2579 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2580 {
2581         CMergeDoc *pd = GetDocument();
2582         pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2583 }
2584
2585 /**
2586  * @brief Refresh display using text-buffers
2587  * @note This DOES NOT reload files!
2588  */
2589 void CMergeEditView::OnRefresh()
2590 {
2591         CMergeDoc *pd = GetDocument();
2592         ASSERT(pd != nullptr);
2593         pd->FlushAndRescan(true);
2594 }
2595
2596 /**
2597  * @brief Handle some keys when in merging mode
2598  */
2599 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2600 {
2601         bool bHandled = false;
2602
2603         // Allow default text selection when SHIFT pressed
2604         if (::GetAsyncKeyState(VK_SHIFT))
2605                 return false;
2606
2607         // Allow default editor functions when CTRL pressed
2608         if (::GetAsyncKeyState(VK_CONTROL))
2609                 return false;
2610
2611         // If we are in merging mode (merge with cursor keys)
2612         // handle some keys here
2613         switch (pMsg->wParam)
2614         {
2615         case VK_LEFT:
2616                 OnR2l();
2617                 bHandled = true;
2618                 break;
2619
2620         case VK_RIGHT:
2621                 OnL2r();
2622                 bHandled = true;
2623                 break;
2624
2625         case VK_UP:
2626                 OnPrevdiff();
2627                 bHandled = true;
2628                 break;
2629
2630         case VK_DOWN:
2631                 OnNextdiff();
2632                 bHandled = true;
2633                 break;
2634         }
2635
2636         return bHandled;
2637 }
2638
2639 /**
2640  * @brief Called before messages are translated.
2641  *
2642  * Checks if ESC key was pressed, saves and closes doc.
2643  * Also if in merge mode traps cursor keys.
2644  */
2645 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2646 {
2647         if (pMsg->message == WM_KEYDOWN)
2648         {
2649                 // If we are in merging mode (merge with cursor keys)
2650                 // handle some keys here
2651                 if (theApp.GetMergingMode())
2652                 {
2653                         bool bHandled = MergeModeKeyDown(pMsg);
2654                         if (bHandled)
2655                                 return false;
2656                 }
2657
2658                 // Close window if user has allowed it from options
2659                 if (pMsg->wParam == VK_ESCAPE)
2660                 {
2661                         int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2662                         if (nCloseWithEsc != 0)
2663                                 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2664                         return false;
2665                 }
2666         }
2667
2668         return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2669 }
2670
2671 /**
2672  * @brief Called when "Save" item is updated
2673  */
2674 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2675 {
2676         CMergeDoc *pd = GetDocument();
2677
2678         bool bModified = false;
2679         for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2680         {
2681                 if (pd->m_ptBuf[nPane]->IsModified())
2682                         bModified = true;
2683         }
2684         pCmdUI->Enable(bModified);
2685 }
2686
2687 /**
2688  * @brief Enable/disable left buffer read-only
2689  */
2690 void CMergeEditView::OnLeftReadOnly()
2691 {
2692         CMergeDoc *pd = GetDocument();
2693         bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2694         pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2695 }
2696
2697 /**
2698  * @brief Called when "Left read-only" item is updated
2699  */
2700 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2701 {
2702         CMergeDoc *pd = GetDocument();
2703         bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2704         pCmdUI->Enable(true);
2705         pCmdUI->SetCheck(bReadOnly);
2706 }
2707
2708 /**
2709  * @brief Enable/disable middle buffer read-only
2710  */
2711 void CMergeEditView::OnMiddleReadOnly()
2712 {
2713         CMergeDoc *pd = GetDocument();
2714         if (pd->m_nBuffers == 3)
2715         {
2716                 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2717                 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2718         }
2719 }
2720
2721 /**
2722  * @brief Called when "Middle read-only" item is updated
2723  */
2724 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2725 {
2726         CMergeDoc *pd = GetDocument();
2727         if (pd->m_nBuffers < 3)
2728         {
2729                 pCmdUI->Enable(false);
2730         }
2731         else
2732         {
2733                 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2734                 pCmdUI->Enable(true);
2735                 pCmdUI->SetCheck(bReadOnly);
2736         }
2737 }
2738
2739 /**
2740  * @brief Enable/disable right buffer read-only
2741  */
2742 void CMergeEditView::OnRightReadOnly()
2743 {
2744         CMergeDoc *pd = GetDocument();
2745         bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2746         pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2747 }
2748
2749 /**
2750  * @brief Called when "Left read-only" item is updated
2751  */
2752 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2753 {
2754         CMergeDoc *pd = GetDocument();
2755         bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2756         pCmdUI->Enable(true);
2757         pCmdUI->SetCheck(bReadOnly);
2758 }
2759
2760 /// Store interface we use to display status line info
2761 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2762 {
2763         ASSERT(m_piMergeEditStatus == nullptr);
2764         m_piMergeEditStatus = piMergeEditStatus;
2765 }
2766
2767 /**
2768  * @brief Update status bar contents.
2769  */
2770 void CMergeEditView::UpdateStatusbar()
2771 {
2772         OnUpdateCaret();
2773 }
2774
2775 /**
2776  * @brief Update statusbar info, Override from CCrystalTextView
2777  * @note we tab-expand column, but we don't tab-expand char count,
2778  * since we want to show how many chars there are and tab is just one
2779  * character although it expands to several spaces.
2780  */
2781 void CMergeEditView::OnUpdateCaret()
2782 {
2783         if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2784                 return;
2785
2786         CPoint cursorPos = GetCursorPos();
2787         int nScreenLine = cursorPos.y;
2788         const int nRealLine = ComputeRealLine(nScreenLine);
2789         CString sLine;
2790         int chars = -1;
2791         CString sEol;
2792         int column = -1;
2793         int columns = -1;
2794         int curChar = -1;
2795         auto [selectedLines, selectedChars] = GetSelectedLineAndCharacterCount();
2796         DWORD dwLineFlags = 0;
2797
2798         dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2799         // Is this a ghost line ?
2800         if (dwLineFlags & LF_GHOST)
2801         {
2802                 // Ghost lines display eg "Line 12-13"
2803                 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2804                 sEol = _T("hidden");
2805         }
2806         else
2807         {
2808                 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2809                 sLine.Format(_T("%d"), nRealLine+1);
2810                 curChar = cursorPos.x + 1;
2811                 chars = GetLineLength(nScreenLine);
2812                 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2813                 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2814                 chars++;
2815                 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2816                                 GetDocument()->IsMixedEOL(m_nThisPane))
2817                 {
2818                         sEol = GetTextBufferEol(nScreenLine);
2819                 }
2820                 else
2821                         sEol = _T("hidden");
2822         }
2823         m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2824                 curChar, chars, selectedLines, selectedChars,
2825                 sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2826
2827         // Is cursor inside difference?
2828         if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2829                 m_bCurrentLineIsDiff = true;
2830         else
2831                 m_bCurrentLineIsDiff = false;
2832
2833         CWnd* pWnd = GetFocus();
2834         if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2835                 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2836 }
2837 /**
2838  * @brief Select linedifference in the current line.
2839  *
2840  * Select line difference in current line. Selection type
2841  * is choosed by highlight type.
2842  */
2843 template<bool reversed>
2844 void CMergeEditView::OnSelectLineDiff()
2845 {
2846         // Pass this to the document, to compare this file to other
2847         GetDocument()->Showlinediff(this, reversed);
2848 }
2849
2850 /// Enable select difference menuitem if current line is inside difference.
2851 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2852 {
2853         pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2854 }
2855
2856 void CMergeEditView::OnAddToSubstitutionFilters()
2857 {
2858         // Pass this to the document, to compare this file to other
2859         GetDocument()->AddToSubstitutionFilters(this, false);
2860 }
2861
2862 void CMergeEditView::OnUpdateAddToSubstitutionFilters(CCmdUI* pCmdUI)
2863 {
2864         pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
2865 }
2866
2867 /**
2868  * @brief Enable/disable Replace-menuitem
2869  */
2870 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2871 {
2872         CMergeDoc *pd = GetDocument();
2873         bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2874
2875         pCmdUI->Enable(!bReadOnly);
2876 }
2877
2878 /**
2879  * @brief Update readonly statusbaritem
2880  */
2881 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2882 {
2883         bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2884         pCmdUI->Enable(bRO);
2885 }
2886
2887 /**
2888  * @brief Create the dynamic submenu for scripts
2889  */
2890 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2891 {
2892         // get scripts list
2893         std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2894
2895         // empty the menu
2896         size_t i = GetMenuItemCount(hMenu);
2897         while (i --)
2898                 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2899
2900         if (functionNamesList.size() == 0)
2901         {
2902                 // no script : create a <empty> entry
2903                 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2904         }
2905         else
2906         {
2907                 // or fill in the submenu with the scripts names
2908                 int ID = ID_SCRIPT_FIRST;       // first ID in menu
2909                 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2910                         AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2911
2912                 functionNamesList.clear();
2913         }
2914
2915         if (!plugin::IsWindowsScriptThere())
2916                 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2917
2918         return hMenu;
2919 }
2920
2921 /**
2922  * @brief Create the dynamic submenu for prediffers
2923  *
2924  * @note The plugins are grouped in (suggested) and (not suggested)
2925  *       The IDs follow the order of GetAvailableScripts
2926  *       For example :
2927  *                              suggested 0         ID_1ST + 0 
2928  *                              suggested 1         ID_1ST + 2 
2929  *                              suggested 2         ID_1ST + 5 
2930  *                              not suggested 0     ID_1ST + 1 
2931  *                              not suggested 1     ID_1ST + 3 
2932  *                              not suggested 2     ID_1ST + 4 
2933  */
2934 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2935 {
2936         // empty the menu
2937         int i = GetMenuItemCount(hMenu);
2938         while (i --)
2939                 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2940
2941         CMergeDoc *pd = GetDocument();
2942         ASSERT(pd != nullptr);
2943
2944         // title
2945         AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2946
2947         // get the scriptlet files
2948         PluginArray * piScriptArray = 
2949                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2950         PluginArray * piScriptArray2 = 
2951                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2952
2953         // build the menu : first part, suggested plugins
2954         // title
2955         AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2956         AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2957
2958         int ID = ID_PREDIFFERS_FIRST;   // first ID in menu
2959         size_t iScript;
2960         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2961         {
2962                 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2963                 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2964                         continue;
2965
2966                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2967         }
2968         for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2969         {
2970                 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2971                 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2972                         continue;
2973
2974                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2975         }
2976
2977         // build the menu : second part, others plugins
2978         // title
2979         AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2980         AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2981
2982         ID = ID_PREDIFFERS_FIRST;       // first ID in menu
2983         for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2984         {
2985                 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2986                 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2987                         continue;
2988
2989                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2990         }
2991         for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2992         {
2993                 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2994                 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2995                         continue;
2996
2997                 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2998         }
2999
3000         // compute the m_CurrentPredifferID (to set the radio button)
3001         PrediffingInfo prediffer;
3002         pd->GetPrediffer(&prediffer);
3003
3004         if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3005                 m_CurrentPredifferID = 0;
3006         else if (prediffer.m_PluginName.empty())
3007                 m_CurrentPredifferID = ID_NO_PREDIFFER;
3008         else
3009         {
3010                 ID = ID_PREDIFFERS_FIRST;       // first ID in menu
3011                 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
3012                 {
3013                         const PluginInfoPtr & plugin = piScriptArray->at(iScript);
3014                         if (prediffer.m_PluginName == plugin->m_name)
3015                                 m_CurrentPredifferID = ID;
3016
3017                 }
3018                 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
3019                 {
3020                         const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
3021                         if (prediffer.m_PluginName == plugin->m_name)
3022                                 m_CurrentPredifferID = ID;
3023                 }
3024         }
3025
3026         return hMenu;
3027 }
3028
3029 /**
3030  * @brief Offer a context menu built with scriptlet/ActiveX functions
3031  */
3032 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
3033 {
3034         // Create the menu and populate it with the available functions
3035         BCMenu menu;
3036         VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
3037
3038         // Remove copying item copying from active side
3039         if (m_nThisPane == 0) // left?
3040         {
3041                 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3042                 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3043                 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3044                 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3045         }
3046         if (m_nThisPane == GetDocument()->m_nBuffers - 1)
3047         {
3048                 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3049                 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3050                 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3051                 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3052         }
3053
3054         // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
3055         // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
3056         // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
3057         int nBuffers = GetDocument()->m_nBuffers;
3058         if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
3059                 menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
3060         else if (nBuffers == 3 && m_nThisPane == 2)
3061                 menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
3062
3063         VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
3064         theApp.TranslateMenu(menu.m_hMenu);
3065
3066         BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
3067         ASSERT(pSub != nullptr);
3068
3069         // Context menu opened using keyboard has no coordinates
3070         if (point.x == -1 && point.y == -1)
3071         {
3072                 CRect rect;
3073                 GetClientRect(rect);
3074                 ClientToScreen(rect);
3075
3076                 point = rect.TopLeft();
3077                 point.Offset(5, 5);
3078         }
3079
3080         pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
3081                 point.x, point.y, AfxGetMainWnd());
3082
3083 }
3084
3085 /**
3086  * @brief Update EOL mode in status bar
3087  */
3088 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
3089 {
3090         GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
3091 }
3092
3093 /**
3094  * @brief Change EOL mode and unify all the lines EOL to this new mode
3095  */
3096 void CMergeEditView::OnConvertEolTo(UINT nID )
3097 {
3098         CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;;
3099         switch (nID)
3100         {
3101                 case ID_EOL_TO_DOS:
3102                         nStyle = CRLFSTYLE::DOS;
3103                         break;
3104                 case ID_EOL_TO_UNIX:
3105                         nStyle = CRLFSTYLE::UNIX;
3106                         break;
3107                 case ID_EOL_TO_MAC:
3108                         nStyle = CRLFSTYLE::MAC;
3109                         break;
3110                 default:
3111                         // Catch errors
3112                         _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
3113                         break;
3114         }
3115         m_pTextBuffer->SetCRLFMode(nStyle);
3116
3117         // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
3118         if (m_pTextBuffer->applyEOLMode())
3119         {
3120                 CMergeDoc *pd = GetDocument();
3121                 ASSERT(pd != nullptr);
3122                 pd->UpdateHeaderPath(m_nThisPane);
3123                 pd->FlushAndRescan(true);
3124         }
3125 }
3126
3127 /**
3128  * @brief allow convert to entries in file submenu
3129  */
3130 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
3131 {
3132         CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;
3133         switch (pCmdUI->m_nID)
3134         {
3135                 case ID_EOL_TO_DOS:
3136                         nStyle = CRLFSTYLE::DOS;
3137                         break;
3138                 case ID_EOL_TO_UNIX:
3139                         nStyle = CRLFSTYLE::UNIX;
3140                         break;
3141                 case ID_EOL_TO_MAC:
3142                         nStyle = CRLFSTYLE::MAC;
3143                         break;
3144                 default:
3145                         // Catch errors
3146                         _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
3147                         break;
3148         }
3149
3150         if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3151                 GetDocument()->IsMixedEOL(m_nThisPane) ||
3152                 nStyle != m_pTextBuffer->GetCRLFMode())
3153         {
3154                 pCmdUI->SetRadio(false);
3155
3156                 // Don't allow selecting other EOL style for protected pane
3157                 if (!QueryEditable())
3158                         pCmdUI->Enable(false);
3159         }
3160         else
3161                 pCmdUI->SetRadio(true);
3162 }
3163
3164 /**
3165  * @brief Copy diff from left to right and advance to next diff
3166  */
3167 void CMergeEditView::OnL2RNext()
3168 {
3169         OnL2r();
3170         if (IsCursorInDiff()) // for 3-way file compare
3171                 OnNextdiff();
3172         OnNextdiff();
3173 }
3174
3175 /**
3176  * @brief Update "Copy right and advance" UI item
3177  */
3178 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
3179 {
3180         OnUpdateL2r(pCmdUI);
3181 }
3182
3183 /**
3184  * @brief Copy diff from right to left and advance to next diff
3185  */
3186 void CMergeEditView::OnR2LNext()
3187 {
3188         OnR2l();
3189         if (IsCursorInDiff()) // for 3-way file compare
3190                 OnNextdiff();
3191         OnNextdiff();
3192 }
3193
3194 /**
3195  * @brief Update "Copy left and advance" UI item
3196  */
3197 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
3198 {
3199         OnUpdateR2l(pCmdUI);
3200 }
3201
3202 /**
3203  * @brief Change active pane in MergeView.
3204  * Changes active pane and makes sure cursor position is kept in
3205  * screen. Currently we put cursor in same line than in original
3206  * active pane but we could be smarter too? Maybe update cursor
3207  * only when it is not visible in new pane?
3208  */
3209 void CMergeEditView::OnChangePane()
3210 {
3211         CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3212         CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
3213         CMergeDoc *pDoc = GetDocument();
3214         bool bFound = false;
3215         CMergeEditView *pNextActiveView = nullptr;
3216         std::vector<CMergeEditView *> list = pDoc->GetViewList();
3217         list.insert(list.end(), list.begin(), list.end());
3218         for (auto& pView : list)
3219         {
3220                 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3221                 {
3222                         pNextActiveView = pView;
3223                         break;
3224                 }
3225                 if (pWnd == pView)
3226                         bFound = true;
3227         }
3228         GetParentFrame()->SetActiveView(pNextActiveView);
3229         CPoint ptCursor = pWnd->GetCursorPos();
3230         ptCursor.x = 0;
3231         if (ptCursor.y >= pNextActiveView->GetLineCount())
3232                 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3233         pNextActiveView->SetCursorPos(ptCursor);
3234         pNextActiveView->SetAnchor(ptCursor);
3235         pNextActiveView->SetSelection(ptCursor, ptCursor);
3236 }
3237
3238 /**
3239  * @brief Show "Go To" dialog and scroll views to line or diff.
3240  *
3241  * Before dialog is opened, current line and file is determined
3242  * and selected.
3243  * @note Conversions needed between apparent and real lines
3244  */
3245 void CMergeEditView::OnWMGoto()
3246 {
3247         WMGotoDlg dlg;
3248         CMergeDoc *pDoc = GetDocument();
3249         CPoint pos = GetCursorPos();
3250         int nRealLine = 0;
3251         int nLastLine = 0;
3252
3253         nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3254         int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
3255         nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
3256
3257         // Set active file and current line selected in dialog
3258         dlg.m_strParam = strutils::to_str(nRealLine + 1);
3259         dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3260         dlg.m_nGotoWhat = 0;
3261
3262         if (dlg.DoModal() == IDOK)
3263         {
3264                 CMergeDoc * pDoc1 = GetDocument();
3265                 CMergeEditView * pCurrentView = nullptr;
3266
3267                 // Get views
3268                 pCurrentView = GetGroupView(m_nThisPane);
3269
3270                 int num = 0;
3271                 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3272
3273                 if (dlg.m_nGotoWhat == 0)
3274                 {
3275                         int nRealLine1 = num;
3276                         if (nRealLine1 < 0)
3277                                 nRealLine1 = 0;
3278                         if (nRealLine1 > nLastLine)
3279                                 nRealLine1 = nLastLine;
3280
3281                         bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
3282                         GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile, !bShift);
3283                 }
3284                 else
3285                 {
3286                         int diff = num;
3287                         if (diff < 0)
3288                                 diff = 0;
3289                         if (diff >= pDoc1->m_diffList.GetSize())
3290                                 diff = pDoc1->m_diffList.GetSize();
3291
3292                         pCurrentView->SelectDiff(diff, true, false);
3293                 }
3294         }
3295 }
3296
3297 /**
3298 * @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
3299 * Go to moved line between the left and right panes when in 2-way file comparison.
3300 * Go to moved line between the left and middle panes when in 3-way file comparison.
3301 */
3302 void CMergeEditView::OnGotoMovedLineLM()
3303 {
3304         if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3305                 return;
3306
3307         CMergeDoc* pDoc = GetDocument();
3308         CPoint pos = GetCursorPos();
3309
3310         ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3311         ASSERT(pDoc != nullptr);
3312         ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3313         ASSERT(pos.y >= 0);
3314
3315         if (m_nThisPane == 0)
3316         {
3317                 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3318                 if (line >= 0)
3319                         GotoLine(line, false, 1);
3320         }
3321         else if (m_nThisPane == 1)
3322         {
3323                 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3324                 if (line >= 0)
3325                         GotoLine(line, false, 0);
3326         }
3327 }
3328
3329 /**
3330  * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
3331  * @param [in] pCmdUI UI component to update.
3332  * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
3333  */
3334 void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
3335 {
3336         CMergeDoc* pDoc = GetDocument();
3337         CPoint pos = GetCursorPos();
3338
3339         ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3340         ASSERT(pCmdUI != nullptr);
3341         ASSERT(pDoc != nullptr);
3342         ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3343         ASSERT(pos.y >= 0);
3344
3345         if (pDoc->m_nBuffers == 2)
3346                 pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
3347
3348         if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
3349         {
3350                 pCmdUI->Enable(false);
3351                 return;
3352         }
3353
3354         if (m_nThisPane == 0)
3355         {
3356                 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3357                 pCmdUI->Enable(bOn);
3358         }
3359         else if (m_nThisPane == 1)
3360         {
3361                 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3362                 pCmdUI->Enable(bOn);
3363         }
3364 }
3365
3366 /**
3367 * @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
3368 * Go to moved line between the middle and right panes when in 3-way file comparison.
3369 */
3370 void CMergeEditView::OnGotoMovedLineMR()
3371 {
3372         if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3373                 return;
3374
3375         CMergeDoc* pDoc = GetDocument();
3376         CPoint pos = GetCursorPos();
3377
3378         ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3379         ASSERT(pDoc != nullptr);
3380         ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3381         ASSERT(pos.y >= 0);
3382
3383         if (m_nThisPane == 1)
3384         {
3385                 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3386                 if (line >= 0)
3387                         GotoLine(line, false, 2);
3388         }
3389         else if (m_nThisPane == 2)
3390         {
3391                 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3392                 if (line >= 0)
3393                         GotoLine(line, false, 1);
3394         }
3395 }
3396
3397 /**
3398  * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
3399  * @param [in] pCmdUI UI component to update.
3400  */
3401 void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
3402 {
3403         CMergeDoc* pDoc = GetDocument();
3404         CPoint pos = GetCursorPos();
3405
3406         ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3407         ASSERT(pCmdUI != nullptr);
3408         ASSERT(pDoc != nullptr);
3409         ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3410         ASSERT(pos.y >= 0);
3411
3412         if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
3413         {
3414                 pCmdUI->Enable(false);
3415                 return;
3416         }
3417
3418         if (m_nThisPane == 1)
3419         {
3420                 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3421                 pCmdUI->Enable(bOn);
3422         }
3423         else if (m_nThisPane == 2)
3424         {
3425                 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3426                 pCmdUI->Enable(bOn);
3427         }
3428 }
3429
3430 void CMergeEditView::OnShellMenu()
3431 {
3432         CFrameWnd *pFrame = GetTopLevelFrame();
3433         ASSERT(pFrame != nullptr);
3434         BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3435         pFrame->m_bAutoMenuEnable = FALSE;
3436
3437         String path = GetDocument()->m_filePaths[m_nThisPane];
3438         std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3439         pContextMenu->Initialize();
3440         pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3441         pContextMenu->RequeryShellContextMenu();
3442         CPoint point;
3443         ::GetCursorPos(&point);
3444         HWND hWnd = GetSafeHwnd();
3445         BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3446         if (nCmd)
3447                 pContextMenu->InvokeCommand(nCmd, hWnd);
3448         pContextMenu->ReleaseShellContextMenu();
3449
3450         pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3451 }
3452
3453 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3454 {
3455         pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3456 }
3457
3458 /**
3459  * @brief Reload options.
3460  */
3461 void CMergeEditView::RefreshOptions()
3462
3463         RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3464         SetRenderingMode(nRenderingMode);
3465
3466         m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3467
3468         if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3469                 SetInsertTabs(true);
3470         else
3471                 SetInsertTabs(false);
3472
3473         SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3474
3475         if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3476                 SetTextType(CrystalLineParser::SRC_PLAIN);
3477         else if (!m_bChangedSchemeManually)
3478         {
3479                 // The syntax highlighting scheme should only be applied if it has not been manually changed.
3480                 String fileName = GetDocument()->m_filePaths[m_nThisPane];
3481                 String sExt;
3482                 paths::SplitFilename(fileName, nullptr, nullptr, &sExt);
3483                 CrystalLineParser::TextDefinition* def = CrystalLineParser::GetTextType(sExt.c_str());
3484                 if (def != nullptr)
3485                         SetTextType(def->type);
3486                 else
3487                         SetTextType(CrystalLineParser::SRC_PLAIN);
3488         }
3489
3490         SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3491         SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3492
3493         SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3494         SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3495                 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3496                 GetDocument()->IsMixedEOL(m_nThisPane));
3497
3498         Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3499 }
3500
3501 void CMergeEditView::OnScripts(UINT nID )
3502 {
3503         // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3504         String text = GetSelectedText();
3505
3506         // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3507         bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3508         if (bChanged)
3509                 // now replace the text
3510                 ReplaceSelection(text.c_str(), text.length(), 0);
3511 }
3512
3513 /**
3514  * @brief Called when an editor script item is updated
3515  */
3516 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3517 {
3518         // append the scripts submenu
3519         HMENU scriptsSubmenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu->m_hMenu : nullptr;
3520         if (scriptsSubmenu != nullptr)
3521                 createScriptsSubmenu(scriptsSubmenu);
3522
3523         pCmdUI->Enable(true);
3524 }
3525
3526 /**
3527  * @brief Called when an editor script item is updated
3528  */
3529 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3530 {
3531         pCmdUI->Enable(true);
3532
3533         CMergeDoc *pd = GetDocument();
3534         ASSERT(pd != nullptr);
3535         PrediffingInfo prediffer;
3536         pd->GetPrediffer(&prediffer);
3537
3538         if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3539         {
3540                 pCmdUI->SetRadio(false);
3541                 return;
3542         }
3543
3544         // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3545         if (prediffer.m_PluginName.empty())
3546                 m_CurrentPredifferID = ID_NO_PREDIFFER;
3547
3548         pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3549 }
3550
3551 /**
3552  * @brief Update "Prediffer" menuitem
3553  */
3554 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3555 {
3556         // recreate the sub menu (to fill the "selected prediffers")
3557         GetMainFrame()->UpdatePrediffersMenu();
3558         pCmdUI->Enable();
3559 }
3560
3561 void CMergeEditView::OnNoPrediffer()
3562 {
3563         OnPrediffer(ID_NO_PREDIFFER);
3564 }
3565 /**
3566  * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3567  */
3568 void CMergeEditView::OnPrediffer(UINT nID )
3569 {
3570         CMergeDoc *pd = GetDocument();
3571         ASSERT(pd != nullptr);
3572
3573         SetPredifferByMenu(nID);
3574         pd->FlushAndRescan(true);
3575 }
3576
3577 /**
3578  * @brief Handler for all prediffer choices.
3579  * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3580  * ID_NO_PREDIFFER, & specific prediffers.
3581  */
3582 void CMergeEditView::SetPredifferByMenu(UINT nID )
3583 {
3584         CMergeDoc *pd = GetDocument();
3585         ASSERT(pd != nullptr);
3586
3587         if (nID == ID_NO_PREDIFFER)
3588         {
3589                 m_CurrentPredifferID = nID;
3590                 // All flags are set correctly during the construction
3591                 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3592                 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3593                 infoPrediffer->m_PluginName.clear();
3594                 pd->SetPrediffer(infoPrediffer);
3595                 pd->FlushAndRescan(true);
3596                 return;
3597         }
3598
3599         // get the scriptlet files
3600         PluginArray * piScriptArray = 
3601                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3602         PluginArray * piScriptArray2 = 
3603                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3604
3605         // build a PrediffingInfo structure fom the ID
3606         PrediffingInfo prediffer;
3607         prediffer.m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3608
3609         size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3610         if (pluginNumber < piScriptArray->size())
3611         {
3612                 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3613                 prediffer.m_PluginName = plugin->m_name;
3614         }
3615         else
3616         {
3617                 pluginNumber -= piScriptArray->size();
3618                 if (pluginNumber >= piScriptArray2->size())
3619                         return;
3620                 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3621                 prediffer.m_PluginName = plugin->m_name;
3622         }
3623
3624         // update data for the radio button
3625         m_CurrentPredifferID = nID;
3626
3627         // update the prediffer and rescan
3628         pd->SetPrediffer(&prediffer);
3629 }
3630
3631 /**
3632  * @brief Look through available prediffers, and return ID of requested one, if found
3633  */
3634 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3635 {
3636         size_t i;
3637         int ID = ID_PREDIFFERS_FIRST;
3638
3639         // Search file prediffers
3640         PluginArray * piScriptArray = 
3641                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3642         for (i=0; i<piScriptArray->size(); ++i, ++ID)
3643         {
3644                 const PluginInfoPtr & plugin = piScriptArray->at(i);
3645                 if (plugin->m_name == prediffer)
3646                         return ID;
3647         }
3648
3649         // Search buffer prediffers
3650         PluginArray * piScriptArray2 = 
3651                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3652         for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3653         {
3654                 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3655                 if (plugin->m_name == prediffer)
3656                         return ID;
3657         }
3658         return -1;
3659 }
3660
3661
3662 /**
3663  * @brief Look through available prediffers, and return ID of requested one, if found
3664  */
3665 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3666 {
3667         int id = FindPrediffer(prediffer);
3668         if (id<0) return false;
3669         SetPredifferByMenu(id);
3670         return true;
3671 }
3672
3673 /** 
3674  * @brief Goto given line.
3675  * @param [in] nLine Destination linenumber
3676  * @param [in] bRealLine if true linenumber is real line, otherwise
3677  * it is apparent line (including deleted lines)
3678  * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3679  * @param [in] bMoveAnchor if true the anchor is moved to nLine
3680  */
3681 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane, bool bMoveAnchor)
3682 {
3683         CMergeDoc *pDoc = GetDocument();
3684         CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3685         CMergeEditView *pCurrentView = nullptr;
3686         if (pSplitterWnd != nullptr)
3687                 pCurrentView = static_cast<CMergeEditView*>
3688                         (pSplitterWnd->GetActivePane());
3689
3690         int nRealLine = nLine;
3691         int nApparentLine = nLine;
3692
3693         // Compute apparent (shown linenumber) line
3694         if (bRealLine)
3695         {
3696                 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3697                         nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3698
3699                 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3700         }
3701         CPoint ptPos;
3702         ptPos.x = 0;
3703         ptPos.y = nApparentLine;
3704
3705         // Scroll line to center of view
3706         int nScrollLine = GetSubLineIndex(nApparentLine);
3707         nScrollLine -= GetScreenLines() / 2;
3708         if (nScrollLine < 0)
3709                 nScrollLine = 0;
3710
3711         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3712         {
3713                 int nGroup = m_bDetailView ? 0 : m_nThisGroup;
3714                 CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
3715                 pView->ScrollToSubLine(nScrollLine);
3716                 if (ptPos.y < pView->GetLineCount())
3717                 {
3718                         pView->SetCursorPos(ptPos);
3719                         if (bMoveAnchor)
3720                                 pView->SetAnchor(ptPos);
3721                         pView->SetSelection(pView->GetAnchor(), ptPos);
3722                 }
3723                 else
3724                 {
3725                         CPoint ptPos1(0, pView->GetLineCount() - 1);
3726                         pView->SetCursorPos(ptPos1);
3727                         if (bMoveAnchor)
3728                                 pView->SetAnchor(ptPos1);
3729                         pView->SetSelection(pView->GetAnchor(), ptPos1);
3730                 }
3731         }
3732
3733         // If goto target is another view - activate another view.
3734         // This is done for user convenience as user probably wants to
3735         // work with goto target file.
3736         if (m_bDetailView)
3737                 GetDocument()->GetView(0, pane)->SetActivePane();
3738         else if (GetGroupView(pane) != pCurrentView)
3739                 GetGroupView(pane)->SetActivePane();
3740 }
3741
3742 /**
3743  * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3744  * a scroll bar.
3745  */
3746 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3747 {
3748         if (pScrollBar == nullptr)
3749         {
3750                 // Scroll did not come frome a scroll bar
3751                 // Find the appropriate scroll bar
3752                 // and send the message to the splitter window instead
3753                 // The event should eventually come back here but with a valid scrollbar
3754                 // Along the way it will be propagated to other windows that need it
3755                 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3756                 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3757                 pSplitterWnd->SendMessage(WM_HSCROLL,
3758                         MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3759                 return;
3760         }
3761         CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3762 }
3763
3764 /**
3765  * @brief When view is scrolled using scrollbars update location pane.
3766  */
3767 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3768 {
3769         if (pScrollBar == nullptr)
3770         {
3771                 // Scroll did not come frome a scroll bar
3772                 // Find the appropriate scroll bar
3773                 // and send the message to the splitter window instead
3774                 // The event should eventually come back here but with a valid scrollbar
3775                 // Along the way it will be propagated to other windows that need it
3776                 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3777                 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3778                 pSplitterWnd->SendMessage(WM_VSCROLL,
3779                         MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3780                 return;
3781         }
3782         CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3783
3784         if (nSBCode == SB_ENDSCROLL)
3785                 return;
3786
3787         // Note we cannot use nPos because of its 16-bit nature
3788         SCROLLINFO si = {0};
3789         si.cbSize = sizeof (si);
3790         si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3791         VERIFY (GetScrollInfo (SB_VERT, &si));
3792
3793         // Get the current position of scroll   box.
3794         int nCurPos =   si.nPos;
3795         
3796         UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3797 }
3798
3799 /**
3800  * @brief Copy selected lines adding linenumbers.
3801  */
3802 void CMergeEditView::OnEditCopyLineNumbers()
3803 {
3804         CString strText;
3805         CString strLine;
3806         CString strNumLine;
3807
3808         CMergeDoc *pDoc = GetDocument();
3809         auto [ptStart, ptEnd] = GetSelection();
3810
3811         // Get last selected line (having widest linenumber)
3812         int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3813         size_t nNumWidth = strutils::to_str(line + 1).length();
3814         
3815         for (int i = ptStart.y; i <= ptEnd.y; i++)
3816         {
3817                 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3818                         continue;
3819
3820                 // We need to convert to real linenumbers
3821                 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3822
3823                 // Insert spaces to align different width linenumbers (99, 100)
3824                 strLine = GetLineText(i);
3825                 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3826                 
3827                 strText += sSpaces;
3828                 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3829                 strText += strNumLine;
3830         }
3831         PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3832 }
3833
3834 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3835 {
3836         CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3837 }
3838
3839 /**
3840  * @brief Open active file with associated application.
3841  *
3842  * First tries to open file using shell 'Edit' action, since that
3843  * action open scripts etc. to editor instead of running them. If
3844  * edit-action is not registered, 'Open' action is used.
3845  */
3846 void CMergeEditView::OnOpenFile()
3847 {
3848         CMergeDoc * pDoc = GetDocument();
3849         ASSERT(pDoc != nullptr);
3850
3851         String sFileName = pDoc->m_filePaths[m_nThisPane];
3852         if (sFileName.empty())
3853                 return;
3854         HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3855                         0, 0, SW_SHOWNORMAL);
3856         if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3857                 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3858                          0, 0, SW_SHOWNORMAL);
3859         if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3860                 OnOpenFileWith();
3861 }
3862
3863 /**
3864  * @brief Open active file with app selection dialog
3865  */
3866 void CMergeEditView::OnOpenFileWith()
3867 {
3868         CMergeDoc * pDoc = GetDocument();
3869         ASSERT(pDoc != nullptr);
3870
3871         String sFileName = pDoc->m_filePaths[m_nThisPane];
3872         if (sFileName.empty())
3873                 return;
3874
3875         CString sysdir;
3876         if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3877                 return;
3878         sysdir.ReleaseBuffer();
3879         CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3880         ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3881                         sysdir, SW_SHOWNORMAL);
3882 }
3883
3884 /**
3885  * @brief Open active file with external editor
3886  */
3887 void CMergeEditView::OnOpenFileWithEditor()
3888 {
3889         CMergeDoc * pDoc = GetDocument();
3890         ASSERT(pDoc != nullptr);
3891
3892         String sFileName = pDoc->m_filePaths[m_nThisPane];
3893         if (sFileName.empty())
3894                 return;
3895
3896         int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3897         theApp.OpenFileToExternalEditor(sFileName, nRealLine);
3898 }
3899
3900 /**
3901  * @brief Open parent folder of active file
3902  */
3903 void CMergeEditView::OnOpenParentFolder()
3904 {
3905         CMergeDoc * pDoc = GetDocument();
3906         ASSERT(pDoc != nullptr);
3907
3908         String sFileName = pDoc->m_filePaths[m_nThisPane];
3909         if (sFileName.empty())
3910                 return;
3911
3912         theApp.OpenParentFolder(sFileName.c_str());
3913 }
3914
3915 /**
3916  * @brief Force repaint of the location pane.
3917  */
3918 void CMergeEditView::RepaintLocationPane()
3919 {
3920         // Must force recalculation due to caching of data in location pane.
3921         CLocationView *pLocationView = GetDocument()->GetLocationView();
3922         if (pLocationView != nullptr)
3923                 pLocationView->ForceRecalculate();
3924 }
3925
3926 /**
3927  * @brief Enables/disables linediff (different color for diffs)
3928  */
3929 void CMergeEditView::OnViewLineDiffs()
3930 {
3931         bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3932         GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3933
3934         // Call CMergeDoc RefreshOptions() to refresh *both* views
3935         CMergeDoc *pDoc = GetDocument();
3936         pDoc->RefreshOptions();
3937         pDoc->FlushAndRescan(true);
3938 }
3939
3940 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3941 {
3942         pCmdUI->Enable(true);
3943         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3944 }
3945
3946 /**
3947  * @brief Enables/disables line number
3948  */
3949 void CMergeEditView::OnViewLineNumbers()
3950 {
3951         GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3952
3953         // Call CMergeDoc RefreshOptions() to refresh *both* views
3954         CMergeDoc *pDoc = GetDocument();
3955         pDoc->RefreshOptions();
3956 }
3957
3958 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3959 {
3960         pCmdUI->Enable(true);
3961         pCmdUI->SetCheck(GetViewLineNumbers());
3962 }
3963
3964 /**
3965  * @brief Enables/disables word wrap
3966  */
3967 void CMergeEditView::OnViewWordWrap()
3968 {
3969         GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3970
3971         // Call CMergeDoc RefreshOptions() to refresh *both* views
3972         CMergeDoc *pDoc = GetDocument();
3973         pDoc->RefreshOptions();
3974         pDoc->UpdateAllViews(this);
3975
3976         UpdateCaret();
3977 }
3978
3979 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3980 {
3981         pCmdUI->Enable(true);
3982         pCmdUI->SetCheck(m_bWordWrap);
3983 }
3984
3985 void CMergeEditView::OnViewWhitespace() 
3986 {
3987         GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3988
3989         // Call CMergeDoc RefreshOptions() to refresh *both* views
3990         CMergeDoc *pDoc = GetDocument();
3991         pDoc->RefreshOptions();
3992 }
3993
3994 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI) 
3995 {
3996         pCmdUI->SetCheck(GetViewTabs());
3997 }
3998
3999 void CMergeEditView::OnViewEOL() 
4000 {
4001         GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
4002         GetDocument()->RefreshOptions();
4003 }
4004
4005 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI) 
4006 {
4007         pCmdUI->SetCheck(GetViewEols());
4008 }
4009
4010 void CMergeEditView::OnSize(UINT nType, int cx, int cy) 
4011 {
4012         if (!IsInitialized())
4013                 return;
4014
4015         CMergeDoc * pDoc = GetDocument();
4016         if (m_nThisPane < pDoc->m_nBuffers - 1)
4017         {
4018                 // To calculate subline index correctly
4019                 // we have to invalidate line cache in all pane before calling the function related the subline.
4020                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
4021                 {
4022                         CMergeEditView *pView = GetGroupView(nPane);
4023                         if (pView != nullptr)
4024                                 pView->InvalidateScreenRect(false);
4025                 }
4026         }
4027         else
4028         {
4029                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
4030                 {
4031                         CMergeEditView *pView = GetGroupView(nPane);
4032                         if (pView != nullptr)
4033                                 pView->Invalidate();
4034                 }
4035         }
4036         // recalculate m_nTopSubLine
4037         m_nTopSubLine = GetSubLineIndex(m_nTopLine);
4038
4039         UpdateCaret();
4040         
4041         RecalcVertScrollBar (false, false);
4042         RecalcHorzScrollBar (false, false);
4043 }
4044
4045 /**
4046 * @brief allocates GDI resources for printing
4047 * @param pDC [in] points to the printer device context
4048 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4049 */
4050 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
4051 {
4052         GetParentFrame()->PostMessage(WM_TIMER);
4053
4054         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4055         {
4056                 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
4057                 pView->m_bPrintHeader = true;
4058                 pView->m_bPrintFooter = true;
4059                 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
4060         }
4061 }
4062
4063 /**
4064 * @brief frees GDI resources for printing
4065 * @param pDC [in] points to the printer device context
4066 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4067 */
4068 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
4069 {
4070         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4071                 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
4072
4073         GetParentFrame()->PostMessage(WM_TIMER);
4074 }
4075
4076 /**
4077 * @brief Gets header text to print
4078 * @param [in]  nPageNum the page number to print
4079 * @param [out] header text to print
4080 */
4081 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
4082 {
4083         text = GetDocument()->GetTitle();
4084 }
4085
4086 /**
4087 * @brief Prints header
4088 * @param [in] nPageNum the page number to print
4089 */
4090 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
4091 {
4092         if (m_nThisPane > 0)
4093                 return;
4094         int oldRight = m_rcPrintArea.right;
4095         m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4096         CGhostTextView::PrintHeader(pdc, nPageNum);
4097         m_rcPrintArea.right = oldRight;
4098 }
4099
4100 /**
4101 * @brief Prints footer
4102 * @param [in] nPageNum the page number to print
4103 */
4104 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
4105 {
4106         if (m_nThisPane > 0)
4107                 return;
4108         int oldRight = m_rcPrintArea.right;
4109         m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4110         CGhostTextView::PrintFooter(pdc, nPageNum);
4111         m_rcPrintArea.right = oldRight;
4112 }
4113
4114 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
4115 {
4116         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4117                 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
4118 }
4119
4120 /**
4121 * @brief Prints or previews both panes.
4122 * @param pDC [in] points to the printer device context
4123 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4124 */
4125 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo) 
4126 {
4127         CRect rDraw = pInfo->m_rectDraw;
4128         CSize sz = rDraw.Size();
4129         CMergeDoc *pDoc = GetDocument();
4130
4131         SIZE szLeftTop, szRightBottom;
4132         GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
4133         pDC->HIMETRICtoLP(&szLeftTop);
4134         pDC->HIMETRICtoLP(&szRightBottom);
4135         
4136         int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
4137
4138         // print pane
4139         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
4140         {
4141                 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
4142                 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
4143                 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
4144                 pPane->CGhostTextView::OnPrint(pDC, pInfo);
4145         }
4146 }
4147
4148 bool CMergeEditView::IsInitialized() const
4149 {
4150         CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
4151         CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
4152         return pBuffer->IsInitialized();
4153 }
4154
4155 /**
4156  * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
4157  */
4158 int CMergeEditView::GetEmptySubLines( int nLineIndex )
4159 {
4160         int     nBreaks[3] = {0};
4161         int nMaxBreaks = -1;
4162         CMergeDoc * pDoc = GetDocument();
4163         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
4164         {
4165                 CMergeEditView *pView = GetGroupView(nPane);
4166                 if (pView != nullptr)
4167                 {
4168                         if (nLineIndex >= pView->GetLineCount())
4169                                 return 0;
4170                         pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
4171                 }
4172                 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
4173         }
4174
4175         if (nBreaks[m_nThisPane] < nMaxBreaks)
4176                 return nMaxBreaks - nBreaks[m_nThisPane];
4177         else
4178                 return 0;
4179 }
4180
4181 /**
4182  * @brief Invalidate sub line index cache from the specified index to the end of file.
4183  * @param [in] nLineIndex Index of the first line to invalidate 
4184  */
4185 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
4186 {
4187         CMergeDoc * pDoc = GetDocument();
4188         ASSERT(pDoc != nullptr);
4189
4190     // We have to invalidate sub line index cache on both panes.
4191         for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
4192         {
4193                 CMergeEditView *pView = GetGroupView(nPane);
4194                 if (pView != nullptr)
4195                         pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
4196         }
4197 }
4198
4199 void CMergeEditView::SetWordWrapping( bool bWordWrap )
4200 {
4201         for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4202                 GetGroupView(pane)->m_bWordWrap = bWordWrap;
4203         CCrystalTextView::SetWordWrapping(bWordWrap);
4204 }
4205
4206 /**
4207  * @brief Swap the positions of the two panes
4208  */
4209 void CMergeEditView::OnViewSwapPanes12()
4210 {
4211         GetDocument()->SwapFiles(0, 1);
4212 }
4213
4214 /**
4215  * @brief Swap the positions of the two panes
4216  */
4217 void CMergeEditView::OnViewSwapPanes23()
4218 {
4219         GetDocument()->SwapFiles(1, 2);
4220 }
4221
4222 /**
4223  * @brief Swap the positions of the two panes
4224  */
4225 void CMergeEditView::OnViewSwapPanes13()
4226 {
4227         GetDocument()->SwapFiles(0, 2);
4228 }
4229
4230 /**
4231 * @brief Determine if difference is visible on screen.
4232 * @param [in] nDiff Number of diff to check.
4233 * @return true if difference is visible.
4234 */
4235 bool CMergeEditView::IsDiffVisible(int nDiff)
4236 {
4237         const CMergeDoc *pd = GetDocument();
4238
4239         DIFFRANGE diff;
4240         pd->m_diffList.GetDiff(nDiff, diff);
4241
4242         return IsDiffVisible(diff);
4243 }
4244
4245 /**
4246  * @brief Determine if difference is visible on screen.
4247  * @param [in] diff diff to check.
4248  * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
4249  * @return true if difference is visible, false otherwise.
4250  */
4251 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
4252 {
4253         const int nDiffStart = GetSubLineIndex(diff.dbegin);
4254         const int nDiffEnd = GetSubLineIndex(diff.dend);
4255         // Diff's height is last line - first line + last line's line count
4256         const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
4257
4258         // If diff first line outside current view - context OR
4259         // if diff last line outside current view - context OR
4260         // if diff is bigger than screen
4261         if ((nDiffStart < m_nTopSubLine) ||
4262                 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
4263                 (nDiffHeight >= GetScreenLines()))
4264         {
4265                 return false;
4266         }
4267         else
4268         {
4269                 return true;
4270         }
4271 }
4272
4273 /** @brief Open help from mainframe when user presses F1*/
4274 void CMergeEditView::OnHelp()
4275 {
4276         theApp.ShowHelp(MergeViewHelpLocation);
4277 }
4278
4279 /**
4280  * @brief Called after document is loaded.
4281  * This function is called from CMergeDoc::OpenDocs() after documents are
4282  * loaded. So this is good place to set View's options etc.
4283  */
4284 void CMergeEditView::DocumentsLoaded()
4285 {
4286         if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
4287         {
4288                 SetTopMargin(true);
4289                 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
4290                         AutoFitColumn();
4291         }
4292         else
4293         {
4294                 SetTopMargin(false);
4295         }
4296
4297         // Enable/disable automatic rescan (rescanning after edit)
4298         EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
4299
4300         // SetTextType will revert to language dependent defaults for tab
4301         SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
4302         SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
4303         const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
4304                 GetDocument()->IsMixedEOL(m_nThisPane);
4305         SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
4306         SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
4307         SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
4308         SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4309
4310         // Enable Backspace at beginning of line
4311         SetDisableBSAtSOL(false);
4312
4313         // Set tab type (tabs/spaces)
4314         bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
4315         SetInsertTabs(bInsertTabs);
4316
4317         // Sometimes WinMerge doesn't update scrollbars correctly (they remain
4318         // disabled) after docs are open in screen. So lets make sure they are
4319         // really updated, even though this is unnecessary in most cases.
4320         RecalcHorzScrollBar();
4321         RecalcVertScrollBar();
4322 }
4323
4324 /**
4325  * @brief Update LocationView position.
4326  * This function updates LocationView position to given lines.
4327  * Usually we want to lines in file compare view and area in
4328  * LocationView to match. Be extra carefull to not call non-existing
4329  * LocationView.
4330  * @param [in] nTopLine Top line of current view.
4331  * @param [in] nBottomLine Bottom line of current view.
4332  */
4333 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
4334                 int nBottomLine /*= -1*/)
4335 {
4336         CMergeDoc *pDoc = GetDocument();
4337         if (pDoc == nullptr)
4338                 return;
4339
4340         CLocationView *pLocationView = pDoc->GetLocationView();
4341
4342         if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
4343         {
4344                 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
4345         }
4346 }
4347
4348 /**
4349  * @brief Enable/Disable view's selection margins.
4350  * Selection margins show bookmarks and word-wrap symbols, so they are pretty
4351  * useful. But it appears many users don't use/need those features and for them
4352  * selection margins are just wasted screen estate.
4353  */
4354 void CMergeEditView::OnViewMargin()
4355 {
4356         bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
4357         GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
4358
4359         SetSelectionMargin(!bViewMargin);
4360         CMergeDoc *pDoc = GetDocument();
4361         pDoc->RefreshOptions();
4362         pDoc->UpdateAllViews(this);
4363 }
4364
4365 /**
4366  * @brief Update GUI for Enable/Disable view's selection margin.
4367  * @param [in] pCmdUI Pointer to UI item to update.
4368  */
4369 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
4370 {
4371         pCmdUI->Enable(true);
4372         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4373 }
4374
4375 /**
4376 * @brief Create the "Change Scheme" sub menu.
4377 * @param [in] pCmdUI Pointer to UI item to update.
4378 */
4379 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4380 {
4381         // Delete the place holder menu.
4382         pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4383
4384         const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4385
4386         String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4387         AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4388         AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
4389
4390         for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
4391         {
4392                 name = theApp.LoadString(i);
4393                 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
4394         }
4395
4396         pCmdUI->Enable(true);
4397 }
4398
4399 /**
4400 * @brief Change the editor's syntax highlighting scheme.
4401 * @param [in] nID Selected color scheme sub menu id.
4402 */
4403 void CMergeEditView::OnChangeScheme(UINT nID)
4404 {
4405         CMergeDoc *pDoc = GetDocument();
4406         ASSERT(pDoc != nullptr);
4407
4408         for (int nGroup = 0; nGroup < pDoc->m_nGroups; nGroup++)
4409                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
4410                 {
4411                         CMergeEditView *pView = pDoc->GetView(nGroup, nPane);
4412                         ASSERT(pView != nullptr);
4413
4414                         if (pView != nullptr)
4415                         {
4416                                 pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
4417                                 pView->SetDisableBSAtSOL(false);
4418                                 pView->m_bChangedSchemeManually = true;
4419                         }
4420                 }
4421
4422         OnRefresh();
4423 }
4424
4425 /**
4426 * @brief Enable all color schemes sub menu items.
4427 * @param [in] pCmdUI Pointer to UI item to update.
4428 */
4429 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4430 {
4431         const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4432         pCmdUI->SetRadio(bIsCurrentScheme);
4433         pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4434 }
4435
4436 /**
4437  * @brief Called when mouse's wheel is scrolled.
4438  */
4439 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4440 {
4441         if ( nFlags == MK_CONTROL )
4442         {
4443                 short amount = zDelta < 0 ? -1: 1;
4444                 ZoomText(amount);
4445
4446                 // no default CCrystalTextView
4447                 return CView::OnMouseWheel(nFlags, zDelta, pt);
4448         }
4449
4450         if (nFlags == MK_SHIFT)
4451         {
4452                 SCROLLINFO si = { sizeof SCROLLINFO };
4453                 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4454
4455                 VERIFY(GetScrollInfo(SB_HORZ, &si));
4456
4457                 // new horz pos
4458                 si.nPos -= zDelta / 40;
4459                 if (si.nPos > si.nMax) si.nPos = si.nMax;
4460                 if (si.nPos < si.nMin) si.nPos = si.nMin;
4461
4462                 SetScrollInfo(SB_HORZ, &si);
4463
4464                 // for update
4465                 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4466
4467                 // no default CCrystalTextView
4468                 return CView::OnMouseWheel(nFlags, zDelta, pt);
4469         }
4470
4471         return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4472 }
4473
4474 /**
4475  * @brief Called when mouse's horizontal wheel is scrolled.
4476  */
4477 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4478 {
4479         SCROLLINFO si = { sizeof SCROLLINFO };
4480         si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4481
4482         VERIFY(GetScrollInfo(SB_HORZ, &si));
4483
4484         // new horz pos
4485         si.nPos += zDelta / 40;
4486         if (si.nPos > si.nMax) si.nPos = si.nMax;
4487         if (si.nPos < si.nMin) si.nPos = si.nMin;
4488
4489         SetScrollInfo(SB_HORZ, &si);
4490
4491         // for update
4492         SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4493
4494         // no default CCrystalTextView
4495         CView::OnMouseHWheel(nFlags, zDelta, pt);
4496 }
4497
4498 /**
4499  * @brief Change font size (zoom) in views.
4500  * @param [in] amount Amount of change/zoom, negative number makes
4501  *  font smaller, positive number bigger and 0 reset the font size.
4502  */
4503 void CMergeEditView::ZoomText(short amount)
4504 {
4505         LOGFONT lf = { 0 };
4506         GetFont(lf);
4507
4508         const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4509         int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4510
4511         if ( amount == 0)
4512         {
4513                 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4514         }
4515
4516         nPointSize += amount;
4517         if (nPointSize < 2)
4518                 nPointSize = 2;
4519
4520         lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4521
4522         CMergeDoc *pDoc = GetDocument();
4523         ASSERT(pDoc != nullptr);
4524
4525         if (pDoc != nullptr)
4526         {
4527                 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++) 
4528                 {
4529                         CMergeEditView *pView = GetGroupView(nPane);
4530                         ASSERT(pView != nullptr);
4531                         
4532                         if (pView != nullptr)
4533                         {
4534                                 pView->SetFont(lf);
4535                         }
4536                 }
4537         }
4538 }
4539
4540 bool CMergeEditView::QueryEditable()
4541 {
4542         return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4543 }
4544
4545 /**
4546  * @brief Adjust the point to remain in the displayed diff
4547  *
4548  * @return Tells if the point has been changed
4549  */
4550 bool CMergeEditView::EnsureInDiff(CPoint& pt)
4551 {
4552         int nLineCount = GetLineCount();
4553         if (m_lineBegin >= nLineCount)
4554                 m_lineBegin = nLineCount - 1;
4555         if (m_lineEnd >= nLineCount)
4556                 m_lineEnd = nLineCount - 1;
4557
4558         int diffLength = m_lineEnd - m_lineBegin + 1;
4559         // first get the degenerate case out of the way
4560         // no diff ?
4561         if (diffLength == 0)
4562         {
4563                 if (pt.y == m_lineBegin && pt.x == 0)
4564                         return false;
4565                 pt.y = m_lineBegin;
4566                 pt.x = 0;
4567                 return true;
4568         }
4569
4570         // not above diff
4571         if (pt.y < m_lineBegin)
4572         {
4573                 pt.y = m_lineBegin;
4574                 pt.x = 0;
4575                 return true;
4576         }
4577         // diff is defined and not below diff
4578         if (m_lineEnd > -1 && pt.y > m_lineEnd)
4579         {
4580                 pt.y = m_lineEnd;
4581                 pt.x = GetLineLength(pt.y);
4582                 return true;
4583         }
4584         return false;
4585 }
4586
4587 void CMergeEditView::EnsureVisible(CPoint pt)
4588 {
4589         CPoint ptNew = pt;
4590         if (m_bDetailView)
4591         {
4592                 // ensure we remain in diff
4593                 if (EnsureInDiff(ptNew))
4594                         SetCursorPos(ptNew);
4595         }
4596         CCrystalTextView::EnsureVisible(ptNew);
4597 }
4598
4599 void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
4600 {
4601         CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4602 }
4603
4604 void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
4605 {
4606         CPoint ptStartNew = ptStart;
4607         CPoint ptEndNew = ptEnd;
4608         if (m_bDetailView)
4609         {
4610                 // ensure we remain in diff
4611                 EnsureInDiff(ptStartNew);
4612                 EnsureInDiff(ptEndNew);
4613         }
4614         CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4615 }
4616
4617 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4618 {
4619         if (m_bDetailView)
4620         {
4621                 int nLineCount = GetLineCount();
4622                 if (m_lineBegin >= nLineCount)
4623                         m_lineBegin = nLineCount - 1;
4624                 if (m_lineEnd >= nLineCount)
4625                         m_lineEnd = nLineCount - 1;
4626
4627                 // ensure we remain in diff
4628                 int sublineBegin = GetSubLineIndex(m_lineBegin);
4629                 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4630                 int diffLength = sublineEnd - sublineBegin + 1;
4631                 int displayLength = GetScreenLines();
4632                 if (diffLength <= displayLength)
4633                         nNewTopLine = sublineBegin;
4634                 else
4635                 {
4636                         if (nNewTopLine < sublineBegin)
4637                                 nNewTopLine = sublineBegin;
4638                         if (nNewTopLine + displayLength - 1 > sublineEnd)
4639                                 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4640                 }
4641
4642                 CPoint pt = GetCursorPos();
4643                 if (EnsureInDiff(pt))
4644                         SetCursorPos(pt);
4645
4646                 auto [ptSelStart, ptSelEnd] = GetSelection();
4647                 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4648                         SetSelection(ptSelStart, ptSelEnd);
4649         }
4650         CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4651 }
4652
4653 void CMergeEditView::SetActivePane()
4654 {
4655         auto* pwndSplitterChild = GetParentSplitter(this, false);
4656         if (!pwndSplitterChild)
4657                 return;
4658         if (pwndSplitterChild->GetColumnCount() > 1)
4659                 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4660         else
4661                 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4662 }
4663
4664 /**
4665  * @brief Called when user selects View/Zoom In from menu.
4666  */
4667 void CMergeEditView::OnViewZoomIn()
4668 {
4669         ZoomText(1);
4670 }
4671
4672 /**
4673  * @brief Called when user selects View/Zoom Out from menu.
4674  */
4675 void CMergeEditView::OnViewZoomOut()
4676 {
4677         ZoomText(-1);
4678 }
4679
4680 /**
4681  * @brief Called when user selects View/Zoom Normal from menu.
4682  */
4683 void CMergeEditView::OnViewZoomNormal()
4684 {
4685         ZoomText(0);
4686 }
4687
4688 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4689 {
4690         if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4691         {
4692                 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4693                 return;
4694         }
4695
4696         GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4697 }
4698
4699 void CMergeEditView::OnWindowSplit()
4700 {
4701
4702         auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4703         CMergeDoc *pDoc = GetDocument();
4704         CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4705         auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4706         int nBuffer = m_nThisPane;
4707         if (pDoc->m_nGroups <= 2)
4708         {
4709                 wndSplitter.SplitRow(1);
4710                 wndSplitter.EqualizeRows();
4711         }
4712         else
4713         {
4714                 wndSplitter.SetActivePane(0, 0);
4715                 wndSplitter.DeleteRow(1);
4716                 pDoc->GetView(0, nBuffer)->SetActivePane();
4717         }
4718 }
4719
4720 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4721 {
4722         pCmdUI->Enable(!m_bDetailView);
4723         pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4724 }
4725
4726 void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
4727 {
4728         *pResult = 0;
4729         LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4730         const int pane = pNMItemActivate->iItem / 4;
4731         CMergeDoc* pDoc = GetDocument();
4732         if (pane >= pDoc->m_nBuffers || !GetParentFrame()->IsChild(CWnd::FromHandle(pNMItemActivate->hdr.hwndFrom)))
4733                 return;
4734
4735         switch (pNMItemActivate->iItem % 4)
4736         {
4737         case 0:
4738                 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4739                 break;
4740         case 1:
4741                 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
4742                 break;
4743         case 2:
4744         {
4745                 CPoint point;
4746                 ::GetCursorPos(&point);
4747
4748                 BCMenu menu;
4749                 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4750                 theApp.TranslateMenu(menu.m_hMenu);
4751                 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4752                 break;
4753         }
4754         case 3:
4755                 pDoc->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());
4756                 break;
4757         default:
4758                 break;
4759         }
4760 }
4761