OSDN Git Service

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