OSDN Git Service

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