1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
8 * @file MergeEditView.cpp
10 * @brief Implementation of the CMergeEditView class
14 #include "MergeEditView.h"
18 #include "LocationView.h"
21 #include "OptionsMgr.h"
22 #include "OptionsDiffColors.h"
23 #include "FileTransform.h"
25 #include "WMGotoDlg.h"
26 #include "OptionsDef.h"
27 #include "SyntaxColors.h"
28 #include "MergeEditFrm.h"
29 #include "MergeLineFlags.h"
31 #include "DropHandler.h"
33 #include "ShellContextMenu.h"
40 #ifndef WM_MOUSEHWHEEL
41 # define WM_MOUSEHWHEEL 0x20e
45 using CrystalLineParser::TEXTBLOCK;
47 /** @brief Timer ID for delayed rescan. */
48 const UINT IDT_RESCAN = 2;
49 /** @brief Timer timeout for delayed rescan. */
50 const UINT RESCAN_TIMEOUT = 1000;
52 /** @brief Location for file compare specific help to open. */
53 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
55 /////////////////////////////////////////////////////////////////////////////
58 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
60 CMergeEditView::CMergeEditView()
61 : m_bCurrentLineIsDiff(false)
64 , m_bDetailView(false)
65 , m_piMergeEditStatus(nullptr)
66 , m_bAutomaticRescan(false)
67 , fTimerWaitingForIdle(0)
70 , m_CurrentPredifferID(0)
72 SetParser(&m_xParser);
74 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
77 CMergeEditView::~CMergeEditView()
82 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
83 //{{AFX_MSG_MAP(CMergeEditView)
84 ON_COMMAND(ID_CURDIFF, OnCurdiff)
85 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
86 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
87 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
88 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
89 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
90 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
91 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
92 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
93 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
94 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
95 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
96 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
97 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
98 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
99 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
100 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
101 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
102 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
103 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
104 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
105 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
106 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
107 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
108 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
109 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
110 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
111 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
112 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
113 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
114 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
115 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
116 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
117 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
118 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
119 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
120 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
121 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
122 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
123 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
124 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
125 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
126 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
127 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
128 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
129 ON_WM_LBUTTONDBLCLK()
132 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
133 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
134 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
135 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
136 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
137 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
138 ON_COMMAND(ID_L2R, OnL2r)
139 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
140 ON_COMMAND(ID_R2L, OnR2l)
141 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
142 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
143 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
144 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
145 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
146 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
147 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
148 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
149 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
150 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
151 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
153 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
154 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
155 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
156 ON_COMMAND(ID_REFRESH, OnRefresh)
157 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
158 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
159 ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
160 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
161 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
163 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
164 ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
165 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
166 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
167 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
168 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
169 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
170 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
171 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
172 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
173 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
174 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
175 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
176 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
177 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
178 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
179 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
180 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
181 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
182 ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
183 ON_COMMAND(ID_NEXT_PANE, OnChangePane)
184 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
185 ON_COMMAND(ID_GOTO_MOVED_LINE_LM, OnGotoMovedLineLM)
186 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_LM, OnUpdateGotoMovedLineLM)
187 ON_COMMAND(ID_GOTO_MOVED_LINE_MR, OnGotoMovedLineMR)
188 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_MR, OnUpdateGotoMovedLineMR)
189 ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
190 ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
191 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
192 ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
193 ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
194 ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
195 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
198 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
199 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
200 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
201 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
202 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
203 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
204 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
205 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
206 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
207 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
208 ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
209 ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
210 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
211 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
212 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
213 ON_COMMAND(ID_VIEW_SWAPPANES, OnViewSwapPanes)
214 ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
217 ON_COMMAND(ID_HELP, OnHelp)
218 ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
219 ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
220 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
221 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
222 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
225 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
226 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
227 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
228 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
229 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
230 ON_NOTIFY(NM_DBLCLK, AFX_IDW_STATUS_BAR, OnStatusBarDblClick)
235 /////////////////////////////////////////////////////////////////////////////
236 // CMergeEditView diagnostics
239 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
241 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
242 return (CMergeDoc*)m_pDocument;
247 /////////////////////////////////////////////////////////////////////////////
248 // CMergeEditView message handlers
251 * @brief Return text buffer for file in view
253 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
255 return GetDocument()->m_ptBuf[m_nThisPane].get();
259 * @brief Update any resources necessary after a GUI language change
261 void CMergeEditView::UpdateResources()
265 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
267 return GetDocument()->GetView(m_nThisGroup, nBuffer);
270 void CMergeEditView::PrimeListWithFile()
272 // Set the tab size now, just in case the options change...
273 // We don't update it at the end of OnOptions,
274 // we can update it safely now
275 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
278 * @brief Return text from line given
280 CString CMergeEditView::GetLineText(int idx)
282 return GetLineChars(idx);
286 * @brief Return text from selection
288 CString CMergeEditView::GetSelectedText()
290 CPoint ptStart, ptEnd;
292 GetSelection(ptStart, ptEnd);
293 if (ptStart != ptEnd)
294 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
299 * @brief Get diffs inside selection.
300 * @param [out] firstDiff First diff inside selection
301 * @param [out] lastDiff Last diff inside selection
302 * @note -1 is returned in parameters if diffs cannot be determined
303 * @todo This shouldn't be called when there is no diffs, so replace
304 * first 'if' with ASSERT()?
306 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
311 CMergeDoc *pd = GetDocument();
312 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
316 int firstLine, lastLine;
317 GetFullySelectedLines(firstLine, lastLine);
318 if (lastLine < firstLine)
321 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
322 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
323 if (firstDiff != -1 && lastDiff != -1)
327 // Check that first selected line is first diff's first line or above it
328 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
329 if ((int)di.dbegin < firstLine)
331 if (firstDiff < lastDiff)
335 // Check that last selected line is last diff's last line or below it
336 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
337 if ((int)di.dend > lastLine)
339 if (firstDiff < lastDiff)
343 // Special case: one-line diff is not selected if cursor is in it
344 if (firstLine == lastLine)
353 * @brief Get diffs inside selection.
354 * @param [out] firstDiff First diff inside selection
355 * @param [out] lastDiff Last diff inside selection
356 * @param [out] firstWordDiff First word level diff inside selection
357 * @param [out] lastWordDiff Last word level diff inside selection
358 * @note -1 is returned in parameters if diffs cannot be determined
359 * @todo This shouldn't be called when there is no diffs, so replace
360 * first 'if' with ASSERT()?
362 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
369 CMergeDoc *pd = GetDocument();
370 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
374 int firstLine, lastLine;
375 CPoint ptStart, ptEnd;
376 GetSelection(ptStart, ptEnd);
377 if (pptStart != nullptr)
379 if (pptEnd != nullptr)
381 firstLine = ptStart.y;
384 firstDiff = pd->m_diffList.LineToDiff(firstLine);
385 bool firstLineIsNotInDiff = firstDiff == -1;
388 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
393 lastDiff = pd->m_diffList.LineToDiff(lastLine);
394 bool lastLineIsNotInDiff = lastDiff == -1;
396 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
397 if (lastDiff < firstDiff)
404 if (firstDiff != -1 && lastDiff != -1)
408 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
410 firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
412 else if (ptStart != ptEnd)
414 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
415 if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
421 if (firstWordDiff == -1)
423 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
424 for (size_t i = 0; i < worddiffs.size(); ++i)
426 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
427 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
428 (firstLine == worddiffs[i].endline[m_nThisPane] &&
429 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
431 firstWordDiff = static_cast<int>(i);
436 if (firstLine >= di.dbegin && firstWordDiff == -1)
443 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
444 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
445 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
447 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
448 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
450 lastWordDiff = static_cast<int>(i);
455 if (lastLine <= di.dend && lastWordDiff == -1)
458 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
465 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
482 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
483 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
484 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
487 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
489 CMergeDoc *pDoc = GetDocument();
490 std::map<int, std::vector<int>> ret;
491 std::map<int, std::vector<int> *> list;
492 CPoint ptStart, ptEnd;
493 GetSelection(ptStart, ptEnd);
494 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
496 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
498 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
500 GetColumnSelection(nLine, nLeft, nRight);
501 CPoint ptStart2, ptEnd2;
504 ptStart2.y = ptEnd2.y = nLine;
505 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
506 if (firstDiff != -1 && lastDiff != -1)
508 std::vector<int> *pWordDiffs;
509 if (list.find(firstDiff) == list.end())
510 list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
511 pWordDiffs = list[firstDiff];
512 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
514 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
515 pWordDiffs->push_back(i);
520 for (auto& it : list)
521 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
525 void CMergeEditView::OnInitialUpdate()
528 CCrystalEditViewEx::OnInitialUpdate();
530 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
531 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
537 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
539 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
541 CMergeDoc* pDoc = GetDocument();
542 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
545 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
549 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
550 return std::vector<CrystalLineParser::TEXTBLOCK>();
552 return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
555 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
557 static const std::vector<TEXTBLOCK> emptyBlocks;
560 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
564 DWORD dwLineFlags = GetLineFlags(nLineIndex);
565 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
568 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
571 CMergeDoc *pDoc = GetDocument();
572 if (pDoc->IsEditedAfterRescan(m_nThisPane))
575 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
580 pDoc->m_diffList.GetDiff(nDiff, cd);
581 int unemptyLineCount = 0;
582 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
584 if (cd.begin[nPane] != cd.end[nPane] + 1)
587 if (unemptyLineCount < 2)
590 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
591 size_t nWordDiffs = worddiffs.size();
593 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
595 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
596 blocks[0].m_nCharPos = 0;
597 blocks[0].m_nColorIndex = COLORINDEX_NONE;
598 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
600 for (i = 0, j = 1; i < nWordDiffs; i++)
602 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
604 if (pDoc->m_nBuffers > 2)
606 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
608 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
611 int begin[3], end[3];
612 bool deleted = false;
613 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
615 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
616 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
617 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
618 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
621 blocks[j].m_nCharPos = begin[m_nThisPane];
622 if (lineInCurrentDiff)
624 if (m_cachedColors.clrSelDiffText != CLR_NONE)
625 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
627 blocks[j].m_nColorIndex = COLORINDEX_NONE;
628 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
629 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
633 if (m_cachedColors.clrDiffText != CLR_NONE)
634 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
636 blocks[j].m_nColorIndex = COLORINDEX_NONE;
637 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
638 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
641 blocks[j].m_nCharPos = end[m_nThisPane];
642 blocks[j].m_nColorIndex = COLORINDEX_NONE;
643 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
652 COLORREF CMergeEditView::GetColor(int nColorIndex)
654 switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
656 case COLORINDEX_HIGHLIGHTBKGND1:
657 return m_cachedColors.clrSelWordDiff;
658 case COLORINDEX_HIGHLIGHTTEXT1:
659 return m_cachedColors.clrSelWordDiffText;
660 case COLORINDEX_HIGHLIGHTBKGND2:
661 return m_cachedColors.clrWordDiff;
662 case COLORINDEX_HIGHLIGHTTEXT2:
663 return m_cachedColors.clrWordDiffText;
664 case COLORINDEX_HIGHLIGHTBKGND3:
665 return m_cachedColors.clrWordDiffDeleted;
666 case COLORINDEX_HIGHLIGHTBKGND4:
667 return m_cachedColors.clrSelWordDiffDeleted;
670 return CCrystalTextView::GetColor(nColorIndex);
675 * @brief Determine text and background color for line
676 * @param [in] nLineIndex Index of line in view (NOT line in file)
677 * @param [out] crBkgnd Backround color for line
678 * @param [out] crText Text color for line
680 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
681 COLORREF & crText, bool & bDrawWhitespace)
683 DWORD ignoreFlags = 0;
684 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
688 * @brief Determine text and background color for line
689 * @param [in] nLineIndex Index of line in view (NOT line in file)
690 * @param [in] ignoreFlags Flags that caller wishes ignored
691 * @param [out] crBkgnd Backround color for line
692 * @param [out] crText Text color for line
694 * This version allows caller to suppress particular flags
696 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
697 COLORREF & crText, bool & bDrawWhitespace)
699 if (GetLineCount() <= nLineIndex)
702 DWORD dwLineFlags = GetLineFlags(nLineIndex);
704 if (dwLineFlags & ignoreFlags)
705 dwLineFlags &= (~ignoreFlags);
709 // Line with WinMerge flag,
710 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
711 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
713 crText = m_cachedColors.clrDiffText;
714 bDrawWhitespace = true;
716 if (dwLineFlags & LF_GHOST)
718 crBkgnd = m_cachedColors.clrDiffDeleted;
723 // If no syntax hilighting
724 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
726 crBkgnd = GetColor (COLORINDEX_BKGND);
727 crText = GetColor (COLORINDEX_NORMALTEXT);
728 bDrawWhitespace = false;
731 // Line not inside diff, get colors from CrystalEditor
732 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
733 crText, bDrawWhitespace);
735 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
737 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
738 crText = GetColor (COLORINDEX_WHITESPACE);
739 bDrawWhitespace = false;
745 if (dwLineFlags & LF_WINMERGE_FLAGS)
747 crText = m_cachedColors.clrDiffText;
748 bDrawWhitespace = true;
749 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
751 if (dwLineFlags & LF_SNP)
753 if (lineInCurrentDiff)
755 if (dwLineFlags & LF_GHOST)
756 crBkgnd = m_cachedColors.clrSelSNPDeleted;
758 crBkgnd = m_cachedColors.clrSelSNP;
759 crText = m_cachedColors.clrSelSNPText;
763 if (dwLineFlags & LF_GHOST)
764 crBkgnd = m_cachedColors.clrSNPDeleted;
766 crBkgnd = m_cachedColors.clrSNP;
767 crText = m_cachedColors.clrSNPText;
771 else if (dwLineFlags & LF_DIFF)
773 if (lineInCurrentDiff)
775 if (dwLineFlags & LF_MOVED)
777 crBkgnd = m_cachedColors.clrSelMoved;
778 crText = m_cachedColors.clrSelMovedText;
782 crBkgnd = m_cachedColors.clrSelDiff;
783 crText = m_cachedColors.clrSelDiffText;
789 if (dwLineFlags & LF_MOVED)
791 crBkgnd = m_cachedColors.clrMoved;
792 crText = m_cachedColors.clrMovedText;
796 crBkgnd = m_cachedColors.clrDiff;
797 crText = m_cachedColors.clrDiffText;
802 else if (dwLineFlags & LF_TRIVIAL)
804 // trivial diff can not be selected
805 if (dwLineFlags & LF_GHOST)
806 // ghost lines in trivial diff has their own color
807 crBkgnd = m_cachedColors.clrTrivialDeleted;
809 crBkgnd = m_cachedColors.clrTrivial;
810 crText = m_cachedColors.clrTrivialText;
813 else if (dwLineFlags & LF_GHOST)
815 if (lineInCurrentDiff)
817 if (dwLineFlags & LF_MOVED)
818 crBkgnd = m_cachedColors.clrSelMovedDeleted;
820 crBkgnd = m_cachedColors.clrSelDiffDeleted;
824 if (dwLineFlags & LF_MOVED)
825 crBkgnd = m_cachedColors.clrMovedDeleted;
827 crBkgnd = m_cachedColors.clrDiffDeleted;
834 // Line not inside diff,
835 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
837 // If no syntax hilighting, get windows default colors
838 crBkgnd = GetColor (COLORINDEX_BKGND);
839 crText = GetColor (COLORINDEX_NORMALTEXT);
840 bDrawWhitespace = false;
843 // Syntax highlighting, get colors from CrystalEditor
844 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
845 crText, bDrawWhitespace);
850 * @brief Sync other pane position
852 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
854 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
855 if (pSplitterWnd != nullptr)
857 // See CSplitterWnd::IdFromRowCol() implementation for details
858 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
859 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
860 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
861 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
863 // limit the TopLine : must be smaller than GetLineCount for all the panels
864 int newTopSubLine = m_nTopSubLine;
865 int nRows = pSplitterWnd->GetRowCount ();
866 int nCols = pSplitterWnd->GetColumnCount ();
868 // for (nRow = 0; nRow < nRows; nRow++)
870 // for (int nCol = 0; nCol < nCols; nCol++)
872 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
873 // if (pSiblingView != nullptr)
874 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
875 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
878 if (m_nTopSubLine != newTopSubLine)
879 ScrollToSubLine(newTopSubLine);
881 for (nRow = 0; nRow < nRows; nRow++)
883 for (int nCol = 0; nCol < nCols; nCol++)
885 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
887 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
888 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
889 pSiblingView->OnUpdateSibling (this, bHorz);
897 * @brief Update other panes
899 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
901 if (pUpdateSource != this)
903 ASSERT (pUpdateSource != nullptr);
904 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
905 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
906 if (!bHorz) // changed this so bHorz works right
908 ASSERT (pSrcView->m_nTopSubLine >= 0);
910 // This ASSERT is wrong: panes have different files and
911 // different linecounts
912 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
913 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
915 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
917 RecalcVertScrollBar(true);
918 RecalcHorzScrollBar();
923 ASSERT (pSrcView->m_nOffsetChar >= 0);
925 // This ASSERT is wrong: panes have different files and
926 // different linelengths
927 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
928 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
930 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
932 RecalcHorzScrollBar(true);
933 RecalcHorzScrollBar();
939 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
941 int newlineBegin, newlineEnd;
942 CMergeDoc *pd = GetDocument();
943 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
951 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
953 newlineBegin = curDiff.dbegin;
954 ASSERT (newlineBegin >= 0);
955 newlineEnd = curDiff.dend;
958 m_lineBegin = newlineBegin;
959 m_lineEnd = newlineEnd;
961 int nLineCount = GetLineCount();
962 if (m_lineBegin > nLineCount)
963 m_lineBegin = nLineCount - 1;
964 if (m_lineEnd > nLineCount)
965 m_lineEnd = nLineCount - 1;
967 if (m_nTopLine == newlineBegin)
970 // scroll to the first line of the diff
971 vector<WordDiff> worddiffs;
972 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
973 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
974 CPoint pt = worddiffs.size() > 0 ?
975 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
976 CPoint{ 0, m_lineBegin };
979 // update the width of the horizontal scrollbar
980 RecalcHorzScrollBar();
984 * @brief Selects diff by number and syncs other file
985 * @param [in] nDiff Diff to select, must be >= 0
986 * @param [in] bScroll Scroll diff to view
987 * @param [in] bSelectText Select diff text
988 * @sa CMergeEditView::ShowDiff()
989 * @sa CMergeDoc::SetCurrentDiff()
990 * @todo Parameter bSelectText is never used?
992 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
994 CMergeDoc *pd = GetDocument();
996 // Check that nDiff is valid
998 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
999 if (nDiff >= pd->m_diffList.GetSize())
1000 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
1001 nDiff, pd->m_diffList.GetSize());
1004 pd->SetCurrentDiff(nDiff);
1005 ShowDiff(bScroll, bSelectText);
1006 pd->UpdateAllViews(this);
1007 UpdateSiblingScrollPos(false);
1009 // notify either side, as it will notify the other one
1010 pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1013 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1015 CMergeDoc *pd = GetDocument();
1016 // If we have a selected diff, deselect it
1017 int nCurrentDiff = pd->GetCurrentDiff();
1018 if (nCurrentDiff != -1)
1020 CPoint pos = GetCursorPos();
1021 if (!IsLineInCurrentDiff(pos.y))
1023 pd->SetCurrentDiff(-1);
1025 pd->UpdateAllViews(this);
1031 * @brief Called when user selects "Current Difference".
1032 * Goes to active diff. If no active diff, selects diff under cursor
1033 * @sa CMergeEditView::SelectDiff()
1034 * @sa CMergeDoc::GetCurrentDiff()
1035 * @sa CMergeDoc::LineToDiff()
1037 void CMergeEditView::OnCurdiff()
1039 CMergeDoc *pd = GetDocument();
1041 // If no diffs, nothing to select
1042 if (!pd->m_diffList.HasSignificantDiffs())
1045 // GetCurrentDiff() returns -1 if no diff selected
1046 int nDiff = pd->GetCurrentDiff();
1049 // Scroll to the first line of the currently selected diff
1050 SelectDiff(nDiff, true, false);
1054 // If cursor is inside diff, select that diff
1055 CPoint pos = GetCursorPos();
1056 nDiff = pd->m_diffList.LineToDiff(pos.y);
1057 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1058 SelectDiff(nDiff, true, false);
1063 * @brief Called when "Current diff" item is updated
1065 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1067 CMergeDoc *pd = GetDocument();
1068 CPoint pos = GetCursorPos();
1069 int nCurrentDiff = pd->GetCurrentDiff();
1070 if (nCurrentDiff == -1)
1072 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1073 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1076 pCmdUI->Enable(true);
1080 * @brief Copy selected text to clipboard
1082 void CMergeEditView::OnEditCopy()
1084 CMergeDoc * pDoc = GetDocument();
1085 CPoint ptSelStart, ptSelEnd;
1086 GetSelection(ptSelStart, ptSelEnd);
1089 if (ptSelStart == ptSelEnd)
1094 if (!m_bRectangularSelection)
1096 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1098 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1099 ptSelEnd.y, ptSelEnd.x, text);
1102 GetTextWithoutEmptysInColumnSelection(text);
1104 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1108 * @brief Called when "Copy" item is updated
1110 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1112 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1116 * @brief Cut current selection to clipboard
1118 void CMergeEditView::OnEditCut()
1120 if (!QueryEditable())
1123 CPoint ptSelStart, ptSelEnd;
1124 CMergeDoc * pDoc = GetDocument();
1125 GetSelection(ptSelStart, ptSelEnd);
1128 if (ptSelStart == ptSelEnd)
1132 if (!m_bRectangularSelection)
1133 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1134 ptSelEnd.y, ptSelEnd.x, text);
1136 GetTextWithoutEmptysInColumnSelection(text);
1138 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1140 if (!m_bRectangularSelection)
1142 CPoint ptCursorPos = ptSelStart;
1143 ASSERT_VALIDTEXTPOS(ptCursorPos);
1144 SetAnchor(ptCursorPos);
1145 SetSelection(ptCursorPos, ptCursorPos);
1146 SetCursorPos(ptCursorPos);
1147 EnsureVisible(ptCursorPos);
1149 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1150 ptSelEnd.x, CE_ACTION_CUT);
1153 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1155 m_pTextBuffer->SetModified(true);
1159 * @brief Called when "Cut" item is updated
1161 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1163 if (QueryEditable())
1164 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1166 pCmdUI->Enable(false);
1170 * @brief Paste text from clipboard
1172 void CMergeEditView::OnEditPaste()
1174 if (!QueryEditable())
1177 CCrystalEditViewEx::Paste();
1178 m_pTextBuffer->SetModified(true);
1182 * @brief Called when "Paste" item is updated
1184 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1186 if (QueryEditable())
1187 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1189 pCmdUI->Enable(false);
1193 * @brief Undo last action
1195 void CMergeEditView::OnEditUndo()
1197 CWaitCursor waitstatus;
1198 CMergeDoc* pDoc = GetDocument();
1199 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1202 if (!QueryEditable())
1205 GetParentFrame()->SetActiveView(this, true);
1206 if(CCrystalEditViewEx::DoEditUndo())
1209 pDoc->UpdateHeaderPath(m_nThisPane);
1210 pDoc->FlushAndRescan();
1213 m_pTextBuffer->GetRedoActionCode(nAction);
1214 if (nAction == CE_ACTION_MERGE)
1215 // select the diff so we may just merge it again
1221 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1223 if (!pDoc->CanUndo())
1224 pDoc->SetAutoMerged(false);
1228 * @brief Called when "Undo" item is updated
1230 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1232 CMergeDoc* pDoc = GetDocument();
1233 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1235 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1236 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1239 pCmdUI->Enable(false);
1243 * @brief Go to first diff
1245 * Called when user selects "First Difference"
1246 * @sa CMergeEditView::SelectDiff()
1248 void CMergeEditView::OnFirstdiff()
1250 CMergeDoc *pd = GetDocument();
1251 if (pd->m_diffList.HasSignificantDiffs())
1253 int nDiff = pd->m_diffList.FirstSignificantDiff();
1254 SelectDiff(nDiff, true, false);
1259 * @brief Update "First diff" UI items
1261 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1263 OnUpdatePrevdiff(pCmdUI);
1267 * @brief Go to last diff
1269 void CMergeEditView::OnLastdiff()
1271 CMergeDoc *pd = GetDocument();
1272 if (pd->m_diffList.HasSignificantDiffs())
1274 int nDiff = pd->m_diffList.LastSignificantDiff();
1275 SelectDiff(nDiff, true, false);
1280 * @brief Update "Last diff" UI items
1282 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1284 OnUpdateNextdiff(pCmdUI);
1288 * @brief Go to next diff and select it.
1290 * Finds and selects next difference. There are several cases:
1291 * - if there is selected difference, and that difference is visible
1292 * on screen, next found difference is selected.
1293 * - if there is selected difference but it is not visible, next
1294 * difference from cursor position is selected. This is what user
1295 * expects to happen and is natural thing to do. Also reduces
1296 * needless scrolling.
1297 * - if there is no selected difference, next difference from cursor
1298 * position is selected.
1300 void CMergeEditView::OnNextdiff()
1302 CMergeDoc *pd = GetDocument();
1303 int cnt = pd->m_ptBuf[0]->GetLineCount();
1307 // Returns -1 if no diff selected
1309 int curDiff = pd->GetCurrentDiff();
1313 if (!IsDiffVisible(curDiff))
1315 // Selected difference not visible, select next from cursor
1316 int line = GetCursorPos().y;
1317 // Make sure we aren't in the first line of the diff
1319 if (!IsValidTextPosY(CPoint(0, line)))
1321 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1325 // Find out if there is a following significant diff
1326 if (curDiff < pd->m_diffList.GetSize() - 1)
1328 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1334 // We don't have a selected difference,
1335 // but cursor can be inside inactive diff
1336 int line = GetCursorPos().y;
1337 if (!IsValidTextPosY(CPoint(0, line)))
1339 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1342 int lastDiff = pd->m_diffList.LastSignificantDiff();
1343 if (nextDiff >= 0 && nextDiff <= lastDiff)
1344 SelectDiff(nextDiff, true, false);
1345 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1347 if (pDirDoc->MoveableToNextDiff())
1348 pDirDoc->MoveToNextDiff(pd);
1353 * @brief Update "Next diff" UI items
1355 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1357 CMergeDoc *pd = GetDocument();
1358 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1363 // There aren't any significant differences
1368 // Enable if the beginning of the last significant difference is after caret
1369 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1372 if (!enabled && pd->GetDirDoc())
1373 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1375 pCmdUI->Enable(enabled);
1379 * @brief Go to previous diff and select it.
1381 * Finds and selects previous difference. There are several cases:
1382 * - if there is selected difference, and that difference is visible
1383 * on screen, previous found difference is selected.
1384 * - if there is selected difference but it is not visible, previous
1385 * difference from cursor position is selected. This is what user
1386 * expects to happen and is natural thing to do. Also reduces
1387 * needless scrolling.
1388 * - if there is no selected difference, previous difference from cursor
1389 * position is selected.
1391 void CMergeEditView::OnPrevdiff()
1393 CMergeDoc *pd = GetDocument();
1394 int cnt = pd->m_ptBuf[0]->GetLineCount();
1398 // GetCurrentDiff() returns -1 if no diff selected
1400 int curDiff = pd->GetCurrentDiff();
1404 if (!IsDiffVisible(curDiff))
1406 // Selected difference not visible, select previous from cursor
1407 int line = GetCursorPos().y;
1408 // Make sure we aren't in the last line of the diff
1410 if (!IsValidTextPosY(CPoint(0, line)))
1412 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1416 // Find out if there is a preceding significant diff
1419 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1425 // We don't have a selected difference,
1426 // but cursor can be inside inactive diff
1427 int line = GetCursorPos().y;
1428 if (!IsValidTextPosY(CPoint(0, line)))
1430 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1433 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1434 if (prevDiff >= 0 && prevDiff >= firstDiff)
1435 SelectDiff(prevDiff, true, false);
1436 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1438 if (pDirDoc->MoveableToPrevDiff())
1439 pDirDoc->MoveToPrevDiff(pd);
1444 * @brief Update "Previous diff" UI items
1446 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1448 CMergeDoc *pd = GetDocument();
1449 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1454 // There aren't any significant differences
1459 // Enable if the end of the first significant difference is before caret
1460 enabled = (GetCursorPos().y > (long)dfi->dend);
1463 if (!enabled && pd->GetDirDoc())
1464 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1466 pCmdUI->Enable(enabled);
1469 void CMergeEditView::OnNextConflict()
1471 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1475 * @brief Update "Next Conflict" UI items
1477 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1479 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1482 void CMergeEditView::OnPrevConflict()
1484 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1488 * @brief Update "Prev Conflict" UI items
1490 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1492 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1496 * @brief Go to next 3-way diff and select it.
1498 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1500 CMergeDoc *pd = GetDocument();
1501 int cnt = pd->m_ptBuf[0]->GetLineCount();
1505 // Returns -1 if no diff selected
1506 int curDiff = pd->GetCurrentDiff();
1510 int nextDiff = curDiff;
1511 if (!IsDiffVisible(curDiff))
1513 // Selected difference not visible, select next from cursor
1514 int line = GetCursorPos().y;
1515 // Make sure we aren't in the first line of the diff
1517 if (!IsValidTextPosY(CPoint(0, line)))
1519 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1523 // Find out if there is a following significant diff
1524 if (curDiff < pd->m_diffList.GetSize() - 1)
1526 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1532 // nextDiff is the next one if there is one, else it is the one we're on
1533 SelectDiff(nextDiff, true, false);
1537 // We don't have a selected difference,
1538 // but cursor can be inside inactive diff
1539 int line = GetCursorPos().y;
1540 if (!IsValidTextPosY(CPoint(0, line)))
1542 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1544 SelectDiff(curDiff, true, false);
1549 * @brief Update "Next 3-way diff" UI items
1551 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1553 CMergeDoc *pd = GetDocument();
1555 if (pd->m_nBuffers < 3)
1557 pCmdUI->Enable(false);
1561 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1565 // There aren't any significant differences
1566 pCmdUI->Enable(false);
1570 // Enable if the beginning of the last significant difference is after caret
1571 CPoint pos = GetCursorPos();
1572 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1577 * @brief Go to previous 3-way diff and select it.
1579 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1581 CMergeDoc *pd = GetDocument();
1583 int cnt = pd->m_ptBuf[0]->GetLineCount();
1587 // GetCurrentDiff() returns -1 if no diff selected
1588 int curDiff = pd->GetCurrentDiff();
1592 int prevDiff = curDiff;
1593 if (!IsDiffVisible(curDiff))
1595 // Selected difference not visible, select previous from cursor
1596 int line = GetCursorPos().y;
1597 // Make sure we aren't in the last line of the diff
1599 if (!IsValidTextPosY(CPoint(0, line)))
1601 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1605 // Find out if there is a preceding significant diff
1608 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1614 // prevDiff is the preceding one if there is one, else it is the one we're on
1615 SelectDiff(prevDiff, true, false);
1619 // We don't have a selected difference,
1620 // but cursor can be inside inactive diff
1621 int line = GetCursorPos().y;
1622 if (!IsValidTextPosY(CPoint(0, line)))
1624 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1626 SelectDiff(curDiff, true, false);
1631 * @brief Update "Previous diff X and Y" UI items
1633 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1635 CMergeDoc *pd = GetDocument();
1637 if (pd->m_nBuffers < 3)
1639 pCmdUI->Enable(false);
1643 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1647 // There aren't any significant differences
1648 pCmdUI->Enable(false);
1652 // Enable if the end of the first significant difference is before caret
1653 CPoint pos = GetCursorPos();
1654 pCmdUI->Enable(pos.y > (long)dfi->dend);
1658 void CMergeEditView::OnNextdiffLM()
1660 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1663 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1665 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1668 void CMergeEditView::OnNextdiffLR()
1670 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1673 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1675 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1678 void CMergeEditView::OnNextdiffMR()
1680 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1683 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1685 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1688 void CMergeEditView::OnNextdiffLO()
1690 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1693 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1695 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1698 void CMergeEditView::OnNextdiffMO()
1700 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1703 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1705 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1708 void CMergeEditView::OnNextdiffRO()
1710 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1713 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1715 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1718 void CMergeEditView::OnPrevdiffLM()
1720 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1723 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1725 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1728 void CMergeEditView::OnPrevdiffLR()
1730 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1733 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1735 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1738 void CMergeEditView::OnPrevdiffMR()
1740 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1743 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1745 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1748 void CMergeEditView::OnPrevdiffLO()
1750 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1753 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1755 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1758 void CMergeEditView::OnPrevdiffMO()
1760 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1763 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1765 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1768 void CMergeEditView::OnPrevdiffRO()
1770 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1773 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1775 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1779 * @brief Clear selection
1781 void CMergeEditView::SelectNone()
1783 SetSelection (GetCursorPos(), GetCursorPos());
1788 * @brief Check if line is inside currently selected diff
1789 * @param [in] nLine 0-based linenumber in view
1790 * @sa CMergeDoc::GetCurrentDiff()
1791 * @sa CMergeDoc::LineInDiff()
1793 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1795 // Check validity of nLine
1798 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1799 int nLineCount = LocateTextBuffer()->GetLineCount();
1800 if (nLine >= nLineCount)
1801 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1804 const CMergeDoc *pd = GetDocument();
1805 int curDiff = pd->GetCurrentDiff();
1808 return pd->m_diffList.LineInDiff(nLine, curDiff);
1812 * @brief Called when mouse left-button double-clicked
1814 * Double-clicking mouse inside diff selects that diff
1816 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1818 CMergeDoc *pd = GetDocument();
1819 CPoint pos = GetCursorPos();
1821 int diff = pd->m_diffList.LineToDiff(pos.y);
1822 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1823 SelectDiff(diff, false, false);
1825 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1829 * @brief Called when mouse left button is released.
1831 * If button is released outside diffs, current diff
1834 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1836 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1837 DeselectDiffIfCursorNotInCurrentDiff();
1841 * @brief Called when mouse right button is pressed.
1843 * If right button is pressed outside diffs, current diff
1846 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1848 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1849 DeselectDiffIfCursorNotInCurrentDiff();
1852 void CMergeEditView::OnX2Y(int srcPane, int dstPane)
1854 // Check that right side is not readonly
1855 if (IsReadOnly(dstPane))
1858 CMergeDoc *pDoc = GetDocument();
1859 int currentDiff = pDoc->GetCurrentDiff();
1861 if (currentDiff == -1)
1864 // If cursor is inside diff get number of that diff
1865 if (m_bCurrentLineIsDiff)
1867 CPoint pt = GetCursorPos();
1868 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1872 CPoint ptStart, ptEnd;
1873 GetSelection(ptStart, ptEnd);
1874 if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1876 if (!m_bRectangularSelection)
1878 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1879 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1880 if (firstDiff != -1 && lastDiff != -1)
1882 CWaitCursor waitstatus;
1884 // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1885 // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1886 if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1888 // old behaviour: copy full line
1889 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1893 // new behaviour: copy selected text only
1894 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1900 CWaitCursor waitstatus;
1901 auto wordDiffs = GetColumnSelectedWordDiffIndice();
1903 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1904 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
1909 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1911 CWaitCursor waitstatus;
1912 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1916 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
1918 // Check that right side is not readonly
1919 if (!IsReadOnly(dstPane))
1921 // If one or more diffs inside selection OR
1922 // there is an active diff OR
1923 // cursor is inside diff
1924 CPoint ptStart, ptEnd;
1925 GetSelection(ptStart, ptEnd);
1926 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
1928 if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
1930 pCmdUI->Enable(true);
1934 int firstDiff, lastDiff;
1935 GetFullySelectedDiffs(firstDiff, lastDiff);
1937 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
1942 const int currDiff = GetDocument()->GetCurrentDiff();
1943 pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
1947 pCmdUI->Enable(false);
1951 * @brief Copy diff from left pane to right pane
1953 * Difference is copied from left to right when
1954 * - difference is selected
1955 * - difference is inside selection (allows merging multiple differences).
1956 * - cursor is inside diff
1958 * If there is selected diff outside selection, we copy selected
1961 void CMergeEditView::OnL2r()
1963 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1964 int srcPane = dstPane - 1;
1965 OnX2Y(srcPane, dstPane);
1969 * @brief Called when "Copy to left" item is updated
1971 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
1973 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
1977 * @brief Copy diff from right pane to left pane
1979 * Difference is copied from left to right when
1980 * - difference is selected
1981 * - difference is inside selection (allows merging multiple differences).
1982 * - cursor is inside diff
1984 * If there is selected diff outside selection, we copy selected
1987 void CMergeEditView::OnR2l()
1989 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
1990 int srcPane = dstPane + 1;
1991 OnX2Y(srcPane, dstPane);
1995 * @brief Called when "Copy to right" item is updated
1997 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
1999 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2002 void CMergeEditView::OnCopyFromLeft()
2004 int dstPane = m_nThisPane;
2005 int srcPane = dstPane - 1;
2008 OnX2Y(srcPane, dstPane);
2011 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2013 int dstPane = m_nThisPane;
2014 int srcPane = dstPane - 1;
2016 pCmdUI->Enable(false);
2018 OnUpdateX2Y(dstPane, pCmdUI);
2021 void CMergeEditView::OnCopyFromRight()
2023 int dstPane = m_nThisPane;
2024 int srcPane = dstPane + 1;
2025 if (srcPane >= GetDocument()->m_nBuffers)
2027 OnX2Y(srcPane, dstPane);
2030 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2032 int dstPane = m_nThisPane;
2033 int srcPane = dstPane + 1;
2034 if (srcPane >= GetDocument()->m_nBuffers)
2035 pCmdUI->Enable(false);
2037 OnUpdateX2Y(dstPane, pCmdUI);
2041 * @brief Copy all diffs from right pane to left pane
2043 void CMergeEditView::OnAllLeft()
2045 // Check that left side is not readonly
2046 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
2047 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2048 if (IsReadOnly(dstPane))
2050 CWaitCursor waitstatus;
2052 GetDocument()->CopyAllList(srcPane, dstPane);
2056 * @brief Called when "Copy all to left" item is updated
2058 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2060 // Check that left side is not readonly
2061 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2062 if (!IsReadOnly(dstPane))
2063 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2065 pCmdUI->Enable(false);
2069 * @brief Copy all diffs from left pane to right pane
2071 void CMergeEditView::OnAllRight()
2073 // Check that right side is not readonly
2074 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
2075 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2076 if (IsReadOnly(dstPane))
2079 CWaitCursor waitstatus;
2081 GetDocument()->CopyAllList(srcPane, dstPane);
2085 * @brief Called when "Copy all to right" item is updated
2087 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2089 // Check that right side is not readonly
2090 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2091 if (!IsReadOnly(dstPane))
2092 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2094 pCmdUI->Enable(false);
2098 * @brief Do Auto merge
2100 void CMergeEditView::OnAutoMerge()
2102 // Check current pane is not readonly
2103 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2106 CWaitCursor waitstatus;
2108 GetDocument()->DoAutoMerge(m_nThisPane);
2112 * @brief Called when "Auto Merge" item is updated
2114 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2116 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2117 !GetDocument()->IsModified() &&
2118 !GetDocument()->GetAutoMerged() &&
2123 * @brief Add synchronization point
2125 void CMergeEditView::OnAddSyncPoint()
2127 GetDocument()->AddSyncPoint();
2131 * @brief Clear synchronization points
2133 void CMergeEditView::OnClearSyncPoints()
2135 GetDocument()->ClearSyncPoints();
2139 * @brief Called when "Clear Synchronization Points" item is updated
2141 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2143 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2147 * @brief This function is called before other edit events.
2148 * @param [in] nAction Edit operation to do
2149 * @param [in] pszText Text to insert, delete etc
2150 * @sa CCrystalEditView::OnEditOperation()
2151 * @todo More edit-events for rescan delaying?
2153 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2155 if (!QueryEditable())
2157 // We must not arrive here, and assert helps detect troubles
2162 CMergeDoc* pDoc = GetDocument();
2163 pDoc->SetEditedAfterRescan(m_nThisPane);
2165 // simple hook for multiplex undo operations
2166 // deleted by jtuc 2003-06-28
2167 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2168 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2170 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2171 pDoc->undoTgt.push_back(this);
2172 pDoc->curUndo = pDoc->undoTgt.end();
2175 // perform original function
2176 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2178 // augment with additional operations
2180 // Change header to inform about changed doc
2181 pDoc->UpdateHeaderPath(m_nThisPane);
2183 // If automatic rescan enabled, rescan after edit events
2184 if (m_bAutomaticRescan)
2186 // keep document up to date
2187 // (Re)start timer to rescan only when user edits text
2188 // If timer starting fails, rescan immediately
2189 if (nAction == CE_ACTION_TYPING ||
2190 nAction == CE_ACTION_REPLACE ||
2191 nAction == CE_ACTION_BACKSPACE ||
2192 nAction == CE_ACTION_INDENT ||
2193 nAction == CE_ACTION_PASTE ||
2194 nAction == CE_ACTION_DELSEL ||
2195 nAction == CE_ACTION_DELETE ||
2196 nAction == CE_ACTION_CUT)
2198 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2199 pDoc->FlushAndRescan();
2202 pDoc->FlushAndRescan();
2208 // Update other pane for sync line.
2209 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2211 if (nPane == m_nThisPane)
2213 CCrystalEditView *pView = GetGroupView(nPane);
2214 if (pView != nullptr)
2215 pView->Invalidate();
2222 * @brief Redo last action
2224 void CMergeEditView::OnEditRedo()
2226 CWaitCursor waitstatus;
2227 CMergeDoc* pDoc = GetDocument();
2228 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2231 if (!QueryEditable())
2234 GetParentFrame()->SetActiveView(this, true);
2235 if(CCrystalEditViewEx::DoEditRedo())
2238 pDoc->UpdateHeaderPath(m_nThisPane);
2239 pDoc->FlushAndRescan();
2244 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2249 * @brief Called when "Redo" item is updated
2251 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2253 CMergeDoc* pDoc = GetDocument();
2254 if (pDoc->curUndo!=pDoc->undoTgt.end())
2256 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2257 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2260 pCmdUI->Enable(false);
2263 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2265 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2269 * @brief Scrolls to current diff and/or selects diff text
2270 * @param [in] bScroll If true scroll diff to view
2271 * @param [in] bSelectText If true select diff text
2272 * @note If bScroll and bSelectText are false, this does nothing!
2273 * @todo This shouldn't be called when no diff is selected, so
2274 * somebody could try to ASSERT(nDiff > -1)...
2276 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2278 CMergeDoc *pd = GetDocument();
2279 const int nDiff = pd->GetCurrentDiff();
2281 // Try to trap some errors
2282 if (nDiff >= pd->m_diffList.GetSize())
2283 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2284 nDiff, pd->m_diffList.GetSize());
2286 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2288 CPoint ptStart, ptEnd;
2290 pd->m_diffList.GetDiff(nDiff, curDiff);
2293 ptStart.y = curDiff.dbegin;
2295 ptEnd.y = curDiff.dend;
2297 if (bScroll && !m_bDetailView)
2299 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2301 // Difference is not visible, scroll it so that max amount of
2302 // scrolling is done while keeping the diff in screen. So if
2303 // scrolling is downwards, scroll the diff to as up in screen
2304 // as possible. This usually brings next diff to the screen
2305 // and we don't need to scroll into it.
2306 int nLine = GetSubLineIndex(ptStart.y);
2307 if (nLine > CONTEXT_LINES_ABOVE)
2309 nLine -= CONTEXT_LINES_ABOVE;
2311 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2312 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2314 if (nPane != m_nThisPane)
2315 GetGroupView(nPane)->ScrollToSubLine(nLine);
2319 vector<WordDiff> worddiffs;
2320 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
2321 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2322 CPoint pt = worddiffs.size() > 0 ?
2323 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2325 GetGroupView(m_nThisPane)->SetCursorPos(pt);
2326 GetGroupView(m_nThisPane)->SetAnchor(pt);
2327 GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2328 GetGroupView(m_nThisPane)->EnsureVisible(pt);
2329 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2331 if (nPane != m_nThisPane)
2333 if (worddiffs.size() > 0)
2335 pt.x = worddiffs[0].begin[nPane];
2336 pt.y = worddiffs[0].beginline[nPane];
2338 GetGroupView(nPane)->SetCursorPos(pt);
2339 GetGroupView(nPane)->SetAnchor(pt);
2340 GetGroupView(nPane)->SetSelection(pt, pt);
2347 ptEnd.x = GetLineLength(ptEnd.y);
2348 SetSelection(ptStart, ptEnd);
2357 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2359 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2360 // ...but for this the queue must be empty
2361 // The timer message is a low priority message but the queue is maybe not yet empty
2362 // So we set a flag, wait for OnIdle to proceed, then come back here...
2363 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2364 // not with SetTimer so there is no delay)
2366 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2368 // IDLE_TIMER is the false timer used to come back here after OnIdle
2369 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2370 // (one normal timer = one flag = one command)
2372 if (nIDEvent == IDT_RESCAN)
2374 KillTimer(IDT_RESCAN);
2375 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2376 // notify the app to come back after OnIdle
2377 theApp.SetNeedIdleTimer();
2380 if (nIDEvent == IDLE_TIMER)
2382 // not a real timer, just come back after OnIdle
2383 // look to flags to know what to do
2384 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2385 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2386 fTimerWaitingForIdle = 0;
2389 CCrystalEditViewEx::OnTimer(nIDEvent);
2393 * @brief Returns if buffer is read-only
2394 * @note This has no any relation to file being read-only!
2396 bool CMergeEditView::IsReadOnly(int pane) const
2398 return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2402 * @brief Called when "Save left (as...)" item is updated
2404 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2406 CMergeDoc *pd = GetDocument();
2407 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2411 * @brief Called when "Save middle (as...)" item is updated
2413 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2415 CMergeDoc *pd = GetDocument();
2416 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2420 * @brief Called when "Save right (as...)" item is updated
2422 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2424 CMergeDoc *pd = GetDocument();
2425 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2429 * @brief Refresh display using text-buffers
2430 * @note This DOES NOT reload files!
2432 void CMergeEditView::OnRefresh()
2434 CMergeDoc *pd = GetDocument();
2435 ASSERT(pd != nullptr);
2436 pd->FlushAndRescan(true);
2440 * @brief Handle some keys when in merging mode
2442 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2444 bool bHandled = false;
2446 // Allow default text selection when SHIFT pressed
2447 if (::GetAsyncKeyState(VK_SHIFT))
2450 // Allow default editor functions when CTRL pressed
2451 if (::GetAsyncKeyState(VK_CONTROL))
2454 // If we are in merging mode (merge with cursor keys)
2455 // handle some keys here
2456 switch (pMsg->wParam)
2483 * @brief Called before messages are translated.
2485 * Checks if ESC key was pressed, saves and closes doc.
2486 * Also if in merge mode traps cursor keys.
2488 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2490 if (pMsg->message == WM_KEYDOWN)
2492 // If we are in merging mode (merge with cursor keys)
2493 // handle some keys here
2494 if (theApp.GetMergingMode())
2496 bool bHandled = MergeModeKeyDown(pMsg);
2501 // Close window if user has allowed it from options
2502 if (pMsg->wParam == VK_ESCAPE)
2504 int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2505 if (nCloseWithEsc != 0)
2506 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2511 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2515 * @brief Called when "Save" item is updated
2517 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2519 CMergeDoc *pd = GetDocument();
2521 bool bModified = false;
2522 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2524 if (pd->m_ptBuf[nPane]->IsModified())
2527 pCmdUI->Enable(bModified);
2531 * @brief Enable/disable left buffer read-only
2533 void CMergeEditView::OnLeftReadOnly()
2535 CMergeDoc *pd = GetDocument();
2536 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2537 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2541 * @brief Called when "Left read-only" item is updated
2543 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2545 CMergeDoc *pd = GetDocument();
2546 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2547 pCmdUI->Enable(true);
2548 pCmdUI->SetCheck(bReadOnly);
2552 * @brief Enable/disable middle buffer read-only
2554 void CMergeEditView::OnMiddleReadOnly()
2556 CMergeDoc *pd = GetDocument();
2557 if (pd->m_nBuffers == 3)
2559 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2560 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2565 * @brief Called when "Middle read-only" item is updated
2567 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2569 CMergeDoc *pd = GetDocument();
2570 if (pd->m_nBuffers < 3)
2572 pCmdUI->Enable(false);
2576 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2577 pCmdUI->Enable(true);
2578 pCmdUI->SetCheck(bReadOnly);
2583 * @brief Enable/disable right buffer read-only
2585 void CMergeEditView::OnRightReadOnly()
2587 CMergeDoc *pd = GetDocument();
2588 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2589 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2593 * @brief Called when "Left read-only" item is updated
2595 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2597 CMergeDoc *pd = GetDocument();
2598 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2599 pCmdUI->Enable(true);
2600 pCmdUI->SetCheck(bReadOnly);
2603 /// Store interface we use to display status line info
2604 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2606 ASSERT(m_piMergeEditStatus == nullptr);
2607 m_piMergeEditStatus = piMergeEditStatus;
2611 * @brief Update status bar contents.
2613 void CMergeEditView::UpdateStatusbar()
2619 * @brief Update statusbar info, Override from CCrystalTextView
2620 * @note we tab-expand column, but we don't tab-expand char count,
2621 * since we want to show how many chars there are and tab is just one
2622 * character although it expands to several spaces.
2624 void CMergeEditView::OnUpdateCaret()
2626 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2629 CPoint cursorPos = GetCursorPos();
2630 int nScreenLine = cursorPos.y;
2631 const int nRealLine = ComputeRealLine(nScreenLine);
2638 DWORD dwLineFlags = 0;
2640 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2641 // Is this a ghost line ?
2642 if (dwLineFlags & LF_GHOST)
2644 // Ghost lines display eg "Line 12-13"
2645 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2646 sEol = _T("hidden");
2650 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2651 sLine.Format(_T("%d"), nRealLine+1);
2652 curChar = cursorPos.x + 1;
2653 chars = GetLineLength(nScreenLine);
2654 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2655 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2657 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2658 GetDocument()->IsMixedEOL(m_nThisPane))
2660 sEol = GetTextBufferEol(nScreenLine);
2663 sEol = _T("hidden");
2665 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2666 curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2668 // Is cursor inside difference?
2669 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2670 m_bCurrentLineIsDiff = true;
2672 m_bCurrentLineIsDiff = false;
2674 CWnd* pWnd = GetFocus();
2675 if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2676 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2679 * @brief Select linedifference in the current line.
2681 * Select line difference in current line. Selection type
2682 * is choosed by highlight type.
2684 template<bool reversed>
2685 void CMergeEditView::OnSelectLineDiff()
2687 // Pass this to the document, to compare this file to other
2688 GetDocument()->Showlinediff(this, reversed);
2691 /// Enable select difference menuitem if current line is inside difference.
2692 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2694 pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2698 * @brief Enable/disable Replace-menuitem
2700 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2702 CMergeDoc *pd = GetDocument();
2703 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2705 pCmdUI->Enable(!bReadOnly);
2709 * @brief Update readonly statusbaritem
2711 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2713 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2714 pCmdUI->Enable(bRO);
2718 * @brief Create the dynamic submenu for scripts
2720 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2723 std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2726 size_t i = GetMenuItemCount(hMenu);
2728 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2730 if (functionNamesList.size() == 0)
2732 // no script : create a <empty> entry
2733 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2737 // or fill in the submenu with the scripts names
2738 int ID = ID_SCRIPT_FIRST; // first ID in menu
2739 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2740 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2742 functionNamesList.clear();
2745 if (!plugin::IsWindowsScriptThere())
2746 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2752 * @brief Create the dynamic submenu for prediffers
2754 * @note The plugins are grouped in (suggested) and (not suggested)
2755 * The IDs follow the order of GetAvailableScripts
2757 * suggested 0 ID_1ST + 0
2758 * suggested 1 ID_1ST + 2
2759 * suggested 2 ID_1ST + 5
2760 * not suggested 0 ID_1ST + 1
2761 * not suggested 1 ID_1ST + 3
2762 * not suggested 2 ID_1ST + 4
2764 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2767 int i = GetMenuItemCount(hMenu);
2769 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2771 CMergeDoc *pd = GetDocument();
2772 ASSERT(pd != nullptr);
2775 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2777 // get the scriptlet files
2778 PluginArray * piScriptArray =
2779 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2780 PluginArray * piScriptArray2 =
2781 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2783 // build the menu : first part, suggested plugins
2785 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2786 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2788 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
2790 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2792 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2793 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2796 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2798 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2800 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2801 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2804 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2807 // build the menu : second part, others plugins
2809 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2810 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2812 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2813 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2815 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2816 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2819 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2821 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2823 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2824 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2827 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2830 // compute the m_CurrentPredifferID (to set the radio button)
2831 PrediffingInfo prediffer;
2832 pd->GetPrediffer(&prediffer);
2834 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2835 m_CurrentPredifferID = 0;
2836 else if (prediffer.m_PluginName.empty())
2837 m_CurrentPredifferID = ID_NO_PREDIFFER;
2840 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2841 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2843 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2844 if (prediffer.m_PluginName == plugin->m_name)
2845 m_CurrentPredifferID = ID;
2848 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2850 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2851 if (prediffer.m_PluginName == plugin->m_name)
2852 m_CurrentPredifferID = ID;
2860 * @brief Offer a context menu built with scriptlet/ActiveX functions
2862 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
2864 // Create the menu and populate it with the available functions
2866 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
2868 // Remove copying item copying from active side
2869 if (m_nThisPane == 0) // left?
2871 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2872 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2874 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
2876 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2877 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2880 // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
2881 // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
2882 // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
2883 int nBuffers = GetDocument()->m_nBuffers;
2884 if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
2885 menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
2886 else if (nBuffers == 3 && m_nThisPane == 2)
2887 menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
2889 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
2890 theApp.TranslateMenu(menu.m_hMenu);
2892 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
2893 ASSERT(pSub != nullptr);
2895 // Context menu opened using keyboard has no coordinates
2896 if (point.x == -1 && point.y == -1)
2899 GetClientRect(rect);
2900 ClientToScreen(rect);
2902 point = rect.TopLeft();
2906 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2907 point.x, point.y, AfxGetMainWnd());
2912 * @brief Update EOL mode in status bar
2914 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
2916 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
2920 * @brief Change EOL mode and unify all the lines EOL to this new mode
2922 void CMergeEditView::OnConvertEolTo(UINT nID )
2924 CRLFSTYLE nStyle = CRLF_STYLE_AUTOMATIC;;
2928 nStyle = CRLF_STYLE_DOS;
2930 case ID_EOL_TO_UNIX:
2931 nStyle = CRLF_STYLE_UNIX;
2934 nStyle = CRLF_STYLE_MAC;
2938 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
2941 m_pTextBuffer->SetCRLFMode(nStyle);
2943 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
2944 if (m_pTextBuffer->applyEOLMode())
2946 CMergeDoc *pd = GetDocument();
2947 ASSERT(pd != nullptr);
2948 pd->UpdateHeaderPath(m_nThisPane);
2949 pd->FlushAndRescan(true);
2954 * @brief allow convert to entries in file submenu
2956 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
2958 int nStyle = CRLF_STYLE_AUTOMATIC;
2959 switch (pCmdUI->m_nID)
2962 nStyle = CRLF_STYLE_DOS;
2964 case ID_EOL_TO_UNIX:
2965 nStyle = CRLF_STYLE_UNIX;
2968 nStyle = CRLF_STYLE_MAC;
2972 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
2976 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2977 GetDocument()->IsMixedEOL(m_nThisPane) ||
2978 nStyle != m_pTextBuffer->GetCRLFMode())
2980 pCmdUI->SetRadio(false);
2982 // Don't allow selecting other EOL style for protected pane
2983 if (!QueryEditable())
2984 pCmdUI->Enable(false);
2987 pCmdUI->SetRadio(true);
2991 * @brief Copy diff from left to right and advance to next diff
2993 void CMergeEditView::OnL2RNext()
2996 if (IsCursorInDiff()) // for 3-way file compare
3002 * @brief Update "Copy right and advance" UI item
3004 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
3006 OnUpdateL2r(pCmdUI);
3010 * @brief Copy diff from right to left and advance to next diff
3012 void CMergeEditView::OnR2LNext()
3015 if (IsCursorInDiff()) // for 3-way file compare
3021 * @brief Update "Copy left and advance" UI item
3023 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
3025 OnUpdateR2l(pCmdUI);
3029 * @brief Change active pane in MergeView.
3030 * Changes active pane and makes sure cursor position is kept in
3031 * screen. Currently we put cursor in same line than in original
3032 * active pane but we could be smarter too? Maybe update cursor
3033 * only when it is not visible in new pane?
3035 void CMergeEditView::OnChangePane()
3037 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3038 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
3039 CMergeDoc *pDoc = GetDocument();
3040 bool bFound = false;
3041 CMergeEditView *pNextActiveView = nullptr;
3042 std::vector<CMergeEditView *> list = pDoc->GetViewList();
3043 list.insert(list.end(), list.begin(), list.end());
3044 for (auto& pView : list)
3046 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3048 pNextActiveView = pView;
3054 GetParentFrame()->SetActiveView(pNextActiveView);
3055 CPoint ptCursor = pWnd->GetCursorPos();
3057 if (ptCursor.y >= pNextActiveView->GetLineCount())
3058 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3059 pNextActiveView->SetCursorPos(ptCursor);
3060 pNextActiveView->SetAnchor(ptCursor);
3061 pNextActiveView->SetSelection(ptCursor, ptCursor);
3065 * @brief Show "Go To" dialog and scroll views to line or diff.
3067 * Before dialog is opened, current line and file is determined
3069 * @note Conversions needed between apparent and real lines
3071 void CMergeEditView::OnWMGoto()
3074 CMergeDoc *pDoc = GetDocument();
3075 CPoint pos = GetCursorPos();
3079 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3080 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
3081 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
3083 // Set active file and current line selected in dialog
3084 dlg.m_strParam = strutils::to_str(nRealLine + 1);
3085 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3086 dlg.m_nGotoWhat = 0;
3088 if (dlg.DoModal() == IDOK)
3090 CMergeDoc * pDoc1 = GetDocument();
3091 CMergeEditView * pCurrentView = nullptr;
3094 pCurrentView = GetGroupView(m_nThisPane);
3097 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3099 if (dlg.m_nGotoWhat == 0)
3101 int nRealLine1 = num;
3104 if (nRealLine1 > nLastLine)
3105 nRealLine1 = nLastLine;
3107 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
3114 if (diff >= pDoc1->m_diffList.GetSize())
3115 diff = pDoc1->m_diffList.GetSize();
3117 pCurrentView->SelectDiff(diff, true, false);
3123 * @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
3124 * Go to moved line between the left and right panes when in 2-way file comparison.
3125 * Go to moved line between the left and middle panes when in 3-way file comparison.
3127 void CMergeEditView::OnGotoMovedLineLM()
3129 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3132 CMergeDoc* pDoc = GetDocument();
3133 CPoint pos = GetCursorPos();
3135 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3136 ASSERT(pDoc != nullptr);
3137 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3140 if (m_nThisPane == 0)
3142 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3144 GotoLine(line, false, 1);
3146 else if (m_nThisPane == 1)
3148 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3150 GotoLine(line, false, 0);
3155 * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
3156 * @param [in] pCmdUI UI component to update.
3157 * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
3159 void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
3161 CMergeDoc* pDoc = GetDocument();
3162 CPoint pos = GetCursorPos();
3164 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3165 ASSERT(pCmdUI != nullptr);
3166 ASSERT(pDoc != nullptr);
3167 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3170 if (pDoc->m_nBuffers == 2)
3171 pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
3173 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
3175 pCmdUI->Enable(false);
3179 if (m_nThisPane == 0)
3181 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3182 pCmdUI->Enable(bOn);
3184 else if (m_nThisPane == 1)
3186 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3187 pCmdUI->Enable(bOn);
3192 * @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
3193 * Go to moved line between the middle and right panes when in 3-way file comparison.
3195 void CMergeEditView::OnGotoMovedLineMR()
3197 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3200 CMergeDoc* pDoc = GetDocument();
3201 CPoint pos = GetCursorPos();
3203 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3204 ASSERT(pDoc != nullptr);
3205 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3208 if (m_nThisPane == 1)
3210 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3212 GotoLine(line, false, 2);
3214 else if (m_nThisPane == 2)
3216 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3218 GotoLine(line, false, 1);
3223 * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
3224 * @param [in] pCmdUI UI component to update.
3226 void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
3228 CMergeDoc* pDoc = GetDocument();
3229 CPoint pos = GetCursorPos();
3231 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3232 ASSERT(pCmdUI != nullptr);
3233 ASSERT(pDoc != nullptr);
3234 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3237 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
3239 pCmdUI->Enable(false);
3243 if (m_nThisPane == 1)
3245 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3246 pCmdUI->Enable(bOn);
3248 else if (m_nThisPane == 2)
3250 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3251 pCmdUI->Enable(bOn);
3255 void CMergeEditView::OnShellMenu()
3257 CFrameWnd *pFrame = GetTopLevelFrame();
3258 ASSERT(pFrame != nullptr);
3259 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3260 pFrame->m_bAutoMenuEnable = FALSE;
3262 String path = GetDocument()->m_filePaths[m_nThisPane];
3263 std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3264 pContextMenu->Initialize();
3265 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3266 pContextMenu->RequeryShellContextMenu();
3268 ::GetCursorPos(&point);
3269 HWND hWnd = GetSafeHwnd();
3270 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3272 pContextMenu->InvokeCommand(nCmd, hWnd);
3273 pContextMenu->ReleaseShellContextMenu();
3275 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3278 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3280 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3284 * @brief Reload options.
3286 void CMergeEditView::RefreshOptions()
3288 RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3289 SetRenderingMode(nRenderingMode);
3291 m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3293 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3294 SetInsertTabs(true);
3296 SetInsertTabs(false);
3298 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3300 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3301 SetTextType(CrystalLineParser::SRC_PLAIN);
3303 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3304 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3306 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3307 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3308 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3309 GetDocument()->IsMixedEOL(m_nThisPane));
3311 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3314 void CMergeEditView::OnScripts(UINT nID )
3316 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3317 String text = GetSelectedText();
3319 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3320 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3322 // now replace the text
3323 ReplaceSelection(text.c_str(), text.length(), 0);
3327 * @brief Called when an editor script item is updated
3329 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3331 // append the scripts submenu
3332 HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
3333 if (scriptsSubmenu != nullptr)
3334 createScriptsSubmenu(scriptsSubmenu);
3336 pCmdUI->Enable(true);
3340 * @brief Called when an editor script item is updated
3342 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3344 pCmdUI->Enable(true);
3346 CMergeDoc *pd = GetDocument();
3347 ASSERT(pd != nullptr);
3348 PrediffingInfo prediffer;
3349 pd->GetPrediffer(&prediffer);
3351 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
3353 pCmdUI->SetRadio(false);
3357 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3358 if (prediffer.m_PluginName.empty())
3359 m_CurrentPredifferID = ID_NO_PREDIFFER;
3361 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3365 * @brief Update "Prediffer" menuitem
3367 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3369 // recreate the sub menu (to fill the "selected prediffers")
3370 GetMainFrame()->UpdatePrediffersMenu();
3374 void CMergeEditView::OnNoPrediffer()
3376 OnPrediffer(ID_NO_PREDIFFER);
3379 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3381 void CMergeEditView::OnPrediffer(UINT nID )
3383 CMergeDoc *pd = GetDocument();
3384 ASSERT(pd != nullptr);
3386 SetPredifferByMenu(nID);
3387 pd->FlushAndRescan(true);
3391 * @brief Handler for all prediffer choices.
3392 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3393 * ID_NO_PREDIFFER, & specific prediffers.
3395 void CMergeEditView::SetPredifferByMenu(UINT nID )
3397 CMergeDoc *pd = GetDocument();
3398 ASSERT(pd != nullptr);
3400 if (nID == ID_NO_PREDIFFER)
3402 m_CurrentPredifferID = nID;
3403 // All flags are set correctly during the construction
3404 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3405 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MANUAL;
3406 infoPrediffer->m_PluginName.clear();
3407 pd->SetPrediffer(infoPrediffer);
3408 pd->FlushAndRescan(true);
3412 // get the scriptlet files
3413 PluginArray * piScriptArray =
3414 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3415 PluginArray * piScriptArray2 =
3416 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3418 // build a PrediffingInfo structure fom the ID
3419 PrediffingInfo prediffer;
3420 prediffer.m_PluginOrPredifferMode = PLUGIN_MANUAL;
3422 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3423 if (pluginNumber < piScriptArray->size())
3425 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3426 prediffer.m_PluginName = plugin->m_name;
3430 pluginNumber -= piScriptArray->size();
3431 if (pluginNumber >= piScriptArray2->size())
3433 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3434 prediffer.m_PluginName = plugin->m_name;
3437 // update data for the radio button
3438 m_CurrentPredifferID = nID;
3440 // update the prediffer and rescan
3441 pd->SetPrediffer(&prediffer);
3445 * @brief Look through available prediffers, and return ID of requested one, if found
3447 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3450 int ID = ID_PREDIFFERS_FIRST;
3452 // Search file prediffers
3453 PluginArray * piScriptArray =
3454 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3455 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3457 const PluginInfoPtr & plugin = piScriptArray->at(i);
3458 if (plugin->m_name == prediffer)
3462 // Search buffer prediffers
3463 PluginArray * piScriptArray2 =
3464 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3465 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3467 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3468 if (plugin->m_name == prediffer)
3476 * @brief Look through available prediffers, and return ID of requested one, if found
3478 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3480 int id = FindPrediffer(prediffer);
3481 if (id<0) return false;
3482 SetPredifferByMenu(id);
3487 * @brief Goto given line.
3488 * @param [in] nLine Destination linenumber
3489 * @param [in] bRealLine if true linenumber is real line, otherwise
3490 * it is apparent line (including deleted lines)
3491 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3493 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
3495 CMergeDoc *pDoc = GetDocument();
3496 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3497 CMergeEditView *pCurrentView = nullptr;
3498 if (pSplitterWnd != nullptr)
3499 pCurrentView = static_cast<CMergeEditView*>
3500 (pSplitterWnd->GetActivePane());
3502 int nRealLine = nLine;
3503 int nApparentLine = nLine;
3505 // Compute apparent (shown linenumber) line
3508 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3509 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3511 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3515 ptPos.y = nApparentLine;
3517 // Scroll line to center of view
3518 int nScrollLine = GetSubLineIndex(nApparentLine);
3519 nScrollLine -= GetScreenLines() / 2;
3520 if (nScrollLine < 0)
3523 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3525 int nGroup = m_bDetailView ? 0 : m_nThisGroup;
3526 CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
3527 pView->ScrollToSubLine(nScrollLine);
3528 if (ptPos.y < pView->GetLineCount())
3530 pView->SetCursorPos(ptPos);
3531 pView->SetAnchor(ptPos);
3535 CPoint ptPos1(0, pView->GetLineCount() - 1);
3536 pView->SetCursorPos(ptPos1);
3537 pView->SetAnchor(ptPos1);
3541 // If goto target is another view - activate another view.
3542 // This is done for user convenience as user probably wants to
3543 // work with goto target file.
3545 GetDocument()->GetView(0, pane)->SetActivePane();
3546 else if (GetGroupView(pane) != pCurrentView)
3547 GetGroupView(pane)->SetActivePane();
3551 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3554 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3556 if (pScrollBar == nullptr)
3558 // Scroll did not come frome a scroll bar
3559 // Find the appropriate scroll bar
3560 // and send the message to the splitter window instead
3561 // The event should eventually come back here but with a valid scrollbar
3562 // Along the way it will be propagated to other windows that need it
3563 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3564 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3565 pSplitterWnd->SendMessage(WM_HSCROLL,
3566 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3569 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3573 * @brief When view is scrolled using scrollbars update location pane.
3575 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3577 if (pScrollBar == nullptr)
3579 // Scroll did not come frome a scroll bar
3580 // Find the appropriate scroll bar
3581 // and send the message to the splitter window instead
3582 // The event should eventually come back here but with a valid scrollbar
3583 // Along the way it will be propagated to other windows that need it
3584 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3585 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3586 pSplitterWnd->SendMessage(WM_VSCROLL,
3587 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3590 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3592 if (nSBCode == SB_ENDSCROLL)
3595 // Note we cannot use nPos because of its 16-bit nature
3596 SCROLLINFO si = {0};
3597 si.cbSize = sizeof (si);
3598 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3599 VERIFY (GetScrollInfo (SB_VERT, &si));
3601 // Get the current position of scroll box.
3602 int nCurPos = si.nPos;
3604 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3608 * @brief Copy selected lines adding linenumbers.
3610 void CMergeEditView::OnEditCopyLineNumbers()
3618 CMergeDoc *pDoc = GetDocument();
3619 GetSelection(ptStart, ptEnd);
3621 // Get last selected line (having widest linenumber)
3622 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3623 size_t nNumWidth = strutils::to_str(line + 1).length();
3625 for (int i = ptStart.y; i <= ptEnd.y; i++)
3627 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3630 // We need to convert to real linenumbers
3631 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3633 // Insert spaces to align different width linenumbers (99, 100)
3634 strLine = GetLineText(i);
3635 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3638 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3639 strText += strNumLine;
3641 PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3644 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3646 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3650 * @brief Open active file with associated application.
3652 * First tries to open file using shell 'Edit' action, since that
3653 * action open scripts etc. to editor instead of running them. If
3654 * edit-action is not registered, 'Open' action is used.
3656 void CMergeEditView::OnOpenFile()
3658 CMergeDoc * pDoc = GetDocument();
3659 ASSERT(pDoc != nullptr);
3661 String sFileName = pDoc->m_filePaths[m_nThisPane];
3662 if (sFileName.empty())
3664 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3665 0, 0, SW_SHOWNORMAL);
3666 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3667 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3668 0, 0, SW_SHOWNORMAL);
3669 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3674 * @brief Open active file with app selection dialog
3676 void CMergeEditView::OnOpenFileWith()
3678 CMergeDoc * pDoc = GetDocument();
3679 ASSERT(pDoc != nullptr);
3681 String sFileName = pDoc->m_filePaths[m_nThisPane];
3682 if (sFileName.empty())
3686 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3688 sysdir.ReleaseBuffer();
3689 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3690 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3691 sysdir, SW_SHOWNORMAL);
3695 * @brief Open active file with external editor
3697 void CMergeEditView::OnOpenFileWithEditor()
3699 CMergeDoc * pDoc = GetDocument();
3700 ASSERT(pDoc != nullptr);
3702 String sFileName = pDoc->m_filePaths[m_nThisPane];
3703 if (sFileName.empty())
3706 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3707 theApp.OpenFileToExternalEditor(sFileName, nRealLine);
3711 * @brief Force repaint of the location pane.
3713 void CMergeEditView::RepaintLocationPane()
3715 // Must force recalculation due to caching of data in location pane.
3716 CLocationView *pLocationView = GetDocument()->GetLocationView();
3717 if (pLocationView != nullptr)
3718 pLocationView->ForceRecalculate();
3722 * @brief Enables/disables linediff (different color for diffs)
3724 void CMergeEditView::OnViewLineDiffs()
3726 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3727 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3729 // Call CMergeDoc RefreshOptions() to refresh *both* views
3730 CMergeDoc *pDoc = GetDocument();
3731 pDoc->RefreshOptions();
3732 pDoc->FlushAndRescan(true);
3735 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3737 pCmdUI->Enable(true);
3738 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3742 * @brief Enables/disables line number
3744 void CMergeEditView::OnViewLineNumbers()
3746 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3748 // Call CMergeDoc RefreshOptions() to refresh *both* views
3749 CMergeDoc *pDoc = GetDocument();
3750 pDoc->RefreshOptions();
3753 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3755 pCmdUI->Enable(true);
3756 pCmdUI->SetCheck(GetViewLineNumbers());
3760 * @brief Enables/disables word wrap
3762 void CMergeEditView::OnViewWordWrap()
3764 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3766 // Call CMergeDoc RefreshOptions() to refresh *both* views
3767 CMergeDoc *pDoc = GetDocument();
3768 pDoc->RefreshOptions();
3769 pDoc->UpdateAllViews(this);
3774 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3776 pCmdUI->Enable(true);
3777 pCmdUI->SetCheck(m_bWordWrap);
3780 void CMergeEditView::OnViewWhitespace()
3782 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3784 // Call CMergeDoc RefreshOptions() to refresh *both* views
3785 CMergeDoc *pDoc = GetDocument();
3786 pDoc->RefreshOptions();
3789 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3791 pCmdUI->SetCheck(GetViewTabs());
3794 void CMergeEditView::OnViewEOL()
3796 GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
3797 GetDocument()->RefreshOptions();
3800 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI)
3802 pCmdUI->SetCheck(GetViewEols());
3805 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
3807 if (!IsInitialized())
3810 CMergeDoc * pDoc = GetDocument();
3811 if (m_nThisPane < pDoc->m_nBuffers - 1)
3813 // To calculate subline index correctly
3814 // we have to invalidate line cache in all pane before calling the function related the subline.
3815 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3817 CMergeEditView *pView = GetGroupView(nPane);
3818 if (pView != nullptr)
3819 pView->InvalidateScreenRect(false);
3824 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3826 CMergeEditView *pView = GetGroupView(nPane);
3827 if (pView != nullptr)
3828 pView->Invalidate();
3831 // recalculate m_nTopSubLine
3832 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3836 RecalcVertScrollBar (false, false);
3837 RecalcHorzScrollBar (false, false);
3841 * @brief allocates GDI resources for printing
3842 * @param pDC [in] points to the printer device context
3843 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3845 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3847 GetParentFrame()->PostMessage(WM_TIMER);
3849 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3851 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3852 pView->m_bPrintHeader = true;
3853 pView->m_bPrintFooter = true;
3854 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3859 * @brief frees GDI resources for printing
3860 * @param pDC [in] points to the printer device context
3861 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3863 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3865 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3866 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3868 GetParentFrame()->PostMessage(WM_TIMER);
3872 * @brief Gets header text to print
3873 * @param [in] nPageNum the page number to print
3874 * @param [out] header text to print
3876 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3878 text = GetDocument()->GetTitle();
3882 * @brief Prints header
3883 * @param [in] nPageNum the page number to print
3885 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3887 if (m_nThisPane > 0)
3889 int oldRight = m_rcPrintArea.right;
3890 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3891 CGhostTextView::PrintHeader(pdc, nPageNum);
3892 m_rcPrintArea.right = oldRight;
3896 * @brief Prints footer
3897 * @param [in] nPageNum the page number to print
3899 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3901 if (m_nThisPane > 0)
3903 int oldRight = m_rcPrintArea.right;
3904 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3905 CGhostTextView::PrintFooter(pdc, nPageNum);
3906 m_rcPrintArea.right = oldRight;
3909 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3911 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3912 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3916 * @brief Prints or previews both panes.
3917 * @param pDC [in] points to the printer device context
3918 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3920 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
3922 CRect rDraw = pInfo->m_rectDraw;
3923 CSize sz = rDraw.Size();
3924 CMergeDoc *pDoc = GetDocument();
3926 SIZE szLeftTop, szRightBottom;
3927 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3928 pDC->HIMETRICtoLP(&szLeftTop);
3929 pDC->HIMETRICtoLP(&szRightBottom);
3931 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3934 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3936 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3937 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3938 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3939 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3943 bool CMergeEditView::IsInitialized() const
3945 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3946 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3947 return pBuffer->IsInitialized();
3951 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3953 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3955 int nBreaks[3] = {0};
3956 int nMaxBreaks = -1;
3957 CMergeDoc * pDoc = GetDocument();
3958 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3960 CMergeEditView *pView = GetGroupView(nPane);
3961 if (pView != nullptr)
3963 if (nLineIndex >= pView->GetLineCount())
3965 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3967 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
3970 if (nBreaks[m_nThisPane] < nMaxBreaks)
3971 return nMaxBreaks - nBreaks[m_nThisPane];
3977 * @brief Invalidate sub line index cache from the specified index to the end of file.
3978 * @param [in] nLineIndex Index of the first line to invalidate
3980 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3982 CMergeDoc * pDoc = GetDocument();
3983 ASSERT(pDoc != nullptr);
3985 // We have to invalidate sub line index cache on both panes.
3986 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3988 CMergeEditView *pView = GetGroupView(nPane);
3989 if (pView != nullptr)
3990 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
3994 void CMergeEditView::SetWordWrapping( bool bWordWrap )
3996 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3997 GetGroupView(pane)->m_bWordWrap = bWordWrap;
3998 CCrystalTextView::SetWordWrapping(bWordWrap);
4002 * @brief Swap the positions of the two panes
4004 void CMergeEditView::OnViewSwapPanes()
4006 GetDocument()->SwapFiles();
4010 * @brief Determine if difference is visible on screen.
4011 * @param [in] nDiff Number of diff to check.
4012 * @return true if difference is visible.
4014 bool CMergeEditView::IsDiffVisible(int nDiff)
4016 const CMergeDoc *pd = GetDocument();
4019 pd->m_diffList.GetDiff(nDiff, diff);
4021 return IsDiffVisible(diff);
4025 * @brief Determine if difference is visible on screen.
4026 * @param [in] diff diff to check.
4027 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
4028 * @return true if difference is visible, false otherwise.
4030 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
4032 const int nDiffStart = GetSubLineIndex(diff.dbegin);
4033 const int nDiffEnd = GetSubLineIndex(diff.dend);
4034 // Diff's height is last line - first line + last line's line count
4035 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
4037 // If diff first line outside current view - context OR
4038 // if diff last line outside current view - context OR
4039 // if diff is bigger than screen
4040 if ((nDiffStart < m_nTopSubLine) ||
4041 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
4042 (nDiffHeight >= GetScreenLines()))
4052 /** @brief Open help from mainframe when user presses F1*/
4053 void CMergeEditView::OnHelp()
4055 theApp.ShowHelp(MergeViewHelpLocation);
4059 * @brief Called after document is loaded.
4060 * This function is called from CMergeDoc::OpenDocs() after documents are
4061 * loaded. So this is good place to set View's options etc.
4063 void CMergeEditView::DocumentsLoaded()
4065 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
4068 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
4073 SetTopMargin(false);
4076 // Enable/disable automatic rescan (rescanning after edit)
4077 EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
4079 // SetTextType will revert to language dependent defaults for tab
4080 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
4081 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
4082 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
4083 GetDocument()->IsMixedEOL(m_nThisPane);
4084 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
4085 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
4086 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
4087 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4089 // Enable Backspace at beginning of line
4090 SetDisableBSAtSOL(false);
4092 // Set tab type (tabs/spaces)
4093 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
4094 SetInsertTabs(bInsertTabs);
4096 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
4097 // disabled) after docs are open in screen. So lets make sure they are
4098 // really updated, even though this is unnecessary in most cases.
4099 RecalcHorzScrollBar();
4100 RecalcVertScrollBar();
4104 * @brief Update LocationView position.
4105 * This function updates LocationView position to given lines.
4106 * Usually we want to lines in file compare view and area in
4107 * LocationView to match. Be extra carefull to not call non-existing
4109 * @param [in] nTopLine Top line of current view.
4110 * @param [in] nBottomLine Bottom line of current view.
4112 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
4113 int nBottomLine /*= -1*/)
4115 CMergeDoc *pDoc = GetDocument();
4116 if (pDoc == nullptr)
4119 CLocationView *pLocationView = pDoc->GetLocationView();
4121 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
4123 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
4128 * @brief Enable/Disable view's selection margins.
4129 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
4130 * useful. But it appears many users don't use/need those features and for them
4131 * selection margins are just wasted screen estate.
4133 void CMergeEditView::OnViewMargin()
4135 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
4136 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
4138 SetSelectionMargin(!bViewMargin);
4139 CMergeDoc *pDoc = GetDocument();
4140 pDoc->RefreshOptions();
4141 pDoc->UpdateAllViews(this);
4145 * @brief Update GUI for Enable/Disable view's selection margin.
4146 * @param [in] pCmdUI Pointer to UI item to update.
4148 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
4150 pCmdUI->Enable(true);
4151 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4155 * @brief Create the "Change Scheme" sub menu.
4156 * @param [in] pCmdUI Pointer to UI item to update.
4158 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4160 // Delete the place holder menu.
4161 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4163 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4165 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4166 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4167 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
4169 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
4171 name = theApp.LoadString(i);
4172 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
4175 pCmdUI->Enable(true);
4179 * @brief Change the editor's syntax highlighting scheme.
4180 * @param [in] nID Selected color scheme sub menu id.
4182 void CMergeEditView::OnChangeScheme(UINT nID)
4184 CMergeDoc *pDoc = GetDocument();
4185 ASSERT(pDoc != nullptr);
4187 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4189 CMergeEditView *pView = GetGroupView(nPane);
4190 ASSERT(pView != nullptr);
4192 if (pView != nullptr)
4194 pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
4195 pView->SetDisableBSAtSOL(false);
4203 * @brief Enable all color schemes sub menu items.
4204 * @param [in] pCmdUI Pointer to UI item to update.
4206 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4208 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4209 pCmdUI->SetRadio(bIsCurrentScheme);
4210 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4214 * @brief Called when mouse's wheel is scrolled.
4216 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4218 if ( nFlags == MK_CONTROL )
4220 short amount = zDelta < 0 ? -1: 1;
4223 // no default CCrystalTextView
4224 return CView::OnMouseWheel(nFlags, zDelta, pt);
4227 if (nFlags == MK_SHIFT)
4229 SCROLLINFO si = { sizeof SCROLLINFO };
4230 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4232 VERIFY(GetScrollInfo(SB_HORZ, &si));
4235 si.nPos -= zDelta / 40;
4236 if (si.nPos > si.nMax) si.nPos = si.nMax;
4237 if (si.nPos < si.nMin) si.nPos = si.nMin;
4239 SetScrollInfo(SB_HORZ, &si);
4242 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4244 // no default CCrystalTextView
4245 return CView::OnMouseWheel(nFlags, zDelta, pt);
4248 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4252 * @brief Called when mouse's horizontal wheel is scrolled.
4254 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4256 SCROLLINFO si = { sizeof SCROLLINFO };
4257 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4259 VERIFY(GetScrollInfo(SB_HORZ, &si));
4262 si.nPos += zDelta / 40;
4263 if (si.nPos > si.nMax) si.nPos = si.nMax;
4264 if (si.nPos < si.nMin) si.nPos = si.nMin;
4266 SetScrollInfo(SB_HORZ, &si);
4269 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4271 // no default CCrystalTextView
4272 CView::OnMouseHWheel(nFlags, zDelta, pt);
4276 * @brief Change font size (zoom) in views.
4277 * @param [in] amount Amount of change/zoom, negative number makes
4278 * font smaller, positive number bigger and 0 reset the font size.
4280 void CMergeEditView::ZoomText(short amount)
4285 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4286 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4290 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4293 nPointSize += amount;
4297 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4299 CMergeDoc *pDoc = GetDocument();
4300 ASSERT(pDoc != nullptr);
4302 if (pDoc != nullptr)
4304 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4306 CMergeEditView *pView = GetGroupView(nPane);
4307 ASSERT(pView != nullptr);
4309 if (pView != nullptr)
4317 bool CMergeEditView::QueryEditable()
4319 return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4323 * @brief Adjust the point to remain in the displayed diff
4325 * @return Tells if the point has been changed
4327 bool CMergeEditView::EnsureInDiff(CPoint& pt)
4329 int nLineCount = GetLineCount();
4330 if (m_lineBegin >= nLineCount)
4331 m_lineBegin = nLineCount - 1;
4332 if (m_lineEnd >= nLineCount)
4333 m_lineEnd = nLineCount - 1;
4335 int diffLength = m_lineEnd - m_lineBegin + 1;
4336 // first get the degenerate case out of the way
4338 if (diffLength == 0)
4340 if (pt.y == m_lineBegin && pt.x == 0)
4348 if (pt.y < m_lineBegin)
4354 // diff is defined and not below diff
4355 if (m_lineEnd > -1 && pt.y > m_lineEnd)
4358 pt.x = GetLineLength(pt.y);
4364 void CMergeEditView::EnsureVisible(CPoint pt)
4369 // ensure we remain in diff
4370 if (EnsureInDiff(ptNew))
4371 SetCursorPos(ptNew);
4373 CCrystalTextView::EnsureVisible(ptNew);
4376 void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
4378 CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4381 void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
4383 CPoint ptStartNew = ptStart;
4384 CPoint ptEndNew = ptEnd;
4387 // ensure we remain in diff
4388 EnsureInDiff(ptStartNew);
4389 EnsureInDiff(ptEndNew);
4391 CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4394 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4398 int nLineCount = GetLineCount();
4399 if (m_lineBegin >= nLineCount)
4400 m_lineBegin = nLineCount - 1;
4401 if (m_lineEnd >= nLineCount)
4402 m_lineEnd = nLineCount - 1;
4404 // ensure we remain in diff
4405 int sublineBegin = GetSubLineIndex(m_lineBegin);
4406 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4407 int diffLength = sublineEnd - sublineBegin + 1;
4408 int displayLength = GetScreenLines();
4409 if (diffLength <= displayLength)
4410 nNewTopLine = sublineBegin;
4413 if (nNewTopLine < sublineBegin)
4414 nNewTopLine = sublineBegin;
4415 if (nNewTopLine + displayLength - 1 > sublineEnd)
4416 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4419 CPoint pt = GetCursorPos();
4420 if (EnsureInDiff(pt))
4423 CPoint ptSelStart, ptSelEnd;
4424 GetSelection(ptSelStart, ptSelEnd);
4425 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4426 SetSelection(ptSelStart, ptSelEnd);
4428 CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4431 void CMergeEditView::SetActivePane()
4433 auto* pwndSplitterChild = GetParentSplitter(this, false);
4434 if (!pwndSplitterChild)
4436 if (pwndSplitterChild->GetColumnCount() > 1)
4437 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4439 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4443 * @brief Called when user selects View/Zoom In from menu.
4445 void CMergeEditView::OnViewZoomIn()
4451 * @brief Called when user selects View/Zoom Out from menu.
4453 void CMergeEditView::OnViewZoomOut()
4459 * @brief Called when user selects View/Zoom Normal from menu.
4461 void CMergeEditView::OnViewZoomNormal()
4466 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4468 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4470 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4474 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4477 void CMergeEditView::OnWindowSplit()
4480 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4481 CMergeDoc *pDoc = GetDocument();
4482 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4483 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4484 int nBuffer = m_nThisPane;
4485 if (pDoc->m_nGroups <= 2)
4487 wndSplitter.SplitRow(1);
4488 wndSplitter.EqualizeRows();
4492 wndSplitter.SetActivePane(0, 0);
4493 wndSplitter.DeleteRow(1);
4494 pDoc->GetView(0, nBuffer)->SetActivePane();
4498 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4500 pCmdUI->Enable(!m_bDetailView);
4501 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4504 void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
4507 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4508 const int pane = pNMItemActivate->iItem / 4;
4510 switch (pNMItemActivate->iItem % 4)
4513 GetDocument()->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4516 GetDocument()->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
4521 ::GetCursorPos(&point);
4524 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4525 theApp.TranslateMenu(menu.m_hMenu);
4526 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4530 GetDocument()->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());