OSDN Git Service

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