OSDN Git Service

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