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 "WMGotoDlg.h"
24 #include "OptionsDef.h"
25 #include "SyntaxColors.h"
26 #include "MergeEditFrm.h"
27 #include "MergeLineFlags.h"
29 #include "DropHandler.h"
31 #include "ShellContextMenu.h"
34 #include "SelectPluginDlg.h"
35 #include "Constants.h"
41 #ifndef WM_MOUSEHWHEEL
42 # define WM_MOUSEHWHEEL 0x20e
46 using CrystalLineParser::TEXTBLOCK;
48 /** @brief Timer ID for delayed rescan. */
49 const UINT IDT_RESCAN = 2;
50 /** @brief Timer timeout for delayed rescan. */
51 const UINT RESCAN_TIMEOUT = 1000;
53 /////////////////////////////////////////////////////////////////////////////
56 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
58 CMergeEditView::CMergeEditView()
59 : m_bCurrentLineIsDiff(false)
62 , m_bDetailView(false)
63 , m_piMergeEditStatus(nullptr)
64 , fTimerWaitingForIdle(0)
68 SetParser(&m_xParser);
70 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
73 CMergeEditView::~CMergeEditView()
78 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
79 //{{AFX_MSG_MAP(CMergeEditView)
92 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
93 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
95 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
96 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
97 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
98 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
99 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
100 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
101 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
102 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
103 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
104 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
105 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
106 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
107 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
108 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
110 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
111 ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
112 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
113 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
114 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
115 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
116 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
117 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
118 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
119 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
120 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
121 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
122 ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
123 ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
124 ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
125 ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
126 ON_COMMAND(ID_VIEW_TOPMARGIN, OnViewTopMargin)
127 ON_UPDATE_COMMAND_UI(ID_VIEW_TOPMARGIN, OnUpdateViewTopMargin)
128 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
129 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
130 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
131 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
132 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
133 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
135 ON_COMMAND(ID_CURDIFF, OnCurdiff)
136 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
137 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
138 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
139 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
140 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
141 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
142 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
143 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
144 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
145 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
146 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
147 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
148 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
149 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
150 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
151 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
152 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
153 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
154 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
155 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
156 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
157 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
158 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
159 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
160 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
161 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
162 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
163 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
164 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
165 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
166 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
167 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
168 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
169 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
170 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
171 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
172 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
173 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
174 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
175 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
176 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
177 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
178 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
179 ON_COMMAND(ID_L2R, OnL2r)
180 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
181 ON_COMMAND(ID_LINES_L2R, OnLinesL2r)
182 ON_UPDATE_COMMAND_UI(ID_LINES_L2R, OnUpdateLinesL2r)
183 ON_COMMAND(ID_R2L, OnR2l)
184 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
185 ON_COMMAND(ID_LINES_R2L, OnLinesR2l)
186 ON_UPDATE_COMMAND_UI(ID_LINES_R2L, OnUpdateLinesR2l)
187 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
188 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
189 ON_COMMAND(ID_COPY_LINES_FROM_LEFT, OnCopyLinesFromLeft)
190 ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_LEFT, OnUpdateCopyLinesFromLeft)
191 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
192 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
193 ON_COMMAND(ID_COPY_LINES_FROM_RIGHT, OnCopyLinesFromRight)
194 ON_UPDATE_COMMAND_UI(ID_COPY_LINES_FROM_RIGHT, OnUpdateCopyLinesFromRight)
195 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
196 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
197 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
198 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
199 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
200 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
201 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
202 ON_COMMAND_RANGE(ID_COPY_TO_MIDDLE_L, ID_COPY_FROM_LEFT_R, OnCopyX2Y)
203 ON_UPDATE_COMMAND_UI_RANGE(ID_COPY_TO_MIDDLE_L, ID_COPY_FROM_LEFT_R, OnUpdateX2Y)
204 ON_COMMAND_RANGE(ID_COPY_LINES_TO_MIDDLE_L, ID_COPY_LINES_FROM_LEFT_R, OnCopyLinesX2Y)
205 ON_UPDATE_COMMAND_UI_RANGE(ID_COPY_LINES_TO_MIDDLE_L, ID_COPY_LINES_FROM_LEFT_R, OnUpdateX2Y)
207 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
208 ON_COMMAND(ID_TRANSFORM_WITH_SCRIPT, OnTransformWithScript)
210 ON_COMMAND_RANGE(ID_NEXT_PANE, ID_PREV_PANE, OnChangePane)
211 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
212 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
214 ON_COMMAND(ID_HELP, OnHelp)
216 ON_COMMAND(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnAddToSubstitutionFilters)
217 ON_UPDATE_COMMAND_UI(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnUpdateAddToSubstitutionFilters)
218 ON_COMMAND(ID_ADD_TO_LINE_FILTERS, OnAddToLineFilters)
219 ON_UPDATE_COMMAND_UI(ID_ADD_TO_LINE_FILTERS, OnUpdateAddToLineFilters)
220 ON_COMMAND(ID_GOTO_MOVED_LINE_LM, OnGotoMovedLineLM)
221 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_LM, OnUpdateGotoMovedLineLM)
222 ON_COMMAND(ID_GOTO_MOVED_LINE_MR, OnGotoMovedLineMR)
223 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_MR, OnUpdateGotoMovedLineMR)
224 ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
225 ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
226 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
227 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
228 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
229 ON_COMMAND(ID_FILE_OPEN_PARENT_FOLDER, OnOpenParentFolder)
230 // Context menu (Header)
231 ON_COMMAND(ID_USE_FIRST_LINE_AS_HEADERS, OnUseFirstLineAsHeaders)
232 ON_UPDATE_COMMAND_UI(ID_USE_FIRST_LINE_AS_HEADERS, OnUpdateUseFirstLineAsHeaders)
233 ON_COMMAND(ID_AUTO_FIT_ALL_COLUMNS, OnAutoFitAllColumns)
235 ON_NOTIFY(NM_CLICK, AFX_IDW_STATUS_BAR, OnStatusBarClick)
236 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
237 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
238 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
243 /////////////////////////////////////////////////////////////////////////////
244 // CMergeEditView diagnostics
247 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
249 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
250 return (CMergeDoc*)m_pDocument;
255 /////////////////////////////////////////////////////////////////////////////
256 // CMergeEditView message handlers
259 * @brief Return text buffer for file in view
261 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
263 return GetDocument()->m_ptBuf[m_nThisPane].get();
267 * @brief Update any resources necessary after a GUI language change
269 void CMergeEditView::UpdateResources()
273 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
275 return GetDocument()->GetView(m_nThisGroup, nBuffer);
278 void CMergeEditView::PrimeListWithFile()
280 // Set the tab size now, just in case the options change...
281 // We don't update it at the end of OnOptions,
282 // we can update it safely now
283 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
286 * @brief Return text from line given
288 CString CMergeEditView::GetLineText(int idx)
290 return GetLineChars(idx);
294 * @brief Return text from selection
296 CString CMergeEditView::GetSelectedText()
299 auto [ptStart, ptEnd] = GetSelection();
300 if (ptStart != ptEnd)
301 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
306 * @brief Return number of selected characters
308 std::pair<int, int> CMergeEditView::GetSelectedLineAndCharacterCount()
310 auto [ptStart, ptEnd] = GetSelection();
311 int nCharsOrColumns =0;
312 int nSelectedLines = 0;
313 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
315 if ((GetLineFlags(nLine) & (LF_GHOST | LF_INVISIBLE)) == 0)
317 int nLineLength = GetLineLength(nLine) + (m_pTextBuffer->GetLineEol(nLine)[0] ? 1 : 0);
318 nCharsOrColumns += (nLine == ptEnd.y) ? ptEnd.x : nLineLength;
319 if (nLine == ptStart.y)
320 nCharsOrColumns -= ptStart.x;
321 if (nLine < ptEnd.y || (ptStart != ptEnd && ptEnd.x > 0))
325 if (m_bRectangularSelection)
327 int nStartLeft, nStartRight, nEndLeft, nEndRight;
328 GetColumnSelection(ptStart.y, nStartLeft, nStartRight);
329 GetColumnSelection(ptEnd.y, nEndLeft, nEndRight);
330 nCharsOrColumns = (std::max)(nStartRight, nEndRight) - (std::min)(nStartLeft, nEndLeft);
332 return { nSelectedLines, nCharsOrColumns };
336 * @brief Get diffs inside selection.
337 * @param [out] firstDiff First diff inside selection
338 * @param [out] lastDiff Last diff inside selection
339 * @note -1 is returned in parameters if diffs cannot be determined
340 * @todo This shouldn't be called when there is no diffs, so replace
341 * first 'if' with ASSERT()?
343 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
348 CMergeDoc *pd = GetDocument();
349 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
353 int firstLine, lastLine;
354 GetFullySelectedLines(firstLine, lastLine);
355 if (lastLine < firstLine)
358 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
359 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
360 if (firstDiff != -1 && lastDiff != -1)
364 // Check that first selected line is first diff's first line or above it
365 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
366 if ((int)di.dbegin < firstLine)
368 if (firstDiff < lastDiff)
372 // Check that last selected line is last diff's last line or below it
373 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
374 if ((int)di.dend > lastLine)
376 if (firstDiff < lastDiff)
380 // Special case: one-line diff is not selected if cursor is in it
381 if (firstLine == lastLine)
390 * @brief Get diffs inside selection.
391 * @param [out] firstDiff First diff inside selection
392 * @param [out] lastDiff Last diff inside selection
393 * @param [out] firstWordDiff First word level diff inside selection
394 * @param [out] lastWordDiff Last word level diff inside selection
395 * @note -1 is returned in parameters if diffs cannot be determined
396 * @todo This shouldn't be called when there is no diffs, so replace
397 * first 'if' with ASSERT()?
399 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CEPoint *pptStart, const CEPoint *pptEnd)
406 CMergeDoc *pd = GetDocument();
407 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
411 int firstLine, lastLine;
412 auto [ptStart, ptEnd] = GetSelection();
413 if (pptStart != nullptr)
415 if (pptEnd != nullptr)
417 firstLine = ptStart.y;
420 firstDiff = pd->m_diffList.LineToDiff(firstLine);
421 bool firstLineIsNotInDiff = firstDiff == -1;
424 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
429 lastDiff = pd->m_diffList.LineToDiff(lastLine);
430 bool lastLineIsNotInDiff = lastDiff == -1;
432 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
433 if (lastDiff < firstDiff)
440 if (firstDiff != -1 && lastDiff != -1)
444 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
446 firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
448 else if (ptStart != ptEnd)
450 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
451 constexpr int LineLimit = 256;
452 if ((lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0))) ||
453 (di.dend - di.dbegin > LineLimit))
459 if (firstWordDiff == -1)
461 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
462 for (size_t i = 0; i < worddiffs.size(); ++i)
464 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
465 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
466 (firstLine == worddiffs[i].endline[m_nThisPane] &&
467 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) > ptStart.x))
469 firstWordDiff = static_cast<int>(i);
474 if (firstLine >= di.dbegin && firstWordDiff == -1)
481 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
482 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
483 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
485 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
486 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
488 lastWordDiff = static_cast<int>(i);
493 if (lastLine <= di.dend && lastWordDiff == -1)
496 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
503 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
520 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
521 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
522 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
525 void CMergeEditView::GetSelectedDiffs(int & firstDiff, int & lastDiff)
530 CMergeDoc *pd = GetDocument();
531 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
535 int firstLine, lastLine;
536 auto [ptStart, ptEnd] = GetSelection();
537 firstLine = ptStart.y;
540 firstDiff = pd->m_diffList.LineToDiff(firstLine);
543 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
547 lastDiff = pd->m_diffList.LineToDiff(lastLine);
549 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
550 if (lastDiff < firstDiff)
556 ASSERT(firstDiff == -1 ? (lastDiff == -1) : true);
557 ASSERT(lastDiff == -1 ? (firstDiff == -1) : true);
560 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
562 CMergeDoc *pDoc = GetDocument();
563 std::map<int, std::vector<int>> ret;
564 std::map<int, std::vector<int> *> list;
565 auto [ptStart, ptEnd] = GetSelection();
566 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
568 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
570 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
572 GetColumnSelection(nLine, nLeft, nRight);
573 CEPoint ptStart2, ptEnd2;
576 ptStart2.y = ptEnd2.y = nLine;
577 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
578 if (firstDiff != -1 && lastDiff != -1)
580 std::vector<int> *pWordDiffs;
581 if (list.find(firstDiff) == list.end())
582 list.emplace(firstDiff, new std::vector<int>());
583 pWordDiffs = list[firstDiff];
584 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
586 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
587 pWordDiffs->push_back(i);
592 for (auto& it : list)
593 ret.emplace(it.first, *it.second);
597 void CMergeEditView::OnInitialUpdate()
600 CCrystalEditViewEx::OnInitialUpdate();
602 LOGFONT lf = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff;
603 lf.lfHeight = static_cast<LONG>(lf.lfHeight * GetOptionsMgr()->GetInt(OPT_VIEW_ZOOM) / 1000.0);
605 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
611 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
613 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
615 CMergeDoc* pDoc = GetDocument();
616 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
619 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
623 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
624 return std::vector<CrystalLineParser::TEXTBLOCK>();
626 return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
629 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
631 static const std::vector<TEXTBLOCK> emptyBlocks;
634 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
638 lineflags_t dwLineFlags = GetLineFlags(nLineIndex);
639 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
642 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
645 CMergeDoc *pDoc = GetDocument();
646 if (pDoc->IsEditedAfterRescan(m_nThisPane))
649 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
654 pDoc->m_diffList.GetDiff(nDiff, cd);
655 int unemptyLineCount = 0;
656 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
658 if (cd.begin[nPane] != cd.end[nPane] + 1)
661 if (unemptyLineCount < 2)
664 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
665 size_t nWordDiffs = worddiffs.size();
667 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
669 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
670 blocks[0].m_nCharPos = 0;
671 blocks[0].m_nColorIndex = COLORINDEX_NONE;
672 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
674 for (i = 0, j = 1; i < nWordDiffs; i++)
676 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
678 if (pDoc->m_nBuffers > 2)
680 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
682 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
685 int begin[3]{}, end[3]{};
686 bool deleted = false;
687 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
689 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
690 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetViewableLineLength(nLineIndex) : worddiffs[i].end[pane];
691 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
692 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
695 blocks[j].m_nCharPos = begin[m_nThisPane];
696 if (lineInCurrentDiff)
698 blocks[j].m_nColorIndex = COLORINDEX_APPLYFORCE |
699 ((m_cachedColors.clrSelWordDiffText != CLR_NONE) ? COLORINDEX_HIGHLIGHTTEXT1 : COLORINDEX_NONE);
700 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
701 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
705 blocks[j].m_nColorIndex = COLORINDEX_APPLYFORCE |
706 ((m_cachedColors.clrWordDiffText != CLR_NONE) ? COLORINDEX_HIGHLIGHTTEXT2 : COLORINDEX_NONE);
707 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
708 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
711 blocks[j].m_nCharPos = end[m_nThisPane];
712 blocks[j].m_nColorIndex = COLORINDEX_NONE;
713 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
722 CEColor CMergeEditView::GetColor(int nColorIndex) const
724 switch (nColorIndex & ~COLORINDEX_MASK)
726 case COLORINDEX_HIGHLIGHTBKGND1:
727 return m_cachedColors.clrSelWordDiff;
728 case COLORINDEX_HIGHLIGHTTEXT1:
729 return m_cachedColors.clrSelWordDiffText;
730 case COLORINDEX_HIGHLIGHTBKGND2:
731 return m_cachedColors.clrWordDiff;
732 case COLORINDEX_HIGHLIGHTTEXT2:
733 return m_cachedColors.clrWordDiffText;
734 case COLORINDEX_HIGHLIGHTBKGND3:
735 return m_cachedColors.clrWordDiffDeleted;
736 case COLORINDEX_HIGHLIGHTBKGND4:
737 return m_cachedColors.clrSelWordDiffDeleted;
740 return CCrystalTextView::GetColor(nColorIndex);
745 * @brief Determine text and background color for line
746 * @param [in] nLineIndex Index of line in view (NOT line in file)
747 * @param [out] crBkgnd Backround color for line
748 * @param [out] crText Text color for line
750 void CMergeEditView::GetLineColors(int nLineIndex, CEColor & crBkgnd,
751 CEColor & crText, bool & bDrawWhitespace)
753 DWORD ignoreFlags = 0;
754 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
758 * @brief Determine text and background color for line
759 * @param [in] nLineIndex Index of line in view (NOT line in file)
760 * @param [in] ignoreFlags Flags that caller wishes ignored
761 * @param [out] crBkgnd Backround color for line
762 * @param [out] crText Text color for line
764 * This version allows caller to suppress particular flags
766 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, CEColor & crBkgnd,
767 CEColor & crText, bool & bDrawWhitespace)
769 if (GetLineCount() <= nLineIndex)
772 lineflags_t dwLineFlags = GetLineFlags(nLineIndex);
774 if (dwLineFlags & ignoreFlags)
775 dwLineFlags &= (~ignoreFlags);
779 // Line with WinMerge flag,
780 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
781 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
783 crText = m_cachedColors.clrDiffText;
784 bDrawWhitespace = true;
786 if (dwLineFlags & LF_GHOST)
788 crBkgnd = m_cachedColors.clrDiffDeleted;
793 // If no syntax hilighting
794 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
796 crBkgnd = GetColor (COLORINDEX_BKGND);
797 crText = GetColor (COLORINDEX_NORMALTEXT);
798 bDrawWhitespace = false;
801 // Line not inside diff, get colors from CrystalEditor
802 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
803 crText, bDrawWhitespace);
805 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
807 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
808 crText = GetColor (COLORINDEX_WHITESPACE);
809 bDrawWhitespace = false;
815 if (dwLineFlags & LF_WINMERGE_FLAGS)
817 crText = m_cachedColors.clrDiffText;
818 bDrawWhitespace = true;
819 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
821 if (dwLineFlags & LF_SNP)
823 if (lineInCurrentDiff)
825 if (dwLineFlags & LF_GHOST)
826 crBkgnd = m_cachedColors.clrSelSNPDeleted;
828 crBkgnd = m_cachedColors.clrSelSNP;
829 crText = m_cachedColors.clrSelSNPText;
833 if (dwLineFlags & LF_GHOST)
834 crBkgnd = m_cachedColors.clrSNPDeleted;
836 crBkgnd = m_cachedColors.clrSNP;
837 crText = m_cachedColors.clrSNPText;
841 else if (dwLineFlags & LF_DIFF)
843 if (lineInCurrentDiff)
845 if (dwLineFlags & LF_MOVED)
847 crBkgnd = m_cachedColors.clrSelMoved;
848 crText = m_cachedColors.clrSelMovedText;
852 crBkgnd = m_cachedColors.clrSelDiff;
853 crText = m_cachedColors.clrSelDiffText;
859 if (dwLineFlags & LF_MOVED)
861 crBkgnd = m_cachedColors.clrMoved;
862 crText = m_cachedColors.clrMovedText;
866 crBkgnd = m_cachedColors.clrDiff;
867 crText = m_cachedColors.clrDiffText;
872 else if (dwLineFlags & LF_TRIVIAL)
874 // trivial diff can not be selected
875 if (dwLineFlags & LF_GHOST)
876 // ghost lines in trivial diff has their own color
877 crBkgnd = m_cachedColors.clrTrivialDeleted;
879 crBkgnd = m_cachedColors.clrTrivial;
880 crText = m_cachedColors.clrTrivialText;
883 else if (dwLineFlags & LF_GHOST)
885 if (lineInCurrentDiff)
887 if (dwLineFlags & LF_MOVED)
888 crBkgnd = m_cachedColors.clrSelMovedDeleted;
890 crBkgnd = m_cachedColors.clrSelDiffDeleted;
894 if (dwLineFlags & LF_MOVED)
895 crBkgnd = m_cachedColors.clrMovedDeleted;
897 crBkgnd = m_cachedColors.clrDiffDeleted;
904 // Line not inside diff,
905 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
907 // If no syntax hilighting, get windows default colors
908 crBkgnd = GetColor (COLORINDEX_BKGND);
909 crText = GetColor (COLORINDEX_NORMALTEXT);
910 bDrawWhitespace = false;
913 // Syntax highlighting, get colors from CrystalEditor
914 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
915 crText, bDrawWhitespace);
920 * @brief Sync other pane position
922 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
924 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
925 if (pSplitterWnd != nullptr)
927 // See CSplitterWnd::IdFromRowCol() implementation for details
928 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
929 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
930 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
931 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
933 // limit the TopLine : must be smaller than GetLineCount for all the panels
934 int newTopSubLine = m_nTopSubLine;
935 int nRows = pSplitterWnd->GetRowCount ();
936 int nCols = pSplitterWnd->GetColumnCount ();
938 // for (nRow = 0; nRow < nRows; nRow++)
940 // for (int nCol = 0; nCol < nCols; nCol++)
942 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
943 // if (pSiblingView != nullptr)
944 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
945 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
948 if (m_nTopSubLine != newTopSubLine)
949 ScrollToSubLine(newTopSubLine);
951 for (nRow = 0; nRow < nRows; nRow++)
953 for (int nCol = 0; nCol < nCols; nCol++)
955 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
957 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
958 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
959 pSiblingView->OnUpdateSibling (this, bHorz);
967 * @brief Update other panes
969 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
971 if (pUpdateSource != this)
973 ASSERT (pUpdateSource != nullptr);
974 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
975 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
976 if (!bHorz) // changed this so bHorz works right
978 ASSERT (pSrcView->m_nTopSubLine >= 0);
980 // This ASSERT is wrong: panes have different files and
981 // different linecounts
982 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
983 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
985 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
987 RecalcVertScrollBar(true);
988 InvalidateHorzScrollBar();
993 ASSERT (pSrcView->m_nOffsetChar >= 0);
995 // This ASSERT is wrong: panes have different files and
996 // different linelengths
997 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
998 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
1000 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
1002 RecalcHorzScrollBar(true);
1003 InvalidateHorzScrollBar();
1009 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
1011 int newlineBegin, newlineEnd;
1012 CMergeDoc *pd = GetDocument();
1013 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
1021 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
1023 newlineBegin = curDiff.dbegin;
1024 ASSERT (newlineBegin >= 0);
1025 newlineEnd = curDiff.dend;
1028 m_lineBegin = newlineBegin;
1029 m_lineEnd = newlineEnd;
1031 int nLineCount = GetLineCount();
1032 if (m_lineBegin > nLineCount)
1033 m_lineBegin = nLineCount - 1;
1034 if (m_lineEnd > nLineCount)
1035 m_lineEnd = nLineCount - 1;
1037 if (m_nTopLine == newlineBegin)
1040 // scroll to the first line of the diff
1041 vector<WordDiff> worddiffs;
1042 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
1043 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
1044 CEPoint pt = worddiffs.size() > 0 ?
1045 CEPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
1046 CEPoint{ 0, m_lineBegin };
1047 ScrollToLine(m_lineBegin);
1051 // update the width of the horizontal scrollbar
1052 InvalidateHorzScrollBar();
1056 * @brief Selects diff by number and syncs other file
1057 * @param [in] nDiff Diff to select, must be >= 0
1058 * @param [in] bScroll Scroll diff to view
1059 * @param [in] bSelectText Select diff text
1060 * @sa CMergeEditView::ShowDiff()
1061 * @sa CMergeDoc::SetCurrentDiff()
1062 * @todo Parameter bSelectText is never used?
1064 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
1066 CMergeDoc *pd = GetDocument();
1068 // Check that nDiff is valid
1070 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
1071 if (nDiff >= pd->m_diffList.GetSize())
1072 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
1073 nDiff, pd->m_diffList.GetSize());
1076 pd->SetCurrentDiff(nDiff);
1077 ShowDiff(bScroll, bSelectText);
1078 pd->UpdateAllViews(this);
1079 UpdateSiblingScrollPos(false);
1081 // notify either side, as it will notify the other one
1082 pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1085 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1087 CMergeDoc *pd = GetDocument();
1088 // If we have a selected diff, deselect it
1089 int nCurrentDiff = pd->GetCurrentDiff();
1090 if (nCurrentDiff != -1)
1092 CEPoint pos = GetCursorPos();
1093 if (!IsLineInCurrentDiff(pos.y))
1095 pd->SetCurrentDiff(-1);
1097 pd->UpdateAllViews(this);
1103 * @brief Called when user selects "Current Difference".
1104 * Goes to active diff. If no active diff, selects diff under cursor
1105 * @sa CMergeEditView::SelectDiff()
1106 * @sa CMergeDoc::GetCurrentDiff()
1107 * @sa CMergeDoc::LineToDiff()
1109 void CMergeEditView::OnCurdiff()
1111 CMergeDoc *pd = GetDocument();
1113 // If no diffs, nothing to select
1114 if (!pd->m_diffList.HasSignificantDiffs())
1117 // GetCurrentDiff() returns -1 if no diff selected
1118 int nDiff = pd->GetCurrentDiff();
1121 // Scroll to the first line of the currently selected diff
1122 SelectDiff(nDiff, true, false);
1126 // If cursor is inside diff, select that diff
1127 CEPoint pos = GetCursorPos();
1128 nDiff = pd->m_diffList.LineToDiff(pos.y);
1129 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1130 SelectDiff(nDiff, true, false);
1135 * @brief Called when "Current diff" item is updated
1137 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1139 CMergeDoc *pd = GetDocument();
1140 int nCurrentDiff = pd->GetCurrentDiff();
1141 if (nCurrentDiff == -1)
1143 CEPoint pos = GetCursorPos();
1144 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1145 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1148 pCmdUI->Enable(true);
1152 * @brief Copy selected text to clipboard
1154 void CMergeEditView::OnEditCopy()
1156 CMergeDoc * pDoc = GetDocument();
1157 auto [ptSelStart, ptSelEnd] = GetSelection();
1160 if (ptSelStart == ptSelEnd)
1165 if (!m_bRectangularSelection)
1167 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1170 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1171 ptSelEnd.y, ptSelEnd.x, sText);
1172 text.SetString(sText.c_str(), static_cast<int>(sText.length())); // TODO: Use String instead of CString
1175 GetTextWithoutEmptysInColumnSelection(text);
1177 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1181 * @brief Called when "Copy" item is updated
1183 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1185 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1189 * @brief Cut current selection to clipboard
1191 void CMergeEditView::OnEditCut()
1193 if (!QueryEditable())
1196 CMergeDoc * pDoc = GetDocument();
1197 auto [ptSelStart, ptSelEnd] = GetSelection();
1200 if (ptSelStart == ptSelEnd)
1204 if (!m_bRectangularSelection)
1207 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1208 ptSelEnd.y, ptSelEnd.x, sText);
1209 text.SetString(sText.c_str(), static_cast<int>(sText.length())); // TODO: Use String instead of CString
1212 GetTextWithoutEmptysInColumnSelection(text);
1214 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1216 if (!m_bRectangularSelection)
1218 CEPoint 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 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
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 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
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(CEPoint(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(CEPoint(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 = (pd->GetCurrentDiff() < 0 && GetCursorPos().y <= (long)dfi->dbegin)
1446 || (GetCursorPos().y < (long)dfi->dbegin);
1449 if (!enabled && pd->GetDirDoc())
1450 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1452 pCmdUI->Enable(enabled);
1456 * @brief Go to previous diff and select it.
1458 * Finds and selects previous difference. There are several cases:
1459 * - if there is selected difference, and that difference is visible
1460 * on screen, previous found difference is selected.
1461 * - if there is selected difference but it is not visible, previous
1462 * difference from cursor position is selected. This is what user
1463 * expects to happen and is natural thing to do. Also reduces
1464 * needless scrolling.
1465 * - if there is no selected difference, previous difference from cursor
1466 * position is selected.
1468 void CMergeEditView::OnPrevdiff()
1470 CMergeDoc *pd = GetDocument();
1471 int cnt = pd->m_ptBuf[0]->GetLineCount();
1475 // GetCurrentDiff() returns -1 if no diff selected
1477 int curDiff = pd->GetCurrentDiff();
1481 if (!IsDiffVisible(curDiff))
1483 // Selected difference not visible, select previous from cursor
1484 int line = GetCursorPos().y;
1485 // Make sure we aren't in the last line of the diff
1487 if (!IsValidTextPosY(CEPoint(0, line)))
1489 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1493 // Find out if there is a preceding significant diff
1496 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1502 // We don't have a selected difference,
1503 // but cursor can be inside inactive diff
1504 int line = GetCursorPos().y;
1505 if (!IsValidTextPosY(CEPoint(0, line)))
1507 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1510 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1511 if (prevDiff >= 0 && prevDiff >= firstDiff)
1512 SelectDiff(prevDiff, true, false);
1513 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1515 if (pDirDoc->MoveableToPrevDiff())
1516 pDirDoc->MoveToPrevDiff(pd);
1521 * @brief Update "Previous diff" UI items
1523 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1525 CMergeDoc *pd = GetDocument();
1526 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1531 // There aren't any significant differences
1536 // Enable if the end of the first significant difference is before caret
1537 enabled = (pd->GetCurrentDiff() < 0 && GetCursorPos().y >= (long)dfi->dbegin)
1538 || (GetCursorPos().y > (long)dfi->dend);
1541 if (!enabled && pd->GetDirDoc())
1542 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1544 pCmdUI->Enable(enabled);
1547 void CMergeEditView::OnNextConflict()
1549 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1553 * @brief Update "Next Conflict" UI items
1555 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1557 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1560 void CMergeEditView::OnPrevConflict()
1562 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1566 * @brief Update "Prev Conflict" UI items
1568 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1570 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1574 * @brief Go to next 3-way diff and select it.
1576 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1578 CMergeDoc *pd = GetDocument();
1579 int cnt = pd->m_ptBuf[0]->GetLineCount();
1583 // Returns -1 if no diff selected
1584 int curDiff = pd->GetCurrentDiff();
1588 int nextDiff = curDiff;
1589 if (!IsDiffVisible(curDiff))
1591 // Selected difference not visible, select next from cursor
1592 int line = GetCursorPos().y;
1593 // Make sure we aren't in the first line of the diff
1595 if (!IsValidTextPosY(CEPoint(0, line)))
1597 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1601 // Find out if there is a following significant diff
1602 if (curDiff < pd->m_diffList.GetSize() - 1)
1604 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1610 // nextDiff is the next one if there is one, else it is the one we're on
1611 SelectDiff(nextDiff, true, false);
1615 // We don't have a selected difference,
1616 // but cursor can be inside inactive diff
1617 int line = GetCursorPos().y;
1618 if (!IsValidTextPosY(CEPoint(0, line)))
1620 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1622 SelectDiff(curDiff, true, false);
1627 * @brief Update "Next 3-way diff" UI items
1629 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1631 CMergeDoc *pd = GetDocument();
1633 if (pd->m_nBuffers < 3)
1635 pCmdUI->Enable(false);
1639 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1643 // There aren't any significant differences
1644 pCmdUI->Enable(false);
1648 // Enable if the beginning of the last significant difference is after caret
1649 CEPoint pos = GetCursorPos();
1650 pCmdUI->Enable((pd->GetCurrentDiff() < 0 && pos.y <= (long)dfi->dbegin)
1651 || (pos.y < (long)dfi->dbegin));
1656 * @brief Go to previous 3-way diff and select it.
1658 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1660 CMergeDoc *pd = GetDocument();
1662 int cnt = pd->m_ptBuf[0]->GetLineCount();
1666 // GetCurrentDiff() returns -1 if no diff selected
1667 int curDiff = pd->GetCurrentDiff();
1671 int prevDiff = curDiff;
1672 if (!IsDiffVisible(curDiff))
1674 // Selected difference not visible, select previous from cursor
1675 int line = GetCursorPos().y;
1676 // Make sure we aren't in the last line of the diff
1678 if (!IsValidTextPosY(CEPoint(0, line)))
1680 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1684 // Find out if there is a preceding significant diff
1687 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1693 // prevDiff is the preceding one if there is one, else it is the one we're on
1694 SelectDiff(prevDiff, true, false);
1698 // We don't have a selected difference,
1699 // but cursor can be inside inactive diff
1700 int line = GetCursorPos().y;
1701 if (!IsValidTextPosY(CEPoint(0, line)))
1703 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1705 SelectDiff(curDiff, true, false);
1710 * @brief Update "Previous diff X and Y" UI items
1712 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1714 CMergeDoc *pd = GetDocument();
1716 if (pd->m_nBuffers < 3)
1718 pCmdUI->Enable(false);
1722 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1726 // There aren't any significant differences
1727 pCmdUI->Enable(false);
1731 // Enable if the end of the first significant difference is before caret
1732 CEPoint pos = GetCursorPos();
1733 pCmdUI->Enable((pd->GetCurrentDiff() < 0 && pos.y >= (long)dfi->dend)
1734 || (pos.y > (long)dfi->dend));
1738 void CMergeEditView::OnNextdiffLM()
1740 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1743 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1745 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1748 void CMergeEditView::OnNextdiffLR()
1750 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1753 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1755 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1758 void CMergeEditView::OnNextdiffMR()
1760 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1763 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1765 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1768 void CMergeEditView::OnNextdiffLO()
1770 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1773 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1775 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1778 void CMergeEditView::OnNextdiffMO()
1780 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1783 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1785 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1788 void CMergeEditView::OnNextdiffRO()
1790 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1793 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1795 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1798 void CMergeEditView::OnPrevdiffLM()
1800 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1803 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1805 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1808 void CMergeEditView::OnPrevdiffLR()
1810 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1813 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1815 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1818 void CMergeEditView::OnPrevdiffMR()
1820 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1823 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1825 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1828 void CMergeEditView::OnPrevdiffLO()
1830 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1833 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1835 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1838 void CMergeEditView::OnPrevdiffMO()
1840 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1843 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1845 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1848 void CMergeEditView::OnPrevdiffRO()
1850 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1853 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1855 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1859 * @brief Clear selection
1861 void CMergeEditView::SelectNone()
1863 SetSelection (GetCursorPos(), GetCursorPos());
1868 * @brief Check if line is inside currently selected diff
1869 * @param [in] nLine 0-based linenumber in view
1870 * @sa CMergeDoc::GetCurrentDiff()
1871 * @sa CMergeDoc::LineInDiff()
1873 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1875 // Check validity of nLine
1878 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1879 int nLineCount = LocateTextBuffer()->GetLineCount();
1880 if (nLine >= nLineCount)
1881 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1884 const CMergeDoc *pd = GetDocument();
1885 int curDiff = pd->GetCurrentDiff();
1888 return pd->m_diffList.LineInDiff(nLine, curDiff);
1892 * @brief Called when mouse left-button double-clicked
1894 * Double-clicking mouse inside diff selects that diff
1896 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1898 CMergeDoc *pd = GetDocument();
1899 CEPoint pos = GetCursorPos();
1901 int diff = pd->m_diffList.LineToDiff(pos.y);
1902 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1903 SelectDiff(diff, false, false);
1905 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1909 * @brief Called when mouse left button is released.
1911 * If button is released outside diffs, current diff
1914 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1916 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1917 DeselectDiffIfCursorNotInCurrentDiff();
1921 * @brief Called when mouse right button is pressed.
1923 * If right button is pressed outside diffs, current diff
1926 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1928 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1929 DeselectDiffIfCursorNotInCurrentDiff();
1932 void CMergeEditView::OnX2Y(int srcPane, int dstPane, bool selectedLineOnly)
1934 // Check that right side is not readonly
1935 if (IsReadOnly(dstPane))
1938 CMergeDoc *pDoc = GetDocument();
1939 int currentDiff = pDoc->GetCurrentDiff();
1941 if (currentDiff == -1)
1944 // If cursor is inside diff get number of that diff
1945 if (m_bCurrentLineIsDiff)
1947 CEPoint pt = GetCursorPos();
1948 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1952 auto [ptStart, ptEnd] = GetSelection();
1953 if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1955 if (!m_bRectangularSelection)
1957 if (selectedLineOnly)
1959 int firstDiff, lastDiff;
1960 GetSelectedDiffs(firstDiff, lastDiff);
1961 if (firstDiff != -1 && lastDiff != -1)
1963 CWaitCursor waitstatus;
1964 pDoc->CopyMultiplePartialList(srcPane, dstPane, firstDiff, lastDiff, ptStart.y, ptEnd.y);
1969 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1970 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1971 if (firstDiff != -1 && lastDiff != -1)
1973 CWaitCursor waitstatus;
1975 // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1976 // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1977 if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1979 // old behaviour: copy full line
1980 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1984 // new behaviour: copy selected text only
1985 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1992 CWaitCursor waitstatus;
1993 auto wordDiffs = GetColumnSelectedWordDiffIndice();
1995 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1996 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
2001 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
2003 if (selectedLineOnly)
2005 CWaitCursor waitstatus;
2006 pDoc->PartialListCopy(srcPane, dstPane, currentDiff, ptStart.y, ptEnd.y);
2010 CWaitCursor waitstatus;
2011 pDoc->ListCopy(srcPane, dstPane, currentDiff);
2016 void CMergeEditView::OnUpdateX2Y(CCmdUI* pCmdUI)
2018 auto [srcPane, dstPane] = CMergeFrameCommon::MenuIDtoXY(pCmdUI->m_nID, m_nThisPane, GetDocument()->m_nBuffers);
2019 if (GetDocument()->m_nBuffers > 2)
2020 CMergeFrameCommon::ChangeMergeMenuText(srcPane, dstPane, pCmdUI);
2021 if (srcPane < 0 || dstPane < 0)
2023 pCmdUI->Enable(false);
2026 // Check that right side is not readonly
2027 if (!IsReadOnly(dstPane))
2029 // If one or more diffs inside selection OR
2030 // there is an active diff OR
2031 // cursor is inside diff
2032 auto [ptStart, ptEnd] = GetSelection();
2033 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
2035 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
2036 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
2038 pCmdUI->Enable((firstDiff != -1 && lastDiff != -1) || (firstWordDiff != -1 && lastWordDiff != -1));
2042 const int currDiff = GetDocument()->GetCurrentDiff();
2043 pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
2047 pCmdUI->Enable(false);
2050 void CMergeEditView::OnCopyX2Y(UINT nID)
2052 auto [srcPane, dstPane] = CMergeFrameCommon::MenuIDtoXY(nID, m_nThisPane, GetDocument()->m_nBuffers);
2053 if (srcPane >= 0 && dstPane >= 0)
2054 OnX2Y(srcPane, dstPane);
2057 void CMergeEditView::OnCopyLinesX2Y(UINT nID)
2059 auto [srcPane, dstPane] = CMergeFrameCommon::MenuIDtoXY(nID, m_nThisPane, GetDocument()->m_nBuffers);
2060 if (srcPane >= 0 && dstPane >= 0)
2061 OnX2Y(srcPane, dstPane, true);
2065 * @brief Copy diff from left pane to right pane
2067 * Difference is copied from left to right when
2068 * - difference is selected
2069 * - difference is inside selection (allows merging multiple differences).
2070 * - cursor is inside diff
2072 * If there is selected diff outside selection, we copy selected
2075 void CMergeEditView::OnL2r()
2081 * @brief Called when "Copy to Right" item is updated
2083 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
2085 OnUpdateX2Y(pCmdUI);
2088 void CMergeEditView::OnLinesL2r()
2090 OnCopyLinesX2Y(ID_LINES_L2R);
2093 void CMergeEditView::OnUpdateLinesL2r(CCmdUI* pCmdUI)
2095 OnUpdateX2Y(pCmdUI);
2099 * @brief Copy diff from right pane to left pane
2101 * Difference is copied from left to right when
2102 * - difference is selected
2103 * - difference is inside selection (allows merging multiple differences).
2104 * - cursor is inside diff
2106 * If there is selected diff outside selection, we copy selected
2109 void CMergeEditView::OnR2l()
2115 * @brief Called when "Copy to Left" item is updated
2117 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
2119 OnUpdateX2Y(pCmdUI);
2122 void CMergeEditView::OnLinesR2l()
2124 OnCopyLinesX2Y(ID_LINES_R2L);
2127 void CMergeEditView::OnUpdateLinesR2l(CCmdUI* pCmdUI)
2129 OnUpdateX2Y(pCmdUI);
2132 void CMergeEditView::OnCopyFromLeft()
2134 OnCopyX2Y(ID_COPY_FROM_LEFT);
2137 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2139 OnUpdateX2Y(pCmdUI);
2142 void CMergeEditView::OnCopyLinesFromLeft()
2144 OnCopyLinesX2Y(ID_COPY_LINES_FROM_LEFT);
2147 void CMergeEditView::OnUpdateCopyLinesFromLeft(CCmdUI* pCmdUI)
2149 OnUpdateX2Y(pCmdUI);
2152 void CMergeEditView::OnCopyFromRight()
2154 OnCopyX2Y(ID_COPY_FROM_RIGHT);
2157 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2159 OnUpdateX2Y(pCmdUI);
2162 void CMergeEditView::OnCopyLinesFromRight()
2164 OnCopyLinesX2Y(ID_COPY_LINES_FROM_RIGHT);
2167 void CMergeEditView::OnUpdateCopyLinesFromRight(CCmdUI* pCmdUI)
2169 OnUpdateX2Y(pCmdUI);
2173 * @brief Copy all diffs from right pane to left pane
2175 void CMergeEditView::OnAllLeft()
2177 UINT userChoice = 0;
2178 String msg = _("Are you sure you want to copy all differences to the other file?");
2179 userChoice = AfxMessageBox(msg.c_str(), MB_YESNO |
2180 MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN, IDS_CONFIRM_COPY_ALL_DIFFS);
2181 if (userChoice == IDNO)
2184 // Check that left side is not readonly
2185 auto [srcPane, dstPane] = CMergeFrameCommon::MenuIDtoXY(ID_ALL_LEFT, m_nThisPane, GetDocument()->m_nBuffers);
2186 if (IsReadOnly(dstPane))
2188 CWaitCursor waitstatus;
2190 GetDocument()->CopyAllList(srcPane, dstPane);
2194 * @brief Called when "Copy all to left" item is updated
2196 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2198 // Check that left side is not readonly
2199 [[maybe_unused]] auto [srcPane, dstPane] = CMergeFrameCommon::MenuIDtoXY(pCmdUI->m_nID, m_nThisPane, GetDocument()->m_nBuffers);
2200 if (!IsReadOnly(dstPane))
2201 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2203 pCmdUI->Enable(false);
2204 if (GetDocument()->m_nBuffers > 2)
2205 CMergeFrameCommon::ChangeMergeMenuText(m_nThisPane, dstPane, pCmdUI);
2209 * @brief Copy all diffs from left pane to right pane
2211 void CMergeEditView::OnAllRight()
2213 UINT userChoice = 0;
2214 String msg = _("Are you sure you want to copy all differences to the other file?");
2215 userChoice = AfxMessageBox(msg.c_str(), MB_YESNO |
2216 MB_ICONWARNING | MB_DEFBUTTON2 | MB_DONT_ASK_AGAIN, IDS_CONFIRM_COPY_ALL_DIFFS);
2217 if (userChoice == IDNO)
2220 // Check that right side is not readonly
2221 auto [srcPane, dstPane] = CMergeFrameCommon::MenuIDtoXY(ID_ALL_RIGHT, m_nThisPane, GetDocument()->m_nBuffers);
2222 if (IsReadOnly(dstPane))
2225 CWaitCursor waitstatus;
2227 GetDocument()->CopyAllList(srcPane, dstPane);
2231 * @brief Called when "Copy all to right" item is updated
2233 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2235 // Check that right side is not readonly
2236 [[maybe_unused]] auto [srcPane, dstPane] = CMergeFrameCommon::MenuIDtoXY(pCmdUI->m_nID, m_nThisPane, GetDocument()->m_nBuffers);
2237 if (!IsReadOnly(dstPane))
2238 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2240 pCmdUI->Enable(false);
2241 if (GetDocument()->m_nBuffers > 2)
2242 CMergeFrameCommon::ChangeMergeMenuText(m_nThisPane, dstPane, pCmdUI);
2246 * @brief Do Auto merge
2248 void CMergeEditView::OnAutoMerge()
2250 // Check current pane is not readonly
2251 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2254 CWaitCursor waitstatus;
2256 GetDocument()->DoAutoMerge(m_nThisPane);
2260 * @brief Called when "Auto Merge" item is updated
2262 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2264 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2265 !GetDocument()->IsModified() &&
2266 !GetDocument()->GetAutoMerged() &&
2271 * @brief Add synchronization point
2273 void CMergeEditView::OnAddSyncPoint()
2275 GetDocument()->AddSyncPoint();
2279 * @brief Clear synchronization points
2281 void CMergeEditView::OnClearSyncPoints()
2283 GetDocument()->ClearSyncPoints();
2287 * @brief Called when "Clear Synchronization Points" item is updated
2289 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2291 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2295 * @brief This function is called before other edit events.
2296 * @param [in] nAction Edit operation to do
2297 * @param [in] pszText Text to insert, delete etc
2298 * @sa CCrystalEditView::OnEditOperation()
2299 * @todo More edit-events for rescan delaying?
2301 void CMergeEditView::OnEditOperation(int nAction, const tchar_t* pszText, size_t cchText)
2303 if (!QueryEditable())
2305 // We must not arrive here, and assert helps detect troubles
2310 CMergeDoc* pDoc = GetDocument();
2311 pDoc->SetEditedAfterRescan(m_nThisPane);
2313 // simple hook for multiplex undo operations
2314 // deleted by jtuc 2003-06-28
2315 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2316 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2318 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2319 pDoc->undoTgt.push_back(this);
2320 pDoc->curUndo = pDoc->undoTgt.end();
2323 // perform original function
2324 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2326 // augment with additional operations
2328 // Change header to inform about changed doc
2329 pDoc->UpdateHeaderPath(m_nThisPane);
2331 // If automatic rescan enabled, rescan after edit events
2332 if (pDoc->GetAutomaticRescan())
2334 // keep document up to date
2335 // (Re)start timer to rescan only when user edits text
2336 // If timer starting fails, rescan immediately
2337 if (nAction == CE_ACTION_TYPING ||
2338 nAction == CE_ACTION_REPLACE ||
2339 nAction == CE_ACTION_BACKSPACE ||
2340 nAction == CE_ACTION_INDENT ||
2341 nAction == CE_ACTION_PASTE ||
2342 nAction == CE_ACTION_DELSEL ||
2343 nAction == CE_ACTION_DELETE ||
2344 nAction == CE_ACTION_CUT)
2346 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2347 pDoc->FlushAndRescan();
2350 pDoc->FlushAndRescan();
2356 // Update other pane for sync line.
2357 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2359 if (nPane == m_nThisPane)
2361 CCrystalEditView *pView = GetGroupView(nPane);
2362 if (pView != nullptr)
2363 pView->Invalidate();
2370 * @brief Redo last action
2372 void CMergeEditView::OnEditRedo()
2374 CWaitCursor waitstatus;
2375 CMergeDoc* pDoc = GetDocument();
2376 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2379 if (!QueryEditable())
2382 GetParentFrame()->SetActiveView(this, true);
2383 if(CCrystalEditViewEx::DoEditRedo())
2386 pDoc->UpdateHeaderPath(m_nThisPane);
2387 pDoc->FlushAndRescan();
2392 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2397 * @brief Called when "Redo" item is updated
2399 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2401 CMergeDoc* pDoc = GetDocument();
2402 if (pDoc->curUndo!=pDoc->undoTgt.end())
2404 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2405 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2408 pCmdUI->Enable(false);
2411 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2413 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2417 * @brief Scrolls to current diff and/or selects diff text
2418 * @param [in] bScroll If true scroll diff to view
2419 * @param [in] bSelectText If true select diff text
2420 * @note If bScroll and bSelectText are false, this does nothing!
2421 * @todo This shouldn't be called when no diff is selected, so
2422 * somebody could try to ASSERT(nDiff > -1)...
2424 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2426 CMergeDoc *pd = GetDocument();
2427 const int nDiff = pd->GetCurrentDiff();
2429 // Try to trap some errors
2430 if (nDiff >= pd->m_diffList.GetSize())
2431 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2432 nDiff, pd->m_diffList.GetSize());
2434 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2436 CEPoint ptStart, ptEnd;
2438 pd->m_diffList.GetDiff(nDiff, curDiff);
2441 ptStart.y = curDiff.dbegin;
2443 ptEnd.y = curDiff.dend;
2445 if (bScroll && !m_bDetailView)
2447 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2449 // Difference is not visible, scroll it so that max amount of
2450 // scrolling is done while keeping the diff in screen. So if
2451 // scrolling is downwards, scroll the diff to as up in screen
2452 // as possible. This usually brings next diff to the screen
2453 // and we don't need to scroll into it.
2454 int nLine = GetSubLineIndex(ptStart.y);
2455 if (nLine > CONTEXT_LINES_ABOVE)
2457 nLine -= CONTEXT_LINES_ABOVE;
2459 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2460 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2462 if (nPane != m_nThisPane)
2463 GetGroupView(nPane)->ScrollToSubLine(nLine);
2467 vector<WordDiff> worddiffs;
2468 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
2469 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2470 CEPoint pt = worddiffs.size() > 0 ?
2471 CEPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2473 GetGroupView(m_nThisPane)->SetCursorPos(pt);
2474 GetGroupView(m_nThisPane)->SetAnchor(pt);
2475 GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2476 GetGroupView(m_nThisPane)->EnsureVisible(pt);
2477 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2479 if (nPane != m_nThisPane)
2481 if (worddiffs.size() > 0)
2483 pt.x = worddiffs[0].begin[nPane];
2484 pt.y = worddiffs[0].beginline[nPane];
2486 GetGroupView(nPane)->SetCursorPos(pt);
2487 GetGroupView(nPane)->SetAnchor(pt);
2488 GetGroupView(nPane)->SetSelection(pt, pt);
2495 ptEnd.x = GetLineLength(ptEnd.y);
2496 SetSelection(ptStart, ptEnd);
2505 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2507 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2508 // ...but for this the queue must be empty
2509 // The timer message is a low priority message but the queue is maybe not yet empty
2510 // So we set a flag, wait for OnIdle to proceed, then come back here...
2511 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2512 // not with SetTimer so there is no delay)
2514 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2516 // IDLE_TIMER is the false timer used to come back here after OnIdle
2517 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2518 // (one normal timer = one flag = one command)
2520 if (nIDEvent == IDT_RESCAN)
2522 KillTimer(IDT_RESCAN);
2523 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2524 // notify the app to come back after OnIdle
2525 theApp.SetNeedIdleTimer();
2528 if (nIDEvent == IDLE_TIMER)
2530 // not a real timer, just come back after OnIdle
2531 // look to flags to know what to do
2532 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2533 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2534 fTimerWaitingForIdle = 0;
2537 CCrystalEditViewEx::OnTimer(nIDEvent);
2541 * @brief Returns if buffer is read-only
2542 * @note This has no any relation to file being read-only!
2544 bool CMergeEditView::IsReadOnly(int pane) const
2546 return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2550 * @brief Handle some keys when in merging mode
2552 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2554 bool bHandled = false;
2556 // Allow default text selection when SHIFT pressed
2557 if (::GetAsyncKeyState(VK_SHIFT))
2560 // Allow default editor functions when CTRL pressed
2561 if (::GetAsyncKeyState(VK_CONTROL))
2564 // If we are in merging mode (merge with cursor keys)
2565 // handle some keys here
2566 switch (pMsg->wParam)
2579 PostMessage(WM_COMMAND, ID_PREVDIFF);
2584 PostMessage(WM_COMMAND, ID_NEXTDIFF);
2593 * @brief Called before messages are translated.
2595 * Checks if ESC key was pressed, saves and closes doc.
2596 * Also if in merge mode traps cursor keys.
2598 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2600 if (pMsg->message == WM_KEYDOWN)
2602 // If we are in merging mode (merge with cursor keys)
2603 // handle some keys here
2604 if (theApp.GetMergingMode())
2606 bool bHandled = MergeModeKeyDown(pMsg);
2611 // Close window if user has allowed it from options
2612 if (pMsg->wParam == VK_ESCAPE)
2614 int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2615 if (nCloseWithEsc != 0)
2616 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2621 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2624 /// Store interface we use to display status line info
2625 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2627 ASSERT(m_piMergeEditStatus == nullptr);
2628 m_piMergeEditStatus = piMergeEditStatus;
2632 * @brief Update status bar contents.
2634 void CMergeEditView::UpdateStatusbar()
2640 * @brief Update statusbar info, Override from CCrystalTextView
2641 * @note we tab-expand column, but we don't tab-expand char count,
2642 * since we want to show how many chars there are and tab is just one
2643 * character although it expands to several spaces.
2645 void CMergeEditView::OnUpdateCaret()
2647 if (m_bCursorHidden || m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2650 CEPoint cursorPos = GetCursorPos();
2651 int nScreenLine = cursorPos.y;
2652 const int nRealLine = ComputeRealLine(nScreenLine);
2659 auto [selectedLines, selectedChars] = GetSelectedLineAndCharacterCount();
2660 lineflags_t dwLineFlags = 0;
2662 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2663 // Is this a ghost line ?
2664 if (dwLineFlags & LF_GHOST)
2666 // Ghost lines display eg "Line 12-13"
2667 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2668 sEol = _T("hidden");
2672 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2673 sLine.Format(_T("%d"), nRealLine+1);
2674 curChar = cursorPos.x + 1;
2675 chars = GetLineLength(nScreenLine);
2676 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2677 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2679 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2680 GetDocument()->IsMixedEOL(m_nThisPane))
2682 sEol = GetTextBufferEol(nScreenLine);
2685 sEol = _T("hidden");
2687 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2688 curChar, chars, selectedLines, selectedChars,
2689 sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2691 // Is cursor inside difference?
2692 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2693 m_bCurrentLineIsDiff = true;
2695 m_bCurrentLineIsDiff = false;
2697 CWnd* pWnd = GetFocus();
2698 if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2699 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2702 * @brief Select linedifference in the current line.
2704 * Select line difference in current line. Selection type
2705 * is choosed by highlight type.
2707 template<bool reversed>
2708 void CMergeEditView::OnSelectLineDiff()
2710 // Pass this to the document, to compare this file to other
2711 GetDocument()->Showlinediff(this, reversed);
2714 /// Enable select difference menuitem if current line is inside difference.
2715 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2717 pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2720 void CMergeEditView::OnAddToSubstitutionFilters()
2722 // Pass this to the document, to compare this file to other
2723 GetDocument()->AddToSubstitutionFilters(this, false);
2726 void CMergeEditView::OnUpdateAddToSubstitutionFilters(CCmdUI* pCmdUI)
2728 pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
2731 void CMergeEditView::OnAddToLineFilters()
2733 // Pass this to the document, to compare this file to other
2734 CMergeDoc* pDoc = GetDocument();
2735 auto [ptSelStart, ptSelEnd] = GetSelection();
2738 if (ptSelStart == ptSelEnd)
2743 if (!m_bRectangularSelection)
2745 CDiffTextBuffer* buffer = pDoc->m_ptBuf[m_nThisPane].get();
2748 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
2749 ptSelEnd.y, ptSelEnd.x, sText);
2750 text.SetString(sText.c_str(), static_cast<int>(sText.length())); // TODO: Use String instead of CString
2753 GetTextWithoutEmptysInColumnSelection(text);
2755 CMergeDoc* pd = GetDocument();
2756 pd->AddToLineFilters(text.GetString());
2757 pd->FlushAndRescan(true);
2760 void CMergeEditView::OnUpdateAddToLineFilters(CCmdUI* pCmdUI)
2762 pCmdUI->Enable(IsSelection() && GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
2766 * @brief Enable/disable Replace-menuitem
2768 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2770 CMergeDoc *pd = GetDocument();
2771 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2773 pCmdUI->Enable(!bReadOnly);
2777 * @brief Offer a context menu built with scriptlet/ActiveX functions
2779 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
2782 GetClientRect(rect);
2783 ClientToScreen(rect);
2785 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
2787 if (rect.top <= point.y && point.y < rect.top + GetTopMarginHeight())
2790 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEWHEADER));
2791 theApp.TranslateMenu(menu.m_hMenu);
2792 BCMenu* pSub = static_cast<BCMenu*>(menu.GetSubMenu(0));
2793 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2794 point.x, point.y, AfxGetMainWnd());
2799 // Create the menu and populate it with the available functions
2801 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
2803 // Remove copying item copying from active side
2804 if (m_nThisPane == 0) // left?
2806 if (GetDocument()->m_nBuffers < 3)
2808 menu.RemoveMenu(ID_COPY_TO_MIDDLE_L, MF_BYCOMMAND);
2809 menu.RemoveMenu(ID_COPY_FROM_MIDDLE_L, MF_BYCOMMAND);
2810 menu.RemoveMenu(ID_COPY_LINES_TO_MIDDLE_L, MF_BYCOMMAND);
2811 menu.RemoveMenu(ID_COPY_LINES_FROM_MIDDLE_L, MF_BYCOMMAND);
2813 for (UINT id = ID_COPY_TO_LEFT_M; id <= ID_COPY_FROM_LEFT_R; ++id)
2814 menu.RemoveMenu(id, MF_BYCOMMAND);
2815 for (UINT id = ID_COPY_LINES_TO_LEFT_M; id <= ID_COPY_LINES_FROM_LEFT_R; ++id)
2816 menu.RemoveMenu(id, MF_BYCOMMAND);
2818 if (m_nThisPane == 1 && GetDocument()->m_nBuffers == 3)
2820 for (UINT id = ID_COPY_TO_MIDDLE_L; id <= ID_COPY_FROM_RIGHT_L; ++id)
2821 menu.RemoveMenu(id, MF_BYCOMMAND);
2822 for (UINT id = ID_COPY_TO_MIDDLE_R; id <= ID_COPY_FROM_LEFT_R; ++id)
2823 menu.RemoveMenu(id, MF_BYCOMMAND);
2824 for (UINT id = ID_COPY_LINES_TO_MIDDLE_L; id <= ID_COPY_LINES_FROM_RIGHT_L; ++id)
2825 menu.RemoveMenu(id, MF_BYCOMMAND);
2826 for (UINT id = ID_COPY_LINES_TO_MIDDLE_R; id <= ID_COPY_LINES_FROM_LEFT_R; ++id)
2827 menu.RemoveMenu(id, MF_BYCOMMAND);
2829 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
2831 if (GetDocument()->m_nBuffers < 3)
2833 menu.RemoveMenu(ID_COPY_TO_MIDDLE_R, MF_BYCOMMAND);
2834 menu.RemoveMenu(ID_COPY_FROM_MIDDLE_R, MF_BYCOMMAND);
2835 menu.RemoveMenu(ID_COPY_LINES_TO_MIDDLE_R, MF_BYCOMMAND);
2836 menu.RemoveMenu(ID_COPY_LINES_FROM_MIDDLE_R, MF_BYCOMMAND);
2838 for (UINT id = ID_COPY_TO_MIDDLE_L; id <= ID_COPY_FROM_RIGHT_M; ++id)
2839 menu.RemoveMenu(id, MF_BYCOMMAND);
2840 for (UINT id = ID_COPY_LINES_TO_MIDDLE_L; id <= ID_COPY_LINES_FROM_RIGHT_M; ++id)
2841 menu.RemoveMenu(id, MF_BYCOMMAND);
2844 // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
2845 // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
2846 // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
2847 int nBuffers = GetDocument()->m_nBuffers;
2848 if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
2849 menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
2850 else if (nBuffers == 3 && m_nThisPane == 2)
2851 menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
2853 VERIFY(menu.LoadToolbar(IDR_MAINFRAME, GetMainFrame()->GetToolbar()));
2854 theApp.TranslateMenu(menu.m_hMenu);
2856 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
2857 ASSERT(pSub != nullptr);
2859 // Context menu opened using keyboard has no coordinates
2860 if (point.x == -1 && point.y == -1)
2862 point = rect.TopLeft();
2866 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2867 point.x, point.y, AfxGetMainWnd());
2872 * @brief Update EOL mode in status bar
2874 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
2876 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
2880 * @brief Change EOL mode and unify all the lines EOL to this new mode
2882 void CMergeEditView::OnConvertEolTo(UINT nID )
2884 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;;
2888 nStyle = CRLFSTYLE::DOS;
2890 case ID_EOL_TO_UNIX:
2891 nStyle = CRLFSTYLE::UNIX;
2894 nStyle = CRLFSTYLE::MAC;
2898 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
2901 m_pTextBuffer->SetCRLFMode(nStyle);
2903 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
2904 if (m_pTextBuffer->applyEOLMode())
2906 CMergeDoc *pd = GetDocument();
2907 ASSERT(pd != nullptr);
2908 pd->UpdateHeaderPath(m_nThisPane);
2909 pd->FlushAndRescan(true);
2914 * @brief allow convert to entries in file submenu
2916 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
2918 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;
2919 switch (pCmdUI->m_nID)
2922 nStyle = CRLFSTYLE::DOS;
2924 case ID_EOL_TO_UNIX:
2925 nStyle = CRLFSTYLE::UNIX;
2928 nStyle = CRLFSTYLE::MAC;
2932 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
2936 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2937 GetDocument()->IsMixedEOL(m_nThisPane) ||
2938 nStyle != m_pTextBuffer->GetCRLFMode())
2940 pCmdUI->SetRadio(false);
2942 // Don't allow selecting other EOL style for protected pane
2943 if (!QueryEditable())
2944 pCmdUI->Enable(false);
2947 pCmdUI->SetRadio(true);
2951 * @brief Copy diff from left to right and advance to next diff
2953 void CMergeEditView::OnL2RNext()
2956 if (GetDocument()->m_nBuffers > 2 && IsCursorInDiff()) // for 3-way file compare
2962 * @brief Update "Copy right and advance" UI item
2964 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
2966 OnUpdateL2r(pCmdUI);
2970 * @brief Copy diff from right to left and advance to next diff
2972 void CMergeEditView::OnR2LNext()
2975 if (GetDocument()->m_nBuffers > 2 && IsCursorInDiff()) // for 3-way file compare
2981 * @brief Update "Copy left and advance" UI item
2983 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
2985 OnUpdateR2l(pCmdUI);
2989 * @brief Change active pane in MergeView.
2990 * Changes active pane and makes sure cursor position is kept in
2991 * screen. Currently we put cursor in same line than in original
2992 * active pane but we could be smarter too? Maybe update cursor
2993 * only when it is not visible in new pane?
2995 void CMergeEditView::OnChangePane(UINT nID)
2997 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
2998 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
2999 CMergeDoc *pDoc = GetDocument();
3000 bool bFound = false;
3001 CMergeEditView *pNextActiveView = nullptr;
3002 std::vector<CMergeEditView *> list = pDoc->GetViewList();
3003 list.insert(list.end(), list.begin(), list.end());
3004 if (nID == ID_PREV_PANE)
3005 std::reverse(list.begin(), list.end());
3007 for (auto& pView : list)
3009 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3011 pNextActiveView = pView;
3017 GetParentFrame()->SetActiveView(pNextActiveView);
3018 CEPoint ptCursor = pWnd->GetCursorPos();
3020 if (ptCursor.y >= pNextActiveView->GetLineCount())
3021 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3022 pNextActiveView->SetCursorPos(ptCursor);
3023 pNextActiveView->SetAnchor(ptCursor);
3024 pNextActiveView->SetSelection(ptCursor, ptCursor);
3028 * @brief Show "Go To" dialog and scroll views to line or diff.
3030 * Before dialog is opened, current line and file is determined
3032 * @note Conversions needed between apparent and real lines
3034 void CMergeEditView::OnWMGoto()
3037 CMergeDoc *pDoc = GetDocument();
3038 CEPoint pos = GetCursorPos();
3040 int nLastLine[3] = {};
3042 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3043 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3045 int nLineCount = pDoc->m_ptBuf[nPane]->GetLineCount();
3046 nLastLine[nPane] = pDoc->m_ptBuf[nPane]->ComputeRealLine(nLineCount - 1);
3049 // Set active file and current line selected in dialog
3050 dlg.m_strParam = strutils::to_str(nRealLine + 1);
3051 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3052 dlg.m_nGotoWhat = 0;
3053 dlg.m_nFiles = pDoc->m_nBuffers;
3054 dlg.m_nLastLine[0] = nLastLine[0] + 1;
3055 dlg.m_nLastLine[1] = (pDoc->m_nBuffers < 3) ? -1 : nLastLine[1] + 1;
3056 dlg.m_nLastLine[2] = (pDoc->m_nBuffers < 3) ? nLastLine[1] + 1: nLastLine[2] + 1;
3057 dlg.m_nLastDiff = pDoc->m_diffList.GetSize();
3059 if (dlg.DoModal() == IDOK)
3061 CMergeDoc * pDoc1 = GetDocument();
3064 CMergeEditView * pCurrentView = GetGroupView(m_nThisPane);
3067 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3069 if (dlg.m_nGotoWhat == 0)
3071 int nPane = (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile;
3072 assert(nPane >= 0 && ((pDoc1->m_nBuffers < 3 && nPane < 2) || (pDoc1->m_nBuffers == 3 && nPane < 3)));
3074 int nRealLine1 = num;
3077 if (nRealLine1 > nLastLine[nPane])
3078 nRealLine1 = nLastLine[nPane];
3080 bool bShift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
3081 GotoLine(nRealLine1, true, nPane, !bShift);
3088 if (diff >= pDoc1->m_diffList.GetSize())
3089 diff = pDoc1->m_diffList.GetSize() - 1;
3091 if (pCurrentView && diff >= 0)
3092 pCurrentView->SelectDiff(diff, true, false);
3098 * @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
3099 * Go to moved line between the left and right panes when in 2-way file comparison.
3100 * Go to moved line between the left and middle panes when in 3-way file comparison.
3102 void CMergeEditView::OnGotoMovedLineLM()
3104 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3107 CMergeDoc* pDoc = GetDocument();
3108 CEPoint pos = GetCursorPos();
3110 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3111 ASSERT(pDoc != nullptr);
3112 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3115 if (m_nThisPane == 0)
3117 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3119 GotoLine(line, false, 1);
3121 else if (m_nThisPane == 1)
3123 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3125 GotoLine(line, false, 0);
3130 * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
3131 * @param [in] pCmdUI UI component to update.
3132 * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
3134 void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
3136 CMergeDoc* pDoc = GetDocument();
3137 CEPoint pos = GetCursorPos();
3139 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3140 ASSERT(pCmdUI != nullptr);
3141 ASSERT(pDoc != nullptr);
3142 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3145 if (pDoc->m_nBuffers == 2)
3146 pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
3148 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
3150 pCmdUI->Enable(false);
3154 if (m_nThisPane == 0)
3156 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3157 pCmdUI->Enable(bOn);
3159 else if (m_nThisPane == 1)
3161 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3162 pCmdUI->Enable(bOn);
3167 * @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
3168 * Go to moved line between the middle and right panes when in 3-way file comparison.
3170 void CMergeEditView::OnGotoMovedLineMR()
3172 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3175 CMergeDoc* pDoc = GetDocument();
3176 CEPoint pos = GetCursorPos();
3178 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3179 ASSERT(pDoc != nullptr);
3180 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3183 if (m_nThisPane == 1)
3185 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3187 GotoLine(line, false, 2);
3189 else if (m_nThisPane == 2)
3191 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3193 GotoLine(line, false, 1);
3198 * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
3199 * @param [in] pCmdUI UI component to update.
3201 void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
3203 CMergeDoc* pDoc = GetDocument();
3204 CEPoint pos = GetCursorPos();
3206 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3207 ASSERT(pCmdUI != nullptr);
3208 ASSERT(pDoc != nullptr);
3209 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3212 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
3214 pCmdUI->Enable(false);
3218 if (m_nThisPane == 1)
3220 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3221 pCmdUI->Enable(bOn);
3223 else if (m_nThisPane == 2)
3225 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3226 pCmdUI->Enable(bOn);
3230 void CMergeEditView::OnShellMenu()
3232 CFrameWnd *pFrame = GetTopLevelFrame();
3233 ASSERT(pFrame != nullptr);
3234 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3235 pFrame->m_bAutoMenuEnable = FALSE;
3237 String path = GetDocument()->m_filePaths[m_nThisPane];
3238 auto pContextMenu = std::make_unique<CShellContextMenu>(CShellContextMenu(0x9000, 0x9FFF));
3239 pContextMenu->Initialize();
3240 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3241 pContextMenu->RequeryShellContextMenu();
3243 ::GetCursorPos(&point);
3244 HWND hWnd = GetSafeHwnd();
3245 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3247 pContextMenu->InvokeCommand(nCmd, hWnd);
3248 pContextMenu->ReleaseShellContextMenu();
3250 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3253 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3255 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3259 * @brief Reload options.
3261 void CMergeEditView::RefreshOptions()
3263 RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3264 SetRenderingMode(nRenderingMode);
3266 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3267 SetInsertTabs(true);
3269 SetInsertTabs(false);
3271 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3272 SetTopMargin(GetOptionsMgr()->GetBool(
3273 GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing() ? OPT_VIEW_TOPMARGIN_TABLE : OPT_VIEW_TOPMARGIN));
3274 SetLineUsedAsHeaders(GetOptionsMgr()->GetInt(OPT_LINE_NUMBER_USED_AS_HEADERS));
3276 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3277 SetTextType(CrystalLineParser::SRC_PLAIN);
3278 else if (!GetDocument()->GetChangedSchemeManually())
3280 // The syntax highlighting scheme should only be applied if it has not been manually changed.
3281 String fileName = GetDocument()->m_ptBuf[m_nThisPane]->GetTempFileName();
3283 paths::SplitFilename(fileName, nullptr, nullptr, &sExt);
3284 CrystalLineParser::TextDefinition* def = CrystalLineParser::GetTextType(sExt.c_str());
3286 SetTextType(def->type);
3288 SetTextType(CrystalLineParser::SRC_PLAIN);
3289 SetDisableBSAtSOL(false);
3292 SetWordWrapping(GetOptionsMgr()->GetBool(
3293 GetDocument()->m_ptBuf[0]->GetTableEditing() ? OPT_WORDWRAP_TABLE : OPT_WORDWRAP));
3294 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3296 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3297 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3298 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3299 GetDocument()->IsMixedEOL(m_nThisPane));
3301 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3304 void CMergeEditView::OnScripts(UINT nID)
3306 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3307 CString ctext = GetSelectedText();
3308 String text{ ctext, static_cast<unsigned>(ctext.GetLength()) };
3310 EditorScriptInfo scriptInfo(
3311 CMainFrame::GetPluginPipelineByMenuId(nID, FileTransform::EditorScriptEventNames, ID_SCRIPT_FIRST));
3312 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3313 bool bChanged = false;
3314 scriptInfo.TransformText(text, { GetDocument()->m_filePaths[m_nThisPane] }, bChanged);
3316 // now replace the text
3317 ReplaceSelection(text.c_str(), text.length(), 0);
3320 void CMergeEditView::OnTransformWithScript()
3322 // let the user choose a handler
3323 CSelectPluginDlg dlg(_T(""),
3324 strutils::join(GetDocument()->m_filePaths.begin(), GetDocument()->m_filePaths.end(), _T("|")),
3325 CSelectPluginDlg::PluginType::EditorScript, false);
3326 if (dlg.DoModal() != IDOK)
3328 EditorScriptInfo scriptInfo(dlg.GetPluginPipeline());
3329 CString ctext = GetSelectedText();
3330 String text{ ctext, static_cast<unsigned>(ctext.GetLength()) };
3331 bool bChanged = false;
3332 scriptInfo.TransformText(text, { GetDocument()->m_filePaths[m_nThisPane] }, bChanged);
3334 // now replace the text
3335 ReplaceSelection(text.c_str(), text.length(), 0);
3339 * @brief Goto given line.
3340 * @param [in] nLine Destination linenumber
3341 * @param [in] bRealLine if true linenumber is real line, otherwise
3342 * it is apparent line (including deleted lines)
3343 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3344 * @param [in] bMoveAnchor if true the anchor is moved to nLine
3345 * @param [in] nChar Destination character position
3347 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane, bool bMoveAnchor, int nChar)
3349 CMergeDoc *pDoc = GetDocument();
3350 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3351 CMergeEditView *pCurrentView = nullptr;
3352 if (pSplitterWnd != nullptr)
3353 pCurrentView = static_cast<CMergeEditView*>
3354 (pSplitterWnd->GetActivePane());
3356 int nRealLine = nLine;
3357 int nApparentLine = nLine;
3359 // Compute apparent (shown linenumber) line
3362 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3363 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3365 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3368 ptPos.x = nChar == -1 ? 0 : nChar;
3369 ptPos.y = nApparentLine;
3371 // Scroll line to center of view
3372 int nScrollLine = GetSubLineIndex(nApparentLine);
3373 nScrollLine -= GetScreenLines() / 2;
3374 if (nScrollLine < 0)
3377 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3379 int nGroup = m_bDetailView ? 0 : m_nThisGroup;
3380 CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
3381 pView->ScrollToSubLine(nScrollLine);
3382 CEPoint pt = (ptPos.y < pView->GetLineCount()) ? ptPos : CEPoint(ptPos.x, pView->GetLineCount() - 1);
3383 pt.x = std::clamp(static_cast<int>(pt.x), 0, pView->GetLineLength(pt.y));
3384 pView->SetCursorPos(pt);
3385 if (bMoveAnchor || nPane != pane)
3386 pView->SetAnchor(pt);
3387 pView->SetSelection(pView->GetAnchor(), pt);
3388 pView->EnsureVisible(pt);
3391 // If goto target is another view - activate another view.
3392 // This is done for user convenience as user probably wants to
3393 // work with goto target file.
3395 GetDocument()->GetView(0, pane)->SetActivePane();
3396 else if (GetGroupView(pane) != pCurrentView)
3397 GetGroupView(pane)->SetActivePane();
3401 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3404 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3406 if (pScrollBar == nullptr)
3408 // Scroll did not come frome a scroll bar
3409 // Find the appropriate scroll bar
3410 // and send the message to the splitter window instead
3411 // The event should eventually come back here but with a valid scrollbar
3412 // Along the way it will be propagated to other windows that need it
3413 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3414 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3415 pSplitterWnd->SendMessage(WM_HSCROLL,
3416 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3419 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3423 * @brief When view is scrolled using scrollbars update location pane.
3425 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3427 if (pScrollBar == nullptr)
3429 // Scroll did not come frome a scroll bar
3430 // Find the appropriate scroll bar
3431 // and send the message to the splitter window instead
3432 // The event should eventually come back here but with a valid scrollbar
3433 // Along the way it will be propagated to other windows that need it
3434 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3435 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3436 pSplitterWnd->SendMessage(WM_VSCROLL,
3437 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3440 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3442 if (nSBCode == SB_ENDSCROLL)
3445 // Note we cannot use nPos because of its 16-bit nature
3446 SCROLLINFO si{ sizeof(si) };
3447 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3448 VERIFY (GetScrollInfo (SB_VERT, &si));
3450 // Get the current position of scroll box.
3451 int nCurPos = si.nPos;
3453 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3457 * @brief Copy selected lines adding linenumbers.
3459 void CMergeEditView::OnEditCopyLineNumbers()
3465 CMergeDoc *pDoc = GetDocument();
3466 auto [ptStart, ptEnd] = GetSelection();
3468 // Get last selected line (having widest linenumber)
3469 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3470 size_t nNumWidth = strutils::to_str(line + 1).length();
3472 for (int i = ptStart.y; i <= ptEnd.y; i++)
3474 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3477 // We need to convert to real linenumbers
3478 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3480 // Insert spaces to align different width linenumbers (99, 100)
3481 strLine = GetLineText(i);
3482 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3485 strNumLine.Format(_T("%d: %s"), line + 1, (const tchar_t*)strLine);
3486 strText += strNumLine;
3488 PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3491 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3493 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3497 * @brief Open active file with associated application.
3499 * First tries to open file using shell 'Edit' action, since that
3500 * action open scripts etc. to editor instead of running them. If
3501 * edit-action is not registered, 'Open' action is used.
3503 void CMergeEditView::OnOpenFile()
3505 CMergeDoc * pDoc = GetDocument();
3506 ASSERT(pDoc != nullptr);
3508 String sFileName = pDoc->m_filePaths[m_nThisPane];
3509 if (sFileName.empty())
3511 shell::Edit(sFileName.c_str());
3515 * @brief Open active file with app selection dialog
3517 void CMergeEditView::OnOpenFileWith()
3519 CMergeDoc * pDoc = GetDocument();
3520 ASSERT(pDoc != nullptr);
3522 String sFileName = pDoc->m_filePaths[m_nThisPane];
3523 if (sFileName.empty())
3525 shell::OpenWith(sFileName.c_str());
3529 * @brief Open active file with external editor
3531 void CMergeEditView::OnOpenFileWithEditor()
3533 CMergeDoc * pDoc = GetDocument();
3534 ASSERT(pDoc != nullptr);
3536 String sFileName = pDoc->m_filePaths[m_nThisPane];
3537 if (sFileName.empty())
3540 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3541 CMergeApp::OpenFileToExternalEditor(sFileName, nRealLine);
3545 * @brief Open parent folder of active file
3547 void CMergeEditView::OnOpenParentFolder()
3549 CMergeDoc * pDoc = GetDocument();
3550 ASSERT(pDoc != nullptr);
3552 String sFileName = pDoc->m_filePaths[m_nThisPane];
3553 if (sFileName.empty())
3556 shell::OpenParentFolder(sFileName.c_str());
3560 * @brief Force repaint of the location pane.
3562 void CMergeEditView::RepaintLocationPane()
3564 // Must force recalculation due to caching of data in location pane.
3565 CLocationView *pLocationView = GetDocument()->GetLocationView();
3566 if (pLocationView != nullptr)
3567 pLocationView->ForceRecalculate();
3571 * @brief Enables/disables linediff (different color for diffs)
3573 void CMergeEditView::OnViewLineDiffs()
3575 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3576 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3578 // Call CMergeDoc RefreshOptions() to refresh *both* views
3579 CMergeDoc *pDoc = GetDocument();
3580 pDoc->RefreshOptions();
3581 pDoc->FlushAndRescan(true);
3584 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3586 pCmdUI->Enable(true);
3587 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3591 * @brief Enables/disables line number
3593 void CMergeEditView::OnViewLineNumbers()
3595 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3597 // Call CMergeDoc RefreshOptions() to refresh *both* views
3598 CMergeDoc *pDoc = GetDocument();
3599 pDoc->RefreshOptions();
3602 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3604 pCmdUI->Enable(true);
3605 pCmdUI->SetCheck(GetViewLineNumbers());
3609 * @brief Enables/disables word wrap
3611 void CMergeEditView::OnViewWordWrap()
3613 GetOptionsMgr()->SaveOption(
3614 GetDocument()->m_ptBuf[0]->GetTableEditing() ? OPT_WORDWRAP_TABLE : OPT_WORDWRAP,
3617 // Call CMergeDoc RefreshOptions() to refresh *both* views
3618 CMergeDoc *pDoc = GetDocument();
3619 pDoc->RefreshOptions();
3620 pDoc->UpdateAllViews(this);
3625 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3627 pCmdUI->Enable(true);
3628 pCmdUI->SetCheck(m_bWordWrap);
3629 pCmdUI->SetText((GetDocument()->m_ptBuf[0]->GetTableEditing() ?
3630 _("W&rap Text") : _("W&rap Lines")).c_str());
3633 void CMergeEditView::OnViewWhitespace()
3635 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3637 // Call CMergeDoc RefreshOptions() to refresh *both* views
3638 CMergeDoc *pDoc = GetDocument();
3639 pDoc->RefreshOptions();
3642 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3644 pCmdUI->SetCheck(GetViewTabs());
3647 void CMergeEditView::OnViewEOL()
3649 GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
3650 GetDocument()->RefreshOptions();
3653 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI)
3655 pCmdUI->SetCheck(GetViewEols());
3658 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
3660 if (!IsInitialized())
3663 CMergeDoc * pDoc = GetDocument();
3664 if (m_nThisPane < pDoc->m_nBuffers - 1)
3666 // To calculate subline index correctly
3667 // we have to invalidate line cache in all pane before calling the function related the subline.
3668 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3670 CMergeEditView *pView = GetGroupView(nPane);
3671 if (pView != nullptr)
3672 pView->InvalidateScreenRect(false);
3677 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3679 CMergeEditView *pView = GetGroupView(nPane);
3680 if (pView != nullptr)
3681 pView->Invalidate();
3684 // recalculate m_nTopSubLine
3685 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3689 RecalcVertScrollBar (false, false);
3690 RecalcHorzScrollBar (false, false);
3694 * @brief allocates GDI resources for printing
3695 * @param pDC [in] points to the printer device context
3696 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3698 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3700 GetParentFrame()->PostMessage(WM_TIMER);
3702 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3704 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3705 pView->m_bPrintHeader = true;
3706 pView->m_bPrintFooter = true;
3707 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3712 * @brief frees GDI resources for printing
3713 * @param pDC [in] points to the printer device context
3714 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3716 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3718 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3719 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3721 GetParentFrame()->PostMessage(WM_TIMER);
3725 * @brief Gets header text to print
3726 * @param [in] nPageNum the page number to print
3727 * @param [out] header text to print
3729 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3731 text = GetDocument()->GetTitle();
3735 * @brief Prints header
3736 * @param [in] nPageNum the page number to print
3738 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3740 if (m_nThisPane > 0)
3742 int oldRight = m_rcPrintArea.right;
3743 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3744 CGhostTextView::PrintHeader(pdc, nPageNum);
3745 m_rcPrintArea.right = oldRight;
3749 * @brief Prints footer
3750 * @param [in] nPageNum the page number to print
3752 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3754 if (m_nThisPane > 0)
3756 int oldRight = m_rcPrintArea.right;
3757 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3758 CGhostTextView::PrintFooter(pdc, nPageNum);
3759 m_rcPrintArea.right = oldRight;
3762 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3764 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3765 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3769 * @brief Prints or previews both panes.
3770 * @param pDC [in] points to the printer device context
3771 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3773 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
3775 CRect rDraw = pInfo->m_rectDraw;
3776 CSize sz = rDraw.Size();
3777 CMergeDoc *pDoc = GetDocument();
3779 SIZE szLeftTop{}, szRightBottom{};
3780 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3781 pDC->HIMETRICtoLP(&szLeftTop);
3782 pDC->HIMETRICtoLP(&szRightBottom);
3784 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3787 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3789 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3790 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3791 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3792 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3796 bool CMergeEditView::IsInitialized() const
3798 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3799 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3800 return pBuffer->IsInitialized();
3804 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3806 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3808 int nBreaks[3] = {0};
3809 int nMaxBreaks = -1;
3810 CMergeDoc * pDoc = GetDocument();
3811 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3813 CMergeEditView *pView = GetGroupView(nPane);
3814 if (pView != nullptr)
3816 if (nLineIndex >= pView->GetLineCount())
3818 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3820 nMaxBreaks = (std::max)(nMaxBreaks, nBreaks[nPane]);
3823 if (nBreaks[m_nThisPane] < nMaxBreaks)
3824 return nMaxBreaks - nBreaks[m_nThisPane];
3830 * @brief Invalidate sub line index cache from the specified index to the end of file.
3831 * @param [in] nLineIndex Index of the first line to invalidate
3833 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3835 CMergeDoc * pDoc = GetDocument();
3836 ASSERT(pDoc != nullptr);
3838 // We have to invalidate sub line index cache on both panes.
3839 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3841 CMergeEditView *pView = GetGroupView(nPane);
3842 if (pView != nullptr)
3843 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
3847 void CMergeEditView::SetWordWrapping( bool bWordWrap )
3849 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3850 GetGroupView(pane)->m_bWordWrap = bWordWrap;
3851 CCrystalTextView::SetWordWrapping(bWordWrap);
3855 * @brief Determine if difference is visible on screen.
3856 * @param [in] nDiff Number of diff to check.
3857 * @return true if difference is visible.
3859 bool CMergeEditView::IsDiffVisible(int nDiff)
3861 const CMergeDoc *pd = GetDocument();
3864 pd->m_diffList.GetDiff(nDiff, diff);
3866 return IsDiffVisible(diff);
3870 * @brief Determine if difference is visible on screen.
3871 * @param [in] diff diff to check.
3872 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
3873 * @return true if difference is visible, false otherwise.
3875 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
3877 const int nDiffStart = GetSubLineIndex(diff.dbegin);
3878 const int nDiffEnd = GetSubLineIndex(diff.dend);
3879 // Diff's height is last line - first line + last line's line count
3880 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
3882 // If diff first line outside current view - context OR
3883 // if diff last line outside current view - context OR
3884 // if diff is bigger than screen
3885 if ((nDiffStart < m_nTopSubLine) ||
3886 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
3887 (nDiffHeight >= GetScreenLines()))
3897 /** @brief Open help from mainframe when user presses F1*/
3898 void CMergeEditView::OnHelp()
3900 theApp.ShowHelp(MergeViewHelpLocation);
3904 * @brief Called after document is loaded.
3905 * This function is called from CMergeDoc::OpenDocs() after documents are
3906 * loaded. So this is good place to set View's options etc.
3908 void CMergeEditView::DocumentsLoaded()
3910 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
3912 SetTopMargin(GetOptionsMgr()->GetBool(OPT_VIEW_TOPMARGIN_TABLE));
3913 SetLineUsedAsHeaders(GetOptionsMgr()->GetInt(OPT_LINE_NUMBER_USED_AS_HEADERS));
3914 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
3919 SetTopMargin(GetOptionsMgr()->GetBool(OPT_VIEW_TOPMARGIN));
3922 // SetTextType will revert to language dependent defaults for tab
3923 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
3924 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3925 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3926 GetDocument()->IsMixedEOL(m_nThisPane);
3927 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
3928 SetWordWrapping(GetOptionsMgr()->GetBool(GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing() ?
3929 OPT_WORDWRAP_TABLE : OPT_WORDWRAP));
3930 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3931 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3933 // Enable Backspace at beginning of line
3934 SetDisableBSAtSOL(false);
3936 // Set tab type (tabs/spaces)
3937 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
3938 SetInsertTabs(bInsertTabs);
3940 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
3941 // disabled) after docs are open in screen. So lets make sure they are
3942 // really updated, even though this is unnecessary in most cases.
3943 InvalidateHorzScrollBar();
3944 InvalidateVertScrollBar();
3948 * @brief Update LocationView position.
3949 * This function updates LocationView position to given lines.
3950 * Usually we want to lines in file compare view and area in
3951 * LocationView to match. Be extra carefull to not call non-existing
3953 * @param [in] nTopLine Top line of current view.
3954 * @param [in] nBottomLine Bottom line of current view.
3956 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
3957 int nBottomLine /*= -1*/)
3959 CMergeDoc *pDoc = GetDocument();
3960 if (pDoc == nullptr)
3963 CLocationView *pLocationView = pDoc->GetLocationView();
3965 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
3967 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
3972 * @brief Enable/Disable view's selection margins.
3973 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
3974 * useful. But it appears many users don't use/need those features and for them
3975 * selection margins are just wasted screen estate.
3977 void CMergeEditView::OnViewMargin()
3979 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
3980 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
3982 SetSelectionMargin(!bViewMargin);
3983 CMergeDoc *pDoc = GetDocument();
3984 pDoc->RefreshOptions();
3985 pDoc->UpdateAllViews(this);
3989 * @brief Update GUI for Enable/Disable view's selection margin.
3990 * @param [in] pCmdUI Pointer to UI item to update.
3992 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
3994 pCmdUI->Enable(true);
3995 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3999 * @brief Enable/Disable view's top margins.
4001 void CMergeEditView::OnViewTopMargin()
4003 bool bTableEditing = GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing();
4004 bool bViewMargin = GetOptionsMgr()->GetBool(bTableEditing ? OPT_VIEW_TOPMARGIN_TABLE : OPT_VIEW_TOPMARGIN);
4005 GetOptionsMgr()->SaveOption(bTableEditing ? OPT_VIEW_TOPMARGIN_TABLE : OPT_VIEW_TOPMARGIN, !bViewMargin);
4007 SetTopMargin(!bViewMargin);
4008 CMergeDoc *pDoc = GetDocument();
4009 pDoc->RefreshOptions();
4010 pDoc->UpdateAllViews(this);
4014 * @brief Update GUI for Enable/Disable view's top margin.
4015 * @param [in] pCmdUI Pointer to UI item to update.
4017 void CMergeEditView::OnUpdateViewTopMargin(CCmdUI* pCmdUI)
4019 pCmdUI->Enable(true);
4020 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(
4021 GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing() ? OPT_VIEW_TOPMARGIN_TABLE : OPT_VIEW_TOPMARGIN));
4024 void CMergeEditView::OnUseFirstLineAsHeaders()
4026 bool bUseFirstLineAsHeaders = GetOptionsMgr()->GetInt(OPT_LINE_NUMBER_USED_AS_HEADERS) != -1;
4027 GetOptionsMgr()->SaveOption(OPT_LINE_NUMBER_USED_AS_HEADERS, !bUseFirstLineAsHeaders ? 0 : -1);
4029 SetLineUsedAsHeaders(!bUseFirstLineAsHeaders ? 0 : -1);
4030 CMergeDoc *pDoc = GetDocument();
4031 pDoc->RefreshOptions();
4032 pDoc->UpdateAllViews(this);
4035 void CMergeEditView::OnUpdateUseFirstLineAsHeaders(CCmdUI* pCmdUI)
4037 pCmdUI->Enable(true);
4038 pCmdUI->SetCheck(GetOptionsMgr()->GetInt(OPT_LINE_NUMBER_USED_AS_HEADERS) != -1);
4041 void CMergeEditView::OnAutoFitAllColumns()
4047 * @brief Create the "Change Scheme" sub menu.
4048 * @param [in] pCmdUI Pointer to UI item to update.
4050 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4052 // Delete the place holder menu.
4053 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4055 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4057 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4058 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4060 for (int i = ID_COLORSCHEME_FIRST + 1, j = 0; i <= ID_COLORSCHEME_LAST; ++i, ++j)
4062 name = theApp.LoadString(i);
4063 AppendMenu(hSubMenu, MF_STRING | (((j % 22) == 21) ? MF_MENUBREAK : 0), i, name.c_str());
4066 pCmdUI->Enable(true);
4070 * @brief Change the editor's syntax highlighting scheme.
4071 * @param [in] nID Selected color scheme sub menu id.
4073 void CMergeEditView::OnChangeScheme(UINT nID)
4075 CMergeDoc *pDoc = GetDocument();
4076 ASSERT(pDoc != nullptr);
4077 pDoc->SetTextType(nID - ID_COLORSCHEME_FIRST);
4078 pDoc->FlushAndRescan(true);
4082 * @brief Enable all color schemes sub menu items.
4083 * @param [in] pCmdUI Pointer to UI item to update.
4085 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4087 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4088 pCmdUI->SetRadio(bIsCurrentScheme);
4089 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4093 * @brief Called when mouse's wheel is scrolled.
4095 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4097 if ( nFlags == MK_CONTROL )
4099 short amount = zDelta < 0 ? -1: 1;
4102 // no default CCrystalTextView
4103 return CView::OnMouseWheel(nFlags, zDelta, pt);
4106 if (nFlags == MK_SHIFT)
4108 SCROLLINFO si = { sizeof SCROLLINFO };
4109 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4111 VERIFY(GetScrollInfo(SB_HORZ, &si));
4114 si.nPos -= zDelta / 40;
4115 if (si.nPos > si.nMax) si.nPos = si.nMax;
4116 if (si.nPos < si.nMin) si.nPos = si.nMin;
4118 SetScrollInfo(SB_HORZ, &si);
4121 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4123 // no default CCrystalTextView
4124 return CView::OnMouseWheel(nFlags, zDelta, pt);
4127 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4131 * @brief Called when mouse's horizontal wheel is scrolled.
4133 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4135 SCROLLINFO si = { sizeof SCROLLINFO };
4136 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4138 VERIFY(GetScrollInfo(SB_HORZ, &si));
4141 si.nPos += zDelta / 40;
4142 if (si.nPos > si.nMax) si.nPos = si.nMax;
4143 if (si.nPos < si.nMin) si.nPos = si.nMin;
4145 SetScrollInfo(SB_HORZ, &si);
4148 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4150 // no default CCrystalTextView
4151 CView::OnMouseHWheel(nFlags, zDelta, pt);
4155 * @brief Change font size (zoom) in views.
4156 * @param [in] amount Amount of change/zoom, negative number makes
4157 * font smaller, positive number bigger and 0 reset the font size.
4159 void CMergeEditView::ZoomText(short amount)
4164 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4165 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4167 int nOrgPointSize = GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_POINTSIZE);
4168 if (nOrgPointSize == 0)
4169 nOrgPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4172 nPointSize = nOrgPointSize;
4174 nPointSize += amount;
4178 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4180 CMergeDoc *pDoc = GetDocument();
4181 ASSERT(pDoc != nullptr);
4183 if (pDoc != nullptr)
4185 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4187 CMergeEditView *pView = GetGroupView(nPane);
4188 ASSERT(pView != nullptr);
4190 if (pView != nullptr)
4197 GetOptionsMgr()->SaveOption(OPT_VIEW_ZOOM, nPointSize * 1000 / nOrgPointSize);
4200 bool CMergeEditView::QueryEditable()
4202 return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4206 * @brief Adjust the point to remain in the displayed diff
4208 * @return Tells if the point has been changed
4210 bool CMergeEditView::EnsureInDiff(CEPoint& pt)
4212 int nLineCount = GetLineCount();
4213 if (m_lineBegin >= nLineCount)
4214 m_lineBegin = nLineCount - 1;
4215 if (m_lineEnd >= nLineCount)
4216 m_lineEnd = nLineCount - 1;
4218 int diffLength = m_lineEnd - m_lineBegin + 1;
4219 // first get the degenerate case out of the way
4221 if (diffLength == 0)
4223 if (pt.y == m_lineBegin && pt.x == 0)
4231 if (pt.y < m_lineBegin)
4237 // diff is defined and not below diff
4238 if (m_lineEnd > -1 && pt.y > m_lineEnd)
4241 pt.x = GetLineLength(pt.y);
4247 void CMergeEditView::EnsureVisible(CEPoint pt)
4252 // ensure we remain in diff
4253 if (EnsureInDiff(ptNew))
4254 SetCursorPos(ptNew);
4256 CCrystalTextView::EnsureVisible(ptNew);
4259 void CMergeEditView::EnsureVisible(CEPoint ptStart, CEPoint ptEnd)
4261 CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4264 void CMergeEditView::SetSelection(const CEPoint& ptStart, const CEPoint& ptEnd, bool bUpdateView)
4266 CEPoint ptStartNew = ptStart;
4267 CEPoint ptEndNew = ptEnd;
4270 // ensure we remain in diff
4271 EnsureInDiff(ptStartNew);
4272 EnsureInDiff(ptEndNew);
4274 CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4277 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4281 int nLineCount = GetLineCount();
4282 if (m_lineBegin >= nLineCount)
4283 m_lineBegin = nLineCount - 1;
4284 if (m_lineEnd >= nLineCount)
4285 m_lineEnd = nLineCount - 1;
4287 // ensure we remain in diff
4288 int sublineBegin = GetSubLineIndex(m_lineBegin);
4289 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4290 int diffLength = sublineEnd - sublineBegin + 1;
4291 int displayLength = GetScreenLines();
4292 if (diffLength <= displayLength)
4293 nNewTopLine = sublineBegin;
4296 if (nNewTopLine < sublineBegin)
4297 nNewTopLine = sublineBegin;
4298 if (nNewTopLine + displayLength - 1 > sublineEnd)
4299 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4302 CEPoint pt = GetCursorPos();
4303 if (EnsureInDiff(pt))
4306 auto [ptSelStart, ptSelEnd] = GetSelection();
4307 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4308 SetSelection(ptSelStart, ptSelEnd);
4310 CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4313 void CMergeEditView::SetActivePane()
4315 auto* pwndSplitterChild = GetParentSplitter(this, false);
4316 if (!pwndSplitterChild)
4318 if (pwndSplitterChild->GetColumnCount() > 1)
4319 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4321 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4325 * @brief Called when user selects View/Zoom In from menu.
4327 void CMergeEditView::OnViewZoomIn()
4333 * @brief Called when user selects View/Zoom Out from menu.
4335 void CMergeEditView::OnViewZoomOut()
4341 * @brief Called when user selects View/Zoom Normal from menu.
4343 void CMergeEditView::OnViewZoomNormal()
4348 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4350 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4352 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4356 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4359 void CMergeEditView::OnWindowSplit()
4362 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4363 CMergeDoc *pDoc = GetDocument();
4364 int nBuffer = m_nThisPane;
4365 if (pDoc->m_nGroups <= 2)
4367 wndSplitter.SplitRow(1);
4368 wndSplitter.EqualizeRows();
4372 wndSplitter.SetActivePane(0, 0);
4373 wndSplitter.DeleteRow(1);
4374 pDoc->GetView(0, nBuffer)->SetActivePane();
4378 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4380 pCmdUI->Enable(!m_bDetailView);
4381 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4384 void CMergeEditView::OnStatusBarClick(NMHDR* pNMHDR, LRESULT* pResult)
4387 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4388 const int pane = pNMItemActivate->iItem / 4;
4389 CMergeDoc* pDoc = GetDocument();
4390 if (pane >= pDoc->m_nBuffers || !GetParentFrame()->IsChild(CWnd::FromHandle(pNMItemActivate->hdr.hwndFrom)))
4393 switch (pNMItemActivate->iItem % 4)
4396 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4399 pDoc->DoFileEncodingDialog(pane);
4404 ::GetCursorPos(&point);
4407 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4408 theApp.TranslateMenu(menu.m_hMenu);
4409 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4413 pDoc->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());