1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2 of the License, or
9 // (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 /////////////////////////////////////////////////////////////////////////////
22 * @file MergeEditView.cpp
24 * @brief Implementation of the CMergeEditView class
28 #include "MergeEditView.h"
32 #include "LocationView.h"
35 #include "OptionsMgr.h"
36 #include "OptionsDiffColors.h"
37 #include "FileTransform.h"
39 #include "WMGotoDlg.h"
40 #include "OptionsDef.h"
41 #include "SyntaxColors.h"
42 #include "MergeEditFrm.h"
43 #include "MergeLineFlags.h"
45 #include "DropHandler.h"
47 #include "ShellContextMenu.h"
54 using CrystalLineParser::TEXTBLOCK;
56 /** @brief Timer ID for delayed rescan. */
57 const UINT IDT_RESCAN = 2;
58 /** @brief Timer timeout for delayed rescan. */
59 const UINT RESCAN_TIMEOUT = 1000;
61 /** @brief Location for file compare specific help to open. */
62 static TCHAR MergeViewHelpLocation[] = _T("::/htmlhelp/Compare_files.html");
64 /////////////////////////////////////////////////////////////////////////////
67 IMPLEMENT_DYNCREATE(CMergeEditView, CCrystalEditViewEx)
69 CMergeEditView::CMergeEditView()
70 : m_bCurrentLineIsDiff(false)
73 , m_bDetailView(false)
74 , m_piMergeEditStatus(nullptr)
75 , m_bAutomaticRescan(false)
76 , fTimerWaitingForIdle(0)
79 , m_CurrentPredifferID(0)
81 SetParser(&m_xParser);
83 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
86 CMergeEditView::~CMergeEditView()
91 BEGIN_MESSAGE_MAP(CMergeEditView, CCrystalEditViewEx)
92 //{{AFX_MSG_MAP(CMergeEditView)
93 ON_COMMAND(ID_CURDIFF, OnCurdiff)
94 ON_UPDATE_COMMAND_UI(ID_CURDIFF, OnUpdateCurdiff)
95 ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
96 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy)
97 ON_COMMAND(ID_EDIT_CUT, OnEditCut)
98 ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut)
99 ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)
100 ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste)
101 ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)
102 ON_COMMAND(ID_FIRSTDIFF, OnFirstdiff)
103 ON_UPDATE_COMMAND_UI(ID_FIRSTDIFF, OnUpdateFirstdiff)
104 ON_COMMAND(ID_LASTDIFF, OnLastdiff)
105 ON_UPDATE_COMMAND_UI(ID_LASTDIFF, OnUpdateLastdiff)
106 ON_COMMAND(ID_NEXTDIFF, OnNextdiff)
107 ON_UPDATE_COMMAND_UI(ID_NEXTDIFF, OnUpdateNextdiff)
108 ON_COMMAND(ID_PREVDIFF, OnPrevdiff)
109 ON_UPDATE_COMMAND_UI(ID_PREVDIFF, OnUpdatePrevdiff)
110 ON_COMMAND(ID_NEXTCONFLICT, OnNextConflict)
111 ON_UPDATE_COMMAND_UI(ID_NEXTCONFLICT, OnUpdateNextConflict)
112 ON_COMMAND(ID_PREVCONFLICT, OnPrevConflict)
113 ON_UPDATE_COMMAND_UI(ID_PREVCONFLICT, OnUpdatePrevConflict)
114 ON_COMMAND(ID_NEXTDIFFLM, OnNextdiffLM)
115 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLM, OnUpdateNextdiffLM)
116 ON_COMMAND(ID_PREVDIFFLM, OnPrevdiffLM)
117 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLM, OnUpdatePrevdiffLM)
118 ON_COMMAND(ID_NEXTDIFFLR, OnNextdiffLR)
119 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLR, OnUpdateNextdiffLR)
120 ON_COMMAND(ID_PREVDIFFLR, OnPrevdiffLR)
121 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLR, OnUpdatePrevdiffLR)
122 ON_COMMAND(ID_NEXTDIFFMR, OnNextdiffMR)
123 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMR, OnUpdateNextdiffMR)
124 ON_COMMAND(ID_PREVDIFFMR, OnPrevdiffMR)
125 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMR, OnUpdatePrevdiffMR)
126 ON_COMMAND(ID_NEXTDIFFLO, OnNextdiffLO)
127 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFLO, OnUpdateNextdiffLO)
128 ON_COMMAND(ID_PREVDIFFLO, OnPrevdiffLO)
129 ON_UPDATE_COMMAND_UI(ID_PREVDIFFLO, OnUpdatePrevdiffLO)
130 ON_COMMAND(ID_NEXTDIFFMO, OnNextdiffMO)
131 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFMO, OnUpdateNextdiffMO)
132 ON_COMMAND(ID_PREVDIFFMO, OnPrevdiffMO)
133 ON_UPDATE_COMMAND_UI(ID_PREVDIFFMO, OnUpdatePrevdiffMO)
134 ON_COMMAND(ID_NEXTDIFFRO, OnNextdiffRO)
135 ON_UPDATE_COMMAND_UI(ID_NEXTDIFFRO, OnUpdateNextdiffRO)
136 ON_COMMAND(ID_PREVDIFFRO, OnPrevdiffRO)
137 ON_UPDATE_COMMAND_UI(ID_PREVDIFFRO, OnUpdatePrevdiffRO)
138 ON_WM_LBUTTONDBLCLK()
141 ON_COMMAND(ID_ALL_LEFT, OnAllLeft)
142 ON_UPDATE_COMMAND_UI(ID_ALL_LEFT, OnUpdateAllLeft)
143 ON_COMMAND(ID_ALL_RIGHT, OnAllRight)
144 ON_UPDATE_COMMAND_UI(ID_ALL_RIGHT, OnUpdateAllRight)
145 ON_COMMAND(ID_AUTO_MERGE, OnAutoMerge)
146 ON_UPDATE_COMMAND_UI(ID_AUTO_MERGE, OnUpdateAutoMerge)
147 ON_COMMAND(ID_L2R, OnL2r)
148 ON_UPDATE_COMMAND_UI(ID_L2R, OnUpdateL2r)
149 ON_COMMAND(ID_R2L, OnR2l)
150 ON_UPDATE_COMMAND_UI(ID_R2L, OnUpdateR2l)
151 ON_COMMAND(ID_COPY_FROM_LEFT, OnCopyFromLeft)
152 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_LEFT, OnUpdateCopyFromLeft)
153 ON_COMMAND(ID_COPY_FROM_RIGHT, OnCopyFromRight)
154 ON_UPDATE_COMMAND_UI(ID_COPY_FROM_RIGHT, OnUpdateCopyFromRight)
155 ON_COMMAND(ID_ADD_SYNCPOINT, OnAddSyncPoint)
156 ON_COMMAND(ID_CLEAR_SYNCPOINTS, OnClearSyncPoints)
157 ON_UPDATE_COMMAND_UI(ID_CLEAR_SYNCPOINTS, OnUpdateClearSyncPoints)
158 ON_UPDATE_COMMAND_UI(ID_EDIT_UNDO, OnUpdateEditUndo)
159 ON_COMMAND(ID_EDIT_REDO, OnEditRedo)
160 ON_UPDATE_COMMAND_UI(ID_EDIT_REDO, OnUpdateEditRedo)
162 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_LEFT, OnUpdateFileSaveLeft)
163 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_MIDDLE, OnUpdateFileSaveMiddle)
164 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE_RIGHT, OnUpdateFileSaveRight)
165 ON_COMMAND(ID_REFRESH, OnRefresh)
166 ON_UPDATE_COMMAND_UI(ID_FILE_SAVE, OnUpdateFileSave)
167 ON_COMMAND(ID_SELECTLINEDIFF, OnSelectLineDiff<false>)
168 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
169 ON_COMMAND(ID_SELECTPREVLINEDIFF, OnSelectLineDiff<true>)
170 ON_UPDATE_COMMAND_UI(ID_SELECTPREVLINEDIFF, OnUpdateSelectLineDiff)
172 ON_UPDATE_COMMAND_UI(ID_EDIT_REPLACE, OnUpdateEditReplace)
173 ON_COMMAND(ID_FILE_LEFT_READONLY, OnLeftReadOnly)
174 ON_UPDATE_COMMAND_UI(ID_FILE_LEFT_READONLY, OnUpdateLeftReadOnly)
175 ON_COMMAND(ID_FILE_MIDDLE_READONLY, OnMiddleReadOnly)
176 ON_UPDATE_COMMAND_UI(ID_FILE_MIDDLE_READONLY, OnUpdateMiddleReadOnly)
177 ON_COMMAND(ID_FILE_RIGHT_READONLY, OnRightReadOnly)
178 ON_UPDATE_COMMAND_UI(ID_FILE_RIGHT_READONLY, OnUpdateRightReadOnly)
179 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_RO, OnUpdateStatusRO)
180 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_RO, OnUpdateStatusRO)
181 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_RO, OnUpdateStatusRO)
182 ON_COMMAND_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnConvertEolTo)
183 ON_UPDATE_COMMAND_UI_RANGE(ID_EOL_TO_DOS, ID_EOL_TO_MAC, OnUpdateConvertEolTo)
184 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE0FILE_EOL, OnUpdateStatusEOL)
185 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE1FILE_EOL, OnUpdateStatusEOL)
186 ON_UPDATE_COMMAND_UI(ID_STATUS_PANE2FILE_EOL, OnUpdateStatusEOL)
187 ON_COMMAND(ID_L2RNEXT, OnL2RNext)
188 ON_UPDATE_COMMAND_UI(ID_L2RNEXT, OnUpdateL2RNext)
189 ON_COMMAND(ID_R2LNEXT, OnR2LNext)
190 ON_UPDATE_COMMAND_UI(ID_R2LNEXT, OnUpdateR2LNext)
191 ON_COMMAND(ID_WINDOW_CHANGE_PANE, OnChangePane)
192 ON_COMMAND(ID_NEXT_PANE, OnChangePane)
193 ON_COMMAND(ID_EDIT_WMGOTO, OnWMGoto)
194 ON_COMMAND(ID_FILE_SHELLMENU, OnShellMenu)
195 ON_UPDATE_COMMAND_UI(ID_FILE_SHELLMENU, OnUpdateShellMenu)
196 ON_COMMAND_RANGE(ID_SCRIPT_FIRST, ID_SCRIPT_LAST, OnScripts)
197 ON_COMMAND(ID_NO_PREDIFFER, OnNoPrediffer)
198 ON_UPDATE_COMMAND_UI(ID_NO_PREDIFFER, OnUpdateNoPrediffer)
199 ON_COMMAND_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnPrediffer)
200 ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFERS_FIRST, ID_PREDIFFERS_LAST, OnUpdatePrediffer)
203 ON_COMMAND(ID_EDIT_COPY_LINENUMBERS, OnEditCopyLineNumbers)
204 ON_UPDATE_COMMAND_UI(ID_EDIT_COPY_LINENUMBERS, OnUpdateEditCopyLinenumbers)
205 ON_COMMAND(ID_VIEW_LINEDIFFS, OnViewLineDiffs)
206 ON_UPDATE_COMMAND_UI(ID_VIEW_LINEDIFFS, OnUpdateViewLineDiffs)
207 ON_COMMAND(ID_VIEW_WORDWRAP, OnViewWordWrap)
208 ON_UPDATE_COMMAND_UI(ID_VIEW_WORDWRAP, OnUpdateViewWordWrap)
209 ON_COMMAND(ID_VIEW_LINENUMBERS, OnViewLineNumbers)
210 ON_UPDATE_COMMAND_UI(ID_VIEW_LINENUMBERS, OnUpdateViewLineNumbers)
211 ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
212 ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
213 ON_COMMAND(ID_FILE_OPEN_REGISTERED, OnOpenFile)
214 ON_COMMAND(ID_FILE_OPEN_WITHEDITOR, OnOpenFileWithEditor)
215 ON_COMMAND(ID_FILE_OPEN_WITH, OnOpenFileWith)
216 ON_COMMAND(ID_VIEW_SWAPPANES, OnViewSwapPanes)
217 ON_UPDATE_COMMAND_UI(ID_NO_EDIT_SCRIPTS, OnUpdateNoEditScripts)
220 ON_COMMAND(ID_HELP, OnHelp)
221 ON_COMMAND(ID_VIEW_FILEMARGIN, OnViewMargin)
222 ON_UPDATE_COMMAND_UI(ID_VIEW_FILEMARGIN, OnUpdateViewMargin)
223 ON_UPDATE_COMMAND_UI(ID_VIEW_CHANGESCHEME, OnUpdateViewChangeScheme)
224 ON_COMMAND_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnChangeScheme)
225 ON_UPDATE_COMMAND_UI_RANGE(ID_COLORSCHEME_FIRST, ID_COLORSCHEME_LAST, OnUpdateChangeScheme)
227 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomIn)
228 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomOut)
229 ON_COMMAND(ID_VIEW_ZOOMNORMAL, OnViewZoomNormal)
230 ON_COMMAND(ID_WINDOW_SPLIT, OnWindowSplit)
231 ON_UPDATE_COMMAND_UI(ID_WINDOW_SPLIT, OnUpdateWindowSplit)
236 /////////////////////////////////////////////////////////////////////////////
237 // CMergeEditView diagnostics
240 CMergeDoc* CMergeEditView::GetDocument() // non-debug version is inline
242 ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMergeDoc)));
243 return (CMergeDoc*)m_pDocument;
248 /////////////////////////////////////////////////////////////////////////////
249 // CMergeEditView message handlers
252 * @brief Return text buffer for file in view
254 CCrystalTextBuffer *CMergeEditView::LocateTextBuffer()
256 return GetDocument()->m_ptBuf[m_nThisPane].get();
260 * @brief Update any resources necessary after a GUI language change
262 void CMergeEditView::UpdateResources()
266 CMergeEditView *CMergeEditView::GetGroupView(int nBuffer) const
268 return GetDocument()->GetView(m_nThisGroup, nBuffer);
271 void CMergeEditView::PrimeListWithFile()
273 // Set the tab size now, just in case the options change...
274 // We don't update it at the end of OnOptions,
275 // we can update it safely now
276 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
279 * @brief Return text from line given
281 CString CMergeEditView::GetLineText(int idx)
283 return GetLineChars(idx);
287 * @brief Return text from selection
289 CString CMergeEditView::GetSelectedText()
291 CPoint ptStart, ptEnd;
293 GetSelection(ptStart, ptEnd);
294 if (ptStart != ptEnd)
295 GetTextWithoutEmptys(ptStart.y, ptStart.x, ptEnd.y, ptEnd.x, strText);
300 * @brief Get diffs inside selection.
301 * @param [out] firstDiff First diff inside selection
302 * @param [out] lastDiff Last diff inside selection
303 * @param [out] firstWordDiff First word level diff inside selection
304 * @param [out] lastWordDiff Last word level diff inside selection
305 * @note -1 is returned in parameters if diffs cannot be determined
306 * @todo This shouldn't be called when there is no diffs, so replace
307 * first 'if' with ASSERT()?
309 void CMergeEditView::GetFullySelectedDiffs(int & firstDiff, int & lastDiff, int & firstWordDiff, int & lastWordDiff, const CPoint *pptStart, const CPoint *pptEnd)
316 CMergeDoc *pd = GetDocument();
317 const int nDiffs = pd->m_diffList.GetSignificantDiffs();
321 int firstLine, lastLine;
322 CPoint ptStart, ptEnd;
323 GetSelection(ptStart, ptEnd);
324 if (pptStart != nullptr)
326 if (pptEnd != nullptr)
328 firstLine = ptStart.y;
331 firstDiff = pd->m_diffList.LineToDiff(firstLine);
332 bool firstLineIsNotInDiff = firstDiff == -1;
335 firstDiff = pd->m_diffList.NextSignificantDiffFromLine(firstLine);
340 lastDiff = pd->m_diffList.LineToDiff(lastLine);
341 bool lastLineIsNotInDiff = lastDiff == -1;
343 lastDiff = pd->m_diffList.PrevSignificantDiffFromLine(lastLine);
344 if (lastDiff < firstDiff)
351 if (firstDiff != -1 && lastDiff != -1)
355 if (ptStart != ptEnd)
357 VERIFY(pd->m_diffList.GetDiff(firstDiff, di));
358 if (lastLineIsNotInDiff && (firstLineIsNotInDiff || (di.dbegin == firstLine && ptStart.x == 0)))
364 if (firstWordDiff == -1)
366 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(firstDiff);
367 for (size_t i = 0; i < worddiffs.size(); ++i)
369 int worddiffLen = worddiffs[i].end[m_nThisPane] - worddiffs[i].begin[m_nThisPane];
370 if (worddiffs[i].endline[m_nThisPane] > firstLine ||
371 (firstLine == worddiffs[i].endline[m_nThisPane] &&
372 worddiffs[i].end[m_nThisPane] - (worddiffLen == 0 ? 0 : 1) >= ptStart.x))
374 firstWordDiff = static_cast<int>(i);
379 if (firstLine >= di.dbegin && firstWordDiff == -1)
386 VERIFY(pd->m_diffList.GetDiff(lastDiff, di));
387 vector<WordDiff> worddiffs = pd->GetWordDiffArrayInDiffBlock(lastDiff);
388 for (size_t i = worddiffs.size() - 1; i != (size_t)-1; --i)
390 if (worddiffs[i].beginline[m_nThisPane] < lastLine ||
391 (lastLine == worddiffs[i].beginline[m_nThisPane] && worddiffs[i].begin[m_nThisPane] + 1 <= ptEnd.x))
393 lastWordDiff = static_cast<int>(i);
398 if (lastLine <= di.dend && lastWordDiff == -1)
401 if (firstDiff == lastDiff && (lastWordDiff != -1 && lastWordDiff < firstWordDiff))
408 else if (lastDiff < firstDiff || (firstDiff == lastDiff && firstWordDiff == -1 && lastWordDiff == -1))
425 ASSERT(firstDiff == -1 ? (lastDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
426 ASSERT(lastDiff == -1 ? (firstDiff == -1 && firstWordDiff == -1 && lastWordDiff == -1) : true);
427 ASSERT(firstDiff != -1 ? firstWordDiff != -1 : true);
430 std::map<int, std::vector<int>> CMergeEditView::GetColumnSelectedWordDiffIndice()
432 CMergeDoc *pDoc = GetDocument();
433 std::map<int, std::vector<int>> ret;
434 std::map<int, std::vector<int> *> list;
435 CPoint ptStart, ptEnd;
436 GetSelection(ptStart, ptEnd);
437 for (int nLine = ptStart.y; nLine <= ptEnd.y; ++nLine)
439 if (pDoc->m_diffList.LineToDiff(nLine) != -1)
441 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
443 GetColumnSelection(nLine, nLeft, nRight);
444 CPoint ptStart2, ptEnd2;
447 ptStart2.y = ptEnd2.y = nLine;
448 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff, &ptStart2, &ptEnd2);
449 if (firstDiff != -1 && lastDiff != -1)
451 std::vector<int> *pWordDiffs;
452 if (list.find(firstDiff) == list.end())
453 list.insert(std::pair<int, std::vector<int> *>(firstDiff, new std::vector<int>()));
454 pWordDiffs = list[firstDiff];
455 for (int i = firstWordDiff; i <= lastWordDiff; ++i)
457 if (pWordDiffs->empty() || i != (*pWordDiffs)[pWordDiffs->size() - 1])
458 pWordDiffs->push_back(i);
463 for (auto& it : list)
464 ret.insert(std::pair<int, std::vector<int>>(it.first, *it.second));
468 void CMergeEditView::OnInitialUpdate()
471 CCrystalEditViewEx::OnInitialUpdate();
473 SetFont(dynamic_cast<CMainFrame*>(AfxGetMainWnd())->m_lfDiff);
474 SetAlternateDropTarget(new DropHandler(std::bind(&CMergeEditView::OnDropFiles, this, std::placeholders::_1)));
480 void CMergeEditView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)
482 CCrystalEditViewEx::OnActivateView(bActivate, pActivateView, pDeactiveView);
484 CMergeDoc* pDoc = GetDocument();
485 pDoc->UpdateHeaderActivity(m_nThisPane, !!bActivate);
488 std::vector<TEXTBLOCK> CMergeEditView::GetAdditionalTextBlocks (int nLineIndex)
490 static const std::vector<TEXTBLOCK> emptyBlocks;
493 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
497 DWORD dwLineFlags = GetLineFlags(nLineIndex);
498 if ((dwLineFlags & LF_SNP) == LF_SNP || (dwLineFlags & LF_DIFF) != LF_DIFF || (dwLineFlags & LF_MOVED) == LF_MOVED)
501 if (!GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT))
504 CMergeDoc *pDoc = GetDocument();
505 if (pDoc->IsEditedAfterRescan(m_nThisPane))
508 int nDiff = pDoc->m_diffList.LineToDiff(nLineIndex);
513 pDoc->m_diffList.GetDiff(nDiff, cd);
514 int unemptyLineCount = 0;
515 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
517 if (cd.begin[nPane] != cd.end[nPane] + 1)
520 if (unemptyLineCount < 2)
523 vector<WordDiff> worddiffs = pDoc->GetWordDiffArray(nLineIndex);
524 size_t nWordDiffs = worddiffs.size();
526 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
528 std::vector<TEXTBLOCK> blocks(nWordDiffs * 2 + 1);
529 blocks[0].m_nCharPos = 0;
530 blocks[0].m_nColorIndex = COLORINDEX_NONE;
531 blocks[0].m_nBgColorIndex = COLORINDEX_NONE;
533 for (i = 0, j = 1; i < nWordDiffs; i++)
535 if (worddiffs[i].beginline[m_nThisPane] > nLineIndex || worddiffs[i].endline[m_nThisPane] < nLineIndex )
537 if (pDoc->m_nBuffers > 2)
539 if (m_nThisPane == 0 && worddiffs[i].op == OP_3RDONLY)
541 else if (m_nThisPane == 2 && worddiffs[i].op == OP_1STONLY)
544 int begin[3], end[3];
545 bool deleted = false;
546 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
548 begin[pane] = (worddiffs[i].beginline[pane] < nLineIndex) ? 0 : worddiffs[i].begin[pane];
549 end[pane] = (worddiffs[i].endline[pane] > nLineIndex) ? GetGroupView(pane)->GetLineLength(nLineIndex) : worddiffs[i].end[pane];
550 if (worddiffs[i].beginline[pane] == worddiffs[i].endline[pane] &&
551 worddiffs[i].begin[pane] == worddiffs[i].end[pane])
554 blocks[j].m_nCharPos = begin[m_nThisPane];
555 if (lineInCurrentDiff)
557 if (m_cachedColors.clrSelDiffText != CLR_NONE)
558 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT1 | COLORINDEX_APPLYFORCE;
560 blocks[j].m_nColorIndex = COLORINDEX_NONE;
561 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
562 (deleted ? COLORINDEX_HIGHLIGHTBKGND4 : COLORINDEX_HIGHLIGHTBKGND1);
566 if (m_cachedColors.clrDiffText != CLR_NONE)
567 blocks[j].m_nColorIndex = COLORINDEX_HIGHLIGHTTEXT2 | COLORINDEX_APPLYFORCE;
569 blocks[j].m_nColorIndex = COLORINDEX_NONE;
570 blocks[j].m_nBgColorIndex = COLORINDEX_APPLYFORCE |
571 (deleted ? COLORINDEX_HIGHLIGHTBKGND3 : COLORINDEX_HIGHLIGHTBKGND2);
574 blocks[j].m_nCharPos = end[m_nThisPane];
575 blocks[j].m_nColorIndex = COLORINDEX_NONE;
576 blocks[j].m_nBgColorIndex = COLORINDEX_NONE;
585 COLORREF CMergeEditView::GetColor(int nColorIndex)
587 switch (nColorIndex & ~COLORINDEX_APPLYFORCE)
589 case COLORINDEX_HIGHLIGHTBKGND1:
590 return m_cachedColors.clrSelWordDiff;
591 case COLORINDEX_HIGHLIGHTTEXT1:
592 return m_cachedColors.clrSelWordDiffText;
593 case COLORINDEX_HIGHLIGHTBKGND2:
594 return m_cachedColors.clrWordDiff;
595 case COLORINDEX_HIGHLIGHTTEXT2:
596 return m_cachedColors.clrWordDiffText;
597 case COLORINDEX_HIGHLIGHTBKGND3:
598 return m_cachedColors.clrWordDiffDeleted;
599 case COLORINDEX_HIGHLIGHTBKGND4:
600 return m_cachedColors.clrSelWordDiffDeleted;
603 return CCrystalTextView::GetColor(nColorIndex);
608 * @brief Determine text and background color for line
609 * @param [in] nLineIndex Index of line in view (NOT line in file)
610 * @param [out] crBkgnd Backround color for line
611 * @param [out] crText Text color for line
613 void CMergeEditView::GetLineColors(int nLineIndex, COLORREF & crBkgnd,
614 COLORREF & crText, bool & bDrawWhitespace)
616 DWORD ignoreFlags = 0;
617 GetLineColors2(nLineIndex, ignoreFlags, crBkgnd, crText, bDrawWhitespace);
621 * @brief Determine text and background color for line
622 * @param [in] nLineIndex Index of line in view (NOT line in file)
623 * @param [in] ignoreFlags Flags that caller wishes ignored
624 * @param [out] crBkgnd Backround color for line
625 * @param [out] crText Text color for line
627 * This version allows caller to suppress particular flags
629 void CMergeEditView::GetLineColors2(int nLineIndex, DWORD ignoreFlags, COLORREF & crBkgnd,
630 COLORREF & crText, bool & bDrawWhitespace)
632 if (GetLineCount() <= nLineIndex)
635 DWORD dwLineFlags = GetLineFlags(nLineIndex);
637 if (dwLineFlags & ignoreFlags)
638 dwLineFlags &= (~ignoreFlags);
642 // Line with WinMerge flag,
643 // Lines with only the LF_DIFF/LF_TRIVIAL flags are not colored with Winmerge colors
644 if (dwLineFlags & (LF_WINMERGE_FLAGS & ~LF_DIFF & ~LF_TRIVIAL & ~LF_MOVED & ~LF_SNP))
646 crText = m_cachedColors.clrDiffText;
647 bDrawWhitespace = true;
649 if (dwLineFlags & LF_GHOST)
651 crBkgnd = m_cachedColors.clrDiffDeleted;
656 // If no syntax hilighting
657 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
659 crBkgnd = GetColor (COLORINDEX_BKGND);
660 crText = GetColor (COLORINDEX_NORMALTEXT);
661 bDrawWhitespace = false;
664 // Line not inside diff, get colors from CrystalEditor
665 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
666 crText, bDrawWhitespace);
668 if (nLineIndex < m_lineBegin || nLineIndex > m_lineEnd)
670 crBkgnd = GetColor (COLORINDEX_WHITESPACE);
671 crText = GetColor (COLORINDEX_WHITESPACE);
672 bDrawWhitespace = false;
678 if (dwLineFlags & LF_WINMERGE_FLAGS)
680 crText = m_cachedColors.clrDiffText;
681 bDrawWhitespace = true;
682 bool lineInCurrentDiff = IsLineInCurrentDiff(nLineIndex);
684 if (dwLineFlags & LF_SNP)
686 if (lineInCurrentDiff)
688 if (dwLineFlags & LF_GHOST)
689 crBkgnd = m_cachedColors.clrSelSNPDeleted;
691 crBkgnd = m_cachedColors.clrSelSNP;
692 crText = m_cachedColors.clrSelSNPText;
696 if (dwLineFlags & LF_GHOST)
697 crBkgnd = m_cachedColors.clrSNPDeleted;
699 crBkgnd = m_cachedColors.clrSNP;
700 crText = m_cachedColors.clrSNPText;
704 else if (dwLineFlags & LF_DIFF)
706 if (lineInCurrentDiff)
708 if (dwLineFlags & LF_MOVED)
710 if (dwLineFlags & LF_GHOST)
711 crBkgnd = m_cachedColors.clrSelMovedDeleted;
713 crBkgnd = m_cachedColors.clrSelMoved;
714 crText = m_cachedColors.clrSelMovedText;
718 crBkgnd = m_cachedColors.clrSelDiff;
719 crText = m_cachedColors.clrSelDiffText;
725 if (dwLineFlags & LF_MOVED)
727 if (dwLineFlags & LF_GHOST)
728 crBkgnd = m_cachedColors.clrMovedDeleted;
730 crBkgnd = m_cachedColors.clrMoved;
731 crText = m_cachedColors.clrMovedText;
735 crBkgnd = m_cachedColors.clrDiff;
736 crText = m_cachedColors.clrDiffText;
741 else if (dwLineFlags & LF_TRIVIAL)
743 // trivial diff can not be selected
744 if (dwLineFlags & LF_GHOST)
745 // ghost lines in trivial diff has their own color
746 crBkgnd = m_cachedColors.clrTrivialDeleted;
748 crBkgnd = m_cachedColors.clrTrivial;
749 crText = m_cachedColors.clrTrivialText;
752 else if (dwLineFlags & LF_GHOST)
754 if (lineInCurrentDiff)
755 crBkgnd = m_cachedColors.clrSelDiffDeleted;
757 crBkgnd = m_cachedColors.clrDiffDeleted;
763 // Line not inside diff,
764 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
766 // If no syntax hilighting, get windows default colors
767 crBkgnd = GetColor (COLORINDEX_BKGND);
768 crText = GetColor (COLORINDEX_NORMALTEXT);
769 bDrawWhitespace = false;
772 // Syntax highlighting, get colors from CrystalEditor
773 CCrystalEditViewEx::GetLineColors(nLineIndex, crBkgnd,
774 crText, bDrawWhitespace);
779 * @brief Sync other pane position
781 void CMergeEditView::UpdateSiblingScrollPos (bool bHorz)
783 CSplitterWnd *pSplitterWnd = GetParentSplitter (this, false);
784 if (pSplitterWnd != nullptr)
786 // See CSplitterWnd::IdFromRowCol() implementation for details
787 int nCurrentRow = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) / 16;
788 int nCurrentCol = (GetDlgCtrlID () - AFX_IDW_PANE_FIRST) % 16;
789 ASSERT (nCurrentRow >= 0 && nCurrentRow < pSplitterWnd->GetRowCount ());
790 ASSERT (nCurrentCol >= 0 && nCurrentCol < pSplitterWnd->GetColumnCount ());
792 // limit the TopLine : must be smaller than GetLineCount for all the panels
793 int newTopSubLine = m_nTopSubLine;
794 int nRows = pSplitterWnd->GetRowCount ();
795 int nCols = pSplitterWnd->GetColumnCount ();
797 // for (nRow = 0; nRow < nRows; nRow++)
799 // for (int nCol = 0; nCol < nCols; nCol++)
801 // CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
802 // if (pSiblingView != nullptr)
803 // if (pSiblingView->GetSubLineCount() <= newTopSubLine)
804 // newTopSubLine = pSiblingView->GetSubLineCount()-1;
807 if (m_nTopSubLine != newTopSubLine)
808 ScrollToSubLine(newTopSubLine);
810 for (nRow = 0; nRow < nRows; nRow++)
812 for (int nCol = 0; nCol < nCols; nCol++)
814 if (!(nRow == nCurrentRow && nCol == nCurrentCol)) // We don't need to update ourselves
816 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
817 if (pSiblingView != nullptr && pSiblingView->m_nThisGroup == m_nThisGroup)
818 pSiblingView->OnUpdateSibling (this, bHorz);
826 * @brief Update other panes
828 void CMergeEditView::OnUpdateSibling (CCrystalTextView * pUpdateSource, bool bHorz)
830 if (pUpdateSource != this)
832 ASSERT (pUpdateSource != nullptr);
833 ASSERT_KINDOF (CCrystalTextView, pUpdateSource);
834 CMergeEditView *pSrcView = static_cast<CMergeEditView*>(pUpdateSource);
835 if (!bHorz) // changed this so bHorz works right
837 ASSERT (pSrcView->m_nTopSubLine >= 0);
839 // This ASSERT is wrong: panes have different files and
840 // different linecounts
841 // ASSERT (pSrcView->m_nTopLine < GetLineCount ());
842 if (pSrcView->m_nTopSubLine != m_nTopSubLine)
844 ScrollToSubLine (pSrcView->m_nTopSubLine, true, false);
846 RecalcVertScrollBar(true);
847 RecalcHorzScrollBar();
852 ASSERT (pSrcView->m_nOffsetChar >= 0);
854 // This ASSERT is wrong: panes have different files and
855 // different linelengths
856 // ASSERT (pSrcView->m_nOffsetChar < GetMaxLineLength ());
857 if (pSrcView->m_nOffsetChar != m_nOffsetChar)
859 ScrollToChar (pSrcView->m_nOffsetChar, true, false);
861 RecalcHorzScrollBar(true);
862 RecalcHorzScrollBar();
868 void CMergeEditView::OnDisplayDiff(int nDiff /*=0*/)
870 int newlineBegin, newlineEnd;
871 CMergeDoc *pd = GetDocument();
872 if (nDiff < 0 || nDiff >= pd->m_diffList.GetSize())
880 VERIFY(pd->m_diffList.GetDiff(nDiff, curDiff));
882 newlineBegin = curDiff.dbegin;
883 ASSERT (newlineBegin >= 0);
884 newlineEnd = curDiff.dend;
887 if (newlineBegin == m_lineBegin && newlineEnd == m_lineEnd)
889 m_lineBegin = newlineBegin;
890 m_lineEnd = newlineEnd;
892 // scroll to the first line of the diff
893 ScrollToLine(m_lineBegin);
895 // tell the others views about this diff (no need to call UpdateSiblingScrollPos)
896 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
898 // pSplitterWnd is `nullptr` if WinMerge started minimized.
899 if (pSplitterWnd != nullptr)
901 int nRows = pSplitterWnd->GetRowCount ();
902 int nCols = pSplitterWnd->GetColumnCount ();
903 for (int nRow = 0; nRow < nRows; nRow++)
905 for (int nCol = 0; nCol < nCols; nCol++)
907 CMergeEditView *pSiblingView = static_cast<CMergeEditView*>(GetSiblingView (nRow, nCol));
908 if (pSiblingView != nullptr)
909 pSiblingView->OnDisplayDiff(nDiff);
914 // update the width of the horizontal scrollbar
915 RecalcHorzScrollBar();
919 * @brief Selects diff by number and syncs other file
920 * @param [in] nDiff Diff to select, must be >= 0
921 * @param [in] bScroll Scroll diff to view
922 * @param [in] bSelectText Select diff text
923 * @sa CMergeEditView::ShowDiff()
924 * @sa CMergeDoc::SetCurrentDiff()
925 * @todo Parameter bSelectText is never used?
927 void CMergeEditView::SelectDiff(int nDiff, bool bScroll /*= true*/, bool bSelectText /*= true*/)
929 CMergeDoc *pd = GetDocument();
931 // Check that nDiff is valid
933 _RPTF1(_CRT_ERROR, "Diffnumber negative (%d)", nDiff);
934 if (nDiff >= pd->m_diffList.GetSize())
935 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d >= %d)",
936 nDiff, pd->m_diffList.GetSize());
939 pd->SetCurrentDiff(nDiff);
940 ShowDiff(bScroll, bSelectText);
941 pd->UpdateAllViews(this);
942 UpdateSiblingScrollPos(false);
944 // notify either side, as it will notify the other one
945 pd->ForEachView (0, [&](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
948 void CMergeEditView::DeselectDiffIfCursorNotInCurrentDiff()
950 CMergeDoc *pd = GetDocument();
951 // If we have a selected diff, deselect it
952 int nCurrentDiff = pd->GetCurrentDiff();
953 if (nCurrentDiff != -1)
955 CPoint pos = GetCursorPos();
956 if (!IsLineInCurrentDiff(pos.y))
958 pd->SetCurrentDiff(-1);
960 pd->UpdateAllViews(this);
966 * @brief Called when user selects "Current Difference".
967 * Goes to active diff. If no active diff, selects diff under cursor
968 * @sa CMergeEditView::SelectDiff()
969 * @sa CMergeDoc::GetCurrentDiff()
970 * @sa CMergeDoc::LineToDiff()
972 void CMergeEditView::OnCurdiff()
974 CMergeDoc *pd = GetDocument();
976 // If no diffs, nothing to select
977 if (!pd->m_diffList.HasSignificantDiffs())
980 // GetCurrentDiff() returns -1 if no diff selected
981 int nDiff = pd->GetCurrentDiff();
984 // Scroll to the first line of the currently selected diff
985 SelectDiff(nDiff, true, false);
989 // If cursor is inside diff, select that diff
990 CPoint pos = GetCursorPos();
991 nDiff = pd->m_diffList.LineToDiff(pos.y);
992 if (nDiff != -1 && pd->m_diffList.IsDiffSignificant(nDiff))
993 SelectDiff(nDiff, true, false);
998 * @brief Called when "Current diff" item is updated
1000 void CMergeEditView::OnUpdateCurdiff(CCmdUI* pCmdUI)
1002 CMergeDoc *pd = GetDocument();
1003 CPoint pos = GetCursorPos();
1004 int nCurrentDiff = pd->GetCurrentDiff();
1005 if (nCurrentDiff == -1)
1007 int nNewDiff = pd->m_diffList.LineToDiff(pos.y);
1008 pCmdUI->Enable(nNewDiff != -1 && pd->m_diffList.IsDiffSignificant(nNewDiff));
1011 pCmdUI->Enable(true);
1015 * @brief Copy selected text to clipboard
1017 void CMergeEditView::OnEditCopy()
1019 CMergeDoc * pDoc = GetDocument();
1020 CPoint ptSelStart, ptSelEnd;
1021 GetSelection(ptSelStart, ptSelEnd);
1024 if (ptSelStart == ptSelEnd)
1029 if (!m_bColumnSelection)
1031 CDiffTextBuffer * buffer = pDoc->m_ptBuf[m_nThisPane].get();
1033 buffer->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1034 ptSelEnd.y, ptSelEnd.x, text);
1037 GetTextWithoutEmptysInColumnSelection(text);
1039 PutToClipboard(text, text.GetLength(), m_bColumnSelection);
1043 * @brief Called when "Copy" item is updated
1045 void CMergeEditView::OnUpdateEditCopy(CCmdUI* pCmdUI)
1047 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
1051 * @brief Cut current selection to clipboard
1053 void CMergeEditView::OnEditCut()
1055 if (IsReadOnly(m_nThisPane))
1058 CPoint ptSelStart, ptSelEnd;
1059 CMergeDoc * pDoc = GetDocument();
1060 GetSelection(ptSelStart, ptSelEnd);
1063 if (ptSelStart == ptSelEnd)
1067 if (!m_bColumnSelection)
1068 pDoc->m_ptBuf[m_nThisPane]->GetTextWithoutEmptys(ptSelStart.y, ptSelStart.x,
1069 ptSelEnd.y, ptSelEnd.x, text);
1071 GetTextWithoutEmptysInColumnSelection(text);
1073 PutToClipboard(text, text.GetLength(), m_bColumnSelection);
1075 if (!m_bColumnSelection)
1077 CPoint ptCursorPos = ptSelStart;
1078 ASSERT_VALIDTEXTPOS(ptCursorPos);
1079 SetAnchor(ptCursorPos);
1080 SetSelection(ptCursorPos, ptCursorPos);
1081 SetCursorPos(ptCursorPos);
1082 EnsureVisible(ptCursorPos);
1084 pDoc->m_ptBuf[m_nThisPane]->DeleteText(this, ptSelStart.y, ptSelStart.x, ptSelEnd.y,
1085 ptSelEnd.x, CE_ACTION_CUT);
1088 DeleteCurrentColumnSelection (CE_ACTION_CUT);
1090 m_pTextBuffer->SetModified(true);
1094 * @brief Called when "Cut" item is updated
1096 void CMergeEditView::OnUpdateEditCut(CCmdUI* pCmdUI)
1098 if (!IsReadOnly(m_nThisPane))
1099 CCrystalEditViewEx::OnUpdateEditCut(pCmdUI);
1101 pCmdUI->Enable(false);
1105 * @brief Paste text from clipboard
1107 void CMergeEditView::OnEditPaste()
1109 if (IsReadOnly(m_nThisPane))
1112 CCrystalEditViewEx::Paste();
1113 m_pTextBuffer->SetModified(true);
1117 * @brief Called when "Paste" item is updated
1119 void CMergeEditView::OnUpdateEditPaste(CCmdUI* pCmdUI)
1121 if (!IsReadOnly(m_nThisPane))
1122 CCrystalEditViewEx::OnUpdateEditPaste(pCmdUI);
1124 pCmdUI->Enable(false);
1128 * @brief Undo last action
1130 void CMergeEditView::OnEditUndo()
1132 CWaitCursor waitstatus;
1133 CMergeDoc* pDoc = GetDocument();
1134 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1137 if (IsReadOnly(m_nThisPane))
1140 GetParentFrame()->SetActiveView(this, true);
1141 if(CCrystalEditViewEx::DoEditUndo())
1144 pDoc->UpdateHeaderPath(m_nThisPane);
1145 pDoc->FlushAndRescan();
1148 m_pTextBuffer->GetRedoActionCode(nAction);
1149 if (nAction == CE_ACTION_MERGE)
1150 // select the diff so we may just merge it again
1156 tgt->SendMessage(WM_COMMAND, ID_EDIT_UNDO);
1158 if (!pDoc->CanUndo())
1159 pDoc->SetAutoMerged(false);
1163 * @brief Called when "Undo" item is updated
1165 void CMergeEditView::OnUpdateEditUndo(CCmdUI* pCmdUI)
1167 CMergeDoc* pDoc = GetDocument();
1168 if (pDoc->curUndo!=pDoc->undoTgt.begin())
1170 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo-1));
1171 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
1174 pCmdUI->Enable(false);
1178 * @brief Go to first diff
1180 * Called when user selects "First Difference"
1181 * @sa CMergeEditView::SelectDiff()
1183 void CMergeEditView::OnFirstdiff()
1185 CMergeDoc *pd = GetDocument();
1186 if (pd->m_diffList.HasSignificantDiffs())
1188 int nDiff = pd->m_diffList.FirstSignificantDiff();
1189 SelectDiff(nDiff, true, false);
1194 * @brief Update "First diff" UI items
1196 void CMergeEditView::OnUpdateFirstdiff(CCmdUI* pCmdUI)
1198 OnUpdatePrevdiff(pCmdUI);
1202 * @brief Go to last diff
1204 void CMergeEditView::OnLastdiff()
1206 CMergeDoc *pd = GetDocument();
1207 if (pd->m_diffList.HasSignificantDiffs())
1209 int nDiff = pd->m_diffList.LastSignificantDiff();
1210 SelectDiff(nDiff, true, false);
1215 * @brief Update "Last diff" UI items
1217 void CMergeEditView::OnUpdateLastdiff(CCmdUI* pCmdUI)
1219 OnUpdateNextdiff(pCmdUI);
1223 * @brief Go to next diff and select it.
1225 * Finds and selects next difference. There are several cases:
1226 * - if there is selected difference, and that difference is visible
1227 * on screen, next found difference is selected.
1228 * - if there is selected difference but it is not visible, next
1229 * difference from cursor position is selected. This is what user
1230 * expects to happen and is natural thing to do. Also reduces
1231 * needless scrolling.
1232 * - if there is no selected difference, next difference from cursor
1233 * position is selected.
1235 void CMergeEditView::OnNextdiff()
1237 CMergeDoc *pd = GetDocument();
1238 int cnt = pd->m_ptBuf[0]->GetLineCount();
1242 // Returns -1 if no diff selected
1244 int curDiff = pd->GetCurrentDiff();
1248 if (!IsDiffVisible(curDiff))
1250 // Selected difference not visible, select next from cursor
1251 int line = GetCursorPos().y;
1252 // Make sure we aren't in the first line of the diff
1254 if (!IsValidTextPosY(CPoint(0, line)))
1256 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1260 // Find out if there is a following significant diff
1261 if (curDiff < pd->m_diffList.GetSize() - 1)
1263 nextDiff = pd->m_diffList.NextSignificantDiff(curDiff);
1269 // We don't have a selected difference,
1270 // but cursor can be inside inactive diff
1271 int line = GetCursorPos().y;
1272 if (!IsValidTextPosY(CPoint(0, line)))
1274 nextDiff = pd->m_diffList.NextSignificantDiffFromLine(line);
1277 int lastDiff = pd->m_diffList.LastSignificantDiff();
1278 if (nextDiff >= 0 && nextDiff <= lastDiff)
1279 SelectDiff(nextDiff, true, false);
1280 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1282 if (pDirDoc->MoveableToNextDiff())
1283 pDirDoc->MoveToNextDiff(pd);
1288 * @brief Update "Next diff" UI items
1290 void CMergeEditView::OnUpdateNextdiff(CCmdUI* pCmdUI)
1292 CMergeDoc *pd = GetDocument();
1293 const DIFFRANGE * dfi = pd->m_diffList.LastSignificantDiffRange();
1298 // There aren't any significant differences
1303 // Enable if the beginning of the last significant difference is after caret
1304 enabled = (GetCursorPos().y < (long)dfi->dbegin);
1307 if (!enabled && pd->GetDirDoc())
1308 enabled = pd->GetDirDoc()->MoveableToNextDiff();
1310 pCmdUI->Enable(enabled);
1314 * @brief Go to previous diff and select it.
1316 * Finds and selects previous difference. There are several cases:
1317 * - if there is selected difference, and that difference is visible
1318 * on screen, previous found difference is selected.
1319 * - if there is selected difference but it is not visible, previous
1320 * difference from cursor position is selected. This is what user
1321 * expects to happen and is natural thing to do. Also reduces
1322 * needless scrolling.
1323 * - if there is no selected difference, previous difference from cursor
1324 * position is selected.
1326 void CMergeEditView::OnPrevdiff()
1328 CMergeDoc *pd = GetDocument();
1329 int cnt = pd->m_ptBuf[0]->GetLineCount();
1333 // GetCurrentDiff() returns -1 if no diff selected
1335 int curDiff = pd->GetCurrentDiff();
1339 if (!IsDiffVisible(curDiff))
1341 // Selected difference not visible, select previous from cursor
1342 int line = GetCursorPos().y;
1343 // Make sure we aren't in the last line of the diff
1345 if (!IsValidTextPosY(CPoint(0, line)))
1347 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1351 // Find out if there is a preceding significant diff
1354 prevDiff = pd->m_diffList.PrevSignificantDiff(curDiff);
1360 // We don't have a selected difference,
1361 // but cursor can be inside inactive diff
1362 int line = GetCursorPos().y;
1363 if (!IsValidTextPosY(CPoint(0, line)))
1365 prevDiff = pd->m_diffList.PrevSignificantDiffFromLine(line);
1368 int firstDiff = pd->m_diffList.FirstSignificantDiff();
1369 if (prevDiff >= 0 && prevDiff >= firstDiff)
1370 SelectDiff(prevDiff, true, false);
1371 else if (CDirDoc *pDirDoc = pd->GetDirDoc())
1373 if (pDirDoc->MoveableToPrevDiff())
1374 pDirDoc->MoveToPrevDiff(pd);
1379 * @brief Update "Previous diff" UI items
1381 void CMergeEditView::OnUpdatePrevdiff(CCmdUI* pCmdUI)
1383 CMergeDoc *pd = GetDocument();
1384 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificantDiffRange();
1389 // There aren't any significant differences
1394 // Enable if the end of the first significant difference is before caret
1395 enabled = (GetCursorPos().y > (long)dfi->dend);
1398 if (!enabled && pd->GetDirDoc())
1399 enabled = pd->GetDirDoc()->MoveableToPrevDiff();
1401 pCmdUI->Enable(enabled);
1404 void CMergeEditView::OnNextConflict()
1406 OnNext3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1410 * @brief Update "Next Conflict" UI items
1412 void CMergeEditView::OnUpdateNextConflict(CCmdUI* pCmdUI)
1414 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1417 void CMergeEditView::OnPrevConflict()
1419 OnPrev3wayDiff(THREEWAYDIFFTYPE_CONFLICT);
1423 * @brief Update "Prev Conflict" UI items
1425 void CMergeEditView::OnUpdatePrevConflict(CCmdUI* pCmdUI)
1427 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_CONFLICT);
1431 * @brief Go to next 3-way diff and select it.
1433 void CMergeEditView::OnNext3wayDiff(int nDiffType)
1435 CMergeDoc *pd = GetDocument();
1436 int cnt = pd->m_ptBuf[0]->GetLineCount();
1440 // Returns -1 if no diff selected
1441 int curDiff = pd->GetCurrentDiff();
1445 int nextDiff = curDiff;
1446 if (!IsDiffVisible(curDiff))
1448 // Selected difference not visible, select next from cursor
1449 int line = GetCursorPos().y;
1450 // Make sure we aren't in the first line of the diff
1452 if (!IsValidTextPosY(CPoint(0, line)))
1454 nextDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1458 // Find out if there is a following significant diff
1459 if (curDiff < pd->m_diffList.GetSize() - 1)
1461 nextDiff = pd->m_diffList.NextSignificant3wayDiff(curDiff, nDiffType);
1467 // nextDiff is the next one if there is one, else it is the one we're on
1468 SelectDiff(nextDiff, true, false);
1472 // We don't have a selected difference,
1473 // but cursor can be inside inactive diff
1474 int line = GetCursorPos().y;
1475 if (!IsValidTextPosY(CPoint(0, line)))
1477 curDiff = pd->m_diffList.NextSignificant3wayDiffFromLine(line, nDiffType);
1479 SelectDiff(curDiff, true, false);
1484 * @brief Update "Next 3-way diff" UI items
1486 void CMergeEditView::OnUpdateNext3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1488 CMergeDoc *pd = GetDocument();
1490 if (pd->m_nBuffers < 3)
1492 pCmdUI->Enable(false);
1496 const DIFFRANGE * dfi = pd->m_diffList.LastSignificant3wayDiffRange(nDiffType);
1500 // There aren't any significant differences
1501 pCmdUI->Enable(false);
1505 // Enable if the beginning of the last significant difference is after caret
1506 CPoint pos = GetCursorPos();
1507 pCmdUI->Enable(pos.y < (long)dfi->dbegin);
1512 * @brief Go to previous 3-way diff and select it.
1514 void CMergeEditView::OnPrev3wayDiff(int nDiffType)
1516 CMergeDoc *pd = GetDocument();
1518 int cnt = pd->m_ptBuf[0]->GetLineCount();
1522 // GetCurrentDiff() returns -1 if no diff selected
1523 int curDiff = pd->GetCurrentDiff();
1527 int prevDiff = curDiff;
1528 if (!IsDiffVisible(curDiff))
1530 // Selected difference not visible, select previous from cursor
1531 int line = GetCursorPos().y;
1532 // Make sure we aren't in the last line of the diff
1534 if (!IsValidTextPosY(CPoint(0, line)))
1536 prevDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1540 // Find out if there is a preceding significant diff
1543 prevDiff = pd->m_diffList.PrevSignificant3wayDiff(curDiff, nDiffType);
1549 // prevDiff is the preceding one if there is one, else it is the one we're on
1550 SelectDiff(prevDiff, true, false);
1554 // We don't have a selected difference,
1555 // but cursor can be inside inactive diff
1556 int line = GetCursorPos().y;
1557 if (!IsValidTextPosY(CPoint(0, line)))
1559 curDiff = pd->m_diffList.PrevSignificant3wayDiffFromLine(line, nDiffType);
1561 SelectDiff(curDiff, true, false);
1566 * @brief Update "Previous diff X and Y" UI items
1568 void CMergeEditView::OnUpdatePrev3wayDiff(CCmdUI* pCmdUI, int nDiffType)
1570 CMergeDoc *pd = GetDocument();
1572 if (pd->m_nBuffers < 3)
1574 pCmdUI->Enable(false);
1578 const DIFFRANGE * dfi = pd->m_diffList.FirstSignificant3wayDiffRange(nDiffType);
1582 // There aren't any significant differences
1583 pCmdUI->Enable(false);
1587 // Enable if the end of the first significant difference is before caret
1588 CPoint pos = GetCursorPos();
1589 pCmdUI->Enable(pos.y > (long)dfi->dend);
1593 void CMergeEditView::OnNextdiffLM()
1595 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1598 void CMergeEditView::OnUpdateNextdiffLM(CCmdUI* pCmdUI)
1600 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1603 void CMergeEditView::OnNextdiffLR()
1605 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1608 void CMergeEditView::OnUpdateNextdiffLR(CCmdUI* pCmdUI)
1610 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1613 void CMergeEditView::OnNextdiffMR()
1615 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1618 void CMergeEditView::OnUpdateNextdiffMR(CCmdUI* pCmdUI)
1620 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1623 void CMergeEditView::OnNextdiffLO()
1625 OnNext3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1628 void CMergeEditView::OnUpdateNextdiffLO(CCmdUI* pCmdUI)
1630 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1633 void CMergeEditView::OnNextdiffMO()
1635 OnNext3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1638 void CMergeEditView::OnUpdateNextdiffMO(CCmdUI* pCmdUI)
1640 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1643 void CMergeEditView::OnNextdiffRO()
1645 OnNext3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1648 void CMergeEditView::OnUpdateNextdiffRO(CCmdUI* pCmdUI)
1650 OnUpdateNext3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1653 void CMergeEditView::OnPrevdiffLM()
1655 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTMIDDLE);
1658 void CMergeEditView::OnUpdatePrevdiffLM(CCmdUI* pCmdUI)
1660 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTMIDDLE);
1663 void CMergeEditView::OnPrevdiffLR()
1665 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTRIGHT);
1668 void CMergeEditView::OnUpdatePrevdiffLR(CCmdUI* pCmdUI)
1670 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTRIGHT);
1673 void CMergeEditView::OnPrevdiffMR()
1675 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLERIGHT);
1678 void CMergeEditView::OnUpdatePrevdiffMR(CCmdUI* pCmdUI)
1680 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLERIGHT);
1683 void CMergeEditView::OnPrevdiffLO()
1685 OnPrev3wayDiff(THREEWAYDIFFTYPE_LEFTONLY);
1688 void CMergeEditView::OnUpdatePrevdiffLO(CCmdUI* pCmdUI)
1690 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_LEFTONLY);
1693 void CMergeEditView::OnPrevdiffMO()
1695 OnPrev3wayDiff(THREEWAYDIFFTYPE_MIDDLEONLY);
1698 void CMergeEditView::OnUpdatePrevdiffMO(CCmdUI* pCmdUI)
1700 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_MIDDLEONLY);
1703 void CMergeEditView::OnPrevdiffRO()
1705 OnPrev3wayDiff(THREEWAYDIFFTYPE_RIGHTONLY);
1708 void CMergeEditView::OnUpdatePrevdiffRO(CCmdUI* pCmdUI)
1710 OnUpdatePrev3wayDiff(pCmdUI, THREEWAYDIFFTYPE_RIGHTONLY);
1714 * @brief Clear selection
1716 void CMergeEditView::SelectNone()
1718 SetSelection (GetCursorPos(), GetCursorPos());
1723 * @brief Check if line is inside currently selected diff
1724 * @param [in] nLine 0-based linenumber in view
1725 * @sa CMergeDoc::GetCurrentDiff()
1726 * @sa CMergeDoc::LineInDiff()
1728 bool CMergeEditView::IsLineInCurrentDiff(int nLine) const
1730 // Check validity of nLine
1733 _RPTF1(_CRT_ERROR, "Linenumber is negative (%d)!", nLine);
1734 int nLineCount = LocateTextBuffer()->GetLineCount();
1735 if (nLine >= nLineCount)
1736 _RPTF2(_CRT_ERROR, "Linenumber > linecount (%d>%d)!", nLine, nLineCount);
1739 const CMergeDoc *pd = GetDocument();
1740 int curDiff = pd->GetCurrentDiff();
1743 return pd->m_diffList.LineInDiff(nLine, curDiff);
1747 * @brief Called when mouse left-button double-clicked
1749 * Double-clicking mouse inside diff selects that diff
1751 void CMergeEditView::OnLButtonDblClk(UINT nFlags, CPoint point)
1753 CMergeDoc *pd = GetDocument();
1754 CPoint pos = GetCursorPos();
1756 int diff = pd->m_diffList.LineToDiff(pos.y);
1757 if (diff != -1 && pd->m_diffList.IsDiffSignificant(diff))
1758 SelectDiff(diff, false, false);
1760 CCrystalEditViewEx::OnLButtonDblClk(nFlags, point);
1764 * @brief Called when mouse left button is released.
1766 * If button is released outside diffs, current diff
1769 void CMergeEditView::OnLButtonUp(UINT nFlags, CPoint point)
1771 CCrystalEditViewEx::OnLButtonUp(nFlags, point);
1772 DeselectDiffIfCursorNotInCurrentDiff();
1776 * @brief Called when mouse right button is pressed.
1778 * If right button is pressed outside diffs, current diff
1781 void CMergeEditView::OnRButtonDown(UINT nFlags, CPoint point)
1783 CCrystalEditViewEx::OnRButtonDown(nFlags, point);
1784 DeselectDiffIfCursorNotInCurrentDiff();
1787 void CMergeEditView::OnX2Y(int srcPane, int dstPane)
1789 // Check that right side is not readonly
1790 if (IsReadOnly(dstPane))
1793 CMergeDoc *pDoc = GetDocument();
1794 int currentDiff = pDoc->GetCurrentDiff();
1796 if (currentDiff == -1)
1799 // If cursor is inside diff get number of that diff
1800 if (m_bCurrentLineIsDiff)
1802 CPoint pt = GetCursorPos();
1803 currentDiff = pDoc->m_diffList.LineToDiff(pt.y);
1809 if (!m_bColumnSelection)
1811 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1812 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1813 if (firstDiff != -1 && lastDiff != -1)
1815 CWaitCursor waitstatus;
1816 pDoc->CopyMultipleList(srcPane, dstPane, firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1821 CWaitCursor waitstatus;
1822 auto wordDiffs = GetColumnSelectedWordDiffIndice();
1824 std::for_each(wordDiffs.rbegin(), wordDiffs.rend(), [&](auto& it) {
1825 pDoc->WordListCopy(srcPane, dstPane, it.first, it.second[0], it.second[it.second.size() - 1], &it.second, i != 0, i == 0);
1830 else if (currentDiff != -1 && pDoc->m_diffList.IsDiffSignificant(currentDiff))
1832 CWaitCursor waitstatus;
1833 pDoc->ListCopy(srcPane, dstPane, currentDiff);
1837 void CMergeEditView::OnUpdateX2Y(int dstPane, CCmdUI* pCmdUI)
1839 // Check that right side is not readonly
1840 if (!IsReadOnly(dstPane))
1842 // If one or more diffs inside selection OR
1843 // there is an active diff OR
1844 // cursor is inside diff
1847 int firstDiff, lastDiff, firstWordDiff, lastWordDiff;
1848 GetFullySelectedDiffs(firstDiff, lastDiff, firstWordDiff, lastWordDiff);
1850 pCmdUI->Enable(firstDiff != -1 && lastDiff != -1);
1854 const int currDiff = GetDocument()->GetCurrentDiff();
1855 if (currDiff != -1 && GetDocument()->m_diffList.IsDiffSignificant(currDiff))
1856 pCmdUI->Enable(true);
1858 pCmdUI->Enable(m_bCurrentLineIsDiff);
1862 pCmdUI->Enable(false);
1866 * @brief Copy diff from left pane to right pane
1868 * Difference is copied from left to right when
1869 * - difference is selected
1870 * - difference is inside selection (allows merging multiple differences).
1871 * - cursor is inside diff
1873 * If there is selected diff outside selection, we copy selected
1876 void CMergeEditView::OnL2r()
1878 int dstPane = (m_nThisPane < GetDocument()->m_nBuffers - 1) ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1879 int srcPane = dstPane - 1;
1880 OnX2Y(srcPane, dstPane);
1884 * @brief Called when "Copy to left" item is updated
1886 void CMergeEditView::OnUpdateL2r(CCmdUI* pCmdUI)
1888 OnUpdateX2Y(m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1, pCmdUI);
1892 * @brief Copy diff from right pane to left pane
1894 * Difference is copied from left to right when
1895 * - difference is selected
1896 * - difference is inside selection (allows merging multiple differences).
1897 * - cursor is inside diff
1899 * If there is selected diff outside selection, we copy selected
1902 void CMergeEditView::OnR2l()
1904 int dstPane = (m_nThisPane > 0) ? m_nThisPane - 1 : 0;
1905 int srcPane = dstPane + 1;
1906 OnX2Y(srcPane, dstPane);
1910 * @brief Called when "Copy to right" item is updated
1912 void CMergeEditView::OnUpdateR2l(CCmdUI* pCmdUI)
1914 OnUpdateX2Y(m_nThisPane > 0 ? m_nThisPane - 1 : 0, pCmdUI);
1917 void CMergeEditView::OnCopyFromLeft()
1919 int dstPane = m_nThisPane;
1920 int srcPane = dstPane - 1;
1923 OnX2Y(srcPane, dstPane);
1926 void CMergeEditView::OnUpdateCopyFromLeft(CCmdUI* pCmdUI)
1928 int dstPane = m_nThisPane;
1929 int srcPane = dstPane - 1;
1931 pCmdUI->Enable(false);
1933 OnUpdateX2Y(dstPane, pCmdUI);
1936 void CMergeEditView::OnCopyFromRight()
1938 int dstPane = m_nThisPane;
1939 int srcPane = dstPane + 1;
1940 if (srcPane >= GetDocument()->m_nBuffers)
1942 OnX2Y(srcPane, dstPane);
1945 void CMergeEditView::OnUpdateCopyFromRight(CCmdUI* pCmdUI)
1947 int dstPane = m_nThisPane;
1948 int srcPane = dstPane + 1;
1949 if (srcPane >= GetDocument()->m_nBuffers)
1950 pCmdUI->Enable(false);
1952 OnUpdateX2Y(dstPane, pCmdUI);
1956 * @brief Copy all diffs from right pane to left pane
1958 void CMergeEditView::OnAllLeft()
1960 // Check that left side is not readonly
1961 int srcPane = m_nThisPane > 0 ? m_nThisPane : 1;
1962 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
1963 if (IsReadOnly(dstPane))
1965 CWaitCursor waitstatus;
1967 GetDocument()->CopyAllList(srcPane, dstPane);
1971 * @brief Called when "Copy all to left" item is updated
1973 void CMergeEditView::OnUpdateAllLeft(CCmdUI* pCmdUI)
1975 // Check that left side is not readonly
1976 int dstPane = m_nThisPane > 0 ? m_nThisPane - 1 : 0;
1977 if (!IsReadOnly(dstPane))
1978 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
1980 pCmdUI->Enable(false);
1984 * @brief Copy all diffs from left pane to right pane
1986 void CMergeEditView::OnAllRight()
1988 // Check that right side is not readonly
1989 int srcPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane : m_nThisPane - 1;
1990 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
1991 if (IsReadOnly(dstPane))
1994 CWaitCursor waitstatus;
1996 GetDocument()->CopyAllList(srcPane, dstPane);
2000 * @brief Called when "Copy all to right" item is updated
2002 void CMergeEditView::OnUpdateAllRight(CCmdUI* pCmdUI)
2004 // Check that right side is not readonly
2005 int dstPane = m_nThisPane < GetDocument()->m_nBuffers - 1 ? m_nThisPane + 1 : GetDocument()->m_nBuffers - 1;
2006 if (!IsReadOnly(dstPane))
2007 pCmdUI->Enable(GetDocument()->m_diffList.HasSignificantDiffs());
2009 pCmdUI->Enable(false);
2013 * @brief Do Auto merge
2015 void CMergeEditView::OnAutoMerge()
2017 // Check current pane is not readonly
2018 if (GetDocument()->IsModified() || GetDocument()->GetAutoMerged() || IsReadOnly(m_nThisPane))
2021 CWaitCursor waitstatus;
2023 GetDocument()->DoAutoMerge(m_nThisPane);
2027 * @brief Called when "Auto Merge" item is updated
2029 void CMergeEditView::OnUpdateAutoMerge(CCmdUI* pCmdUI)
2031 pCmdUI->Enable(GetDocument()->m_nBuffers == 3 &&
2032 !GetDocument()->IsModified() &&
2033 !GetDocument()->GetAutoMerged() &&
2034 !IsReadOnly(m_nThisPane));
2038 * @brief Add synchronization point
2040 void CMergeEditView::OnAddSyncPoint()
2042 GetDocument()->AddSyncPoint();
2046 * @brief Clear synchronization points
2048 void CMergeEditView::OnClearSyncPoints()
2050 GetDocument()->ClearSyncPoints();
2054 * @brief Called when "Clear Synchronization Points" item is updated
2056 void CMergeEditView::OnUpdateClearSyncPoints(CCmdUI* pCmdUI)
2058 pCmdUI->Enable(GetDocument()->HasSyncPoints());
2062 * @brief This function is called before other edit events.
2063 * @param [in] nAction Edit operation to do
2064 * @param [in] pszText Text to insert, delete etc
2065 * @sa CCrystalEditView::OnEditOperation()
2066 * @todo More edit-events for rescan delaying?
2068 void CMergeEditView::OnEditOperation(int nAction, LPCTSTR pszText, size_t cchText)
2070 if (IsReadOnly(m_nThisPane))
2072 // We must not arrive here, and assert helps detect troubles
2077 CMergeDoc* pDoc = GetDocument();
2078 pDoc->SetEditedAfterRescan(m_nThisPane);
2080 // simple hook for multiplex undo operations
2081 // deleted by jtuc 2003-06-28
2082 // now AddUndoRecords does it (so we don't create entry for OnEditOperation with no Undo data in m_pTextBuffer)
2083 /*if(dynamic_cast<CMergeDoc::CDiffTextBuffer*>(m_pTextBuffer)->curUndoGroup())
2085 pDoc->undoTgt.erase(pDoc->curUndo, pDoc->undoTgt.end());
2086 pDoc->undoTgt.push_back(this);
2087 pDoc->curUndo = pDoc->undoTgt.end();
2090 // perform original function
2091 CCrystalEditViewEx::OnEditOperation(nAction, pszText, cchText);
2093 // augment with additional operations
2095 // Change header to inform about changed doc
2096 pDoc->UpdateHeaderPath(m_nThisPane);
2098 // If automatic rescan enabled, rescan after edit events
2099 if (m_bAutomaticRescan)
2101 // keep document up to date
2102 // (Re)start timer to rescan only when user edits text
2103 // If timer starting fails, rescan immediately
2104 if (nAction == CE_ACTION_TYPING ||
2105 nAction == CE_ACTION_REPLACE ||
2106 nAction == CE_ACTION_BACKSPACE ||
2107 nAction == CE_ACTION_INDENT ||
2108 nAction == CE_ACTION_PASTE ||
2109 nAction == CE_ACTION_DELSEL ||
2110 nAction == CE_ACTION_DELETE ||
2111 nAction == CE_ACTION_CUT)
2113 if (!SetTimer(IDT_RESCAN, RESCAN_TIMEOUT, nullptr))
2114 pDoc->FlushAndRescan();
2117 pDoc->FlushAndRescan();
2123 // Update other pane for sync line.
2124 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
2126 if (nPane == m_nThisPane)
2128 CCrystalEditView *pView = GetGroupView(nPane);
2129 if (pView != nullptr)
2130 pView->Invalidate();
2137 * @brief Redo last action
2139 void CMergeEditView::OnEditRedo()
2141 CWaitCursor waitstatus;
2142 CMergeDoc* pDoc = GetDocument();
2143 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2146 if (IsReadOnly(m_nThisPane))
2149 GetParentFrame()->SetActiveView(this, true);
2150 if(CCrystalEditViewEx::DoEditRedo())
2153 pDoc->UpdateHeaderPath(m_nThisPane);
2154 pDoc->FlushAndRescan();
2159 tgt->SendMessage(WM_COMMAND, ID_EDIT_REDO);
2164 * @brief Called when "Redo" item is updated
2166 void CMergeEditView::OnUpdateEditRedo(CCmdUI* pCmdUI)
2168 CMergeDoc* pDoc = GetDocument();
2169 if (pDoc->curUndo!=pDoc->undoTgt.end())
2171 CMergeEditView *tgt = pDoc->GetView(m_nThisGroup, *(pDoc->curUndo));
2172 pCmdUI->Enable( !IsReadOnly(tgt->m_nThisPane));
2175 pCmdUI->Enable(false);
2178 void CMergeEditView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint)
2180 CCrystalEditViewEx::OnUpdate(pSender, lHint, pHint);
2184 * @brief Scrolls to current diff and/or selects diff text
2185 * @param [in] bScroll If true scroll diff to view
2186 * @param [in] bSelectText If true select diff text
2187 * @note If bScroll and bSelectText are false, this does nothing!
2188 * @todo This shouldn't be called when no diff is selected, so
2189 * somebody could try to ASSERT(nDiff > -1)...
2191 void CMergeEditView::ShowDiff(bool bScroll, bool bSelectText)
2193 CMergeDoc *pd = GetDocument();
2194 const int nDiff = pd->GetCurrentDiff();
2196 // Try to trap some errors
2197 if (nDiff >= pd->m_diffList.GetSize())
2198 _RPTF2(_CRT_ERROR, "Selected diff > diffcount (%d > %d)!",
2199 nDiff, pd->m_diffList.GetSize());
2201 if (nDiff >= 0 && nDiff < pd->m_diffList.GetSize())
2203 CPoint ptStart, ptEnd;
2205 pd->m_diffList.GetDiff(nDiff, curDiff);
2208 ptStart.y = curDiff.dbegin;
2210 ptEnd.y = curDiff.dend;
2214 if (!IsDiffVisible(curDiff, CONTEXT_LINES_BELOW))
2216 // Difference is not visible, scroll it so that max amount of
2217 // scrolling is done while keeping the diff in screen. So if
2218 // scrolling is downwards, scroll the diff to as up in screen
2219 // as possible. This usually brings next diff to the screen
2220 // and we don't need to scroll into it.
2221 int nLine = GetSubLineIndex(ptStart.y);
2222 if (nLine > CONTEXT_LINES_ABOVE)
2224 nLine -= CONTEXT_LINES_ABOVE;
2226 GetGroupView(m_nThisPane)->ScrollToSubLine(nLine);
2227 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2229 if (nPane != m_nThisPane)
2230 GetGroupView(nPane)->ScrollToSubLine(nLine);
2233 GetGroupView(m_nThisPane)->SetCursorPos(ptStart);
2234 GetGroupView(m_nThisPane)->SetAnchor(ptStart);
2235 GetGroupView(m_nThisPane)->SetSelection(ptStart, ptStart);
2236 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2238 if (nPane != m_nThisPane)
2240 GetGroupView(nPane)->SetCursorPos(ptStart);
2241 GetGroupView(nPane)->SetAnchor(ptStart);
2242 GetGroupView(nPane)->SetSelection(ptStart, ptStart);
2249 ptEnd.x = GetLineLength(ptEnd.y);
2250 SetSelection(ptStart, ptEnd);
2259 void CMergeEditView::OnTimer(UINT_PTR nIDEvent)
2261 // Maybe we want theApp::OnIdle to proceed before processing a timer message
2262 // ...but for this the queue must be empty
2263 // The timer message is a low priority message but the queue is maybe not yet empty
2264 // So we set a flag, wait for OnIdle to proceed, then come back here...
2265 // We come back here with a IDLE_TIMER OnTimer message (send with SendMessage
2266 // not with SetTimer so there is no delay)
2268 // IDT_RESCAN was posted because the app wanted to do a flushAndRescan with some delay
2270 // IDLE_TIMER is the false timer used to come back here after OnIdle
2271 // fTimerWaitingForIdle is a bool to store the commands waiting for idle
2272 // (one normal timer = one flag = one command)
2274 if (nIDEvent == IDT_RESCAN)
2276 KillTimer(IDT_RESCAN);
2277 fTimerWaitingForIdle |= FLAG_RESCAN_WAITS_FOR_IDLE;
2278 // notify the app to come back after OnIdle
2279 theApp.SetNeedIdleTimer();
2282 if (nIDEvent == IDLE_TIMER)
2284 // not a real timer, just come back after OnIdle
2285 // look to flags to know what to do
2286 if (fTimerWaitingForIdle & FLAG_RESCAN_WAITS_FOR_IDLE)
2287 GetDocument()->RescanIfNeeded(RESCAN_TIMEOUT/1000);
2288 fTimerWaitingForIdle = 0;
2291 CCrystalEditViewEx::OnTimer(nIDEvent);
2295 * @brief Returns if buffer is read-only
2296 * @note This has no any relation to file being read-only!
2298 bool CMergeEditView::IsReadOnly(int pane) const
2300 return GetDocument()->m_ptBuf[pane]->GetReadOnly() != false;
2304 * @brief Called when "Save left (as...)" item is updated
2306 void CMergeEditView::OnUpdateFileSaveLeft(CCmdUI* pCmdUI)
2308 CMergeDoc *pd = GetDocument();
2309 pCmdUI->Enable(!IsReadOnly(0) && pd->m_ptBuf[0]->IsModified());
2313 * @brief Called when "Save middle (as...)" item is updated
2315 void CMergeEditView::OnUpdateFileSaveMiddle(CCmdUI* pCmdUI)
2317 CMergeDoc *pd = GetDocument();
2318 pCmdUI->Enable(pd->m_nBuffers == 3 && !IsReadOnly(1) && pd->m_ptBuf[1]->IsModified());
2322 * @brief Called when "Save right (as...)" item is updated
2324 void CMergeEditView::OnUpdateFileSaveRight(CCmdUI* pCmdUI)
2326 CMergeDoc *pd = GetDocument();
2327 pCmdUI->Enable(!IsReadOnly(pd->m_nBuffers - 1) && pd->m_ptBuf[pd->m_nBuffers - 1]->IsModified());
2331 * @brief Refresh display using text-buffers
2332 * @note This DOES NOT reload files!
2334 void CMergeEditView::OnRefresh()
2336 CMergeDoc *pd = GetDocument();
2337 ASSERT(pd != nullptr);
2338 pd->FlushAndRescan(true);
2342 * @brief Handle some keys when in merging mode
2344 bool CMergeEditView::MergeModeKeyDown(MSG* pMsg)
2346 bool bHandled = false;
2348 // Allow default text selection when SHIFT pressed
2349 if (::GetAsyncKeyState(VK_SHIFT))
2352 // Allow default editor functions when CTRL pressed
2353 if (::GetAsyncKeyState(VK_CONTROL))
2356 // If we are in merging mode (merge with cursor keys)
2357 // handle some keys here
2358 switch (pMsg->wParam)
2385 * @brief Called before messages are translated.
2387 * Checks if ESC key was pressed, saves and closes doc.
2388 * Also if in merge mode traps cursor keys.
2390 BOOL CMergeEditView::PreTranslateMessage(MSG* pMsg)
2392 if (pMsg->message == WM_KEYDOWN)
2394 // If we are in merging mode (merge with cursor keys)
2395 // handle some keys here
2396 if (theApp.GetMergingMode())
2398 bool bHandled = MergeModeKeyDown(pMsg);
2403 // Close window if user has allowed it from options
2404 if (pMsg->wParam == VK_ESCAPE)
2406 bool bCloseWithEsc = GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC);
2408 GetParentFrame()->PostMessage(WM_CLOSE, 0, 0);
2413 return CCrystalEditViewEx::PreTranslateMessage(pMsg);
2417 * @brief Called when "Save" item is updated
2419 void CMergeEditView::OnUpdateFileSave(CCmdUI* pCmdUI)
2421 CMergeDoc *pd = GetDocument();
2423 bool bModified = false;
2424 for (int nPane = 0; nPane < pd->m_nBuffers; nPane++)
2426 if (pd->m_ptBuf[nPane]->IsModified())
2429 pCmdUI->Enable(bModified);
2433 * @brief Enable/disable left buffer read-only
2435 void CMergeEditView::OnLeftReadOnly()
2437 CMergeDoc *pd = GetDocument();
2438 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2439 pd->m_ptBuf[0]->SetReadOnly(!bReadOnly);
2443 * @brief Called when "Left read-only" item is updated
2445 void CMergeEditView::OnUpdateLeftReadOnly(CCmdUI* pCmdUI)
2447 CMergeDoc *pd = GetDocument();
2448 bool bReadOnly = pd->m_ptBuf[0]->GetReadOnly();
2449 pCmdUI->Enable(true);
2450 pCmdUI->SetCheck(bReadOnly);
2454 * @brief Enable/disable middle buffer read-only
2456 void CMergeEditView::OnMiddleReadOnly()
2458 CMergeDoc *pd = GetDocument();
2459 if (pd->m_nBuffers == 3)
2461 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2462 pd->m_ptBuf[1]->SetReadOnly(!bReadOnly);
2467 * @brief Called when "Middle read-only" item is updated
2469 void CMergeEditView::OnUpdateMiddleReadOnly(CCmdUI* pCmdUI)
2471 CMergeDoc *pd = GetDocument();
2472 if (pd->m_nBuffers < 3)
2474 pCmdUI->Enable(false);
2478 bool bReadOnly = pd->m_ptBuf[1]->GetReadOnly();
2479 pCmdUI->Enable(true);
2480 pCmdUI->SetCheck(bReadOnly);
2485 * @brief Enable/disable right buffer read-only
2487 void CMergeEditView::OnRightReadOnly()
2489 CMergeDoc *pd = GetDocument();
2490 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2491 pd->m_ptBuf[pd->m_nBuffers - 1]->SetReadOnly(!bReadOnly);
2495 * @brief Called when "Left read-only" item is updated
2497 void CMergeEditView::OnUpdateRightReadOnly(CCmdUI* pCmdUI)
2499 CMergeDoc *pd = GetDocument();
2500 bool bReadOnly = pd->m_ptBuf[pd->m_nBuffers - 1]->GetReadOnly();
2501 pCmdUI->Enable(true);
2502 pCmdUI->SetCheck(bReadOnly);
2505 /// Store interface we use to display status line info
2506 void CMergeEditView::SetStatusInterface(IMergeEditStatus * piMergeEditStatus)
2508 ASSERT(m_piMergeEditStatus == nullptr);
2509 m_piMergeEditStatus = piMergeEditStatus;
2513 * @brief Update status bar contents.
2515 void CMergeEditView::UpdateStatusbar()
2521 * @brief Update statusbar info, Override from CCrystalTextView
2522 * @note we tab-expand column, but we don't tab-expand char count,
2523 * since we want to show how many chars there are and tab is just one
2524 * character although it expands to several spaces.
2526 void CMergeEditView::OnUpdateCaret()
2528 if (m_piMergeEditStatus == nullptr || !IsTextBufferInitialized())
2531 CPoint cursorPos = GetCursorPos();
2532 int nScreenLine = cursorPos.y;
2533 const int nRealLine = ComputeRealLine(nScreenLine);
2540 DWORD dwLineFlags = 0;
2542 dwLineFlags = m_pTextBuffer->GetLineFlags(nScreenLine);
2543 // Is this a ghost line ?
2544 if (dwLineFlags & LF_GHOST)
2546 // Ghost lines display eg "Line 12-13"
2547 sLine.Format(_T("%d-%d"), nRealLine, nRealLine+1);
2548 sEol = _T("hidden");
2552 // Regular lines display eg "Line 13 Characters: 25 EOL: CRLF"
2553 sLine.Format(_T("%d"), nRealLine+1);
2554 curChar = cursorPos.x + 1;
2555 chars = GetLineLength(nScreenLine);
2556 column = CalculateActualOffset(nScreenLine, cursorPos.x, true) + 1;
2557 columns = CalculateActualOffset(nScreenLine, chars, true) + 1;
2559 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2560 GetDocument()->IsMixedEOL(m_nThisPane))
2562 sEol = GetTextBufferEol(nScreenLine);
2565 sEol = _T("hidden");
2567 m_piMergeEditStatus->SetLineInfo(sLine, column, columns,
2568 curChar, chars, sEol, GetDocument()->m_ptBuf[m_nThisPane]->getCodepage(), GetDocument()->m_ptBuf[m_nThisPane]->getHasBom());
2570 // Is cursor inside difference?
2571 if (dwLineFlags & LF_NONTRIVIAL_DIFF)
2572 m_bCurrentLineIsDiff = true;
2574 m_bCurrentLineIsDiff = false;
2576 UpdateLocationViewPosition(m_nTopSubLine, m_nTopSubLine + GetScreenLines());
2579 * @brief Select linedifference in the current line.
2581 * Select line difference in current line. Selection type
2582 * is choosed by highlight type.
2584 template<bool reversed>
2585 void CMergeEditView::OnSelectLineDiff()
2587 // Pass this to the document, to compare this file to other
2588 GetDocument()->Showlinediff(this, reversed);
2591 /// Enable select difference menuitem if current line is inside difference.
2592 void CMergeEditView::OnUpdateSelectLineDiff(CCmdUI* pCmdUI)
2594 int line = GetCursorPos().y;
2595 bool enable = ((GetLineFlags(line) & (LF_DIFF | LF_GHOST)) != 0);
2596 if (GetDocument()->IsEditedAfterRescan(m_nThisPane))
2598 pCmdUI->Enable(enable);
2602 * @brief Enable/disable Replace-menuitem
2604 void CMergeEditView::OnUpdateEditReplace(CCmdUI* pCmdUI)
2606 CMergeDoc *pd = GetDocument();
2607 bool bReadOnly = pd->m_ptBuf[m_nThisPane]->GetReadOnly();
2609 pCmdUI->Enable(!bReadOnly);
2613 * @brief Update readonly statusbaritem
2615 void CMergeEditView::OnUpdateStatusRO(CCmdUI* pCmdUI)
2617 bool bRO = GetDocument()->m_ptBuf[pCmdUI->m_nID - ID_STATUS_PANE0FILE_RO]->GetReadOnly();
2618 pCmdUI->Enable(bRO);
2622 * @brief Create the dynamic submenu for scripts
2624 HMENU CMergeEditView::createScriptsSubmenu(HMENU hMenu)
2627 std::vector<String> functionNamesList = FileTransform::GetFreeFunctionsInScripts(L"EDITOR_SCRIPT");
2630 size_t i = GetMenuItemCount(hMenu);
2632 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2634 if (functionNamesList.size() == 0)
2636 // no script : create a <empty> entry
2637 AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, _("< Empty >").c_str());
2641 // or fill in the submenu with the scripts names
2642 int ID = ID_SCRIPT_FIRST; // first ID in menu
2643 for (i = 0 ; i < functionNamesList.size() ; i++, ID++)
2644 AppendMenu(hMenu, MF_STRING, ID, functionNamesList[i].c_str());
2646 functionNamesList.clear();
2649 if (!plugin::IsWindowsScriptThere())
2650 AppendMenu(hMenu, MF_STRING, ID_NO_SCT_SCRIPTS, _("WSH not found - .sct scripts disabled").c_str());
2656 * @brief Create the dynamic submenu for prediffers
2658 * @note The plugins are grouped in (suggested) and (not suggested)
2659 * The IDs follow the order of GetAvailableScripts
2661 * suggested 0 ID_1ST + 0
2662 * suggested 1 ID_1ST + 2
2663 * suggested 2 ID_1ST + 5
2664 * not suggested 0 ID_1ST + 1
2665 * not suggested 1 ID_1ST + 3
2666 * not suggested 2 ID_1ST + 4
2668 HMENU CMergeEditView::createPrediffersSubmenu(HMENU hMenu)
2671 int i = GetMenuItemCount(hMenu);
2673 DeleteMenu(hMenu, 0, MF_BYPOSITION);
2675 CMergeDoc *pd = GetDocument();
2676 ASSERT(pd != nullptr);
2679 AppendMenu(hMenu, MF_STRING, ID_NO_PREDIFFER, _("No prediffer (normal)").c_str());
2681 // get the scriptlet files
2682 PluginArray * piScriptArray =
2683 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
2684 PluginArray * piScriptArray2 =
2685 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
2687 // build the menu : first part, suggested plugins
2689 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2690 AppendMenu(hMenu, MF_STRING, ID_SUGGESTED_PLUGINS, _("Suggested plugins").c_str());
2692 int ID = ID_PREDIFFERS_FIRST; // first ID in menu
2694 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2696 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2697 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2700 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2702 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2704 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2705 if (plugin->m_disabled || !plugin->TestAgainstRegList(pd->m_strBothFilenames))
2708 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2711 // build the menu : second part, others plugins
2713 AppendMenu(hMenu, MF_SEPARATOR, 0, nullptr);
2714 AppendMenu(hMenu, MF_STRING, ID_NOT_SUGGESTED_PLUGINS, _("Other plugins").c_str());
2716 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2717 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2719 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2720 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2723 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2725 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2727 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2728 if (plugin->m_disabled || plugin->TestAgainstRegList(pd->m_strBothFilenames) != false)
2731 AppendMenu(hMenu, MF_STRING, ID, plugin->m_name.c_str());
2734 // compute the m_CurrentPredifferID (to set the radio button)
2735 PrediffingInfo prediffer;
2736 pd->GetPrediffer(&prediffer);
2738 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
2739 m_CurrentPredifferID = 0;
2740 else if (prediffer.m_PluginName.empty())
2741 m_CurrentPredifferID = ID_NO_PREDIFFER;
2744 ID = ID_PREDIFFERS_FIRST; // first ID in menu
2745 for (iScript = 0 ; iScript < piScriptArray->size() ; iScript++, ID ++)
2747 const PluginInfoPtr & plugin = piScriptArray->at(iScript);
2748 if (prediffer.m_PluginName == plugin->m_name)
2749 m_CurrentPredifferID = ID;
2752 for (iScript = 0 ; iScript < piScriptArray2->size() ; iScript++, ID ++)
2754 const PluginInfoPtr & plugin = piScriptArray2->at(iScript);
2755 if (prediffer.m_PluginName == plugin->m_name)
2756 m_CurrentPredifferID = ID;
2764 * @brief Offer a context menu built with scriptlet/ActiveX functions
2766 void CMergeEditView::OnContextMenu(CWnd* pWnd, CPoint point)
2768 // Create the menu and populate it with the available functions
2770 VERIFY(menu.LoadMenu(IDR_POPUP_MERGEVIEW));
2772 // Remove copying item copying from active side
2773 if (m_nThisPane == 0) // left?
2775 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2776 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2778 if (m_nThisPane == GetDocument()->m_nBuffers - 1)
2780 menu.RemoveMenu(ID_COPY_FROM_LEFT, MF_BYCOMMAND);
2781 menu.RemoveMenu(ID_COPY_FROM_RIGHT, MF_BYCOMMAND);
2784 VERIFY(menu.LoadToolbar(IDR_MAINFRAME));
2785 theApp.TranslateMenu(menu.m_hMenu);
2787 BCMenu *pSub = static_cast<BCMenu *>(menu.GetSubMenu(0));
2788 ASSERT(pSub != nullptr);
2790 // Context menu opened using keyboard has no coordinates
2791 if (point.x == -1 && point.y == -1)
2794 GetClientRect(rect);
2795 ClientToScreen(rect);
2797 point = rect.TopLeft();
2801 pSub->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,
2802 point.x, point.y, AfxGetMainWnd());
2807 * @brief Update EOL mode in status bar
2809 void CMergeEditView::OnUpdateStatusEOL(CCmdUI* pCmdUI)
2811 GetGroupView(pCmdUI->m_nID - ID_STATUS_PANE0FILE_EOL)->OnUpdateIndicatorCRLF(pCmdUI);
2815 * @brief Change EOL mode and unify all the lines EOL to this new mode
2817 void CMergeEditView::OnConvertEolTo(UINT nID )
2819 CRLFSTYLE nStyle = CRLF_STYLE_AUTOMATIC;;
2823 nStyle = CRLF_STYLE_DOS;
2825 case ID_EOL_TO_UNIX:
2826 nStyle = CRLF_STYLE_UNIX;
2829 nStyle = CRLF_STYLE_MAC;
2833 _RPTF0(_CRT_ERROR, "Unhandled EOL type conversion!");
2836 m_pTextBuffer->SetCRLFMode(nStyle);
2838 // we don't need a derived applyEOLMode for ghost lines as they have no EOL char
2839 if (m_pTextBuffer->applyEOLMode())
2841 CMergeDoc *pd = GetDocument();
2842 ASSERT(pd != nullptr);
2843 pd->UpdateHeaderPath(m_nThisPane);
2844 pd->FlushAndRescan(true);
2849 * @brief allow convert to entries in file submenu
2851 void CMergeEditView::OnUpdateConvertEolTo(CCmdUI* pCmdUI)
2853 int nStyle = CRLF_STYLE_AUTOMATIC;
2854 switch (pCmdUI->m_nID)
2857 nStyle = CRLF_STYLE_DOS;
2859 case ID_EOL_TO_UNIX:
2860 nStyle = CRLF_STYLE_UNIX;
2863 nStyle = CRLF_STYLE_MAC;
2867 _RPTF0(_CRT_ERROR, "Missing menuitem handler for EOL convert menu!");
2871 if (GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
2872 GetDocument()->IsMixedEOL(m_nThisPane) ||
2873 nStyle != m_pTextBuffer->GetCRLFMode())
2875 pCmdUI->SetRadio(false);
2877 // Don't allow selecting other EOL style for protected pane
2878 if (IsReadOnly(m_nThisPane))
2879 pCmdUI->Enable(false);
2882 pCmdUI->SetRadio(true);
2886 * @brief Copy diff from left to right and advance to next diff
2888 void CMergeEditView::OnL2RNext()
2891 if (IsCursorInDiff()) // for 3-way file compare
2897 * @brief Update "Copy right and advance" UI item
2899 void CMergeEditView::OnUpdateL2RNext(CCmdUI* pCmdUI)
2901 OnUpdateL2r(pCmdUI);
2905 * @brief Copy diff from right to left and advance to next diff
2907 void CMergeEditView::OnR2LNext()
2910 if (IsCursorInDiff()) // for 3-way file compare
2916 * @brief Update "Copy left and advance" UI item
2918 void CMergeEditView::OnUpdateR2LNext(CCmdUI* pCmdUI)
2920 OnUpdateR2l(pCmdUI);
2924 * @brief Change active pane in MergeView.
2925 * Changes active pane and makes sure cursor position is kept in
2926 * screen. Currently we put cursor in same line than in original
2927 * active pane but we could be smarter too? Maybe update cursor
2928 * only when it is not visible in new pane?
2930 void CMergeEditView::OnChangePane()
2932 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
2933 CMergeEditView *pWnd = static_cast<CMergeEditView*>(pSplitterWnd->GetActivePane());
2934 CMergeDoc *pDoc = GetDocument();
2935 bool bFound = false;
2936 CMergeEditView *pNextActiveView = nullptr;
2937 std::vector<CMergeEditView *> list = pDoc->GetViewList();
2938 list.insert(list.end(), list.begin(), list.end());
2939 for (auto& pView : list)
2941 if (bFound && pView->m_bDetailView == pWnd->m_bDetailView)
2943 pNextActiveView = pView;
2949 GetParentFrame()->SetActiveView(pNextActiveView);
2950 CPoint ptCursor = pWnd->GetCursorPos();
2952 if (ptCursor.y >= pNextActiveView->GetLineCount())
2953 ptCursor.y = pNextActiveView->GetLineCount() - 1;
2954 pNextActiveView->SetCursorPos(ptCursor);
2955 pNextActiveView->SetAnchor(ptCursor);
2956 pNextActiveView->SetSelection(ptCursor, ptCursor);
2960 * @brief Show "Go To" dialog and scroll views to line or diff.
2962 * Before dialog is opened, current line and file is determined
2964 * @note Conversions needed between apparent and real lines
2966 void CMergeEditView::OnWMGoto()
2969 CMergeDoc *pDoc = GetDocument();
2970 CPoint pos = GetCursorPos();
2974 nRealLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(pos.y);
2975 int nLineCount = pDoc->m_ptBuf[m_nThisPane]->GetLineCount();
2976 nLastLine = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(nLineCount - 1);
2978 // Set active file and current line selected in dialog
2979 dlg.m_strParam = strutils::to_str(nRealLine + 1);
2980 dlg.m_nFile = (pDoc->m_nBuffers < 3) ? (m_nThisPane == 1 ? 2 : 0) : m_nThisPane;
2981 dlg.m_nGotoWhat = 0;
2983 if (dlg.DoModal() == IDOK)
2985 CMergeDoc * pDoc1 = GetDocument();
2986 CMergeEditView * pCurrentView = nullptr;
2989 pCurrentView = GetGroupView(m_nThisPane);
2992 try { num = std::stoi(dlg.m_strParam) - 1; } catch(...) {}
2994 if (dlg.m_nGotoWhat == 0)
2996 int nRealLine1 = num;
2999 if (nRealLine1 > nLastLine)
3000 nRealLine1 = nLastLine;
3002 GotoLine(nRealLine1, true, (pDoc1->m_nBuffers < 3) ? (dlg.m_nFile == 2 ? 1 : 0) : dlg.m_nFile);
3009 if (diff >= pDoc1->m_diffList.GetSize())
3010 diff = pDoc1->m_diffList.GetSize();
3012 pCurrentView->SelectDiff(diff, true, false);
3017 void CMergeEditView::OnShellMenu()
3019 CFrameWnd *pFrame = GetTopLevelFrame();
3020 ASSERT(pFrame != nullptr);
3021 BOOL bAutoMenuEnableOld = pFrame->m_bAutoMenuEnable;
3022 pFrame->m_bAutoMenuEnable = FALSE;
3024 String path = GetDocument()->m_filePaths[m_nThisPane];
3025 std::unique_ptr<CShellContextMenu> pContextMenu(new CShellContextMenu(0x9000, 0x9FFF));
3026 pContextMenu->Initialize();
3027 pContextMenu->AddItem(paths::GetParentPath(path), paths::FindFileName(path));
3028 pContextMenu->RequeryShellContextMenu();
3030 ::GetCursorPos(&point);
3031 HWND hWnd = GetSafeHwnd();
3032 BOOL nCmd = TrackPopupMenu(pContextMenu->GetHMENU(), TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD, point.x, point.y, 0, hWnd, nullptr);
3034 pContextMenu->InvokeCommand(nCmd, hWnd);
3035 pContextMenu->ReleaseShellContextMenu();
3037 pFrame->m_bAutoMenuEnable = bAutoMenuEnableOld;
3040 void CMergeEditView::OnUpdateShellMenu(CCmdUI* pCmdUI)
3042 pCmdUI->Enable(!GetDocument()->m_filePaths[m_nThisPane].empty());
3046 * @brief Reload options.
3048 void CMergeEditView::RefreshOptions()
3050 m_bAutomaticRescan = GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN);
3052 if (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0)
3053 SetInsertTabs(true);
3055 SetInsertTabs(false);
3057 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3059 if (!GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT))
3060 SetTextType(CCrystalTextView::SRC_PLAIN);
3062 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3063 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3065 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3066 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE),
3067 GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3068 GetDocument()->IsMixedEOL(m_nThisPane));
3070 Options::DiffColors::Load(GetOptionsMgr(), m_cachedColors);
3073 void CMergeEditView::OnScripts(UINT nID )
3075 // text is CHAR if compiled without UNICODE, WCHAR with UNICODE
3076 String text = GetSelectedText();
3078 // transform the text with a script/ActiveX function, event=EDITOR_SCRIPT
3079 bool bChanged = FileTransform::Interactive(text, L"EDITOR_SCRIPT", nID - ID_SCRIPT_FIRST);
3081 // now replace the text
3082 ReplaceSelection(text.c_str(), text.length(), 0);
3086 * @brief Called when an editor script item is updated
3088 void CMergeEditView::OnUpdateNoEditScripts(CCmdUI* pCmdUI)
3090 // append the scripts submenu
3091 HMENU scriptsSubmenu = dynamic_cast<CMainFrame*>(AfxGetMainWnd())->GetScriptsSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu);
3092 if (scriptsSubmenu != nullptr)
3093 createScriptsSubmenu(scriptsSubmenu);
3095 pCmdUI->Enable(true);
3099 * @brief Called when an editor script item is updated
3101 void CMergeEditView::OnUpdatePrediffer(CCmdUI* pCmdUI)
3103 pCmdUI->Enable(true);
3105 CMergeDoc *pd = GetDocument();
3106 ASSERT(pd != nullptr);
3107 PrediffingInfo prediffer;
3108 pd->GetPrediffer(&prediffer);
3110 if (prediffer.m_PluginOrPredifferMode != PLUGIN_MANUAL)
3112 pCmdUI->SetRadio(false);
3116 // Detect when CDiffWrapper::RunFileDiff has canceled a buggy prediffer
3117 if (prediffer.m_PluginName.empty())
3118 m_CurrentPredifferID = ID_NO_PREDIFFER;
3120 pCmdUI->SetRadio(pCmdUI->m_nID == static_cast<UINT>(m_CurrentPredifferID));
3124 * @brief Update "Prediffer" menuitem
3126 void CMergeEditView::OnUpdateNoPrediffer(CCmdUI* pCmdUI)
3128 // recreate the sub menu (to fill the "selected prediffers")
3129 GetMainFrame()->UpdatePrediffersMenu();
3133 void CMergeEditView::OnNoPrediffer()
3135 OnPrediffer(ID_NO_PREDIFFER);
3138 * @brief Handler for all prediffer choices, including ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO, ID_NO_PREDIFFER, & specific prediffers
3140 void CMergeEditView::OnPrediffer(UINT nID )
3142 CMergeDoc *pd = GetDocument();
3143 ASSERT(pd != nullptr);
3145 SetPredifferByMenu(nID);
3146 pd->FlushAndRescan(true);
3150 * @brief Handler for all prediffer choices.
3151 * Prediffer choises include ID_PREDIFF_MANUAL, ID_PREDIFF_AUTO,
3152 * ID_NO_PREDIFFER, & specific prediffers.
3154 void CMergeEditView::SetPredifferByMenu(UINT nID )
3156 CMergeDoc *pd = GetDocument();
3157 ASSERT(pd != nullptr);
3159 if (nID == ID_NO_PREDIFFER)
3161 m_CurrentPredifferID = nID;
3162 // All flags are set correctly during the construction
3163 PrediffingInfo *infoPrediffer = new PrediffingInfo;
3164 infoPrediffer->m_PluginOrPredifferMode = PLUGIN_MANUAL;
3165 infoPrediffer->m_PluginName.clear();
3166 pd->SetPrediffer(infoPrediffer);
3167 pd->FlushAndRescan(true);
3171 // get the scriptlet files
3172 PluginArray * piScriptArray =
3173 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3174 PluginArray * piScriptArray2 =
3175 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3177 // build a PrediffingInfo structure fom the ID
3178 PrediffingInfo prediffer;
3179 prediffer.m_PluginOrPredifferMode = PLUGIN_MANUAL;
3181 size_t pluginNumber = nID - ID_PREDIFFERS_FIRST;
3182 if (pluginNumber < piScriptArray->size())
3184 prediffer.m_bWithFile = true;
3185 const PluginInfoPtr & plugin = piScriptArray->at(pluginNumber);
3186 prediffer.m_PluginName = plugin->m_name;
3190 pluginNumber -= piScriptArray->size();
3191 if (pluginNumber >= piScriptArray2->size())
3193 prediffer.m_bWithFile = false;
3194 const PluginInfoPtr & plugin = piScriptArray2->at(pluginNumber);
3195 prediffer.m_PluginName = plugin->m_name;
3198 // update data for the radio button
3199 m_CurrentPredifferID = nID;
3201 // update the prediffer and rescan
3202 pd->SetPrediffer(&prediffer);
3206 * @brief Look through available prediffers, and return ID of requested one, if found
3208 int CMergeEditView::FindPrediffer(LPCTSTR prediffer) const
3211 int ID = ID_PREDIFFERS_FIRST;
3213 // Search file prediffers
3214 PluginArray * piScriptArray =
3215 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"FILE_PREDIFF");
3216 for (i=0; i<piScriptArray->size(); ++i, ++ID)
3218 const PluginInfoPtr & plugin = piScriptArray->at(i);
3219 if (plugin->m_name == prediffer)
3223 // Search buffer prediffers
3224 PluginArray * piScriptArray2 =
3225 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(L"BUFFER_PREDIFF");
3226 for (i=0; i<piScriptArray2->size(); ++i, ++ID)
3228 const PluginInfoPtr & plugin = piScriptArray2->at(i);
3229 if (plugin->m_name == prediffer)
3237 * @brief Look through available prediffers, and return ID of requested one, if found
3239 bool CMergeEditView::SetPredifferByName(const CString & prediffer)
3241 int id = FindPrediffer(prediffer);
3242 if (id<0) return false;
3243 SetPredifferByMenu(id);
3248 * @brief Goto given line.
3249 * @param [in] nLine Destination linenumber
3250 * @param [in] bRealLine if true linenumber is real line, otherwise
3251 * it is apparent line (including deleted lines)
3252 * @param [in] pane Pane index of goto target pane (0 = left, 1 = right).
3254 void CMergeEditView::GotoLine(UINT nLine, bool bRealLine, int pane)
3256 CMergeDoc *pDoc = GetDocument();
3257 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3258 CMergeEditView *pCurrentView = nullptr;
3259 if (pSplitterWnd != nullptr)
3260 pCurrentView = static_cast<CMergeEditView*>
3261 (pSplitterWnd->GetActivePane());
3263 int nRealLine = nLine;
3264 int nApparentLine = nLine;
3266 // Compute apparent (shown linenumber) line
3269 if (nRealLine > pDoc->m_ptBuf[pane]->GetLineCount() - 1)
3270 nRealLine = pDoc->m_ptBuf[pane]->GetLineCount() - 1;
3272 nApparentLine = pDoc->m_ptBuf[pane]->ComputeApparentLine(nRealLine);
3276 ptPos.y = nApparentLine;
3278 // Scroll line to center of view
3279 int nScrollLine = GetSubLineIndex(nApparentLine);
3280 nScrollLine -= GetScreenLines() / 2;
3281 if (nScrollLine < 0)
3284 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3286 CMergeEditView *pView = GetGroupView(nPane);
3287 pView->ScrollToSubLine(nScrollLine);
3288 if (ptPos.y < pView->GetLineCount())
3290 pView->SetCursorPos(ptPos);
3291 pView->SetAnchor(ptPos);
3295 CPoint ptPos1(0, pView->GetLineCount() - 1);
3296 pView->SetCursorPos(ptPos1);
3297 pView->SetAnchor(ptPos1);
3301 // If goto target is another view - activate another view.
3302 // This is done for user convenience as user probably wants to
3303 // work with goto target file.
3304 if (GetGroupView(pane) != pCurrentView)
3306 if (pSplitterWnd != nullptr)
3308 if (pSplitterWnd->GetColumnCount() > 1)
3309 pSplitterWnd->SetActivePane(0, pane);
3311 pSplitterWnd->SetActivePane(pane, 0);
3317 * @brief Check for horizontal scroll. Re-route to CSplitterEx if not from
3320 void CMergeEditView::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3322 if (pScrollBar == nullptr)
3324 // Scroll did not come frome a scroll bar
3325 // Find the appropriate scroll bar
3326 // and send the message to the splitter window instead
3327 // The event should eventually come back here but with a valid scrollbar
3328 // Along the way it will be propagated to other windows that need it
3329 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3330 CScrollBar* curBar = this->GetScrollBarCtrl(SB_HORZ);
3331 pSplitterWnd->SendMessage(WM_HSCROLL,
3332 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3335 CCrystalTextView::OnHScroll (nSBCode, nPos, pScrollBar);
3339 * @brief When view is scrolled using scrollbars update location pane.
3341 void CMergeEditView::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar *pScrollBar)
3343 if (pScrollBar == nullptr)
3345 // Scroll did not come frome a scroll bar
3346 // Find the appropriate scroll bar
3347 // and send the message to the splitter window instead
3348 // The event should eventually come back here but with a valid scrollbar
3349 // Along the way it will be propagated to other windows that need it
3350 CSplitterWnd *pSplitterWnd = GetParentSplitter(this, false);
3351 CScrollBar* curBar = this->GetScrollBarCtrl(SB_VERT);
3352 pSplitterWnd->SendMessage(WM_VSCROLL,
3353 MAKELONG(nSBCode, nPos), (LPARAM)curBar->m_hWnd);
3356 CCrystalTextView::OnVScroll (nSBCode, nPos, pScrollBar);
3358 if (nSBCode == SB_ENDSCROLL)
3361 // Note we cannot use nPos because of its 16-bit nature
3362 SCROLLINFO si = {0};
3363 si.cbSize = sizeof (si);
3364 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
3365 VERIFY (GetScrollInfo (SB_VERT, &si));
3367 // Get the current position of scroll box.
3368 int nCurPos = si.nPos;
3370 UpdateLocationViewPosition(nCurPos, nCurPos + GetScreenLines());
3374 * @brief Copy selected lines adding linenumbers.
3376 void CMergeEditView::OnEditCopyLineNumbers()
3384 CMergeDoc *pDoc = GetDocument();
3385 GetSelection(ptStart, ptEnd);
3387 // Get last selected line (having widest linenumber)
3388 int line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(ptEnd.y);
3389 size_t nNumWidth = strutils::to_str(line + 1).length();
3391 for (int i = ptStart.y; i <= ptEnd.y; i++)
3393 if (GetLineFlags(i) & LF_GHOST || (GetEnableHideLines() && (GetLineFlags(i) & LF_INVISIBLE)))
3396 // We need to convert to real linenumbers
3397 line = pDoc->m_ptBuf[m_nThisPane]->ComputeRealLine(i);
3399 // Insert spaces to align different width linenumbers (99, 100)
3400 strLine = GetLineText(i);
3401 CString sSpaces(' ', static_cast<int>(nNumWidth - strutils::to_str(line + 1).length()));
3404 strNumLine.Format(_T("%d: %s"), line + 1, (LPCTSTR)strLine);
3405 strText += strNumLine;
3407 PutToClipboard(strText, strText.GetLength(), m_bColumnSelection);
3410 void CMergeEditView::OnUpdateEditCopyLinenumbers(CCmdUI* pCmdUI)
3412 CCrystalEditViewEx::OnUpdateEditCopy(pCmdUI);
3416 * @brief Open active file with associated application.
3418 * First tries to open file using shell 'Edit' action, since that
3419 * action open scripts etc. to editor instead of running them. If
3420 * edit-action is not registered, 'Open' action is used.
3422 void CMergeEditView::OnOpenFile()
3424 CMergeDoc * pDoc = GetDocument();
3425 ASSERT(pDoc != nullptr);
3427 String sFileName = pDoc->m_filePaths[m_nThisPane];
3428 if (sFileName.empty())
3430 HINSTANCE rtn = ShellExecute(::GetDesktopWindow(), _T("edit"), sFileName.c_str(),
3431 0, 0, SW_SHOWNORMAL);
3432 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3433 rtn = ShellExecute(::GetDesktopWindow(), _T("open"), sFileName.c_str(),
3434 0, 0, SW_SHOWNORMAL);
3435 if (reinterpret_cast<uintptr_t>(rtn) == SE_ERR_NOASSOC)
3440 * @brief Open active file with app selection dialog
3442 void CMergeEditView::OnOpenFileWith()
3444 CMergeDoc * pDoc = GetDocument();
3445 ASSERT(pDoc != nullptr);
3447 String sFileName = pDoc->m_filePaths[m_nThisPane];
3448 if (sFileName.empty())
3452 if (!GetSystemDirectory(sysdir.GetBuffer(MAX_PATH), MAX_PATH))
3454 sysdir.ReleaseBuffer();
3455 CString arg = (CString)_T("shell32.dll,OpenAs_RunDLL ") + sFileName.c_str();
3456 ShellExecute(::GetDesktopWindow(), 0, _T("RUNDLL32.EXE"), arg,
3457 sysdir, SW_SHOWNORMAL);
3461 * @brief Open active file with external editor
3463 void CMergeEditView::OnOpenFileWithEditor()
3465 CMergeDoc * pDoc = GetDocument();
3466 ASSERT(pDoc != nullptr);
3468 String sFileName = pDoc->m_filePaths[m_nThisPane];
3469 if (sFileName.empty())
3472 int nRealLine = ComputeRealLine(GetCursorPos().y) + 1;
3473 theApp.OpenFileToExternalEditor(sFileName, nRealLine);
3477 * @brief Force repaint of the location pane.
3479 void CMergeEditView::RepaintLocationPane()
3481 // Must force recalculation due to caching of data in location pane.
3482 CLocationView *pLocationView = GetDocument()->GetLocationView();
3483 if (pLocationView != nullptr)
3484 pLocationView->ForceRecalculate();
3488 * @brief Enables/disables linediff (different color for diffs)
3490 void CMergeEditView::OnViewLineDiffs()
3492 bool bWordDiffHighlight = GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT);
3493 GetOptionsMgr()->SaveOption(OPT_WORDDIFF_HIGHLIGHT, !bWordDiffHighlight);
3495 // Call CMergeDoc RefreshOptions() to refresh *both* views
3496 CMergeDoc *pDoc = GetDocument();
3497 pDoc->RefreshOptions();
3498 pDoc->FlushAndRescan(true);
3501 void CMergeEditView::OnUpdateViewLineDiffs(CCmdUI* pCmdUI)
3503 pCmdUI->Enable(true);
3504 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_WORDDIFF_HIGHLIGHT));
3508 * @brief Enables/disables line number
3510 void CMergeEditView::OnViewLineNumbers()
3512 GetOptionsMgr()->SaveOption(OPT_VIEW_LINENUMBERS, !GetViewLineNumbers());
3514 // Call CMergeDoc RefreshOptions() to refresh *both* views
3515 CMergeDoc *pDoc = GetDocument();
3516 pDoc->RefreshOptions();
3519 void CMergeEditView::OnUpdateViewLineNumbers(CCmdUI* pCmdUI)
3521 pCmdUI->Enable(true);
3522 pCmdUI->SetCheck(GetViewLineNumbers());
3526 * @brief Enables/disables word wrap
3528 void CMergeEditView::OnViewWordWrap()
3530 GetOptionsMgr()->SaveOption(OPT_WORDWRAP, !m_bWordWrap);
3532 // Call CMergeDoc RefreshOptions() to refresh *both* views
3533 CMergeDoc *pDoc = GetDocument();
3534 pDoc->RefreshOptions();
3535 pDoc->UpdateAllViews(this);
3540 void CMergeEditView::OnUpdateViewWordWrap(CCmdUI* pCmdUI)
3542 pCmdUI->Enable(true);
3543 pCmdUI->SetCheck(m_bWordWrap);
3546 void CMergeEditView::OnViewWhitespace()
3548 GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !GetViewTabs());
3550 // Call CMergeDoc RefreshOptions() to refresh *both* views
3551 CMergeDoc *pDoc = GetDocument();
3552 pDoc->RefreshOptions();
3555 void CMergeEditView::OnUpdateViewWhitespace(CCmdUI* pCmdUI)
3557 pCmdUI->SetCheck(GetViewTabs());
3560 void CMergeEditView::OnSize(UINT nType, int cx, int cy)
3562 if (!IsInitialized())
3565 CMergeDoc * pDoc = GetDocument();
3566 if (m_nThisPane < pDoc->m_nBuffers - 1)
3568 // To calculate subline index correctly
3569 // we have to invalidate line cache in all pane before calling the function related the subline.
3570 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3572 CMergeEditView *pView = GetGroupView(nPane);
3573 if (pView != nullptr)
3574 pView->InvalidateScreenRect(false);
3579 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3581 CMergeEditView *pView = GetGroupView(nPane);
3582 if (pView != nullptr)
3583 pView->Invalidate();
3586 // recalculate m_nTopSubLine
3587 m_nTopSubLine = GetSubLineIndex(m_nTopLine);
3591 RecalcVertScrollBar (false, false);
3592 RecalcHorzScrollBar (false, false);
3596 * @brief allocates GDI resources for printing
3597 * @param pDC [in] points to the printer device context
3598 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3600 void CMergeEditView::OnBeginPrinting(CDC * pDC, CPrintInfo * pInfo)
3602 GetParentFrame()->PostMessage(WM_TIMER);
3604 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3606 CMergeEditView *pView = GetDocument()->GetView(m_nThisGroup, pane);
3607 pView->m_bPrintHeader = true;
3608 pView->m_bPrintFooter = true;
3609 pView->CGhostTextView::OnBeginPrinting(pDC, pInfo);
3614 * @brief frees GDI resources for printing
3615 * @param pDC [in] points to the printer device context
3616 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3618 void CMergeEditView::OnEndPrinting(CDC * pDC, CPrintInfo * pInfo)
3620 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3621 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::OnEndPrinting(pDC, pInfo);
3623 GetParentFrame()->PostMessage(WM_TIMER);
3627 * @brief Gets header text to print
3628 * @param [in] nPageNum the page number to print
3629 * @param [out] header text to print
3631 void CMergeEditView::GetPrintHeaderText(int nPageNum, CString & text)
3633 text = GetDocument()->GetTitle();
3637 * @brief Prints header
3638 * @param [in] nPageNum the page number to print
3640 void CMergeEditView::PrintHeader(CDC * pdc, int nPageNum)
3642 if (m_nThisPane > 0)
3644 int oldRight = m_rcPrintArea.right;
3645 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3646 CGhostTextView::PrintHeader(pdc, nPageNum);
3647 m_rcPrintArea.right = oldRight;
3651 * @brief Prints footer
3652 * @param [in] nPageNum the page number to print
3654 void CMergeEditView::PrintFooter(CDC * pdc, int nPageNum)
3656 if (m_nThisPane > 0)
3658 int oldRight = m_rcPrintArea.right;
3659 m_rcPrintArea.right += m_rcPrintArea.Width() * (GetDocument()->m_nBuffers - 1);
3660 CGhostTextView::PrintFooter(pdc, nPageNum);
3661 m_rcPrintArea.right = oldRight;
3664 void CMergeEditView::RecalcPageLayouts (CDC * pDC, CPrintInfo * pInfo)
3666 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3667 GetDocument()->GetView(m_nThisGroup, pane)->CGhostTextView::RecalcPageLayouts(pDC, pInfo);
3671 * @brief Prints or previews both panes.
3672 * @param pDC [in] points to the printer device context
3673 * @param pInfo [in] points to a CPrintInfo structure that describes the current print job
3675 void CMergeEditView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
3677 CRect rDraw = pInfo->m_rectDraw;
3678 CSize sz = rDraw.Size();
3679 CMergeDoc *pDoc = GetDocument();
3681 SIZE szLeftTop, szRightBottom;
3682 GetPrintMargins(szLeftTop.cx, szLeftTop.cy, szRightBottom.cx, szRightBottom.cy);
3683 pDC->HIMETRICtoLP(&szLeftTop);
3684 pDC->HIMETRICtoLP(&szRightBottom);
3686 int midX = (sz.cx - szLeftTop.cx - szRightBottom.cx) / pDoc->m_nBuffers;
3689 for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
3691 pInfo->m_rectDraw.left = rDraw.left + midX * pane;
3692 pInfo->m_rectDraw.right = pInfo->m_rectDraw.left + midX + szLeftTop.cx + szRightBottom.cx;
3693 CMergeEditView* pPane = pDoc->GetView(m_nThisGroup, pane);
3694 pPane->CGhostTextView::OnPrint(pDC, pInfo);
3698 bool CMergeEditView::IsInitialized() const
3700 CMergeEditView * pThis = const_cast<CMergeEditView *>(this);
3701 CDiffTextBuffer * pBuffer = dynamic_cast<CDiffTextBuffer *>(pThis->LocateTextBuffer());
3702 return pBuffer->IsInitialized();
3706 * @brief returns the number of empty lines which are added for synchronizing the line in two/three panes.
3708 int CMergeEditView::GetEmptySubLines( int nLineIndex )
3710 int nBreaks[3] = {0};
3711 int nMaxBreaks = -1;
3712 CMergeDoc * pDoc = GetDocument();
3713 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3715 CMergeEditView *pView = GetGroupView(nPane);
3716 if (pView != nullptr)
3718 if (nLineIndex >= pView->GetLineCount())
3720 pView->WrapLineCached( nLineIndex, pView->GetScreenChars(), nullptr, nBreaks[nPane] );
3722 nMaxBreaks = max(nMaxBreaks, nBreaks[nPane]);
3725 if (nBreaks[m_nThisPane] < nMaxBreaks)
3726 return nMaxBreaks - nBreaks[m_nThisPane];
3732 * @brief Invalidate sub line index cache from the specified index to the end of file.
3733 * @param [in] nLineIndex Index of the first line to invalidate
3735 void CMergeEditView::InvalidateSubLineIndexCache( int nLineIndex )
3737 CMergeDoc * pDoc = GetDocument();
3738 ASSERT(pDoc != nullptr);
3740 // We have to invalidate sub line index cache on both panes.
3741 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3743 CMergeEditView *pView = GetGroupView(nPane);
3744 if (pView != nullptr)
3745 pView->CCrystalTextView::InvalidateSubLineIndexCache( nLineIndex );
3749 void CMergeEditView::SetWordWrapping( bool bWordWrap )
3751 for (int pane = 0; pane < GetDocument()->m_nBuffers; pane++)
3752 GetGroupView(pane)->m_bWordWrap = bWordWrap;
3753 CCrystalTextView::SetWordWrapping(bWordWrap);
3757 * @brief Swap the positions of the two panes
3759 void CMergeEditView::OnViewSwapPanes()
3761 GetDocument()->SwapFiles();
3765 * @brief Determine if difference is visible on screen.
3766 * @param [in] nDiff Number of diff to check.
3767 * @return true if difference is visible.
3769 bool CMergeEditView::IsDiffVisible(int nDiff)
3771 const CMergeDoc *pd = GetDocument();
3774 pd->m_diffList.GetDiff(nDiff, diff);
3776 return IsDiffVisible(diff);
3780 * @brief Determine if difference is visible on screen.
3781 * @param [in] diff diff to check.
3782 * @param [in] nLinesBelow Allow "minimizing" the number of visible lines.
3783 * @return true if difference is visible, false otherwise.
3785 bool CMergeEditView::IsDiffVisible(const DIFFRANGE& diff, int nLinesBelow /*=0*/)
3787 const int nDiffStart = GetSubLineIndex(diff.dbegin);
3788 const int nDiffEnd = GetSubLineIndex(diff.dend);
3789 // Diff's height is last line - first line + last line's line count
3790 const int nDiffHeight = nDiffEnd - nDiffStart + GetSubLines(diff.dend) + 1;
3792 // If diff first line outside current view - context OR
3793 // if diff last line outside current view - context OR
3794 // if diff is bigger than screen
3795 if ((nDiffStart < m_nTopSubLine) ||
3796 (nDiffEnd >= m_nTopSubLine + GetScreenLines() - nLinesBelow) ||
3797 (nDiffHeight >= GetScreenLines()))
3807 /** @brief Open help from mainframe when user presses F1*/
3808 void CMergeEditView::OnHelp()
3810 theApp.ShowHelp(MergeViewHelpLocation);
3814 * @brief Called after document is loaded.
3815 * This function is called from CMergeDoc::OpenDocs() after documents are
3816 * loaded. So this is good place to set View's options etc.
3818 void CMergeEditView::DocumentsLoaded()
3820 // Enable/disable automatic rescan (rescanning after edit)
3821 EnableRescan(GetOptionsMgr()->GetBool(OPT_AUTOMATIC_RESCAN));
3823 // SetTextType will revert to language dependent defaults for tab
3824 SetTabSize(GetOptionsMgr()->GetInt(OPT_TAB_SIZE));
3825 SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
3826 const bool mixedEOLs = GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
3827 GetDocument()->IsMixedEOL(m_nThisPane);
3828 SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE), mixedEOLs);
3829 SetWordWrapping(GetOptionsMgr()->GetBool(OPT_WORDWRAP));
3830 SetViewLineNumbers(GetOptionsMgr()->GetBool(OPT_VIEW_LINENUMBERS));
3831 SetSelectionMargin(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3833 // Enable Backspace at beginning of line
3834 SetDisableBSAtSOL(false);
3836 // Set tab type (tabs/spaces)
3837 bool bInsertTabs = (GetOptionsMgr()->GetInt(OPT_TAB_TYPE) == 0);
3838 SetInsertTabs(bInsertTabs);
3840 // Sometimes WinMerge doesn't update scrollbars correctly (they remain
3841 // disabled) after docs are open in screen. So lets make sure they are
3842 // really updated, even though this is unnecessary in most cases.
3843 RecalcHorzScrollBar();
3844 RecalcVertScrollBar();
3848 * @brief Update LocationView position.
3849 * This function updates LocationView position to given lines.
3850 * Usually we want to lines in file compare view and area in
3851 * LocationView to match. Be extra carefull to not call non-existing
3853 * @param [in] nTopLine Top line of current view.
3854 * @param [in] nBottomLine Bottom line of current view.
3856 void CMergeEditView::UpdateLocationViewPosition(int nTopLine /*=-1*/,
3857 int nBottomLine /*= -1*/)
3859 CMergeDoc *pDoc = GetDocument();
3860 if (pDoc == nullptr)
3863 CLocationView *pLocationView = pDoc->GetLocationView();
3865 if (pLocationView != nullptr && IsWindow(pLocationView->GetSafeHwnd()))
3867 pLocationView->UpdateVisiblePos(nTopLine, nBottomLine);
3872 * @brief Enable/Disable view's selection margins.
3873 * Selection margins show bookmarks and word-wrap symbols, so they are pretty
3874 * useful. But it appears many users don't use/need those features and for them
3875 * selection margins are just wasted screen estate.
3877 void CMergeEditView::OnViewMargin()
3879 bool bViewMargin = GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN);
3880 GetOptionsMgr()->SaveOption(OPT_VIEW_FILEMARGIN, !bViewMargin);
3882 SetSelectionMargin(!bViewMargin);
3883 CMergeDoc *pDoc = GetDocument();
3884 pDoc->RefreshOptions();
3885 pDoc->UpdateAllViews(this);
3889 * @brief Update GUI for Enable/Disable view's selection margin.
3890 * @param [in] pCmdUI Pointer to UI item to update.
3892 void CMergeEditView::OnUpdateViewMargin(CCmdUI* pCmdUI)
3894 pCmdUI->Enable(true);
3895 pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_FILEMARGIN));
3899 * @brief Create the "Change Scheme" sub menu.
3900 * @param [in] pCmdUI Pointer to UI item to update.
3902 void CMergeEditView::OnUpdateViewChangeScheme(CCmdUI *pCmdUI)
3904 // Delete the place holder menu.
3905 pCmdUI->m_pSubMenu->DeleteMenu(0, MF_BYPOSITION);
3907 const HMENU hSubMenu = pCmdUI->m_pSubMenu->m_hMenu;
3909 String name = theApp.LoadString(ID_COLORSCHEME_FIRST);
3910 AppendMenu(hSubMenu, MF_STRING, ID_COLORSCHEME_FIRST, name.c_str());
3911 AppendMenu(hSubMenu, MF_SEPARATOR, 0, nullptr);
3913 for (int i = ID_COLORSCHEME_FIRST + 1; i <= ID_COLORSCHEME_LAST; ++i)
3915 name = theApp.LoadString(i);
3916 AppendMenu(hSubMenu, MF_STRING, i, name.c_str());
3919 pCmdUI->Enable(true);
3923 * @brief Change the editor's syntax highlighting scheme.
3924 * @param [in] nID Selected color scheme sub menu id.
3926 void CMergeEditView::OnChangeScheme(UINT nID)
3928 CMergeDoc *pDoc = GetDocument();
3929 ASSERT(pDoc != nullptr);
3931 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
3933 CMergeEditView *pView = GetGroupView(nPane);
3934 ASSERT(pView != nullptr);
3936 if (pView != nullptr)
3938 pView->SetTextType(CCrystalTextView::TextType(nID - ID_COLORSCHEME_FIRST));
3939 pView->SetDisableBSAtSOL(false);
3943 pDoc->UpdateAllViews(nullptr);
3947 * @brief Enable all color schemes sub menu items.
3948 * @param [in] pCmdUI Pointer to UI item to update.
3950 void CMergeEditView::OnUpdateChangeScheme(CCmdUI* pCmdUI)
3952 const bool bIsCurrentScheme = (static_cast<UINT>(m_CurSourceDef->type) == (pCmdUI->m_nID - ID_COLORSCHEME_FIRST));
3953 pCmdUI->SetRadio(bIsCurrentScheme);
3954 pCmdUI->Enable(GetOptionsMgr()->GetBool(OPT_SYNTAX_HIGHLIGHT));
3958 * @brief Called when mouse's wheel is scrolled.
3960 BOOL CMergeEditView::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
3962 if ( nFlags == MK_CONTROL )
3964 short amount = zDelta < 0 ? -1: 1;
3967 // no default CCrystalTextView
3968 return CView::OnMouseWheel(nFlags, zDelta, pt);
3971 if (nFlags == MK_SHIFT)
3973 SCROLLINFO si = { sizeof SCROLLINFO };
3974 si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE;
3976 VERIFY(GetScrollInfo(SB_HORZ, &si));
3979 si.nPos -= zDelta / 40;
3980 if (si.nPos > si.nMax) si.nPos = si.nMax;
3981 if (si.nPos < si.nMin) si.nPos = si.nMin;
3983 SetScrollInfo(SB_HORZ, &si);
3986 SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBPOSITION, si.nPos) , NULL );
3988 // no default CCrystalTextView
3989 return CView::OnMouseWheel(nFlags, zDelta, pt);
3992 return CGhostTextView::OnMouseWheel(nFlags, zDelta, pt);
3996 * @brief Change font size (zoom) in views.
3997 * @param [in] amount Amount of change/zoom, negative number makes
3998 * font smaller, positive number bigger and 0 reset the font size.
4000 void CMergeEditView::ZoomText(short amount)
4005 const int nLogPixelsY = CClientDC(this).GetDeviceCaps(LOGPIXELSY);
4006 int nPointSize = -MulDiv(lf.lfHeight, 72, nLogPixelsY);
4010 nPointSize = -MulDiv(GetOptionsMgr()->GetInt(OPT_FONT_FILECMP + OPT_FONT_HEIGHT), 72, nLogPixelsY);
4013 nPointSize += amount;
4017 lf.lfHeight = -MulDiv(nPointSize, nLogPixelsY, 72);
4019 CMergeDoc *pDoc = GetDocument();
4020 ASSERT(pDoc != nullptr);
4022 if (pDoc != nullptr)
4024 for (int nPane = 0; nPane < pDoc->m_nBuffers; nPane++)
4026 CMergeEditView *pView = GetGroupView(nPane);
4027 ASSERT(pView != nullptr);
4029 if (pView != nullptr)
4038 * @brief Called when user selects View/Zoom In from menu.
4040 void CMergeEditView::OnViewZoomIn()
4046 * @brief Called when user selects View/Zoom Out from menu.
4048 void CMergeEditView::OnViewZoomOut()
4054 * @brief Called when user selects View/Zoom Normal from menu.
4056 void CMergeEditView::OnViewZoomNormal()
4061 void CMergeEditView::OnDropFiles(const std::vector<String>& tFiles)
4063 if (tFiles.size() > 1 || paths::IsDirectory(tFiles[0]))
4065 GetMainFrame()->GetDropHandler()->GetCallback()(tFiles);
4069 GetDocument()->ChangeFile(m_nThisPane, tFiles[0]);
4072 void CMergeEditView::OnWindowSplit()
4075 auto& wndSplitter = dynamic_cast<CMergeEditFrame *>(GetParentFrame())->GetSplitter();
4076 CMergeDoc *pDoc = GetDocument();
4077 CMergeEditView *pView = pDoc->GetView(0, m_nThisPane);
4078 auto* pwndSplitterChild = pView->GetParentSplitter(pView, false);
4079 int nBuffer = m_nThisPane;
4080 if (pDoc->m_nGroups <= 2)
4082 wndSplitter.SplitRow(1);
4083 wndSplitter.EqualizeRows();
4087 wndSplitter.SetActivePane(0, 0);
4088 wndSplitter.DeleteRow(1);
4089 if (pwndSplitterChild->GetColumnCount() > 1)
4090 pwndSplitterChild->SetActivePane(0, nBuffer);
4092 pwndSplitterChild->SetActivePane(nBuffer, 0);
4096 void CMergeEditView::OnUpdateWindowSplit(CCmdUI* pCmdUI)
4098 pCmdUI->Enable(TRUE);
4099 pCmdUI->SetCheck(GetDocument()->m_nGroups > 2);