OSDN Git Service

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