OSDN Git Service

Merge with xdiff
[winmerge-jp/winmerge-jp.git] / Src / DiffTextBuffer.cpp
1 /** 
2  * @file  DiffTextBuffer.cpp
3  *
4  * @brief Implementation file for CDiffTextBuffer
5  *
6  */
7
8 #include "StdAfx.h"
9 #include "DiffTextBuffer.h"
10 #include <Poco/Exception.h>
11 #include "UniFile.h"
12 #include "files.h"
13 #include "locality.h"
14 #include "paths.h"
15 #include "OptionsDef.h"
16 #include "OptionsMgr.h"
17 #include "Environment.h"
18 #include "MergeLineFlags.h"
19 #include "MergeDoc.h"
20 #include "FileTransform.h"
21 #include "FileTextEncoding.h"
22 #include "codepage_detect.h"
23 #include "TFile.h"
24
25 using Poco::Exception;
26
27 #ifdef _DEBUG
28 #define new DEBUG_NEW
29 #endif
30
31 static bool IsTextFileStylePure(const UniMemFile::txtstats & stats);
32 static CRLFSTYLE GetTextFileStyle(const UniMemFile::txtstats & stats);
33
34 /**
35  * @brief Check if file has only one EOL type.
36  * @param [in] stats File's text stats.
37  * @return true if only one EOL type is found, false otherwise.
38  */
39 static bool IsTextFileStylePure(const UniMemFile::txtstats & stats)
40 {
41         int nType = 0;
42         if (stats.ncrlfs > 0)
43                 nType++;
44         if ( stats.ncrs > 0)
45                 nType++;
46         if (stats.nlfs > 0)
47                 nType++;
48         return (nType <= 1);
49 }
50
51 /**
52  * @brief Get file's EOL type.
53  * @param [in] stats File's text stats.
54  * @return EOL type.
55  */
56 static CRLFSTYLE GetTextFileStyle(const UniMemFile::txtstats & stats)
57 {
58         // Check if file has more than one EOL type.
59         if (!IsTextFileStylePure(stats))
60                         return CRLF_STYLE_MIXED;
61         else if (stats.ncrlfs >= stats.nlfs)
62         {
63                 if (stats.ncrlfs >= stats.ncrs)
64                         return CRLF_STYLE_DOS;
65                 else
66                         return CRLF_STYLE_MAC;
67         }
68         else
69         {
70                 if (stats.nlfs >= stats.ncrs)
71                         return CRLF_STYLE_UNIX;
72                 else
73                         return CRLF_STYLE_MAC;
74         }
75 }
76
77 /**
78  * @brief Constructor.
79  * @param [in] pDoc Owning CMergeDoc.
80  * @param [in] pane Pane number this buffer is associated with.
81  */
82 CDiffTextBuffer::CDiffTextBuffer(CMergeDoc * pDoc, int pane)
83 : m_pOwnerDoc(pDoc)
84 , m_nThisPane(pane)
85 , m_unpackerSubcode(0)
86 , m_bMixedEOL(false)
87 {
88 }
89
90 /**
91  * @brief Get a line from the buffer.
92  * @param [in] nLineIndex Index of the line to get.
93  * @param [out] strLine Returns line text in the index.
94  */
95 bool CDiffTextBuffer::GetLine(int nLineIndex, CString &strLine) const
96 {
97         int nLineLength = CCrystalTextBuffer::GetLineLength(nLineIndex);
98         if (nLineLength < 0)
99                 return false;
100         else if (nLineLength == 0)
101                 strLine.Empty();
102         else
103         {
104                 _tcsncpy_s(strLine.GetBuffer(nLineLength + 1), nLineLength + 1,
105                         CCrystalTextBuffer::GetLineChars(nLineIndex), nLineLength);
106                 strLine.ReleaseBuffer(nLineLength);
107         }
108         return true;
109 }
110
111 /**
112  * @brief Set the buffer modified status.
113  * @param [in] bModified New modified status, true if buffer has been
114  *   modified since last saving.
115  */
116 void CDiffTextBuffer::                  /* virtual override */
117 SetModified(bool bModified /*= true*/)  
118 {
119         CCrystalTextBuffer::SetModified (bModified);
120         m_pOwnerDoc->SetModifiedFlag (bModified);
121 }
122
123 /**
124  * @brief Get a line (with EOL bytes) from the buffer.
125  * This function is like GetLine() but it also includes line's EOL to the
126  * returned string.
127  * @param [in] nLineIndex Index of the line to get.
128  * @param [out] strLine Returns line text in the index. Existing content
129  * of this string is overwritten.
130  */
131 bool CDiffTextBuffer::GetFullLine(int nLineIndex, CString &strLine) const
132 {
133         int cchText = GetFullLineLength(nLineIndex);
134         if (cchText == 0)
135         {
136                 strLine.Empty();
137                 return false;
138         }
139         LPTSTR pchText = strLine.GetBufferSetLength(cchText);
140         memcpy(pchText, GetLineChars(nLineIndex), cchText * sizeof(TCHAR));
141         return true;
142 }
143
144 void CDiffTextBuffer::                  /* virtual override */
145 AddUndoRecord(bool bInsert, const CPoint & ptStartPos,
146                 const CPoint & ptEndPos, LPCTSTR pszText, size_t cchText,
147                 int nActionType /*= CE_ACTION_UNKNOWN*/,
148                 CDWordArray *paSavedRevisionNumbers /*= nullptr*/)
149 {
150         CGhostTextBuffer::AddUndoRecord(bInsert, ptStartPos, ptEndPos, pszText,
151                 cchText, nActionType, paSavedRevisionNumbers);
152         if (m_aUndoBuf[m_nUndoPosition - 1].m_dwFlags & UNDO_BEGINGROUP)
153         {
154                 m_pOwnerDoc->undoTgt.erase(m_pOwnerDoc->curUndo, m_pOwnerDoc->undoTgt.end());
155                 m_pOwnerDoc->undoTgt.push_back(m_nThisPane);
156                 m_pOwnerDoc->curUndo = m_pOwnerDoc->undoTgt.end();
157         }
158 }
159
160 /**
161  * @brief Checks if a flag is set for line.
162  * @param [in] line Index (0-based) for line.
163  * @param [in] flag Flag to check.
164  * @return true if flag is set, false otherwise.
165  */
166 bool CDiffTextBuffer::FlagIsSet(UINT line, DWORD flag) const
167 {
168         return ((m_aLines[line].m_dwFlags & flag) == flag);
169 }
170
171 /**
172 Remove blank lines and clear winmerge flags
173 (2003-06-21, Perry: I don't understand why this is necessary, but if this isn't 
174 done, more and more gray lines appear in the file)
175 (2003-07-31, Laoran I don't understand either why it is necessary, but it works
176 fine, so let's go on with it)
177 */
178 void CDiffTextBuffer::prepareForRescan()
179 {
180         RemoveAllGhostLines();
181         for (int ct = GetLineCount() - 1; ct >= 0; --ct)
182         {
183                 SetLineFlag(ct, 
184                         LF_INVISIBLE | LF_DIFF | LF_TRIVIAL | LF_MOVED | LF_SNP,
185                         false, false, false);
186         }
187 }
188
189 /** 
190  * @brief Called when line has been edited.
191  * After editing a line, we don't know if there is a diff or not.
192  * So we clear the LF_DIFF flag (and it is more easy to read during editing).
193  * Rescan will set the proper color.
194  * @param [in] nLine Line that has been edited.
195  */
196
197 void CDiffTextBuffer::                  /* virtual override */
198 OnNotifyLineHasBeenEdited(int nLine)
199 {
200         SetLineFlag(nLine, LF_DIFF, false, false, false);
201         SetLineFlag(nLine, LF_TRIVIAL, false, false, false);
202         SetLineFlag(nLine, LF_MOVED, false, false, false);
203         SetLineFlag(nLine, LF_SNP, false, false, false);
204         CGhostTextBuffer::OnNotifyLineHasBeenEdited(nLine);
205 }
206
207 /**
208  * @brief Set the folder for temp files.
209  * @param [in] path Temp files folder.
210  */
211 void CDiffTextBuffer::SetTempPath(const String &path)
212 {
213         m_strTempPath = path;
214 }
215
216 /**
217  * @brief Is the buffer initialized?
218  * @return true if the buffer is initialized, false otherwise.
219  */
220 bool CDiffTextBuffer::IsInitialized() const
221 {
222         return !!m_bInit;
223 }
224
225 /**
226  * @brief Load file from disk into buffer
227  *
228  * @param [in] pszFileNameInit File to load
229  * @param [in] infoUnpacker Unpacker plugin
230  * @param [in] sToFindUnpacker String for finding unpacker plugin
231  * @param [out] readOnly Loading was lossy so file should be read-only
232  * @param [in] nCrlfStyle EOL style used
233  * @param [in] encoding Encoding used
234  * @param [out] sError Error message returned
235  * @return FRESULT_OK when loading succeed or (list in files.h):
236  * - FRESULT_OK_IMPURE : load OK, but the EOL are of different types
237  * - FRESULT_ERROR_UNPACK : plugin failed to unpack
238  * - FRESULT_ERROR : loading failed, sError contains error message
239  * - FRESULT_BINARY : file is binary file
240  * @note If this method fails, it calls InitNew so the CDiffTextBuffer is in a valid state
241  */
242 int CDiffTextBuffer::LoadFromFile(LPCTSTR pszFileNameInit,
243                 PackingInfo * infoUnpacker, LPCTSTR sToFindUnpacker, bool & readOnly,
244                 CRLFSTYLE nCrlfStyle, const FileTextEncoding & encoding, CString &sError)
245 {
246         ASSERT(!m_bInit);
247         ASSERT(m_aLines.size() == 0);
248
249         // Unpacking the file here, save the result in a temporary file
250         String sFileName(pszFileNameInit);
251         if (!FileTransform::Unpacking(infoUnpacker, sFileName, sToFindUnpacker))
252         {
253                 InitNew(); // leave crystal editor in valid, empty state
254                 return FileLoadResult::FRESULT_ERROR_UNPACK;
255         }
256         m_unpackerSubcode = infoUnpacker->m_subcode;
257
258         // we use the same unpacker for both files, so it must be defined after first file
259         ASSERT(infoUnpacker->m_PluginOrPredifferMode != PLUGIN_AUTO);
260         // we will load the transformed file
261         LPCTSTR pszFileName = sFileName.c_str();
262
263         String sExt;
264         DWORD nRetVal = FileLoadResult::FRESULT_OK;
265
266         // Set encoding based on extension, if we know one
267         paths::SplitFilename(pszFileName, nullptr, nullptr, &sExt);
268         CCrystalTextView::TextDefinition *def = 
269                 CCrystalTextView::GetTextType(sExt.c_str());
270         if (def && def->encoding != -1)
271                 m_nSourceEncoding = def->encoding;
272         
273         UniFile *pufile = infoUnpacker->m_pufile;
274         if (pufile == nullptr)
275                 pufile = new UniMemFile;
276
277         // Now we only use the UniFile interface
278         // which is something we could implement for HTTP and/or FTP files
279
280         if (!pufile->OpenReadOnly(pszFileName))
281         {
282                 nRetVal = FileLoadResult::FRESULT_ERROR;
283                 UniFile::UniError uniErr = pufile->GetLastUniError();
284                 if (uniErr.HasError())
285                 {
286                         sError = uniErr.GetError().c_str();
287                 }
288                 InitNew(); // leave crystal editor in valid, empty state
289         }
290         else
291         {
292                 if (infoUnpacker->m_PluginName.length() > 0)
293                 {
294                         // re-detect codepage
295                         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
296                         FileTextEncoding encoding2 = GuessCodepageEncoding(pszFileName, iGuessEncodingType);
297                         pufile->SetUnicoding(encoding2.m_unicoding);
298                         pufile->SetCodepage(encoding2.m_codepage);
299                         pufile->SetBom(encoding2.m_bom);
300                         if (encoding2.m_bom)
301                                 pufile->ReadBom();
302                 }
303                 else
304                 {
305                         // If the file is not unicode file, use the codepage we were given to
306                         // interpret the 8-bit characters. If the file is unicode file,
307                         // determine its type (IsUnicode() does that).
308                         if (encoding.m_unicoding == ucr::NONE  || !pufile->IsUnicode())
309                                 pufile->SetCodepage(encoding.m_codepage);
310                 }
311                 UINT lineno = 0;
312                 String eol, preveol;
313                 String sline;
314                 bool done = false;
315                 COleDateTime start = COleDateTime::GetCurrentTime(); // for trace messages
316
317                 // Manually grow line array exponentially
318                 UINT arraysize = 500;
319                 m_aLines.resize(arraysize);
320                 
321                 // preveol must be initialized for empty files
322                 preveol = _T("\n");
323                 
324                 do {
325                         bool lossy = false;
326                         done = !pufile->ReadString(sline, eol, &lossy);
327
328                         // if last line had no eol, we can quit
329                         if (done && preveol.empty())
330                                 break;
331                         // but if last line had eol, we add an extra (empty) line to buffer
332
333                         // Grow line array
334                         if (lineno == arraysize)
335                         {
336                                 // For smaller sizes use exponential growth, but for larger
337                                 // sizes grow by constant ratio. Unlimited exponential growth
338                                 // easily runs out of memory.
339                                 if (arraysize < 100 * 1024)
340                                         arraysize *= 2;
341                                 else
342                                         arraysize += 100 * 1024;
343                                 m_aLines.resize(arraysize);
344                         }
345
346                         sline += eol; // TODO: opportunity for optimization, as CString append is terrible
347                         if (lossy)
348                         {
349                                 // TODO: Should record lossy status of line
350                         }
351                         AppendLine(lineno, sline.c_str(), static_cast<int>(sline.length()));
352                         ++lineno;
353                         preveol = eol;
354
355                 } while (!done);
356
357                 // fix array size (due to our manual exponential growth
358                 m_aLines.resize(lineno);
359         
360                 
361                 //Try to determine current CRLF mode (most frequent)
362                 if (nCrlfStyle == CRLF_STYLE_AUTOMATIC)
363                 {
364                         nCrlfStyle = GetTextFileStyle(pufile->GetTxtStats());
365                 }
366                 ASSERT(nCrlfStyle >= 0 && nCrlfStyle <= 3);
367                 SetCRLFMode(nCrlfStyle);
368                 
369                 //  At least one empty line must present
370                 // (view does not work for empty buffers)
371                 ASSERT(m_aLines.size() > 0);
372                 
373                 m_bInit = true;
374                 m_bModified = false;
375                 m_bUndoGroup = m_bUndoBeginGroup = false;
376                 m_nSyncPosition = m_nUndoPosition = 0;
377                 ASSERT(m_aUndoBuf.size() == 0);
378                 m_ptLastChange.x = m_ptLastChange.y = -1;
379                 
380                 FinishLoading();
381                 // flags don't need initialization because 0 is the default value
382
383                 // Set the return value : OK + info if the file is impure
384                 // A pure file is a file where EOL are consistent (all DOS, or all UNIX, or all MAC)
385                 // An impure file is a file with several EOL types
386                 // WinMerge may display impure files, but the default option is to unify the EOL
387                 // We return this info to the caller, so it may display a confirmation box
388                 if (IsTextFileStylePure(pufile->GetTxtStats()))
389                         nRetVal = FileLoadResult::FRESULT_OK;
390                 else
391                         nRetVal = FileLoadResult::FRESULT_OK_IMPURE;
392
393                 // stash original encoding away
394                 m_encoding.m_unicoding = pufile->GetUnicoding();
395                 m_encoding.m_bom = pufile->HasBom();
396                 m_encoding.m_codepage = pufile->GetCodepage();
397
398                 if (pufile->GetTxtStats().nlosses)
399                 {
400                         FileLoadResult::AddModifier(nRetVal, FileLoadResult::FRESULT_LOSSY);
401                         readOnly = true;
402                 }
403         }
404         
405         // close the file now to free the handle
406         pufile->Close();
407         delete pufile;
408
409         // delete the file that unpacking may have created
410         if (_tcscmp(pszFileNameInit, pszFileName) != 0)
411         {
412                 try
413                 {
414                         TFile(pszFileName).remove();
415                 }
416                 catch (Exception& e)
417                 {
418                         LogErrorStringUTF8(e.displayText());
419                 }
420         }
421         return nRetVal;
422 }
423
424 /**
425  * @brief Saves file from buffer to disk
426  *
427  * @param bTempFile : false if we are saving user files and
428  * true if we are saving working-temp-files for diff-engine
429  *
430  * @return SAVE_DONE or an error code (list in MergeDoc.h)
431  */
432 int CDiffTextBuffer::SaveToFile (const String& pszFileName,
433                 bool bTempFile, String & sError, PackingInfo * infoUnpacker /*= nullptr*/,
434                 CRLFSTYLE nCrlfStyle /*= CRLF_STYLE_AUTOMATIC*/,
435                 bool bClearModifiedFlag /*= true*/,
436                 int nStartLine /*= 0*/, int nLines /*= -1*/)
437 {
438         ASSERT (nCrlfStyle == CRLF_STYLE_AUTOMATIC || nCrlfStyle == CRLF_STYLE_DOS ||
439                 nCrlfStyle == CRLF_STYLE_UNIX || nCrlfStyle == CRLF_STYLE_MAC);
440         ASSERT (m_bInit);
441
442         if (nLines == -1)
443                 nLines = static_cast<int>(m_aLines.size() - nStartLine);
444
445         if (pszFileName.empty())
446                 return SAVE_FAILED;     // No filename, cannot save...
447
448         if (nCrlfStyle == CRLF_STYLE_AUTOMATIC &&
449                 !GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
450                 infoUnpacker!=nullptr && infoUnpacker->m_bDisallowMixedEOL)
451         {
452                         // get the default nCrlfStyle of the CDiffTextBuffer
453                 nCrlfStyle = GetCRLFMode();
454                 ASSERT(nCrlfStyle >= 0 && nCrlfStyle <= 3);
455         }
456
457         bool bOpenSuccess = true;
458         bool bSaveSuccess = false;
459
460         UniStdioFile file;
461
462         String sIntermediateFilename; // used when !bTempFile
463
464         if (bTempFile)
465         {
466                 file.SetUnicoding(ucr::UTF8);
467                 file.SetBom(true);
468                 bOpenSuccess = !!file.OpenCreate(pszFileName);
469         }
470         else
471         {
472                 file.SetUnicoding(m_encoding.m_unicoding);
473                 file.SetBom(m_encoding.m_bom);
474                 file.SetCodepage(m_encoding.m_codepage);
475                 sIntermediateFilename = env::GetTemporaryFileName(m_strTempPath,
476                         _T("MRG_"), nullptr);
477                 if (sIntermediateFilename.empty())
478                         return SAVE_FAILED;  //Nothing to do if even tempfile name fails
479                 bOpenSuccess = !!file.OpenCreate(sIntermediateFilename);
480         }
481
482         if (!bOpenSuccess)
483         {       
484                 UniFile::UniError uniErr = file.GetLastUniError();
485                 if (uniErr.HasError())
486                 {
487                         sError = uniErr.GetError();
488                         if (bTempFile)
489                                 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
490                                         pszFileName.c_str(), sError.c_str()));
491                         else
492                                 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
493                                         sIntermediateFilename.c_str(), sError.c_str()));
494                 }
495                 return SAVE_FAILED;
496         }
497
498         file.WriteBom();
499
500         // line loop : get each real line and write it in the file
501         String sLine;
502         String sEol = GetStringEol(nCrlfStyle);
503         int lastRealLine = ApparentLastRealLine();
504         for (int line = nStartLine; line < nStartLine + nLines; ++line)
505         {
506                 if (GetLineFlags(line) & LF_GHOST)
507                         continue;
508
509                 // get the characters of the line (excluding EOL)
510                 if (GetLineLength(line) > 0)
511                 {
512                         int nLineLength = GetLineLength(line);
513                         sLine.resize(0);
514                         sLine.reserve(nLineLength + 4);
515                         sLine.append(GetLineChars(line), nLineLength);
516                 }
517                 else
518                         sLine = _T("");
519
520                 // last real line ?
521                 if (line == lastRealLine || lastRealLine == -1 )
522                 {
523                         // If original last line had no EOL, then we are done
524                         if( !m_aLines[line].HasEol() )
525                         {
526                                 file.WriteString(sLine);
527                                 break;
528                         }
529                         // Otherwise, add the appropriate EOL to the last line ...
530                 }
531
532                 // normal line : append an EOL
533                 if (nCrlfStyle == CRLF_STYLE_AUTOMATIC || nCrlfStyle == CRLF_STYLE_MIXED)
534                 {
535                         // either the EOL of the line (when preserve original EOL chars is on)
536                         sLine += GetLineEol(line);
537                 }
538                 else
539                 {
540                         // or the default EOL for this file
541                         sLine += sEol;
542                 }
543
544                 // write this line to the file (codeset or unicode conversions are done there
545                 file.WriteString(sLine);
546
547                 if (line == lastRealLine || lastRealLine == -1)
548                 {
549                         // Last line, so now done
550                         break;
551                 }
552         }
553         file.Close();
554
555         if (!bTempFile)
556         {
557                 // If we are saving user files
558                 // we need an unpacker/packer, at least a "do nothing" one
559                 ASSERT(infoUnpacker != nullptr);
560                 // repack the file here, overwrite the temporary file we did save in
561                 String csTempFileName = sIntermediateFilename;
562                 infoUnpacker->m_subcode = m_unpackerSubcode;
563                 if (!FileTransform::Packing(csTempFileName, *infoUnpacker))
564                 {
565                         try
566                         {
567                                 TFile(sIntermediateFilename).remove();
568                         }
569                         catch (Exception& e)
570                         {
571                                 LogErrorStringUTF8(e.displayText());
572                         }
573                         // returns now, don't overwrite the original file
574                         return SAVE_PACK_FAILED;
575                 }
576                 // the temp filename may have changed during packing
577                 if (csTempFileName != sIntermediateFilename)
578                 {
579                         try
580                         {
581                                 TFile(sIntermediateFilename).remove();
582                         }
583                         catch (Exception& e)
584                         {
585                                 LogErrorStringUTF8(e.displayText());
586                         }
587                         sIntermediateFilename = csTempFileName;
588                 }
589
590                 // Write tempfile over original file
591                 try
592                 {
593                         TFile file1(sIntermediateFilename);
594                         file1.copyTo(pszFileName);
595                         file1.remove();
596                         if (bClearModifiedFlag)
597                         {
598                                 SetModified(false);
599                                 m_nSyncPosition = m_nUndoPosition;
600                         }
601                         bSaveSuccess = true;
602
603                         // remember revision number on save
604                         m_dwRevisionNumberOnSave = m_dwCurrentRevisionNumber;
605
606                         // redraw line revision marks
607                         UpdateViews (nullptr, nullptr, UPDATE_FLAGSONLY);       
608                 }
609                 catch (Exception& e)
610                 {
611                         LogErrorStringUTF8(e.displayText());
612                 }
613         }
614         else
615         {
616                 if (bClearModifiedFlag)
617                 {
618                         SetModified(false);
619                         m_nSyncPosition = m_nUndoPosition;
620                 }
621                 bSaveSuccess = true;
622         }
623
624         if (bSaveSuccess)
625                 return SAVE_DONE;
626         else
627                 return SAVE_FAILED;
628 }
629
630 /// Replace line (removing any eol, and only including one if in strText)
631 void CDiffTextBuffer::ReplaceFullLines(CDiffTextBuffer& dbuf, CDiffTextBuffer& sbuf, CCrystalTextView * pSource, int nLineBegin, int nLineEnd, int nAction /*=CE_ACTION_UNKNOWN*/)
632 {
633         CString strText;
634         if (nLineBegin != nLineEnd || sbuf.GetLineLength(nLineEnd) > 0)
635                 sbuf.GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, sbuf.GetLineLength(nLineEnd), strText);
636         strText += sbuf.GetLineEol(nLineEnd);
637
638         if (nLineBegin != nLineEnd || dbuf.GetFullLineLength(nLineEnd) > 0)
639         {
640                 int nLineEndSource = nLineEnd < dbuf.GetLineCount() ? nLineEnd : dbuf.GetLineCount();
641                 if (nLineEnd+1 < GetLineCount())
642                         dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource + 1, 0, nAction);
643                 else
644                         dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource, dbuf.GetLineLength(nLineEndSource), nAction); 
645         }
646
647         if (int cchText = strText.GetLength())
648         {
649                 int endl,endc;
650                 dbuf.InsertText(pSource, nLineBegin, 0, strText, cchText, endl,endc, nAction);
651         }
652 }
653
654 bool CDiffTextBuffer::curUndoGroup()
655 {
656         return (m_aUndoBuf.size() != 0 && m_aUndoBuf[0].m_dwFlags&UNDO_BEGINGROUP);
657 }
658
659 bool CDiffTextBuffer::                  /* virtual override */
660 DeleteText2(CCrystalTextView * pSource, int nStartLine, int nStartChar,
661         int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
662 {
663         for (auto syncpnt : m_pOwnerDoc->GetSyncPointList())
664         {
665                 const int nLineSyncPoint = syncpnt[m_nThisPane];
666                 if (((nStartChar == 0 && nStartLine == nLineSyncPoint) || nStartLine < nLineSyncPoint) &&
667                         nLineSyncPoint < nEndLine)
668                         m_pOwnerDoc->DeleteSyncPoint(m_nThisPane, nLineSyncPoint, false);
669         }
670         return CGhostTextBuffer::DeleteText2(pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory);
671 }