1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
8 * @file MergeEditView.cpp
10 * @brief Implementation of the CMergeEditView class
14 #include "MergeEditView.h"
18 #include "LocationView.h"
21 #include "OptionsMgr.h"
22 #include "OptionsDiffColors.h"
23 #include "FileTransform.h"
25 #include "WMGotoDlg.h"
26 #include "OptionsDef.h"
27 #include "SyntaxColors.h"
28 #include "MergeEditFrm.h"
29 #include "MergeLineFlags.h"
31 #include "DropHandler.h"
33 #include "ShellContextMenu.h"
40 #ifndef WM_MOUSEHWHEEL
41 # define WM_MOUSEHWHEEL 0x20e
45 using CrystalLineParser::TEXTBLOCK;
47 /** @brief Timer ID for delayed rescan. */
48 const UINT IDT_RESCAN = 2;
49 /** @brief Timer timeout for delayed rescan. */
50 const UINT RESCAN_TIMEOUT = 1000;
52 /** @brief Location for file compare specific help to open. */
53 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
55 /////////////////////////////////////////////////////////////////////////////
58 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
60 CMergeEditView::CMergeEditView()
61 : m_bCurrentLineIsDiff(false)
64 , m_bDetailView(false)
65 , m_piMergeEditStatus(nullptr)
66 , m_bAutomaticRescan(false)
67 , fTimerWaitingForIdle(0)
70 , m_CurrentPredifferID(0)
72 SetParser(&m_xParser);
74 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
77 CMergeEditView::~CMergeEditView()
82 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
83 //{{AFX_MSG_MAP(CMergeEditView)
84 ON_COMMAND(ID_CURDIFF, OnCurdiff)
85 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
86 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
87 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
88 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
89 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
90 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
91 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
92 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
93 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
94 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
95 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
96 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
97 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
98 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
99 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
100 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
101 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
102 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
103 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
104 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
105 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
106 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
107 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
108 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
109 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
110 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
111 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
112 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
113 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
114 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
115 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
116 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
117 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
118 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
119 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
120 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
121 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
122 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
123 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
124 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
125 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
126 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
127 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
128 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
129 ON_WM_LBUTTONDBLCLK()
132 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
133 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
134 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
135 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
136 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
137 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
138 ON_COMMAND(ID_L2R, OnL2r)
139 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
140 ON_COMMAND(ID_R2L, OnR2l)
141 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
142 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
143 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
144 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
145 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
146 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
147 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
148 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
149 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
150 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
151 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
153 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
154 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
155 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
156 ON_COMMAND(ID_REFRESH, OnRefresh)
157 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
158 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
159 ON_UPDATE_COMMAND_UI(ID_SELECTLINEDIFF, OnUpdateSelectLineDiff)
160 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
161 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
162 ON_COMMAND(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnAddToIgnoredSubstitutions<false>)
163 ON_UPDATE_COMMAND_UI(ID_ADD_TO_IGNORED_SUBSTITUTIONS, OnUpdateAddToIgnoredSubstitutions)
165 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
166 ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
167 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
168 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
169 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
170 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
171 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
172 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
173 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
174 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
175 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
176 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
177 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
178 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
179 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
180 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
181 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
182 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
183 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
184 ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
185 ON_COMMAND(ID_NEXT_PANE, OnChangePane)
186 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
187 ON_COMMAND(ID_GOTO_MOVED_LINE_LM, OnGotoMovedLineLM)
188 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_LM, OnUpdateGotoMovedLineLM)
189 ON_COMMAND(ID_GOTO_MOVED_LINE_MR, OnGotoMovedLineMR)
190 ON_UPDATE_COMMAND_UI(ID_GOTO_MOVED_LINE_MR, OnUpdateGotoMovedLineMR)
191 ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
192 ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
193 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
194 ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
195 ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
196 ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
197 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
200 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
201 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
202 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
203 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
204 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
205 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
206 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
207 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
208 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
209 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
210 ON_COMMAND(ID_VIEW_EOL, OnViewEOL)
211 ON_UPDATE_COMMAND_UI(ID_VIEW_EOL, OnUpdateViewEOL)
212 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
213 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
214 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
215 ON_COMMAND(ID_SWAPPANES_SWAP12, OnViewSwapPanes12)
216 ON_COMMAND(ID_SWAPPANES_SWAP23, OnViewSwapPanes23)
217 ON_COMMAND(ID_SWAPPANES_SWAP13, OnViewSwapPanes13)
218 ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
221 ON_COMMAND(ID_HELP, OnHelp)
222 ON_COMMAND(ID_VIEW_SELMARGIN, OnViewMargin)
223 ON_UPDATE_COMMAND_UI(ID_VIEW_SELMARGIN, OnUpdateViewMargin)
224 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
225 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
226 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
229 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
230 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
231 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
232 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
233 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
234 ON_NOTIFY(NM_DBLCLK, AFX_IDW_STATUS_BAR, OnStatusBarDblClick)
239 /////////////////////////////////////////////////////////////////////////////
240 // CMergeEditView diagnostics
243 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
245 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
246 return (CMergeDoc*)m_pDocument;
251 /////////////////////////////////////////////////////////////////////////////
252 // CMergeEditView message handlers
255 * @brief Return text buffer for file in view
257 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
259 return GetDocument()->m_ptBuf[m_nThisPane].get();
263 * @brief Update any resources necessary after a GUI language change
265 void CMergeEditView::UpdateResources()
269 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
271 return GetDocument()->GetView(m_nThisGroup, nBuffer);
274 void CMergeEditView::PrimeListWithFile()
276 // Set the tab size now, just in case the options change...
277 // We don't update it at the end of OnOptions,
278 // we can update it safely now
279 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
282 * @brief Return text from line given
284 CString CMergeEditView::GetLineText(int idx)
286 return GetLineChars(idx);
290 * @brief Return text from selection
292 CString CMergeEditView::GetSelectedText()
294 CPoint ptStart, ptEnd;
296 GetSelection(ptStart, ptEnd);
297 if (ptStart != ptEnd)
298 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
303 * @brief Get diffs inside selection.
304 * @param [out] firstDiff First diff inside selection
305 * @param [out] lastDiff Last diff inside selection
306 * @note -1 is returned in parameters if diffs cannot be determined
307 * @todo This shouldn't be called when there is no diffs, so replace
308 * first 'if' with ASSERT()?
310 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff)
315 CMergeDoc *pd = GetDocument();
316 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
320 int firstLine, lastLine;
321 GetFullySelectedLines(firstLine, lastLine);
322 if (lastLine < firstLine)
325 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
326 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
327 if (firstDiff != -1 && lastDiff != -1)
331 // Check that first selected line is first diff's first line or above it
332 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
333 if ((int)di.dbegin < firstLine)
335 if (firstDiff < lastDiff)
339 // Check that last selected line is last diff's last line or below it
340 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
341 if ((int)di.dend > lastLine)
343 if (firstDiff < lastDiff)
347 // Special case: one-line diff is not selected if cursor is in it
348 if (firstLine == lastLine)
357 * @brief Get diffs inside selection.
358 * @param [out] firstDiff First diff inside selection
359 * @param [out] lastDiff Last diff inside selection
360 * @param [out] firstWordDiff First word level diff inside selection
361 * @param [out] lastWordDiff Last word level diff inside selection
362 * @note -1 is returned in parameters if diffs cannot be determined
363 * @todo This shouldn't be called when there is no diffs, so replace
364 * first 'if' with ASSERT()?
366 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
373 CMergeDoc *pd = GetDocument();
374 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
378 int firstLine, lastLine;
379 CPoint ptStart, ptEnd;
380 GetSelection(ptStart, ptEnd);
381 if (pptStart != nullptr)
383 if (pptEnd != nullptr)
385 firstLine = ptStart.y;
388 firstDiff = pd->m_diffList.LineToDiff(firstLine);
389 bool firstLineIsNotInDiff = firstDiff == -1;
392 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
397 lastDiff = pd->m_diffList.LineToDiff(lastLine);
398 bool lastLineIsNotInDiff = lastDiff == -1;
400 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
401 if (lastDiff < firstDiff)
408 if (firstDiff != -1 && lastDiff != -1)
412 if (pd->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
414 firstWordDiff = lastWordDiff = static_cast<int>(pd->GetCurrentWordDiff().nWordDiff);
416 else if (ptStart != ptEnd)
418 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
419 if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
425 if (firstWordDiff == -1)
427 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
428 for (size_t i = 0; i < worddiffs.size(); ++i)
430 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
431 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
432 (firstLine == worddiffs[i].endline[m_nThisPane] &&
433 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
435 firstWordDiff = static_cast<int>(i);
440 if (firstLine >= di.dbegin && firstWordDiff == -1)
447 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
448 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
449 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
451 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
452 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
454 lastWordDiff = static_cast<int>(i);
459 if (lastLine <= di.dend && lastWordDiff == -1)
462 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
469 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
486 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
487 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
488 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
491 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
493 CMergeDoc *pDoc = GetDocument();
494 std::map<int, std::vector<int>> ret;
495 std::map<int, std::vector<int> *> list;
496 CPoint ptStart, ptEnd;
497 GetSelection(ptStart, ptEnd);
498 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
500 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
502 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
504 GetColumnSelection(nLine, nLeft, nRight);
505 CPoint ptStart2, ptEnd2;
508 ptStart2.y = ptEnd2.y = nLine;
509 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
510 if (firstDiff != -1 && lastDiff != -1)
512 std::vector<int> *pWordDiffs;
513 if (list.find(firstDiff) == list.end())
514 list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
515 pWordDiffs = list[firstDiff];
516 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
518 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
519 pWordDiffs->push_back(i);
524 for (auto& it : list)
525 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
529 void CMergeEditView::OnInitialUpdate()
532 CCrystalEditViewEx::OnInitialUpdate();
534 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
535 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
541 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
543 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
545 CMergeDoc* pDoc = GetDocument();
546 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
549 std::vector<CrystalLineParser::TEXTBLOCK> CMergeEditView::GetMarkerTextBlocks(int nLineIndex) const
553 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
554 return std::vector<CrystalLineParser::TEXTBLOCK>();
556 return CCrystalTextView::GetMarkerTextBlocks(nLineIndex);
559 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
561 static const std::vector<TEXTBLOCK> emptyBlocks;
564 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
568 DWORD dwLineFlags = GetLineFlags(nLineIndex);
569 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
572 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
575 CMergeDoc *pDoc = GetDocument();
576 if (pDoc->IsEditedAfterRescan(m_nThisPane))
579 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
584 pDoc->m_diffList.GetDiff(nDiff, cd);
585 int unemptyLineCount = 0;
586 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
588 if (cd.begin[nPane] != cd.end[nPane] + 1)
591 if (unemptyLineCount < 2)
594 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
595 size_t nWordDiffs = worddiffs.size();
597 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
599 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
600 blocks[0].m_nCharPos = 0;
601 blocks[0].m_nColorIndex = COLORINDEX_NONE;
602 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
604 for (i = 0, j = 1; i < nWordDiffs; i++)
606 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
608 if (pDoc->m_nBuffers > 2)
610 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
612 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
615 int begin[3], end[3];
616 bool deleted = false;
617 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
619 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
620 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
621 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
622 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
625 blocks[j].m_nCharPos = begin[m_nThisPane];
626 if (lineInCurrentDiff)
628 if (m_cachedColors.clrSelDiffText != CLR_NONE)
629 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
631 blocks[j].m_nColorIndex = COLORINDEX_NONE;
632 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
633 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
637 if (m_cachedColors.clrDiffText != CLR_NONE)
638 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
640 blocks[j].m_nColorIndex = COLORINDEX_NONE;
641 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
642 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
645 blocks[j].m_nCharPos = end[m_nThisPane];
646 blocks[j].m_nColorIndex = COLORINDEX_NONE;
647 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
656 COLORREF CMergeEditView::GetColor(int nColorIndex)
658 switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
660 case COLORINDEX_HIGHLIGHTBKGND1:
661 return m_cachedColors.clrSelWordDiff;
662 case COLORINDEX_HIGHLIGHTTEXT1:
663 return m_cachedColors.clrSelWordDiffText;
664 case COLORINDEX_HIGHLIGHTBKGND2:
665 return m_cachedColors.clrWordDiff;
666 case COLORINDEX_HIGHLIGHTTEXT2:
667 return m_cachedColors.clrWordDiffText;
668 case COLORINDEX_HIGHLIGHTBKGND3:
669 return m_cachedColors.clrWordDiffDeleted;
670 case COLORINDEX_HIGHLIGHTBKGND4:
671 return m_cachedColors.clrSelWordDiffDeleted;
674 return CCrystalTextView::GetColor(nColorIndex);
679 * @brief Determine text and background color for line
680 * @param [in] nLineIndex Index of line in view (NOT line in file)
681 * @param [out] crBkgnd Backround color for line
682 * @param [out] crText Text color for line
684 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
685 COLORREF & crText, bool & bDrawWhitespace)
687 DWORD ignoreFlags = 0;
688 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
692 * @brief Determine text and background color for line
693 * @param [in] nLineIndex Index of line in view (NOT line in file)
694 * @param [in] ignoreFlags Flags that caller wishes ignored
695 * @param [out] crBkgnd Backround color for line
696 * @param [out] crText Text color for line
698 * This version allows caller to suppress particular flags
700 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
701 COLORREF & crText, bool & bDrawWhitespace)
703 if (GetLineCount() <= nLineIndex)
706 DWORD dwLineFlags = GetLineFlags(nLineIndex);
708 if (dwLineFlags & ignoreFlags)
709 dwLineFlags &= (~ignoreFlags);
713 // Line with WinMerge flag,
714 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
715 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
717 crText = m_cachedColors.clrDiffText;
718 bDrawWhitespace = true;
720 if (dwLineFlags & LF_GHOST)
722 crBkgnd = m_cachedColors.clrDiffDeleted;
727 // If no syntax hilighting
728 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
730 crBkgnd = GetColor (COLORINDEX_BKGND);
731 crText = GetColor (COLORINDEX_NORMALTEXT);
732 bDrawWhitespace = false;
735 // Line not inside diff, get colors from CrystalEditor
736 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
737 crText, bDrawWhitespace);
739 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
741 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
742 crText = GetColor (COLORINDEX_WHITESPACE);
743 bDrawWhitespace = false;
749 if (dwLineFlags & LF_WINMERGE_FLAGS)
751 crText = m_cachedColors.clrDiffText;
752 bDrawWhitespace = true;
753 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
755 if (dwLineFlags & LF_SNP)
757 if (lineInCurrentDiff)
759 if (dwLineFlags & LF_GHOST)
760 crBkgnd = m_cachedColors.clrSelSNPDeleted;
762 crBkgnd = m_cachedColors.clrSelSNP;
763 crText = m_cachedColors.clrSelSNPText;
767 if (dwLineFlags & LF_GHOST)
768 crBkgnd = m_cachedColors.clrSNPDeleted;
770 crBkgnd = m_cachedColors.clrSNP;
771 crText = m_cachedColors.clrSNPText;
775 else if (dwLineFlags & LF_DIFF)
777 if (lineInCurrentDiff)
779 if (dwLineFlags & LF_MOVED)
781 crBkgnd = m_cachedColors.clrSelMoved;
782 crText = m_cachedColors.clrSelMovedText;
786 crBkgnd = m_cachedColors.clrSelDiff;
787 crText = m_cachedColors.clrSelDiffText;
793 if (dwLineFlags & LF_MOVED)
795 crBkgnd = m_cachedColors.clrMoved;
796 crText = m_cachedColors.clrMovedText;
800 crBkgnd = m_cachedColors.clrDiff;
801 crText = m_cachedColors.clrDiffText;
806 else if (dwLineFlags & LF_TRIVIAL)
808 // trivial diff can not be selected
809 if (dwLineFlags & LF_GHOST)
810 // ghost lines in trivial diff has their own color
811 crBkgnd = m_cachedColors.clrTrivialDeleted;
813 crBkgnd = m_cachedColors.clrTrivial;
814 crText = m_cachedColors.clrTrivialText;
817 else if (dwLineFlags & LF_GHOST)
819 if (lineInCurrentDiff)
821 if (dwLineFlags & LF_MOVED)
822 crBkgnd = m_cachedColors.clrSelMovedDeleted;
824 crBkgnd = m_cachedColors.clrSelDiffDeleted;
828 if (dwLineFlags & LF_MOVED)
829 crBkgnd = m_cachedColors.clrMovedDeleted;
831 crBkgnd = m_cachedColors.clrDiffDeleted;
838 // Line not inside diff,
839 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
841 // If no syntax hilighting, get windows default colors
842 crBkgnd = GetColor (COLORINDEX_BKGND);
843 crText = GetColor (COLORINDEX_NORMALTEXT);
844 bDrawWhitespace = false;
847 // Syntax highlighting, get colors from CrystalEditor
848 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
849 crText, bDrawWhitespace);
854 * @brief Sync other pane position
856 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
858 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
859 if (pSplitterWnd != nullptr)
861 // See CSplitterWnd::IdFromRowCol() implementation for details
862 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
863 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
864 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
865 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
867 // limit the TopLine : must be smaller than GetLineCount for all the panels
868 int newTopSubLine = m_nTopSubLine;
869 int nRows = pSplitterWnd->GetRowCount ();
870 int nCols = pSplitterWnd->GetColumnCount ();
872 // for (nRow = 0; nRow < nRows; nRow++)
874 // for (int nCol = 0; nCol < nCols; nCol++)
876 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
877 // if (pSiblingView != nullptr)
878 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
879 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
882 if (m_nTopSubLine != newTopSubLine)
883 ScrollToSubLine(newTopSubLine);
885 for (nRow = 0; nRow < nRows; nRow++)
887 for (int nCol = 0; nCol < nCols; nCol++)
889 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
891 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
892 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
893 pSiblingView->OnUpdateSibling (this, bHorz);
901 * @brief Update other panes
903 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
905 if (pUpdateSource != this)
907 ASSERT (pUpdateSource != nullptr);
908 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
909 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
910 if (!bHorz) // changed this so bHorz works right
912 ASSERT (pSrcView->m_nTopSubLine >= 0);
914 // This ASSERT is wrong: panes have different files and
915 // different linecounts
916 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
917 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
919 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
921 RecalcVertScrollBar(true);
922 RecalcHorzScrollBar();
927 ASSERT (pSrcView->m_nOffsetChar >= 0);
929 // This ASSERT is wrong: panes have different files and
930 // different linelengths
931 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
932 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
934 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
936 RecalcHorzScrollBar(true);
937 RecalcHorzScrollBar();
943 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
945 int newlineBegin, newlineEnd;
946 CMergeDoc *pd = GetDocument();
947 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
955 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
957 newlineBegin = curDiff.dbegin;
958 ASSERT (newlineBegin >= 0);
959 newlineEnd = curDiff.dend;
962 m_lineBegin = newlineBegin;
963 m_lineEnd = newlineEnd;
965 int nLineCount = GetLineCount();
966 if (m_lineBegin > nLineCount)
967 m_lineBegin = nLineCount - 1;
968 if (m_lineEnd > nLineCount)
969 m_lineEnd = nLineCount - 1;
971 if (m_nTopLine == newlineBegin)
974 // scroll to the first line of the diff
975 vector<WordDiff> worddiffs;
976 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
977 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
978 CPoint pt = worddiffs.size() > 0 ?
979 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
980 CPoint{ 0, m_lineBegin };
983 // update the width of the horizontal scrollbar
984 RecalcHorzScrollBar();
988 * @brief Selects diff by number and syncs other file
989 * @param [in] nDiff Diff to select, must be >= 0
990 * @param [in] bScroll Scroll diff to view
991 * @param [in] bSelectText Select diff text
992 * @sa CMergeEditView::ShowDiff()
993 * @sa CMergeDoc::SetCurrentDiff()
994 * @todo Parameter bSelectText is never used?
996 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
998 CMergeDoc *pd = GetDocument();
1000 // Check that nDiff is valid
1002 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
1003 if (nDiff >= pd->m_diffList.GetSize())
1004 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
1005 nDiff, pd->m_diffList.GetSize());
1008 pd->SetCurrentDiff(nDiff);
1009 ShowDiff(bScroll, bSelectText);
1010 pd->UpdateAllViews(this);
1011 UpdateSiblingScrollPos(false);
1013 // notify either side, as it will notify the other one
1014 pd->ForEachView ([&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
1017 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
1019 CMergeDoc *pd = GetDocument();
1020 // If we have a selected diff, deselect it
1021 int nCurrentDiff = pd->GetCurrentDiff();
1022 if (nCurrentDiff != -1)
1024 CPoint pos = GetCursorPos();
1025 if (!IsLineInCurrentDiff(pos.y))
1027 pd->SetCurrentDiff(-1);
1029 pd->UpdateAllViews(this);
1035 * @brief Called when user selects "Current Difference".
1036 * Goes to active diff. If no active diff, selects diff under cursor
1037 * @sa CMergeEditView::SelectDiff()
1038 * @sa CMergeDoc::GetCurrentDiff()
1039 * @sa CMergeDoc::LineToDiff()
1041 void CMergeEditView::OnCurdiff()
1043 CMergeDoc *pd = GetDocument();
1045 // If no diffs, nothing to select
1046 if (!pd->m_diffList.HasSignificantDiffs())
1049 // GetCurrentDiff() returns -1 if no diff selected
1050 int nDiff = pd->GetCurrentDiff();
1053 // Scroll to the first line of the currently selected diff
1054 SelectDiff(nDiff, true, false);
1058 // If cursor is inside diff, select that diff
1059 CPoint pos = GetCursorPos();
1060 nDiff = pd->m_diffList.LineToDiff(pos.y);
1061 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
1062 SelectDiff(nDiff, true, false);
1067 * @brief Called when "Current diff" item is updated
1069 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1071 CMergeDoc *pd = GetDocument();
1072 CPoint pos = GetCursorPos();
1073 int nCurrentDiff = pd->GetCurrentDiff();
1074 if (nCurrentDiff == -1)
1076 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1077 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1080 pCmdUI->Enable(true);
1084 * @brief Copy selected text to clipboard
1086 void CMergeEditView::OnEditCopy()
1088 CMergeDoc * pDoc = GetDocument();
1089 CPoint ptSelStart, ptSelEnd;
1090 GetSelection(ptSelStart, ptSelEnd);
1093 if (ptSelStart == ptSelEnd)
1098 if (!m_bRectangularSelection)
1100 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1102 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1103 ptSelEnd.y, ptSelEnd.x, text);
1106 GetTextWithoutEmptysInColumnSelection(text);
1108 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1112 * @brief Called when "Copy" item is updated
1114 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1116 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1120 * @brief Cut current selection to clipboard
1122 void CMergeEditView::OnEditCut()
1124 if (!QueryEditable())
1127 CPoint ptSelStart, ptSelEnd;
1128 CMergeDoc * pDoc = GetDocument();
1129 GetSelection(ptSelStart, ptSelEnd);
1132 if (ptSelStart == ptSelEnd)
1136 if (!m_bRectangularSelection)
1137 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1138 ptSelEnd.y, ptSelEnd.x, text);
1140 GetTextWithoutEmptysInColumnSelection(text);
1142 PutToClipboard(text, text.GetLength(), m_bRectangularSelection);
1144 if (!m_bRectangularSelection)
1146 CPoint ptCursorPos = ptSelStart;
1147 ASSERT_VALIDTEXTPOS(ptCursorPos);
1148 SetAnchor(ptCursorPos);
1149 SetSelection(ptCursorPos, ptCursorPos);
1150 SetCursorPos(ptCursorPos);
1151 EnsureVisible(ptCursorPos);
1153 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1154 ptSelEnd.x, CE_ACTION_CUT);
1157 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1159 m_pTextBuffer->SetModified(true);
1163 * @brief Called when "Cut" item is updated
1165 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1167 if (QueryEditable())
1168 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1170 pCmdUI->Enable(false);
1174 * @brief Paste text from clipboard
1176 void CMergeEditView::OnEditPaste()
1178 if (!QueryEditable())
1181 CCrystalEditViewEx::Paste();
1182 m_pTextBuffer->SetModified(true);
1186 * @brief Called when "Paste" item is updated
1188 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1190 if (QueryEditable())
1191 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1193 pCmdUI->Enable(false);
1197 * @brief Undo last action
1199 void CMergeEditView::OnEditUndo()
1201 CWaitCursor waitstatus;
1202 CMergeDoc* pDoc = GetDocument();
1203 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1206 if (!QueryEditable())
1209 GetParentFrame()->SetActiveView(this, true);
1210 if(CCrystalEditViewEx::DoEditUndo())
1213 pDoc->UpdateHeaderPath(m_nThisPane);
1214 pDoc->FlushAndRescan();
1217 m_pTextBuffer->GetRedoActionCode(nAction);
1218 if (nAction == CE_ACTION_MERGE)
1219 // select the diff so we may just merge it again
1225 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1227 if (!pDoc->CanUndo())
1228 pDoc->SetAutoMerged(false);
1232 * @brief Called when "Undo" item is updated
1234 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1236 CMergeDoc* pDoc = GetDocument();
1237 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1239 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1240 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1243 pCmdUI->Enable(false);
1247 * @brief Go to first diff
1249 * Called when user selects "First Difference"
1250 * @sa CMergeEditView::SelectDiff()
1252 void CMergeEditView::OnFirstdiff()
1254 CMergeDoc *pd = GetDocument();
1255 if (pd->m_diffList.HasSignificantDiffs())
1257 int nDiff = pd->m_diffList.FirstSignificantDiff();
1258 SelectDiff(nDiff, true, false);
1263 * @brief Update "First diff" UI items
1265 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1267 OnUpdatePrevdiff(pCmdUI);
1271 * @brief Go to last diff
1273 void CMergeEditView::OnLastdiff()
1275 CMergeDoc *pd = GetDocument();
1276 if (pd->m_diffList.HasSignificantDiffs())
1278 int nDiff = pd->m_diffList.LastSignificantDiff();
1279 SelectDiff(nDiff, true, false);
1284 * @brief Update "Last diff" UI items
1286 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1288 OnUpdateNextdiff(pCmdUI);
1292 * @brief Go to next diff and select it.
1294 * Finds and selects next difference. There are several cases:
1295 * - if there is selected difference, and that difference is visible
1296 * on screen, next found difference is selected.
1297 * - if there is selected difference but it is not visible, next
1298 * difference from cursor position is selected. This is what user
1299 * expects to happen and is natural thing to do. Also reduces
1300 * needless scrolling.
1301 * - if there is no selected difference, next difference from cursor
1302 * position is selected.
1304 void CMergeEditView::OnNextdiff()
1306 CMergeDoc *pd = GetDocument();
1307 int cnt = pd->m_ptBuf[0]->GetLineCount();
1311 // Returns -1 if no diff selected
1313 int curDiff = pd->GetCurrentDiff();
1317 if (!IsDiffVisible(curDiff))
1319 // Selected difference not visible, select next from cursor
1320 int line = GetCursorPos().y;
1321 // Make sure we aren't in the first line of the diff
1323 if (!IsValidTextPosY(CPoint(0, line)))
1325 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1329 // Find out if there is a following significant diff
1330 if (curDiff < pd->m_diffList.GetSize() - 1)
1332 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1338 // We don't have a selected difference,
1339 // but cursor can be inside inactive diff
1340 int line = GetCursorPos().y;
1341 if (!IsValidTextPosY(CPoint(0, line)))
1343 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1346 int lastDiff = pd->m_diffList.LastSignificantDiff();
1347 if (nextDiff >= 0 && nextDiff <= lastDiff)
1348 SelectDiff(nextDiff, true, false);
1349 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1351 if (pDirDoc->MoveableToNextDiff())
1352 pDirDoc->MoveToNextDiff(pd);
1357 * @brief Update "Next diff" UI items
1359 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1361 CMergeDoc *pd = GetDocument();
1362 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1367 // There aren't any significant differences
1372 // Enable if the beginning of the last significant difference is after caret
1373 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1376 if (!enabled && pd->GetDirDoc())
1377 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1379 pCmdUI->Enable(enabled);
1383 * @brief Go to previous diff and select it.
1385 * Finds and selects previous difference. There are several cases:
1386 * - if there is selected difference, and that difference is visible
1387 * on screen, previous found difference is selected.
1388 * - if there is selected difference but it is not visible, previous
1389 * difference from cursor position is selected. This is what user
1390 * expects to happen and is natural thing to do. Also reduces
1391 * needless scrolling.
1392 * - if there is no selected difference, previous difference from cursor
1393 * position is selected.
1395 void CMergeEditView::OnPrevdiff()
1397 CMergeDoc *pd = GetDocument();
1398 int cnt = pd->m_ptBuf[0]->GetLineCount();
1402 // GetCurrentDiff() returns -1 if no diff selected
1404 int curDiff = pd->GetCurrentDiff();
1408 if (!IsDiffVisible(curDiff))
1410 // Selected difference not visible, select previous from cursor
1411 int line = GetCursorPos().y;
1412 // Make sure we aren't in the last line of the diff
1414 if (!IsValidTextPosY(CPoint(0, line)))
1416 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1420 // Find out if there is a preceding significant diff
1423 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1429 // We don't have a selected difference,
1430 // but cursor can be inside inactive diff
1431 int line = GetCursorPos().y;
1432 if (!IsValidTextPosY(CPoint(0, line)))
1434 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1437 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1438 if (prevDiff >= 0 && prevDiff >= firstDiff)
1439 SelectDiff(prevDiff, true, false);
1440 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1442 if (pDirDoc->MoveableToPrevDiff())
1443 pDirDoc->MoveToPrevDiff(pd);
1448 * @brief Update "Previous diff" UI items
1450 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1452 CMergeDoc *pd = GetDocument();
1453 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1458 // There aren't any significant differences
1463 // Enable if the end of the first significant difference is before caret
1464 enabled = (GetCursorPos().y > (long)dfi->dend);
1467 if (!enabled && pd->GetDirDoc())
1468 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1470 pCmdUI->Enable(enabled);
1473 void CMergeEditView::OnNextConflict()
1475 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1479 * @brief Update "Next Conflict" UI items
1481 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1483 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1486 void CMergeEditView::OnPrevConflict()
1488 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1492 * @brief Update "Prev Conflict" UI items
1494 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1496 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1500 * @brief Go to next 3-way diff and select it.
1502 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1504 CMergeDoc *pd = GetDocument();
1505 int cnt = pd->m_ptBuf[0]->GetLineCount();
1509 // Returns -1 if no diff selected
1510 int curDiff = pd->GetCurrentDiff();
1514 int nextDiff = curDiff;
1515 if (!IsDiffVisible(curDiff))
1517 // Selected difference not visible, select next from cursor
1518 int line = GetCursorPos().y;
1519 // Make sure we aren't in the first line of the diff
1521 if (!IsValidTextPosY(CPoint(0, line)))
1523 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1527 // Find out if there is a following significant diff
1528 if (curDiff < pd->m_diffList.GetSize() - 1)
1530 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1536 // nextDiff is the next one if there is one, else it is the one we're on
1537 SelectDiff(nextDiff, true, false);
1541 // We don't have a selected difference,
1542 // but cursor can be inside inactive diff
1543 int line = GetCursorPos().y;
1544 if (!IsValidTextPosY(CPoint(0, line)))
1546 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1548 SelectDiff(curDiff, true, false);
1553 * @brief Update "Next 3-way diff" UI items
1555 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1557 CMergeDoc *pd = GetDocument();
1559 if (pd->m_nBuffers < 3)
1561 pCmdUI->Enable(false);
1565 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1569 // There aren't any significant differences
1570 pCmdUI->Enable(false);
1574 // Enable if the beginning of the last significant difference is after caret
1575 CPoint pos = GetCursorPos();
1576 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1581 * @brief Go to previous 3-way diff and select it.
1583 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1585 CMergeDoc *pd = GetDocument();
1587 int cnt = pd->m_ptBuf[0]->GetLineCount();
1591 // GetCurrentDiff() returns -1 if no diff selected
1592 int curDiff = pd->GetCurrentDiff();
1596 int prevDiff = curDiff;
1597 if (!IsDiffVisible(curDiff))
1599 // Selected difference not visible, select previous from cursor
1600 int line = GetCursorPos().y;
1601 // Make sure we aren't in the last line of the diff
1603 if (!IsValidTextPosY(CPoint(0, line)))
1605 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1609 // Find out if there is a preceding significant diff
1612 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1618 // prevDiff is the preceding one if there is one, else it is the one we're on
1619 SelectDiff(prevDiff, true, false);
1623 // We don't have a selected difference,
1624 // but cursor can be inside inactive diff
1625 int line = GetCursorPos().y;
1626 if (!IsValidTextPosY(CPoint(0, line)))
1628 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1630 SelectDiff(curDiff, true, false);
1635 * @brief Update "Previous diff X and Y" UI items
1637 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1639 CMergeDoc *pd = GetDocument();
1641 if (pd->m_nBuffers < 3)
1643 pCmdUI->Enable(false);
1647 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1651 // There aren't any significant differences
1652 pCmdUI->Enable(false);
1656 // Enable if the end of the first significant difference is before caret
1657 CPoint pos = GetCursorPos();
1658 pCmdUI->Enable(pos.y > (long)dfi->dend);
1662 void CMergeEditView::OnNextdiffLM()
1664 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1667 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1669 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1672 void CMergeEditView::OnNextdiffLR()
1674 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1677 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1679 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1682 void CMergeEditView::OnNextdiffMR()
1684 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1687 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1689 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1692 void CMergeEditView::OnNextdiffLO()
1694 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1697 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1699 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1702 void CMergeEditView::OnNextdiffMO()
1704 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1707 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1709 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1712 void CMergeEditView::OnNextdiffRO()
1714 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1717 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1719 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1722 void CMergeEditView::OnPrevdiffLM()
1724 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1727 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1729 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1732 void CMergeEditView::OnPrevdiffLR()
1734 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1737 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1739 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1742 void CMergeEditView::OnPrevdiffMR()
1744 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1747 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1749 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1752 void CMergeEditView::OnPrevdiffLO()
1754 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1757 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1759 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1762 void CMergeEditView::OnPrevdiffMO()
1764 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1767 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1769 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1772 void CMergeEditView::OnPrevdiffRO()
1774 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1777 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1779 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1783 * @brief Clear selection
1785 void CMergeEditView::SelectNone()
1787 SetSelection (GetCursorPos(), GetCursorPos());
1792 * @brief Check if line is inside currently selected diff
1793 * @param [in] nLine 0-based linenumber in view
1794 * @sa CMergeDoc::GetCurrentDiff()
1795 * @sa CMergeDoc::LineInDiff()
1797 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1799 // Check validity of nLine
1802 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1803 int nLineCount = LocateTextBuffer()->GetLineCount();
1804 if (nLine >= nLineCount)
1805 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1808 const CMergeDoc *pd = GetDocument();
1809 int curDiff = pd->GetCurrentDiff();
1812 return pd->m_diffList.LineInDiff(nLine, curDiff);
1816 * @brief Called when mouse left-button double-clicked
1818 * Double-clicking mouse inside diff selects that diff
1820 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1822 CMergeDoc *pd = GetDocument();
1823 CPoint pos = GetCursorPos();
1825 int diff = pd->m_diffList.LineToDiff(pos.y);
1826 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1827 SelectDiff(diff, false, false);
1829 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1833 * @brief Called when mouse left button is released.
1835 * If button is released outside diffs, current diff
1838 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1840 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1841 DeselectDiffIfCursorNotInCurrentDiff();
1845 * @brief Called when mouse right button is pressed.
1847 * If right button is pressed outside diffs, current diff
1850 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1852 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1853 DeselectDiffIfCursorNotInCurrentDiff();
1856 void CMergeEditView::OnX2Y(int srcPane, int dstPane)
1858 // Check that right side is not readonly
1859 if (IsReadOnly(dstPane))
1862 CMergeDoc *pDoc = GetDocument();
1863 int currentDiff = pDoc->GetCurrentDiff();
1865 if (currentDiff == -1)
1868 // If cursor is inside diff get number of that diff
1869 if (m_bCurrentLineIsDiff)
1871 CPoint pt = GetCursorPos();
1872 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1876 CPoint ptStart, ptEnd;
1877 GetSelection(ptStart, ptEnd);
1878 if (IsSelection() || pDoc->EqualCurrentWordDiff(srcPane, ptStart, ptEnd))
1880 if (!m_bRectangularSelection)
1882 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1883 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1884 if (firstDiff != -1 && lastDiff != -1)
1886 CWaitCursor waitstatus;
1888 // Setting CopyFullLine (OPT_COPY_FULL_LINE)
1889 // restore old copy behaviour (always copy "full line" instead of "selected text only"), with a hidden option
1890 if (GetOptionsMgr()->GetBool(OPT_COPY_FULL_LINE))
1892 // old behaviour: copy full line
1893 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff);
1897 // new behaviour: copy selected text only
1898 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1904 CWaitCursor waitstatus;
1905 auto wordDiffs = GetColumnSelectedWordDiffIndice();
1907 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1908 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
1913 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1915 CWaitCursor waitstatus;
1916 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1920 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
1922 // Check that right side is not readonly
1923 if (!IsReadOnly(dstPane))
1925 // If one or more diffs inside selection OR
1926 // there is an active diff OR
1927 // cursor is inside diff
1928 CPoint ptStart, ptEnd;
1929 GetSelection(ptStart, ptEnd);
1930 if (IsSelection() || GetDocument()->EqualCurrentWordDiff(m_nThisPane, ptStart, ptEnd))
1932 if (m_bCurrentLineIsDiff || (m_pTextBuffer->GetLineFlags(m_ptSelStart.y) & LF_NONTRIVIAL_DIFF) != 0)
1934 pCmdUI->Enable(true);
1938 int firstDiff, lastDiff;
1939 GetFullySelectedDiffs(firstDiff, lastDiff);
1941 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1 && (lastDiff >= firstDiff));
1946 const int currDiff = GetDocument()->GetCurrentDiff();
1947 pCmdUI->Enable(m_bCurrentLineIsDiff || (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff)));
1951 pCmdUI->Enable(false);
1955 * @brief Copy diff from left pane to right pane
1957 * Difference is copied from left to right when
1958 * - difference is selected
1959 * - difference is inside selection (allows merging multiple differences).
1960 * - cursor is inside diff
1962 * If there is selected diff outside selection, we copy selected
1965 void CMergeEditView::OnL2r()
1967 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1968 int srcPane = dstPane - 1;
1969 OnX2Y(srcPane, dstPane);
1973 * @brief Called when "Copy to left" item is updated
1975 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
1977 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
1981 * @brief Copy diff from right pane to left pane
1983 * Difference is copied from left to right when
1984 * - difference is selected
1985 * - difference is inside selection (allows merging multiple differences).
1986 * - cursor is inside diff
1988 * If there is selected diff outside selection, we copy selected
1991 void CMergeEditView::OnR2l()
1993 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
1994 int srcPane = dstPane + 1;
1995 OnX2Y(srcPane, dstPane);
1999 * @brief Called when "Copy to right" item is updated
2001 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
2003 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
2006 void CMergeEditView::OnCopyFromLeft()
2008 int dstPane = m_nThisPane;
2009 int srcPane = dstPane - 1;
2012 OnX2Y(srcPane, dstPane);
2015 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
2017 int dstPane = m_nThisPane;
2018 int srcPane = dstPane - 1;
2020 pCmdUI->Enable(false);
2022 OnUpdateX2Y(dstPane, pCmdUI);
2025 void CMergeEditView::OnCopyFromRight()
2027 int dstPane = m_nThisPane;
2028 int srcPane = dstPane + 1;
2029 if (srcPane >= GetDocument()->m_nBuffers)
2031 OnX2Y(srcPane, dstPane);
2034 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
2036 int dstPane = m_nThisPane;
2037 int srcPane = dstPane + 1;
2038 if (srcPane >= GetDocument()->m_nBuffers)
2039 pCmdUI->Enable(false);
2041 OnUpdateX2Y(dstPane, pCmdUI);
2045 * @brief Copy all diffs from right pane to left pane
2047 void CMergeEditView::OnAllLeft()
2049 // Check that left side is not readonly
2050 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
2051 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2052 if (IsReadOnly(dstPane))
2054 CWaitCursor waitstatus;
2056 GetDocument()->CopyAllList(srcPane, dstPane);
2060 * @brief Called when "Copy all to left" item is updated
2062 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
2064 // Check that left side is not readonly
2065 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
2066 if (!IsReadOnly(dstPane))
2067 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2069 pCmdUI->Enable(false);
2073 * @brief Copy all diffs from left pane to right pane
2075 void CMergeEditView::OnAllRight()
2077 // Check that right side is not readonly
2078 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
2079 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2080 if (IsReadOnly(dstPane))
2083 CWaitCursor waitstatus;
2085 GetDocument()->CopyAllList(srcPane, dstPane);
2089 * @brief Called when "Copy all to right" item is updated
2091 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2093 // Check that right side is not readonly
2094 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2095 if (!IsReadOnly(dstPane))
2096 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2098 pCmdUI->Enable(false);
2102 * @brief Do Auto merge
2104 void CMergeEditView::OnAutoMerge()
2106 // Check current pane is not readonly
2107 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || !QueryEditable())
2110 CWaitCursor waitstatus;
2112 GetDocument()->DoAutoMerge(m_nThisPane);
2116 * @brief Called when "Auto Merge" item is updated
2118 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2120 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2121 !GetDocument()->IsModified() &&
2122 !GetDocument()->GetAutoMerged() &&
2127 * @brief Add synchronization point
2129 void CMergeEditView::OnAddSyncPoint()
2131 GetDocument()->AddSyncPoint();
2135 * @brief Clear synchronization points
2137 void CMergeEditView::OnClearSyncPoints()
2139 GetDocument()->ClearSyncPoints();
2143 * @brief Called when "Clear Synchronization Points" item is updated
2145 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2147 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2151 * @brief This function is called before other edit events.
2152 * @param [in] nAction Edit operation to do
2153 * @param [in] pszText Text to insert, delete etc
2154 * @sa CCrystalEditView::OnEditOperation()
2155 * @todo More edit-events for rescan delaying?
2157 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2159 if (!QueryEditable())
2161 // We must not arrive here, and assert helps detect troubles
2166 CMergeDoc* pDoc = GetDocument();
2167 pDoc->SetEditedAfterRescan(m_nThisPane);
2169 // simple hook for multiplex undo operations
2170 // deleted by jtuc 2003-06-28
2171 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2172 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2174 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2175 pDoc->undoTgt.push_back(this);
2176 pDoc->curUndo = pDoc->undoTgt.end();
2179 // perform original function
2180 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2182 // augment with additional operations
2184 // Change header to inform about changed doc
2185 pDoc->UpdateHeaderPath(m_nThisPane);
2187 // If automatic rescan enabled, rescan after edit events
2188 if (m_bAutomaticRescan)
2190 // keep document up to date
2191 // (Re)start timer to rescan only when user edits text
2192 // If timer starting fails, rescan immediately
2193 if (nAction == CE_ACTION_TYPING ||
2194 nAction == CE_ACTION_REPLACE ||
2195 nAction == CE_ACTION_BACKSPACE ||
2196 nAction == CE_ACTION_INDENT ||
2197 nAction == CE_ACTION_PASTE ||
2198 nAction == CE_ACTION_DELSEL ||
2199 nAction == CE_ACTION_DELETE ||
2200 nAction == CE_ACTION_CUT)
2202 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2203 pDoc->FlushAndRescan();
2206 pDoc->FlushAndRescan();
2212 // Update other pane for sync line.
2213 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2215 if (nPane == m_nThisPane)
2217 CCrystalEditView *pView = GetGroupView(nPane);
2218 if (pView != nullptr)
2219 pView->Invalidate();
2226 * @brief Redo last action
2228 void CMergeEditView::OnEditRedo()
2230 CWaitCursor waitstatus;
2231 CMergeDoc* pDoc = GetDocument();
2232 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2235 if (!QueryEditable())
2238 GetParentFrame()->SetActiveView(this, true);
2239 if(CCrystalEditViewEx::DoEditRedo())
2242 pDoc->UpdateHeaderPath(m_nThisPane);
2243 pDoc->FlushAndRescan();
2248 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2253 * @brief Called when "Redo" item is updated
2255 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2257 CMergeDoc* pDoc = GetDocument();
2258 if (pDoc->curUndo!=pDoc->undoTgt.end())
2260 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2261 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2264 pCmdUI->Enable(false);
2267 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2269 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2273 * @brief Scrolls to current diff and/or selects diff text
2274 * @param [in] bScroll If true scroll diff to view
2275 * @param [in] bSelectText If true select diff text
2276 * @note If bScroll and bSelectText are false, this does nothing!
2277 * @todo This shouldn't be called when no diff is selected, so
2278 * somebody could try to ASSERT(nDiff > -1)...
2280 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2282 CMergeDoc *pd = GetDocument();
2283 const int nDiff = pd->GetCurrentDiff();
2285 // Try to trap some errors
2286 if (nDiff >= pd->m_diffList.GetSize())
2287 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2288 nDiff, pd->m_diffList.GetSize());
2290 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2292 CPoint ptStart, ptEnd;
2294 pd->m_diffList.GetDiff(nDiff, curDiff);
2297 ptStart.y = curDiff.dbegin;
2299 ptEnd.y = curDiff.dend;
2301 if (bScroll && !m_bDetailView)
2303 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2305 // Difference is not visible, scroll it so that max amount of
2306 // scrolling is done while keeping the diff in screen. So if
2307 // scrolling is downwards, scroll the diff to as up in screen
2308 // as possible. This usually brings next diff to the screen
2309 // and we don't need to scroll into it.
2310 int nLine = GetSubLineIndex(ptStart.y);
2311 if (nLine > CONTEXT_LINES_ABOVE)
2313 nLine -= CONTEXT_LINES_ABOVE;
2315 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2316 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2318 if (nPane != m_nThisPane)
2319 GetGroupView(nPane)->ScrollToSubLine(nLine);
2323 vector<WordDiff> worddiffs;
2324 if (GetOptionsMgr()->GetBool(OPT_SCROLL_TO_FIRST_INLINE_DIFF))
2325 worddiffs = pd->GetWordDiffArrayInDiffBlock(nDiff);
2326 CPoint pt = worddiffs.size() > 0 ?
2327 CPoint{ worddiffs[0].begin[m_nThisPane], worddiffs[0].beginline[m_nThisPane] } :
2329 GetGroupView(m_nThisPane)->SetCursorPos(pt);
2330 GetGroupView(m_nThisPane)->SetAnchor(pt);
2331 GetGroupView(m_nThisPane)->SetSelection(pt, pt);
2332 GetGroupView(m_nThisPane)->EnsureVisible(pt);
2333 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2335 if (nPane != m_nThisPane)
2337 if (worddiffs.size() > 0)
2339 pt.x = worddiffs[0].begin[nPane];
2340 pt.y = worddiffs[0].beginline[nPane];
2342 GetGroupView(nPane)->SetCursorPos(pt);
2343 GetGroupView(nPane)->SetAnchor(pt);
2344 GetGroupView(nPane)->SetSelection(pt, pt);
2351 ptEnd.x = GetLineLength(ptEnd.y);
2352 SetSelection(ptStart, ptEnd);
2361 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2363 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2364 // ...but for this the queue must be empty
2365 // The timer message is a low priority message but the queue is maybe not yet empty
2366 // So we set a flag, wait for OnIdle to proceed, then come back here...
2367 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2368 // not with SetTimer so there is no delay)
2370 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2372 // IDLE_TIMER is the false timer used to come back here after OnIdle
2373 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2374 // (one normal timer = one flag = one command)
2376 if (nIDEvent == IDT_RESCAN)
2378 KillTimer(IDT_RESCAN);
2379 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2380 // notify the app to come back after OnIdle
2381 theApp.SetNeedIdleTimer();
2384 if (nIDEvent == IDLE_TIMER)
2386 // not a real timer, just come back after OnIdle
2387 // look to flags to know what to do
2388 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2389 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2390 fTimerWaitingForIdle = 0;
2393 CCrystalEditViewEx::OnTimer(nIDEvent);
2397 * @brief Returns if buffer is read-only
2398 * @note This has no any relation to file being read-only!
2400 bool CMergeEditView::IsReadOnly(int pane) const
2402 return m_bDetailView ? true : (GetDocument()->m_ptBuf[pane]->GetReadOnly() != false);
2406 * @brief Called when "Save left (as...)" item is updated
2408 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2410 CMergeDoc *pd = GetDocument();
2411 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2415 * @brief Called when "Save middle (as...)" item is updated
2417 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2419 CMergeDoc *pd = GetDocument();
2420 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2424 * @brief Called when "Save right (as...)" item is updated
2426 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2428 CMergeDoc *pd = GetDocument();
2429 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2433 * @brief Refresh display using text-buffers
2434 * @note This DOES NOT reload files!
2436 void CMergeEditView::OnRefresh()
2438 CMergeDoc *pd = GetDocument();
2439 ASSERT(pd != nullptr);
2440 pd->FlushAndRescan(true);
2444 * @brief Handle some keys when in merging mode
2446 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2448 bool bHandled = false;
2450 // Allow default text selection when SHIFT pressed
2451 if (::GetAsyncKeyState(VK_SHIFT))
2454 // Allow default editor functions when CTRL pressed
2455 if (::GetAsyncKeyState(VK_CONTROL))
2458 // If we are in merging mode (merge with cursor keys)
2459 // handle some keys here
2460 switch (pMsg->wParam)
2487 * @brief Called before messages are translated.
2489 * Checks if ESC key was pressed, saves and closes doc.
2490 * Also if in merge mode traps cursor keys.
2492 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2494 if (pMsg->message == WM_KEYDOWN)
2496 // If we are in merging mode (merge with cursor keys)
2497 // handle some keys here
2498 if (theApp.GetMergingMode())
2500 bool bHandled = MergeModeKeyDown(pMsg);
2505 // Close window if user has allowed it from options
2506 if (pMsg->wParam == VK_ESCAPE)
2508 int nCloseWithEsc = GetOptionsMgr()->GetInt(OPT_CLOSE_WITH_ESC);
2509 if (nCloseWithEsc != 0)
2510 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2515 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2519 * @brief Called when "Save" item is updated
2521 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2523 CMergeDoc *pd = GetDocument();
2525 bool bModified = false;
2526 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2528 if (pd->m_ptBuf[nPane]->IsModified())
2531 pCmdUI->Enable(bModified);
2535 * @brief Enable/disable left buffer read-only
2537 void CMergeEditView::OnLeftReadOnly()
2539 CMergeDoc *pd = GetDocument();
2540 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2541 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2545 * @brief Called when "Left read-only" item is updated
2547 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2549 CMergeDoc *pd = GetDocument();
2550 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2551 pCmdUI->Enable(true);
2552 pCmdUI->SetCheck(bReadOnly);
2556 * @brief Enable/disable middle buffer read-only
2558 void CMergeEditView::OnMiddleReadOnly()
2560 CMergeDoc *pd = GetDocument();
2561 if (pd->m_nBuffers == 3)
2563 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2564 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2569 * @brief Called when "Middle read-only" item is updated
2571 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2573 CMergeDoc *pd = GetDocument();
2574 if (pd->m_nBuffers < 3)
2576 pCmdUI->Enable(false);
2580 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2581 pCmdUI->Enable(true);
2582 pCmdUI->SetCheck(bReadOnly);
2587 * @brief Enable/disable right buffer read-only
2589 void CMergeEditView::OnRightReadOnly()
2591 CMergeDoc *pd = GetDocument();
2592 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2593 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2597 * @brief Called when "Left read-only" item is updated
2599 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2601 CMergeDoc *pd = GetDocument();
2602 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2603 pCmdUI->Enable(true);
2604 pCmdUI->SetCheck(bReadOnly);
2607 /// Store interface we use to display status line info
2608 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2610 ASSERT(m_piMergeEditStatus == nullptr);
2611 m_piMergeEditStatus = piMergeEditStatus;
2615 * @brief Update status bar contents.
2617 void CMergeEditView::UpdateStatusbar()
2623 * @brief Update statusbar info, Override from CCrystalTextView
2624 * @note we tab-expand column, but we don't tab-expand char count,
2625 * since we want to show how many chars there are and tab is just one
2626 * character although it expands to several spaces.
2628 void CMergeEditView::OnUpdateCaret()
2630 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2633 CPoint cursorPos = GetCursorPos();
2634 int nScreenLine = cursorPos.y;
2635 const int nRealLine = ComputeRealLine(nScreenLine);
2642 DWORD dwLineFlags = 0;
2644 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2645 // Is this a ghost line ?
2646 if (dwLineFlags & LF_GHOST)
2648 // Ghost lines display eg "Line 12-13"
2649 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2650 sEol = _T("hidden");
2654 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2655 sLine.Format(_T("%d"), nRealLine+1);
2656 curChar = cursorPos.x + 1;
2657 chars = GetLineLength(nScreenLine);
2658 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2659 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2661 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2662 GetDocument()->IsMixedEOL(m_nThisPane))
2664 sEol = GetTextBufferEol(nScreenLine);
2667 sEol = _T("hidden");
2669 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2670 curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2672 // Is cursor inside difference?
2673 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2674 m_bCurrentLineIsDiff = true;
2676 m_bCurrentLineIsDiff = false;
2678 CWnd* pWnd = GetFocus();
2679 if (!m_bDetailView || (pWnd && pWnd->m_hWnd == this->m_hWnd))
2680 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2683 * @brief Select linedifference in the current line.
2685 * Select line difference in current line. Selection type
2686 * is choosed by highlight type.
2688 template<bool reversed>
2689 void CMergeEditView::OnSelectLineDiff()
2691 // Pass this to the document, to compare this file to other
2692 GetDocument()->Showlinediff(this, reversed);
2695 /// Enable select difference menuitem if current line is inside difference.
2696 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2698 pCmdUI->Enable(!GetDocument()->IsEditedAfterRescan());
2701 template<bool reversed>
2702 void CMergeEditView::OnAddToIgnoredSubstitutions()
2704 // Pass this to the document, to compare this file to other
2705 GetDocument()->AddToIgnoredSubstitutions(this, reversed);
2708 void CMergeEditView::OnUpdateAddToIgnoredSubstitutions(CCmdUI* pCmdUI)
2710 pCmdUI->Enable(GetDocument()->m_nBuffers == 2 && !GetDocument()->IsEditedAfterRescan());
2715 * @brief Enable/disable Replace-menuitem
2717 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2719 CMergeDoc *pd = GetDocument();
2720 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2722 pCmdUI->Enable(!bReadOnly);
2726 * @brief Update readonly statusbaritem
2728 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2730 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2731 pCmdUI->Enable(bRO);
2735 * @brief Create the dynamic submenu for scripts
2737 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2740 std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2743 size_t i = GetMenuItemCount(hMenu);
2745 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2747 if (functionNamesList.size() == 0)
2749 // no script : create a <empty> entry
2750 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2754 // or fill in the submenu with the scripts names
2755 int ID = ID_SCRIPT_FIRST; // first ID in menu
2756 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2757 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2759 functionNamesList.clear();
2762 if (!plugin::IsWindowsScriptThere())
2763 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2769 * @brief Create the dynamic submenu for prediffers
2771 * @note The plugins are grouped in (suggested) and (not suggested)
2772 * The IDs follow the order of GetAvailableScripts
2774 * suggested 0 ID_1ST + 0
2775 * suggested 1 ID_1ST + 2
2776 * suggested 2 ID_1ST + 5
2777 * not suggested 0 ID_1ST + 1
2778 * not suggested 1 ID_1ST + 3
2779 * not suggested 2 ID_1ST + 4
2781 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2784 int i = GetMenuItemCount(hMenu);
2786 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2788 CMergeDoc *pd = GetDocument();
2789 ASSERT(pd != nullptr);
2792 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2794 // get the scriptlet files
2795 PluginArray * piScriptArray =
2796 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2797 PluginArray * piScriptArray2 =
2798 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2800 // build the menu : first part, suggested plugins
2802 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2803 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2805 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
2807 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2809 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2810 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2813 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2815 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2817 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2818 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2821 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2824 // build the menu : second part, others plugins
2826 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2827 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2829 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2830 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2832 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2833 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2836 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2838 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2840 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2841 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2844 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2847 // compute the m_CurrentPredifferID (to set the radio button)
2848 PrediffingInfo prediffer;
2849 pd->GetPrediffer(&prediffer);
2851 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
2852 m_CurrentPredifferID = 0;
2853 else if (prediffer.m_PluginName.empty())
2854 m_CurrentPredifferID = ID_NO_PREDIFFER;
2857 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2858 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2860 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2861 if (prediffer.m_PluginName == plugin->m_name)
2862 m_CurrentPredifferID = ID;
2865 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2867 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2868 if (prediffer.m_PluginName == plugin->m_name)
2869 m_CurrentPredifferID = ID;
2877 * @brief Offer a context menu built with scriptlet/ActiveX functions
2879 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
2881 // Create the menu and populate it with the available functions
2883 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
2885 // Remove copying item copying from active side
2886 if (m_nThisPane == 0) // left?
2888 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2889 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2891 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
2893 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2894 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2897 // Remove "Go to Moved Line Between Middle and Right" if in 2-way file comparison.
2898 // Remove "Go to Moved Line Between Middle and Right" if the right pane is active in 3-way file comparison.
2899 // Remove "Go to Moved Line Between Left and Middle" if the right pane is active in 3-way file comparison.
2900 int nBuffers = GetDocument()->m_nBuffers;
2901 if (nBuffers == 2 || (nBuffers == 3 && m_nThisPane == 0))
2902 menu.RemoveMenu(ID_GOTO_MOVED_LINE_MR, MF_BYCOMMAND);
2903 else if (nBuffers == 3 && m_nThisPane == 2)
2904 menu.RemoveMenu(ID_GOTO_MOVED_LINE_LM, MF_BYCOMMAND);
2906 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
2907 theApp.TranslateMenu(menu.m_hMenu);
2909 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
2910 ASSERT(pSub != nullptr);
2912 // Context menu opened using keyboard has no coordinates
2913 if (point.x == -1 && point.y == -1)
2916 GetClientRect(rect);
2917 ClientToScreen(rect);
2919 point = rect.TopLeft();
2923 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2924 point.x, point.y, AfxGetMainWnd());
2929 * @brief Update EOL mode in status bar
2931 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
2933 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
2937 * @brief Change EOL mode and unify all the lines EOL to this new mode
2939 void CMergeEditView::OnConvertEolTo(UINT nID )
2941 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;;
2945 nStyle = CRLFSTYLE::DOS;
2947 case ID_EOL_TO_UNIX:
2948 nStyle = CRLFSTYLE::UNIX;
2951 nStyle = CRLFSTYLE::MAC;
2955 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
2958 m_pTextBuffer->SetCRLFMode(nStyle);
2960 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
2961 if (m_pTextBuffer->applyEOLMode())
2963 CMergeDoc *pd = GetDocument();
2964 ASSERT(pd != nullptr);
2965 pd->UpdateHeaderPath(m_nThisPane);
2966 pd->FlushAndRescan(true);
2971 * @brief allow convert to entries in file submenu
2973 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
2975 CRLFSTYLE nStyle = CRLFSTYLE::AUTOMATIC;
2976 switch (pCmdUI->m_nID)
2979 nStyle = CRLFSTYLE::DOS;
2981 case ID_EOL_TO_UNIX:
2982 nStyle = CRLFSTYLE::UNIX;
2985 nStyle = CRLFSTYLE::MAC;
2989 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
2993 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2994 GetDocument()->IsMixedEOL(m_nThisPane) ||
2995 nStyle != m_pTextBuffer->GetCRLFMode())
2997 pCmdUI->SetRadio(false);
2999 // Don't allow selecting other EOL style for protected pane
3000 if (!QueryEditable())
3001 pCmdUI->Enable(false);
3004 pCmdUI->SetRadio(true);
3008 * @brief Copy diff from left to right and advance to next diff
3010 void CMergeEditView::OnL2RNext()
3013 if (IsCursorInDiff()) // for 3-way file compare
3019 * @brief Update "Copy right and advance" UI item
3021 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
3023 OnUpdateL2r(pCmdUI);
3027 * @brief Copy diff from right to left and advance to next diff
3029 void CMergeEditView::OnR2LNext()
3032 if (IsCursorInDiff()) // for 3-way file compare
3038 * @brief Update "Copy left and advance" UI item
3040 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
3042 OnUpdateR2l(pCmdUI);
3046 * @brief Change active pane in MergeView.
3047 * Changes active pane and makes sure cursor position is kept in
3048 * screen. Currently we put cursor in same line than in original
3049 * active pane but we could be smarter too? Maybe update cursor
3050 * only when it is not visible in new pane?
3052 void CMergeEditView::OnChangePane()
3054 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3055 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
3056 CMergeDoc *pDoc = GetDocument();
3057 bool bFound = false;
3058 CMergeEditView *pNextActiveView = nullptr;
3059 std::vector<CMergeEditView *> list = pDoc->GetViewList();
3060 list.insert(list.end(), list.begin(), list.end());
3061 for (auto& pView : list)
3063 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
3065 pNextActiveView = pView;
3071 GetParentFrame()->SetActiveView(pNextActiveView);
3072 CPoint ptCursor = pWnd->GetCursorPos();
3074 if (ptCursor.y >= pNextActiveView->GetLineCount())
3075 ptCursor.y = pNextActiveView->GetLineCount() - 1;
3076 pNextActiveView->SetCursorPos(ptCursor);
3077 pNextActiveView->SetAnchor(ptCursor);
3078 pNextActiveView->SetSelection(ptCursor, ptCursor);
3082 * @brief Show "Go To" dialog and scroll views to line or diff.
3084 * Before dialog is opened, current line and file is determined
3086 * @note Conversions needed between apparent and real lines
3088 void CMergeEditView::OnWMGoto()
3091 CMergeDoc *pDoc = GetDocument();
3092 CPoint pos = GetCursorPos();
3096 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
3097 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
3098 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
3100 // Set active file and current line selected in dialog
3101 dlg.m_strParam = strutils::to_str(nRealLine + 1);
3102 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
3103 dlg.m_nGotoWhat = 0;
3105 if (dlg.DoModal() == IDOK)
3107 CMergeDoc * pDoc1 = GetDocument();
3108 CMergeEditView * pCurrentView = nullptr;
3111 pCurrentView = GetGroupView(m_nThisPane);
3114 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
3116 if (dlg.m_nGotoWhat == 0)
3118 int nRealLine1 = num;
3121 if (nRealLine1 > nLastLine)
3122 nRealLine1 = nLastLine;
3124 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
3131 if (diff >= pDoc1->m_diffList.GetSize())
3132 diff = pDoc1->m_diffList.GetSize();
3134 pCurrentView->SelectDiff(diff, true, false);
3140 * @brief Called when "Go to Moved Line Between Left and Middle" item is selected.
3141 * Go to moved line between the left and right panes when in 2-way file comparison.
3142 * Go to moved line between the left and middle panes when in 3-way file comparison.
3144 void CMergeEditView::OnGotoMovedLineLM()
3146 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3149 CMergeDoc* pDoc = GetDocument();
3150 CPoint pos = GetCursorPos();
3152 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3153 ASSERT(pDoc != nullptr);
3154 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3157 if (m_nThisPane == 0)
3159 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3161 GotoLine(line, false, 1);
3163 else if (m_nThisPane == 1)
3165 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3167 GotoLine(line, false, 0);
3172 * @brief Called when "Go to Moved Line Between Left and Middle" item is updated.
3173 * @param [in] pCmdUI UI component to update.
3174 * @note The item label is changed to "Go to Moved Line" when 2-way file comparison.
3176 void CMergeEditView::OnUpdateGotoMovedLineLM(CCmdUI* pCmdUI)
3178 CMergeDoc* pDoc = GetDocument();
3179 CPoint pos = GetCursorPos();
3181 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3182 ASSERT(pCmdUI != nullptr);
3183 ASSERT(pDoc != nullptr);
3184 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3187 if (pDoc->m_nBuffers == 2)
3188 pCmdUI->SetText(_("Go to Moved Line\tCtrl+Shift+G").c_str());
3190 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || m_nThisPane == 2)
3192 pCmdUI->Enable(false);
3196 if (m_nThisPane == 0)
3198 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3199 pCmdUI->Enable(bOn);
3201 else if (m_nThisPane == 1)
3203 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3204 pCmdUI->Enable(bOn);
3209 * @brief Called when "Go to Moved Line Between Middle and Right" item is selected.
3210 * Go to moved line between the middle and right panes when in 3-way file comparison.
3212 void CMergeEditView::OnGotoMovedLineMR()
3214 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS))
3217 CMergeDoc* pDoc = GetDocument();
3218 CPoint pos = GetCursorPos();
3220 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3221 ASSERT(pDoc != nullptr);
3222 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3225 if (m_nThisPane == 1)
3227 int line = pDoc->RightLineInMovedBlock(m_nThisPane, pos.y);
3229 GotoLine(line, false, 2);
3231 else if (m_nThisPane == 2)
3233 int line = pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y);
3235 GotoLine(line, false, 1);
3240 * @brief Called when "Go to Moved Line Between Middle and Right" item is updated.
3241 * @param [in] pCmdUI UI component to update.
3243 void CMergeEditView::OnUpdateGotoMovedLineMR(CCmdUI* pCmdUI)
3245 CMergeDoc* pDoc = GetDocument();
3246 CPoint pos = GetCursorPos();
3248 ASSERT(m_nThisPane >= 0 && m_nThisPane < 3);
3249 ASSERT(pCmdUI != nullptr);
3250 ASSERT(pDoc != nullptr);
3251 ASSERT(pDoc->m_nBuffers == 2 || pDoc->m_nBuffers == 3);
3254 if (!GetOptionsMgr()->GetBool(OPT_CMP_MOVED_BLOCKS) || pDoc->m_nBuffers == 2 || m_nThisPane == 0)
3256 pCmdUI->Enable(false);
3260 if (m_nThisPane == 1)
3262 bool bOn = (pDoc->RightLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3263 pCmdUI->Enable(bOn);
3265 else if (m_nThisPane == 2)
3267 bool bOn = (pDoc->LeftLineInMovedBlock(m_nThisPane, pos.y) >= 0);
3268 pCmdUI->Enable(bOn);
3272 void CMergeEditView::OnShellMenu()
3274 CFrameWnd *pFrame = GetTopLevelFrame();
3275 ASSERT(pFrame != nullptr);
3276 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3277 pFrame->m_bAutoMenuEnable = FALSE;
3279 String path = GetDocument()->m_filePaths[m_nThisPane];
3280 std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3281 pContextMenu->Initialize();
3282 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3283 pContextMenu->RequeryShellContextMenu();
3285 ::GetCursorPos(&point);
3286 HWND hWnd = GetSafeHwnd();
3287 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3289 pContextMenu->InvokeCommand(nCmd, hWnd);
3290 pContextMenu->ReleaseShellContextMenu();
3292 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3295 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3297 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3301 * @brief Reload options.
3303 void CMergeEditView::RefreshOptions()
3305 RENDERING_MODE nRenderingMode = static_cast<RENDERING_MODE>(GetOptionsMgr()->GetInt(OPT_RENDERING_MODE));
3306 SetRenderingMode(nRenderingMode);
3308 m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3310 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3311 SetInsertTabs(true);
3313 SetInsertTabs(false);
3315 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3317 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3318 SetTextType(CrystalLineParser::SRC_PLAIN);
3320 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3321 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3323 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3324 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL),
3325 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3326 GetDocument()->IsMixedEOL(m_nThisPane));
3328 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3331 void CMergeEditView::OnScripts(UINT nID )
3333 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3334 String text = GetSelectedText();
3336 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3337 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3339 // now replace the text
3340 ReplaceSelection(text.c_str(), text.length(), 0);
3344 * @brief Called when an editor script item is updated
3346 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3348 // append the scripts submenu
3349 HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
3350 if (scriptsSubmenu != nullptr)
3351 createScriptsSubmenu(scriptsSubmenu);
3353 pCmdUI->Enable(true);
3357 * @brief Called when an editor script item is updated
3359 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3361 pCmdUI->Enable(true);
3363 CMergeDoc *pd = GetDocument();
3364 ASSERT(pd != nullptr);
3365 PrediffingInfo prediffer;
3366 pd->GetPrediffer(&prediffer);
3368 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_MANUAL)
3370 pCmdUI->SetRadio(false);
3374 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3375 if (prediffer.m_PluginName.empty())
3376 m_CurrentPredifferID = ID_NO_PREDIFFER;
3378 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3382 * @brief Update "Prediffer" menuitem
3384 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3386 // recreate the sub menu (to fill the "selected prediffers")
3387 GetMainFrame()->UpdatePrediffersMenu();
3391 void CMergeEditView::OnNoPrediffer()
3393 OnPrediffer(ID_NO_PREDIFFER);
3396 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3398 void CMergeEditView::OnPrediffer(UINT nID )
3400 CMergeDoc *pd = GetDocument();
3401 ASSERT(pd != nullptr);
3403 SetPredifferByMenu(nID);
3404 pd->FlushAndRescan(true);
3408 * @brief Handler for all prediffer choices.
3409 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3410 * ID_NO_PREDIFFER, & specific prediffers.
3412 void CMergeEditView::SetPredifferByMenu(UINT nID )
3414 CMergeDoc *pd = GetDocument();
3415 ASSERT(pd != nullptr);
3417 if (nID == ID_NO_PREDIFFER)
3419 m_CurrentPredifferID = nID;
3420 // All flags are set correctly during the construction
3421 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3422 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3423 infoPrediffer->m_PluginName.clear();
3424 pd->SetPrediffer(infoPrediffer);
3425 pd->FlushAndRescan(true);
3429 // get the scriptlet files
3430 PluginArray * piScriptArray =
3431 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3432 PluginArray * piScriptArray2 =
3433 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3435 // build a PrediffingInfo structure fom the ID
3436 PrediffingInfo prediffer;
3437 prediffer.m_PluginOrPredifferMode = PLUGIN_MODE::PLUGIN_MANUAL;
3439 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3440 if (pluginNumber < piScriptArray->size())
3442 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3443 prediffer.m_PluginName = plugin->m_name;
3447 pluginNumber -= piScriptArray->size();
3448 if (pluginNumber >= piScriptArray2->size())
3450 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3451 prediffer.m_PluginName = plugin->m_name;
3454 // update data for the radio button
3455 m_CurrentPredifferID = nID;
3457 // update the prediffer and rescan
3458 pd->SetPrediffer(&prediffer);
3462 * @brief Look through available prediffers, and return ID of requested one, if found
3464 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3467 int ID = ID_PREDIFFERS_FIRST;
3469 // Search file prediffers
3470 PluginArray * piScriptArray =
3471 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3472 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3474 const PluginInfoPtr & plugin = piScriptArray->at(i);
3475 if (plugin->m_name == prediffer)
3479 // Search buffer prediffers
3480 PluginArray * piScriptArray2 =
3481 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3482 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3484 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3485 if (plugin->m_name == prediffer)
3493 * @brief Look through available prediffers, and return ID of requested one, if found
3495 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3497 int id = FindPrediffer(prediffer);
3498 if (id<0) return false;
3499 SetPredifferByMenu(id);
3504 * @brief Goto given line.
3505 * @param [in] nLine Destination linenumber
3506 * @param [in] bRealLine if true linenumber is real line, otherwise
3507 * it is apparent line (including deleted lines)
3508 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3510 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
3512 CMergeDoc *pDoc = GetDocument();
3513 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3514 CMergeEditView *pCurrentView = nullptr;
3515 if (pSplitterWnd != nullptr)
3516 pCurrentView = static_cast<CMergeEditView*>
3517 (pSplitterWnd->GetActivePane());
3519 int nRealLine = nLine;
3520 int nApparentLine = nLine;
3522 // Compute apparent (shown linenumber) line
3525 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3526 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3528 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3532 ptPos.y = nApparentLine;
3534 // Scroll line to center of view
3535 int nScrollLine = GetSubLineIndex(nApparentLine);
3536 nScrollLine -= GetScreenLines() / 2;
3537 if (nScrollLine < 0)
3540 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3542 int nGroup = m_bDetailView ? 0 : m_nThisGroup;
3543 CMergeEditView* pView = GetDocument()->GetView(nGroup, nPane);
3544 pView->ScrollToSubLine(nScrollLine);
3545 if (ptPos.y < pView->GetLineCount())
3547 pView->SetCursorPos(ptPos);
3548 pView->SetAnchor(ptPos);
3552 CPoint ptPos1(0, pView->GetLineCount() - 1);
3553 pView->SetCursorPos(ptPos1);
3554 pView->SetAnchor(ptPos1);
3558 // If goto target is another view - activate another view.
3559 // This is done for user convenience as user probably wants to
3560 // work with goto target file.
3562 GetDocument()->GetView(0, pane)->SetActivePane();
3563 else if (GetGroupView(pane) != pCurrentView)
3564 GetGroupView(pane)->SetActivePane();
3568 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3571 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3573 if (pScrollBar == nullptr)
3575 // Scroll did not come frome a scroll bar
3576 // Find the appropriate scroll bar
3577 // and send the message to the splitter window instead
3578 // The event should eventually come back here but with a valid scrollbar
3579 // Along the way it will be propagated to other windows that need it
3580 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3581 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3582 pSplitterWnd->SendMessage(WM_HSCROLL,
3583 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3586 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3590 * @brief When view is scrolled using scrollbars update location pane.
3592 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3594 if (pScrollBar == nullptr)
3596 // Scroll did not come frome a scroll bar
3597 // Find the appropriate scroll bar
3598 // and send the message to the splitter window instead
3599 // The event should eventually come back here but with a valid scrollbar
3600 // Along the way it will be propagated to other windows that need it
3601 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3602 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3603 pSplitterWnd->SendMessage(WM_VSCROLL,
3604 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3607 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3609 if (nSBCode == SB_ENDSCROLL)
3612 // Note we cannot use nPos because of its 16-bit nature
3613 SCROLLINFO si = {0};
3614 si.cbSize = sizeof (si);
3615 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3616 VERIFY (GetScrollInfo (SB_VERT, &si));
3618 // Get the current position of scroll box.
3619 int nCurPos = si.nPos;
3621 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3625 * @brief Copy selected lines adding linenumbers.
3627 void CMergeEditView::OnEditCopyLineNumbers()
3635 CMergeDoc *pDoc = GetDocument();
3636 GetSelection(ptStart, ptEnd);
3638 // Get last selected line (having widest linenumber)
3639 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3640 size_t nNumWidth = strutils::to_str(line + 1).length();
3642 for (int i = ptStart.y; i <= ptEnd.y; i++)
3644 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3647 // We need to convert to real linenumbers
3648 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3650 // Insert spaces to align different width linenumbers (99, 100)
3651 strLine = GetLineText(i);
3652 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3655 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3656 strText += strNumLine;
3658 PutToClipboard(strText, strText.GetLength(), m_bRectangularSelection);
3661 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3663 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3667 * @brief Open active file with associated application.
3669 * First tries to open file using shell 'Edit' action, since that
3670 * action open scripts etc. to editor instead of running them. If
3671 * edit-action is not registered, 'Open' action is used.
3673 void CMergeEditView::OnOpenFile()
3675 CMergeDoc * pDoc = GetDocument();
3676 ASSERT(pDoc != nullptr);
3678 String sFileName = pDoc->m_filePaths[m_nThisPane];
3679 if (sFileName.empty())
3681 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3682 0, 0, SW_SHOWNORMAL);
3683 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3684 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3685 0, 0, SW_SHOWNORMAL);
3686 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3691 * @brief Open active file with app selection dialog
3693 void CMergeEditView::OnOpenFileWith()
3695 CMergeDoc * pDoc = GetDocument();
3696 ASSERT(pDoc != nullptr);
3698 String sFileName = pDoc->m_filePaths[m_nThisPane];
3699 if (sFileName.empty())
3703 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3705 sysdir.ReleaseBuffer();
3706 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3707 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3708 sysdir, SW_SHOWNORMAL);
3712 * @brief Open active file with external editor
3714 void CMergeEditView::OnOpenFileWithEditor()
3716 CMergeDoc * pDoc = GetDocument();
3717 ASSERT(pDoc != nullptr);
3719 String sFileName = pDoc->m_filePaths[m_nThisPane];
3720 if (sFileName.empty())
3723 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3724 theApp.OpenFileToExternalEditor(sFileName, nRealLine);
3728 * @brief Force repaint of the location pane.
3730 void CMergeEditView::RepaintLocationPane()
3732 // Must force recalculation due to caching of data in location pane.
3733 CLocationView *pLocationView = GetDocument()->GetLocationView();
3734 if (pLocationView != nullptr)
3735 pLocationView->ForceRecalculate();
3739 * @brief Enables/disables linediff (different color for diffs)
3741 void CMergeEditView::OnViewLineDiffs()
3743 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3744 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3746 // Call CMergeDoc RefreshOptions() to refresh *both* views
3747 CMergeDoc *pDoc = GetDocument();
3748 pDoc->RefreshOptions();
3749 pDoc->FlushAndRescan(true);
3752 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3754 pCmdUI->Enable(true);
3755 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3759 * @brief Enables/disables line number
3761 void CMergeEditView::OnViewLineNumbers()
3763 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3765 // Call CMergeDoc RefreshOptions() to refresh *both* views
3766 CMergeDoc *pDoc = GetDocument();
3767 pDoc->RefreshOptions();
3770 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3772 pCmdUI->Enable(true);
3773 pCmdUI->SetCheck(GetViewLineNumbers());
3777 * @brief Enables/disables word wrap
3779 void CMergeEditView::OnViewWordWrap()
3781 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3783 // Call CMergeDoc RefreshOptions() to refresh *both* views
3784 CMergeDoc *pDoc = GetDocument();
3785 pDoc->RefreshOptions();
3786 pDoc->UpdateAllViews(this);
3791 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3793 pCmdUI->Enable(true);
3794 pCmdUI->SetCheck(m_bWordWrap);
3797 void CMergeEditView::OnViewWhitespace()
3799 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3801 // Call CMergeDoc RefreshOptions() to refresh *both* views
3802 CMergeDoc *pDoc = GetDocument();
3803 pDoc->RefreshOptions();
3806 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3808 pCmdUI->SetCheck(GetViewTabs());
3811 void CMergeEditView::OnViewEOL()
3813 GetOptionsMgr()->SaveOption(OPT_VIEW_EOL, !GetViewEols());
3814 GetDocument()->RefreshOptions();
3817 void CMergeEditView::OnUpdateViewEOL(CCmdUI* pCmdUI)
3819 pCmdUI->SetCheck(GetViewEols());
3822 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
3824 if (!IsInitialized())
3827 CMergeDoc * pDoc = GetDocument();
3828 if (m_nThisPane < pDoc->m_nBuffers - 1)
3830 // To calculate subline index correctly
3831 // we have to invalidate line cache in all pane before calling the function related the subline.
3832 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3834 CMergeEditView *pView = GetGroupView(nPane);
3835 if (pView != nullptr)
3836 pView->InvalidateScreenRect(false);
3841 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3843 CMergeEditView *pView = GetGroupView(nPane);
3844 if (pView != nullptr)
3845 pView->Invalidate();
3848 // recalculate m_nTopSubLine
3849 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3853 RecalcVertScrollBar (false, false);
3854 RecalcHorzScrollBar (false, false);
3858 * @brief allocates GDI resources for printing
3859 * @param pDC [in] points to the printer device context
3860 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3862 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3864 GetParentFrame()->PostMessage(WM_TIMER);
3866 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3868 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3869 pView->m_bPrintHeader = true;
3870 pView->m_bPrintFooter = true;
3871 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3876 * @brief frees GDI resources for printing
3877 * @param pDC [in] points to the printer device context
3878 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3880 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3882 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3883 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3885 GetParentFrame()->PostMessage(WM_TIMER);
3889 * @brief Gets header text to print
3890 * @param [in] nPageNum the page number to print
3891 * @param [out] header text to print
3893 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3895 text = GetDocument()->GetTitle();
3899 * @brief Prints header
3900 * @param [in] nPageNum the page number to print
3902 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3904 if (m_nThisPane > 0)
3906 int oldRight = m_rcPrintArea.right;
3907 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3908 CGhostTextView::PrintHeader(pdc, nPageNum);
3909 m_rcPrintArea.right = oldRight;
3913 * @brief Prints footer
3914 * @param [in] nPageNum the page number to print
3916 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3918 if (m_nThisPane > 0)
3920 int oldRight = m_rcPrintArea.right;
3921 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3922 CGhostTextView::PrintFooter(pdc, nPageNum);
3923 m_rcPrintArea.right = oldRight;
3926 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3928 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3929 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3933 * @brief Prints or previews both panes.
3934 * @param pDC [in] points to the printer device context
3935 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3937 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
3939 CRect rDraw = pInfo->m_rectDraw;
3940 CSize sz = rDraw.Size();
3941 CMergeDoc *pDoc = GetDocument();
3943 SIZE szLeftTop, szRightBottom;
3944 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3945 pDC->HIMETRICtoLP(&szLeftTop);
3946 pDC->HIMETRICtoLP(&szRightBottom);
3948 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3951 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3953 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3954 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3955 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3956 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3960 bool CMergeEditView::IsInitialized() const
3962 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3963 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3964 return pBuffer->IsInitialized();
3968 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3970 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3972 int nBreaks[3] = {0};
3973 int nMaxBreaks = -1;
3974 CMergeDoc * pDoc = GetDocument();
3975 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3977 CMergeEditView *pView = GetGroupView(nPane);
3978 if (pView != nullptr)
3980 if (nLineIndex >= pView->GetLineCount())
3982 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3984 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
3987 if (nBreaks[m_nThisPane] < nMaxBreaks)
3988 return nMaxBreaks - nBreaks[m_nThisPane];
3994 * @brief Invalidate sub line index cache from the specified index to the end of file.
3995 * @param [in] nLineIndex Index of the first line to invalidate
3997 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3999 CMergeDoc * pDoc = GetDocument();
4000 ASSERT(pDoc != nullptr);
4002 // We have to invalidate sub line index cache on both panes.
4003 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4005 CMergeEditView *pView = GetGroupView(nPane);
4006 if (pView != nullptr)
4007 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
4011 void CMergeEditView::SetWordWrapping( bool bWordWrap )
4013 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
4014 GetGroupView(pane)->m_bWordWrap = bWordWrap;
4015 CCrystalTextView::SetWordWrapping(bWordWrap);
4019 * @brief Swap the positions of the two panes
4021 void CMergeEditView::OnViewSwapPanes12()
4023 GetDocument()->SwapFiles(0, 1);
4027 * @brief Swap the positions of the two panes
4029 void CMergeEditView::OnViewSwapPanes23()
4031 GetDocument()->SwapFiles(1, 2);
4035 * @brief Swap the positions of the two panes
4037 void CMergeEditView::OnViewSwapPanes13()
4039 GetDocument()->SwapFiles(0, 2);
4043 * @brief Determine if difference is visible on screen.
4044 * @param [in] nDiff Number of diff to check.
4045 * @return true if difference is visible.
4047 bool CMergeEditView::IsDiffVisible(int nDiff)
4049 const CMergeDoc *pd = GetDocument();
4052 pd->m_diffList.GetDiff(nDiff, diff);
4054 return IsDiffVisible(diff);
4058 * @brief Determine if difference is visible on screen.
4059 * @param [in] diff diff to check.
4060 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
4061 * @return true if difference is visible, false otherwise.
4063 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
4065 const int nDiffStart = GetSubLineIndex(diff.dbegin);
4066 const int nDiffEnd = GetSubLineIndex(diff.dend);
4067 // Diff's height is last line - first line + last line's line count
4068 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
4070 // If diff first line outside current view - context OR
4071 // if diff last line outside current view - context OR
4072 // if diff is bigger than screen
4073 if ((nDiffStart < m_nTopSubLine) ||
4074 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
4075 (nDiffHeight >= GetScreenLines()))
4085 /** @brief Open help from mainframe when user presses F1*/
4086 void CMergeEditView::OnHelp()
4088 theApp.ShowHelp(MergeViewHelpLocation);
4092 * @brief Called after document is loaded.
4093 * This function is called from CMergeDoc::OpenDocs() after documents are
4094 * loaded. So this is good place to set View's options etc.
4096 void CMergeEditView::DocumentsLoaded()
4098 if (GetDocument()->m_ptBuf[m_nThisPane]->GetTableEditing())
4101 if (m_nThisPane == GetDocument()->m_nBuffers - 1 && !m_bDetailView)
4106 SetTopMargin(false);
4109 // Enable/disable automatic rescan (rescanning after edit)
4110 EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
4112 // SetTextType will revert to language dependent defaults for tab
4113 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
4114 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
4115 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
4116 GetDocument()->IsMixedEOL(m_nThisPane);
4117 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_EOL), mixedEOLs);
4118 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
4119 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
4120 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4122 // Enable Backspace at beginning of line
4123 SetDisableBSAtSOL(false);
4125 // Set tab type (tabs/spaces)
4126 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
4127 SetInsertTabs(bInsertTabs);
4129 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
4130 // disabled) after docs are open in screen. So lets make sure they are
4131 // really updated, even though this is unnecessary in most cases.
4132 RecalcHorzScrollBar();
4133 RecalcVertScrollBar();
4137 * @brief Update LocationView position.
4138 * This function updates LocationView position to given lines.
4139 * Usually we want to lines in file compare view and area in
4140 * LocationView to match. Be extra carefull to not call non-existing
4142 * @param [in] nTopLine Top line of current view.
4143 * @param [in] nBottomLine Bottom line of current view.
4145 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
4146 int nBottomLine /*= -1*/)
4148 CMergeDoc *pDoc = GetDocument();
4149 if (pDoc == nullptr)
4152 CLocationView *pLocationView = pDoc->GetLocationView();
4154 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
4156 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
4161 * @brief Enable/Disable view's selection margins.
4162 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
4163 * useful. But it appears many users don't use/need those features and for them
4164 * selection margins are just wasted screen estate.
4166 void CMergeEditView::OnViewMargin()
4168 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
4169 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
4171 SetSelectionMargin(!bViewMargin);
4172 CMergeDoc *pDoc = GetDocument();
4173 pDoc->RefreshOptions();
4174 pDoc->UpdateAllViews(this);
4178 * @brief Update GUI for Enable/Disable view's selection margin.
4179 * @param [in] pCmdUI Pointer to UI item to update.
4181 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
4183 pCmdUI->Enable(true);
4184 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
4188 * @brief Create the "Change Scheme" sub menu.
4189 * @param [in] pCmdUI Pointer to UI item to update.
4191 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
4193 // Delete the place holder menu.
4194 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
4196 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
4198 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
4199 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
4200 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
4202 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
4204 name = theApp.LoadString(i);
4205 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
4208 pCmdUI->Enable(true);
4212 * @brief Change the editor's syntax highlighting scheme.
4213 * @param [in] nID Selected color scheme sub menu id.
4215 void CMergeEditView::OnChangeScheme(UINT nID)
4217 CMergeDoc *pDoc = GetDocument();
4218 ASSERT(pDoc != nullptr);
4220 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4222 CMergeEditView *pView = GetGroupView(nPane);
4223 ASSERT(pView != nullptr);
4225 if (pView != nullptr)
4227 pView->SetTextType(CrystalLineParser::TextType(nID - ID_COLORSCHEME_FIRST));
4228 pView->SetDisableBSAtSOL(false);
4236 * @brief Enable all color schemes sub menu items.
4237 * @param [in] pCmdUI Pointer to UI item to update.
4239 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
4241 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
4242 pCmdUI->SetRadio(bIsCurrentScheme);
4243 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
4247 * @brief Called when mouse's wheel is scrolled.
4249 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
4251 if ( nFlags == MK_CONTROL )
4253 short amount = zDelta < 0 ? -1: 1;
4256 // no default CCrystalTextView
4257 return CView::OnMouseWheel(nFlags, zDelta, pt);
4260 if (nFlags == MK_SHIFT)
4262 SCROLLINFO si = { sizeof SCROLLINFO };
4263 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4265 VERIFY(GetScrollInfo(SB_HORZ, &si));
4268 si.nPos -= zDelta / 40;
4269 if (si.nPos > si.nMax) si.nPos = si.nMax;
4270 if (si.nPos < si.nMin) si.nPos = si.nMin;
4272 SetScrollInfo(SB_HORZ, &si);
4275 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4277 // no default CCrystalTextView
4278 return CView::OnMouseWheel(nFlags, zDelta, pt);
4281 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
4285 * @brief Called when mouse's horizontal wheel is scrolled.
4287 void CMergeEditView::OnMouseHWheel(UINT nFlags, short zDelta, CPoint pt)
4289 SCROLLINFO si = { sizeof SCROLLINFO };
4290 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
4292 VERIFY(GetScrollInfo(SB_HORZ, &si));
4295 si.nPos += zDelta / 40;
4296 if (si.nPos > si.nMax) si.nPos = si.nMax;
4297 if (si.nPos < si.nMin) si.nPos = si.nMin;
4299 SetScrollInfo(SB_HORZ, &si);
4302 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
4304 // no default CCrystalTextView
4305 CView::OnMouseHWheel(nFlags, zDelta, pt);
4309 * @brief Change font size (zoom) in views.
4310 * @param [in] amount Amount of change/zoom, negative number makes
4311 * font smaller, positive number bigger and 0 reset the font size.
4313 void CMergeEditView::ZoomText(short amount)
4318 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4319 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4323 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4326 nPointSize += amount;
4330 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4332 CMergeDoc *pDoc = GetDocument();
4333 ASSERT(pDoc != nullptr);
4335 if (pDoc != nullptr)
4337 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4339 CMergeEditView *pView = GetGroupView(nPane);
4340 ASSERT(pView != nullptr);
4342 if (pView != nullptr)
4350 bool CMergeEditView::QueryEditable()
4352 return m_bDetailView ? false : !GetDocument()->m_ptBuf[m_nThisPane]->GetReadOnly();
4356 * @brief Adjust the point to remain in the displayed diff
4358 * @return Tells if the point has been changed
4360 bool CMergeEditView::EnsureInDiff(CPoint& pt)
4362 int nLineCount = GetLineCount();
4363 if (m_lineBegin >= nLineCount)
4364 m_lineBegin = nLineCount - 1;
4365 if (m_lineEnd >= nLineCount)
4366 m_lineEnd = nLineCount - 1;
4368 int diffLength = m_lineEnd - m_lineBegin + 1;
4369 // first get the degenerate case out of the way
4371 if (diffLength == 0)
4373 if (pt.y == m_lineBegin && pt.x == 0)
4381 if (pt.y < m_lineBegin)
4387 // diff is defined and not below diff
4388 if (m_lineEnd > -1 && pt.y > m_lineEnd)
4391 pt.x = GetLineLength(pt.y);
4397 void CMergeEditView::EnsureVisible(CPoint pt)
4402 // ensure we remain in diff
4403 if (EnsureInDiff(ptNew))
4404 SetCursorPos(ptNew);
4406 CCrystalTextView::EnsureVisible(ptNew);
4409 void CMergeEditView::EnsureVisible(CPoint ptStart, CPoint ptEnd)
4411 CCrystalTextView::EnsureVisible(ptStart, ptEnd);
4414 void CMergeEditView::SetSelection(const CPoint& ptStart, const CPoint& ptEnd, bool bUpdateView)
4416 CPoint ptStartNew = ptStart;
4417 CPoint ptEndNew = ptEnd;
4420 // ensure we remain in diff
4421 EnsureInDiff(ptStartNew);
4422 EnsureInDiff(ptEndNew);
4424 CCrystalTextView::SetSelection(ptStartNew, ptEndNew, bUpdateView);
4427 void CMergeEditView::ScrollToSubLine(int nNewTopLine, bool bNoSmoothScroll /*= FALSE*/, bool bTrackScrollBar /*= TRUE*/)
4431 int nLineCount = GetLineCount();
4432 if (m_lineBegin >= nLineCount)
4433 m_lineBegin = nLineCount - 1;
4434 if (m_lineEnd >= nLineCount)
4435 m_lineEnd = nLineCount - 1;
4437 // ensure we remain in diff
4438 int sublineBegin = GetSubLineIndex(m_lineBegin);
4439 int sublineEnd = m_lineEnd < 0 ? -1 : GetSubLineIndex(m_lineEnd) + GetSubLines(m_lineEnd) - 1;
4440 int diffLength = sublineEnd - sublineBegin + 1;
4441 int displayLength = GetScreenLines();
4442 if (diffLength <= displayLength)
4443 nNewTopLine = sublineBegin;
4446 if (nNewTopLine < sublineBegin)
4447 nNewTopLine = sublineBegin;
4448 if (nNewTopLine + displayLength - 1 > sublineEnd)
4449 nNewTopLine = GetSubLineIndex(sublineEnd - displayLength + 1);
4452 CPoint pt = GetCursorPos();
4453 if (EnsureInDiff(pt))
4456 CPoint ptSelStart, ptSelEnd;
4457 GetSelection(ptSelStart, ptSelEnd);
4458 if (EnsureInDiff(ptSelStart) || EnsureInDiff(ptSelEnd))
4459 SetSelection(ptSelStart, ptSelEnd);
4461 CCrystalTextView::ScrollToSubLine(nNewTopLine, bNoSmoothScroll, bTrackScrollBar);
4464 void CMergeEditView::SetActivePane()
4466 auto* pwndSplitterChild = GetParentSplitter(this, false);
4467 if (!pwndSplitterChild)
4469 if (pwndSplitterChild->GetColumnCount() > 1)
4470 pwndSplitterChild->SetActivePane(0, m_nThisPane);
4472 pwndSplitterChild->SetActivePane(m_nThisPane, 0);
4476 * @brief Called when user selects View/Zoom In from menu.
4478 void CMergeEditView::OnViewZoomIn()
4484 * @brief Called when user selects View/Zoom Out from menu.
4486 void CMergeEditView::OnViewZoomOut()
4492 * @brief Called when user selects View/Zoom Normal from menu.
4494 void CMergeEditView::OnViewZoomNormal()
4499 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4501 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4503 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4507 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4510 void CMergeEditView::OnWindowSplit()
4513 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4514 CMergeDoc *pDoc = GetDocument();
4515 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4516 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4517 int nBuffer = m_nThisPane;
4518 if (pDoc->m_nGroups <= 2)
4520 wndSplitter.SplitRow(1);
4521 wndSplitter.EqualizeRows();
4525 wndSplitter.SetActivePane(0, 0);
4526 wndSplitter.DeleteRow(1);
4527 pDoc->GetView(0, nBuffer)->SetActivePane();
4531 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4533 pCmdUI->Enable(!m_bDetailView);
4534 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);
4537 void CMergeEditView::OnStatusBarDblClick(NMHDR* pNMHDR, LRESULT* pResult)
4540 LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
4541 const int pane = pNMItemActivate->iItem / 4;
4542 CMergeDoc* pDoc = GetDocument();
4543 if (pane >= pDoc->m_nBuffers || !GetParentFrame()->IsChild(CWnd::FromHandle(pNMItemActivate->hdr.hwndFrom)))
4546 switch (pNMItemActivate->iItem % 4)
4549 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_EDIT_WMGOTO);
4552 pDoc->GetView(0, pane)->PostMessage(WM_COMMAND, ID_FILE_ENCODING);
4557 ::GetCursorPos(&point);
4560 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEEDITFRAME_STATUSBAR_EOL));
4561 theApp.TranslateMenu(menu.m_hMenu);
4562 menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, GetDocument()->GetView(0, pane));
4566 pDoc->m_ptBuf[pane]->SetReadOnly(!GetDocument()->m_ptBuf[pane]->GetReadOnly());