OSDN Git Service

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