OSDN Git Service

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