OSDN Git Service

Fix Warning C26812: Prefer 'enum class' over 'enum' (Enum.3)
[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 CRLFSTYLE::MIXED;
61         else if (stats.ncrlfs >= stats.nlfs)
62         {
63                 if (stats.ncrlfs >= stats.ncrs)
64                         return CRLFSTYLE::DOS;
65                 else
66                         return CRLFSTYLE::MAC;
67         }
68         else
69         {
70                 if (stats.nlfs >= stats.ncrs)
71                         return CRLFSTYLE::UNIX;
72                 else
73                         return CRLFSTYLE::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 Load file from disk into buffer
209  *
210  * @param [in] pszFileNameInit File to load
211  * @param [in] infoUnpacker Unpacker plugin
212  * @param [in] sToFindUnpacker String for finding unpacker plugin
213  * @param [out] readOnly Loading was lossy so file should be read-only
214  * @param [in] nCrlfStyle EOL style used
215  * @param [in] encoding Encoding used
216  * @param [out] sError Error message returned
217  * @return FRESULT_OK when loading succeed or (list in files.h):
218  * - FRESULT_OK_IMPURE : load OK, but the EOL are of different types
219  * - FRESULT_ERROR_UNPACK : plugin failed to unpack
220  * - FRESULT_ERROR : loading failed, sError contains error message
221  * - FRESULT_BINARY : file is binary file
222  * @note If this method fails, it calls InitNew so the CDiffTextBuffer is in a valid state
223  */
224 int CDiffTextBuffer::LoadFromFile(LPCTSTR pszFileNameInit,
225                 PackingInfo * infoUnpacker, LPCTSTR sToFindUnpacker, bool & readOnly,
226                 CRLFSTYLE nCrlfStyle, const FileTextEncoding & encoding, CString &sError)
227 {
228         ASSERT(!m_bInit);
229         ASSERT(m_aLines.size() == 0);
230
231         // Unpacking the file here, save the result in a temporary file
232         m_strTempFileName = pszFileNameInit;
233         if (!FileTransform::Unpacking(infoUnpacker, m_strTempFileName, sToFindUnpacker))
234         {
235                 InitNew(); // leave crystal editor in valid, empty state
236                 return FileLoadResult::FRESULT_ERROR_UNPACK;
237         }
238         m_unpackerSubcode = infoUnpacker->m_subcode;
239
240         // we use the same unpacker for both files, so it must be defined after first file
241         ASSERT(infoUnpacker->m_PluginOrPredifferMode != PLUGIN_MODE::PLUGIN_AUTO);
242         // we will load the transformed file
243         LPCTSTR pszFileName = m_strTempFileName.c_str();
244
245         String sExt;
246         DWORD nRetVal = FileLoadResult::FRESULT_OK;
247
248         // Set encoding based on extension, if we know one
249         paths::SplitFilename(pszFileName, nullptr, nullptr, &sExt);
250         CrystalLineParser::TextDefinition *def = 
251                 CrystalLineParser::GetTextType(sExt.c_str());
252         if (def && def->encoding != -1)
253                 m_nSourceEncoding = def->encoding;
254         
255         UniFile *pufile = infoUnpacker->m_pufile;
256         if (pufile == nullptr)
257                 pufile = new UniMemFile;
258
259         // Now we only use the UniFile interface
260         // which is something we could implement for HTTP and/or FTP files
261
262         if (!pufile->OpenReadOnly(pszFileName))
263         {
264                 nRetVal = FileLoadResult::FRESULT_ERROR;
265                 UniFile::UniError uniErr = pufile->GetLastUniError();
266                 if (uniErr.HasError())
267                 {
268                         sError = uniErr.GetError().c_str();
269                 }
270                 InitNew(); // leave crystal editor in valid, empty state
271         }
272         else
273         {
274                 if (infoUnpacker->m_PluginName.length() > 0)
275                 {
276                         // re-detect codepage
277                         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
278                         FileTextEncoding encoding2 = GuessCodepageEncoding(pszFileName, iGuessEncodingType);
279                         pufile->SetUnicoding(encoding2.m_unicoding);
280                         pufile->SetCodepage(encoding2.m_codepage);
281                         pufile->SetBom(encoding2.m_bom);
282                         if (encoding2.m_bom)
283                                 pufile->ReadBom();
284                 }
285                 else
286                 {
287                         // If the file is not unicode file, use the codepage we were given to
288                         // interpret the 8-bit characters. If the file is unicode file,
289                         // determine its type (IsUnicode() does that).
290                         if (encoding.m_unicoding == ucr::NONE  || !pufile->IsUnicode())
291                                 pufile->SetCodepage(encoding.m_codepage);
292                 }
293                 UINT lineno = 0;
294                 String eol, preveol;
295                 String sline;
296                 bool done = false;
297                 COleDateTime start = COleDateTime::GetCurrentTime(); // for trace messages
298
299                 // Manually grow line array exponentially
300                 UINT arraysize = 500;
301                 m_aLines.resize(arraysize);
302                 
303                 // preveol must be initialized for empty files
304                 preveol = _T("\n");
305                 
306                 do {
307                         bool lossy = false;
308                         done = !pufile->ReadString(sline, eol, &lossy);
309
310                         // if last line had no eol, we can quit
311                         if (done && preveol.empty())
312                                 break;
313                         // but if last line had eol, we add an extra (empty) line to buffer
314
315                         // Grow line array
316                         if (lineno == arraysize)
317                         {
318                                 // For smaller sizes use exponential growth, but for larger
319                                 // sizes grow by constant ratio. Unlimited exponential growth
320                                 // easily runs out of memory.
321                                 if (arraysize < 100 * 1024)
322                                         arraysize *= 2;
323                                 else
324                                         arraysize += 100 * 1024;
325                                 m_aLines.resize(arraysize);
326                         }
327
328                         sline += eol; // TODO: opportunity for optimization, as CString append is terrible
329                         if (lossy)
330                         {
331                                 // TODO: Should record lossy status of line
332                         }
333                         AppendLine(lineno, sline.c_str(), static_cast<int>(sline.length()));
334                         ++lineno;
335                         preveol = eol;
336
337                 } while (!done);
338
339                 // fix array size (due to our manual exponential growth
340                 m_aLines.resize(lineno);
341         
342                 
343                 //Try to determine current CRLF mode (most frequent)
344                 if (nCrlfStyle == CRLFSTYLE::AUTOMATIC)
345                 {
346                         nCrlfStyle = GetTextFileStyle(pufile->GetTxtStats());
347                 }
348                 ASSERT (nCrlfStyle != CRLFSTYLE::AUTOMATIC);
349                 SetCRLFMode(nCrlfStyle);
350                 
351                 //  At least one empty line must present
352                 // (view does not work for empty buffers)
353                 ASSERT(m_aLines.size() > 0);
354                 
355                 m_bInit = true;
356                 m_bModified = false;
357                 m_bUndoGroup = m_bUndoBeginGroup = false;
358                 m_nSyncPosition = m_nUndoPosition = 0;
359                 ASSERT(m_aUndoBuf.size() == 0);
360                 m_ptLastChange.x = m_ptLastChange.y = -1;
361                 
362                 FinishLoading();
363                 // flags don't need initialization because 0 is the default value
364
365                 // Set the return value : OK + info if the file is impure
366                 // A pure file is a file where EOL are consistent (all DOS, or all UNIX, or all MAC)
367                 // An impure file is a file with several EOL types
368                 // WinMerge may display impure files, but the default option is to unify the EOL
369                 // We return this info to the caller, so it may display a confirmation box
370                 if (IsTextFileStylePure(pufile->GetTxtStats()))
371                         nRetVal = FileLoadResult::FRESULT_OK;
372                 else
373                         nRetVal = FileLoadResult::FRESULT_OK_IMPURE;
374
375                 // stash original encoding away
376                 m_encoding.m_unicoding = pufile->GetUnicoding();
377                 m_encoding.m_bom = pufile->HasBom();
378                 m_encoding.m_codepage = pufile->GetCodepage();
379
380                 if (pufile->GetTxtStats().nlosses)
381                 {
382                         FileLoadResult::AddModifier(nRetVal, FileLoadResult::FRESULT_LOSSY);
383                         readOnly = true;
384                 }
385         }
386         
387         // close the file now to free the handle
388         pufile->Close();
389         delete pufile;
390
391         // delete the file that unpacking may have created
392         if (_tcscmp(pszFileNameInit, pszFileName) != 0)
393         {
394                 try
395                 {
396                         TFile(pszFileName).remove();
397                 }
398                 catch (Exception& e)
399                 {
400                         LogErrorStringUTF8(e.displayText());
401                 }
402         }
403         return nRetVal;
404 }
405
406 /**
407  * @brief Saves file from buffer to disk
408  *
409  * @param bTempFile : false if we are saving user files and
410  * true if we are saving working-temp-files for diff-engine
411  *
412  * @return SAVE_DONE or an error code (list in MergeDoc.h)
413  */
414 int CDiffTextBuffer::SaveToFile (const String& pszFileName,
415                 bool bTempFile, String & sError, PackingInfo * infoUnpacker /*= nullptr*/,
416                 CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC*/,
417                 bool bClearModifiedFlag /*= true*/,
418                 int nStartLine /*= 0*/, int nLines /*= -1*/)
419 {
420         ASSERT (nCrlfStyle == CRLFSTYLE::AUTOMATIC || nCrlfStyle == CRLFSTYLE::DOS ||
421                 nCrlfStyle == CRLFSTYLE::UNIX || nCrlfStyle == CRLFSTYLE::MAC);
422         ASSERT (m_bInit);
423
424         if (nLines == -1)
425                 nLines = static_cast<int>(m_aLines.size() - nStartLine);
426
427         if (pszFileName.empty())
428                 return SAVE_FAILED;     // No filename, cannot save...
429
430         if (nCrlfStyle == CRLFSTYLE::AUTOMATIC &&
431                 !GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
432                 infoUnpacker!=nullptr && infoUnpacker->m_bDisallowMixedEOL)
433         {
434                         // get the default nCrlfStyle of the CDiffTextBuffer
435                 nCrlfStyle = GetCRLFMode();
436                 ASSERT(nCrlfStyle != CRLFSTYLE::AUTOMATIC);
437         }
438
439         bool bOpenSuccess = true;
440         bool bSaveSuccess = false;
441
442         UniStdioFile file;
443
444         String sIntermediateFilename; // used when !bTempFile
445
446         if (bTempFile)
447         {
448                 file.SetUnicoding(ucr::UTF8);
449                 file.SetBom(GetOptionsMgr()->GetInt(OPT_CMP_DIFF_ALGORITHM) == 0);
450                 bOpenSuccess = !!file.OpenCreate(pszFileName);
451         }
452         else
453         {
454                 file.SetUnicoding(m_encoding.m_unicoding);
455                 file.SetBom(m_encoding.m_bom);
456                 file.SetCodepage(m_encoding.m_codepage);
457                 sIntermediateFilename = env::GetTemporaryFileName(m_strTempPath,
458                         _T("MRG_"), nullptr);
459                 if (sIntermediateFilename.empty())
460                         return SAVE_FAILED;  //Nothing to do if even tempfile name fails
461                 bOpenSuccess = !!file.OpenCreate(sIntermediateFilename);
462         }
463
464         if (!bOpenSuccess)
465         {       
466                 UniFile::UniError uniErr = file.GetLastUniError();
467                 if (uniErr.HasError())
468                 {
469                         sError = uniErr.GetError();
470                         if (bTempFile)
471                                 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
472                                         pszFileName, sError));
473                         else
474                                 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
475                                         sIntermediateFilename, sError));
476                 }
477                 return SAVE_FAILED;
478         }
479
480         file.WriteBom();
481
482         // line loop : get each real line and write it in the file
483         String sLine;
484         String sEol = GetStringEol(nCrlfStyle);
485         int lastRealLine = ApparentLastRealLine();
486         for (int line = nStartLine; line < nStartLine + nLines; ++line)
487         {
488                 if (GetLineFlags(line) & LF_GHOST)
489                         continue;
490
491                 // get the characters of the line (excluding EOL)
492                 if (GetLineLength(line) > 0)
493                 {
494                         int nLineLength = GetLineLength(line);
495                         sLine.resize(0);
496                         sLine.reserve(nLineLength + 4);
497                         sLine.append(GetLineChars(line), nLineLength);
498                 }
499                 else
500                         sLine = _T("");
501
502                 if (bTempFile && m_bTableEditing && m_bAllowNewlinesInQuotes)
503                 {
504                         strutils::replace(sLine, _T("\x1b"), _T("\x1b\x1b"));
505                         strutils::replace(sLine, _T("\r"), _T("\x1br"));
506                         strutils::replace(sLine, _T("\n"), _T("\x1bn"));
507                 }
508
509                 // last real line ?
510                 if (line == lastRealLine || lastRealLine == -1 )
511                 {
512                         // If original last line had no EOL, then we are done
513                         if( !m_aLines[line].HasEol() )
514                         {
515                                 file.WriteString(sLine);
516                                 break;
517                         }
518                         // Otherwise, add the appropriate EOL to the last line ...
519                 }
520
521                 // normal line : append an EOL
522                 if (nCrlfStyle == CRLFSTYLE::AUTOMATIC || nCrlfStyle == CRLFSTYLE::MIXED)
523                 {
524                         // either the EOL of the line (when preserve original EOL chars is on)
525                         sLine += GetLineEol(line);
526                 }
527                 else
528                 {
529                         // or the default EOL for this file
530                         sLine += sEol;
531                 }
532
533                 // write this line to the file (codeset or unicode conversions are done there
534                 file.WriteString(sLine);
535
536                 if (line == lastRealLine || lastRealLine == -1)
537                 {
538                         // Last line, so now done
539                         break;
540                 }
541         }
542         file.Close();
543
544         if (!bTempFile)
545         {
546                 // If we are saving user files
547                 // we need an unpacker/packer, at least a "do nothing" one
548                 ASSERT(infoUnpacker != nullptr);
549                 // repack the file here, overwrite the temporary file we did save in
550                 String csTempFileName = sIntermediateFilename;
551                 infoUnpacker->m_subcode = m_unpackerSubcode;
552                 if (!FileTransform::Packing(csTempFileName, *infoUnpacker))
553                 {
554                         try
555                         {
556                                 TFile(sIntermediateFilename).remove();
557                         }
558                         catch (Exception& e)
559                         {
560                                 LogErrorStringUTF8(e.displayText());
561                         }
562                         // returns now, don't overwrite the original file
563                         return SAVE_PACK_FAILED;
564                 }
565                 // the temp filename may have changed during packing
566                 if (csTempFileName != sIntermediateFilename)
567                 {
568                         try
569                         {
570                                 TFile(sIntermediateFilename).remove();
571                         }
572                         catch (Exception& e)
573                         {
574                                 LogErrorStringUTF8(e.displayText());
575                         }
576                         sIntermediateFilename = csTempFileName;
577                 }
578
579                 // Write tempfile over original file
580                 try
581                 {
582                         TFile file1(sIntermediateFilename);
583                         file1.copyTo(pszFileName);
584                         file1.remove();
585                         if (bClearModifiedFlag)
586                         {
587                                 SetModified(false);
588                                 m_nSyncPosition = m_nUndoPosition;
589                         }
590                         bSaveSuccess = true;
591
592                         // remember revision number on save
593                         m_dwRevisionNumberOnSave = m_dwCurrentRevisionNumber;
594
595                         // redraw line revision marks
596                         UpdateViews (nullptr, nullptr, UPDATE_FLAGSONLY);       
597                 }
598                 catch (Exception& e)
599                 {
600                         LogErrorStringUTF8(e.displayText());
601                 }
602         }
603         else
604         {
605                 if (bClearModifiedFlag)
606                 {
607                         SetModified(false);
608                         m_nSyncPosition = m_nUndoPosition;
609                 }
610                 bSaveSuccess = true;
611         }
612
613         if (bSaveSuccess)
614                 return SAVE_DONE;
615         else
616                 return SAVE_FAILED;
617 }
618
619 /// Replace line (removing any eol, and only including one if in strText)
620 void CDiffTextBuffer::ReplaceFullLines(CDiffTextBuffer& dbuf, CDiffTextBuffer& sbuf, CCrystalTextView * pSource, int nLineBegin, int nLineEnd, int nAction /*=CE_ACTION_UNKNOWN*/)
621 {
622         CString strText;
623         if (nLineBegin != nLineEnd || sbuf.GetLineLength(nLineEnd) > 0)
624                 sbuf.GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, sbuf.GetLineLength(nLineEnd), strText);
625         strText += sbuf.GetLineEol(nLineEnd);
626
627         if (nLineBegin != nLineEnd || dbuf.GetFullLineLength(nLineEnd) > 0)
628         {
629                 int nLineEndSource = nLineEnd < dbuf.GetLineCount() ? nLineEnd : dbuf.GetLineCount();
630                 if (nLineEnd+1 < GetLineCount())
631                         dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource + 1, 0, nAction);
632                 else
633                         dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource, dbuf.GetLineLength(nLineEndSource), nAction); 
634         }
635
636         if (int cchText = strText.GetLength())
637         {
638                 int endl,endc;
639                 dbuf.InsertText(pSource, nLineBegin, 0, strText, cchText, endl,endc, nAction);
640         }
641 }
642
643 bool CDiffTextBuffer::curUndoGroup()
644 {
645         return (m_aUndoBuf.size() != 0 && m_aUndoBuf[0].m_dwFlags&UNDO_BEGINGROUP);
646 }
647
648 bool CDiffTextBuffer::                  /* virtual override */
649 DeleteText2(CCrystalTextView * pSource, int nStartLine, int nStartChar,
650         int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
651 {
652         for (auto syncpnt : m_pOwnerDoc->GetSyncPointList())
653         {
654                 const int nLineSyncPoint = syncpnt[m_nThisPane];
655                 if (((nStartChar == 0 && nStartLine == nLineSyncPoint) || nStartLine < nLineSyncPoint) &&
656                         nLineSyncPoint < nEndLine)
657                         m_pOwnerDoc->DeleteSyncPoint(m_nThisPane, nLineSyncPoint, false);
658         }
659         return CGhostTextBuffer::DeleteText2(pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory);
660 }