OSDN Git Service

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