OSDN Git Service

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