1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
8 * @file MergeEditView.cpp
10 * @brief Implementation of the CMergeEditView class
14 #include "MergeEditView.h"
18 #include "LocationView.h"
21 #include "OptionsMgr.h"
22 #include "OptionsDiffColors.h"
23 #include "FileTransform.h"
25 #include "WMGotoDlg.h"
26 #include "OptionsDef.h"
27 #include "SyntaxColors.h"
28 #include "MergeEditFrm.h"
29 #include "MergeLineFlags.h"
31 #include "DropHandler.h"
33 #include "ShellContextMenu.h"
40 #ifndef WM_MOUSEHWHEEL
41 # define WM_MOUSEHWHEEL 0x20e
45 using CrystalLineParser::TEXTBLOCK;
47 /** @brief Timer ID for delayed rescan. */
48 const UINT IDT_RESCAN = 2;
49 /** @brief Timer timeout for delayed rescan. */
50 const UINT RESCAN_TIMEOUT = 1000;
52 /** @brief Location for file compare specific help to open. */
53 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
55 /////////////////////////////////////////////////////////////////////////////
58 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
60 CMergeEditView::CMergeEditView()
61 : m_bCurrentLineIsDiff(false)
64 , m_bDetailView(false)
65 , m_piMergeEditStatus(nullptr)
66 , m_bAutomaticRescan(false)
67 , fTimerWaitingForIdle(0)
70 , m_CurrentPredifferID(0)
71 , m_bChangedSchemeManually(false)
73 SetParser(&m_xParser);
75 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
78 CMergeEditView::~CMergeEditView()
83 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
84 //{{AFX_MSG_MAP(CMergeEditView)
85 ON_COMMAND(ID_CURDIFF, OnCurdiff)
86 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
87 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
88 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
89 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
90 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
91 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
92 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
93 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
94 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
95 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
96 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
97 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
98 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
99 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
100 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
101 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
102 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
103 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
104 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
105 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
106 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
107 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
108 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
109 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
110 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
111 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
112 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
113 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
114 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
115 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
116 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
117 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
118 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
119 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
120 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
121 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
122 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
123 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
124 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
125 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
126 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
127 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
128 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
129 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
130 ON_WM_LBUTTONDBLCLK()
133 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
134 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
135 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
136 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
137 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
138 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
139 ON_COMMAND(ID_L2R, OnL2r)
140 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
141 ON_COMMAND(ID_LINES_L2R, OnLinesL2r)
142 ON_UPDATE_COMMAND_UI(ID_LINES_L2R, OnUpdateLinesL2r)
143 ON_COMMAND(ID_R2L, OnR2l)
144 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
145 ON_COMMAND(ID_LINES_R2L, OnLinesR2l)
146 ON_UPDATE_COMMAND_UI(ID_LINES_R2L, OnUpdateLinesR2l)
147 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
148 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
149 ON_COMMAND(ID_COPY_LINES_FROM_LEFT, OnCopyLinesFromLeft)
150 ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_LEFT, OnUpdateCopyLinesFromLeft)
151 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
152 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
153 ON_COMMAND(ID_COPY_LINES_FROM_RIGHT, OnCopyLinesFromRight)
154 ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_RIGHT, OnUpdateCopyLinesFromRight)
155 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
156 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
157 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
158 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
159 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
160 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
162 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
163 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
164 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
165 ON_COMMAND(ID_REFRESH, OnRefresh)
166 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
167 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
168 ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
169 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
170 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
171 ON_COMMAND(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnAddToSubstitutionFilters)
172 ON_UPDATE_COMMAND_UI(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnUpdateAddToSubstitutionFilters)
174 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
175 ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
176 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
177 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
178 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
179 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
180 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
181 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
182 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
183 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
184 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
185 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
186 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
187 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
188 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
189 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
190 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
191 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
192 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
193 ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
194 ON_COMMAND(ID_NEXT_PANE, OnChangePane)
195 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
196 ON_COMMAND(ID_GOTO_MOVED_LINE_LM, OnGotoMovedLineLM)
197 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_LM, OnUpdateGotoMovedLineLM)
198 ON_COMMAND(ID_GOTO_MOVED_LINE_MR, OnGotoMovedLineMR)
199 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_MR, OnUpdateGotoMovedLineMR)
200 ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
201 ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
202 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
203 ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
204 ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
205 ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
206 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
209 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
210 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
211 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
212 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
213 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
214 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
215 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
216 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
217 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
218 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
219 ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
220 ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
221 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
222 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
223 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
224 ON_COMMAND(ID_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();
273 * @brief Update any resources necessary after a GUI language change
275 void CMergeEditView::UpdateResources()
279 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
281 return GetDocument()->GetView(m_nThisGroup, nBuffer);
284 void CMergeEditView::PrimeListWithFile()
286 // Set the tab size now, just in case the options change...
287 // We don't update it at the end of OnOptions,
288 // we can update it safely now
289 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
292 * @brief Return text from line given
294 CString CMergeEditView::GetLineText(int idx)
296 return GetLineChars(idx);
300 * @brief Return text from selection
302 CString CMergeEditView::GetSelectedText()
305 auto [ptStart, ptEnd] = GetSelection();
306 if (ptStart != ptEnd)
307 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
312 * @brief Return number of selected characters
314 std::pair<int, int> CMergeEditView::GetSelectedLineAndCharacterCount()
316 auto [ptStart, ptEnd] = GetSelection();
317 int nCharsOrColumns =0;
318 int nSelectedLines = 0;
319 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
321 if ((GetLineFlags(nLine) & (LF_GHOST | LF_INVISIBLE)) == 0)
323 int nLineLength = GetLineLength(nLine) + (m_pTextBuffer->GetLineEol(nLine)[0] ? 1 : 0);
324 nCharsOrColumns += (nLine == ptEnd.y) ? ptEnd.x : nLineLength;
325 if (nLine == ptStart.y)
326 nCharsOrColumns -= ptStart.x;
327 if (nLine < ptEnd.y || (ptStart != ptEnd && ptEnd.x > 0))
331 if (m_bRectangularSelection)
333 int nStartLeft, nStartRight, nEndLeft, nEndRight;
334 GetColumnSelection(ptStart.y, nStartLeft, nStartRight);
335 GetColumnSelection(ptEnd.y, nEndLeft, nEndRight);
336 nCharsOrColumns = (std::max)(nStartRight, nEndRight) - (std::min)(nStartLeft, nEndLeft);
338 return { nSelectedLines, nCharsOrColumns };
342 * @brief Get diffs inside selection.
343 * @param [out] firstDiff First diff inside selection
344 * @param [out] lastDiff Last diff inside selection
345 * @note -1 is returned in parameters if diffs cannot be determined
346 * @todo This shouldn't be called when there is no diffs, so replace
347 * first 'if' with ASSERT()?
349 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
354 CMergeDoc *pd = GetDocument();
355 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
359 int firstLine, lastLine;
360 GetFullySelectedLines(firstLine, lastLine);
361 if (lastLine < firstLine)
364 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
365 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
366 if (firstDiff != -1 && lastDiff != -1)
370 // Check that first selected line is first diff's first line or above it
371 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
372 if ((int)di.dbegin < firstLine)
374 if (firstDiff < lastDiff)
378 // Check that last selected line is last diff's last line or below it
379 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
380 if ((int)di.dend > lastLine)
382 if (firstDiff < lastDiff)
386 // Special case: one-line diff is not selected if cursor is in it
387 if (firstLine == lastLine)
396 * @brief Get diffs inside selection.
397 * @param [out] firstDiff First diff inside selection
398 * @param [out] lastDiff Last diff inside selection
399 * @param [out] firstWordDiff First word level diff inside selection
400 * @param [out] lastWordDiff Last word level diff inside selection
401 * @note -1 is returned in parameters if diffs cannot be determined
402 * @todo This shouldn't be called when there is no diffs, so replace
403 * first 'if' with ASSERT()?
405 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
412 CMergeDoc *pd = GetDocument();
413 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
417 int firstLine, lastLine;
418 auto [ptStart, ptEnd] = GetSelection();
419 if (pptStart != nullptr)
421 if (pptEnd != nullptr)
423 firstLine = ptStart.y;
426 firstDiff = pd->m_diffList.LineToDiff(firstLine);
427 bool firstLineIsNotInDiff = firstDiff == -1;
430 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
435 lastDiff = pd->m_diffList.LineToDiff(lastLine);
436 bool lastLineIsNotInDiff = lastDiff == -1;
438 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
439 if (lastDiff < firstDiff)
446 if (firstDiff != -1 && lastDiff != -1)
450 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
452 firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
454 else if (ptStart != ptEnd)
456 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
457 if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
463 if (firstWordDiff == -1)
465 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
466 for (size_t i = 0; i < worddiffs.size(); ++i)
468 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
469 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
470 (firstLine == worddiffs[i].endline[m_nThisPane] &&
471 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
473 firstWordDiff = static_cast<int>(i);
478 if (firstLine >= di.dbegin && firstWordDiff == -1)
485 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
486 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
487 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
489 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
490 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
492 lastWordDiff = static_cast<int>(i);
497 if (lastLine <= di.dend && lastWordDiff == -1)
500 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
507 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
524 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
525 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
526 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
529 void CMergeEditView::GetSelectedDiffs(int & firstDiff, int & lastDiff)
534 CMergeDoc *pd = GetDocument();
535 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
539 int firstLine, lastLine;
540 auto [ptStart, ptEnd] = GetSelection();
541 firstLine = ptStart.y;
544 firstDiff = pd->m_diffList.LineToDiff(firstLine);
547 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
551 lastDiff = pd->m_diffList.LineToDiff(lastLine);
553 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
554 if (lastDiff < firstDiff)
560 ASSERT(firstDiff == -1 ? (lastDiff == -1) : true);
561 ASSERT(lastDiff == -1 ? (firstDiff == -1) : true);
564 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
566 CMergeDoc *pDoc = GetDocument();
567 std::map<int, std::vector<int>> ret;
568 std::map<int, std::vector<int> *> list;
569 auto [ptStart, ptEnd] = GetSelection();
570 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
572 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
574 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
576 GetColumnSelection(nLine, nLeft, nRight);
577 CPoint ptStart2, ptEnd2;
580 ptStart2.y = ptEnd2.y = nLine;
581 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
582 if (firstDiff != -1 && lastDiff != -1)
584 std::vector<int> *pWordDiffs;
585 if (list.find(firstDiff) == list.end())
586 list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
587 pWordDiffs = list[firstDiff];
588 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
590 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
591 pWordDiffs->push_back(i);
596 for (auto& it : list)
597 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
601 void CMergeEditView::OnInitialUpdate()
604 CCrystalEditViewEx::OnInitialUpdate();
606 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
607 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
613 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
615 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
617 CMergeDoc* pDoc = GetDocument();
618 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
621 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
625 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
626 return std::vector<CrystalLineParser::TEXTBLOCK>();
628 return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
631 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
633 static const std::vector<TEXTBLOCK> emptyBlocks;
636 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
640 DWORD dwLineFlags = GetLineFlags(nLineIndex);
641 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
644 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
647 CMergeDoc *pDoc = GetDocument();
648 if (pDoc->IsEditedAfterRescan(m_nThisPane))
651 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
656 pDoc->m_diffList.GetDiff(nDiff, cd);
657 int unemptyLineCount = 0;
658 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
660 if (cd.begin[nPane] != cd.end[nPane] + 1)
663 if (unemptyLineCount < 2)
666 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
667 size_t nWordDiffs = worddiffs.size();
669 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
671 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
672 blocks[0].m_nCharPos = 0;
673 blocks[0].m_nColorIndex = COLORINDEX_NONE;
674 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
676 for (i = 0, j = 1; i < nWordDiffs; i++)
678 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
680 if (pDoc->m_nBuffers > 2)
682 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
684 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
687 int begin[3], end[3];
688 bool deleted = false;
689 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
691 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
692 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
693 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
694 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
697 blocks[j].m_nCharPos = begin[m_nThisPane];
698 if (lineInCurrentDiff)
700 if (m_cachedColors.clrSelDiffText != CLR_NONE)
701 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
703 blocks[j].m_nColorIndex = COLORINDEX_NONE;
704 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
705 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
709 if (m_cachedColors.clrDiffText != CLR_NONE)
710 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
712 blocks[j].m_nColorIndex = COLORINDEX_NONE;
713 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
714 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
717 blocks[j].m_nCharPos = end[m_nThisPane];
718 blocks[j].m_nColorIndex = COLORINDEX_NONE;
719 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
728 COLORREF CMergeEditView::GetColor(int nColorIndex) const
730 switch (nColorIndex & ~COLORINDEX_MASK)
732 case COLORINDEX_HIGHLIGHTBKGND1:
733 return m_cachedColors.clrSelWordDiff;
734 case COLORINDEX_HIGHLIGHTTEXT1:
735 return m_cachedColors.clrSelWordDiffText;
736 case COLORINDEX_HIGHLIGHTBKGND2:
737 return m_cachedColors.clrWordDiff;
738 case COLORINDEX_HIGHLIGHTTEXT2:
739 return m_cachedColors.clrWordDiffText;
740 case COLORINDEX_HIGHLIGHTBKGND3:
741 return m_cachedColors.clrWordDiffDeleted;
742 case COLORINDEX_HIGHLIGHTBKGND4:
743 return m_cachedColors.clrSelWordDiffDeleted;
746 return CCrystalTextView::GetColor(nColorIndex);
751 * @brief Determine text and background color for line
752 * @param [in] nLineIndex Index of line in view (NOT line in file)
753 * @param [out] crBkgnd Backround color for line
754 * @param [out] crText Text color for line
756 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
757 COLORREF & crText, bool & bDrawWhitespace)
759 DWORD ignoreFlags = 0;
760 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
764 * @brief Determine text and background color for line
765 * @param [in] nLineIndex Index of line in view (NOT line in file)
766 * @param [in] ignoreFlags Flags that caller wishes ignored
767 * @param [out] crBkgnd Backround color for line
768 * @param [out] crText Text color for line
770 * This version allows caller to suppress particular flags
772 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
773 COLORREF & crText, bool & bDrawWhitespace)
775 if (GetLineCount() <= nLineIndex)
778 DWORD dwLineFlags = GetLineFlags(nLineIndex);
780 if (dwLineFlags & ignoreFlags)
781 dwLineFlags &= (~ignoreFlags);
785 // Line with WinMerge flag,
786 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
787 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
789 crText = m_cachedColors.clrDiffText;
790 bDrawWhitespace = true;
792 if (dwLineFlags & LF_GHOST)
794 crBkgnd = m_cachedColors.clrDiffDeleted;
799 // If no syntax hilighting
800 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
802 crBkgnd = GetColor (COLORINDEX_BKGND);
803 crText = GetColor (COLORINDEX_NORMALTEXT);
804 bDrawWhitespace = false;
807 // Line not inside diff, get colors from CrystalEditor
808 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
809 crText, bDrawWhitespace);
811 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
813 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
814 crText = GetColor (COLORINDEX_WHITESPACE);
815 bDrawWhitespace = false;
821 if (dwLineFlags & LF_WINMERGE_FLAGS)
823 crText = m_cachedColors.clrDiffText;
824 bDrawWhitespace = true;
825 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
827 if (dwLineFlags & LF_SNP)
829 if (lineInCurrentDiff)
831 if (dwLineFlags & LF_GHOST)
832 crBkgnd = m_cachedColors.clrSelSNPDeleted;
834 crBkgnd = m_cachedColors.clrSelSNP;
835 crText = m_cachedColors.clrSelSNPText;
839 if (dwLineFlags & LF_GHOST)
840 crBkgnd = m_cachedColors.clrSNPDeleted;
842 crBkgnd = m_cachedColors.clrSNP;
843 crText = m_cachedColors.clrSNPText;
847 else if (dwLineFlags & LF_DIFF)
849 if (lineInCurrentDiff)
851 if (dwLineFlags & LF_MOVED)
853 crBkgnd = m_cachedColors.clrSelMoved;
854 crText = m_cachedColors.clrSelMovedText;
858 crBkgnd = m_cachedColors.clrSelDiff;
859 crText = m_cachedColors.clrSelDiffText;
865 if (dwLineFlags & LF_MOVED)
867 crBkgnd = m_cachedColors.clrMoved;
868 crText = m_cachedColors.clrMovedText;
872 crBkgnd = m_cachedColors.clrDiff;
873 crText = m_cachedColors.clrDiffText;
878 else if (dwLineFlags & LF_TRIVIAL)
880 // trivial diff can not be selected
881 if (dwLineFlags & LF_GHOST)
882 // ghost lines in trivial diff has their own color
883 crBkgnd = m_cachedColors.clrTrivialDeleted;
885 crBkgnd = m_cachedColors.clrTrivial;
886 crText = m_cachedColors.clrTrivialText;
889 else if (dwLineFlags & LF_GHOST)
891 if (lineInCurrentDiff)
893 if (dwLineFlags & LF_MOVED)
894 crBkgnd = m_cachedColors.clrSelMovedDeleted;
896 crBkgnd = m_cachedColors.clrSelDiffDeleted;
900 if (dwLineFlags & LF_MOVED)
901 crBkgnd = m_cachedColors.clrMovedDeleted;
903 crBkgnd = m_cachedColors.clrDiffDeleted;
910 // Line not inside diff,
911 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
913 // If no syntax hilighting, get windows default colors
914 crBkgnd = GetColor (COLORINDEX_BKGND);
915 crText = GetColor (COLORINDEX_NORMALTEXT);
916 bDrawWhitespace = false;
919 // Syntax highlighting, get colors from CrystalEditor
920 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
921 crText, bDrawWhitespace);
926 * @brief Sync other pane position
928 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
930 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
931 if (pSplitterWnd != nullptr)
933 // See CSplitterWnd::IdFromRowCol() implementation for details
934 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
935 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
936 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
937 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
939 // limit the TopLine : must be smaller than GetLineCount for all the panels
940 int newTopSubLine = m_nTopSubLine;
941 int nRows = pSplitterWnd->GetRowCount ();
942 int nCols = pSplitterWnd->GetColumnCount ();
944 // for (nRow = 0; nRow < nRows; nRow++)
946 // for (int nCol = 0; nCol < nCols; nCol++)
948 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
949 // if (pSiblingView != nullptr)
950 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
951 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
954 if (m_nTopSubLine != newTopSubLine)
955 ScrollToSubLine(newTopSubLine);
957 for (nRow = 0; nRow < nRows; nRow++)
959 for (int nCol = 0; nCol < nCols; nCol++)
961 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
963 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
964 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
965 pSiblingView->OnUpdateSibling (this, bHorz);
973 * @brief Update other panes
975 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
977 if (pUpdateSource != this)
979 ASSERT (pUpdateSource != nullptr);
980 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
981 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
982 if (!bHorz) // changed this so bHorz works right
984 ASSERT (pSrcView->m_nTopSubLine >= 0);
986 // This ASSERT is wrong: panes have different files and
987 // different linecounts
988 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
989 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
991 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
993 RecalcVertScrollBar(true);
994 RecalcHorzScrollBar();
999 ASSERT (pSrcView->m_nOffsetChar >= 0);
1001 // This ASSERT is wrong: panes have different files and
1002 // different linelengths
1003 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
1004 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
1006 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
1008 RecalcHorzScrollBar(true);
1009 RecalcHorzScrollBar();
1015 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
1017 int newlineBegin, newlineEnd;
1018 CMergeDoc *pd = GetDocument();
1019 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
1027 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
1029 newlineBegin = curDiff.dbegin;
1030 ASSERT (newlineBegin >= 0);
1031 newlineEnd = curDiff.dend;
1034 m_lineBegin = newlineBegin;
1035 m_lineEnd = newlineEnd;
1037 int nLineCount = GetLineCount();
1038 if (m_lineBegin > nLineCount)
1039 m_lineBegin = nLineCount - 1;
1040 if (m_lineEnd > nLineCount)
1041 m_lineEnd = nLineCount - 1;
1043 if (m_nTopLine == newlineBegin)
1046 // scroll to the first line of the diff
1047 vector<WordDiff> worddiffs;
1048 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
1049 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
1050 CPoint pt = worddiffs.size() > 0 ?
1051 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
1052 CPoint{ 0, m_lineBegin };
1053 ScrollToLine(m_lineBegin);
1057 // update the width of the horizontal scrollbar
1058 RecalcHorzScrollBar();
1062 * @brief Selects diff by number and syncs other file
1063 * @param [in] nDiff Diff to select, must be >= 0
1064 * @param [in] bScroll Scroll diff to view
1065 * @param [in] bSelectText Select diff text
1066 * @sa CMergeEditView::ShowDiff()
1067 * @sa CMergeDoc::SetCurrentDiff()
1068 * @todo Parameter bSelectText is never used?
1070 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
1072 CMergeDoc *pd = GetDocument();
1074 // Check that nDiff is valid
1076 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
1077 if (nDiff >= pd->m_diffList.GetSize())
1078 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
1079 nDiff, pd->m_diffList.GetSize());
1082 pd->SetCurrentDiff(nDiff);
1083 ShowDiff(bScroll, bSelectText);
1084 pd->UpdateAllViews(this);
1085 UpdateSiblingScrollPos(false);
1087 // notify either side, as it will notify the other one
1088 pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1091 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1093 CMergeDoc *pd = GetDocument();
1094 // If we have a selected diff, deselect it
1095 int nCurrentDiff = pd->GetCurrentDiff();
1096 if (nCurrentDiff != -1)
1098 CPoint pos = GetCursorPos();
1099 if (!IsLineInCurrentDiff(pos.y))
1101 pd->SetCurrentDiff(-1);
1103 pd->UpdateAllViews(this);
1109 * @brief Called when user selects "Current Difference".
1110 * Goes to active diff. If no active diff, selects diff under cursor
1111 * @sa CMergeEditView::SelectDiff()
1112 * @sa CMergeDoc::GetCurrentDiff()
1113 * @sa CMergeDoc::LineToDiff()
1115 void CMergeEditView::OnCurdiff()
1117 CMergeDoc *pd = GetDocument();
1119 // If no diffs, nothing to select
1120 if (!pd->m_diffList.HasSignificantDiffs())
1123 // GetCurrentDiff() returns -1 if no diff selected
1124 int nDiff = pd->GetCurrentDiff();
1127 // Scroll to the first line of the currently selected diff
1128 SelectDiff(nDiff, true, false);
1132 // If cursor is inside diff, select that diff
1133 CPoint pos = GetCursorPos();
1134 nDiff = pd->m_diffList.LineToDiff(pos.y);
1135 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1136 SelectDiff(nDiff, true, false);
1141 * @brief Called when "Current diff" item is updated
1143 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1145 CMergeDoc *pd = GetDocument();
1146 CPoint pos = GetCursorPos();
1147 int nCurrentDiff = pd->GetCurrentDiff();
1148 if (nCurrentDiff == -1)
1150 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1151 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1154 pCmdUI->Enable(true);
1158 * @brief Copy selected text to clipboard
1160 void CMergeEditView::OnEditCopy()
1162 CMergeDoc * pDoc = GetDocument();
1163 auto [ptSelStart, ptSelEnd] = GetSelection();
1166 if (ptSelStart == ptSelEnd)
1171 if (!m_bRectangularSelection)
1173 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1175 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1176 ptSelEnd.y, ptSelEnd.x, text);
1179 GetTextWithoutEmptysInColumnSelection(text);
1181 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1185 * @brief Called when "Copy" item is updated
1187 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1189 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1193 * @brief Cut current selection to clipboard
1195 void CMergeEditView::OnEditCut()
1197 if (!QueryEditable())
1200 CMergeDoc * pDoc = GetDocument();
1201 auto [ptSelStart, ptSelEnd] = GetSelection();
1204 if (ptSelStart == ptSelEnd)
1208 if (!m_bRectangularSelection)
1209 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1210 ptSelEnd.y, ptSelEnd.x, text);
1212 GetTextWithoutEmptysInColumnSelection(text);
1214 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1216 if (!m_bRectangularSelection)
1218 CPoint ptCursorPos = ptSelStart;
1219 ASSERT_VALIDTEXTPOS(ptCursorPos);
1220 SetAnchor(ptCursorPos);
1221 SetSelection(ptCursorPos, ptCursorPos);
1222 SetCursorPos(ptCursorPos);
1223 EnsureVisible(ptCursorPos);
1225 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1226 ptSelEnd.x, CE_ACTION_CUT);
1229 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1231 m_pTextBuffer->SetModified(true);
1235 * @brief Called when "Cut" item is updated
1237 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1239 if (QueryEditable())
1240 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1242 pCmdUI->Enable(false);
1246 * @brief Paste text from clipboard
1248 void CMergeEditView::OnEditPaste()
1250 if (!QueryEditable())
1253 CCrystalEditViewEx::Paste();
1254 m_pTextBuffer->SetModified(true);
1258 * @brief Called when "Paste" item is updated
1260 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1262 if (QueryEditable())
1263 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1265 pCmdUI->Enable(false);
1269 * @brief Undo last action
1271 void CMergeEditView::OnEditUndo()
1273 CWaitCursor waitstatus;
1274 CMergeDoc* pDoc = GetDocument();
1275 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1278 if (!QueryEditable())
1281 GetParentFrame()->SetActiveView(this, true);
1282 if(CCrystalEditViewEx::DoEditUndo())
1285 pDoc->UpdateHeaderPath(m_nThisPane);
1286 pDoc->FlushAndRescan();
1289 m_pTextBuffer->GetRedoActionCode(nAction);
1290 if (nAction == CE_ACTION_MERGE)
1291 // select the diff so we may just merge it again
1297 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1299 if (!pDoc->CanUndo())
1300 pDoc->SetAutoMerged(false);
1304 * @brief Called when "Undo" item is updated
1306 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1308 CMergeDoc* pDoc = GetDocument();
1309 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1311 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1312 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1315 pCmdUI->Enable(false);
1319 * @brief Go to first diff
1321 * Called when user selects "First Difference"
1322 * @sa CMergeEditView::SelectDiff()
1324 void CMergeEditView::OnFirstdiff()
1326 CMergeDoc *pd = GetDocument();
1327 if (pd->m_diffList.HasSignificantDiffs())
1329 int nDiff = pd->m_diffList.FirstSignificantDiff();
1330 SelectDiff(nDiff, true, false);
1335 * @brief Update "First diff" UI items
1337 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1339 OnUpdatePrevdiff(pCmdUI);
1343 * @brief Go to last diff
1345 void CMergeEditView::OnLastdiff()
1347 CMergeDoc *pd = GetDocument();
1348 if (pd->m_diffList.HasSignificantDiffs())
1350 int nDiff = pd->m_diffList.LastSignificantDiff();
1351 SelectDiff(nDiff, true, false);
1356 * @brief Update "Last diff" UI items
1358 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1360 OnUpdateNextdiff(pCmdUI);
1364 * @brief Go to next diff and select it.
1366 * Finds and selects next difference. There are several cases:
1367 * - if there is selected difference, and that difference is visible
1368 * on screen, next found difference is selected.
1369 * - if there is selected difference but it is not visible, next
1370 * difference from cursor position is selected. This is what user
1371 * expects to happen and is natural thing to do. Also reduces
1372 * needless scrolling.
1373 * - if there is no selected difference, next difference from cursor
1374 * position is selected.
1376 void CMergeEditView::OnNextdiff()
1378 CMergeDoc *pd = GetDocument();
1379 int cnt = pd->m_ptBuf[0]->GetLineCount();
1383 // Returns -1 if no diff selected
1385 int curDiff = pd->GetCurrentDiff();
1389 if (!IsDiffVisible(curDiff))
1391 // Selected difference not visible, select next from cursor
1392 int line = GetCursorPos().y;
1393 // Make sure we aren't in the first line of the diff
1395 if (!IsValidTextPosY(CPoint(0, line)))
1397 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1401 // Find out if there is a following significant diff
1402 if (curDiff < pd->m_diffList.GetSize() - 1)
1404 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1410 // We don't have a selected difference,
1411 // but cursor can be inside inactive diff
1412 int line = GetCursorPos().y;
1413 if (!IsValidTextPosY(CPoint(0, line)))
1415 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1418 int lastDiff = pd->m_diffList.LastSignificantDiff();
1419 if (nextDiff >= 0 && nextDiff <= lastDiff)
1420 SelectDiff(nextDiff, true, false);
1421 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1423 if (pDirDoc->MoveableToNextDiff())
1424 pDirDoc->MoveToNextDiff(pd);
1429 * @brief Update "Next diff" UI items
1431 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1433 CMergeDoc *pd = GetDocument();
1434 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1439 // There aren't any significant differences
1444 // Enable if the beginning of the last significant difference is after caret
1445 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1448 if (!enabled && pd->GetDirDoc())
1449 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1451 pCmdUI->Enable(enabled);
1455 * @brief Go to previous diff and select it.
1457 * Finds and selects previous difference. There are several cases:
1458 * - if there is selected difference, and that difference is visible
1459 * on screen, previous found difference is selected.
1460 * - if there is selected difference but it is not visible, previous
1461 * difference from cursor position is selected. This is what user
1462 * expects to happen and is natural thing to do. Also reduces
1463 * needless scrolling.
1464 * - if there is no selected difference, previous difference from cursor
1465 * position is selected.
1467 void CMergeEditView::OnPrevdiff()
1469 CMergeDoc *pd = GetDocument();
1470 int cnt = pd->m_ptBuf[0]->GetLineCount();
1474 // GetCurrentDiff() returns -1 if no diff selected
1476 int curDiff = pd->GetCurrentDiff();
1480 if (!IsDiffVisible(curDiff))
1482 // Selected difference not visible, select previous from cursor
1483 int line = GetCursorPos().y;
1484 // Make sure we aren't in the last line of the diff
1486 if (!IsValidTextPosY(CPoint(0, line)))
1488 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1492 // Find out if there is a preceding significant diff
1495 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1501 // We don't have a selected difference,
1502 // but cursor can be inside inactive diff
1503 int line = GetCursorPos().y;
1504 if (!IsValidTextPosY(CPoint(0, line)))
1506 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1509 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1510 if (prevDiff >= 0 && prevDiff >= firstDiff)
1511 SelectDiff(prevDiff, true, false);
1512 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1514 if (pDirDoc->MoveableToPrevDiff())
1515 pDirDoc->MoveToPrevDiff(pd);
1520 * @brief Update "Previous diff" UI items
1522 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1524 CMergeDoc *pd = GetDocument();
1525 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1530 // There aren't any significant differences
1535 // Enable if the end of the first significant difference is before caret
1536 enabled = (GetCursorPos().y > (long)dfi->dend);
1539 if (!enabled && pd->GetDirDoc())
1540 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1542 pCmdUI->Enable(enabled);
1545 void CMergeEditView::OnNextConflict()
1547 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1551 * @brief Update "Next Conflict" UI items
1553 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1555 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1558 void CMergeEditView::OnPrevConflict()
1560 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1564 * @brief Update "Prev Conflict" UI items
1566 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1568 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1572 * @brief Go to next 3-way diff and select it.
1574 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1576 CMergeDoc *pd = GetDocument();
1577 int cnt = pd->m_ptBuf[0]->GetLineCount();
1581 // Returns -1 if no diff selected
1582 int curDiff = pd->GetCurrentDiff();
1586 int nextDiff = curDiff;
1587 if (!IsDiffVisible(curDiff))
1589 // Selected difference not visible, select next from cursor
1590 int line = GetCursorPos().y;
1591 // Make sure we aren't in the first line of the diff
1593 if (!IsValidTextPosY(CPoint(0, line)))
1595 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1599 // Find out if there is a following significant diff
1600 if (curDiff < pd->m_diffList.GetSize() - 1)
1602 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1608 // nextDiff is the next one if there is one, else it is the one we're on
1609 SelectDiff(nextDiff, true, false);
1613 // We don't have a selected difference,
1614 // but cursor can be inside inactive diff
1615 int line = GetCursorPos().y;
1616 if (!IsValidTextPosY(CPoint(0, line)))
1618 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1620 SelectDiff(curDiff, true, false);
1625 * @brief Update "Next 3-way diff" UI items
1627 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1629 CMergeDoc *pd = GetDocument();
1631 if (pd->m_nBuffers < 3)
1633 pCmdUI->Enable(false);
1637 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1641 // There aren't any significant differences
1642 pCmdUI->Enable(false);
1646 // Enable if the beginning of the last significant difference is after caret
1647 CPoint pos = GetCursorPos();
1648 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1653 * @brief Go to previous 3-way diff and select it.
1655 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1657 CMergeDoc *pd = GetDocument();
1659 int cnt = pd->m_ptBuf[0]->GetLineCount();
1663 // GetCurrentDiff() returns -1 if no diff selected
1664 int curDiff = pd->GetCurrentDiff();
1668 int prevDiff = curDiff;
1669 if (!IsDiffVisible(curDiff))
1671 // Selected difference not visible, select previous from cursor
1672 int line = GetCursorPos().y;
1673 // Make sure we aren't in the last line of the diff
1675 if (!IsValidTextPosY(CPoint(0, line)))
1677 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1681 // Find out if there is a preceding significant diff
1684 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1690 // prevDiff is the preceding one if there is one, else it is the one we're on
1691 SelectDiff(prevDiff, true, false);
1695 // We don't have a selected difference,
1696 // but cursor can be inside inactive diff
1697 int line = GetCursorPos().y;
1698 if (!IsValidTextPosY(CPoint(0, line)))
1700 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1702 SelectDiff(curDiff, true, false);
1707 * @brief Update "Previous diff X and Y" UI items
1709 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1711 CMergeDoc *pd = GetDocument();
1713 if (pd->m_nBuffers < 3)
1715 pCmdUI->Enable(false);
1719 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1723 // There aren't any significant differences
1724 pCmdUI->Enable(false);
1728 // Enable if the end of the first significant difference is before caret
1729 CPoint pos = GetCursorPos();
1730 pCmdUI->Enable(pos.y > (long)dfi->dend);
1734 void CMergeEditView::OnNextdiffLM()
1736 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1739 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1741 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1744 void CMergeEditView::OnNextdiffLR()
1746 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1749 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1751 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1754 void CMergeEditView::OnNextdiffMR()
1756 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1759 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1761 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1764 void CMergeEditView::OnNextdiffLO()
1766 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1769 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1771 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1774 void CMergeEditView::OnNextdiffMO()
1776 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1779 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1781 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1784 void CMergeEditView::OnNextdiffRO()
1786 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1789 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1791 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1794 void CMergeEditView::OnPrevdiffLM()
1796 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1799 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1801 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1804 void CMergeEditView::OnPrevdiffLR()
1806 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1809 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1811 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1814 void CMergeEditView::OnPrevdiffMR()
1816 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1819 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1821 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1824 void CMergeEditView::OnPrevdiffLO()
1826 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1829 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1831 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1834 void CMergeEditView::OnPrevdiffMO()
1836 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1839 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1841 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1844 void CMergeEditView::OnPrevdiffRO()
1846 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1849 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1851 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1855 * @brief Clear selection
1857 void CMergeEditView::SelectNone()
1859 SetSelection (GetCursorPos(), GetCursorPos());
1864 * @brief Check if line is inside currently selected diff
1865 * @param [in] nLine 0-based linenumber in view
1866 * @sa CMergeDoc::GetCurrentDiff()
1867 * @sa CMergeDoc::LineInDiff()
1869 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1871 // Check validity of nLine
1874 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1875 int nLineCount = LocateTextBuffer()->GetLineCount();
1876 if (nLine >= nLineCount)
1877 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1880 const CMergeDoc *pd = GetDocument();
1881 int curDiff = pd->GetCurrentDiff();
1884 return pd->m_diffList.LineInDiff(nLine, curDiff);
1888 * @brief Called when mouse left-button double-clicked
1890 * Double-clicking mouse inside diff selects that diff
1892 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1894 CMergeDoc *pd = GetDocument();
1895 CPoint pos = GetCursorPos();
1897 int diff = pd->m_diffList.LineToDiff(pos.y);
1898 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1899 SelectDiff(diff, false, false);
1901 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1905 * @brief Called when mouse left button is released.
1907 * If button is released outside diffs, current diff
1910 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1912 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1913 DeselectDiffIfCursorNotInCurrentDiff();
1917 * @brief Called when mouse right button is pressed.
1919 * If right button is pressed outside diffs, current diff
1922 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1924 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1925 DeselectDiffIfCursorNotInCurrentDiff();
1928 void CMergeEditView::OnX2Y(int srcPane, int dstPane, bool selectedLineOnly)
1930 // Check that right side is not readonly
1931 if (IsReadOnly(dstPane))
1934 CMergeDoc *pDoc = GetDocument();
1935 int currentDiff = pDoc->GetCurrentDiff();
1937 if (currentDiff == -1)
1940 // If cursor is inside diff get number of that diff
1941 if (m_bCurrentLineIsDiff)
1943 CPoint pt = GetCursorPos();
1944 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1948 auto [ptStart, ptEnd] = GetSelection();
1949 if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1951 if (!m_bRectangularSelection)
1953 if (selectedLineOnly)
1955 int firstDiff, lastDiff;
1956 GetSelectedDiffs(firstDiff, lastDiff);
1957 if (firstDiff != -1 && lastDiff != -1)
1959 CWaitCursor waitstatus;
1960 pDoc->CopyMultiplePartialList(srcPane, dstPane, firstDiff, lastDiff, ptStart.y, ptEnd.y);
1965 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1966 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1967 if (firstDiff != -1 && lastDiff != -1)
1969 CWaitCursor waitstatus;
1971 // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1972 // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1973 if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1975 // old behaviour: copy full line
1976 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1980 // new behaviour: copy selected text only
1981 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1988 CWaitCursor waitstatus;
1989 auto wordDiffs = GetColumnSelectedWordDiffIndice();
1991 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1992 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
1997 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1999 if (selectedLineOnly)
2001 CWaitCursor waitstatus;
2002 pDoc->PartialListCopy(srcPane, dstPane, currentDiff, ptStart.y, ptEnd.y);
2006 CWaitCursor waitstatus;
2007 pDoc->ListCopy(srcPane, dstPane, currentDiff);
2012 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
2014 // Check that right side is not readonly
2015 if (!IsReadOnly(dstPane))
2017 // If one or more diffs inside selection OR
2018 // there is an active diff OR
2019 // cursor is inside diff
2020 auto [ptStart, ptEnd] = GetSelection();
2021 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
2023 if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
2025 pCmdUI->Enable(true);
2029 int firstDiff, lastDiff;
2030 GetFullySelectedDiffs(firstDiff, lastDiff);
2032 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
2037 const int currDiff = GetDocument()->GetCurrentDiff();
2038 pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
2042 pCmdUI->Enable(false);
2046 * @brief Copy diff from left pane to right pane
2048 * Difference is copied from left to right when
2049 * - difference is selected
2050 * - difference is inside selection (allows merging multiple differences).
2051 * - cursor is inside diff
2053 * If there is selected diff outside selection, we copy selected
2056 void CMergeEditView::OnL2r()
2058 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2059 int srcPane = dstPane - 1;
2060 OnX2Y(srcPane, dstPane);
2064 * @brief Called when "Copy to left" item is updated
2066 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
2068 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2071 void CMergeEditView::OnLinesL2r()
2073 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2074 int srcPane = dstPane - 1;
2075 OnX2Y(srcPane, dstPane, true);
2078 void CMergeEditView::OnUpdateLinesL2r(CCmdUI* pCmdUI)
2080 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
2084 * @brief Copy diff from right pane to left pane
2086 * Difference is copied from left to right when
2087 * - difference is selected
2088 * - difference is inside selection (allows merging multiple differences).
2089 * - cursor is inside diff
2091 * If there is selected diff outside selection, we copy selected
2094 void CMergeEditView::OnR2l()
2096 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2097 int srcPane = dstPane + 1;
2098 OnX2Y(srcPane, dstPane);
2102 * @brief Called when "Copy to right" item is updated
2104 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
2106 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2109 void CMergeEditView::OnLinesR2l()
2111 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
2112 int srcPane = dstPane + 1;
2113 OnX2Y(srcPane, dstPane, true);
2116 void CMergeEditView::OnUpdateLinesR2l(CCmdUI* pCmdUI)
2118 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2121 void CMergeEditView::OnCopyFromLeft()
2123 int dstPane = m_nThisPane;
2124 int srcPane = dstPane - 1;
2127 OnX2Y(srcPane, dstPane);
2130 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2132 int dstPane = m_nThisPane;
2133 int srcPane = dstPane - 1;
2135 pCmdUI->Enable(false);
2137 OnUpdateX2Y(dstPane, pCmdUI);
2140 void CMergeEditView::OnCopyLinesFromLeft()
2142 int dstPane = m_nThisPane;
2143 int srcPane = dstPane - 1;
2146 OnX2Y(srcPane, dstPane, true);
2149 void CMergeEditView::OnUpdateCopyLinesFromLeft(CCmdUI* pCmdUI)
2151 int dstPane = m_nThisPane;
2152 int srcPane = dstPane - 1;
2154 pCmdUI->Enable(false);
2156 OnUpdateX2Y(dstPane, pCmdUI);
2159 void CMergeEditView::OnCopyFromRight()
2161 int dstPane = m_nThisPane;
2162 int srcPane = dstPane + 1;
2163 if (srcPane >= GetDocument()->m_nBuffers)
2165 OnX2Y(srcPane, dstPane);
2168 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2170 int dstPane = m_nThisPane;
2171 int srcPane = dstPane + 1;
2172 if (srcPane >= GetDocument()->m_nBuffers)
2173 pCmdUI->Enable(false);
2175 OnUpdateX2Y(dstPane, pCmdUI);
2178 void CMergeEditView::OnCopyLinesFromRight()
2180 int dstPane = m_nThisPane;
2181 int srcPane = dstPane + 1;
2182 if (srcPane >= GetDocument()->m_nBuffers)
2184 OnX2Y(srcPane, dstPane, true);
2187 void CMergeEditView::OnUpdateCopyLinesFromRight(CCmdUI* pCmdUI)
2189 int dstPane = m_nThisPane;
2190 int srcPane = dstPane + 1;
2191 if (srcPane >= GetDocument()->m_nBuffers)
2192 pCmdUI->Enable(false);
2194 OnUpdateX2Y(dstPane, pCmdUI);
2198 * @brief Copy all diffs from right pane to left pane
2200 void CMergeEditView::OnAllLeft()
2202 // Check that left side is not readonly
2203 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
2204 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2205 if (IsReadOnly(dstPane))
2207 CWaitCursor waitstatus;
2209 GetDocument()->CopyAllList(srcPane, dstPane);
2213 * @brief Called when "Copy all to left" item is updated
2215 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2217 // Check that left side is not readonly
2218 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2219 if (!IsReadOnly(dstPane))
2220 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2222 pCmdUI->Enable(false);
2226 * @brief Copy all diffs from left pane to right pane
2228 void CMergeEditView::OnAllRight()
2230 // Check that right side is not readonly
2231 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
2232 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2233 if (IsReadOnly(dstPane))
2236 CWaitCursor waitstatus;
2238 GetDocument()->CopyAllList(srcPane, dstPane);
2242 * @brief Called when "Copy all to right" item is updated
2244 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2246 // Check that right side is not readonly
2247 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2248 if (!IsReadOnly(dstPane))
2249 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2251 pCmdUI->Enable(false);
2255 * @brief Do Auto merge
2257 void CMergeEditView::OnAutoMerge()
2259 // Check current pane is not readonly
2260 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2263 CWaitCursor waitstatus;
2265 GetDocument()->DoAutoMerge(m_nThisPane);
2269 * @brief Called when "Auto Merge" item is updated
2271 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2273 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2274 !GetDocument()->IsModified() &&
2275 !GetDocument()->GetAutoMerged() &&
2280 * @brief Add synchronization point
2282 void CMergeEditView::OnAddSyncPoint()
2284 GetDocument()->AddSyncPoint();
2288 * @brief Clear synchronization points
2290 void CMergeEditView::OnClearSyncPoints()
2292 GetDocument()->ClearSyncPoints();
2296 * @brief Called when "Clear Synchronization Points" item is updated
2298 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2300 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2304 * @brief This function is called before other edit events.
2305 * @param [in] nAction Edit operation to do
2306 * @param [in] pszText Text to insert, delete etc
2307 * @sa CCrystalEditView::OnEditOperation()
2308 * @todo More edit-events for rescan delaying?
2310 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2312 if (!QueryEditable())
2314 // We must not arrive here, and assert helps detect troubles
2319 CMergeDoc* pDoc = GetDocument();
2320 pDoc->SetEditedAfterRescan(m_nThisPane);
2322 // simple hook for multiplex undo operations
2323 // deleted by jtuc 2003-06-28
2324 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2325 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2327 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2328 pDoc->undoTgt.push_back(this);
2329 pDoc->curUndo = pDoc->undoTgt.end();
2332 // perform original function
2333 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2335 // augment with additional operations
2337 // Change header to inform about changed doc
2338 pDoc->UpdateHeaderPath(m_nThisPane);
2340 // If automatic rescan enabled, rescan after edit events
2341 if (m_bAutomaticRescan)
2343 // keep document up to date
2344 // (Re)start timer to rescan only when user edits text
2345 // If timer starting fails, rescan immediately
2346 if (nAction == CE_ACTION_TYPING ||
2347 nAction == CE_ACTION_REPLACE ||
2348 nAction == CE_ACTION_BACKSPACE ||
2349 nAction == CE_ACTION_INDENT ||
2350 nAction == CE_ACTION_PASTE ||
2351 nAction == CE_ACTION_DELSEL ||
2352 nAction == CE_ACTION_DELETE ||
2353 nAction == CE_ACTION_CUT)
2355 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2356 pDoc->FlushAndRescan();
2359 pDoc->FlushAndRescan();
2365 // Update other pane for sync line.
2366 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2368 if (nPane == m_nThisPane)
2370 CCrystalEditView *pView = GetGroupView(nPane);
2371 if (pView != nullptr)
2372 pView->Invalidate();
2379 * @brief Redo last action
2381 void CMergeEditView::OnEditRedo()
2383 CWaitCursor waitstatus;
2384 CMergeDoc* pDoc = GetDocument();
2385 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2388 if (!QueryEditable())
2391 GetParentFrame()->SetActiveView(this, true);
2392 if(CCrystalEditViewEx::DoEditRedo())
2395 pDoc->UpdateHeaderPath(m_nThisPane);
2396 pDoc->FlushAndRescan();
2401 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2406 * @brief Called when "Redo" item is updated
2408 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2410 CMergeDoc* pDoc = GetDocument();
2411 if (pDoc->curUndo!=pDoc->undoTgt.end())
2413 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2414 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2417 pCmdUI->Enable(false);
2420 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2422 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2426 * @brief Scrolls to current diff and/or selects diff text
2427 * @param [in] bScroll If true scroll diff to view
2428 * @param [in] bSelectText If true select diff text
2429 * @note If bScroll and bSelectText are false, this does nothing!
2430 * @todo This shouldn't be called when no diff is selected, so
2431 * somebody could try to ASSERT(nDiff > -1)...
2433 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2435 CMergeDoc *pd = GetDocument();
2436 const int nDiff = pd->GetCurrentDiff();
2438 // Try to trap some errors
2439 if (nDiff >= pd->m_diffList.GetSize())
2440 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2441 nDiff, pd->m_diffList.GetSize());
2443 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2445 CPoint ptStart, ptEnd;
2447 pd->m_diffList.GetDiff(nDiff, curDiff);
2450 ptStart.y = curDiff.dbegin;
2452 ptEnd.y = curDiff.dend;
2454 if (bScroll && !m_bDetailView)
2456 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2458 // Difference is not visible, scroll it so that max amount of
2459 // scrolling is done while keeping the diff in screen. So if
2460 // scrolling is downwards, scroll the diff to as up in screen
2461 // as possible. This usually brings next diff to the screen
2462 // and we don't need to scroll into it.
2463 int nLine = GetSubLineIndex(ptStart.y);
2464 if (nLine > CONTEXT_LINES_ABOVE)
2466 nLine -= CONTEXT_LINES_ABOVE;
2468 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2469 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2471 if (nPane != m_nThisPane)
2472 GetGroupView(nPane)->ScrollToSubLine(nLine);
2476 vector<WordDiff> worddiffs;
2477 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
2478 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2479 CPoint pt = worddiffs.size() > 0 ?
2480 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2482 GetGroupView(m_nThisPane)->SetCursorPos(pt);
2483 GetGroupView(m_nThisPane)->SetAnchor(pt);
2484 GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2485 GetGroupView(m_nThisPane)->EnsureVisible(pt);
2486 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2488 if (nPane != m_nThisPane)
2490 if (worddiffs.size() > 0)
2492 pt.x = worddiffs[0].begin[nPane];
2493 pt.y = worddiffs[0].beginline[nPane];
2495 GetGroupView(nPane)->SetCursorPos(pt);
2496 GetGroupView(nPane)->SetAnchor(pt);
2497 GetGroupView(nPane)->SetSelection(pt, pt);
2504 ptEnd.x = GetLineLength(ptEnd.y);
2505 SetSelection(ptStart, ptEnd);
2514 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2516 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2517 // ...but for this the queue must be empty
2518 // The timer message is a low priority message but the queue is maybe not yet empty
2519 // So we set a flag, wait for OnIdle to proceed, then come back here...
2520 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2521 // not with SetTimer so there is no delay)
2523 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2525 // IDLE_TIMER is the false timer used to come back here after OnIdle
2526 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2527 // (one normal timer = one flag = one command)
2529 if (nIDEvent == IDT_RESCAN)
2531 KillTimer(IDT_RESCAN);
2532 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2533 // notify the app to come back after OnIdle
2534 theApp.SetNeedIdleTimer();
2537 if (nIDEvent == IDLE_TIMER)
2539 // not a real timer, just come back after OnIdle
2540 // look to flags to know what to do
2541 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2542 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2543 fTimerWaitingForIdle = 0;
2546 CCrystalEditViewEx::OnTimer(nIDEvent);
2550 * @brief Returns if buffer is read-only
2551 * @note This has no any relation to file being read-only!
2553 bool CMergeEditView::IsReadOnly(int pane) const
2555 return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2559 * @brief Called when "Save left (as...)" item is updated
2561 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2563 CMergeDoc *pd = GetDocument();
2564 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2568 * @brief Called when "Save middle (as...)" item is updated
2570 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2572 CMergeDoc *pd = GetDocument();
2573 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2577 * @brief Called when "Save right (as...)" item is updated
2579 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2581 CMergeDoc *pd = GetDocument();
2582 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2586 * @brief Refresh display using text-buffers
2587 * @note This DOES NOT reload files!
2589 void CMergeEditView::OnRefresh()
2591 CMergeDoc *pd = GetDocument();
2592 ASSERT(pd != nullptr);
2593 pd->FlushAndRescan(true);
2597 * @brief Handle some keys when in merging mode
2599 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2601 bool bHandled = false;
2603 // Allow default text selection when SHIFT pressed
2604 if (::GetAsyncKeyState(VK_SHIFT))
2607 // Allow default editor functions when CTRL pressed
2608 if (::GetAsyncKeyState(VK_CONTROL))
2611 // If we are in merging mode (merge with cursor keys)
2612 // handle some keys here
2613 switch (pMsg->wParam)
2640 * @brief Called before messages are translated.
2642 * Checks if ESC key was pressed, saves and closes doc.
2643 * Also if in merge mode traps cursor keys.
2645 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2647 if (pMsg->message == WM_KEYDOWN)
2649 // If we are in merging mode (merge with cursor keys)
2650 // handle some keys here
2651 if (theApp.GetMergingMode())
2653 bool bHandled = MergeModeKeyDown(pMsg);
2658 // Close window if user has allowed it from options
2659 if (pMsg->wParam == VK_ESCAPE)
2661 int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2662 if (nCloseWithEsc != 0)
2663 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2668 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2672 * @brief Called when "Save" item is updated
2674 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2676 CMergeDoc *pd = GetDocument();
2678 bool bModified = false;
2679 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2681 if (pd->m_ptBuf[nPane]->IsModified())
2684 pCmdUI->Enable(bModified);
2688 * @brief Enable/disable left buffer read-only
2690 void CMergeEditView::OnLeftReadOnly()
2692 CMergeDoc *pd = GetDocument();
2693 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2694 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2698 * @brief Called when "Left read-only" item is updated
2700 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2702 CMergeDoc *pd = GetDocument();
2703 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2704 pCmdUI->Enable(true);
2705 pCmdUI->SetCheck(bReadOnly);
2709 * @brief Enable/disable middle buffer read-only
2711 void CMergeEditView::OnMiddleReadOnly()
2713 CMergeDoc *pd = GetDocument();
2714 if (pd->m_nBuffers == 3)
2716 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2717 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2722 * @brief Called when "Middle read-only" item is updated
2724 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2726 CMergeDoc *pd = GetDocument();
2727 if (pd->m_nBuffers < 3)
2729 pCmdUI->Enable(false);
2733 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2734 pCmdUI->Enable(true);
2735 pCmdUI->SetCheck(bReadOnly);
2740 * @brief Enable/disable right buffer read-only
2742 void CMergeEditView::OnRightReadOnly()
2744 CMergeDoc *pd = GetDocument();
2745 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2746 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2750 * @brief Called when "Left read-only" item is updated
2752 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2754 CMergeDoc *pd = GetDocument();
2755 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2756 pCmdUI->Enable(true);
2757 pCmdUI->SetCheck(bReadOnly);
2760 /// Store interface we use to display status line info
2761 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2763 ASSERT(m_piMergeEditStatus == nullptr);
2764 m_piMergeEditStatus = piMergeEditStatus;
2768 * @brief Update status bar contents.
2770 void CMergeEditView::UpdateStatusbar()
2776 * @brief Update statusbar info, Override from CCrystalTextView
2777 * @note we tab-expand column, but we don't tab-expand char count,
2778 * since we want to show how many chars there are and tab is just one
2779 * character although it expands to several spaces.
2781 void CMergeEditView::OnUpdateCaret()
2783 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2786 CPoint cursorPos = GetCursorPos();
2787 int nScreenLine = cursorPos.y;
2788 const int nRealLine = ComputeRealLine(nScreenLine);
2795 auto [selectedLines, selectedChars] = GetSelectedLineAndCharacterCount();
2796 DWORD dwLineFlags = 0;
2798 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2799 // Is this a ghost line ?
2800 if (dwLineFlags & LF_GHOST)
2802 // Ghost lines display eg "Line 12-13"
2803 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2804 sEol = _T("hidden");
2808 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2809 sLine.Format(_T("%d"), nRealLine+1);
2810 curChar = cursorPos.x + 1;
2811 chars = GetLineLength(nScreenLine);
2812 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2813 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2815 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2816 GetDocument()->IsMixedEOL(m_nThisPane))
2818 sEol = GetTextBufferEol(nScreenLine);
2821 sEol = _T("hidden");
2823 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2824 curChar, chars, selectedLines, selectedChars,
2825 sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2827 // Is cursor inside difference?
2828 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2829 m_bCurrentLineIsDiff = true;
2831 m_bCurrentLineIsDiff = false;
2833 CWnd* pWnd = GetFocus();
2834 if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2835 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2838 * @brief Select linedifference in the current line.
2840 * Select line difference in current line. Selection type
2841 * is choosed by highlight type.
2843 template<bool reversed>
2844 void CMergeEditView::OnSelectLineDiff()
2846 // Pass this to the document, to compare this file to other
2847 GetDocument()->Showlinediff(this, reversed);
2850 /// Enable select difference menuitem if current line is inside difference.
2851 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2853 pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2856 void CMergeEditView::OnAddToSubstitutionFilters()
2858 // Pass this to the document, to compare this file to other
2859 GetDocument()->AddToSubstitutionFilters(this, false);
2862 void CMergeEditView::OnUpdateAddToSubstitutionFilters(CCmdUI* pCmdUI)
2864 pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
2868 * @brief Enable/disable Replace-menuitem
2870 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2872 CMergeDoc *pd = GetDocument();
2873 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2875 pCmdUI->Enable(!bReadOnly);
2879 * @brief Update readonly statusbaritem
2881 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2883 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2884 pCmdUI->Enable(bRO);
2888 * @brief Create the dynamic submenu for scripts
2890 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2893 std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2896 size_t i = GetMenuItemCount(hMenu);
2898 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2900 if (functionNamesList.size() == 0)
2902 // no script : create a <empty> entry
2903 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2907 // or fill in the submenu with the scripts names
2908 int ID = ID_SCRIPT_FIRST; // first ID in menu
2909 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2910 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2912 functionNamesList.clear();
2915 if (!plugin::IsWindowsScriptThere())
2916 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2922 * @brief Create the dynamic submenu for prediffers
2924 * @note The plugins are grouped in (suggested) and (not suggested)
2925 * The IDs follow the order of GetAvailableScripts
2927 * suggested 0 ID_1ST + 0
2928 * suggested 1 ID_1ST + 2
2929 * suggested 2 ID_1ST + 5
2930 * not suggested 0 ID_1ST + 1
2931 * not suggested 1 ID_1ST + 3
2932 * not suggested 2 ID_1ST + 4
2934 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2937 int i = GetMenuItemCount(hMenu);
2939 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2941 CMergeDoc *pd = GetDocument();
2942 ASSERT(pd != nullptr);
2945 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2947 // get the scriptlet files
2948 PluginArray * piScriptArray =
2949 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2950 PluginArray * piScriptArray2 =
2951 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2953 // build the menu : first part, suggested plugins
2955 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2956 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2958 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
2960 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2962 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2963 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2966 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2968 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2970 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2971 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2974 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2977 // build the menu : second part, others plugins
2979 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2980 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2982 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2983 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2985 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2986 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2989 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2991 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2993 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2994 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2997 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
3000 // compute the m_CurrentPredifferID (to set the radio button)
3001 PrediffingInfo prediffer;
3002 pd->GetPrediffer(&prediffer);
3004 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3005 m_CurrentPredifferID = 0;
3006 else if (prediffer.m_PluginName.empty())
3007 m_CurrentPredifferID = ID_NO_PREDIFFER;
3010 ID = ID_PREDIFFERS_FIRST; // first ID in menu
3011 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
3013 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
3014 if (prediffer.m_PluginName == plugin->m_name)
3015 m_CurrentPredifferID = ID;
3018 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
3020 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
3021 if (prediffer.m_PluginName == plugin->m_name)
3022 m_CurrentPredifferID = ID;
3030 * @brief Offer a context menu built with scriptlet/ActiveX functions
3032 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
3034 // Create the menu and populate it with the available functions
3036 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
3038 // Remove copying item copying from active side
3039 if (m_nThisPane == 0) // left?
3041 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3042 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3043 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3044 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3046 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
3048 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
3049 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
3050 menu.RemoveMenu(ID_COPY_LINES_FROM_RIGHT, MF_BYCOMMAND);
3051 menu.RemoveMenu(ID_COPY_LINES_FROM_LEFT, MF_BYCOMMAND);
3054 // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
3055 // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
3056 // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
3057 int nBuffers = GetDocument()->m_nBuffers;
3058 if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
3059 menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
3060 else if (nBuffers == 3 && m_nThisPane == 2)
3061 menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
3063 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
3064 theApp.TranslateMenu(menu.m_hMenu);
3066 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
3067 ASSERT(pSub != nullptr);
3069 // Context menu opened using keyboard has no coordinates
3070 if (point.x == -1 && point.y == -1)
3073 GetClientRect(rect);
3074 ClientToScreen(rect);
3076 point = rect.TopLeft();
3080 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
3081 point.x, point.y, AfxGetMainWnd());
3086 * @brief Update EOL mode in status bar
3088 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
3090 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
3094 * @brief Change EOL mode and unify all the lines EOL to this new mode
3096 void CMergeEditView::OnConvertEolTo(UINT nID )
3098 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;;
3102 nStyle = CRLFSTYLE::DOS;
3104 case ID_EOL_TO_UNIX:
3105 nStyle = CRLFSTYLE::UNIX;
3108 nStyle = CRLFSTYLE::MAC;
3112 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
3115 m_pTextBuffer->SetCRLFMode(nStyle);
3117 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
3118 if (m_pTextBuffer->applyEOLMode())
3120 CMergeDoc *pd = GetDocument();
3121 ASSERT(pd != nullptr);
3122 pd->UpdateHeaderPath(m_nThisPane);
3123 pd->FlushAndRescan(true);
3128 * @brief allow convert to entries in file submenu
3130 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
3132 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;
3133 switch (pCmdUI->m_nID)
3136 nStyle = CRLFSTYLE::DOS;
3138 case ID_EOL_TO_UNIX:
3139 nStyle = CRLFSTYLE::UNIX;
3142 nStyle = CRLFSTYLE::MAC;
3146 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
3150 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3151 GetDocument()->IsMixedEOL(m_nThisPane) ||
3152 nStyle != m_pTextBuffer->GetCRLFMode())
3154 pCmdUI->SetRadio(false);
3156 // Don't allow selecting other EOL style for protected pane
3157 if (!QueryEditable())
3158 pCmdUI->Enable(false);
3161 pCmdUI->SetRadio(true);
3165 * @brief Copy diff from left to right and advance to next diff
3167 void CMergeEditView::OnL2RNext()
3170 if (IsCursorInDiff()) // for 3-way file compare
3176 * @brief Update "Copy right and advance" UI item
3178 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
3180 OnUpdateL2r(pCmdUI);
3184 * @brief Copy diff from right to left and advance to next diff
3186 void CMergeEditView::OnR2LNext()
3189 if (IsCursorInDiff()) // for 3-way file compare
3195 * @brief Update "Copy left and advance" UI item
3197 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
3199 OnUpdateR2l(pCmdUI);
3203 * @brief Change active pane in MergeView.
3204 * Changes active pane and makes sure cursor position is kept in
3205 * screen. Currently we put cursor in same line than in original
3206 * active pane but we could be smarter too? Maybe update cursor
3207 * only when it is not visible in new pane?
3209 void CMergeEditView::OnChangePane()
3211 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3212 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
3213 CMergeDoc *pDoc = GetDocument();
3214 bool bFound = false;
3215 CMergeEditView *pNextActiveView = nullptr;
3216 std::vector<CMergeEditView *> list = pDoc->GetViewList();
3217 list.insert(list.end(), list.begin(), list.end());
3218 for (auto& pView : list)
3220 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3222 pNextActiveView = pView;
3228 GetParentFrame()->SetActiveView(pNextActiveView);
3229 CPoint ptCursor = pWnd->GetCursorPos();
3231 if (ptCursor.y >= pNextActiveView->GetLineCount())
3232 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3233 pNextActiveView->SetCursorPos(ptCursor);
3234 pNextActiveView->SetAnchor(ptCursor);
3235 pNextActiveView->SetSelection(ptCursor, ptCursor);
3239 * @brief Show "Go To" dialog and scroll views to line or diff.
3241 * Before dialog is opened, current line and file is determined
3243 * @note Conversions needed between apparent and real lines
3245 void CMergeEditView::OnWMGoto()
3248 CMergeDoc *pDoc = GetDocument();
3249 CPoint pos = GetCursorPos();
3253 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3254 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
3255 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
3257 // Set active file and current line selected in dialog
3258 dlg.m_strParam = strutils::to_str(nRealLine + 1);
3259 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3260 dlg.m_nGotoWhat = 0;
3262 if (dlg.DoModal() == IDOK)
3264 CMergeDoc * pDoc1 = GetDocument();
3265 CMergeEditView * pCurrentView = nullptr;
3268 pCurrentView = GetGroupView(m_nThisPane);
3271 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3273 if (dlg.m_nGotoWhat == 0)
3275 int nRealLine1 = num;
3278 if (nRealLine1 > nLastLine)
3279 nRealLine1 = nLastLine;
3281 bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
3282 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile, !bShift);
3289 if (diff >= pDoc1->m_diffList.GetSize())
3290 diff = pDoc1->m_diffList.GetSize();
3292 pCurrentView->SelectDiff(diff, true, false);
3298 * @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
3299 * Go to moved line between the left and right panes when in 2-way file comparison.
3300 * Go to moved line between the left and middle panes when in 3-way file comparison.
3302 void CMergeEditView::OnGotoMovedLineLM()
3304 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3307 CMergeDoc* pDoc = GetDocument();
3308 CPoint pos = GetCursorPos();
3310 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3311 ASSERT(pDoc != nullptr);
3312 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3315 if (m_nThisPane == 0)
3317 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3319 GotoLine(line, false, 1);
3321 else if (m_nThisPane == 1)
3323 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3325 GotoLine(line, false, 0);
3330 * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
3331 * @param [in] pCmdUI UI component to update.
3332 * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
3334 void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
3336 CMergeDoc* pDoc = GetDocument();
3337 CPoint pos = GetCursorPos();
3339 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3340 ASSERT(pCmdUI != nullptr);
3341 ASSERT(pDoc != nullptr);
3342 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3345 if (pDoc->m_nBuffers == 2)
3346 pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
3348 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
3350 pCmdUI->Enable(false);
3354 if (m_nThisPane == 0)
3356 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3357 pCmdUI->Enable(bOn);
3359 else if (m_nThisPane == 1)
3361 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3362 pCmdUI->Enable(bOn);
3367 * @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
3368 * Go to moved line between the middle and right panes when in 3-way file comparison.
3370 void CMergeEditView::OnGotoMovedLineMR()
3372 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3375 CMergeDoc* pDoc = GetDocument();
3376 CPoint pos = GetCursorPos();
3378 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3379 ASSERT(pDoc != nullptr);
3380 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3383 if (m_nThisPane == 1)
3385 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3387 GotoLine(line, false, 2);
3389 else if (m_nThisPane == 2)
3391 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3393 GotoLine(line, false, 1);
3398 * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
3399 * @param [in] pCmdUI UI component to update.
3401 void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
3403 CMergeDoc* pDoc = GetDocument();
3404 CPoint pos = GetCursorPos();
3406 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3407 ASSERT(pCmdUI != nullptr);
3408 ASSERT(pDoc != nullptr);
3409 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3412 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
3414 pCmdUI->Enable(false);
3418 if (m_nThisPane == 1)
3420 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3421 pCmdUI->Enable(bOn);
3423 else if (m_nThisPane == 2)
3425 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3426 pCmdUI->Enable(bOn);
3430 void CMergeEditView::OnShellMenu()
3432 CFrameWnd *pFrame = GetTopLevelFrame();
3433 ASSERT(pFrame != nullptr);
3434 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3435 pFrame->m_bAutoMenuEnable = FALSE;
3437 String path = GetDocument()->m_filePaths[m_nThisPane];
3438 std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3439 pContextMenu->Initialize();
3440 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3441 pContextMenu->RequeryShellContextMenu();
3443 ::GetCursorPos(&point);
3444 HWND hWnd = GetSafeHwnd();
3445 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3447 pContextMenu->InvokeCommand(nCmd, hWnd);
3448 pContextMenu->ReleaseShellContextMenu();
3450 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3453 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3455 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3459 * @brief Reload options.
3461 void CMergeEditView::RefreshOptions()
3463 RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3464 SetRenderingMode(nRenderingMode);
3466 m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3468 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3469 SetInsertTabs(true);
3471 SetInsertTabs(false);
3473 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3475 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3476 SetTextType(CrystalLineParser::SRC_PLAIN);
3477 else if (!m_bChangedSchemeManually)
3479 // The syntax highlighting scheme should only be applied if it has not been manually changed.
3480 String fileName = GetDocument()->m_filePaths[m_nThisPane];
3482 paths::SplitFilename(fileName, nullptr, nullptr, &sExt);
3483 CrystalLineParser::TextDefinition* def = CrystalLineParser::GetTextType(sExt.c_str());
3485 SetTextType(def->type);
3487 SetTextType(CrystalLineParser::SRC_PLAIN);
3490 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3491 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3493 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3494 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3495 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3496 GetDocument()->IsMixedEOL(m_nThisPane));
3498 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3501 void CMergeEditView::OnScripts(UINT nID )
3503 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3504 String text = GetSelectedText();
3506 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3507 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3509 // now replace the text
3510 ReplaceSelection(text.c_str(), text.length(), 0);
3514 * @brief Called when an editor script item is updated
3516 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3518 // append the scripts submenu
3519 HMENU scriptsSubmenu = pCmdUI->m_pSubMenu ? pCmdUI->m_pSubMenu->m_hMenu : nullptr;
3520 if (scriptsSubmenu != nullptr)
3521 createScriptsSubmenu(scriptsSubmenu);
3523 pCmdUI->Enable(true);
3527 * @brief Called when an editor script item is updated
3529 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3531 pCmdUI->Enable(true);
3533 CMergeDoc *pd = GetDocument();
3534 ASSERT(pd != nullptr);
3535 PrediffingInfo prediffer;
3536 pd->GetPrediffer(&prediffer);
3538 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3540 pCmdUI->SetRadio(false);
3544 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3545 if (prediffer.m_PluginName.empty())
3546 m_CurrentPredifferID = ID_NO_PREDIFFER;
3548 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3552 * @brief Update "Prediffer" menuitem
3554 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3556 // recreate the sub menu (to fill the "selected prediffers")
3557 GetMainFrame()->UpdatePrediffersMenu();
3561 void CMergeEditView::OnNoPrediffer()
3563 OnPrediffer(ID_NO_PREDIFFER);
3566 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3568 void CMergeEditView::OnPrediffer(UINT nID )
3570 CMergeDoc *pd = GetDocument();
3571 ASSERT(pd != nullptr);
3573 SetPredifferByMenu(nID);
3574 pd->FlushAndRescan(true);
3578 * @brief Handler for all prediffer choices.
3579 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3580 * ID_NO_PREDIFFER, & specific prediffers.
3582 void CMergeEditView::SetPredifferByMenu(UINT nID )
3584 CMergeDoc *pd = GetDocument();
3585 ASSERT(pd != nullptr);
3587 if (nID == ID_NO_PREDIFFER)
3589 m_CurrentPredifferID = nID;
3590 // All flags are set correctly during the construction
3591 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3592 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3593 infoPrediffer->m_PluginName.clear();
3594 pd->SetPrediffer(infoPrediffer);
3595 pd->FlushAndRescan(true);
3599 // get the scriptlet files
3600 PluginArray * piScriptArray =
3601 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3602 PluginArray * piScriptArray2 =
3603 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3605 // build a PrediffingInfo structure fom the ID
3606 PrediffingInfo prediffer;
3607 prediffer.m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3609 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3610 if (pluginNumber < piScriptArray->size())
3612 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3613 prediffer.m_PluginName = plugin->m_name;
3617 pluginNumber -= piScriptArray->size();
3618 if (pluginNumber >= piScriptArray2->size())
3620 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3621 prediffer.m_PluginName = plugin->m_name;
3624 // update data for the radio button
3625 m_CurrentPredifferID = nID;
3627 // update the prediffer and rescan
3628 pd->SetPrediffer(&prediffer);
3632 * @brief Look through available prediffers, and return ID of requested one, if found
3634 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3637 int ID = ID_PREDIFFERS_FIRST;
3639 // Search file prediffers
3640 PluginArray * piScriptArray =
3641 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3642 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3644 const PluginInfoPtr & plugin = piScriptArray->at(i);
3645 if (plugin->m_name == prediffer)
3649 // Search buffer prediffers
3650 PluginArray * piScriptArray2 =
3651 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3652 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3654 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3655 if (plugin->m_name == prediffer)
3663 * @brief Look through available prediffers, and return ID of requested one, if found
3665 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3667 int id = FindPrediffer(prediffer);
3668 if (id<0) return false;
3669 SetPredifferByMenu(id);
3674 * @brief Goto given line.
3675 * @param [in] nLine Destination linenumber
3676 * @param [in] bRealLine if true linenumber is real line, otherwise
3677 * it is apparent line (including deleted lines)
3678 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3679 * @param [in] bMoveAnchor if true the anchor is moved to nLine
3681 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane, bool bMoveAnchor)
3683 CMergeDoc *pDoc = GetDocument();
3684 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3685 CMergeEditView *pCurrentView = nullptr;
3686 if (pSplitterWnd != nullptr)
3687 pCurrentView = static_cast<CMergeEditView*>
3688 (pSplitterWnd->GetActivePane());
3690 int nRealLine = nLine;
3691 int nApparentLine = nLine;
3693 // Compute apparent (shown linenumber) line
3696 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3697 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3699 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3703 ptPos.y = nApparentLine;
3705 // Scroll line to center of view
3706 int nScrollLine = GetSubLineIndex(nApparentLine);
3707 nScrollLine -= GetScreenLines() / 2;
3708 if (nScrollLine < 0)
3711 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3713 int nGroup = m_bDetailView ? 0 : m_nThisGroup;
3714 CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
3715 pView->ScrollToSubLine(nScrollLine);
3716 if (ptPos.y < pView->GetLineCount())
3718 pView->SetCursorPos(ptPos);
3720 pView->SetAnchor(ptPos);
3721 pView->SetSelection(pView->GetAnchor(), ptPos);
3725 CPoint ptPos1(0, pView->GetLineCount() - 1);
3726 pView->SetCursorPos(ptPos1);
3728 pView->SetAnchor(ptPos1);
3729 pView->SetSelection(pView->GetAnchor(), ptPos1);
3733 // If goto target is another view - activate another view.
3734 // This is done for user convenience as user probably wants to
3735 // work with goto target file.
3737 GetDocument()->GetView(0, pane)->SetActivePane();
3738 else if (GetGroupView(pane) != pCurrentView)
3739 GetGroupView(pane)->SetActivePane();
3743 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3746 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3748 if (pScrollBar == nullptr)
3750 // Scroll did not come frome a scroll bar
3751 // Find the appropriate scroll bar
3752 // and send the message to the splitter window instead
3753 // The event should eventually come back here but with a valid scrollbar
3754 // Along the way it will be propagated to other windows that need it
3755 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3756 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3757 pSplitterWnd->SendMessage(WM_HSCROLL,
3758 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3761 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3765 * @brief When view is scrolled using scrollbars update location pane.
3767 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3769 if (pScrollBar == nullptr)
3771 // Scroll did not come frome a scroll bar
3772 // Find the appropriate scroll bar
3773 // and send the message to the splitter window instead
3774 // The event should eventually come back here but with a valid scrollbar
3775 // Along the way it will be propagated to other windows that need it
3776 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3777 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3778 pSplitterWnd->SendMessage(WM_VSCROLL,
3779 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3782 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3784 if (nSBCode == SB_ENDSCROLL)
3787 // Note we cannot use nPos because of its 16-bit nature
3788 SCROLLINFO si = {0};
3789 si.cbSize = sizeof (si);
3790 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3791 VERIFY (GetScrollInfo (SB_VERT, &si));
3793 // Get the current position of scroll box.
3794 int nCurPos = si.nPos;
3796 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3800 * @brief Copy selected lines adding linenumbers.
3802 void CMergeEditView::OnEditCopyLineNumbers()
3808 CMergeDoc *pDoc = GetDocument();
3809 auto [ptStart, ptEnd] = GetSelection();
3811 // Get last selected line (having widest linenumber)
3812 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3813 size_t nNumWidth = strutils::to_str(line + 1).length();
3815 for (int i = ptStart.y; i <= ptEnd.y; i++)
3817 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3820 // We need to convert to real linenumbers
3821 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3823 // Insert spaces to align different width linenumbers (99, 100)
3824 strLine = GetLineText(i);
3825 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3828 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3829 strText += strNumLine;
3831 PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3834 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3836 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3840 * @brief Open active file with associated application.
3842 * First tries to open file using shell 'Edit' action, since that
3843 * action open scripts etc. to editor instead of running them. If
3844 * edit-action is not registered, 'Open' action is used.
3846 void CMergeEditView::OnOpenFile()
3848 CMergeDoc * pDoc = GetDocument();
3849 ASSERT(pDoc != nullptr);
3851 String sFileName = pDoc->m_filePaths[m_nThisPane];
3852 if (sFileName.empty())
3854 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3855 0, 0, SW_SHOWNORMAL);
3856 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3857 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3858 0, 0, SW_SHOWNORMAL);
3859 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3864 * @brief Open active file with app selection dialog
3866 void CMergeEditView::OnOpenFileWith()
3868 CMergeDoc * pDoc = GetDocument();
3869 ASSERT(pDoc != nullptr);
3871 String sFileName = pDoc->m_filePaths[m_nThisPane];
3872 if (sFileName.empty())
3876 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3878 sysdir.ReleaseBuffer();
3879 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3880 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3881 sysdir, SW_SHOWNORMAL);
3885 * @brief Open active file with external editor
3887 void CMergeEditView::OnOpenFileWithEditor()
3889 CMergeDoc * pDoc = GetDocument();
3890 ASSERT(pDoc != nullptr);
3892 String sFileName = pDoc->m_filePaths[m_nThisPane];
3893 if (sFileName.empty())
3896 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3897 theApp.OpenFileToExternalEditor(sFileName, nRealLine);
3901 * @brief Open parent folder of active file
3903 void CMergeEditView::OnOpenParentFolder()
3905 CMergeDoc * pDoc = GetDocument();
3906 ASSERT(pDoc != nullptr);
3908 String sFileName = pDoc->m_filePaths[m_nThisPane];
3909 if (sFileName.empty())
3912 theApp.OpenParentFolder(sFileName.c_str());
3916 * @brief Force repaint of the location pane.
3918 void CMergeEditView::RepaintLocationPane()
3920 // Must force recalculation due to caching of data in location pane.
3921 CLocationView *pLocationView = GetDocument()->GetLocationView();
3922 if (pLocationView != nullptr)
3923 pLocationView->ForceRecalculate();
3927 * @brief Enables/disables linediff (different color for diffs)
3929 void CMergeEditView::OnViewLineDiffs()
3931 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3932 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3934 // Call CMergeDoc RefreshOptions() to refresh *both* views
3935 CMergeDoc *pDoc = GetDocument();
3936 pDoc->RefreshOptions();
3937 pDoc->FlushAndRescan(true);
3940 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3942 pCmdUI->Enable(true);
3943 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3947 * @brief Enables/disables line number
3949 void CMergeEditView::OnViewLineNumbers()
3951 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3953 // Call CMergeDoc RefreshOptions() to refresh *both* views
3954 CMergeDoc *pDoc = GetDocument();
3955 pDoc->RefreshOptions();
3958 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3960 pCmdUI->Enable(true);
3961 pCmdUI->SetCheck(GetViewLineNumbers());
3965 * @brief Enables/disables word wrap
3967 void CMergeEditView::OnViewWordWrap()
3969 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3971 // Call CMergeDoc RefreshOptions() to refresh *both* views
3972 CMergeDoc *pDoc = GetDocument();
3973 pDoc->RefreshOptions();
3974 pDoc->UpdateAllViews(this);
3979 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3981 pCmdUI->Enable(true);
3982 pCmdUI->SetCheck(m_bWordWrap);
3985 void CMergeEditView::OnViewWhitespace()
3987 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3989 // Call CMergeDoc RefreshOptions() to refresh *both* views
3990 CMergeDoc *pDoc = GetDocument();
3991 pDoc->RefreshOptions();
3994 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3996 pCmdUI->SetCheck(GetViewTabs());
3999 void CMergeEditView::OnViewEOL()
4001 GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
4002 GetDocument()->RefreshOptions();
4005 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI)
4007 pCmdUI->SetCheck(GetViewEols());
4010 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
4012 if (!IsInitialized())
4015 CMergeDoc * pDoc = GetDocument();
4016 if (m_nThisPane < pDoc->m_nBuffers - 1)
4018 // To calculate subline index correctly
4019 // we have to invalidate line cache in all pane before calling the function related the subline.
4020 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4022 CMergeEditView *pView = GetGroupView(nPane);
4023 if (pView != nullptr)
4024 pView->InvalidateScreenRect(false);
4029 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4031 CMergeEditView *pView = GetGroupView(nPane);
4032 if (pView != nullptr)
4033 pView->Invalidate();
4036 // recalculate m_nTopSubLine
4037 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
4041 RecalcVertScrollBar (false, false);
4042 RecalcHorzScrollBar (false, false);
4046 * @brief allocates GDI resources for printing
4047 * @param pDC [in] points to the printer device context
4048 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4050 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
4052 GetParentFrame()->PostMessage(WM_TIMER);
4054 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4056 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
4057 pView->m_bPrintHeader = true;
4058 pView->m_bPrintFooter = true;
4059 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
4064 * @brief frees GDI resources for printing
4065 * @param pDC [in] points to the printer device context
4066 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4068 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
4070 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4071 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
4073 GetParentFrame()->PostMessage(WM_TIMER);
4077 * @brief Gets header text to print
4078 * @param [in] nPageNum the page number to print
4079 * @param [out] header text to print
4081 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
4083 text = GetDocument()->GetTitle();
4087 * @brief Prints header
4088 * @param [in] nPageNum the page number to print
4090 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
4092 if (m_nThisPane > 0)
4094 int oldRight = m_rcPrintArea.right;
4095 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4096 CGhostTextView::PrintHeader(pdc, nPageNum);
4097 m_rcPrintArea.right = oldRight;
4101 * @brief Prints footer
4102 * @param [in] nPageNum the page number to print
4104 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
4106 if (m_nThisPane > 0)
4108 int oldRight = m_rcPrintArea.right;
4109 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
4110 CGhostTextView::PrintFooter(pdc, nPageNum);
4111 m_rcPrintArea.right = oldRight;
4114 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
4116 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4117 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
4121 * @brief Prints or previews both panes.
4122 * @param pDC [in] points to the printer device context
4123 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
4125 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
4127 CRect rDraw = pInfo->m_rectDraw;
4128 CSize sz = rDraw.Size();
4129 CMergeDoc *pDoc = GetDocument();
4131 SIZE szLeftTop, szRightBottom;
4132 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
4133 pDC->HIMETRICtoLP(&szLeftTop);
4134 pDC->HIMETRICtoLP(&szRightBottom);
4136 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
4139 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
4141 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
4142 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
4143 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
4144 pPane->CGhostTextView::OnPrint(pDC, pInfo);
4148 bool CMergeEditView::IsInitialized() const
4150 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
4151 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
4152 return pBuffer->IsInitialized();
4156 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
4158 int CMergeEditView::GetEmptySubLines( int nLineIndex )
4160 int nBreaks[3] = {0};
4161 int nMaxBreaks = -1;
4162 CMergeDoc * pDoc = GetDocument();
4163 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4165 CMergeEditView *pView = GetGroupView(nPane);
4166 if (pView != nullptr)
4168 if (nLineIndex >= pView->GetLineCount())
4170 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
4172 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
4175 if (nBreaks[m_nThisPane] < nMaxBreaks)
4176 return nMaxBreaks - nBreaks[m_nThisPane];
4182 * @brief Invalidate sub line index cache from the specified index to the end of file.
4183 * @param [in] nLineIndex Index of the first line to invalidate
4185 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
4187 CMergeDoc * pDoc = GetDocument();
4188 ASSERT(pDoc != nullptr);
4190 // We have to invalidate sub line index cache on both panes.
4191 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4193 CMergeEditView *pView = GetGroupView(nPane);
4194 if (pView != nullptr)
4195 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
4199 void CMergeEditView::SetWordWrapping( bool bWordWrap )
4201 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4202 GetGroupView(pane)->m_bWordWrap = bWordWrap;
4203 CCrystalTextView::SetWordWrapping(bWordWrap);
4207 * @brief Swap the positions of the two panes
4209 void CMergeEditView::OnViewSwapPanes12()
4211 GetDocument()->SwapFiles(0, 1);
4215 * @brief Swap the positions of the two panes
4217 void CMergeEditView::OnViewSwapPanes23()
4219 GetDocument()->SwapFiles(1, 2);
4223 * @brief Swap the positions of the two panes
4225 void CMergeEditView::OnViewSwapPanes13()
4227 GetDocument()->SwapFiles(0, 2);
4231 * @brief Determine if difference is visible on screen.
4232 * @param [in] nDiff Number of diff to check.
4233 * @return true if difference is visible.
4235 bool CMergeEditView::IsDiffVisible(int nDiff)
4237 const CMergeDoc *pd = GetDocument();
4240 pd->m_diffList.GetDiff(nDiff, diff);
4242 return IsDiffVisible(diff);
4246 * @brief Determine if difference is visible on screen.
4247 * @param [in] diff diff to check.
4248 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
4249 * @return true if difference is visible, false otherwise.
4251 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
4253 const int nDiffStart = GetSubLineIndex(diff.dbegin);
4254 const int nDiffEnd = GetSubLineIndex(diff.dend);
4255 // Diff's height is last line - first line + last line's line count
4256 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
4258 // If diff first line outside current view - context OR
4259 // if diff last line outside current view - context OR
4260 // if diff is bigger than screen
4261 if ((nDiffStart < m_nTopSubLine) ||
4262 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
4263 (nDiffHeight >= GetScreenLines()))
4273 /** @brief Open help from mainframe when user presses F1*/
4274 void CMergeEditView::OnHelp()
4276 theApp.ShowHelp(MergeViewHelpLocation);
4280 * @brief Called after document is loaded.
4281 * This function is called from CMergeDoc::OpenDocs() after documents are
4282 * loaded. So this is good place to set View's options etc.
4284 void CMergeEditView::DocumentsLoaded()
4286 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
4289 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
4294 SetTopMargin(false);
4297 // Enable/disable automatic rescan (rescanning after edit)
4298 EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
4300 // SetTextType will revert to language dependent defaults for tab
4301 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
4302 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
4303 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
4304 GetDocument()->IsMixedEOL(m_nThisPane);
4305 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
4306 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
4307 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
4308 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4310 // Enable Backspace at beginning of line
4311 SetDisableBSAtSOL(false);
4313 // Set tab type (tabs/spaces)
4314 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
4315 SetInsertTabs(bInsertTabs);
4317 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
4318 // disabled) after docs are open in screen. So lets make sure they are
4319 // really updated, even though this is unnecessary in most cases.
4320 RecalcHorzScrollBar();
4321 RecalcVertScrollBar();
4325 * @brief Update LocationView position.
4326 * This function updates LocationView position to given lines.
4327 * Usually we want to lines in file compare view and area in
4328 * LocationView to match. Be extra carefull to not call non-existing
4330 * @param [in] nTopLine Top line of current view.
4331 * @param [in] nBottomLine Bottom line of current view.
4333 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
4334 int nBottomLine /*= -1*/)
4336 CMergeDoc *pDoc = GetDocument();
4337 if (pDoc == nullptr)
4340 CLocationView *pLocationView = pDoc->GetLocationView();
4342 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
4344 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
4349 * @brief Enable/Disable view's selection margins.
4350 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
4351 * useful. But it appears many users don't use/need those features and for them
4352 * selection margins are just wasted screen estate.
4354 void CMergeEditView::OnViewMargin()
4356 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
4357 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
4359 SetSelectionMargin(!bViewMargin);
4360 CMergeDoc *pDoc = GetDocument();
4361 pDoc->RefreshOptions();
4362 pDoc->UpdateAllViews(this);
4366 * @brief Update GUI for Enable/Disable view's selection margin.
4367 * @param [in] pCmdUI Pointer to UI item to update.
4369 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
4371 pCmdUI->Enable(true);
4372 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4376 * @brief Create the "Change Scheme" sub menu.
4377 * @param [in] pCmdUI Pointer to UI item to update.
4379 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4381 // Delete the place holder menu.
4382 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4384 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4386 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4387 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4388 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
4390 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
4392 name = theApp.LoadString(i);
4393 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
4396 pCmdUI->Enable(true);
4400 * @brief Change the editor's syntax highlighting scheme.
4401 * @param [in] nID Selected color scheme sub menu id.
4403 void CMergeEditView::OnChangeScheme(UINT nID)
4405 CMergeDoc *pDoc = GetDocument();
4406 ASSERT(pDoc != nullptr);
4408 for (int nGroup = 0; nGroup < pDoc->m_nGroups; nGroup++)
4409 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4411 CMergeEditView *pView = pDoc->GetView(nGroup, nPane);
4412 ASSERT(pView != nullptr);
4414 if (pView != nullptr)
4416 pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
4417 pView->SetDisableBSAtSOL(false);
4418 pView->m_bChangedSchemeManually = true;
4426 * @brief Enable all color schemes sub menu items.
4427 * @param [in] pCmdUI Pointer to UI item to update.
4429 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4431 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4432 pCmdUI->SetRadio(bIsCurrentScheme);
4433 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4437 * @brief Called when mouse's wheel is scrolled.
4439 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4441 if ( nFlags == MK_CONTROL )
4443 short amount = zDelta < 0 ? -1: 1;
4446 // no default CCrystalTextView
4447 return CView::OnMouseWheel(nFlags, zDelta, pt);
4450 if (nFlags == MK_SHIFT)
4452 SCROLLINFO si = { sizeof SCROLLINFO };
4453 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4455 VERIFY(GetScrollInfo(SB_HORZ, &si));
4458 si.nPos -= zDelta / 40;
4459 if (si.nPos > si.nMax) si.nPos = si.nMax;
4460 if (si.nPos < si.nMin) si.nPos = si.nMin;
4462 SetScrollInfo(SB_HORZ, &si);
4465 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4467 // no default CCrystalTextView
4468 return CView::OnMouseWheel(nFlags, zDelta, pt);
4471 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4475 * @brief Called when mouse's horizontal wheel is scrolled.
4477 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4479 SCROLLINFO si = { sizeof SCROLLINFO };
4480 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4482 VERIFY(GetScrollInfo(SB_HORZ, &si));
4485 si.nPos += zDelta / 40;
4486 if (si.nPos > si.nMax) si.nPos = si.nMax;
4487 if (si.nPos < si.nMin) si.nPos = si.nMin;
4489 SetScrollInfo(SB_HORZ, &si);
4492 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4494 // no default CCrystalTextView
4495 CView::OnMouseHWheel(nFlags, zDelta, pt);
4499 * @brief Change font size (zoom) in views.
4500 * @param [in] amount Amount of change/zoom, negative number makes
4501 * font smaller, positive number bigger and 0 reset the font size.
4503 void CMergeEditView::ZoomText(short amount)
4508 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4509 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4513 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4516 nPointSize += amount;
4520 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4522 CMergeDoc *pDoc = GetDocument();
4523 ASSERT(pDoc != nullptr);
4525 if (pDoc != nullptr)
4527 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4529 CMergeEditView *pView = GetGroupView(nPane);
4530 ASSERT(pView != nullptr);
4532 if (pView != nullptr)
4540 bool CMergeEditView::QueryEditable()
4542 return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4546 * @brief Adjust the point to remain in the displayed diff
4548 * @return Tells if the point has been changed
4550 bool CMergeEditView::EnsureInDiff(CPoint& pt)
4552 int nLineCount = GetLineCount();
4553 if (m_lineBegin >= nLineCount)
4554 m_lineBegin = nLineCount - 1;
4555 if (m_lineEnd >= nLineCount)
4556 m_lineEnd = nLineCount - 1;
4558 int diffLength = m_lineEnd - m_lineBegin + 1;
4559 // first get the degenerate case out of the way
4561 if (diffLength == 0)
4563 if (pt.y == m_lineBegin && pt.x == 0)
4571 if (pt.y < m_lineBegin)
4577 // diff is defined and not below diff
4578 if (m_lineEnd > -1 && pt.y > m_lineEnd)
4581 pt.x = GetLineLength(pt.y);
4587 void CMergeEditView::EnsureVisible(CPoint pt)
4592 // ensure we remain in diff
4593 if (EnsureInDiff(ptNew))
4594 SetCursorPos(ptNew);
4596 CCrystalTextView::EnsureVisible(ptNew);
4599 void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
4601 CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4604 void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
4606 CPoint ptStartNew = ptStart;
4607 CPoint ptEndNew = ptEnd;
4610 // ensure we remain in diff
4611 EnsureInDiff(ptStartNew);
4612 EnsureInDiff(ptEndNew);
4614 CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4617 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4621 int nLineCount = GetLineCount();
4622 if (m_lineBegin >= nLineCount)
4623 m_lineBegin = nLineCount - 1;
4624 if (m_lineEnd >= nLineCount)
4625 m_lineEnd = nLineCount - 1;
4627 // ensure we remain in diff
4628 int sublineBegin = GetSubLineIndex(m_lineBegin);
4629 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4630 int diffLength = sublineEnd - sublineBegin + 1;
4631 int displayLength = GetScreenLines();
4632 if (diffLength <= displayLength)
4633 nNewTopLine = sublineBegin;
4636 if (nNewTopLine < sublineBegin)
4637 nNewTopLine = sublineBegin;
4638 if (nNewTopLine + displayLength - 1 > sublineEnd)
4639 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4642 CPoint pt = GetCursorPos();
4643 if (EnsureInDiff(pt))
4646 auto [ptSelStart, ptSelEnd] = GetSelection();
4647 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4648 SetSelection(ptSelStart, ptSelEnd);
4650 CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4653 void CMergeEditView::SetActivePane()
4655 auto* pwndSplitterChild = GetParentSplitter(this, false);
4656 if (!pwndSplitterChild)
4658 if (pwndSplitterChild->GetColumnCount() > 1)
4659 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4661 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4665 * @brief Called when user selects View/Zoom In from menu.
4667 void CMergeEditView::OnViewZoomIn()
4673 * @brief Called when user selects View/Zoom Out from menu.
4675 void CMergeEditView::OnViewZoomOut()
4681 * @brief Called when user selects View/Zoom Normal from menu.
4683 void CMergeEditView::OnViewZoomNormal()
4688 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4690 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4692 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4696 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4699 void CMergeEditView::OnWindowSplit()
4702 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4703 CMergeDoc *pDoc = GetDocument();
4704 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4705 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4706 int nBuffer = m_nThisPane;
4707 if (pDoc->m_nGroups <= 2)
4709 wndSplitter.SplitRow(1);
4710 wndSplitter.EqualizeRows();
4714 wndSplitter.SetActivePane(0, 0);
4715 wndSplitter.DeleteRow(1);
4716 pDoc->GetView(0, nBuffer)->SetActivePane();
4720 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4722 pCmdUI->Enable(!m_bDetailView);
4723 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4726 void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
4729 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4730 const int pane = pNMItemActivate->iItem / 4;
4731 CMergeDoc* pDoc = GetDocument();
4732 if (pane >= pDoc->m_nBuffers || !GetParentFrame()->IsChild(CWnd::FromHandle(pNMItemActivate->hdr.hwndFrom)))
4735 switch (pNMItemActivate->iItem % 4)
4738 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4741 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
4746 ::GetCursorPos(&point);
4749 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4750 theApp.TranslateMenu(menu.m_hMenu);
4751 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4755 pDoc->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());