OSDN Git Service

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