2 * @file DiffTextBuffer.cpp
4 * @brief Implementation file for CDiffTextBuffer
9 #include "DiffTextBuffer.h"
10 #include <Poco/Exception.h>
15 #include "OptionsDef.h"
16 #include "OptionsMgr.h"
17 #include "Environment.h"
18 #include "MergeLineFlags.h"
20 #include "FileTransform.h"
21 #include "FileTextEncoding.h"
22 #include "codepage_detect.h"
25 using Poco::Exception;
31 static bool IsTextFileStylePure(const UniMemFile::txtstats & stats);
32 static CRLFSTYLE GetTextFileStyle(const UniMemFile::txtstats & stats);
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.
39 static bool IsTextFileStylePure(const UniMemFile::txtstats & stats)
52 * @brief Get file's EOL type.
53 * @param [in] stats File's text stats.
56 static CRLFSTYLE GetTextFileStyle(const UniMemFile::txtstats & stats)
58 // Check if file has more than one EOL type.
59 if (!IsTextFileStylePure(stats))
60 return CRLF_STYLE_MIXED;
61 else if (stats.ncrlfs >= stats.nlfs)
63 if (stats.ncrlfs >= stats.ncrs)
64 return CRLF_STYLE_DOS;
66 return CRLF_STYLE_MAC;
70 if (stats.nlfs >= stats.ncrs)
71 return CRLF_STYLE_UNIX;
73 return CRLF_STYLE_MAC;
79 * @param [in] pDoc Owning CMergeDoc.
80 * @param [in] pane Pane number this buffer is associated with.
82 CDiffTextBuffer::CDiffTextBuffer(CMergeDoc * pDoc, int pane)
85 , m_unpackerSubcode(0)
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.
95 bool CDiffTextBuffer::GetLine(int nLineIndex, CString &strLine) const
97 int nLineLength = CCrystalTextBuffer::GetLineLength(nLineIndex);
100 else if (nLineLength == 0)
104 _tcsncpy_s(strLine.GetBuffer(nLineLength + 1), nLineLength + 1,
105 CCrystalTextBuffer::GetLineChars(nLineIndex), nLineLength);
106 strLine.ReleaseBuffer(nLineLength);
112 * @brief Set the buffer modified status.
113 * @param [in] bModified New modified status, true if buffer has been
114 * modified since last saving.
116 void CDiffTextBuffer:: /* virtual override */
117 SetModified(bool bModified /*= true*/)
119 CCrystalTextBuffer::SetModified (bModified);
120 m_pOwnerDoc->SetModifiedFlag (bModified);
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
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.
131 bool CDiffTextBuffer::GetFullLine(int nLineIndex, CString &strLine) const
133 int cchText = GetFullLineLength(nLineIndex);
139 LPTSTR pchText = strLine.GetBufferSetLength(cchText);
140 memcpy(pchText, GetLineChars(nLineIndex), cchText * sizeof(TCHAR));
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*/)
150 CGhostTextBuffer::AddUndoRecord(bInsert, ptStartPos, ptEndPos, pszText,
151 cchText, nActionType, paSavedRevisionNumbers);
152 if (m_aUndoBuf[m_nUndoPosition - 1].m_dwFlags & UNDO_BEGINGROUP)
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();
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.
166 bool CDiffTextBuffer::FlagIsSet(UINT line, DWORD flag) const
168 return ((m_aLines[line].m_dwFlags & flag) == flag);
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)
178 void CDiffTextBuffer::prepareForRescan()
180 RemoveAllGhostLines();
181 for (int ct = GetLineCount() - 1; ct >= 0; --ct)
184 LF_INVISIBLE | LF_DIFF | LF_TRIVIAL | LF_MOVED | LF_SNP,
185 false, false, false);
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.
197 void CDiffTextBuffer:: /* virtual override */
198 OnNotifyLineHasBeenEdited(int nLine)
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);
208 * @brief Load file from disk into buffer
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
224 int CDiffTextBuffer::LoadFromFile(LPCTSTR pszFileNameInit,
225 PackingInfo * infoUnpacker, LPCTSTR sToFindUnpacker, bool & readOnly,
226 CRLFSTYLE nCrlfStyle, const FileTextEncoding & encoding, CString &sError)
229 ASSERT(m_aLines.size() == 0);
231 // Unpacking the file here, save the result in a temporary file
232 m_strTempFileName = pszFileNameInit;
233 if (!FileTransform::Unpacking(infoUnpacker, m_strTempFileName, sToFindUnpacker))
235 InitNew(); // leave crystal editor in valid, empty state
236 return FileLoadResult::FRESULT_ERROR_UNPACK;
238 m_unpackerSubcode = infoUnpacker->m_subcode;
240 // we use the same unpacker for both files, so it must be defined after first file
241 ASSERT(infoUnpacker->m_PluginOrPredifferMode != PLUGIN_AUTO);
242 // we will load the transformed file
243 LPCTSTR pszFileName = m_strTempFileName.c_str();
246 DWORD nRetVal = FileLoadResult::FRESULT_OK;
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;
255 UniFile *pufile = infoUnpacker->m_pufile;
256 if (pufile == nullptr)
257 pufile = new UniMemFile;
259 // Now we only use the UniFile interface
260 // which is something we could implement for HTTP and/or FTP files
262 if (!pufile->OpenReadOnly(pszFileName))
264 nRetVal = FileLoadResult::FRESULT_ERROR;
265 UniFile::UniError uniErr = pufile->GetLastUniError();
266 if (uniErr.HasError())
268 sError = uniErr.GetError().c_str();
270 InitNew(); // leave crystal editor in valid, empty state
274 if (infoUnpacker->m_PluginName.length() > 0)
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);
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);
297 COleDateTime start = COleDateTime::GetCurrentTime(); // for trace messages
299 // Manually grow line array exponentially
300 UINT arraysize = 500;
301 m_aLines.resize(arraysize);
303 // preveol must be initialized for empty files
308 done = !pufile->ReadString(sline, eol, &lossy);
310 // if last line had no eol, we can quit
311 if (done && preveol.empty())
313 // but if last line had eol, we add an extra (empty) line to buffer
316 if (lineno == arraysize)
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)
324 arraysize += 100 * 1024;
325 m_aLines.resize(arraysize);
328 sline += eol; // TODO: opportunity for optimization, as CString append is terrible
331 // TODO: Should record lossy status of line
333 AppendLine(lineno, sline.c_str(), static_cast<int>(sline.length()));
339 // fix array size (due to our manual exponential growth
340 m_aLines.resize(lineno);
343 //Try to determine current CRLF mode (most frequent)
344 if (nCrlfStyle == CRLF_STYLE_AUTOMATIC)
346 nCrlfStyle = GetTextFileStyle(pufile->GetTxtStats());
348 ASSERT(nCrlfStyle >= 0 && nCrlfStyle <= 3);
349 SetCRLFMode(nCrlfStyle);
351 // At least one empty line must present
352 // (view does not work for empty buffers)
353 ASSERT(m_aLines.size() > 0);
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;
363 // flags don't need initialization because 0 is the default value
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;
373 nRetVal = FileLoadResult::FRESULT_OK_IMPURE;
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();
380 if (pufile->GetTxtStats().nlosses)
382 FileLoadResult::AddModifier(nRetVal, FileLoadResult::FRESULT_LOSSY);
387 // close the file now to free the handle
391 // delete the file that unpacking may have created
392 if (_tcscmp(pszFileNameInit, pszFileName) != 0)
396 TFile(pszFileName).remove();
400 LogErrorStringUTF8(e.displayText());
407 * @brief Saves file from buffer to disk
409 * @param bTempFile : false if we are saving user files and
410 * true if we are saving working-temp-files for diff-engine
412 * @return SAVE_DONE or an error code (list in MergeDoc.h)
414 int CDiffTextBuffer::SaveToFile (const String& pszFileName,
415 bool bTempFile, String & sError, PackingInfo * infoUnpacker /*= nullptr*/,
416 CRLFSTYLE nCrlfStyle /*= CRLF_STYLE_AUTOMATIC*/,
417 bool bClearModifiedFlag /*= true*/,
418 int nStartLine /*= 0*/, int nLines /*= -1*/)
420 ASSERT (nCrlfStyle == CRLF_STYLE_AUTOMATIC || nCrlfStyle == CRLF_STYLE_DOS ||
421 nCrlfStyle == CRLF_STYLE_UNIX || nCrlfStyle == CRLF_STYLE_MAC);
425 nLines = static_cast<int>(m_aLines.size() - nStartLine);
427 if (pszFileName.empty())
428 return SAVE_FAILED; // No filename, cannot save...
430 if (nCrlfStyle == CRLF_STYLE_AUTOMATIC &&
431 !GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
432 infoUnpacker!=nullptr && infoUnpacker->m_bDisallowMixedEOL)
434 // get the default nCrlfStyle of the CDiffTextBuffer
435 nCrlfStyle = GetCRLFMode();
436 ASSERT(nCrlfStyle >= 0 && nCrlfStyle <= 3);
439 bool bOpenSuccess = true;
440 bool bSaveSuccess = false;
444 String sIntermediateFilename; // used when !bTempFile
448 file.SetUnicoding(ucr::UTF8);
449 file.SetBom(GetOptionsMgr()->GetInt(OPT_CMP_DIFF_ALGORITHM) == 0);
450 bOpenSuccess = !!file.OpenCreate(pszFileName);
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);
466 UniFile::UniError uniErr = file.GetLastUniError();
467 if (uniErr.HasError())
469 sError = uniErr.GetError();
471 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
472 pszFileName, sError));
474 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
475 sIntermediateFilename, sError));
482 // line loop : get each real line and write it in the file
484 String sEol = GetStringEol(nCrlfStyle);
485 int lastRealLine = ApparentLastRealLine();
486 for (int line = nStartLine; line < nStartLine + nLines; ++line)
488 if (GetLineFlags(line) & LF_GHOST)
491 // get the characters of the line (excluding EOL)
492 if (GetLineLength(line) > 0)
494 int nLineLength = GetLineLength(line);
496 sLine.reserve(nLineLength + 4);
497 sLine.append(GetLineChars(line), nLineLength);
502 if (bTempFile && m_bTableEditing && m_bAllowNewlinesInQuotes)
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"));
510 if (line == lastRealLine || lastRealLine == -1 )
512 // If original last line had no EOL, then we are done
513 if( !m_aLines[line].HasEol() )
515 file.WriteString(sLine);
518 // Otherwise, add the appropriate EOL to the last line ...
521 // normal line : append an EOL
522 if (nCrlfStyle == CRLF_STYLE_AUTOMATIC || nCrlfStyle == CRLF_STYLE_MIXED)
524 // either the EOL of the line (when preserve original EOL chars is on)
525 sLine += GetLineEol(line);
529 // or the default EOL for this file
533 // write this line to the file (codeset or unicode conversions are done there
534 file.WriteString(sLine);
536 if (line == lastRealLine || lastRealLine == -1)
538 // Last line, so now done
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))
556 TFile(sIntermediateFilename).remove();
560 LogErrorStringUTF8(e.displayText());
562 // returns now, don't overwrite the original file
563 return SAVE_PACK_FAILED;
565 // the temp filename may have changed during packing
566 if (csTempFileName != sIntermediateFilename)
570 TFile(sIntermediateFilename).remove();
574 LogErrorStringUTF8(e.displayText());
576 sIntermediateFilename = csTempFileName;
579 // Write tempfile over original file
582 TFile file1(sIntermediateFilename);
583 file1.copyTo(pszFileName);
585 if (bClearModifiedFlag)
588 m_nSyncPosition = m_nUndoPosition;
592 // remember revision number on save
593 m_dwRevisionNumberOnSave = m_dwCurrentRevisionNumber;
595 // redraw line revision marks
596 UpdateViews (nullptr, nullptr, UPDATE_FLAGSONLY);
600 LogErrorStringUTF8(e.displayText());
605 if (bClearModifiedFlag)
608 m_nSyncPosition = m_nUndoPosition;
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*/)
623 if (nLineBegin != nLineEnd || sbuf.GetLineLength(nLineEnd) > 0)
624 sbuf.GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, sbuf.GetLineLength(nLineEnd), strText);
625 strText += sbuf.GetLineEol(nLineEnd);
627 if (nLineBegin != nLineEnd || dbuf.GetFullLineLength(nLineEnd) > 0)
629 int nLineEndSource = nLineEnd < dbuf.GetLineCount() ? nLineEnd : dbuf.GetLineCount();
630 if (nLineEnd+1 < GetLineCount())
631 dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource + 1, 0, nAction);
633 dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource, dbuf.GetLineLength(nLineEndSource), nAction);
636 if (int cchText = strText.GetLength())
639 dbuf.InsertText(pSource, nLineBegin, 0, strText, cchText, endl,endc, nAction);
643 bool CDiffTextBuffer::curUndoGroup()
645 return (m_aUndoBuf.size() != 0 && m_aUndoBuf[0].m_dwFlags&UNDO_BEGINGROUP);
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*/)
652 for (auto syncpnt : m_pOwnerDoc->GetSyncPointList())
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);
659 return CGhostTextBuffer::DeleteText2(pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory);