OSDN Git Service

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