OSDN Git Service

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