1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997 Dean P. Grimm
4 // SPDX-License-Identifier: GPL-2.0-or-later
5 /////////////////////////////////////////////////////////////////////////////
9 * @brief Declaration of CMergeDoc class
13 #include "IMergeDoc.h"
14 #include "DiffTextBuffer.h"
15 #include "DiffWrapper.h"
18 #include "PathContext.h"
19 #include "FileLoadResult.h"
26 * @brief Additional action codes for WinMerge.
27 * @note Reserve first 100 for CrystalEditor
31 CE_ACTION_MERGE = 100, /**< Merging action */
35 * @brief Return statuses of file rescan
39 RESCAN_OK = 0, /**< Rescan succeeded */
40 RESCAN_SUPPRESSED, /**< Rescan not done - suppressed */
41 RESCAN_FILE_ERR, /**< Error reading file */
42 RESCAN_TEMP_ERR, /**< Error saving to temp file */
46 * @brief File saving statuses
50 SAVE_DONE = 0, /**< Saving succeeded */
51 SAVE_FAILED, /**< Saving failed */
52 SAVE_PACK_FAILED, /**< Plugin failed to pack file */
53 SAVE_NO_FILENAME, /**< File has no filename */
54 SAVE_CANCELLED, /**< Saving was cancelled */
58 * @brief Types for buffer. Buffer's type defines behavior
59 * of buffer when saving etc.
61 * Difference between BUFFERTYPE::NORMAL and BUFFERTYPE::NORMAL_NAMED is
62 * that _NAMED has description text given and which is shown
63 * instead of filename.
65 * BUFFERTYPE::UNNAMED is created empty buffer (scratchpad), it has
66 * no filename, and default description is given for it. After
67 * this buffer is saved it becomes _SAVED. It is not equal to
68 * NORMAL_NAMED, since scratchpads don't have plugins etc.
72 NORMAL = 0, /**< Normal, file loaded from disk */
73 NORMAL_NAMED, /**< Normal, description given */
74 UNNAMED, /**< Empty, created buffer */
75 UNNAMED_SAVED, /**< Empty buffer saved with filename */
79 std::array<int, 3> begin; // 0-based, eg, begin[0] is from str1
80 std::array<int, 3> end; // 0-based, eg, end[1] is from str2
81 std::array<int, 3> beginline;
82 std::array<int, 3> endline;
85 WordDiff(int s1=0, int e1=0, int bl1=0, int el1=0, int s2=0, int e2=0, int bl2=0, int el2=0, int s3=0, int e3=0, int bl3=0, int el3=0, int op=0)
87 , beginline{bl1, bl2, bl3}
88 , endline{el1, el2, el3}
100 struct CurrentWordDiff
110 class CMergeEditView;
112 class PrediffingInfo;
113 class CMergeEditFrame;
115 class CEncodingErrorBar;
117 class CMergeEditSplitterView;
120 * @brief Document class for merging two files
122 class CMergeDoc : public CDocument, public IMergeDoc
125 struct TableProps { bool istable; tchar_t delimiter; tchar_t quote; bool allowNewlinesInQuotes; };
128 static int m_nBuffersTemp;
130 // Begin declaration of CMergeDoc
132 std::unique_ptr<CDiffTextBuffer> m_ptBuf[3]; /**< Left/Middle/Right side text buffer */
136 protected: // create from serialization only
138 DECLARE_DYNCREATE(CMergeDoc)
142 std::unique_ptr<DiffFileInfo> m_pSaveFileInfo[3];
143 std::unique_ptr<DiffFileInfo> m_pRescanFileInfo[3];
145 UINT m_nTrivialDiffs; /**< Amount of trivial (ignored) diffs */
146 PathContext m_filePaths; /**< Filepaths for this document */
147 /// String of concatenated filenames as text to apply plugins filter to
148 String m_strBothFilenames;
150 CMergeEditView * GetActiveMergeView();
151 CMergeEditView * GetActiveMergeGroupView(int nBuffer);
152 void UpdateHeaderPath(int pane);
153 void UpdateHeaderActivity(int pane, bool bActivate);
154 void RefreshOptions();
155 void UpdateResources();
156 bool OpenDocs(int nFiles, const FileLocation fileloc[],
157 const bool bRO[], const String strDesc[]);
158 int LoadFile(const String& sFileName, int nBuffer, bool & readOnly, const FileTextEncoding & encoding);
159 void MoveOnLoad(int nPane = -1, int nLinIndex = -1, bool bRealLine = false, int nCharIndex = -1);
160 void ChangeFile(int nBuffer, const String& path, int nLineIndex = -1);
161 void RescanIfNeeded(float timeOutInSecond);
162 int Rescan(bool &bBinary, IDENTLEVEL &identical, bool bForced = false);
163 void CheckFileChanged(void) override;
164 int ShowMessageBox(const String& sText, unsigned nType = MB_OK, unsigned nIDHelp = 0);
165 void ShowRescanError(int nRescanResult, IDENTLEVEL identical);
167 void CopyAllList(int srcPane, int dstPane);
168 void CopyMultipleList(int srcPane, int dstPane, int firstDiff, int lastDiff, int firstWordDiff = -1, int lastWordDiff = -1);
169 void CopyMultiplePartialList(int srcPane, int dstPane, int firstDiff, int lastDiff, int firstLineDiff = -1, int lastLineDiff = -1);
170 void DoAutoMerge(int dstPane);
171 bool SanityCheckDiff(const DIFFRANGE& dr) const;
172 bool WordListCopy(int srcPane, int dstPane, int nDiff, int nFirstWordDiff, int nLastWordDiff, const std::vector<int> *pWordDiffIndice, bool bGroupWithPrevious = false, bool bUpdateView = true);
173 bool PartialListCopy(int srcPane, int dstPane, int nDiff, int firstLine, int lastLine = -1, bool bGroupWithPrevious = false, bool bUpdateView = true);
174 bool ListCopy(int srcPane, int dstPane, int nDiff = -1, bool bGroupWithPrevious = false, bool bUpdateView = true);
175 bool TrySaveAs(String& strPath, int &nLastErrorCode, String & sError,
176 int nBuffer, PackingInfo& infoTempUnpacker);
177 bool DoSave(const tchar_t* szPath, bool &bSaveSuccess, int nBuffer);
178 bool DoSaveAs(const tchar_t* szPath, bool &bSaveSuccess, int nBuffer);
179 int RightLineInMovedBlock(int pane, int line);
180 int LeftLineInMovedBlock(int pane, int line);
181 void SetEditedAfterRescan(int nBuffer);
182 bool IsEditedAfterRescan(int nBuffer = -1) const;
184 const PackingInfo* GetUnpacker() const override { return &m_infoUnpacker; }
185 void SetUnpacker(const PackingInfo * infoUnpacker) override;
186 void SetPrediffer(const PrediffingInfo * infoPrediffer);
187 void GetPrediffer(PrediffingInfo * infoPrediffer) const;
188 const PrediffingInfo *GetPrediffer() const override;
189 void AddMergeViews(CMergeEditSplitterView* pMergeEditSplitterView, CMergeEditView* pView[3]);
190 void RemoveMergeViews(CMergeEditSplitterView* pMergeEditSplitterView);
191 void SetLocationView(CLocationView *pLocationView) { m_pLocationView = pLocationView; }
193 CDirDoc * GetDirDoc() const override { return m_pDirDoc; }
194 void SetDirDoc(CDirDoc * pDirDoc) override;
195 void DirDocClosing(CDirDoc * pDirDoc) override;
196 bool CloseNow() override;
197 int GetFileCount() const override { return m_filePaths.GetSize(); }
198 String GetPath(int pane) const override { return m_filePaths[pane]; }
199 bool GetReadOnly(int pane) const override { return m_ptBuf[pane]->m_bReadOnly; }
200 CString GetTooltipString() const override;
201 void SwapFiles(int nFromIndex, int nToIndex);
203 CMergeEditView * GetView(int group, int buffer) const { return m_pView[group][buffer]; }
204 CLocationView * GetLocationView() { return m_pLocationView; }
205 std::vector<CMergeEditView *> GetViewList(int nGroup = -1, int nBuffer = -1) const {
206 std::vector<CMergeEditView *> list;
208 for (int nBuffer2 = 0; nBuffer2 < m_nBuffers; ++nBuffer2)
209 list.push_back(m_pView[nGroup][nBuffer2]);
210 else if (nBuffer != -1)
211 for (int nGroup2 = 0; nGroup2 < m_nGroups; ++nGroup2)
212 list.push_back(m_pView[nGroup2][nBuffer]);
215 for (int nGroup3 = 0; nGroup3 < m_nGroups; nGroup3++)
216 for (int nBuffer3 = 0; nBuffer3 < m_nBuffers; ++nBuffer3)
217 list.push_back(m_pView[nGroup3][nBuffer3]);
221 template <typename Function>
222 void ForEachView(int nBuffer, Function func) {
223 for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
224 func(m_pView[nGroup][nBuffer]);
226 template <typename Function>
227 void ForEachView(Function func) {
228 for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
230 for (int nGroup = 0; nGroup < m_nGroups; nGroup++)
231 func(m_pView[nGroup][nBuffer]);
234 template <typename Function>
235 void ForEachActiveGroupView(Function func) {
236 int nGroup = GetActiveMergeView()->m_nThisGroup;
237 for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
238 func(m_pView[nGroup][nBuffer]);
240 CMergeEditFrame * GetParentFrame();
243 bool DeleteSyncPoint(int pane, int nLine, bool bRescan = true);
244 void ClearSyncPoints();
245 bool HasSyncPoints();
246 std::vector<std::vector<int> > GetSyncPointList();
247 String GetDescription(int pane) const override { return m_strDesc[pane]; }
248 void SetDescription(int pane, const String& sText) { m_strDesc[pane] = sText; }
251 // ClassWizard generated virtual function overrides
252 //{{AFX_VIRTUAL(CMergeDoc)
254 virtual BOOL OnNewDocument();
255 virtual void Serialize(CArchive& ar);
256 virtual BOOL SaveModified();
257 virtual void DeleteContents ();
258 virtual void SetTitle(LPCTSTR lpszTitle);
261 // Implementation in MergeDocLineDiffs.cpp
263 typedef enum { BYTEDIFF, WORDDIFF } DIFFLEVEL;
264 void Showlinediff(CMergeEditView *pView, bool bReversed = false);
265 void AddToSubstitutionFilters(CMergeEditView* pView, bool bReversed = false);
266 void AddToLineFilters(const String& text);
267 std::vector<WordDiff> GetWordDiffArrayInDiffBlock(int nDiff);
268 std::vector<WordDiff> GetWordDiffArray(int nLineIndex);
269 std::vector<WordDiff> GetWordDiffArrayInRange(const int begin[3], const int end[3], int pane1 = -1, int pane2 = -1);
270 void ClearWordDiffCache(int nDiff = -1);
272 void Computelinediff(CMergeEditView *pView, std::pair<CEPoint, CEPoint> rc[], bool bReversed);
273 std::map<int, std::vector<WordDiff> > m_cacheWordDiffs;
274 // End MergeDocLineDiffs.cpp
276 // Implementation in MergeDocEncoding.cpp
278 bool DoFileEncodingDialog(int pane = -1);
279 // End MergeDocEncoding.cpp
283 FileChange IsFileChangedOnDisk(const tchar_t* szPath, DiffFileInfo &dfi,
284 bool bSave, int nBuffer);
285 bool PromptAndSaveIfNeeded(bool bAllowCancel);
286 std::vector<int> undoTgt;
287 std::vector<int>::iterator curUndo;
288 void FlushAndRescan(bool bForced = false);
289 void SetCurrentDiff(int nDiff);
290 int GetCurrentDiff() const { return m_nCurDiff; }
291 const CurrentWordDiff& GetCurrentWordDiff() const { return m_CurWordDiff; }
292 bool EqualCurrentWordDiff(int nBuffer, const CEPoint& ptStart, const CEPoint& ptEnd) const
294 return (m_CurWordDiff.nPane == nBuffer && m_CurWordDiff.ptStart == ptStart && m_CurWordDiff.ptEnd == ptEnd);
296 virtual ~CMergeDoc();
297 void SetDetectMovedBlocks(bool bDetectMovedBlocks);
298 bool IsMixedEOL(int nBuffer) const;
299 bool GenerateReport(const String& sFileName) const override;
300 void SetAutoMerged(bool bAutoMerged) { m_bAutoMerged = bAutoMerged; }
301 bool GetAutoMerged() const { return m_bAutoMerged; };
302 bool IsModified() const
304 for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
305 if (m_ptBuf[nBuffer]->IsModified())
311 for (int nBuffer = 0; nBuffer < m_nBuffers; ++nBuffer)
312 if (m_ptBuf[nBuffer]->CanUndo())
317 std::optional<bool> GetEnableTableEditing() const { return m_bEnableTableEditing; }
318 void SetEnableTableEditing(std::optional<bool> bEnableTableEditing) { m_bEnableTableEditing = bEnableTableEditing; }
319 static TableProps MakeTablePropertiesByFileName(const String& path, const std::optional<bool>& enableTableEditing, bool showDialog = true);
320 void SetPreparedTableProperties(const TableProps& props) { m_pTablePropsPrepared.reset(new TableProps(props)); }
322 void SetTextType(int textType);
323 void SetTextType(const String& ext);
324 bool GetChangedSchemeManually() const { return m_bChangedSchemeManually; }
326 bool GetAutomaticRescan() const { return m_bAutomaticRescan; }
327 // to customize the mergeview menu
328 HMENU createPrediffersSubmenu(HMENU hMenu);
329 String GetSaveAsPath() const { return m_strSaveAsPath; }
330 void SetSaveAsPath(const String& strSaveAsPath) { m_strSaveAsPath = strSaveAsPath; }
332 // implementation methods
334 bool GetBreakType() const;
335 bool GetByteColoringOption() const;
336 bool IsValidCodepageForMergeEditor(unsigned cp) const;
337 void SanityCheckCodepage(FileLocation & fileinfo);
338 FileLoadResult::flags_t LoadOneFile(int index, const String& filename, bool readOnly, const String& strDesc, const FileTextEncoding & encoding);
339 void SetTableProperties();
341 // Implementation data
343 int m_nCurDiff; /**< Selected diff, 0-based index, -1 if no diff selected */
344 CurrentWordDiff m_CurWordDiff;
345 CMergeEditView * m_pView[3][3]; /**< Pointer to left/middle/right view */
346 CMergeEditSplitterView * m_pMergeEditSplitterView[3];
347 CLocationView * m_pLocationView; /**< Pointer to locationview */
349 bool m_bEnableRescan; /**< Automatic rescan enabled/disabled */
350 COleDateTime m_LastRescan; /**< Time of last rescan (for delaying) */
351 CDiffWrapper m_diffWrapper;
352 /// information about the file packer/unpacker
353 PackingInfo m_infoUnpacker;
354 String m_strDesc[3]; /**< Left/Middle/Right side description text */
355 BUFFERTYPE m_nBufferType[3];
356 bool m_bEditAfterRescan[3]; /**< Left/middle/right doc edited after rescanning */
357 TempFile m_tempFiles[3]; /**< Temp files for compared files */
358 String m_strSaveAsPath; /**< "3rd path" where output saved if given */
360 bool m_bInvertDiffContext;
361 bool m_bMixedEol; /**< Does this document have mixed EOL style? */
362 std::unique_ptr<CEncodingErrorBar> m_pEncodingErrorBar;
363 bool m_bHasSyncPoints;
365 std::optional<bool> m_bEnableTableEditing;
366 std::unique_ptr<TableProps> m_pTablePropsPrepared;
368 * Are automatic rescans enabled?
369 * If automatic rescans are enabled then we rescan files after edit
370 * events, unless timer suppresses rescan. We suppress rescans within
371 * certain time from previous rescan.
373 bool m_bAutomaticRescan;
374 /// active prediffer ID : helper to check the radio button
375 int m_CurrentPredifferID;
376 bool m_bChangedSchemeManually; /**< `true` if the syntax highlighting scheme is changed manually */
377 String m_sCurrentHeaderTitle[3];
380 friend class RescanSuppress;
383 // Generated message map functions
385 //{{AFX_MSG(CMergeDoc)
386 afx_msg void OnFileSave();
387 afx_msg void OnUpdateFileSave(CCmdUI* pCmdUI);
388 afx_msg void OnFileSaveLeft();
389 afx_msg void OnUpdateFileSaveLeft(CCmdUI* pCmdUI);
390 afx_msg void OnFileSaveMiddle();
391 afx_msg void OnUpdateFileSaveMiddle(CCmdUI* pCmdUI);
392 afx_msg void OnFileSaveRight();
393 afx_msg void OnUpdateFileSaveRight(CCmdUI* pCmdUI);
394 afx_msg void OnFileSaveAsLeft();
395 afx_msg void OnUpdateFileSaveAsMiddle(CCmdUI* pCmdUI);
396 afx_msg void OnFileSaveAsMiddle();
397 afx_msg void OnFileSaveAsRight();
398 afx_msg void OnUpdateStatusNum(CCmdUI* pCmdUI);
399 afx_msg void OnFileReload();
400 afx_msg void OnFileEncoding();
401 afx_msg void OnFileReadOnlyLeft();
402 afx_msg void OnUpdateFileReadOnlyLeft(CCmdUI* pCmdUI);
403 afx_msg void OnFileReadOnlyMiddle();
404 afx_msg void OnUpdateFileReadOnlyMiddle(CCmdUI* pCmdUI);
405 afx_msg void OnFileReadOnlyRight();
406 afx_msg void OnUpdateFileReadOnlyRight(CCmdUI* pCmdUI);
407 afx_msg void OnUpdateStatusRO(CCmdUI* pCmdUI);
408 afx_msg void OnDiffContext(UINT nID);
409 afx_msg void OnUpdateDiffContext(CCmdUI* pCmdUI);
410 afx_msg void OnToolsGenerateReport();
411 afx_msg void OnToolsGeneratePatch();
412 afx_msg void OnOpenWithUnpacker();
413 afx_msg void OnApplyPrediffer();
414 afx_msg void OnBnClickedFileEncoding();
415 afx_msg void OnBnClickedPlugin();
416 afx_msg void OnBnClickedHexView();
418 afx_msg void OnFileRecompareAsText();
419 afx_msg void OnFileRecompareAsTable();
420 afx_msg void OnUpdateFileRecompareAsText(CCmdUI* pCmdUI);
421 afx_msg void OnUpdateFileRecompareAsTable(CCmdUI* pCmdUI);
422 afx_msg void OnFileRecompareAs(UINT nID);
423 template<int srcPane, int dstPane>
424 afx_msg void OnViewSwapPanes();
425 afx_msg void OnUpdateSwapContext(CCmdUI* pCmdUI);
426 afx_msg void OnRefresh();
427 afx_msg void OnUpdatePrediffer(CCmdUI* pCmdUI);
428 afx_msg void OnPrediffer(UINT nID );
430 DECLARE_MESSAGE_MAP()
432 void PrimeTextBuffers();
434 void AdjustDiffBlocks();
435 void AdjustDiffBlocks3way();
436 void AdjustDiffBlock(DiffMap & diffmap, const DIFFRANGE & diffrange,
437 const std::vector<WordDiff>& worddiffs,
438 int i0, int i1, int lo0, int hi0, int lo1, int hi1);
439 int GetMatchCost(const DIFFRANGE& dr, int i0, int i1, int line0, int line1, const std::vector<WordDiff>& worddiffs);
440 OP_TYPE ComputeOpType3way(const std::vector<std::array<int, 3>>& vlines, size_t index,
441 const DIFFRANGE& diffrange, const DIFFOPTIONS& diffOptions);
442 void FlagTrivialLines();
443 void FlagMovedLines();
444 String GetFileExt(const tchar_t* sFileName, const tchar_t* sDescription) const;
445 void DoFileSave(int pane);
446 void SetPredifferByMenu(UINT nID);
450 * @brief return true if there are synchronization points
452 inline bool CMergeDoc::HasSyncPoints()
454 return m_bHasSyncPoints;