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"
41 #ifndef WM_MOUSEHWHEEL
42 # define WM_MOUSEHWHEEL 0x20e
46 using CrystalLineParser::TEXTBLOCK;
48 /** @brief Timer ID for delayed rescan. */
49 const UINT IDT_RESCAN = 2;
50 /** @brief Timer timeout for delayed rescan. */
51 const UINT RESCAN_TIMEOUT = 1000;
53 /** @brief Location for file compare specific help to open. */
54 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
56 /////////////////////////////////////////////////////////////////////////////
59 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
61 CMergeEditView::CMergeEditView()
62 : m_bCurrentLineIsDiff(false)
65 , m_bDetailView(false)
66 , m_piMergeEditStatus(nullptr)
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_FILE_OPEN_PARENT_FOLDER, OnOpenParentFolder)
225 ON_COMMAND(ID_SWAPPANES_SWAP12, OnViewSwapPanes12)
226 ON_COMMAND(ID_SWAPPANES_SWAP23, OnViewSwapPanes23)
227 ON_COMMAND(ID_SWAPPANES_SWAP13, OnViewSwapPanes13)
228 ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
231 ON_COMMAND(ID_HELP, OnHelp)
232 ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
233 ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
234 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
235 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
236 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
239 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
240 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
241 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
242 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
243 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
244 ON_NOTIFY(NM_DBLCLK, AFX_IDW_STATUS_BAR, OnStatusBarDblClick)
249 /////////////////////////////////////////////////////////////////////////////
250 // CMergeEditView diagnostics
253 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
255 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
256 return (CMergeDoc*)m_pDocument;
261 /////////////////////////////////////////////////////////////////////////////
262 // CMergeEditView message handlers
265 * @brief Return text buffer for file in view
267 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
269 return GetDocument()->m_ptBuf[m_nThisPane].get();
272 void CMergeEditView::CopyProperties(CCrystalTextView* pSource)
274 __super::CopyProperties(pSource);
275 auto pSourceEditView = dynamic_cast<decltype(this)>(pSource);
276 if (!pSourceEditView)
278 m_bChangedSchemeManually = pSourceEditView->m_bChangedSchemeManually;
282 * @brief Update any resources necessary after a GUI language change
284 void CMergeEditView::UpdateResources()
288 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
290 return GetDocument()->GetView(m_nThisGroup, nBuffer);
293 void CMergeEditView::PrimeListWithFile()
295 // Set the tab size now, just in case the options change...
296 // We don't update it at the end of OnOptions,
297 // we can update it safely now
298 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
301 * @brief Return text from line given
303 CString CMergeEditView::GetLineText(int idx)
305 return GetLineChars(idx);
309 * @brief Return text from selection
311 CString CMergeEditView::GetSelectedText()
314 auto [ptStart, ptEnd] = GetSelection();
315 if (ptStart != ptEnd)
316 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
321 * @brief Return number of selected characters
323 std::pair<int, int> CMergeEditView::GetSelectedLineAndCharacterCount()
325 auto [ptStart, ptEnd] = GetSelection();
326 int nCharsOrColumns =0;
327 int nSelectedLines = 0;
328 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
330 if ((GetLineFlags(nLine) & (LF_GHOST | LF_INVISIBLE)) == 0)
332 int nLineLength = GetLineLength(nLine) + (m_pTextBuffer->GetLineEol(nLine)[0] ? 1 : 0);
333 nCharsOrColumns += (nLine == ptEnd.y) ? ptEnd.x : nLineLength;
334 if (nLine == ptStart.y)
335 nCharsOrColumns -= ptStart.x;
336 if (nLine < ptEnd.y || (ptStart != ptEnd && ptEnd.x > 0))
340 if (m_bRectangularSelection)
342 int nStartLeft, nStartRight, nEndLeft, nEndRight;
343 GetColumnSelection(ptStart.y, nStartLeft, nStartRight);
344 GetColumnSelection(ptEnd.y, nEndLeft, nEndRight);
345 nCharsOrColumns = (std::max)(nStartRight, nEndRight) - (std::min)(nStartLeft, nEndLeft);
347 return { nSelectedLines, nCharsOrColumns };
351 * @brief Get diffs inside selection.
352 * @param [out] firstDiff First diff inside selection
353 * @param [out] lastDiff Last diff inside selection
354 * @note -1 is returned in parameters if diffs cannot be determined
355 * @todo This shouldn't be called when there is no diffs, so replace
356 * first 'if' with ASSERT()?
358 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
363 CMergeDoc *pd = GetDocument();
364 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
368 int firstLine, lastLine;
369 GetFullySelectedLines(firstLine, lastLine);
370 if (lastLine < firstLine)
373 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
374 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
375 if (firstDiff != -1 && lastDiff != -1)
379 // Check that first selected line is first diff's first line or above it
380 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
381 if ((int)di.dbegin < firstLine)
383 if (firstDiff < lastDiff)
387 // Check that last selected line is last diff's last line or below it
388 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
389 if ((int)di.dend > lastLine)
391 if (firstDiff < lastDiff)
395 // Special case: one-line diff is not selected if cursor is in it
396 if (firstLine == lastLine)
405 * @brief Get diffs inside selection.
406 * @param [out] firstDiff First diff inside selection
407 * @param [out] lastDiff Last diff inside selection
408 * @param [out] firstWordDiff First word level diff inside selection
409 * @param [out] lastWordDiff Last word level diff inside selection
410 * @note -1 is returned in parameters if diffs cannot be determined
411 * @todo This shouldn't be called when there is no diffs, so replace
412 * first 'if' with ASSERT()?
414 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
421 CMergeDoc *pd = GetDocument();
422 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
426 int firstLine, lastLine;
427 auto [ptStart, ptEnd] = GetSelection();
428 if (pptStart != nullptr)
430 if (pptEnd != nullptr)
432 firstLine = ptStart.y;
435 firstDiff = pd->m_diffList.LineToDiff(firstLine);
436 bool firstLineIsNotInDiff = firstDiff == -1;
439 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
444 lastDiff = pd->m_diffList.LineToDiff(lastLine);
445 bool lastLineIsNotInDiff = lastDiff == -1;
447 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
448 if (lastDiff < firstDiff)
455 if (firstDiff != -1 && lastDiff != -1)
459 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
461 firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
463 else if (ptStart != ptEnd)
465 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
466 if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
472 if (firstWordDiff == -1)
474 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
475 for (size_t i = 0; i < worddiffs.size(); ++i)
477 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
478 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
479 (firstLine == worddiffs[i].endline[m_nThisPane] &&
480 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
482 firstWordDiff = static_cast<int>(i);
487 if (firstLine >= di.dbegin && firstWordDiff == -1)
494 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
495 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
496 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
498 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
499 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
501 lastWordDiff = static_cast<int>(i);
506 if (lastLine <= di.dend && lastWordDiff == -1)
509 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
516 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
533 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
534 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
535 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
538 void CMergeEditView::GetSelectedDiffs(int & firstDiff, int & lastDiff)
543 CMergeDoc *pd = GetDocument();
544 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
548 int firstLine, lastLine;
549 auto [ptStart, ptEnd] = GetSelection();
550 firstLine = ptStart.y;
553 firstDiff = pd->m_diffList.LineToDiff(firstLine);
556 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
560 lastDiff = pd->m_diffList.LineToDiff(lastLine);
562 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
563 if (lastDiff < firstDiff)
569 ASSERT(firstDiff == -1 ? (lastDiff == -1) : true);
570 ASSERT(lastDiff == -1 ? (firstDiff == -1) : true);
573 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
575 CMergeDoc *pDoc = GetDocument();
576 std::map<int, std::vector<int>> ret;
577 std::map<int, std::vector<int> *> list;
578 auto [ptStart, ptEnd] = GetSelection();
579 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
581 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
583 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
585 GetColumnSelection(nLine, nLeft, nRight);
586 CPoint ptStart2, ptEnd2;
589 ptStart2.y = ptEnd2.y = nLine;
590 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
591 if (firstDiff != -1 && lastDiff != -1)
593 std::vector<int> *pWordDiffs;
594 if (list.find(firstDiff) == list.end())
595 list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
596 pWordDiffs = list[firstDiff];
597 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
599 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
600 pWordDiffs->push_back(i);
605 for (auto& it : list)
606 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
610 void CMergeEditView::OnInitialUpdate()
613 CCrystalEditViewEx::OnInitialUpdate();
615 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
616 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
622 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
624 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
626 CMergeDoc* pDoc = GetDocument();
627 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
630 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
634 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
635 return std::vector<CrystalLineParser::TEXTBLOCK>();
637 return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
640 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
642 static const std::vector<TEXTBLOCK> emptyBlocks;
645 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
649 DWORD dwLineFlags = GetLineFlags(nLineIndex);
650 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
653 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
656 CMergeDoc *pDoc = GetDocument();
657 if (pDoc->IsEditedAfterRescan(m_nThisPane))
660 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
665 pDoc->m_diffList.GetDiff(nDiff, cd);
666 int unemptyLineCount = 0;
667 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
669 if (cd.begin[nPane] != cd.end[nPane] + 1)
672 if (unemptyLineCount < 2)
675 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
676 size_t nWordDiffs = worddiffs.size();
678 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
680 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
681 blocks[0].m_nCharPos = 0;
682 blocks[0].m_nColorIndex = COLORINDEX_NONE;
683 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
685 for (i = 0, j = 1; i < nWordDiffs; i++)
687 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
689 if (pDoc->m_nBuffers > 2)
691 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
693 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
696 int begin[3], end[3];
697 bool deleted = false;
698 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
700 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
701 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
702 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
703 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
706 blocks[j].m_nCharPos = begin[m_nThisPane];
707 if (lineInCurrentDiff)
709 if (m_cachedColors.clrSelDiffText != CLR_NONE)
710 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
712 blocks[j].m_nColorIndex = COLORINDEX_NONE;
713 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
714 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
718 if (m_cachedColors.clrDiffText != CLR_NONE)
719 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
721 blocks[j].m_nColorIndex = COLORINDEX_NONE;
722 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
723 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
726 blocks[j].m_nCharPos = end[m_nThisPane];
727 blocks[j].m_nColorIndex = COLORINDEX_NONE;
728 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
737 COLORREF CMergeEditView::GetColor(int nColorIndex) const
739 switch (nColorIndex & ~COLORINDEX_MASK)
741 case COLORINDEX_HIGHLIGHTBKGND1:
742 return m_cachedColors.clrSelWordDiff;
743 case COLORINDEX_HIGHLIGHTTEXT1:
744 return m_cachedColors.clrSelWordDiffText;
745 case COLORINDEX_HIGHLIGHTBKGND2:
746 return m_cachedColors.clrWordDiff;
747 case COLORINDEX_HIGHLIGHTTEXT2:
748 return m_cachedColors.clrWordDiffText;
749 case COLORINDEX_HIGHLIGHTBKGND3:
750 return m_cachedColors.clrWordDiffDeleted;
751 case COLORINDEX_HIGHLIGHTBKGND4:
752 return m_cachedColors.clrSelWordDiffDeleted;
755 return CCrystalTextView::GetColor(nColorIndex);
760 * @brief Determine text and background color for line
761 * @param [in] nLineIndex Index of line in view (NOT line in file)
762 * @param [out] crBkgnd Backround color for line
763 * @param [out] crText Text color for line
765 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
766 COLORREF & crText, bool & bDrawWhitespace)
768 DWORD ignoreFlags = 0;
769 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
773 * @brief Determine text and background color for line
774 * @param [in] nLineIndex Index of line in view (NOT line in file)
775 * @param [in] ignoreFlags Flags that caller wishes ignored
776 * @param [out] crBkgnd Backround color for line
777 * @param [out] crText Text color for line
779 * This version allows caller to suppress particular flags
781 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
782 COLORREF & crText, bool & bDrawWhitespace)
784 if (GetLineCount() <= nLineIndex)
787 DWORD dwLineFlags = GetLineFlags(nLineIndex);
789 if (dwLineFlags & ignoreFlags)
790 dwLineFlags &= (~ignoreFlags);
794 // Line with WinMerge flag,
795 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
796 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
798 crText = m_cachedColors.clrDiffText;
799 bDrawWhitespace = true;
801 if (dwLineFlags & LF_GHOST)
803 crBkgnd = m_cachedColors.clrDiffDeleted;
808 // If no syntax hilighting
809 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
811 crBkgnd = GetColor (COLORINDEX_BKGND);
812 crText = GetColor (COLORINDEX_NORMALTEXT);
813 bDrawWhitespace = false;
816 // Line not inside diff, get colors from CrystalEditor
817 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
818 crText, bDrawWhitespace);
820 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
822 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
823 crText = GetColor (COLORINDEX_WHITESPACE);
824 bDrawWhitespace = false;
830 if (dwLineFlags & LF_WINMERGE_FLAGS)
832 crText = m_cachedColors.clrDiffText;
833 bDrawWhitespace = true;
834 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
836 if (dwLineFlags & LF_SNP)
838 if (lineInCurrentDiff)
840 if (dwLineFlags & LF_GHOST)
841 crBkgnd = m_cachedColors.clrSelSNPDeleted;
843 crBkgnd = m_cachedColors.clrSelSNP;
844 crText = m_cachedColors.clrSelSNPText;
848 if (dwLineFlags & LF_GHOST)
849 crBkgnd = m_cachedColors.clrSNPDeleted;
851 crBkgnd = m_cachedColors.clrSNP;
852 crText = m_cachedColors.clrSNPText;
856 else if (dwLineFlags & LF_DIFF)
858 if (lineInCurrentDiff)
860 if (dwLineFlags & LF_MOVED)
862 crBkgnd = m_cachedColors.clrSelMoved;
863 crText = m_cachedColors.clrSelMovedText;
867 crBkgnd = m_cachedColors.clrSelDiff;
868 crText = m_cachedColors.clrSelDiffText;
874 if (dwLineFlags & LF_MOVED)
876 crBkgnd = m_cachedColors.clrMoved;
877 crText = m_cachedColors.clrMovedText;
881 crBkgnd = m_cachedColors.clrDiff;
882 crText = m_cachedColors.clrDiffText;
887 else if (dwLineFlags & LF_TRIVIAL)
889 // trivial diff can not be selected
890 if (dwLineFlags & LF_GHOST)
891 // ghost lines in trivial diff has their own color
892 crBkgnd = m_cachedColors.clrTrivialDeleted;
894 crBkgnd = m_cachedColors.clrTrivial;
895 crText = m_cachedColors.clrTrivialText;
898 else if (dwLineFlags & LF_GHOST)
900 if (lineInCurrentDiff)
902 if (dwLineFlags & LF_MOVED)
903 crBkgnd = m_cachedColors.clrSelMovedDeleted;
905 crBkgnd = m_cachedColors.clrSelDiffDeleted;
909 if (dwLineFlags & LF_MOVED)
910 crBkgnd = m_cachedColors.clrMovedDeleted;
912 crBkgnd = m_cachedColors.clrDiffDeleted;
919 // Line not inside diff,
920 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
922 // If no syntax hilighting, get windows default colors
923 crBkgnd = GetColor (COLORINDEX_BKGND);
924 crText = GetColor (COLORINDEX_NORMALTEXT);
925 bDrawWhitespace = false;
928 // Syntax highlighting, get colors from CrystalEditor
929 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
930 crText, bDrawWhitespace);
935 * @brief Sync other pane position
937 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
939 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
940 if (pSplitterWnd != nullptr)
942 // See CSplitterWnd::IdFromRowCol() implementation for details
943 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
944 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
945 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
946 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
948 // limit the TopLine : must be smaller than GetLineCount for all the panels
949 int newTopSubLine = m_nTopSubLine;
950 int nRows = pSplitterWnd->GetRowCount ();
951 int nCols = pSplitterWnd->GetColumnCount ();
953 // for (nRow = 0; nRow < nRows; nRow++)
955 // for (int nCol = 0; nCol < nCols; nCol++)
957 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
958 // if (pSiblingView != nullptr)
959 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
960 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
963 if (m_nTopSubLine != newTopSubLine)
964 ScrollToSubLine(newTopSubLine);
966 for (nRow = 0; nRow < nRows; nRow++)
968 for (int nCol = 0; nCol < nCols; nCol++)
970 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
972 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
973 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
974 pSiblingView->OnUpdateSibling (this, bHorz);
982 * @brief Update other panes
984 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
986 if (pUpdateSource != this)
988 ASSERT (pUpdateSource != nullptr);
989 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
990 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
991 if (!bHorz) // changed this so bHorz works right
993 ASSERT (pSrcView->m_nTopSubLine >= 0);
995 // This ASSERT is wrong: panes have different files and
996 // different linecounts
997 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
998 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
1000 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
1002 RecalcVertScrollBar(true);
1003 RecalcHorzScrollBar();
1008 ASSERT (pSrcView->m_nOffsetChar >= 0);
1010 // This ASSERT is wrong: panes have different files and
1011 // different linelengths
1012 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
1013 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
1015 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
1017 RecalcHorzScrollBar(true);
1018 RecalcHorzScrollBar();
1024 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
1026 int newlineBegin, newlineEnd;
1027 CMergeDoc *pd = GetDocument();
1028 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
1036 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
1038 newlineBegin = curDiff.dbegin;
1039 ASSERT (newlineBegin >= 0);
1040 newlineEnd = curDiff.dend;
1043 m_lineBegin = newlineBegin;
1044 m_lineEnd = newlineEnd;
1046 int nLineCount = GetLineCount();
1047 if (m_lineBegin > nLineCount)
1048 m_lineBegin = nLineCount - 1;
1049 if (m_lineEnd > nLineCount)
1050 m_lineEnd = nLineCount - 1;
1052 if (m_nTopLine == newlineBegin)
1055 // scroll to the first line of the diff
1056 vector<WordDiff> worddiffs;
1057 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
1058 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
1059 CPoint pt = worddiffs.size() > 0 ?
1060 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
1061 CPoint{ 0, m_lineBegin };
1062 ScrollToLine(m_lineBegin);
1066 // update the width of the horizontal scrollbar
1067 RecalcHorzScrollBar();
1071 * @brief Selects diff by number and syncs other file
1072 * @param [in] nDiff Diff to select, must be >= 0
1073 * @param [in] bScroll Scroll diff to view
1074 * @param [in] bSelectText Select diff text
1075 * @sa CMergeEditView::ShowDiff()
1076 * @sa CMergeDoc::SetCurrentDiff()
1077 * @todo Parameter bSelectText is never used?
1079 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
1081 CMergeDoc *pd = GetDocument();
1083 // Check that nDiff is valid
1085 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
1086 if (nDiff >= pd->m_diffList.GetSize())
1087 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
1088 nDiff, pd->m_diffList.GetSize());
1091 pd->SetCurrentDiff(nDiff);
1092 ShowDiff(bScroll, bSelectText);
1093 pd->UpdateAllViews(this);
1094 UpdateSiblingScrollPos(false);
1096 // notify either side, as it will notify the other one
1097 pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1100 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1102 CMergeDoc *pd = GetDocument();
1103 // If we have a selected diff, deselect it
1104 int nCurrentDiff = pd->GetCurrentDiff();
1105 if (nCurrentDiff != -1)
1107 CPoint pos = GetCursorPos();
1108 if (!IsLineInCurrentDiff(pos.y))
1110 pd->SetCurrentDiff(-1);
1112 pd->UpdateAllViews(this);
1118 * @brief Called when user selects "Current Difference".
1119 * Goes to active diff. If no active diff, selects diff under cursor
1120 * @sa CMergeEditView::SelectDiff()
1121 * @sa CMergeDoc::GetCurrentDiff()
1122 * @sa CMergeDoc::LineToDiff()
1124 void CMergeEditView::OnCurdiff()
1126 CMergeDoc *pd = GetDocument();
1128 // If no diffs, nothing to select
1129 if (!pd->m_diffList.HasSignificantDiffs())
1132 // GetCurrentDiff() returns -1 if no diff selected
1133 int nDiff = pd->GetCurrentDiff();
1136 // Scroll to the first line of the currently selected diff
1137 SelectDiff(nDiff, true, false);
1141 // If cursor is inside diff, select that diff
1142 CPoint pos = GetCursorPos();
1143 nDiff = pd->m_diffList.LineToDiff(pos.y);
1144 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1145 SelectDiff(nDiff, true, false);
1150 * @brief Called when "Current diff" item is updated
1152 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1154 CMergeDoc *pd = GetDocument();
1155 CPoint pos = GetCursorPos();
1156 int nCurrentDiff = pd->GetCurrentDiff();
1157 if (nCurrentDiff == -1)
1159 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1160 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1163 pCmdUI->Enable(true);
1167 * @brief Copy selected text to clipboard
1169 void CMergeEditView::OnEditCopy()
1171 CMergeDoc * pDoc = GetDocument();
1172 auto [ptSelStart, ptSelEnd] = GetSelection();
1175 if (ptSelStart == ptSelEnd)
1180 if (!m_bRectangularSelection)
1182 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1184 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1185 ptSelEnd.y, ptSelEnd.x, text);
1188 GetTextWithoutEmptysInColumnSelection(text);
1190 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1194 * @brief Called when "Copy" item is updated
1196 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1198 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1202 * @brief Cut current selection to clipboard
1204 void CMergeEditView::OnEditCut()
1206 if (!QueryEditable())
1209 CMergeDoc * pDoc = GetDocument();
1210 auto [ptSelStart, ptSelEnd] = GetSelection();
1213 if (ptSelStart == ptSelEnd)
1217 if (!m_bRectangularSelection)
1218 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1219 ptSelEnd.y, ptSelEnd.x, text);
1221 GetTextWithoutEmptysInColumnSelection(text);
1223 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1225 if (!m_bRectangularSelection)
1227 CPoint ptCursorPos = ptSelStart;
1228 ASSERT_VALIDTEXTPOS(ptCursorPos);
1229 SetAnchor(ptCursorPos);
1230 SetSelection(ptCursorPos, ptCursorPos);
1231 SetCursorPos(ptCursorPos);
1232 EnsureVisible(ptCursorPos);
1234 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1235 ptSelEnd.x, CE_ACTION_CUT);
1238 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1240 m_pTextBuffer->SetModified(true);
1244 * @brief Called when "Cut" item is updated
1246 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1248 if (QueryEditable())
1249 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1251 pCmdUI->Enable(false);
1255 * @brief Paste text from clipboard
1257 void CMergeEditView::OnEditPaste()
1259 if (!QueryEditable())
1262 CCrystalEditViewEx::Paste();
1263 m_pTextBuffer->SetModified(true);
1267 * @brief Called when "Paste" item is updated
1269 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1271 if (QueryEditable())
1272 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1274 pCmdUI->Enable(false);
1278 * @brief Undo last action
1280 void CMergeEditView::OnEditUndo()
1282 CWaitCursor waitstatus;
1283 CMergeDoc* pDoc = GetDocument();
1284 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1287 if (!QueryEditable())
1290 GetParentFrame()->SetActiveView(this, true);
1291 if(CCrystalEditViewEx::DoEditUndo())
1294 pDoc->UpdateHeaderPath(m_nThisPane);
1295 pDoc->FlushAndRescan();
1298 m_pTextBuffer->GetRedoActionCode(nAction);
1299 if (nAction == CE_ACTION_MERGE)
1300 // select the diff so we may just merge it again
1306 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1308 if (!pDoc->CanUndo())
1309 pDoc->SetAutoMerged(false);
1313 * @brief Called when "Undo" item is updated
1315 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1317 CMergeDoc* pDoc = GetDocument();
1318 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1320 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1321 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1324 pCmdUI->Enable(false);
1328 * @brief Go to first diff
1330 * Called when user selects "First Difference"
1331 * @sa CMergeEditView::SelectDiff()
1333 void CMergeEditView::OnFirstdiff()
1335 CMergeDoc *pd = GetDocument();
1336 if (pd->m_diffList.HasSignificantDiffs())
1338 int nDiff = pd->m_diffList.FirstSignificantDiff();
1339 SelectDiff(nDiff, true, false);
1344 * @brief Update "First diff" UI items
1346 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1348 OnUpdatePrevdiff(pCmdUI);
1352 * @brief Go to last diff
1354 void CMergeEditView::OnLastdiff()
1356 CMergeDoc *pd = GetDocument();
1357 if (pd->m_diffList.HasSignificantDiffs())
1359 int nDiff = pd->m_diffList.LastSignificantDiff();
1360 SelectDiff(nDiff, true, false);
1365 * @brief Update "Last diff" UI items
1367 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1369 OnUpdateNextdiff(pCmdUI);
1373 * @brief Go to next diff and select it.
1375 * Finds and selects next difference. There are several cases:
1376 * - if there is selected difference, and that difference is visible
1377 * on screen, next found difference is selected.
1378 * - if there is selected difference but it is not visible, next
1379 * difference from cursor position is selected. This is what user
1380 * expects to happen and is natural thing to do. Also reduces
1381 * needless scrolling.
1382 * - if there is no selected difference, next difference from cursor
1383 * position is selected.
1385 void CMergeEditView::OnNextdiff()
1387 CMergeDoc *pd = GetDocument();
1388 int cnt = pd->m_ptBuf[0]->GetLineCount();
1392 // Returns -1 if no diff selected
1394 int curDiff = pd->GetCurrentDiff();
1398 if (!IsDiffVisible(curDiff))
1400 // Selected difference not visible, select next from cursor
1401 int line = GetCursorPos().y;
1402 // Make sure we aren't in the first line of the diff
1404 if (!IsValidTextPosY(CPoint(0, line)))
1406 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1410 // Find out if there is a following significant diff
1411 if (curDiff < pd->m_diffList.GetSize() - 1)
1413 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1419 // We don't have a selected difference,
1420 // but cursor can be inside inactive diff
1421 int line = GetCursorPos().y;
1422 if (!IsValidTextPosY(CPoint(0, line)))
1424 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1427 int lastDiff = pd->m_diffList.LastSignificantDiff();
1428 if (nextDiff >= 0 && nextDiff <= lastDiff)
1429 SelectDiff(nextDiff, true, false);
1430 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1432 if (pDirDoc->MoveableToNextDiff())
1433 pDirDoc->MoveToNextDiff(pd);
1438 * @brief Update "Next diff" UI items
1440 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1442 CMergeDoc *pd = GetDocument();
1443 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1448 // There aren't any significant differences
1453 // Enable if the beginning of the last significant difference is after caret
1454 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1457 if (!enabled && pd->GetDirDoc())
1458 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1460 pCmdUI->Enable(enabled);
1464 * @brief Go to previous diff and select it.
1466 * Finds and selects previous difference. There are several cases:
1467 * - if there is selected difference, and that difference is visible
1468 * on screen, previous found difference is selected.
1469 * - if there is selected difference but it is not visible, previous
1470 * difference from cursor position is selected. This is what user
1471 * expects to happen and is natural thing to do. Also reduces
1472 * needless scrolling.
1473 * - if there is no selected difference, previous difference from cursor
1474 * position is selected.
1476 void CMergeEditView::OnPrevdiff()
1478 CMergeDoc *pd = GetDocument();
1479 int cnt = pd->m_ptBuf[0]->GetLineCount();
1483 // GetCurrentDiff() returns -1 if no diff selected
1485 int curDiff = pd->GetCurrentDiff();
1489 if (!IsDiffVisible(curDiff))
1491 // Selected difference not visible, select previous from cursor
1492 int line = GetCursorPos().y;
1493 // Make sure we aren't in the last line of the diff
1495 if (!IsValidTextPosY(CPoint(0, line)))
1497 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1501 // Find out if there is a preceding significant diff
1504 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1510 // We don't have a selected difference,
1511 // but cursor can be inside inactive diff
1512 int line = GetCursorPos().y;
1513 if (!IsValidTextPosY(CPoint(0, line)))
1515 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1518 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1519 if (prevDiff >= 0 && prevDiff >= firstDiff)
1520 SelectDiff(prevDiff, true, false);
1521 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1523 if (pDirDoc->MoveableToPrevDiff())
1524 pDirDoc->MoveToPrevDiff(pd);
1529 * @brief Update "Previous diff" UI items
1531 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1533 CMergeDoc *pd = GetDocument();
1534 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1539 // There aren't any significant differences
1544 // Enable if the end of the first significant difference is before caret
1545 enabled = (GetCursorPos().y > (long)dfi->dend);
1548 if (!enabled && pd->GetDirDoc())
1549 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1551 pCmdUI->Enable(enabled);
1554 void CMergeEditView::OnNextConflict()
1556 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1560 * @brief Update "Next Conflict" UI items
1562 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1564 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1567 void CMergeEditView::OnPrevConflict()
1569 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1573 * @brief Update "Prev Conflict" UI items
1575 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1577 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1581 * @brief Go to next 3-way diff and select it.
1583 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1585 CMergeDoc *pd = GetDocument();
1586 int cnt = pd->m_ptBuf[0]->GetLineCount();
1590 // Returns -1 if no diff selected
1591 int curDiff = pd->GetCurrentDiff();
1595 int nextDiff = curDiff;
1596 if (!IsDiffVisible(curDiff))
1598 // Selected difference not visible, select next from cursor
1599 int line = GetCursorPos().y;
1600 // Make sure we aren't in the first line of the diff
1602 if (!IsValidTextPosY(CPoint(0, line)))
1604 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1608 // Find out if there is a following significant diff
1609 if (curDiff < pd->m_diffList.GetSize() - 1)
1611 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1617 // nextDiff is the next one if there is one, else it is the one we're on
1618 SelectDiff(nextDiff, true, false);
1622 // We don't have a selected difference,
1623 // but cursor can be inside inactive diff
1624 int line = GetCursorPos().y;
1625 if (!IsValidTextPosY(CPoint(0, line)))
1627 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1629 SelectDiff(curDiff, true, false);
1634 * @brief Update "Next 3-way diff" UI items
1636 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1638 CMergeDoc *pd = GetDocument();
1640 if (pd->m_nBuffers < 3)
1642 pCmdUI->Enable(false);
1646 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1650 // There aren't any significant differences
1651 pCmdUI->Enable(false);
1655 // Enable if the beginning of the last significant difference is after caret
1656 CPoint pos = GetCursorPos();
1657 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1662 * @brief Go to previous 3-way diff and select it.
1664 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1666 CMergeDoc *pd = GetDocument();
1668 int cnt = pd->m_ptBuf[0]->GetLineCount();
1672 // GetCurrentDiff() returns -1 if no diff selected
1673 int curDiff = pd->GetCurrentDiff();
1677 int prevDiff = curDiff;
1678 if (!IsDiffVisible(curDiff))
1680 // Selected difference not visible, select previous from cursor
1681 int line = GetCursorPos().y;
1682 // Make sure we aren't in the last line of the diff
1684 if (!IsValidTextPosY(CPoint(0, line)))
1686 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1690 // Find out if there is a preceding significant diff
1693 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1699 // prevDiff is the preceding one if there is one, else it is the one we're on
1700 SelectDiff(prevDiff, true, false);
1704 // We don't have a selected difference,
1705 // but cursor can be inside inactive diff
1706 int line = GetCursorPos().y;
1707 if (!IsValidTextPosY(CPoint(0, line)))
1709 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1711 SelectDiff(curDiff, true, false);
1716 * @brief Update "Previous diff X and Y" UI items
1718 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1720 CMergeDoc *pd = GetDocument();
1722 if (pd->m_nBuffers < 3)
1724 pCmdUI->Enable(false);
1728 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1732 // There aren't any significant differences
1733 pCmdUI->Enable(false);
1737 // Enable if the end of the first significant difference is before caret
1738 CPoint pos = GetCursorPos();
1739 pCmdUI->Enable(pos.y > (long)dfi->dend);
1743 void CMergeEditView::OnNextdiffLM()
1745 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1748 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1750 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1753 void CMergeEditView::OnNextdiffLR()
1755 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1758 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1760 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1763 void CMergeEditView::OnNextdiffMR()
1765 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1768 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1770 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1773 void CMergeEditView::OnNextdiffLO()
1775 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1778 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1780 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1783 void CMergeEditView::OnNextdiffMO()
1785 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1788 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1790 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1793 void CMergeEditView::OnNextdiffRO()
1795 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1798 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1800 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1803 void CMergeEditView::OnPrevdiffLM()
1805 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1808 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1810 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1813 void CMergeEditView::OnPrevdiffLR()
1815 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1818 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1820 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1823 void CMergeEditView::OnPrevdiffMR()
1825 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1828 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1830 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1833 void CMergeEditView::OnPrevdiffLO()
1835 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1838 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1840 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1843 void CMergeEditView::OnPrevdiffMO()
1845 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1848 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1850 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1853 void CMergeEditView::OnPrevdiffRO()
1855 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1858 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1860 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1864 * @brief Clear selection
1866 void CMergeEditView::SelectNone()
1868 SetSelection (GetCursorPos(), GetCursorPos());
1873 * @brief Check if line is inside currently selected diff
1874 * @param [in] nLine 0-based linenumber in view
1875 * @sa CMergeDoc::GetCurrentDiff()
1876 * @sa CMergeDoc::LineInDiff()
1878 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1880 // Check validity of nLine
1883 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1884 int nLineCount = LocateTextBuffer()->GetLineCount();
1885 if (nLine >= nLineCount)
1886 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1889 const CMergeDoc *pd = GetDocument();
1890 int curDiff = pd->GetCurrentDiff();
1893 return pd->m_diffList.LineInDiff(nLine, curDiff);
1897 * @brief Called when mouse left-button double-clicked
1899 * Double-clicking mouse inside diff selects that diff
1901 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1903 CMergeDoc *pd = GetDocument();
1904 CPoint pos = GetCursorPos();
1906 int diff = pd->m_diffList.LineToDiff(pos.y);
1907 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1908 SelectDiff(diff, false, false);
1910 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1914 * @brief Called when mouse left button is released.
1916 * If button is released outside diffs, current diff
1919 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1921 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1922 DeselectDiffIfCursorNotInCurrentDiff();
1926 * @brief Called when mouse right button is pressed.
1928 * If right button is pressed outside diffs, current diff
1931 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1933 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1934 DeselectDiffIfCursorNotInCurrentDiff();
1937 void CMergeEditView::OnX2Y(int srcPane, int dstPane, bool selectedLineOnly)
1939 // Check that right side is not readonly
1940 if (IsReadOnly(dstPane))
1943 CMergeDoc *pDoc = GetDocument();
1944 int currentDiff = pDoc->GetCurrentDiff();
1946 if (currentDiff == -1)
1949 // If cursor is inside diff get number of that diff
1950 if (m_bCurrentLineIsDiff)
1952 CPoint pt = GetCursorPos();
1953 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1957 auto [ptStart, ptEnd] = GetSelection();
1958 if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1960 if (!m_bRectangularSelection)
1962 if (selectedLineOnly)
1964 int firstDiff, lastDiff;
1965 GetSelectedDiffs(firstDiff, lastDiff);
1966 if (firstDiff != -1 && lastDiff != -1)
1968 CWaitCursor waitstatus;
1969 pDoc->CopyMultiplePartialList(srcPane, dstPane, firstDiff, lastDiff, ptStart.y, ptEnd.y);
1974 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1975 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1976 if (firstDiff != -1 && lastDiff != -1)
1978 CWaitCursor waitstatus;
1980 // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1981 // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1982 if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1984 // old behaviour: copy full line
1985 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1989 // new behaviour: copy selected text only
1990 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1997 CWaitCursor waitstatus;
1998 auto wordDiffs = GetColumnSelectedWordDiffIndice();
2000 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
2001 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
2006 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
2008 if (selectedLineOnly)
2010 CWaitCursor waitstatus;
2011 pDoc->PartialListCopy(srcPane, dstPane, currentDiff, ptStart.y, ptEnd.y);
2015 CWaitCursor waitstatus;
2016 pDoc->ListCopy(srcPane, dstPane, currentDiff);
2021 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
2023 // Check that right side is not readonly
2024 if (!IsReadOnly(dstPane))
2026 // If one or more diffs inside selection OR
2027 // there is an active diff OR
2028 // cursor is inside diff
2029 auto [ptStart, ptEnd] = GetSelection();
2030 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
2032 if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
2034 pCmdUI->Enable(true);
2038 int firstDiff, lastDiff;
2039 GetFullySelectedDiffs(firstDiff, lastDiff);
2041 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
2046 const int currDiff = GetDocument()->GetCurrentDiff();
2047 pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
2051 pCmdUI->Enable(false);
2055 * @brief Copy diff from left pane to right pane
2057 * Difference is copied from left to right when
2058 * - difference is selected
2059 * - difference is inside selection (allows merging multiple differences).
2060 * - cursor is inside diff
2062 * If there is selected diff outside selection, we copy selected
2065 void CMergeEditView::OnL2r()
2067 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2068 int srcPane = dstPane - 1;
2069 OnX2Y(srcPane, dstPane);
2073 * @brief Called when "Copy to left" item is updated
2075 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
2077 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2080 void CMergeEditView::OnLinesL2r()
2082 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2083 int srcPane = dstPane - 1;
2084 OnX2Y(srcPane, dstPane, true);
2087 void CMergeEditView::OnUpdateLinesL2r(CCmdUI* pCmdUI)
2089 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2093 * @brief Copy diff from right pane to left pane
2095 * Difference is copied from left to right when
2096 * - difference is selected
2097 * - difference is inside selection (allows merging multiple differences).
2098 * - cursor is inside diff
2100 * If there is selected diff outside selection, we copy selected
2103 void CMergeEditView::OnR2l()
2105 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2106 int srcPane = dstPane + 1;
2107 OnX2Y(srcPane, dstPane);
2111 * @brief Called when "Copy to right" item is updated
2113 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
2115 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2118 void CMergeEditView::OnLinesR2l()
2120 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2121 int srcPane = dstPane + 1;
2122 OnX2Y(srcPane, dstPane, true);
2125 void CMergeEditView::OnUpdateLinesR2l(CCmdUI* pCmdUI)
2127 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2130 void CMergeEditView::OnCopyFromLeft()
2132 int dstPane = m_nThisPane;
2133 int srcPane = dstPane - 1;
2136 OnX2Y(srcPane, dstPane);
2139 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2141 int dstPane = m_nThisPane;
2142 int srcPane = dstPane - 1;
2144 pCmdUI->Enable(false);
2146 OnUpdateX2Y(dstPane, pCmdUI);
2149 void CMergeEditView::OnCopyLinesFromLeft()
2151 int dstPane = m_nThisPane;
2152 int srcPane = dstPane - 1;
2155 OnX2Y(srcPane, dstPane, true);
2158 void CMergeEditView::OnUpdateCopyLinesFromLeft(CCmdUI* pCmdUI)
2160 int dstPane = m_nThisPane;
2161 int srcPane = dstPane - 1;
2163 pCmdUI->Enable(false);
2165 OnUpdateX2Y(dstPane, pCmdUI);
2168 void CMergeEditView::OnCopyFromRight()
2170 int dstPane = m_nThisPane;
2171 int srcPane = dstPane + 1;
2172 if (srcPane >= GetDocument()->m_nBuffers)
2174 OnX2Y(srcPane, dstPane);
2177 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2179 int dstPane = m_nThisPane;
2180 int srcPane = dstPane + 1;
2181 if (srcPane >= GetDocument()->m_nBuffers)
2182 pCmdUI->Enable(false);
2184 OnUpdateX2Y(dstPane, pCmdUI);
2187 void CMergeEditView::OnCopyLinesFromRight()
2189 int dstPane = m_nThisPane;
2190 int srcPane = dstPane + 1;
2191 if (srcPane >= GetDocument()->m_nBuffers)
2193 OnX2Y(srcPane, dstPane, true);
2196 void CMergeEditView::OnUpdateCopyLinesFromRight(CCmdUI* pCmdUI)
2198 int dstPane = m_nThisPane;
2199 int srcPane = dstPane + 1;
2200 if (srcPane >= GetDocument()->m_nBuffers)
2201 pCmdUI->Enable(false);
2203 OnUpdateX2Y(dstPane, pCmdUI);
2207 * @brief Copy all diffs from right pane to left pane
2209 void CMergeEditView::OnAllLeft()
2211 // Check that left side is not readonly
2212 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
2213 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2214 if (IsReadOnly(dstPane))
2216 CWaitCursor waitstatus;
2218 GetDocument()->CopyAllList(srcPane, dstPane);
2222 * @brief Called when "Copy all to left" item is updated
2224 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2226 // Check that left side is not readonly
2227 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2228 if (!IsReadOnly(dstPane))
2229 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2231 pCmdUI->Enable(false);
2235 * @brief Copy all diffs from left pane to right pane
2237 void CMergeEditView::OnAllRight()
2239 // Check that right side is not readonly
2240 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
2241 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2242 if (IsReadOnly(dstPane))
2245 CWaitCursor waitstatus;
2247 GetDocument()->CopyAllList(srcPane, dstPane);
2251 * @brief Called when "Copy all to right" item is updated
2253 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2255 // Check that right side is not readonly
2256 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2257 if (!IsReadOnly(dstPane))
2258 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2260 pCmdUI->Enable(false);
2264 * @brief Do Auto merge
2266 void CMergeEditView::OnAutoMerge()
2268 // Check current pane is not readonly
2269 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2272 CWaitCursor waitstatus;
2274 GetDocument()->DoAutoMerge(m_nThisPane);
2278 * @brief Called when "Auto Merge" item is updated
2280 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2282 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2283 !GetDocument()->IsModified() &&
2284 !GetDocument()->GetAutoMerged() &&
2289 * @brief Add synchronization point
2291 void CMergeEditView::OnAddSyncPoint()
2293 GetDocument()->AddSyncPoint();
2297 * @brief Clear synchronization points
2299 void CMergeEditView::OnClearSyncPoints()
2301 GetDocument()->ClearSyncPoints();
2305 * @brief Called when "Clear Synchronization Points" item is updated
2307 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2309 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2313 * @brief This function is called before other edit events.
2314 * @param [in] nAction Edit operation to do
2315 * @param [in] pszText Text to insert, delete etc
2316 * @sa CCrystalEditView::OnEditOperation()
2317 * @todo More edit-events for rescan delaying?
2319 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2321 if (!QueryEditable())
2323 // We must not arrive here, and assert helps detect troubles
2328 CMergeDoc* pDoc = GetDocument();
2329 pDoc->SetEditedAfterRescan(m_nThisPane);
2331 // simple hook for multiplex undo operations
2332 // deleted by jtuc 2003-06-28
2333 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2334 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2336 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2337 pDoc->undoTgt.push_back(this);
2338 pDoc->curUndo = pDoc->undoTgt.end();
2341 // perform original function
2342 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2344 // augment with additional operations
2346 // Change header to inform about changed doc
2347 pDoc->UpdateHeaderPath(m_nThisPane);
2349 // If automatic rescan enabled, rescan after edit events
2350 if (pDoc->GetAutomaticRescan())
2352 // keep document up to date
2353 // (Re)start timer to rescan only when user edits text
2354 // If timer starting fails, rescan immediately
2355 if (nAction == CE_ACTION_TYPING ||
2356 nAction == CE_ACTION_REPLACE ||
2357 nAction == CE_ACTION_BACKSPACE ||
2358 nAction == CE_ACTION_INDENT ||
2359 nAction == CE_ACTION_PASTE ||
2360 nAction == CE_ACTION_DELSEL ||
2361 nAction == CE_ACTION_DELETE ||
2362 nAction == CE_ACTION_CUT)
2364 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2365 pDoc->FlushAndRescan();
2368 pDoc->FlushAndRescan();
2374 // Update other pane for sync line.
2375 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2377 if (nPane == m_nThisPane)
2379 CCrystalEditView *pView = GetGroupView(nPane);
2380 if (pView != nullptr)
2381 pView->Invalidate();
2388 * @brief Redo last action
2390 void CMergeEditView::OnEditRedo()
2392 CWaitCursor waitstatus;
2393 CMergeDoc* pDoc = GetDocument();
2394 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2397 if (!QueryEditable())
2400 GetParentFrame()->SetActiveView(this, true);
2401 if(CCrystalEditViewEx::DoEditRedo())
2404 pDoc->UpdateHeaderPath(m_nThisPane);
2405 pDoc->FlushAndRescan();
2410 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2415 * @brief Called when "Redo" item is updated
2417 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2419 CMergeDoc* pDoc = GetDocument();
2420 if (pDoc->curUndo!=pDoc->undoTgt.end())
2422 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2423 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2426 pCmdUI->Enable(false);
2429 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2431 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2435 * @brief Scrolls to current diff and/or selects diff text
2436 * @param [in] bScroll If true scroll diff to view
2437 * @param [in] bSelectText If true select diff text
2438 * @note If bScroll and bSelectText are false, this does nothing!
2439 * @todo This shouldn't be called when no diff is selected, so
2440 * somebody could try to ASSERT(nDiff > -1)...
2442 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2444 CMergeDoc *pd = GetDocument();
2445 const int nDiff = pd->GetCurrentDiff();
2447 // Try to trap some errors
2448 if (nDiff >= pd->m_diffList.GetSize())
2449 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2450 nDiff, pd->m_diffList.GetSize());
2452 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2454 CPoint ptStart, ptEnd;
2456 pd->m_diffList.GetDiff(nDiff, curDiff);
2459 ptStart.y = curDiff.dbegin;
2461 ptEnd.y = curDiff.dend;
2463 if (bScroll && !m_bDetailView)
2465 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2467 // Difference is not visible, scroll it so that max amount of
2468 // scrolling is done while keeping the diff in screen. So if
2469 // scrolling is downwards, scroll the diff to as up in screen
2470 // as possible. This usually brings next diff to the screen
2471 // and we don't need to scroll into it.
2472 int nLine = GetSubLineIndex(ptStart.y);
2473 if (nLine > CONTEXT_LINES_ABOVE)
2475 nLine -= CONTEXT_LINES_ABOVE;
2477 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2478 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2480 if (nPane != m_nThisPane)
2481 GetGroupView(nPane)->ScrollToSubLine(nLine);
2485 vector<WordDiff> worddiffs;
2486 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
2487 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2488 CPoint pt = worddiffs.size() > 0 ?
2489 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2491 GetGroupView(m_nThisPane)->SetCursorPos(pt);
2492 GetGroupView(m_nThisPane)->SetAnchor(pt);
2493 GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2494 GetGroupView(m_nThisPane)->EnsureVisible(pt);
2495 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2497 if (nPane != m_nThisPane)
2499 if (worddiffs.size() > 0)
2501 pt.x = worddiffs[0].begin[nPane];
2502 pt.y = worddiffs[0].beginline[nPane];
2504 GetGroupView(nPane)->SetCursorPos(pt);
2505 GetGroupView(nPane)->SetAnchor(pt);
2506 GetGroupView(nPane)->SetSelection(pt, pt);
2513 ptEnd.x = GetLineLength(ptEnd.y);
2514 SetSelection(ptStart, ptEnd);
2523 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2525 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2526 // ...but for this the queue must be empty
2527 // The timer message is a low priority message but the queue is maybe not yet empty
2528 // So we set a flag, wait for OnIdle to proceed, then come back here...
2529 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2530 // not with SetTimer so there is no delay)
2532 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2534 // IDLE_TIMER is the false timer used to come back here after OnIdle
2535 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2536 // (one normal timer = one flag = one command)
2538 if (nIDEvent == IDT_RESCAN)
2540 KillTimer(IDT_RESCAN);
2541 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2542 // notify the app to come back after OnIdle
2543 theApp.SetNeedIdleTimer();
2546 if (nIDEvent == IDLE_TIMER)
2548 // not a real timer, just come back after OnIdle
2549 // look to flags to know what to do
2550 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2551 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2552 fTimerWaitingForIdle = 0;
2555 CCrystalEditViewEx::OnTimer(nIDEvent);
2559 * @brief Returns if buffer is read-only
2560 * @note This has no any relation to file being read-only!
2562 bool CMergeEditView::IsReadOnly(int pane) const
2564 return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2568 * @brief Called when "Save left (as...)" item is updated
2570 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2572 CMergeDoc *pd = GetDocument();
2573 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2577 * @brief Called when "Save middle (as...)" item is updated
2579 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2581 CMergeDoc *pd = GetDocument();
2582 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2586 * @brief Called when "Save right (as...)" item is updated
2588 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2590 CMergeDoc *pd = GetDocument();
2591 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2595 * @brief Refresh display using text-buffers
2596 * @note This DOES NOT reload files!
2598 void CMergeEditView::OnRefresh()
2600 CMergeDoc *pd = GetDocument();
2601 ASSERT(pd != nullptr);
2602 pd->FlushAndRescan(true);
2606 * @brief Handle some keys when in merging mode
2608 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2610 bool bHandled = false;
2612 // Allow default text selection when SHIFT pressed
2613 if (::GetAsyncKeyState(VK_SHIFT))
2616 // Allow default editor functions when CTRL pressed
2617 if (::GetAsyncKeyState(VK_CONTROL))
2620 // If we are in merging mode (merge with cursor keys)
2621 // handle some keys here
2622 switch (pMsg->wParam)
2649 * @brief Called before messages are translated.
2651 * Checks if ESC key was pressed, saves and closes doc.
2652 * Also if in merge mode traps cursor keys.
2654 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2656 if (pMsg->message == WM_KEYDOWN)
2658 // If we are in merging mode (merge with cursor keys)
2659 // handle some keys here
2660 if (theApp.GetMergingMode())
2662 bool bHandled = MergeModeKeyDown(pMsg);
2667 // Close window if user has allowed it from options
2668 if (pMsg->wParam == VK_ESCAPE)
2670 int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2671 if (nCloseWithEsc != 0)
2672 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2677 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2681 * @brief Called when "Save" item is updated
2683 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2685 CMergeDoc *pd = GetDocument();
2687 bool bModified = false;
2688 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2690 if (pd->m_ptBuf[nPane]->IsModified())
2693 pCmdUI->Enable(bModified);
2697 * @brief Enable/disable left buffer read-only
2699 void CMergeEditView::OnLeftReadOnly()
2701 CMergeDoc *pd = GetDocument();
2702 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2703 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2707 * @brief Called when "Left read-only" item is updated
2709 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2711 CMergeDoc *pd = GetDocument();
2712 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2713 pCmdUI->Enable(true);
2714 pCmdUI->SetCheck(bReadOnly);
2718 * @brief Enable/disable middle buffer read-only
2720 void CMergeEditView::OnMiddleReadOnly()
2722 CMergeDoc *pd = GetDocument();
2723 if (pd->m_nBuffers == 3)
2725 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2726 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2731 * @brief Called when "Middle read-only" item is updated
2733 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2735 CMergeDoc *pd = GetDocument();
2736 if (pd->m_nBuffers < 3)
2738 pCmdUI->Enable(false);
2742 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2743 pCmdUI->Enable(true);
2744 pCmdUI->SetCheck(bReadOnly);
2749 * @brief Enable/disable right buffer read-only
2751 void CMergeEditView::OnRightReadOnly()
2753 CMergeDoc *pd = GetDocument();
2754 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2755 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2759 * @brief Called when "Left read-only" item is updated
2761 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2763 CMergeDoc *pd = GetDocument();
2764 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2765 pCmdUI->Enable(true);
2766 pCmdUI->SetCheck(bReadOnly);
2769 /// Store interface we use to display status line info
2770 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2772 ASSERT(m_piMergeEditStatus == nullptr);
2773 m_piMergeEditStatus = piMergeEditStatus;
2777 * @brief Update status bar contents.
2779 void CMergeEditView::UpdateStatusbar()
2785 * @brief Update statusbar info, Override from CCrystalTextView
2786 * @note we tab-expand column, but we don't tab-expand char count,
2787 * since we want to show how many chars there are and tab is just one
2788 * character although it expands to several spaces.
2790 void CMergeEditView::OnUpdateCaret()
2792 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2795 CPoint cursorPos = GetCursorPos();
2796 int nScreenLine = cursorPos.y;
2797 const int nRealLine = ComputeRealLine(nScreenLine);
2804 auto [selectedLines, selectedChars] = GetSelectedLineAndCharacterCount();
2805 DWORD dwLineFlags = 0;
2807 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2808 // Is this a ghost line ?
2809 if (dwLineFlags & LF_GHOST)
2811 // Ghost lines display eg "Line 12-13"
2812 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2813 sEol = _T("hidden");
2817 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2818 sLine.Format(_T("%d"), nRealLine+1);
2819 curChar = cursorPos.x + 1;
2820 chars = GetLineLength(nScreenLine);
2821 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2822 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2824 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2825 GetDocument()->IsMixedEOL(m_nThisPane))
2827 sEol = GetTextBufferEol(nScreenLine);
2830 sEol = _T("hidden");
2832 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2833 curChar, chars, selectedLines, selectedChars,
2834 sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2836 // Is cursor inside difference?
2837 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2838 m_bCurrentLineIsDiff = true;
2840 m_bCurrentLineIsDiff = false;
2842 CWnd* pWnd = GetFocus();
2843 if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2844 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2847 * @brief Select linedifference in the current line.
2849 * Select line difference in current line. Selection type
2850 * is choosed by highlight type.
2852 template<bool reversed>
2853 void CMergeEditView::OnSelectLineDiff()
2855 // Pass this to the document, to compare this file to other
2856 GetDocument()->Showlinediff(this, reversed);
2859 /// Enable select difference menuitem if current line is inside difference.
2860 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2862 pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2865 void CMergeEditView::OnAddToSubstitutionFilters()
2867 // Pass this to the document, to compare this file to other
2868 GetDocument()->AddToSubstitutionFilters(this, false);
2871 void CMergeEditView::OnUpdateAddToSubstitutionFilters(CCmdUI* pCmdUI)
2873 pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
2877 * @brief Enable/disable Replace-menuitem
2879 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2881 CMergeDoc *pd = GetDocument();
2882 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2884 pCmdUI->Enable(!bReadOnly);
2888 * @brief Update readonly statusbaritem
2890 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2892 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2893 pCmdUI->Enable(bRO);
2897 * @brief Create the dynamic submenu for scripts
2899 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2902 std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2905 size_t i = GetMenuItemCount(hMenu);
2907 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2909 if (functionNamesList.size() == 0)
2911 // no script : create a <empty> entry
2912 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2916 // or fill in the submenu with the scripts names
2917 int ID = ID_SCRIPT_FIRST; // first ID in menu
2918 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2919 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2921 functionNamesList.clear();
2924 if (!plugin::IsWindowsScriptThere())
2925 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2931 * @brief Create the dynamic submenu for prediffers
2933 * @note The plugins are grouped in (suggested) and (not suggested)
2934 * The IDs follow the order of GetAvailableScripts
2936 * suggested 0 ID_1ST + 0
2937 * suggested 1 ID_1ST + 2
2938 * suggested 2 ID_1ST + 5
2939 * not suggested 0 ID_1ST + 1
2940 * not suggested 1 ID_1ST + 3
2941 * not suggested 2 ID_1ST + 4
2943 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2946 int i = GetMenuItemCount(hMenu);
2948 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2950 CMergeDoc *pd = GetDocument();
2951 ASSERT(pd != nullptr);
2954 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2956 // get the scriptlet files
2957 PluginArray * piScriptArray =
2958 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2959 PluginArray * piScriptArray2 =
2960 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2962 // build the menu : first part, suggested plugins
2964 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2965 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2967 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
2969 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2971 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2972 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2975 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2977 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2979 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2980 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2983 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2986 // build the menu : second part, others plugins
2988 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2989 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2991 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2992 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2994 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2995 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2998 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
3000 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
3002 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
3003 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
3006 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
3009 // compute the m_CurrentPredifferID (to set the radio button)
3010 PrediffingInfo prediffer;
3011 pd->GetPrediffer(&prediffer);
3013 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3014 m_CurrentPredifferID = 0;
3015 else if (prediffer.m_PluginName.empty())
3016 m_CurrentPredifferID = ID_NO_PREDIFFER;
3019 ID = ID_PREDIFFERS_FIRST; // first ID in menu
3020 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
3022 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
3023 if (prediffer.m_PluginName == plugin->m_name)
3024 m_CurrentPredifferID = ID;
3027 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
3029 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
3030 if (prediffer.m_PluginName == plugin->m_name)
3031 m_CurrentPredifferID = ID;
3039 * @brief Offer a context menu built with scriptlet/ActiveX functions
3041 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
3043 // Create the menu and populate it with the available functions
3045 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
3047 // Remove copying item copying from active side
3048 if (m_nThisPane == 0) // left?
3050 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3051 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3052 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3053 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3055 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
3057 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3058 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3059 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3060 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3063 // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
3064 // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
3065 // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
3066 int nBuffers = GetDocument()->m_nBuffers;
3067 if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
3068 menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
3069 else if (nBuffers == 3 && m_nThisPane == 2)
3070 menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
3072 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
3073 theApp.TranslateMenu(menu.m_hMenu);
3075 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
3076 ASSERT(pSub != nullptr);
3078 // Context menu opened using keyboard has no coordinates
3079 if (point.x == -1 && point.y == -1)
3082 GetClientRect(rect);
3083 ClientToScreen(rect);
3085 point = rect.TopLeft();
3089 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
3090 point.x, point.y, AfxGetMainWnd());
3095 * @brief Update EOL mode in status bar
3097 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
3099 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
3103 * @brief Change EOL mode and unify all the lines EOL to this new mode
3105 void CMergeEditView::OnConvertEolTo(UINT nID )
3107 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;;
3111 nStyle = CRLFSTYLE::DOS;
3113 case ID_EOL_TO_UNIX:
3114 nStyle = CRLFSTYLE::UNIX;
3117 nStyle = CRLFSTYLE::MAC;
3121 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
3124 m_pTextBuffer->SetCRLFMode(nStyle);
3126 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
3127 if (m_pTextBuffer->applyEOLMode())
3129 CMergeDoc *pd = GetDocument();
3130 ASSERT(pd != nullptr);
3131 pd->UpdateHeaderPath(m_nThisPane);
3132 pd->FlushAndRescan(true);
3137 * @brief allow convert to entries in file submenu
3139 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
3141 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;
3142 switch (pCmdUI->m_nID)
3145 nStyle = CRLFSTYLE::DOS;
3147 case ID_EOL_TO_UNIX:
3148 nStyle = CRLFSTYLE::UNIX;
3151 nStyle = CRLFSTYLE::MAC;
3155 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
3159 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3160 GetDocument()->IsMixedEOL(m_nThisPane) ||
3161 nStyle != m_pTextBuffer->GetCRLFMode())
3163 pCmdUI->SetRadio(false);
3165 // Don't allow selecting other EOL style for protected pane
3166 if (!QueryEditable())
3167 pCmdUI->Enable(false);
3170 pCmdUI->SetRadio(true);
3174 * @brief Copy diff from left to right and advance to next diff
3176 void CMergeEditView::OnL2RNext()
3179 if (IsCursorInDiff()) // for 3-way file compare
3185 * @brief Update "Copy right and advance" UI item
3187 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
3189 OnUpdateL2r(pCmdUI);
3193 * @brief Copy diff from right to left and advance to next diff
3195 void CMergeEditView::OnR2LNext()
3198 if (IsCursorInDiff()) // for 3-way file compare
3204 * @brief Update "Copy left and advance" UI item
3206 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
3208 OnUpdateR2l(pCmdUI);
3212 * @brief Change active pane in MergeView.
3213 * Changes active pane and makes sure cursor position is kept in
3214 * screen. Currently we put cursor in same line than in original
3215 * active pane but we could be smarter too? Maybe update cursor
3216 * only when it is not visible in new pane?
3218 void CMergeEditView::OnChangePane()
3220 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3221 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
3222 CMergeDoc *pDoc = GetDocument();
3223 bool bFound = false;
3224 CMergeEditView *pNextActiveView = nullptr;
3225 std::vector<CMergeEditView *> list = pDoc->GetViewList();
3226 list.insert(list.end(), list.begin(), list.end());
3227 for (auto& pView : list)
3229 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3231 pNextActiveView = pView;
3237 GetParentFrame()->SetActiveView(pNextActiveView);
3238 CPoint ptCursor = pWnd->GetCursorPos();
3240 if (ptCursor.y >= pNextActiveView->GetLineCount())
3241 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3242 pNextActiveView->SetCursorPos(ptCursor);
3243 pNextActiveView->SetAnchor(ptCursor);
3244 pNextActiveView->SetSelection(ptCursor, ptCursor);
3248 * @brief Show "Go To" dialog and scroll views to line or diff.
3250 * Before dialog is opened, current line and file is determined
3252 * @note Conversions needed between apparent and real lines
3254 void CMergeEditView::OnWMGoto()
3257 CMergeDoc *pDoc = GetDocument();
3258 CPoint pos = GetCursorPos();
3262 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3263 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
3264 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
3266 // Set active file and current line selected in dialog
3267 dlg.m_strParam = strutils::to_str(nRealLine + 1);
3268 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3269 dlg.m_nGotoWhat = 0;
3271 if (dlg.DoModal() == IDOK)
3273 CMergeDoc * pDoc1 = GetDocument();
3274 CMergeEditView * pCurrentView = nullptr;
3277 pCurrentView = GetGroupView(m_nThisPane);
3280 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3282 if (dlg.m_nGotoWhat == 0)
3284 int nRealLine1 = num;
3287 if (nRealLine1 > nLastLine)
3288 nRealLine1 = nLastLine;
3290 bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
3291 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile, !bShift);
3298 if (diff >= pDoc1->m_diffList.GetSize())
3299 diff = pDoc1->m_diffList.GetSize();
3301 pCurrentView->SelectDiff(diff, true, false);
3307 * @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
3308 * Go to moved line between the left and right panes when in 2-way file comparison.
3309 * Go to moved line between the left and middle panes when in 3-way file comparison.
3311 void CMergeEditView::OnGotoMovedLineLM()
3313 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3316 CMergeDoc* pDoc = GetDocument();
3317 CPoint pos = GetCursorPos();
3319 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3320 ASSERT(pDoc != nullptr);
3321 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3324 if (m_nThisPane == 0)
3326 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3328 GotoLine(line, false, 1);
3330 else if (m_nThisPane == 1)
3332 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3334 GotoLine(line, false, 0);
3339 * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
3340 * @param [in] pCmdUI UI component to update.
3341 * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
3343 void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
3345 CMergeDoc* pDoc = GetDocument();
3346 CPoint pos = GetCursorPos();
3348 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3349 ASSERT(pCmdUI != nullptr);
3350 ASSERT(pDoc != nullptr);
3351 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3354 if (pDoc->m_nBuffers == 2)
3355 pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
3357 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
3359 pCmdUI->Enable(false);
3363 if (m_nThisPane == 0)
3365 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3366 pCmdUI->Enable(bOn);
3368 else if (m_nThisPane == 1)
3370 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3371 pCmdUI->Enable(bOn);
3376 * @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
3377 * Go to moved line between the middle and right panes when in 3-way file comparison.
3379 void CMergeEditView::OnGotoMovedLineMR()
3381 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3384 CMergeDoc* pDoc = GetDocument();
3385 CPoint pos = GetCursorPos();
3387 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3388 ASSERT(pDoc != nullptr);
3389 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3392 if (m_nThisPane == 1)
3394 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3396 GotoLine(line, false, 2);
3398 else if (m_nThisPane == 2)
3400 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3402 GotoLine(line, false, 1);
3407 * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
3408 * @param [in] pCmdUI UI component to update.
3410 void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
3412 CMergeDoc* pDoc = GetDocument();
3413 CPoint pos = GetCursorPos();
3415 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3416 ASSERT(pCmdUI != nullptr);
3417 ASSERT(pDoc != nullptr);
3418 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3421 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
3423 pCmdUI->Enable(false);
3427 if (m_nThisPane == 1)
3429 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3430 pCmdUI->Enable(bOn);
3432 else if (m_nThisPane == 2)
3434 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3435 pCmdUI->Enable(bOn);
3439 void CMergeEditView::OnShellMenu()
3441 CFrameWnd *pFrame = GetTopLevelFrame();
3442 ASSERT(pFrame != nullptr);
3443 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3444 pFrame->m_bAutoMenuEnable = FALSE;
3446 String path = GetDocument()->m_filePaths[m_nThisPane];
3447 std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3448 pContextMenu->Initialize();
3449 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3450 pContextMenu->RequeryShellContextMenu();
3452 ::GetCursorPos(&point);
3453 HWND hWnd = GetSafeHwnd();
3454 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3456 pContextMenu->InvokeCommand(nCmd, hWnd);
3457 pContextMenu->ReleaseShellContextMenu();
3459 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3462 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3464 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3468 * @brief Reload options.
3470 void CMergeEditView::RefreshOptions()
3472 RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3473 SetRenderingMode(nRenderingMode);
3475 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3476 SetInsertTabs(true);
3478 SetInsertTabs(false);
3480 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3482 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3483 SetTextType(CrystalLineParser::SRC_PLAIN);
3484 else if (!m_bChangedSchemeManually)
3486 // The syntax highlighting scheme should only be applied if it has not been manually changed.
3487 String fileName = GetDocument()->m_filePaths[m_nThisPane];
3489 paths::SplitFilename(fileName, nullptr, nullptr, &sExt);
3490 CrystalLineParser::TextDefinition* def = CrystalLineParser::GetTextType(sExt.c_str());
3492 SetTextType(def->type);
3494 SetTextType(CrystalLineParser::SRC_PLAIN);
3495 SetDisableBSAtSOL(false);
3498 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3499 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3501 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3502 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3503 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3504 GetDocument()->IsMixedEOL(m_nThisPane));
3506 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3509 void CMergeEditView::OnScripts(UINT nID )
3511 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3512 String text = GetSelectedText();
3514 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3515 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3517 // now replace the text
3518 ReplaceSelection(text.c_str(), text.length(), 0);
3522 * @brief Called when an editor script item is updated
3524 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3526 // append the scripts submenu
3527 HMENU scriptsSubmenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu->m_hMenu : nullptr;
3528 if (scriptsSubmenu != nullptr)
3529 createScriptsSubmenu(scriptsSubmenu);
3531 pCmdUI->Enable(true);
3535 * @brief Called when an editor script item is updated
3537 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3539 pCmdUI->Enable(true);
3541 CMergeDoc *pd = GetDocument();
3542 ASSERT(pd != nullptr);
3543 PrediffingInfo prediffer;
3544 pd->GetPrediffer(&prediffer);
3546 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3548 pCmdUI->SetRadio(false);
3552 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3553 if (prediffer.m_PluginName.empty())
3554 m_CurrentPredifferID = ID_NO_PREDIFFER;
3556 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3560 * @brief Update "Prediffer" menuitem
3562 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3564 // recreate the sub menu (to fill the "selected prediffers")
3565 GetMainFrame()->UpdatePrediffersMenu();
3569 void CMergeEditView::OnNoPrediffer()
3571 OnPrediffer(ID_NO_PREDIFFER);
3574 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3576 void CMergeEditView::OnPrediffer(UINT nID )
3578 CMergeDoc *pd = GetDocument();
3579 ASSERT(pd != nullptr);
3581 SetPredifferByMenu(nID);
3582 pd->FlushAndRescan(true);
3586 * @brief Handler for all prediffer choices.
3587 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3588 * ID_NO_PREDIFFER, & specific prediffers.
3590 void CMergeEditView::SetPredifferByMenu(UINT nID )
3592 CMergeDoc *pd = GetDocument();
3593 ASSERT(pd != nullptr);
3595 if (nID == ID_NO_PREDIFFER)
3597 m_CurrentPredifferID = nID;
3598 // All flags are set correctly during the construction
3599 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3600 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3601 infoPrediffer->m_PluginName.clear();
3602 pd->SetPrediffer(infoPrediffer);
3603 pd->FlushAndRescan(true);
3607 // get the scriptlet files
3608 PluginArray * piScriptArray =
3609 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3610 PluginArray * piScriptArray2 =
3611 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3613 // build a PrediffingInfo structure fom the ID
3614 PrediffingInfo prediffer;
3615 prediffer.m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3617 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3618 if (pluginNumber < piScriptArray->size())
3620 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3621 prediffer.m_PluginName = plugin->m_name;
3625 pluginNumber -= piScriptArray->size();
3626 if (pluginNumber >= piScriptArray2->size())
3628 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3629 prediffer.m_PluginName = plugin->m_name;
3632 // update data for the radio button
3633 m_CurrentPredifferID = nID;
3635 // update the prediffer and rescan
3636 pd->SetPrediffer(&prediffer);
3640 * @brief Look through available prediffers, and return ID of requested one, if found
3642 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3645 int ID = ID_PREDIFFERS_FIRST;
3647 // Search file prediffers
3648 PluginArray * piScriptArray =
3649 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3650 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3652 const PluginInfoPtr & plugin = piScriptArray->at(i);
3653 if (plugin->m_name == prediffer)
3657 // Search buffer prediffers
3658 PluginArray * piScriptArray2 =
3659 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3660 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3662 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3663 if (plugin->m_name == prediffer)
3671 * @brief Look through available prediffers, and return ID of requested one, if found
3673 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3675 int id = FindPrediffer(prediffer);
3676 if (id<0) return false;
3677 SetPredifferByMenu(id);
3682 * @brief Goto given line.
3683 * @param [in] nLine Destination linenumber
3684 * @param [in] bRealLine if true linenumber is real line, otherwise
3685 * it is apparent line (including deleted lines)
3686 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3687 * @param [in] bMoveAnchor if true the anchor is moved to nLine
3689 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane, bool bMoveAnchor)
3691 CMergeDoc *pDoc = GetDocument();
3692 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3693 CMergeEditView *pCurrentView = nullptr;
3694 if (pSplitterWnd != nullptr)
3695 pCurrentView = static_cast<CMergeEditView*>
3696 (pSplitterWnd->GetActivePane());
3698 int nRealLine = nLine;
3699 int nApparentLine = nLine;
3701 // Compute apparent (shown linenumber) line
3704 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3705 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3707 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3711 ptPos.y = nApparentLine;
3713 // Scroll line to center of view
3714 int nScrollLine = GetSubLineIndex(nApparentLine);
3715 nScrollLine -= GetScreenLines() / 2;
3716 if (nScrollLine < 0)
3719 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3721 int nGroup = m_bDetailView ? 0 : m_nThisGroup;
3722 CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
3723 pView->ScrollToSubLine(nScrollLine);
3724 if (ptPos.y < pView->GetLineCount())
3726 pView->SetCursorPos(ptPos);
3728 pView->SetAnchor(ptPos);
3729 pView->SetSelection(pView->GetAnchor(), ptPos);
3733 CPoint ptPos1(0, pView->GetLineCount() - 1);
3734 pView->SetCursorPos(ptPos1);
3736 pView->SetAnchor(ptPos1);
3737 pView->SetSelection(pView->GetAnchor(), ptPos1);
3741 // If goto target is another view - activate another view.
3742 // This is done for user convenience as user probably wants to
3743 // work with goto target file.
3745 GetDocument()->GetView(0, pane)->SetActivePane();
3746 else if (GetGroupView(pane) != pCurrentView)
3747 GetGroupView(pane)->SetActivePane();
3751 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3754 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3756 if (pScrollBar == nullptr)
3758 // Scroll did not come frome a scroll bar
3759 // Find the appropriate scroll bar
3760 // and send the message to the splitter window instead
3761 // The event should eventually come back here but with a valid scrollbar
3762 // Along the way it will be propagated to other windows that need it
3763 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3764 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3765 pSplitterWnd->SendMessage(WM_HSCROLL,
3766 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3769 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3773 * @brief When view is scrolled using scrollbars update location pane.
3775 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3777 if (pScrollBar == nullptr)
3779 // Scroll did not come frome a scroll bar
3780 // Find the appropriate scroll bar
3781 // and send the message to the splitter window instead
3782 // The event should eventually come back here but with a valid scrollbar
3783 // Along the way it will be propagated to other windows that need it
3784 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3785 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3786 pSplitterWnd->SendMessage(WM_VSCROLL,
3787 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3790 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3792 if (nSBCode == SB_ENDSCROLL)
3795 // Note we cannot use nPos because of its 16-bit nature
3796 SCROLLINFO si = {0};
3797 si.cbSize = sizeof (si);
3798 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3799 VERIFY (GetScrollInfo (SB_VERT, &si));
3801 // Get the current position of scroll box.
3802 int nCurPos = si.nPos;
3804 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3808 * @brief Copy selected lines adding linenumbers.
3810 void CMergeEditView::OnEditCopyLineNumbers()
3816 CMergeDoc *pDoc = GetDocument();
3817 auto [ptStart, ptEnd] = GetSelection();
3819 // Get last selected line (having widest linenumber)
3820 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3821 size_t nNumWidth = strutils::to_str(line + 1).length();
3823 for (int i = ptStart.y; i <= ptEnd.y; i++)
3825 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3828 // We need to convert to real linenumbers
3829 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3831 // Insert spaces to align different width linenumbers (99, 100)
3832 strLine = GetLineText(i);
3833 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3836 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3837 strText += strNumLine;
3839 PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3842 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3844 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3848 * @brief Open active file with associated application.
3850 * First tries to open file using shell 'Edit' action, since that
3851 * action open scripts etc. to editor instead of running them. If
3852 * edit-action is not registered, 'Open' action is used.
3854 void CMergeEditView::OnOpenFile()
3856 CMergeDoc * pDoc = GetDocument();
3857 ASSERT(pDoc != nullptr);
3859 String sFileName = pDoc->m_filePaths[m_nThisPane];
3860 if (sFileName.empty())
3862 shell::Edit(sFileName.c_str());
3866 * @brief Open active file with app selection dialog
3868 void CMergeEditView::OnOpenFileWith()
3870 CMergeDoc * pDoc = GetDocument();
3871 ASSERT(pDoc != nullptr);
3873 String sFileName = pDoc->m_filePaths[m_nThisPane];
3874 if (sFileName.empty())
3876 shell::OpenWith(sFileName.c_str());
3880 * @brief Open active file with external editor
3882 void CMergeEditView::OnOpenFileWithEditor()
3884 CMergeDoc * pDoc = GetDocument();
3885 ASSERT(pDoc != nullptr);
3887 String sFileName = pDoc->m_filePaths[m_nThisPane];
3888 if (sFileName.empty())
3891 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3892 CMergeApp::OpenFileToExternalEditor(sFileName, nRealLine);
3896 * @brief Open parent folder of active file
3898 void CMergeEditView::OnOpenParentFolder()
3900 CMergeDoc * pDoc = GetDocument();
3901 ASSERT(pDoc != nullptr);
3903 String sFileName = pDoc->m_filePaths[m_nThisPane];
3904 if (sFileName.empty())
3907 shell::OpenParentFolder(sFileName.c_str());
3911 * @brief Force repaint of the location pane.
3913 void CMergeEditView::RepaintLocationPane()
3915 // Must force recalculation due to caching of data in location pane.
3916 CLocationView *pLocationView = GetDocument()->GetLocationView();
3917 if (pLocationView != nullptr)
3918 pLocationView->ForceRecalculate();
3922 * @brief Enables/disables linediff (different color for diffs)
3924 void CMergeEditView::OnViewLineDiffs()
3926 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3927 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3929 // Call CMergeDoc RefreshOptions() to refresh *both* views
3930 CMergeDoc *pDoc = GetDocument();
3931 pDoc->RefreshOptions();
3932 pDoc->FlushAndRescan(true);
3935 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3937 pCmdUI->Enable(true);
3938 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3942 * @brief Enables/disables line number
3944 void CMergeEditView::OnViewLineNumbers()
3946 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3948 // Call CMergeDoc RefreshOptions() to refresh *both* views
3949 CMergeDoc *pDoc = GetDocument();
3950 pDoc->RefreshOptions();
3953 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3955 pCmdUI->Enable(true);
3956 pCmdUI->SetCheck(GetViewLineNumbers());
3960 * @brief Enables/disables word wrap
3962 void CMergeEditView::OnViewWordWrap()
3964 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3966 // Call CMergeDoc RefreshOptions() to refresh *both* views
3967 CMergeDoc *pDoc = GetDocument();
3968 pDoc->RefreshOptions();
3969 pDoc->UpdateAllViews(this);
3974 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3976 pCmdUI->Enable(true);
3977 pCmdUI->SetCheck(m_bWordWrap);
3980 void CMergeEditView::OnViewWhitespace()
3982 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3984 // Call CMergeDoc RefreshOptions() to refresh *both* views
3985 CMergeDoc *pDoc = GetDocument();
3986 pDoc->RefreshOptions();
3989 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3991 pCmdUI->SetCheck(GetViewTabs());
3994 void CMergeEditView::OnViewEOL()
3996 GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
3997 GetDocument()->RefreshOptions();
4000 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI)
4002 pCmdUI->SetCheck(GetViewEols());
4005 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
4007 if (!IsInitialized())
4010 CMergeDoc * pDoc = GetDocument();
4011 if (m_nThisPane < pDoc->m_nBuffers - 1)
4013 // To calculate subline index correctly
4014 // we have to invalidate line cache in all pane before calling the function related the subline.
4015 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4017 CMergeEditView *pView = GetGroupView(nPane);
4018 if (pView != nullptr)
4019 pView->InvalidateScreenRect(false);
4024 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4026 CMergeEditView *pView = GetGroupView(nPane);
4027 if (pView != nullptr)
4028 pView->Invalidate();
4031 // recalculate m_nTopSubLine
4032 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
4036 RecalcVertScrollBar (false, false);
4037 RecalcHorzScrollBar (false, false);
4041 * @brief allocates GDI resources for printing
4042 * @param pDC [in] points to the printer device context
4043 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4045 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
4047 GetParentFrame()->PostMessage(WM_TIMER);
4049 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4051 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
4052 pView->m_bPrintHeader = true;
4053 pView->m_bPrintFooter = true;
4054 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
4059 * @brief frees GDI resources for printing
4060 * @param pDC [in] points to the printer device context
4061 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4063 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
4065 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4066 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
4068 GetParentFrame()->PostMessage(WM_TIMER);
4072 * @brief Gets header text to print
4073 * @param [in] nPageNum the page number to print
4074 * @param [out] header text to print
4076 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
4078 text = GetDocument()->GetTitle();
4082 * @brief Prints header
4083 * @param [in] nPageNum the page number to print
4085 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
4087 if (m_nThisPane > 0)
4089 int oldRight = m_rcPrintArea.right;
4090 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4091 CGhostTextView::PrintHeader(pdc, nPageNum);
4092 m_rcPrintArea.right = oldRight;
4096 * @brief Prints footer
4097 * @param [in] nPageNum the page number to print
4099 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
4101 if (m_nThisPane > 0)
4103 int oldRight = m_rcPrintArea.right;
4104 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4105 CGhostTextView::PrintFooter(pdc, nPageNum);
4106 m_rcPrintArea.right = oldRight;
4109 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
4111 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4112 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
4116 * @brief Prints or previews both panes.
4117 * @param pDC [in] points to the printer device context
4118 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4120 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
4122 CRect rDraw = pInfo->m_rectDraw;
4123 CSize sz = rDraw.Size();
4124 CMergeDoc *pDoc = GetDocument();
4126 SIZE szLeftTop, szRightBottom;
4127 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
4128 pDC->HIMETRICtoLP(&szLeftTop);
4129 pDC->HIMETRICtoLP(&szRightBottom);
4131 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
4134 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
4136 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
4137 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
4138 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
4139 pPane->CGhostTextView::OnPrint(pDC, pInfo);
4143 bool CMergeEditView::IsInitialized() const
4145 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
4146 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
4147 return pBuffer->IsInitialized();
4151 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
4153 int CMergeEditView::GetEmptySubLines( int nLineIndex )
4155 int nBreaks[3] = {0};
4156 int nMaxBreaks = -1;
4157 CMergeDoc * pDoc = GetDocument();
4158 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4160 CMergeEditView *pView = GetGroupView(nPane);
4161 if (pView != nullptr)
4163 if (nLineIndex >= pView->GetLineCount())
4165 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
4167 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
4170 if (nBreaks[m_nThisPane] < nMaxBreaks)
4171 return nMaxBreaks - nBreaks[m_nThisPane];
4177 * @brief Invalidate sub line index cache from the specified index to the end of file.
4178 * @param [in] nLineIndex Index of the first line to invalidate
4180 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
4182 CMergeDoc * pDoc = GetDocument();
4183 ASSERT(pDoc != nullptr);
4185 // We have to invalidate sub line index cache on both panes.
4186 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4188 CMergeEditView *pView = GetGroupView(nPane);
4189 if (pView != nullptr)
4190 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
4194 void CMergeEditView::SetWordWrapping( bool bWordWrap )
4196 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4197 GetGroupView(pane)->m_bWordWrap = bWordWrap;
4198 CCrystalTextView::SetWordWrapping(bWordWrap);
4202 * @brief Swap the positions of the two panes
4204 void CMergeEditView::OnViewSwapPanes12()
4206 GetDocument()->SwapFiles(0, 1);
4210 * @brief Swap the positions of the two panes
4212 void CMergeEditView::OnViewSwapPanes23()
4214 GetDocument()->SwapFiles(1, 2);
4218 * @brief Swap the positions of the two panes
4220 void CMergeEditView::OnViewSwapPanes13()
4222 GetDocument()->SwapFiles(0, 2);
4226 * @brief Determine if difference is visible on screen.
4227 * @param [in] nDiff Number of diff to check.
4228 * @return true if difference is visible.
4230 bool CMergeEditView::IsDiffVisible(int nDiff)
4232 const CMergeDoc *pd = GetDocument();
4235 pd->m_diffList.GetDiff(nDiff, diff);
4237 return IsDiffVisible(diff);
4241 * @brief Determine if difference is visible on screen.
4242 * @param [in] diff diff to check.
4243 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
4244 * @return true if difference is visible, false otherwise.
4246 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
4248 const int nDiffStart = GetSubLineIndex(diff.dbegin);
4249 const int nDiffEnd = GetSubLineIndex(diff.dend);
4250 // Diff's height is last line - first line + last line's line count
4251 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
4253 // If diff first line outside current view - context OR
4254 // if diff last line outside current view - context OR
4255 // if diff is bigger than screen
4256 if ((nDiffStart < m_nTopSubLine) ||
4257 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
4258 (nDiffHeight >= GetScreenLines()))
4268 /** @brief Open help from mainframe when user presses F1*/
4269 void CMergeEditView::OnHelp()
4271 theApp.ShowHelp(MergeViewHelpLocation);
4275 * @brief Called after document is loaded.
4276 * This function is called from CMergeDoc::OpenDocs() after documents are
4277 * loaded. So this is good place to set View's options etc.
4279 void CMergeEditView::DocumentsLoaded()
4281 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
4284 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
4289 SetTopMargin(false);
4292 // SetTextType will revert to language dependent defaults for tab
4293 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
4294 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
4295 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
4296 GetDocument()->IsMixedEOL(m_nThisPane);
4297 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
4298 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
4299 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
4300 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4302 // Enable Backspace at beginning of line
4303 SetDisableBSAtSOL(false);
4305 // Set tab type (tabs/spaces)
4306 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
4307 SetInsertTabs(bInsertTabs);
4309 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
4310 // disabled) after docs are open in screen. So lets make sure they are
4311 // really updated, even though this is unnecessary in most cases.
4312 RecalcHorzScrollBar();
4313 RecalcVertScrollBar();
4317 * @brief Update LocationView position.
4318 * This function updates LocationView position to given lines.
4319 * Usually we want to lines in file compare view and area in
4320 * LocationView to match. Be extra carefull to not call non-existing
4322 * @param [in] nTopLine Top line of current view.
4323 * @param [in] nBottomLine Bottom line of current view.
4325 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
4326 int nBottomLine /*= -1*/)
4328 CMergeDoc *pDoc = GetDocument();
4329 if (pDoc == nullptr)
4332 CLocationView *pLocationView = pDoc->GetLocationView();
4334 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
4336 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
4341 * @brief Enable/Disable view's selection margins.
4342 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
4343 * useful. But it appears many users don't use/need those features and for them
4344 * selection margins are just wasted screen estate.
4346 void CMergeEditView::OnViewMargin()
4348 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
4349 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
4351 SetSelectionMargin(!bViewMargin);
4352 CMergeDoc *pDoc = GetDocument();
4353 pDoc->RefreshOptions();
4354 pDoc->UpdateAllViews(this);
4358 * @brief Update GUI for Enable/Disable view's selection margin.
4359 * @param [in] pCmdUI Pointer to UI item to update.
4361 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
4363 pCmdUI->Enable(true);
4364 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4368 * @brief Create the "Change Scheme" sub menu.
4369 * @param [in] pCmdUI Pointer to UI item to update.
4371 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4373 // Delete the place holder menu.
4374 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4376 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4378 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4379 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4380 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
4382 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
4384 name = theApp.LoadString(i);
4385 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
4388 pCmdUI->Enable(true);
4392 * @brief Change the editor's syntax highlighting scheme.
4393 * @param [in] nID Selected color scheme sub menu id.
4395 void CMergeEditView::OnChangeScheme(UINT nID)
4397 CMergeDoc *pDoc = GetDocument();
4398 ASSERT(pDoc != nullptr);
4400 for (int nGroup = 0; nGroup < pDoc->m_nGroups; nGroup++)
4401 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4403 CMergeEditView *pView = pDoc->GetView(nGroup, nPane);
4404 ASSERT(pView != nullptr);
4406 if (pView != nullptr)
4408 pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
4409 pView->SetDisableBSAtSOL(false);
4410 pView->m_bChangedSchemeManually = true;
4418 * @brief Enable all color schemes sub menu items.
4419 * @param [in] pCmdUI Pointer to UI item to update.
4421 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4423 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4424 pCmdUI->SetRadio(bIsCurrentScheme);
4425 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4429 * @brief Called when mouse's wheel is scrolled.
4431 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4433 if ( nFlags == MK_CONTROL )
4435 short amount = zDelta < 0 ? -1: 1;
4438 // no default CCrystalTextView
4439 return CView::OnMouseWheel(nFlags, zDelta, pt);
4442 if (nFlags == MK_SHIFT)
4444 SCROLLINFO si = { sizeof SCROLLINFO };
4445 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4447 VERIFY(GetScrollInfo(SB_HORZ, &si));
4450 si.nPos -= zDelta / 40;
4451 if (si.nPos > si.nMax) si.nPos = si.nMax;
4452 if (si.nPos < si.nMin) si.nPos = si.nMin;
4454 SetScrollInfo(SB_HORZ, &si);
4457 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4459 // no default CCrystalTextView
4460 return CView::OnMouseWheel(nFlags, zDelta, pt);
4463 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4467 * @brief Called when mouse's horizontal wheel is scrolled.
4469 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4471 SCROLLINFO si = { sizeof SCROLLINFO };
4472 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4474 VERIFY(GetScrollInfo(SB_HORZ, &si));
4477 si.nPos += zDelta / 40;
4478 if (si.nPos > si.nMax) si.nPos = si.nMax;
4479 if (si.nPos < si.nMin) si.nPos = si.nMin;
4481 SetScrollInfo(SB_HORZ, &si);
4484 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4486 // no default CCrystalTextView
4487 CView::OnMouseHWheel(nFlags, zDelta, pt);
4491 * @brief Change font size (zoom) in views.
4492 * @param [in] amount Amount of change/zoom, negative number makes
4493 * font smaller, positive number bigger and 0 reset the font size.
4495 void CMergeEditView::ZoomText(short amount)
4500 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4501 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4505 nPointSize = GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_POINTSIZE);
4506 if (nPointSize == 0)
4507 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4510 nPointSize += amount;
4514 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4516 CMergeDoc *pDoc = GetDocument();
4517 ASSERT(pDoc != nullptr);
4519 if (pDoc != nullptr)
4521 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4523 CMergeEditView *pView = GetGroupView(nPane);
4524 ASSERT(pView != nullptr);
4526 if (pView != nullptr)
4534 bool CMergeEditView::QueryEditable()
4536 return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4540 * @brief Adjust the point to remain in the displayed diff
4542 * @return Tells if the point has been changed
4544 bool CMergeEditView::EnsureInDiff(CPoint& pt)
4546 int nLineCount = GetLineCount();
4547 if (m_lineBegin >= nLineCount)
4548 m_lineBegin = nLineCount - 1;
4549 if (m_lineEnd >= nLineCount)
4550 m_lineEnd = nLineCount - 1;
4552 int diffLength = m_lineEnd - m_lineBegin + 1;
4553 // first get the degenerate case out of the way
4555 if (diffLength == 0)
4557 if (pt.y == m_lineBegin && pt.x == 0)
4565 if (pt.y < m_lineBegin)
4571 // diff is defined and not below diff
4572 if (m_lineEnd > -1 && pt.y > m_lineEnd)
4575 pt.x = GetLineLength(pt.y);
4581 void CMergeEditView::EnsureVisible(CPoint pt)
4586 // ensure we remain in diff
4587 if (EnsureInDiff(ptNew))
4588 SetCursorPos(ptNew);
4590 CCrystalTextView::EnsureVisible(ptNew);
4593 void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
4595 CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4598 void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
4600 CPoint ptStartNew = ptStart;
4601 CPoint ptEndNew = ptEnd;
4604 // ensure we remain in diff
4605 EnsureInDiff(ptStartNew);
4606 EnsureInDiff(ptEndNew);
4608 CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4611 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4615 int nLineCount = GetLineCount();
4616 if (m_lineBegin >= nLineCount)
4617 m_lineBegin = nLineCount - 1;
4618 if (m_lineEnd >= nLineCount)
4619 m_lineEnd = nLineCount - 1;
4621 // ensure we remain in diff
4622 int sublineBegin = GetSubLineIndex(m_lineBegin);
4623 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4624 int diffLength = sublineEnd - sublineBegin + 1;
4625 int displayLength = GetScreenLines();
4626 if (diffLength <= displayLength)
4627 nNewTopLine = sublineBegin;
4630 if (nNewTopLine < sublineBegin)
4631 nNewTopLine = sublineBegin;
4632 if (nNewTopLine + displayLength - 1 > sublineEnd)
4633 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4636 CPoint pt = GetCursorPos();
4637 if (EnsureInDiff(pt))
4640 auto [ptSelStart, ptSelEnd] = GetSelection();
4641 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4642 SetSelection(ptSelStart, ptSelEnd);
4644 CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4647 void CMergeEditView::SetActivePane()
4649 auto* pwndSplitterChild = GetParentSplitter(this, false);
4650 if (!pwndSplitterChild)
4652 if (pwndSplitterChild->GetColumnCount() > 1)
4653 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4655 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4659 * @brief Called when user selects View/Zoom In from menu.
4661 void CMergeEditView::OnViewZoomIn()
4667 * @brief Called when user selects View/Zoom Out from menu.
4669 void CMergeEditView::OnViewZoomOut()
4675 * @brief Called when user selects View/Zoom Normal from menu.
4677 void CMergeEditView::OnViewZoomNormal()
4682 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4684 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4686 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4690 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4693 void CMergeEditView::OnWindowSplit()
4696 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4697 CMergeDoc *pDoc = GetDocument();
4698 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4699 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4700 int nBuffer = m_nThisPane;
4701 if (pDoc->m_nGroups <= 2)
4703 wndSplitter.SplitRow(1);
4704 wndSplitter.EqualizeRows();
4708 wndSplitter.SetActivePane(0, 0);
4709 wndSplitter.DeleteRow(1);
4710 pDoc->GetView(0, nBuffer)->SetActivePane();
4714 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4716 pCmdUI->Enable(!m_bDetailView);
4717 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4720 void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
4723 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4724 const int pane = pNMItemActivate->iItem / 4;
4725 CMergeDoc* pDoc = GetDocument();
4726 if (pane >= pDoc->m_nBuffers || !GetParentFrame()->IsChild(CWnd::FromHandle(pNMItemActivate->hdr.hwndFrom)))
4729 switch (pNMItemActivate->iItem % 4)
4732 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4735 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
4740 ::GetCursorPos(&point);
4743 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4744 theApp.TranslateMenu(menu.m_hMenu);
4745 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4749 pDoc->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());