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)
71 , m_bChangedSchemeManually(false)
73 SetParser(&m_xParser);
75 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
78 CMergeEditView::~CMergeEditView()
83 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
84 //{{AFX_MSG_MAP(CMergeEditView)
85 ON_COMMAND(ID_CURDIFF, OnCurdiff)
86 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
87 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
88 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
89 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
90 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
91 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
92 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
93 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
94 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
95 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
96 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
97 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
98 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
99 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
100 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
101 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
102 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
103 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
104 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
105 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
106 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
107 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
108 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
109 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
110 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
111 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
112 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
113 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
114 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
115 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
116 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
117 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
118 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
119 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
120 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
121 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
122 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
123 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
124 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
125 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
126 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
127 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
128 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
129 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
130 ON_WM_LBUTTONDBLCLK()
133 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
134 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
135 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
136 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
137 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
138 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
139 ON_COMMAND(ID_L2R, OnL2r)
140 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
141 ON_COMMAND(ID_LINES_L2R, OnLinesL2r)
142 ON_UPDATE_COMMAND_UI(ID_LINES_L2R, OnUpdateLinesL2r)
143 ON_COMMAND(ID_R2L, OnR2l)
144 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
145 ON_COMMAND(ID_LINES_R2L, OnLinesR2l)
146 ON_UPDATE_COMMAND_UI(ID_LINES_R2L, OnUpdateLinesR2l)
147 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
148 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
149 ON_COMMAND(ID_COPY_LINES_FROM_LEFT, OnCopyLinesFromLeft)
150 ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_LEFT, OnUpdateCopyLinesFromLeft)
151 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
152 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
153 ON_COMMAND(ID_COPY_LINES_FROM_RIGHT, OnCopyLinesFromRight)
154 ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_RIGHT, OnUpdateCopyLinesFromRight)
155 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
156 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
157 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
158 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
159 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
160 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
162 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
163 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
164 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
165 ON_COMMAND(ID_REFRESH, OnRefresh)
166 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
167 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
168 ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
169 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
170 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
171 ON_COMMAND(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnAddToSubstitutionFilters)
172 ON_UPDATE_COMMAND_UI(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnUpdateAddToSubstitutionFilters)
174 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
175 ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
176 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
177 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
178 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
179 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
180 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
181 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
182 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
183 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
184 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
185 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
186 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
187 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
188 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
189 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
190 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
191 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
192 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
193 ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
194 ON_COMMAND(ID_NEXT_PANE, OnChangePane)
195 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
196 ON_COMMAND(ID_GOTO_MOVED_LINE_LM, OnGotoMovedLineLM)
197 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_LM, OnUpdateGotoMovedLineLM)
198 ON_COMMAND(ID_GOTO_MOVED_LINE_MR, OnGotoMovedLineMR)
199 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_MR, OnUpdateGotoMovedLineMR)
200 ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
201 ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
202 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
203 ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
204 ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
205 ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
206 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
209 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
210 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
211 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
212 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
213 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
214 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
215 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
216 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
217 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
218 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
219 ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
220 ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
221 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
222 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
223 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
224 ON_COMMAND(ID_SWAPPANES_SWAP12, OnViewSwapPanes12)
225 ON_COMMAND(ID_SWAPPANES_SWAP23, OnViewSwapPanes23)
226 ON_COMMAND(ID_SWAPPANES_SWAP13, OnViewSwapPanes13)
227 ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
230 ON_COMMAND(ID_HELP, OnHelp)
231 ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
232 ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
233 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
234 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
235 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
238 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
239 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
240 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
241 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
242 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
243 ON_COMMAND(ID_FIRSTFILE, OnFirstFile)
244 ON_UPDATE_COMMAND_UI(ID_FIRSTFILE, OnUpdateFirstFile)
245 ON_COMMAND(ID_PREVFILE, OnPrevFile)
246 ON_UPDATE_COMMAND_UI(ID_PREVFILE, OnUpdatePrevFile)
247 ON_COMMAND(ID_NEXTFILE, OnNextFile)
248 ON_UPDATE_COMMAND_UI(ID_NEXTFILE, OnUpdateNextFile)
249 ON_COMMAND(ID_LASTFILE, OnLastFile)
250 ON_UPDATE_COMMAND_UI(ID_LASTFILE, OnUpdateLastFile)
251 ON_NOTIFY(NM_DBLCLK, AFX_IDW_STATUS_BAR, OnStatusBarDblClick)
256 /////////////////////////////////////////////////////////////////////////////
257 // CMergeEditView diagnostics
260 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
262 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
263 return (CMergeDoc*)m_pDocument;
268 /////////////////////////////////////////////////////////////////////////////
269 // CMergeEditView message handlers
272 * @brief Return text buffer for file in view
274 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
276 return GetDocument()->m_ptBuf[m_nThisPane].get();
280 * @brief Update any resources necessary after a GUI language change
282 void CMergeEditView::UpdateResources()
286 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
288 return GetDocument()->GetView(m_nThisGroup, nBuffer);
291 void CMergeEditView::PrimeListWithFile()
293 // Set the tab size now, just in case the options change...
294 // We don't update it at the end of OnOptions,
295 // we can update it safely now
296 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
299 * @brief Return text from line given
301 CString CMergeEditView::GetLineText(int idx)
303 return GetLineChars(idx);
307 * @brief Return text from selection
309 CString CMergeEditView::GetSelectedText()
311 CPoint ptStart, ptEnd;
313 GetSelection(ptStart, ptEnd);
314 if (ptStart != ptEnd)
315 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
320 * @brief Get diffs inside selection.
321 * @param [out] firstDiff First diff inside selection
322 * @param [out] lastDiff Last diff inside selection
323 * @note -1 is returned in parameters if diffs cannot be determined
324 * @todo This shouldn't be called when there is no diffs, so replace
325 * first 'if' with ASSERT()?
327 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
332 CMergeDoc *pd = GetDocument();
333 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
337 int firstLine, lastLine;
338 GetFullySelectedLines(firstLine, lastLine);
339 if (lastLine < firstLine)
342 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
343 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
344 if (firstDiff != -1 && lastDiff != -1)
348 // Check that first selected line is first diff's first line or above it
349 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
350 if ((int)di.dbegin < firstLine)
352 if (firstDiff < lastDiff)
356 // Check that last selected line is last diff's last line or below it
357 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
358 if ((int)di.dend > lastLine)
360 if (firstDiff < lastDiff)
364 // Special case: one-line diff is not selected if cursor is in it
365 if (firstLine == lastLine)
374 * @brief Get diffs inside selection.
375 * @param [out] firstDiff First diff inside selection
376 * @param [out] lastDiff Last diff inside selection
377 * @param [out] firstWordDiff First word level diff inside selection
378 * @param [out] lastWordDiff Last word level diff inside selection
379 * @note -1 is returned in parameters if diffs cannot be determined
380 * @todo This shouldn't be called when there is no diffs, so replace
381 * first 'if' with ASSERT()?
383 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
390 CMergeDoc *pd = GetDocument();
391 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
395 int firstLine, lastLine;
396 CPoint ptStart, ptEnd;
397 GetSelection(ptStart, ptEnd);
398 if (pptStart != nullptr)
400 if (pptEnd != nullptr)
402 firstLine = ptStart.y;
405 firstDiff = pd->m_diffList.LineToDiff(firstLine);
406 bool firstLineIsNotInDiff = firstDiff == -1;
409 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
414 lastDiff = pd->m_diffList.LineToDiff(lastLine);
415 bool lastLineIsNotInDiff = lastDiff == -1;
417 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
418 if (lastDiff < firstDiff)
425 if (firstDiff != -1 && lastDiff != -1)
429 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
431 firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
433 else if (ptStart != ptEnd)
435 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
436 if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
442 if (firstWordDiff == -1)
444 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
445 for (size_t i = 0; i < worddiffs.size(); ++i)
447 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
448 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
449 (firstLine == worddiffs[i].endline[m_nThisPane] &&
450 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
452 firstWordDiff = static_cast<int>(i);
457 if (firstLine >= di.dbegin && firstWordDiff == -1)
464 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
465 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
466 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
468 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
469 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
471 lastWordDiff = static_cast<int>(i);
476 if (lastLine <= di.dend && lastWordDiff == -1)
479 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
486 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
503 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
504 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
505 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
508 void CMergeEditView::GetSelectedDiffs(int & firstDiff, int & lastDiff)
513 CMergeDoc *pd = GetDocument();
514 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
518 int firstLine, lastLine;
519 CPoint ptStart, ptEnd;
520 GetSelection(ptStart, ptEnd);
521 firstLine = ptStart.y;
524 firstDiff = pd->m_diffList.LineToDiff(firstLine);
527 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
531 lastDiff = pd->m_diffList.LineToDiff(lastLine);
533 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
534 if (lastDiff < firstDiff)
540 ASSERT(firstDiff == -1 ? (lastDiff == -1) : true);
541 ASSERT(lastDiff == -1 ? (firstDiff == -1) : true);
544 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
546 CMergeDoc *pDoc = GetDocument();
547 std::map<int, std::vector<int>> ret;
548 std::map<int, std::vector<int> *> list;
549 CPoint ptStart, ptEnd;
550 GetSelection(ptStart, ptEnd);
551 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
553 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
555 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
557 GetColumnSelection(nLine, nLeft, nRight);
558 CPoint ptStart2, ptEnd2;
561 ptStart2.y = ptEnd2.y = nLine;
562 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
563 if (firstDiff != -1 && lastDiff != -1)
565 std::vector<int> *pWordDiffs;
566 if (list.find(firstDiff) == list.end())
567 list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
568 pWordDiffs = list[firstDiff];
569 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
571 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
572 pWordDiffs->push_back(i);
577 for (auto& it : list)
578 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
582 void CMergeEditView::OnInitialUpdate()
585 CCrystalEditViewEx::OnInitialUpdate();
587 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
588 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
594 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
596 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
598 CMergeDoc* pDoc = GetDocument();
599 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
602 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
606 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
607 return std::vector<CrystalLineParser::TEXTBLOCK>();
609 return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
612 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
614 static const std::vector<TEXTBLOCK> emptyBlocks;
617 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
621 DWORD dwLineFlags = GetLineFlags(nLineIndex);
622 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
625 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
628 CMergeDoc *pDoc = GetDocument();
629 if (pDoc->IsEditedAfterRescan(m_nThisPane))
632 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
637 pDoc->m_diffList.GetDiff(nDiff, cd);
638 int unemptyLineCount = 0;
639 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
641 if (cd.begin[nPane] != cd.end[nPane] + 1)
644 if (unemptyLineCount < 2)
647 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
648 size_t nWordDiffs = worddiffs.size();
650 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
652 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
653 blocks[0].m_nCharPos = 0;
654 blocks[0].m_nColorIndex = COLORINDEX_NONE;
655 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
657 for (i = 0, j = 1; i < nWordDiffs; i++)
659 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
661 if (pDoc->m_nBuffers > 2)
663 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
665 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
668 int begin[3], end[3];
669 bool deleted = false;
670 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
672 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
673 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
674 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
675 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
678 blocks[j].m_nCharPos = begin[m_nThisPane];
679 if (lineInCurrentDiff)
681 if (m_cachedColors.clrSelDiffText != CLR_NONE)
682 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
684 blocks[j].m_nColorIndex = COLORINDEX_NONE;
685 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
686 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
690 if (m_cachedColors.clrDiffText != CLR_NONE)
691 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
693 blocks[j].m_nColorIndex = COLORINDEX_NONE;
694 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
695 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
698 blocks[j].m_nCharPos = end[m_nThisPane];
699 blocks[j].m_nColorIndex = COLORINDEX_NONE;
700 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
709 COLORREF CMergeEditView::GetColor(int nColorIndex)
711 switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
713 case COLORINDEX_HIGHLIGHTBKGND1:
714 return m_cachedColors.clrSelWordDiff;
715 case COLORINDEX_HIGHLIGHTTEXT1:
716 return m_cachedColors.clrSelWordDiffText;
717 case COLORINDEX_HIGHLIGHTBKGND2:
718 return m_cachedColors.clrWordDiff;
719 case COLORINDEX_HIGHLIGHTTEXT2:
720 return m_cachedColors.clrWordDiffText;
721 case COLORINDEX_HIGHLIGHTBKGND3:
722 return m_cachedColors.clrWordDiffDeleted;
723 case COLORINDEX_HIGHLIGHTBKGND4:
724 return m_cachedColors.clrSelWordDiffDeleted;
727 return CCrystalTextView::GetColor(nColorIndex);
732 * @brief Determine text and background color for line
733 * @param [in] nLineIndex Index of line in view (NOT line in file)
734 * @param [out] crBkgnd Backround color for line
735 * @param [out] crText Text color for line
737 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
738 COLORREF & crText, bool & bDrawWhitespace)
740 DWORD ignoreFlags = 0;
741 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
745 * @brief Determine text and background color for line
746 * @param [in] nLineIndex Index of line in view (NOT line in file)
747 * @param [in] ignoreFlags Flags that caller wishes ignored
748 * @param [out] crBkgnd Backround color for line
749 * @param [out] crText Text color for line
751 * This version allows caller to suppress particular flags
753 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
754 COLORREF & crText, bool & bDrawWhitespace)
756 if (GetLineCount() <= nLineIndex)
759 DWORD dwLineFlags = GetLineFlags(nLineIndex);
761 if (dwLineFlags & ignoreFlags)
762 dwLineFlags &= (~ignoreFlags);
766 // Line with WinMerge flag,
767 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
768 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
770 crText = m_cachedColors.clrDiffText;
771 bDrawWhitespace = true;
773 if (dwLineFlags & LF_GHOST)
775 crBkgnd = m_cachedColors.clrDiffDeleted;
780 // If no syntax hilighting
781 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
783 crBkgnd = GetColor (COLORINDEX_BKGND);
784 crText = GetColor (COLORINDEX_NORMALTEXT);
785 bDrawWhitespace = false;
788 // Line not inside diff, get colors from CrystalEditor
789 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
790 crText, bDrawWhitespace);
792 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
794 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
795 crText = GetColor (COLORINDEX_WHITESPACE);
796 bDrawWhitespace = false;
802 if (dwLineFlags & LF_WINMERGE_FLAGS)
804 crText = m_cachedColors.clrDiffText;
805 bDrawWhitespace = true;
806 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
808 if (dwLineFlags & LF_SNP)
810 if (lineInCurrentDiff)
812 if (dwLineFlags & LF_GHOST)
813 crBkgnd = m_cachedColors.clrSelSNPDeleted;
815 crBkgnd = m_cachedColors.clrSelSNP;
816 crText = m_cachedColors.clrSelSNPText;
820 if (dwLineFlags & LF_GHOST)
821 crBkgnd = m_cachedColors.clrSNPDeleted;
823 crBkgnd = m_cachedColors.clrSNP;
824 crText = m_cachedColors.clrSNPText;
828 else if (dwLineFlags & LF_DIFF)
830 if (lineInCurrentDiff)
832 if (dwLineFlags & LF_MOVED)
834 crBkgnd = m_cachedColors.clrSelMoved;
835 crText = m_cachedColors.clrSelMovedText;
839 crBkgnd = m_cachedColors.clrSelDiff;
840 crText = m_cachedColors.clrSelDiffText;
846 if (dwLineFlags & LF_MOVED)
848 crBkgnd = m_cachedColors.clrMoved;
849 crText = m_cachedColors.clrMovedText;
853 crBkgnd = m_cachedColors.clrDiff;
854 crText = m_cachedColors.clrDiffText;
859 else if (dwLineFlags & LF_TRIVIAL)
861 // trivial diff can not be selected
862 if (dwLineFlags & LF_GHOST)
863 // ghost lines in trivial diff has their own color
864 crBkgnd = m_cachedColors.clrTrivialDeleted;
866 crBkgnd = m_cachedColors.clrTrivial;
867 crText = m_cachedColors.clrTrivialText;
870 else if (dwLineFlags & LF_GHOST)
872 if (lineInCurrentDiff)
874 if (dwLineFlags & LF_MOVED)
875 crBkgnd = m_cachedColors.clrSelMovedDeleted;
877 crBkgnd = m_cachedColors.clrSelDiffDeleted;
881 if (dwLineFlags & LF_MOVED)
882 crBkgnd = m_cachedColors.clrMovedDeleted;
884 crBkgnd = m_cachedColors.clrDiffDeleted;
891 // Line not inside diff,
892 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
894 // If no syntax hilighting, get windows default colors
895 crBkgnd = GetColor (COLORINDEX_BKGND);
896 crText = GetColor (COLORINDEX_NORMALTEXT);
897 bDrawWhitespace = false;
900 // Syntax highlighting, get colors from CrystalEditor
901 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
902 crText, bDrawWhitespace);
907 * @brief Sync other pane position
909 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
911 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
912 if (pSplitterWnd != nullptr)
914 // See CSplitterWnd::IdFromRowCol() implementation for details
915 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
916 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
917 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
918 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
920 // limit the TopLine : must be smaller than GetLineCount for all the panels
921 int newTopSubLine = m_nTopSubLine;
922 int nRows = pSplitterWnd->GetRowCount ();
923 int nCols = pSplitterWnd->GetColumnCount ();
925 // for (nRow = 0; nRow < nRows; nRow++)
927 // for (int nCol = 0; nCol < nCols; nCol++)
929 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
930 // if (pSiblingView != nullptr)
931 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
932 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
935 if (m_nTopSubLine != newTopSubLine)
936 ScrollToSubLine(newTopSubLine);
938 for (nRow = 0; nRow < nRows; nRow++)
940 for (int nCol = 0; nCol < nCols; nCol++)
942 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
944 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
945 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
946 pSiblingView->OnUpdateSibling (this, bHorz);
954 * @brief Update other panes
956 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
958 if (pUpdateSource != this)
960 ASSERT (pUpdateSource != nullptr);
961 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
962 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
963 if (!bHorz) // changed this so bHorz works right
965 ASSERT (pSrcView->m_nTopSubLine >= 0);
967 // This ASSERT is wrong: panes have different files and
968 // different linecounts
969 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
970 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
972 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
974 RecalcVertScrollBar(true);
975 RecalcHorzScrollBar();
980 ASSERT (pSrcView->m_nOffsetChar >= 0);
982 // This ASSERT is wrong: panes have different files and
983 // different linelengths
984 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
985 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
987 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
989 RecalcHorzScrollBar(true);
990 RecalcHorzScrollBar();
996 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
998 int newlineBegin, newlineEnd;
999 CMergeDoc *pd = GetDocument();
1000 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
1008 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
1010 newlineBegin = curDiff.dbegin;
1011 ASSERT (newlineBegin >= 0);
1012 newlineEnd = curDiff.dend;
1015 m_lineBegin = newlineBegin;
1016 m_lineEnd = newlineEnd;
1018 int nLineCount = GetLineCount();
1019 if (m_lineBegin > nLineCount)
1020 m_lineBegin = nLineCount - 1;
1021 if (m_lineEnd > nLineCount)
1022 m_lineEnd = nLineCount - 1;
1024 if (m_nTopLine == newlineBegin)
1027 // scroll to the first line of the diff
1028 vector<WordDiff> worddiffs;
1029 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
1030 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
1031 CPoint pt = worddiffs.size() > 0 ?
1032 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
1033 CPoint{ 0, m_lineBegin };
1034 ScrollToLine(m_lineBegin);
1038 // update the width of the horizontal scrollbar
1039 RecalcHorzScrollBar();
1043 * @brief Selects diff by number and syncs other file
1044 * @param [in] nDiff Diff to select, must be >= 0
1045 * @param [in] bScroll Scroll diff to view
1046 * @param [in] bSelectText Select diff text
1047 * @sa CMergeEditView::ShowDiff()
1048 * @sa CMergeDoc::SetCurrentDiff()
1049 * @todo Parameter bSelectText is never used?
1051 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
1053 CMergeDoc *pd = GetDocument();
1055 // Check that nDiff is valid
1057 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
1058 if (nDiff >= pd->m_diffList.GetSize())
1059 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
1060 nDiff, pd->m_diffList.GetSize());
1063 pd->SetCurrentDiff(nDiff);
1064 ShowDiff(bScroll, bSelectText);
1065 pd->UpdateAllViews(this);
1066 UpdateSiblingScrollPos(false);
1068 // notify either side, as it will notify the other one
1069 pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1072 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1074 CMergeDoc *pd = GetDocument();
1075 // If we have a selected diff, deselect it
1076 int nCurrentDiff = pd->GetCurrentDiff();
1077 if (nCurrentDiff != -1)
1079 CPoint pos = GetCursorPos();
1080 if (!IsLineInCurrentDiff(pos.y))
1082 pd->SetCurrentDiff(-1);
1084 pd->UpdateAllViews(this);
1090 * @brief Called when user selects "Current Difference".
1091 * Goes to active diff. If no active diff, selects diff under cursor
1092 * @sa CMergeEditView::SelectDiff()
1093 * @sa CMergeDoc::GetCurrentDiff()
1094 * @sa CMergeDoc::LineToDiff()
1096 void CMergeEditView::OnCurdiff()
1098 CMergeDoc *pd = GetDocument();
1100 // If no diffs, nothing to select
1101 if (!pd->m_diffList.HasSignificantDiffs())
1104 // GetCurrentDiff() returns -1 if no diff selected
1105 int nDiff = pd->GetCurrentDiff();
1108 // Scroll to the first line of the currently selected diff
1109 SelectDiff(nDiff, true, false);
1113 // If cursor is inside diff, select that diff
1114 CPoint pos = GetCursorPos();
1115 nDiff = pd->m_diffList.LineToDiff(pos.y);
1116 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1117 SelectDiff(nDiff, true, false);
1122 * @brief Called when "Current diff" item is updated
1124 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1126 CMergeDoc *pd = GetDocument();
1127 CPoint pos = GetCursorPos();
1128 int nCurrentDiff = pd->GetCurrentDiff();
1129 if (nCurrentDiff == -1)
1131 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1132 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1135 pCmdUI->Enable(true);
1139 * @brief Copy selected text to clipboard
1141 void CMergeEditView::OnEditCopy()
1143 CMergeDoc * pDoc = GetDocument();
1144 CPoint ptSelStart, ptSelEnd;
1145 GetSelection(ptSelStart, ptSelEnd);
1148 if (ptSelStart == ptSelEnd)
1153 if (!m_bRectangularSelection)
1155 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1157 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1158 ptSelEnd.y, ptSelEnd.x, text);
1161 GetTextWithoutEmptysInColumnSelection(text);
1163 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1167 * @brief Called when "Copy" item is updated
1169 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1171 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1175 * @brief Cut current selection to clipboard
1177 void CMergeEditView::OnEditCut()
1179 if (!QueryEditable())
1182 CPoint ptSelStart, ptSelEnd;
1183 CMergeDoc * pDoc = GetDocument();
1184 GetSelection(ptSelStart, ptSelEnd);
1187 if (ptSelStart == ptSelEnd)
1191 if (!m_bRectangularSelection)
1192 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1193 ptSelEnd.y, ptSelEnd.x, text);
1195 GetTextWithoutEmptysInColumnSelection(text);
1197 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1199 if (!m_bRectangularSelection)
1201 CPoint ptCursorPos = ptSelStart;
1202 ASSERT_VALIDTEXTPOS(ptCursorPos);
1203 SetAnchor(ptCursorPos);
1204 SetSelection(ptCursorPos, ptCursorPos);
1205 SetCursorPos(ptCursorPos);
1206 EnsureVisible(ptCursorPos);
1208 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1209 ptSelEnd.x, CE_ACTION_CUT);
1212 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1214 m_pTextBuffer->SetModified(true);
1218 * @brief Called when "Cut" item is updated
1220 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1222 if (QueryEditable())
1223 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1225 pCmdUI->Enable(false);
1229 * @brief Paste text from clipboard
1231 void CMergeEditView::OnEditPaste()
1233 if (!QueryEditable())
1236 CCrystalEditViewEx::Paste();
1237 m_pTextBuffer->SetModified(true);
1241 * @brief Called when "Paste" item is updated
1243 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1245 if (QueryEditable())
1246 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1248 pCmdUI->Enable(false);
1252 * @brief Undo last action
1254 void CMergeEditView::OnEditUndo()
1256 CWaitCursor waitstatus;
1257 CMergeDoc* pDoc = GetDocument();
1258 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1261 if (!QueryEditable())
1264 GetParentFrame()->SetActiveView(this, true);
1265 if(CCrystalEditViewEx::DoEditUndo())
1268 pDoc->UpdateHeaderPath(m_nThisPane);
1269 pDoc->FlushAndRescan();
1272 m_pTextBuffer->GetRedoActionCode(nAction);
1273 if (nAction == CE_ACTION_MERGE)
1274 // select the diff so we may just merge it again
1280 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1282 if (!pDoc->CanUndo())
1283 pDoc->SetAutoMerged(false);
1287 * @brief Called when "Undo" item is updated
1289 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1291 CMergeDoc* pDoc = GetDocument();
1292 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1294 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1295 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1298 pCmdUI->Enable(false);
1302 * @brief Go to first diff
1304 * Called when user selects "First Difference"
1305 * @sa CMergeEditView::SelectDiff()
1307 void CMergeEditView::OnFirstdiff()
1309 CMergeDoc *pd = GetDocument();
1310 if (pd->m_diffList.HasSignificantDiffs())
1312 int nDiff = pd->m_diffList.FirstSignificantDiff();
1313 SelectDiff(nDiff, true, false);
1318 * @brief Update "First diff" UI items
1320 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1322 OnUpdatePrevdiff(pCmdUI);
1326 * @brief Go to last diff
1328 void CMergeEditView::OnLastdiff()
1330 CMergeDoc *pd = GetDocument();
1331 if (pd->m_diffList.HasSignificantDiffs())
1333 int nDiff = pd->m_diffList.LastSignificantDiff();
1334 SelectDiff(nDiff, true, false);
1339 * @brief Update "Last diff" UI items
1341 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1343 OnUpdateNextdiff(pCmdUI);
1347 * @brief Go to next diff and select it.
1349 * Finds and selects next difference. There are several cases:
1350 * - if there is selected difference, and that difference is visible
1351 * on screen, next found difference is selected.
1352 * - if there is selected difference but it is not visible, next
1353 * difference from cursor position is selected. This is what user
1354 * expects to happen and is natural thing to do. Also reduces
1355 * needless scrolling.
1356 * - if there is no selected difference, next difference from cursor
1357 * position is selected.
1359 void CMergeEditView::OnNextdiff()
1361 CMergeDoc *pd = GetDocument();
1362 int cnt = pd->m_ptBuf[0]->GetLineCount();
1366 // Returns -1 if no diff selected
1368 int curDiff = pd->GetCurrentDiff();
1372 if (!IsDiffVisible(curDiff))
1374 // Selected difference not visible, select next from cursor
1375 int line = GetCursorPos().y;
1376 // Make sure we aren't in the first line of the diff
1378 if (!IsValidTextPosY(CPoint(0, line)))
1380 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1384 // Find out if there is a following significant diff
1385 if (curDiff < pd->m_diffList.GetSize() - 1)
1387 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1393 // We don't have a selected difference,
1394 // but cursor can be inside inactive diff
1395 int line = GetCursorPos().y;
1396 if (!IsValidTextPosY(CPoint(0, line)))
1398 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1401 int lastDiff = pd->m_diffList.LastSignificantDiff();
1402 if (nextDiff >= 0 && nextDiff <= lastDiff)
1403 SelectDiff(nextDiff, true, false);
1404 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1406 if (pDirDoc->MoveableToNextDiff())
1407 pDirDoc->MoveToNextDiff(pd);
1412 * @brief Update "Next diff" UI items
1414 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1416 CMergeDoc *pd = GetDocument();
1417 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1422 // There aren't any significant differences
1427 // Enable if the beginning of the last significant difference is after caret
1428 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1431 if (!enabled && pd->GetDirDoc())
1432 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1434 pCmdUI->Enable(enabled);
1438 * @brief Go to previous diff and select it.
1440 * Finds and selects previous difference. There are several cases:
1441 * - if there is selected difference, and that difference is visible
1442 * on screen, previous found difference is selected.
1443 * - if there is selected difference but it is not visible, previous
1444 * difference from cursor position is selected. This is what user
1445 * expects to happen and is natural thing to do. Also reduces
1446 * needless scrolling.
1447 * - if there is no selected difference, previous difference from cursor
1448 * position is selected.
1450 void CMergeEditView::OnPrevdiff()
1452 CMergeDoc *pd = GetDocument();
1453 int cnt = pd->m_ptBuf[0]->GetLineCount();
1457 // GetCurrentDiff() returns -1 if no diff selected
1459 int curDiff = pd->GetCurrentDiff();
1463 if (!IsDiffVisible(curDiff))
1465 // Selected difference not visible, select previous from cursor
1466 int line = GetCursorPos().y;
1467 // Make sure we aren't in the last line of the diff
1469 if (!IsValidTextPosY(CPoint(0, line)))
1471 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1475 // Find out if there is a preceding significant diff
1478 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1484 // We don't have a selected difference,
1485 // but cursor can be inside inactive diff
1486 int line = GetCursorPos().y;
1487 if (!IsValidTextPosY(CPoint(0, line)))
1489 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1492 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1493 if (prevDiff >= 0 && prevDiff >= firstDiff)
1494 SelectDiff(prevDiff, true, false);
1495 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1497 if (pDirDoc->MoveableToPrevDiff())
1498 pDirDoc->MoveToPrevDiff(pd);
1503 * @brief Update "Previous diff" UI items
1505 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1507 CMergeDoc *pd = GetDocument();
1508 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1513 // There aren't any significant differences
1518 // Enable if the end of the first significant difference is before caret
1519 enabled = (GetCursorPos().y > (long)dfi->dend);
1522 if (!enabled && pd->GetDirDoc())
1523 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1525 pCmdUI->Enable(enabled);
1528 void CMergeEditView::OnNextConflict()
1530 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1534 * @brief Update "Next Conflict" UI items
1536 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1538 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1541 void CMergeEditView::OnPrevConflict()
1543 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1547 * @brief Update "Prev Conflict" UI items
1549 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1551 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1555 * @brief Go to next 3-way diff and select it.
1557 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1559 CMergeDoc *pd = GetDocument();
1560 int cnt = pd->m_ptBuf[0]->GetLineCount();
1564 // Returns -1 if no diff selected
1565 int curDiff = pd->GetCurrentDiff();
1569 int nextDiff = curDiff;
1570 if (!IsDiffVisible(curDiff))
1572 // Selected difference not visible, select next from cursor
1573 int line = GetCursorPos().y;
1574 // Make sure we aren't in the first line of the diff
1576 if (!IsValidTextPosY(CPoint(0, line)))
1578 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1582 // Find out if there is a following significant diff
1583 if (curDiff < pd->m_diffList.GetSize() - 1)
1585 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1591 // nextDiff is the next one if there is one, else it is the one we're on
1592 SelectDiff(nextDiff, true, false);
1596 // We don't have a selected difference,
1597 // but cursor can be inside inactive diff
1598 int line = GetCursorPos().y;
1599 if (!IsValidTextPosY(CPoint(0, line)))
1601 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1603 SelectDiff(curDiff, true, false);
1608 * @brief Update "Next 3-way diff" UI items
1610 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1612 CMergeDoc *pd = GetDocument();
1614 if (pd->m_nBuffers < 3)
1616 pCmdUI->Enable(false);
1620 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1624 // There aren't any significant differences
1625 pCmdUI->Enable(false);
1629 // Enable if the beginning of the last significant difference is after caret
1630 CPoint pos = GetCursorPos();
1631 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1636 * @brief Go to previous 3-way diff and select it.
1638 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1640 CMergeDoc *pd = GetDocument();
1642 int cnt = pd->m_ptBuf[0]->GetLineCount();
1646 // GetCurrentDiff() returns -1 if no diff selected
1647 int curDiff = pd->GetCurrentDiff();
1651 int prevDiff = curDiff;
1652 if (!IsDiffVisible(curDiff))
1654 // Selected difference not visible, select previous from cursor
1655 int line = GetCursorPos().y;
1656 // Make sure we aren't in the last line of the diff
1658 if (!IsValidTextPosY(CPoint(0, line)))
1660 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1664 // Find out if there is a preceding significant diff
1667 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1673 // prevDiff is the preceding one if there is one, else it is the one we're on
1674 SelectDiff(prevDiff, true, false);
1678 // We don't have a selected difference,
1679 // but cursor can be inside inactive diff
1680 int line = GetCursorPos().y;
1681 if (!IsValidTextPosY(CPoint(0, line)))
1683 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1685 SelectDiff(curDiff, true, false);
1690 * @brief Update "Previous diff X and Y" UI items
1692 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1694 CMergeDoc *pd = GetDocument();
1696 if (pd->m_nBuffers < 3)
1698 pCmdUI->Enable(false);
1702 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1706 // There aren't any significant differences
1707 pCmdUI->Enable(false);
1711 // Enable if the end of the first significant difference is before caret
1712 CPoint pos = GetCursorPos();
1713 pCmdUI->Enable(pos.y > (long)dfi->dend);
1717 void CMergeEditView::OnNextdiffLM()
1719 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1722 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1724 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1727 void CMergeEditView::OnNextdiffLR()
1729 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1732 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1734 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1737 void CMergeEditView::OnNextdiffMR()
1739 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1742 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1744 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1747 void CMergeEditView::OnNextdiffLO()
1749 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1752 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1754 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1757 void CMergeEditView::OnNextdiffMO()
1759 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1762 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1764 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1767 void CMergeEditView::OnNextdiffRO()
1769 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1772 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1774 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1777 void CMergeEditView::OnPrevdiffLM()
1779 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1782 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1784 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1787 void CMergeEditView::OnPrevdiffLR()
1789 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1792 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1794 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1797 void CMergeEditView::OnPrevdiffMR()
1799 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1802 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1804 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1807 void CMergeEditView::OnPrevdiffLO()
1809 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1812 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1814 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1817 void CMergeEditView::OnPrevdiffMO()
1819 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1822 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1824 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1827 void CMergeEditView::OnPrevdiffRO()
1829 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1832 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1834 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1838 * @brief Clear selection
1840 void CMergeEditView::SelectNone()
1842 SetSelection (GetCursorPos(), GetCursorPos());
1847 * @brief Check if line is inside currently selected diff
1848 * @param [in] nLine 0-based linenumber in view
1849 * @sa CMergeDoc::GetCurrentDiff()
1850 * @sa CMergeDoc::LineInDiff()
1852 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1854 // Check validity of nLine
1857 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1858 int nLineCount = LocateTextBuffer()->GetLineCount();
1859 if (nLine >= nLineCount)
1860 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1863 const CMergeDoc *pd = GetDocument();
1864 int curDiff = pd->GetCurrentDiff();
1867 return pd->m_diffList.LineInDiff(nLine, curDiff);
1871 * @brief Called when mouse left-button double-clicked
1873 * Double-clicking mouse inside diff selects that diff
1875 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1877 CMergeDoc *pd = GetDocument();
1878 CPoint pos = GetCursorPos();
1880 int diff = pd->m_diffList.LineToDiff(pos.y);
1881 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1882 SelectDiff(diff, false, false);
1884 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1888 * @brief Called when mouse left button is released.
1890 * If button is released outside diffs, current diff
1893 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1895 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1896 DeselectDiffIfCursorNotInCurrentDiff();
1900 * @brief Called when mouse right button is pressed.
1902 * If right button is pressed outside diffs, current diff
1905 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1907 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1908 DeselectDiffIfCursorNotInCurrentDiff();
1911 void CMergeEditView::OnX2Y(int srcPane, int dstPane, bool selectedLineOnly)
1913 // Check that right side is not readonly
1914 if (IsReadOnly(dstPane))
1917 CMergeDoc *pDoc = GetDocument();
1918 int currentDiff = pDoc->GetCurrentDiff();
1920 if (currentDiff == -1)
1923 // If cursor is inside diff get number of that diff
1924 if (m_bCurrentLineIsDiff)
1926 CPoint pt = GetCursorPos();
1927 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1931 CPoint ptStart, ptEnd;
1932 GetSelection(ptStart, ptEnd);
1933 if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1935 if (!m_bRectangularSelection)
1937 if (selectedLineOnly)
1939 int firstDiff, lastDiff;
1940 GetSelectedDiffs(firstDiff, lastDiff);
1941 if (firstDiff != -1 && lastDiff != -1)
1943 CWaitCursor waitstatus;
1944 pDoc->CopyMultiplePartialList(srcPane, dstPane, firstDiff, lastDiff, ptStart.y, ptEnd.y);
1949 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1950 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1951 if (firstDiff != -1 && lastDiff != -1)
1953 CWaitCursor waitstatus;
1955 // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1956 // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1957 if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1959 // old behaviour: copy full line
1960 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1964 // new behaviour: copy selected text only
1965 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1972 CWaitCursor waitstatus;
1973 auto wordDiffs = GetColumnSelectedWordDiffIndice();
1975 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1976 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
1981 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1983 if (selectedLineOnly)
1985 CWaitCursor waitstatus;
1986 pDoc->PartialListCopy(srcPane, dstPane, currentDiff, ptStart.y, ptEnd.y);
1990 CWaitCursor waitstatus;
1991 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1996 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
1998 // Check that right side is not readonly
1999 if (!IsReadOnly(dstPane))
2001 // If one or more diffs inside selection OR
2002 // there is an active diff OR
2003 // cursor is inside diff
2004 CPoint ptStart, ptEnd;
2005 GetSelection(ptStart, ptEnd);
2006 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
2008 if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
2010 pCmdUI->Enable(true);
2014 int firstDiff, lastDiff;
2015 GetFullySelectedDiffs(firstDiff, lastDiff);
2017 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
2022 const int currDiff = GetDocument()->GetCurrentDiff();
2023 pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
2027 pCmdUI->Enable(false);
2031 * @brief Copy diff from left pane to right pane
2033 * Difference is copied from left to right when
2034 * - difference is selected
2035 * - difference is inside selection (allows merging multiple differences).
2036 * - cursor is inside diff
2038 * If there is selected diff outside selection, we copy selected
2041 void CMergeEditView::OnL2r()
2043 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2044 int srcPane = dstPane - 1;
2045 OnX2Y(srcPane, dstPane);
2049 * @brief Called when "Copy to left" item is updated
2051 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
2053 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2056 void CMergeEditView::OnLinesL2r()
2058 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2059 int srcPane = dstPane - 1;
2060 OnX2Y(srcPane, dstPane, true);
2063 void CMergeEditView::OnUpdateLinesL2r(CCmdUI* pCmdUI)
2065 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2069 * @brief Copy diff from right pane to left pane
2071 * Difference is copied from left to right when
2072 * - difference is selected
2073 * - difference is inside selection (allows merging multiple differences).
2074 * - cursor is inside diff
2076 * If there is selected diff outside selection, we copy selected
2079 void CMergeEditView::OnR2l()
2081 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2082 int srcPane = dstPane + 1;
2083 OnX2Y(srcPane, dstPane);
2087 * @brief Called when "Copy to right" item is updated
2089 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
2091 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2094 void CMergeEditView::OnLinesR2l()
2096 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2097 int srcPane = dstPane + 1;
2098 OnX2Y(srcPane, dstPane, true);
2101 void CMergeEditView::OnUpdateLinesR2l(CCmdUI* pCmdUI)
2103 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2106 void CMergeEditView::OnCopyFromLeft()
2108 int dstPane = m_nThisPane;
2109 int srcPane = dstPane - 1;
2112 OnX2Y(srcPane, dstPane);
2115 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2117 int dstPane = m_nThisPane;
2118 int srcPane = dstPane - 1;
2120 pCmdUI->Enable(false);
2122 OnUpdateX2Y(dstPane, pCmdUI);
2125 void CMergeEditView::OnCopyLinesFromLeft()
2127 int dstPane = m_nThisPane;
2128 int srcPane = dstPane - 1;
2131 OnX2Y(srcPane, dstPane, true);
2134 void CMergeEditView::OnUpdateCopyLinesFromLeft(CCmdUI* pCmdUI)
2136 int dstPane = m_nThisPane;
2137 int srcPane = dstPane - 1;
2139 pCmdUI->Enable(false);
2141 OnUpdateX2Y(dstPane, pCmdUI);
2144 void CMergeEditView::OnCopyFromRight()
2146 int dstPane = m_nThisPane;
2147 int srcPane = dstPane + 1;
2148 if (srcPane >= GetDocument()->m_nBuffers)
2150 OnX2Y(srcPane, dstPane);
2153 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2155 int dstPane = m_nThisPane;
2156 int srcPane = dstPane + 1;
2157 if (srcPane >= GetDocument()->m_nBuffers)
2158 pCmdUI->Enable(false);
2160 OnUpdateX2Y(dstPane, pCmdUI);
2163 void CMergeEditView::OnCopyLinesFromRight()
2165 int dstPane = m_nThisPane;
2166 int srcPane = dstPane + 1;
2167 if (srcPane >= GetDocument()->m_nBuffers)
2169 OnX2Y(srcPane, dstPane, true);
2172 void CMergeEditView::OnUpdateCopyLinesFromRight(CCmdUI* pCmdUI)
2174 int dstPane = m_nThisPane;
2175 int srcPane = dstPane + 1;
2176 if (srcPane >= GetDocument()->m_nBuffers)
2177 pCmdUI->Enable(false);
2179 OnUpdateX2Y(dstPane, pCmdUI);
2183 * @brief Copy all diffs from right pane to left pane
2185 void CMergeEditView::OnAllLeft()
2187 // Check that left side is not readonly
2188 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
2189 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2190 if (IsReadOnly(dstPane))
2192 CWaitCursor waitstatus;
2194 GetDocument()->CopyAllList(srcPane, dstPane);
2198 * @brief Called when "Copy all to left" item is updated
2200 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2202 // Check that left side is not readonly
2203 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2204 if (!IsReadOnly(dstPane))
2205 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2207 pCmdUI->Enable(false);
2211 * @brief Copy all diffs from left pane to right pane
2213 void CMergeEditView::OnAllRight()
2215 // Check that right side is not readonly
2216 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
2217 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2218 if (IsReadOnly(dstPane))
2221 CWaitCursor waitstatus;
2223 GetDocument()->CopyAllList(srcPane, dstPane);
2227 * @brief Called when "Copy all to right" item is updated
2229 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2231 // Check that right side is not readonly
2232 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2233 if (!IsReadOnly(dstPane))
2234 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2236 pCmdUI->Enable(false);
2240 * @brief Move to next file
2242 void CMergeEditView::OnNextFile()
2244 CMergeDoc* pd = GetDocument();
2245 CDirDoc* pDirDoc = pd->GetDirDoc();
2248 pDirDoc->MoveToNextFile(pd);
2253 * @brief Called when Move to next file is updated
2255 void CMergeEditView::OnUpdateNextFile(CCmdUI* pCmdUI)
2257 CMergeDoc* pd = GetDocument();
2258 CDirDoc* pDirDoc = pd->GetDirDoc();
2259 bool enabled = pDirDoc ? !pd->GetDirDoc()->IsLastFile() : false;
2260 pCmdUI->Enable(enabled);
2264 * @brief Move to previous file
2266 void CMergeEditView::OnPrevFile()
2268 CMergeDoc* pd = GetDocument();
2269 CDirDoc* pDirDoc = pd->GetDirDoc();
2272 pDirDoc->MoveToPrevFile(pd);
2277 * @brief Called when Move to previous file is updated
2279 void CMergeEditView::OnUpdatePrevFile(CCmdUI* pCmdUI)
2281 CMergeDoc* pd = GetDocument();
2282 CDirDoc* pDirDoc = pd->GetDirDoc();
2283 bool enabled = pDirDoc ? !pd->GetDirDoc()->IsFirstFile() : false;
2284 pCmdUI->Enable(enabled);
2288 * @brief Move to first file
2290 void CMergeEditView::OnFirstFile()
2292 CMergeDoc* pd = GetDocument();
2293 CDirDoc* pDirDoc = pd->GetDirDoc();
2296 pDirDoc->MoveToFirstFile(pd);
2301 * @brief Called when Move to first file is updated
2303 void CMergeEditView::OnUpdateFirstFile(CCmdUI* pCmdUI)
2305 CMergeDoc* pd = GetDocument();
2306 CDirDoc* pDirDoc = pd->GetDirDoc();
2307 bool enabled = pDirDoc ? !pDirDoc->IsFirstFile() : false;
2308 pCmdUI->Enable(enabled);
2312 * @brief Move to last file
2314 void CMergeEditView::OnLastFile()
2316 CMergeDoc* pd = GetDocument();
2317 CDirDoc* pDirDoc = pd->GetDirDoc();
2320 pDirDoc->MoveToLastFile(pd);
2325 * @brief Called when Move to last file item is updated
2327 void CMergeEditView::OnUpdateLastFile(CCmdUI* pCmdUI)
2329 CMergeDoc* pd = GetDocument();
2330 CDirDoc* pDirDoc = pd->GetDirDoc();
2331 bool enabled = pDirDoc ? !pd->GetDirDoc()->IsLastFile() : false;
2332 pCmdUI->Enable(enabled);
2336 * @brief Do Auto merge
2338 void CMergeEditView::OnAutoMerge()
2340 // Check current pane is not readonly
2341 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2344 CWaitCursor waitstatus;
2346 GetDocument()->DoAutoMerge(m_nThisPane);
2350 * @brief Called when "Auto Merge" item is updated
2352 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2354 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2355 !GetDocument()->IsModified() &&
2356 !GetDocument()->GetAutoMerged() &&
2361 * @brief Add synchronization point
2363 void CMergeEditView::OnAddSyncPoint()
2365 GetDocument()->AddSyncPoint();
2369 * @brief Clear synchronization points
2371 void CMergeEditView::OnClearSyncPoints()
2373 GetDocument()->ClearSyncPoints();
2377 * @brief Called when "Clear Synchronization Points" item is updated
2379 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2381 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2385 * @brief This function is called before other edit events.
2386 * @param [in] nAction Edit operation to do
2387 * @param [in] pszText Text to insert, delete etc
2388 * @sa CCrystalEditView::OnEditOperation()
2389 * @todo More edit-events for rescan delaying?
2391 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2393 if (!QueryEditable())
2395 // We must not arrive here, and assert helps detect troubles
2400 CMergeDoc* pDoc = GetDocument();
2401 pDoc->SetEditedAfterRescan(m_nThisPane);
2403 // simple hook for multiplex undo operations
2404 // deleted by jtuc 2003-06-28
2405 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2406 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2408 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2409 pDoc->undoTgt.push_back(this);
2410 pDoc->curUndo = pDoc->undoTgt.end();
2413 // perform original function
2414 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2416 // augment with additional operations
2418 // Change header to inform about changed doc
2419 pDoc->UpdateHeaderPath(m_nThisPane);
2421 // If automatic rescan enabled, rescan after edit events
2422 if (m_bAutomaticRescan)
2424 // keep document up to date
2425 // (Re)start timer to rescan only when user edits text
2426 // If timer starting fails, rescan immediately
2427 if (nAction == CE_ACTION_TYPING ||
2428 nAction == CE_ACTION_REPLACE ||
2429 nAction == CE_ACTION_BACKSPACE ||
2430 nAction == CE_ACTION_INDENT ||
2431 nAction == CE_ACTION_PASTE ||
2432 nAction == CE_ACTION_DELSEL ||
2433 nAction == CE_ACTION_DELETE ||
2434 nAction == CE_ACTION_CUT)
2436 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2437 pDoc->FlushAndRescan();
2440 pDoc->FlushAndRescan();
2446 // Update other pane for sync line.
2447 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2449 if (nPane == m_nThisPane)
2451 CCrystalEditView *pView = GetGroupView(nPane);
2452 if (pView != nullptr)
2453 pView->Invalidate();
2460 * @brief Redo last action
2462 void CMergeEditView::OnEditRedo()
2464 CWaitCursor waitstatus;
2465 CMergeDoc* pDoc = GetDocument();
2466 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2469 if (!QueryEditable())
2472 GetParentFrame()->SetActiveView(this, true);
2473 if(CCrystalEditViewEx::DoEditRedo())
2476 pDoc->UpdateHeaderPath(m_nThisPane);
2477 pDoc->FlushAndRescan();
2482 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2487 * @brief Called when "Redo" item is updated
2489 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2491 CMergeDoc* pDoc = GetDocument();
2492 if (pDoc->curUndo!=pDoc->undoTgt.end())
2494 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2495 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2498 pCmdUI->Enable(false);
2501 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2503 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2507 * @brief Scrolls to current diff and/or selects diff text
2508 * @param [in] bScroll If true scroll diff to view
2509 * @param [in] bSelectText If true select diff text
2510 * @note If bScroll and bSelectText are false, this does nothing!
2511 * @todo This shouldn't be called when no diff is selected, so
2512 * somebody could try to ASSERT(nDiff > -1)...
2514 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2516 CMergeDoc *pd = GetDocument();
2517 const int nDiff = pd->GetCurrentDiff();
2519 // Try to trap some errors
2520 if (nDiff >= pd->m_diffList.GetSize())
2521 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2522 nDiff, pd->m_diffList.GetSize());
2524 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2526 CPoint ptStart, ptEnd;
2528 pd->m_diffList.GetDiff(nDiff, curDiff);
2531 ptStart.y = curDiff.dbegin;
2533 ptEnd.y = curDiff.dend;
2535 if (bScroll && !m_bDetailView)
2537 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2539 // Difference is not visible, scroll it so that max amount of
2540 // scrolling is done while keeping the diff in screen. So if
2541 // scrolling is downwards, scroll the diff to as up in screen
2542 // as possible. This usually brings next diff to the screen
2543 // and we don't need to scroll into it.
2544 int nLine = GetSubLineIndex(ptStart.y);
2545 if (nLine > CONTEXT_LINES_ABOVE)
2547 nLine -= CONTEXT_LINES_ABOVE;
2549 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2550 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2552 if (nPane != m_nThisPane)
2553 GetGroupView(nPane)->ScrollToSubLine(nLine);
2557 vector<WordDiff> worddiffs;
2558 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
2559 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2560 CPoint pt = worddiffs.size() > 0 ?
2561 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2563 GetGroupView(m_nThisPane)->SetCursorPos(pt);
2564 GetGroupView(m_nThisPane)->SetAnchor(pt);
2565 GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2566 GetGroupView(m_nThisPane)->EnsureVisible(pt);
2567 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2569 if (nPane != m_nThisPane)
2571 if (worddiffs.size() > 0)
2573 pt.x = worddiffs[0].begin[nPane];
2574 pt.y = worddiffs[0].beginline[nPane];
2576 GetGroupView(nPane)->SetCursorPos(pt);
2577 GetGroupView(nPane)->SetAnchor(pt);
2578 GetGroupView(nPane)->SetSelection(pt, pt);
2585 ptEnd.x = GetLineLength(ptEnd.y);
2586 SetSelection(ptStart, ptEnd);
2595 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2597 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2598 // ...but for this the queue must be empty
2599 // The timer message is a low priority message but the queue is maybe not yet empty
2600 // So we set a flag, wait for OnIdle to proceed, then come back here...
2601 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2602 // not with SetTimer so there is no delay)
2604 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2606 // IDLE_TIMER is the false timer used to come back here after OnIdle
2607 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2608 // (one normal timer = one flag = one command)
2610 if (nIDEvent == IDT_RESCAN)
2612 KillTimer(IDT_RESCAN);
2613 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2614 // notify the app to come back after OnIdle
2615 theApp.SetNeedIdleTimer();
2618 if (nIDEvent == IDLE_TIMER)
2620 // not a real timer, just come back after OnIdle
2621 // look to flags to know what to do
2622 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2623 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2624 fTimerWaitingForIdle = 0;
2627 CCrystalEditViewEx::OnTimer(nIDEvent);
2631 * @brief Returns if buffer is read-only
2632 * @note This has no any relation to file being read-only!
2634 bool CMergeEditView::IsReadOnly(int pane) const
2636 return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2640 * @brief Called when "Save left (as...)" item is updated
2642 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2644 CMergeDoc *pd = GetDocument();
2645 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2649 * @brief Called when "Save middle (as...)" item is updated
2651 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2653 CMergeDoc *pd = GetDocument();
2654 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2658 * @brief Called when "Save right (as...)" item is updated
2660 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2662 CMergeDoc *pd = GetDocument();
2663 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2667 * @brief Refresh display using text-buffers
2668 * @note This DOES NOT reload files!
2670 void CMergeEditView::OnRefresh()
2672 CMergeDoc *pd = GetDocument();
2673 ASSERT(pd != nullptr);
2674 pd->FlushAndRescan(true);
2678 * @brief Handle some keys when in merging mode
2680 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2682 bool bHandled = false;
2684 // Allow default text selection when SHIFT pressed
2685 if (::GetAsyncKeyState(VK_SHIFT))
2688 // Allow default editor functions when CTRL pressed
2689 if (::GetAsyncKeyState(VK_CONTROL))
2692 // If we are in merging mode (merge with cursor keys)
2693 // handle some keys here
2694 switch (pMsg->wParam)
2721 * @brief Called before messages are translated.
2723 * Checks if ESC key was pressed, saves and closes doc.
2724 * Also if in merge mode traps cursor keys.
2726 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2728 if (pMsg->message == WM_KEYDOWN)
2730 // If we are in merging mode (merge with cursor keys)
2731 // handle some keys here
2732 if (theApp.GetMergingMode())
2734 bool bHandled = MergeModeKeyDown(pMsg);
2739 // Close window if user has allowed it from options
2740 if (pMsg->wParam == VK_ESCAPE)
2742 int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2743 if (nCloseWithEsc != 0)
2744 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2749 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2753 * @brief Called when "Save" item is updated
2755 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2757 CMergeDoc *pd = GetDocument();
2759 bool bModified = false;
2760 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2762 if (pd->m_ptBuf[nPane]->IsModified())
2765 pCmdUI->Enable(bModified);
2769 * @brief Enable/disable left buffer read-only
2771 void CMergeEditView::OnLeftReadOnly()
2773 CMergeDoc *pd = GetDocument();
2774 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2775 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2779 * @brief Called when "Left read-only" item is updated
2781 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2783 CMergeDoc *pd = GetDocument();
2784 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2785 pCmdUI->Enable(true);
2786 pCmdUI->SetCheck(bReadOnly);
2790 * @brief Enable/disable middle buffer read-only
2792 void CMergeEditView::OnMiddleReadOnly()
2794 CMergeDoc *pd = GetDocument();
2795 if (pd->m_nBuffers == 3)
2797 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2798 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2803 * @brief Called when "Middle read-only" item is updated
2805 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2807 CMergeDoc *pd = GetDocument();
2808 if (pd->m_nBuffers < 3)
2810 pCmdUI->Enable(false);
2814 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2815 pCmdUI->Enable(true);
2816 pCmdUI->SetCheck(bReadOnly);
2821 * @brief Enable/disable right buffer read-only
2823 void CMergeEditView::OnRightReadOnly()
2825 CMergeDoc *pd = GetDocument();
2826 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2827 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2831 * @brief Called when "Left read-only" item is updated
2833 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2835 CMergeDoc *pd = GetDocument();
2836 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2837 pCmdUI->Enable(true);
2838 pCmdUI->SetCheck(bReadOnly);
2841 /// Store interface we use to display status line info
2842 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2844 ASSERT(m_piMergeEditStatus == nullptr);
2845 m_piMergeEditStatus = piMergeEditStatus;
2849 * @brief Update status bar contents.
2851 void CMergeEditView::UpdateStatusbar()
2857 * @brief Update statusbar info, Override from CCrystalTextView
2858 * @note we tab-expand column, but we don't tab-expand char count,
2859 * since we want to show how many chars there are and tab is just one
2860 * character although it expands to several spaces.
2862 void CMergeEditView::OnUpdateCaret()
2864 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2867 CPoint cursorPos = GetCursorPos();
2868 int nScreenLine = cursorPos.y;
2869 const int nRealLine = ComputeRealLine(nScreenLine);
2876 DWORD dwLineFlags = 0;
2878 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2879 // Is this a ghost line ?
2880 if (dwLineFlags & LF_GHOST)
2882 // Ghost lines display eg "Line 12-13"
2883 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2884 sEol = _T("hidden");
2888 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2889 sLine.Format(_T("%d"), nRealLine+1);
2890 curChar = cursorPos.x + 1;
2891 chars = GetLineLength(nScreenLine);
2892 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2893 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2895 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2896 GetDocument()->IsMixedEOL(m_nThisPane))
2898 sEol = GetTextBufferEol(nScreenLine);
2901 sEol = _T("hidden");
2903 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2904 curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2906 // Is cursor inside difference?
2907 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2908 m_bCurrentLineIsDiff = true;
2910 m_bCurrentLineIsDiff = false;
2912 CWnd* pWnd = GetFocus();
2913 if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2914 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2917 * @brief Select linedifference in the current line.
2919 * Select line difference in current line. Selection type
2920 * is choosed by highlight type.
2922 template<bool reversed>
2923 void CMergeEditView::OnSelectLineDiff()
2925 // Pass this to the document, to compare this file to other
2926 GetDocument()->Showlinediff(this, reversed);
2929 /// Enable select difference menuitem if current line is inside difference.
2930 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2932 pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2935 void CMergeEditView::OnAddToSubstitutionFilters()
2937 // Pass this to the document, to compare this file to other
2938 GetDocument()->AddToSubstitutionFilters(this, false);
2941 void CMergeEditView::OnUpdateAddToSubstitutionFilters(CCmdUI* pCmdUI)
2943 pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
2947 * @brief Enable/disable Replace-menuitem
2949 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2951 CMergeDoc *pd = GetDocument();
2952 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2954 pCmdUI->Enable(!bReadOnly);
2958 * @brief Update readonly statusbaritem
2960 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2962 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2963 pCmdUI->Enable(bRO);
2967 * @brief Create the dynamic submenu for scripts
2969 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2972 std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2975 size_t i = GetMenuItemCount(hMenu);
2977 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2979 if (functionNamesList.size() == 0)
2981 // no script : create a <empty> entry
2982 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2986 // or fill in the submenu with the scripts names
2987 int ID = ID_SCRIPT_FIRST; // first ID in menu
2988 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2989 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2991 functionNamesList.clear();
2994 if (!plugin::IsWindowsScriptThere())
2995 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
3001 * @brief Create the dynamic submenu for prediffers
3003 * @note The plugins are grouped in (suggested) and (not suggested)
3004 * The IDs follow the order of GetAvailableScripts
3006 * suggested 0 ID_1ST + 0
3007 * suggested 1 ID_1ST + 2
3008 * suggested 2 ID_1ST + 5
3009 * not suggested 0 ID_1ST + 1
3010 * not suggested 1 ID_1ST + 3
3011 * not suggested 2 ID_1ST + 4
3013 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
3016 int i = GetMenuItemCount(hMenu);
3018 DeleteMenu(hMenu, 0, MF_BYPOSITION);
3020 CMergeDoc *pd = GetDocument();
3021 ASSERT(pd != nullptr);
3024 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
3026 // get the scriptlet files
3027 PluginArray * piScriptArray =
3028 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3029 PluginArray * piScriptArray2 =
3030 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3032 // build the menu : first part, suggested plugins
3034 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
3035 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
3037 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
3039 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
3041 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
3042 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
3045 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
3047 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
3049 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
3050 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
3053 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
3056 // build the menu : second part, others plugins
3058 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
3059 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
3061 ID = ID_PREDIFFERS_FIRST; // first ID in menu
3062 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
3064 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
3065 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
3068 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
3070 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
3072 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
3073 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
3076 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
3079 // compute the m_CurrentPredifferID (to set the radio button)
3080 PrediffingInfo prediffer;
3081 pd->GetPrediffer(&prediffer);
3083 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3084 m_CurrentPredifferID = 0;
3085 else if (prediffer.m_PluginName.empty())
3086 m_CurrentPredifferID = ID_NO_PREDIFFER;
3089 ID = ID_PREDIFFERS_FIRST; // first ID in menu
3090 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
3092 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
3093 if (prediffer.m_PluginName == plugin->m_name)
3094 m_CurrentPredifferID = ID;
3097 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
3099 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
3100 if (prediffer.m_PluginName == plugin->m_name)
3101 m_CurrentPredifferID = ID;
3109 * @brief Offer a context menu built with scriptlet/ActiveX functions
3111 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
3113 // Create the menu and populate it with the available functions
3115 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
3117 // Remove copying item copying from active side
3118 if (m_nThisPane == 0) // left?
3120 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3121 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3122 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3123 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3125 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
3127 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3128 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3129 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3130 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3133 // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
3134 // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
3135 // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
3136 int nBuffers = GetDocument()->m_nBuffers;
3137 if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
3138 menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
3139 else if (nBuffers == 3 && m_nThisPane == 2)
3140 menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
3142 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
3143 theApp.TranslateMenu(menu.m_hMenu);
3145 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
3146 ASSERT(pSub != nullptr);
3148 // Context menu opened using keyboard has no coordinates
3149 if (point.x == -1 && point.y == -1)
3152 GetClientRect(rect);
3153 ClientToScreen(rect);
3155 point = rect.TopLeft();
3159 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
3160 point.x, point.y, AfxGetMainWnd());
3165 * @brief Update EOL mode in status bar
3167 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
3169 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
3173 * @brief Change EOL mode and unify all the lines EOL to this new mode
3175 void CMergeEditView::OnConvertEolTo(UINT nID )
3177 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;;
3181 nStyle = CRLFSTYLE::DOS;
3183 case ID_EOL_TO_UNIX:
3184 nStyle = CRLFSTYLE::UNIX;
3187 nStyle = CRLFSTYLE::MAC;
3191 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
3194 m_pTextBuffer->SetCRLFMode(nStyle);
3196 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
3197 if (m_pTextBuffer->applyEOLMode())
3199 CMergeDoc *pd = GetDocument();
3200 ASSERT(pd != nullptr);
3201 pd->UpdateHeaderPath(m_nThisPane);
3202 pd->FlushAndRescan(true);
3207 * @brief allow convert to entries in file submenu
3209 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
3211 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;
3212 switch (pCmdUI->m_nID)
3215 nStyle = CRLFSTYLE::DOS;
3217 case ID_EOL_TO_UNIX:
3218 nStyle = CRLFSTYLE::UNIX;
3221 nStyle = CRLFSTYLE::MAC;
3225 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
3229 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3230 GetDocument()->IsMixedEOL(m_nThisPane) ||
3231 nStyle != m_pTextBuffer->GetCRLFMode())
3233 pCmdUI->SetRadio(false);
3235 // Don't allow selecting other EOL style for protected pane
3236 if (!QueryEditable())
3237 pCmdUI->Enable(false);
3240 pCmdUI->SetRadio(true);
3244 * @brief Copy diff from left to right and advance to next diff
3246 void CMergeEditView::OnL2RNext()
3249 if (IsCursorInDiff()) // for 3-way file compare
3255 * @brief Update "Copy right and advance" UI item
3257 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
3259 OnUpdateL2r(pCmdUI);
3263 * @brief Copy diff from right to left and advance to next diff
3265 void CMergeEditView::OnR2LNext()
3268 if (IsCursorInDiff()) // for 3-way file compare
3274 * @brief Update "Copy left and advance" UI item
3276 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
3278 OnUpdateR2l(pCmdUI);
3282 * @brief Change active pane in MergeView.
3283 * Changes active pane and makes sure cursor position is kept in
3284 * screen. Currently we put cursor in same line than in original
3285 * active pane but we could be smarter too? Maybe update cursor
3286 * only when it is not visible in new pane?
3288 void CMergeEditView::OnChangePane()
3290 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3291 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
3292 CMergeDoc *pDoc = GetDocument();
3293 bool bFound = false;
3294 CMergeEditView *pNextActiveView = nullptr;
3295 std::vector<CMergeEditView *> list = pDoc->GetViewList();
3296 list.insert(list.end(), list.begin(), list.end());
3297 for (auto& pView : list)
3299 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3301 pNextActiveView = pView;
3307 GetParentFrame()->SetActiveView(pNextActiveView);
3308 CPoint ptCursor = pWnd->GetCursorPos();
3310 if (ptCursor.y >= pNextActiveView->GetLineCount())
3311 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3312 pNextActiveView->SetCursorPos(ptCursor);
3313 pNextActiveView->SetAnchor(ptCursor);
3314 pNextActiveView->SetSelection(ptCursor, ptCursor);
3318 * @brief Show "Go To" dialog and scroll views to line or diff.
3320 * Before dialog is opened, current line and file is determined
3322 * @note Conversions needed between apparent and real lines
3324 void CMergeEditView::OnWMGoto()
3327 CMergeDoc *pDoc = GetDocument();
3328 CPoint pos = GetCursorPos();
3332 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3333 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
3334 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
3336 // Set active file and current line selected in dialog
3337 dlg.m_strParam = strutils::to_str(nRealLine + 1);
3338 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3339 dlg.m_nGotoWhat = 0;
3341 if (dlg.DoModal() == IDOK)
3343 CMergeDoc * pDoc1 = GetDocument();
3344 CMergeEditView * pCurrentView = nullptr;
3347 pCurrentView = GetGroupView(m_nThisPane);
3350 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3352 if (dlg.m_nGotoWhat == 0)
3354 int nRealLine1 = num;
3357 if (nRealLine1 > nLastLine)
3358 nRealLine1 = nLastLine;
3360 bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
3361 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile, !bShift);
3368 if (diff >= pDoc1->m_diffList.GetSize())
3369 diff = pDoc1->m_diffList.GetSize();
3371 pCurrentView->SelectDiff(diff, true, false);
3377 * @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
3378 * Go to moved line between the left and right panes when in 2-way file comparison.
3379 * Go to moved line between the left and middle panes when in 3-way file comparison.
3381 void CMergeEditView::OnGotoMovedLineLM()
3383 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3386 CMergeDoc* pDoc = GetDocument();
3387 CPoint pos = GetCursorPos();
3389 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3390 ASSERT(pDoc != nullptr);
3391 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3394 if (m_nThisPane == 0)
3396 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3398 GotoLine(line, false, 1);
3400 else if (m_nThisPane == 1)
3402 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3404 GotoLine(line, false, 0);
3409 * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
3410 * @param [in] pCmdUI UI component to update.
3411 * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
3413 void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
3415 CMergeDoc* pDoc = GetDocument();
3416 CPoint pos = GetCursorPos();
3418 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3419 ASSERT(pCmdUI != nullptr);
3420 ASSERT(pDoc != nullptr);
3421 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3424 if (pDoc->m_nBuffers == 2)
3425 pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
3427 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
3429 pCmdUI->Enable(false);
3433 if (m_nThisPane == 0)
3435 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3436 pCmdUI->Enable(bOn);
3438 else if (m_nThisPane == 1)
3440 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3441 pCmdUI->Enable(bOn);
3446 * @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
3447 * Go to moved line between the middle and right panes when in 3-way file comparison.
3449 void CMergeEditView::OnGotoMovedLineMR()
3451 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3454 CMergeDoc* pDoc = GetDocument();
3455 CPoint pos = GetCursorPos();
3457 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3458 ASSERT(pDoc != nullptr);
3459 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3462 if (m_nThisPane == 1)
3464 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3466 GotoLine(line, false, 2);
3468 else if (m_nThisPane == 2)
3470 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3472 GotoLine(line, false, 1);
3477 * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
3478 * @param [in] pCmdUI UI component to update.
3480 void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
3482 CMergeDoc* pDoc = GetDocument();
3483 CPoint pos = GetCursorPos();
3485 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3486 ASSERT(pCmdUI != nullptr);
3487 ASSERT(pDoc != nullptr);
3488 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3491 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
3493 pCmdUI->Enable(false);
3497 if (m_nThisPane == 1)
3499 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3500 pCmdUI->Enable(bOn);
3502 else if (m_nThisPane == 2)
3504 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3505 pCmdUI->Enable(bOn);
3509 void CMergeEditView::OnShellMenu()
3511 CFrameWnd *pFrame = GetTopLevelFrame();
3512 ASSERT(pFrame != nullptr);
3513 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3514 pFrame->m_bAutoMenuEnable = FALSE;
3516 String path = GetDocument()->m_filePaths[m_nThisPane];
3517 std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3518 pContextMenu->Initialize();
3519 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3520 pContextMenu->RequeryShellContextMenu();
3522 ::GetCursorPos(&point);
3523 HWND hWnd = GetSafeHwnd();
3524 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3526 pContextMenu->InvokeCommand(nCmd, hWnd);
3527 pContextMenu->ReleaseShellContextMenu();
3529 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3532 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3534 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3538 * @brief Reload options.
3540 void CMergeEditView::RefreshOptions()
3542 RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3543 SetRenderingMode(nRenderingMode);
3545 m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3547 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3548 SetInsertTabs(true);
3550 SetInsertTabs(false);
3552 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3554 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3555 SetTextType(CrystalLineParser::SRC_PLAIN);
3556 else if (!m_bChangedSchemeManually)
3558 // The syntax highlighting scheme should only be applied if it has not been manually changed.
3559 String fileName = GetDocument()->m_filePaths[m_nThisPane];
3561 paths::SplitFilename(fileName, nullptr, nullptr, &sExt);
3562 CrystalLineParser::TextDefinition* def = CrystalLineParser::GetTextType(sExt.c_str());
3564 SetTextType(def->type);
3566 SetTextType(CrystalLineParser::SRC_PLAIN);
3569 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3570 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3572 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3573 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3574 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3575 GetDocument()->IsMixedEOL(m_nThisPane));
3577 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3580 void CMergeEditView::OnScripts(UINT nID )
3582 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3583 String text = GetSelectedText();
3585 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3586 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3588 // now replace the text
3589 ReplaceSelection(text.c_str(), text.length(), 0);
3593 * @brief Called when an editor script item is updated
3595 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3597 // append the scripts submenu
3598 HMENU scriptsSubmenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu->m_hMenu : nullptr;
3599 if (scriptsSubmenu != nullptr)
3600 createScriptsSubmenu(scriptsSubmenu);
3602 pCmdUI->Enable(true);
3606 * @brief Called when an editor script item is updated
3608 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3610 pCmdUI->Enable(true);
3612 CMergeDoc *pd = GetDocument();
3613 ASSERT(pd != nullptr);
3614 PrediffingInfo prediffer;
3615 pd->GetPrediffer(&prediffer);
3617 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3619 pCmdUI->SetRadio(false);
3623 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3624 if (prediffer.m_PluginName.empty())
3625 m_CurrentPredifferID = ID_NO_PREDIFFER;
3627 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3631 * @brief Update "Prediffer" menuitem
3633 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3635 // recreate the sub menu (to fill the "selected prediffers")
3636 GetMainFrame()->UpdatePrediffersMenu();
3640 void CMergeEditView::OnNoPrediffer()
3642 OnPrediffer(ID_NO_PREDIFFER);
3645 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3647 void CMergeEditView::OnPrediffer(UINT nID )
3649 CMergeDoc *pd = GetDocument();
3650 ASSERT(pd != nullptr);
3652 SetPredifferByMenu(nID);
3653 pd->FlushAndRescan(true);
3657 * @brief Handler for all prediffer choices.
3658 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3659 * ID_NO_PREDIFFER, & specific prediffers.
3661 void CMergeEditView::SetPredifferByMenu(UINT nID )
3663 CMergeDoc *pd = GetDocument();
3664 ASSERT(pd != nullptr);
3666 if (nID == ID_NO_PREDIFFER)
3668 m_CurrentPredifferID = nID;
3669 // All flags are set correctly during the construction
3670 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3671 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3672 infoPrediffer->m_PluginName.clear();
3673 pd->SetPrediffer(infoPrediffer);
3674 pd->FlushAndRescan(true);
3678 // get the scriptlet files
3679 PluginArray * piScriptArray =
3680 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3681 PluginArray * piScriptArray2 =
3682 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3684 // build a PrediffingInfo structure fom the ID
3685 PrediffingInfo prediffer;
3686 prediffer.m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3688 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3689 if (pluginNumber < piScriptArray->size())
3691 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3692 prediffer.m_PluginName = plugin->m_name;
3696 pluginNumber -= piScriptArray->size();
3697 if (pluginNumber >= piScriptArray2->size())
3699 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3700 prediffer.m_PluginName = plugin->m_name;
3703 // update data for the radio button
3704 m_CurrentPredifferID = nID;
3706 // update the prediffer and rescan
3707 pd->SetPrediffer(&prediffer);
3711 * @brief Look through available prediffers, and return ID of requested one, if found
3713 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3716 int ID = ID_PREDIFFERS_FIRST;
3718 // Search file prediffers
3719 PluginArray * piScriptArray =
3720 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3721 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3723 const PluginInfoPtr & plugin = piScriptArray->at(i);
3724 if (plugin->m_name == prediffer)
3728 // Search buffer prediffers
3729 PluginArray * piScriptArray2 =
3730 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3731 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3733 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3734 if (plugin->m_name == prediffer)
3742 * @brief Look through available prediffers, and return ID of requested one, if found
3744 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3746 int id = FindPrediffer(prediffer);
3747 if (id<0) return false;
3748 SetPredifferByMenu(id);
3753 * @brief Goto given line.
3754 * @param [in] nLine Destination linenumber
3755 * @param [in] bRealLine if true linenumber is real line, otherwise
3756 * it is apparent line (including deleted lines)
3757 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3758 * @param [in] bMoveAnchor if true the anchor is moved to nLine
3760 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane, bool bMoveAnchor)
3762 CMergeDoc *pDoc = GetDocument();
3763 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3764 CMergeEditView *pCurrentView = nullptr;
3765 if (pSplitterWnd != nullptr)
3766 pCurrentView = static_cast<CMergeEditView*>
3767 (pSplitterWnd->GetActivePane());
3769 int nRealLine = nLine;
3770 int nApparentLine = nLine;
3772 // Compute apparent (shown linenumber) line
3775 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3776 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3778 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3782 ptPos.y = nApparentLine;
3784 // Scroll line to center of view
3785 int nScrollLine = GetSubLineIndex(nApparentLine);
3786 nScrollLine -= GetScreenLines() / 2;
3787 if (nScrollLine < 0)
3790 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3792 int nGroup = m_bDetailView ? 0 : m_nThisGroup;
3793 CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
3794 pView->ScrollToSubLine(nScrollLine);
3795 if (ptPos.y < pView->GetLineCount())
3797 pView->SetCursorPos(ptPos);
3799 pView->SetAnchor(ptPos);
3800 pView->SetSelection(pView->GetAnchor(), ptPos);
3804 CPoint ptPos1(0, pView->GetLineCount() - 1);
3805 pView->SetCursorPos(ptPos1);
3807 pView->SetAnchor(ptPos1);
3808 pView->SetSelection(pView->GetAnchor(), ptPos1);
3812 // If goto target is another view - activate another view.
3813 // This is done for user convenience as user probably wants to
3814 // work with goto target file.
3816 GetDocument()->GetView(0, pane)->SetActivePane();
3817 else if (GetGroupView(pane) != pCurrentView)
3818 GetGroupView(pane)->SetActivePane();
3822 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3825 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3827 if (pScrollBar == nullptr)
3829 // Scroll did not come frome a scroll bar
3830 // Find the appropriate scroll bar
3831 // and send the message to the splitter window instead
3832 // The event should eventually come back here but with a valid scrollbar
3833 // Along the way it will be propagated to other windows that need it
3834 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3835 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3836 pSplitterWnd->SendMessage(WM_HSCROLL,
3837 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3840 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3844 * @brief When view is scrolled using scrollbars update location pane.
3846 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3848 if (pScrollBar == nullptr)
3850 // Scroll did not come frome a scroll bar
3851 // Find the appropriate scroll bar
3852 // and send the message to the splitter window instead
3853 // The event should eventually come back here but with a valid scrollbar
3854 // Along the way it will be propagated to other windows that need it
3855 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3856 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3857 pSplitterWnd->SendMessage(WM_VSCROLL,
3858 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3861 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3863 if (nSBCode == SB_ENDSCROLL)
3866 // Note we cannot use nPos because of its 16-bit nature
3867 SCROLLINFO si = {0};
3868 si.cbSize = sizeof (si);
3869 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3870 VERIFY (GetScrollInfo (SB_VERT, &si));
3872 // Get the current position of scroll box.
3873 int nCurPos = si.nPos;
3875 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3879 * @brief Copy selected lines adding linenumbers.
3881 void CMergeEditView::OnEditCopyLineNumbers()
3889 CMergeDoc *pDoc = GetDocument();
3890 GetSelection(ptStart, ptEnd);
3892 // Get last selected line (having widest linenumber)
3893 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3894 size_t nNumWidth = strutils::to_str(line + 1).length();
3896 for (int i = ptStart.y; i <= ptEnd.y; i++)
3898 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3901 // We need to convert to real linenumbers
3902 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3904 // Insert spaces to align different width linenumbers (99, 100)
3905 strLine = GetLineText(i);
3906 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3909 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3910 strText += strNumLine;
3912 PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3915 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3917 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3921 * @brief Open active file with associated application.
3923 * First tries to open file using shell 'Edit' action, since that
3924 * action open scripts etc. to editor instead of running them. If
3925 * edit-action is not registered, 'Open' action is used.
3927 void CMergeEditView::OnOpenFile()
3929 CMergeDoc * pDoc = GetDocument();
3930 ASSERT(pDoc != nullptr);
3932 String sFileName = pDoc->m_filePaths[m_nThisPane];
3933 if (sFileName.empty())
3935 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3936 0, 0, SW_SHOWNORMAL);
3937 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3938 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3939 0, 0, SW_SHOWNORMAL);
3940 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3945 * @brief Open active file with app selection dialog
3947 void CMergeEditView::OnOpenFileWith()
3949 CMergeDoc * pDoc = GetDocument();
3950 ASSERT(pDoc != nullptr);
3952 String sFileName = pDoc->m_filePaths[m_nThisPane];
3953 if (sFileName.empty())
3957 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3959 sysdir.ReleaseBuffer();
3960 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3961 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3962 sysdir, SW_SHOWNORMAL);
3966 * @brief Open active file with external editor
3968 void CMergeEditView::OnOpenFileWithEditor()
3970 CMergeDoc * pDoc = GetDocument();
3971 ASSERT(pDoc != nullptr);
3973 String sFileName = pDoc->m_filePaths[m_nThisPane];
3974 if (sFileName.empty())
3977 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3978 theApp.OpenFileToExternalEditor(sFileName, nRealLine);
3982 * @brief Force repaint of the location pane.
3984 void CMergeEditView::RepaintLocationPane()
3986 // Must force recalculation due to caching of data in location pane.
3987 CLocationView *pLocationView = GetDocument()->GetLocationView();
3988 if (pLocationView != nullptr)
3989 pLocationView->ForceRecalculate();
3993 * @brief Enables/disables linediff (different color for diffs)
3995 void CMergeEditView::OnViewLineDiffs()
3997 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3998 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
4000 // Call CMergeDoc RefreshOptions() to refresh *both* views
4001 CMergeDoc *pDoc = GetDocument();
4002 pDoc->RefreshOptions();
4003 pDoc->FlushAndRescan(true);
4006 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
4008 pCmdUI->Enable(true);
4009 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
4013 * @brief Enables/disables line number
4015 void CMergeEditView::OnViewLineNumbers()
4017 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
4019 // Call CMergeDoc RefreshOptions() to refresh *both* views
4020 CMergeDoc *pDoc = GetDocument();
4021 pDoc->RefreshOptions();
4024 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
4026 pCmdUI->Enable(true);
4027 pCmdUI->SetCheck(GetViewLineNumbers());
4031 * @brief Enables/disables word wrap
4033 void CMergeEditView::OnViewWordWrap()
4035 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
4037 // Call CMergeDoc RefreshOptions() to refresh *both* views
4038 CMergeDoc *pDoc = GetDocument();
4039 pDoc->RefreshOptions();
4040 pDoc->UpdateAllViews(this);
4045 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
4047 pCmdUI->Enable(true);
4048 pCmdUI->SetCheck(m_bWordWrap);
4051 void CMergeEditView::OnViewWhitespace()
4053 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
4055 // Call CMergeDoc RefreshOptions() to refresh *both* views
4056 CMergeDoc *pDoc = GetDocument();
4057 pDoc->RefreshOptions();
4060 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
4062 pCmdUI->SetCheck(GetViewTabs());
4065 void CMergeEditView::OnViewEOL()
4067 GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
4068 GetDocument()->RefreshOptions();
4071 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI)
4073 pCmdUI->SetCheck(GetViewEols());
4076 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
4078 if (!IsInitialized())
4081 CMergeDoc * pDoc = GetDocument();
4082 if (m_nThisPane < pDoc->m_nBuffers - 1)
4084 // To calculate subline index correctly
4085 // we have to invalidate line cache in all pane before calling the function related the subline.
4086 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4088 CMergeEditView *pView = GetGroupView(nPane);
4089 if (pView != nullptr)
4090 pView->InvalidateScreenRect(false);
4095 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4097 CMergeEditView *pView = GetGroupView(nPane);
4098 if (pView != nullptr)
4099 pView->Invalidate();
4102 // recalculate m_nTopSubLine
4103 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
4107 RecalcVertScrollBar (false, false);
4108 RecalcHorzScrollBar (false, false);
4112 * @brief allocates GDI resources for printing
4113 * @param pDC [in] points to the printer device context
4114 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4116 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
4118 GetParentFrame()->PostMessage(WM_TIMER);
4120 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4122 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
4123 pView->m_bPrintHeader = true;
4124 pView->m_bPrintFooter = true;
4125 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
4130 * @brief frees GDI resources for printing
4131 * @param pDC [in] points to the printer device context
4132 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4134 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
4136 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4137 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
4139 GetParentFrame()->PostMessage(WM_TIMER);
4143 * @brief Gets header text to print
4144 * @param [in] nPageNum the page number to print
4145 * @param [out] header text to print
4147 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
4149 text = GetDocument()->GetTitle();
4153 * @brief Prints header
4154 * @param [in] nPageNum the page number to print
4156 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
4158 if (m_nThisPane > 0)
4160 int oldRight = m_rcPrintArea.right;
4161 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4162 CGhostTextView::PrintHeader(pdc, nPageNum);
4163 m_rcPrintArea.right = oldRight;
4167 * @brief Prints footer
4168 * @param [in] nPageNum the page number to print
4170 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
4172 if (m_nThisPane > 0)
4174 int oldRight = m_rcPrintArea.right;
4175 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4176 CGhostTextView::PrintFooter(pdc, nPageNum);
4177 m_rcPrintArea.right = oldRight;
4180 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
4182 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4183 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
4187 * @brief Prints or previews both panes.
4188 * @param pDC [in] points to the printer device context
4189 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4191 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
4193 CRect rDraw = pInfo->m_rectDraw;
4194 CSize sz = rDraw.Size();
4195 CMergeDoc *pDoc = GetDocument();
4197 SIZE szLeftTop, szRightBottom;
4198 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
4199 pDC->HIMETRICtoLP(&szLeftTop);
4200 pDC->HIMETRICtoLP(&szRightBottom);
4202 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
4205 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
4207 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
4208 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
4209 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
4210 pPane->CGhostTextView::OnPrint(pDC, pInfo);
4214 bool CMergeEditView::IsInitialized() const
4216 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
4217 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
4218 return pBuffer->IsInitialized();
4222 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
4224 int CMergeEditView::GetEmptySubLines( int nLineIndex )
4226 int nBreaks[3] = {0};
4227 int nMaxBreaks = -1;
4228 CMergeDoc * pDoc = GetDocument();
4229 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4231 CMergeEditView *pView = GetGroupView(nPane);
4232 if (pView != nullptr)
4234 if (nLineIndex >= pView->GetLineCount())
4236 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
4238 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
4241 if (nBreaks[m_nThisPane] < nMaxBreaks)
4242 return nMaxBreaks - nBreaks[m_nThisPane];
4248 * @brief Invalidate sub line index cache from the specified index to the end of file.
4249 * @param [in] nLineIndex Index of the first line to invalidate
4251 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
4253 CMergeDoc * pDoc = GetDocument();
4254 ASSERT(pDoc != nullptr);
4256 // We have to invalidate sub line index cache on both panes.
4257 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4259 CMergeEditView *pView = GetGroupView(nPane);
4260 if (pView != nullptr)
4261 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
4265 void CMergeEditView::SetWordWrapping( bool bWordWrap )
4267 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4268 GetGroupView(pane)->m_bWordWrap = bWordWrap;
4269 CCrystalTextView::SetWordWrapping(bWordWrap);
4273 * @brief Swap the positions of the two panes
4275 void CMergeEditView::OnViewSwapPanes12()
4277 GetDocument()->SwapFiles(0, 1);
4281 * @brief Swap the positions of the two panes
4283 void CMergeEditView::OnViewSwapPanes23()
4285 GetDocument()->SwapFiles(1, 2);
4289 * @brief Swap the positions of the two panes
4291 void CMergeEditView::OnViewSwapPanes13()
4293 GetDocument()->SwapFiles(0, 2);
4297 * @brief Determine if difference is visible on screen.
4298 * @param [in] nDiff Number of diff to check.
4299 * @return true if difference is visible.
4301 bool CMergeEditView::IsDiffVisible(int nDiff)
4303 const CMergeDoc *pd = GetDocument();
4306 pd->m_diffList.GetDiff(nDiff, diff);
4308 return IsDiffVisible(diff);
4312 * @brief Determine if difference is visible on screen.
4313 * @param [in] diff diff to check.
4314 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
4315 * @return true if difference is visible, false otherwise.
4317 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
4319 const int nDiffStart = GetSubLineIndex(diff.dbegin);
4320 const int nDiffEnd = GetSubLineIndex(diff.dend);
4321 // Diff's height is last line - first line + last line's line count
4322 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
4324 // If diff first line outside current view - context OR
4325 // if diff last line outside current view - context OR
4326 // if diff is bigger than screen
4327 if ((nDiffStart < m_nTopSubLine) ||
4328 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
4329 (nDiffHeight >= GetScreenLines()))
4339 /** @brief Open help from mainframe when user presses F1*/
4340 void CMergeEditView::OnHelp()
4342 theApp.ShowHelp(MergeViewHelpLocation);
4346 * @brief Called after document is loaded.
4347 * This function is called from CMergeDoc::OpenDocs() after documents are
4348 * loaded. So this is good place to set View's options etc.
4350 void CMergeEditView::DocumentsLoaded()
4352 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
4355 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
4360 SetTopMargin(false);
4363 // Enable/disable automatic rescan (rescanning after edit)
4364 EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
4366 // SetTextType will revert to language dependent defaults for tab
4367 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
4368 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
4369 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
4370 GetDocument()->IsMixedEOL(m_nThisPane);
4371 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
4372 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
4373 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
4374 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4376 // Enable Backspace at beginning of line
4377 SetDisableBSAtSOL(false);
4379 // Set tab type (tabs/spaces)
4380 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
4381 SetInsertTabs(bInsertTabs);
4383 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
4384 // disabled) after docs are open in screen. So lets make sure they are
4385 // really updated, even though this is unnecessary in most cases.
4386 RecalcHorzScrollBar();
4387 RecalcVertScrollBar();
4391 * @brief Update LocationView position.
4392 * This function updates LocationView position to given lines.
4393 * Usually we want to lines in file compare view and area in
4394 * LocationView to match. Be extra carefull to not call non-existing
4396 * @param [in] nTopLine Top line of current view.
4397 * @param [in] nBottomLine Bottom line of current view.
4399 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
4400 int nBottomLine /*= -1*/)
4402 CMergeDoc *pDoc = GetDocument();
4403 if (pDoc == nullptr)
4406 CLocationView *pLocationView = pDoc->GetLocationView();
4408 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
4410 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
4415 * @brief Enable/Disable view's selection margins.
4416 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
4417 * useful. But it appears many users don't use/need those features and for them
4418 * selection margins are just wasted screen estate.
4420 void CMergeEditView::OnViewMargin()
4422 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
4423 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
4425 SetSelectionMargin(!bViewMargin);
4426 CMergeDoc *pDoc = GetDocument();
4427 pDoc->RefreshOptions();
4428 pDoc->UpdateAllViews(this);
4432 * @brief Update GUI for Enable/Disable view's selection margin.
4433 * @param [in] pCmdUI Pointer to UI item to update.
4435 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
4437 pCmdUI->Enable(true);
4438 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4442 * @brief Create the "Change Scheme" sub menu.
4443 * @param [in] pCmdUI Pointer to UI item to update.
4445 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4447 // Delete the place holder menu.
4448 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4450 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4452 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4453 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4454 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
4456 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
4458 name = theApp.LoadString(i);
4459 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
4462 pCmdUI->Enable(true);
4466 * @brief Change the editor's syntax highlighting scheme.
4467 * @param [in] nID Selected color scheme sub menu id.
4469 void CMergeEditView::OnChangeScheme(UINT nID)
4471 CMergeDoc *pDoc = GetDocument();
4472 ASSERT(pDoc != nullptr);
4474 for (int nGroup = 0; nGroup < pDoc->m_nGroups; nGroup++)
4475 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4477 CMergeEditView *pView = pDoc->GetView(nGroup, nPane);
4478 ASSERT(pView != nullptr);
4480 if (pView != nullptr)
4482 pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
4483 pView->SetDisableBSAtSOL(false);
4484 pView->m_bChangedSchemeManually = true;
4492 * @brief Enable all color schemes sub menu items.
4493 * @param [in] pCmdUI Pointer to UI item to update.
4495 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4497 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4498 pCmdUI->SetRadio(bIsCurrentScheme);
4499 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4503 * @brief Called when mouse's wheel is scrolled.
4505 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4507 if ( nFlags == MK_CONTROL )
4509 short amount = zDelta < 0 ? -1: 1;
4512 // no default CCrystalTextView
4513 return CView::OnMouseWheel(nFlags, zDelta, pt);
4516 if (nFlags == MK_SHIFT)
4518 SCROLLINFO si = { sizeof SCROLLINFO };
4519 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4521 VERIFY(GetScrollInfo(SB_HORZ, &si));
4524 si.nPos -= zDelta / 40;
4525 if (si.nPos > si.nMax) si.nPos = si.nMax;
4526 if (si.nPos < si.nMin) si.nPos = si.nMin;
4528 SetScrollInfo(SB_HORZ, &si);
4531 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4533 // no default CCrystalTextView
4534 return CView::OnMouseWheel(nFlags, zDelta, pt);
4537 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4541 * @brief Called when mouse's horizontal wheel is scrolled.
4543 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4545 SCROLLINFO si = { sizeof SCROLLINFO };
4546 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4548 VERIFY(GetScrollInfo(SB_HORZ, &si));
4551 si.nPos += zDelta / 40;
4552 if (si.nPos > si.nMax) si.nPos = si.nMax;
4553 if (si.nPos < si.nMin) si.nPos = si.nMin;
4555 SetScrollInfo(SB_HORZ, &si);
4558 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4560 // no default CCrystalTextView
4561 CView::OnMouseHWheel(nFlags, zDelta, pt);
4565 * @brief Change font size (zoom) in views.
4566 * @param [in] amount Amount of change/zoom, negative number makes
4567 * font smaller, positive number bigger and 0 reset the font size.
4569 void CMergeEditView::ZoomText(short amount)
4574 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4575 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4579 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4582 nPointSize += amount;
4586 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4588 CMergeDoc *pDoc = GetDocument();
4589 ASSERT(pDoc != nullptr);
4591 if (pDoc != nullptr)
4593 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4595 CMergeEditView *pView = GetGroupView(nPane);
4596 ASSERT(pView != nullptr);
4598 if (pView != nullptr)
4606 bool CMergeEditView::QueryEditable()
4608 return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4612 * @brief Adjust the point to remain in the displayed diff
4614 * @return Tells if the point has been changed
4616 bool CMergeEditView::EnsureInDiff(CPoint& pt)
4618 int nLineCount = GetLineCount();
4619 if (m_lineBegin >= nLineCount)
4620 m_lineBegin = nLineCount - 1;
4621 if (m_lineEnd >= nLineCount)
4622 m_lineEnd = nLineCount - 1;
4624 int diffLength = m_lineEnd - m_lineBegin + 1;
4625 // first get the degenerate case out of the way
4627 if (diffLength == 0)
4629 if (pt.y == m_lineBegin && pt.x == 0)
4637 if (pt.y < m_lineBegin)
4643 // diff is defined and not below diff
4644 if (m_lineEnd > -1 && pt.y > m_lineEnd)
4647 pt.x = GetLineLength(pt.y);
4653 void CMergeEditView::EnsureVisible(CPoint pt)
4658 // ensure we remain in diff
4659 if (EnsureInDiff(ptNew))
4660 SetCursorPos(ptNew);
4662 CCrystalTextView::EnsureVisible(ptNew);
4665 void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
4667 CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4670 void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
4672 CPoint ptStartNew = ptStart;
4673 CPoint ptEndNew = ptEnd;
4676 // ensure we remain in diff
4677 EnsureInDiff(ptStartNew);
4678 EnsureInDiff(ptEndNew);
4680 CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4683 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4687 int nLineCount = GetLineCount();
4688 if (m_lineBegin >= nLineCount)
4689 m_lineBegin = nLineCount - 1;
4690 if (m_lineEnd >= nLineCount)
4691 m_lineEnd = nLineCount - 1;
4693 // ensure we remain in diff
4694 int sublineBegin = GetSubLineIndex(m_lineBegin);
4695 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4696 int diffLength = sublineEnd - sublineBegin + 1;
4697 int displayLength = GetScreenLines();
4698 if (diffLength <= displayLength)
4699 nNewTopLine = sublineBegin;
4702 if (nNewTopLine < sublineBegin)
4703 nNewTopLine = sublineBegin;
4704 if (nNewTopLine + displayLength - 1 > sublineEnd)
4705 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4708 CPoint pt = GetCursorPos();
4709 if (EnsureInDiff(pt))
4712 CPoint ptSelStart, ptSelEnd;
4713 GetSelection(ptSelStart, ptSelEnd);
4714 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4715 SetSelection(ptSelStart, ptSelEnd);
4717 CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4720 void CMergeEditView::SetActivePane()
4722 auto* pwndSplitterChild = GetParentSplitter(this, false);
4723 if (!pwndSplitterChild)
4725 if (pwndSplitterChild->GetColumnCount() > 1)
4726 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4728 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4732 * @brief Called when user selects View/Zoom In from menu.
4734 void CMergeEditView::OnViewZoomIn()
4740 * @brief Called when user selects View/Zoom Out from menu.
4742 void CMergeEditView::OnViewZoomOut()
4748 * @brief Called when user selects View/Zoom Normal from menu.
4750 void CMergeEditView::OnViewZoomNormal()
4755 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4757 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4759 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4763 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4766 void CMergeEditView::OnWindowSplit()
4769 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4770 CMergeDoc *pDoc = GetDocument();
4771 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4772 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4773 int nBuffer = m_nThisPane;
4774 if (pDoc->m_nGroups <= 2)
4776 wndSplitter.SplitRow(1);
4777 wndSplitter.EqualizeRows();
4781 wndSplitter.SetActivePane(0, 0);
4782 wndSplitter.DeleteRow(1);
4783 pDoc->GetView(0, nBuffer)->SetActivePane();
4787 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4789 pCmdUI->Enable(!m_bDetailView);
4790 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4793 void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
4796 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4797 const int pane = pNMItemActivate->iItem / 4;
4798 CMergeDoc* pDoc = GetDocument();
4799 if (pane >= pDoc->m_nBuffers || !GetParentFrame()->IsChild(CWnd::FromHandle(pNMItemActivate->hdr.hwndFrom)))
4802 switch (pNMItemActivate->iItem % 4)
4805 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4808 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
4813 ::GetCursorPos(&point);
4816 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4817 theApp.TranslateMenu(menu.m_hMenu);
4818 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4822 pDoc->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());