2 * @file DiffTextBuffer.cpp
4 * @brief Implementation file for CDiffTextBuffer
9 #include "DiffTextBuffer.h"
10 #include "ccrystaltextview.h"
12 #include "FileLoadResult.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"
24 #include <Poco/Exception.h>
26 using Poco::Exception;
32 static bool IsTextFileStylePure(const UniMemFile::txtstats & stats);
33 static CRLFSTYLE GetTextFileStyle(const UniMemFile::txtstats & stats);
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.
40 static bool IsTextFileStylePure(const UniMemFile::txtstats & stats)
53 * @brief Get file's EOL type.
54 * @param [in] stats File's text stats.
57 static CRLFSTYLE GetTextFileStyle(const UniMemFile::txtstats & stats)
59 // Check if file has more than one EOL type.
60 if (!IsTextFileStylePure(stats))
61 return CRLFSTYLE::MIXED;
62 else if (stats.ncrlfs >= stats.nlfs)
64 if (stats.ncrlfs >= stats.ncrs)
65 return CRLFSTYLE::DOS;
67 return CRLFSTYLE::MAC;
71 if (stats.nlfs >= stats.ncrs)
72 return CRLFSTYLE::UNIX;
74 return CRLFSTYLE::MAC;
80 * @param [in] pDoc Owning CMergeDoc.
81 * @param [in] pane Pane number this buffer is associated with.
83 CDiffTextBuffer::CDiffTextBuffer(CMergeDoc * pDoc, int pane)
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, String &strLine) const
97 int nLineLength = CCrystalTextBuffer::GetLineLength(nLineIndex);
100 else if (nLineLength == 0)
103 strLine.assign(CCrystalTextBuffer::GetLineChars(nLineIndex), nLineLength);
108 * @brief Set the buffer modified status.
109 * @param [in] bModified New modified status, true if buffer has been
110 * modified since last saving.
112 void CDiffTextBuffer:: /* virtual override */
113 SetModified(bool bModified /*= true*/)
115 CCrystalTextBuffer::SetModified (bModified);
116 m_pOwnerDoc->SetModifiedFlag (bModified);
119 void CDiffTextBuffer:: /* virtual override */
120 AddUndoRecord(bool bInsert, const CEPoint & ptStartPos,
121 const CEPoint & ptEndPos, const tchar_t* pszText, size_t cchText,
122 int nActionType /*= CE_ACTION_UNKNOWN*/,
123 std::vector<uint32_t> *paSavedRevisionNumbers /*= nullptr*/)
125 CGhostTextBuffer::AddUndoRecord(bInsert, ptStartPos, ptEndPos, pszText,
126 cchText, nActionType, paSavedRevisionNumbers);
127 if (m_aUndoBuf[m_nUndoPosition - 1].m_dwFlags & UNDO_BEGINGROUP)
129 m_pOwnerDoc->undoTgt.erase(m_pOwnerDoc->curUndo, m_pOwnerDoc->undoTgt.end());
130 m_pOwnerDoc->undoTgt.push_back(m_nThisPane);
131 m_pOwnerDoc->curUndo = m_pOwnerDoc->undoTgt.end();
136 * @brief Checks if a flag is set for line.
137 * @param [in] line Index (0-based) for line.
138 * @param [in] flag Flag to check.
139 * @return true if flag is set, false otherwise.
141 bool CDiffTextBuffer::FlagIsSet(int line, lineflags_t flag) const
143 return ((m_aLines[line].m_dwFlags & flag) == flag);
147 Remove blank lines and clear winmerge flags
148 (2003-06-21, Perry: I don't understand why this is necessary, but if this isn't
149 done, more and more gray lines appear in the file)
150 (2003-07-31, Laoran I don't understand either why it is necessary, but it works
151 fine, so let's go on with it)
153 void CDiffTextBuffer::prepareForRescan()
155 RemoveAllGhostLines();
156 for (int ct = GetLineCount() - 1; ct >= 0; --ct)
159 LF_INVISIBLE | LF_DIFF | LF_TRIVIAL | LF_MOVED | LF_SNP,
160 false, false, false);
165 * @brief Called when line has been edited.
166 * After editing a line, we don't know if there is a diff or not.
167 * So we clear the LF_DIFF flag (and it is more easy to read during editing).
168 * Rescan will set the proper color.
169 * @param [in] nLine Line that has been edited.
172 void CDiffTextBuffer:: /* virtual override */
173 OnNotifyLineHasBeenEdited(int nLine)
175 SetLineFlag(nLine, LF_DIFF, false, false, false);
176 SetLineFlag(nLine, LF_TRIVIAL, false, false, false);
177 SetLineFlag(nLine, LF_MOVED, false, false, false);
178 SetLineFlag(nLine, LF_SNP, false, false, false);
179 CGhostTextBuffer::OnNotifyLineHasBeenEdited(nLine);
183 * @brief Load file from disk into buffer
185 * @param [in] pszFileNameInit File to load
186 * @param [in] infoUnpacker Unpacker plugin
187 * @param [in] sToFindUnpacker String for finding unpacker plugin
188 * @param [out] readOnly Loading was lossy so file should be read-only
189 * @param [in] nCrlfStyle EOL style used
190 * @param [in] encoding Encoding used
191 * @param [out] sError Error message returned
192 * @return FRESULT_OK when loading succeed or (list in files.h):
193 * - FRESULT_OK_IMPURE : load OK, but the EOL are of different types
194 * - FRESULT_ERROR_UNPACK : plugin failed to unpack
195 * - FRESULT_ERROR : loading failed, sError contains error message
196 * - FRESULT_BINARY : file is binary file
197 * @note If this method fails, it calls InitNew so the CDiffTextBuffer is in a valid state
199 int CDiffTextBuffer::LoadFromFile(const tchar_t* pszFileNameInit,
200 PackingInfo& infoUnpacker, const tchar_t* sToFindUnpacker, bool & readOnly,
201 CRLFSTYLE nCrlfStyle, const FileTextEncoding & encoding, String &sError)
204 ASSERT(m_aLines.size() == 0);
206 // Unpacking the file here, save the result in a temporary file
207 m_strTempFileName = pszFileNameInit;
208 if (!infoUnpacker.Unpacking(&m_unpackerSubcodes, m_strTempFileName, sToFindUnpacker, { m_strTempFileName }))
210 InitNew(); // leave crystal editor in valid, empty state
211 return FileLoadResult::FRESULT_ERROR_UNPACK;
214 // we will load the transformed file
215 const tchar_t* pszFileName = m_strTempFileName.c_str();
218 FileLoadResult::flags_t nRetVal = FileLoadResult::FRESULT_OK;
220 // Set encoding based on extension, if we know one
221 paths::SplitFilename(pszFileName, nullptr, nullptr, &sExt);
222 CrystalLineParser::TextDefinition *def =
223 CrystalLineParser::GetTextType(sExt.c_str());
224 if (def && def->encoding != -1)
225 m_nSourceEncoding = def->encoding;
227 UniFile *pufile = new UniMemFile;
229 // Now we only use the UniFile interface
230 // which is something we could implement for HTTP and/or FTP files
232 if (!pufile->OpenReadOnly(pszFileName))
234 nRetVal = FileLoadResult::FRESULT_ERROR;
235 UniFile::UniError uniErr = pufile->GetLastUniError();
236 if (uniErr.HasError())
238 sError = uniErr.GetError();
240 InitNew(); // leave crystal editor in valid, empty state
244 if (!m_unpackerSubcodes.empty())
246 // re-detect codepage
247 int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
248 FileTextEncoding encoding2 = codepage_detect::Guess(pszFileName, iGuessEncodingType);
249 pufile->SetUnicoding(encoding2.m_unicoding);
250 pufile->SetCodepage(encoding2.m_codepage);
251 pufile->SetBom(encoding2.m_bom);
257 // If the file is not unicode file, use the codepage we were given to
258 // interpret the 8-bit characters. If the file is unicode file,
259 // determine its type (IsUnicode() does that).
260 if (encoding.m_unicoding == ucr::NONE || !pufile->IsUnicode())
261 pufile->SetCodepage(encoding.m_codepage);
268 // Manually grow line array exponentially
269 size_t arraysize = 500;
270 m_aLines.resize(arraysize);
272 // preveol must be initialized for empty files
277 done = !pufile->ReadString(sline, eol, &lossy);
279 // if last line had no eol, we can quit
280 if (done && preveol.empty())
282 // but if last line had eol, we add an extra (empty) line to buffer
285 if (lineno == arraysize)
287 // For smaller sizes use exponential growth, but for larger
288 // sizes grow by constant ratio. Unlimited exponential growth
289 // easily runs out of memory.
290 if (arraysize < 100 * 1024)
293 arraysize += 100 * 1024;
294 m_aLines.resize(arraysize);
297 sline += eol; // TODO: opportunity for optimization, as CString append is terrible
300 // TODO: Should record lossy status of line
302 AppendLine(lineno, sline.c_str(), static_cast<int>(sline.length()));
308 // fix array size (due to our manual exponential growth
309 m_aLines.resize(lineno);
312 //Try to determine current CRLF mode (most frequent)
313 if (nCrlfStyle == CRLFSTYLE::AUTOMATIC)
315 nCrlfStyle = GetTextFileStyle(pufile->GetTxtStats());
317 ASSERT (nCrlfStyle != CRLFSTYLE::AUTOMATIC);
318 SetCRLFMode(nCrlfStyle);
320 // At least one empty line must present
321 // (view does not work for empty buffers)
322 ASSERT(m_aLines.size() > 0);
326 m_bUndoGroup = m_bUndoBeginGroup = false;
327 m_nSyncPosition = m_nUndoPosition = 0;
328 ASSERT(m_aUndoBuf.size() == 0);
329 m_ptLastChange.x = m_ptLastChange.y = -1;
332 // flags don't need initialization because 0 is the default value
334 // Set the return value : OK + info if the file is impure
335 // A pure file is a file where EOL are consistent (all DOS, or all UNIX, or all MAC)
336 // An impure file is a file with several EOL types
337 // WinMerge may display impure files, but the default option is to unify the EOL
338 // We return this info to the caller, so it may display a confirmation box
339 if (IsTextFileStylePure(pufile->GetTxtStats()))
340 nRetVal = FileLoadResult::FRESULT_OK;
342 nRetVal = FileLoadResult::FRESULT_OK_IMPURE;
344 // stash original encoding away
345 m_encoding.m_unicoding = pufile->GetUnicoding();
346 m_encoding.m_bom = pufile->HasBom();
347 m_encoding.m_codepage = pufile->GetCodepage();
349 if (pufile->GetTxtStats().nlosses)
351 FileLoadResult::AddModifier(nRetVal, FileLoadResult::FRESULT_LOSSY);
356 // close the file now to free the handle
360 // delete the file that unpacking may have created
361 if (tc::tcscmp(pszFileNameInit, pszFileName) != 0)
365 TFile(pszFileName).remove();
369 LogErrorStringUTF8(e.displayText());
376 * @brief Saves file from buffer to disk
378 * @param bTempFile : false if we are saving user files and
379 * true if we are saving working-temp-files for diff-engine
381 * @return SAVE_DONE or an error code (list in MergeDoc.h)
383 int CDiffTextBuffer::SaveToFile (const String& pszFileName,
384 bool bTempFile, String & sError, PackingInfo& infoUnpacker /*= nullptr*/,
385 CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC*/,
386 bool bClearModifiedFlag /*= true*/,
387 int nStartLine /*= 0*/, int nLines /*= -1*/)
389 ASSERT (nCrlfStyle == CRLFSTYLE::AUTOMATIC || nCrlfStyle == CRLFSTYLE::DOS ||
390 nCrlfStyle == CRLFSTYLE::UNIX || nCrlfStyle == CRLFSTYLE::MAC);
394 nLines = static_cast<int>(m_aLines.size() - nStartLine);
396 if (pszFileName.empty())
397 return SAVE_FAILED; // No filename, cannot save...
399 if (nCrlfStyle == CRLFSTYLE::AUTOMATIC &&
400 !GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL))
402 // get the default nCrlfStyle of the CDiffTextBuffer
403 nCrlfStyle = GetCRLFMode();
404 ASSERT(nCrlfStyle != CRLFSTYLE::AUTOMATIC);
407 bool bOpenSuccess = true;
408 bool bSaveSuccess = false;
412 String sIntermediateFilename; // used when !bTempFile
416 file.SetUnicoding(ucr::UTF8);
417 file.SetBom(GetOptionsMgr()->GetInt(OPT_CMP_DIFF_ALGORITHM) == 0);
418 bOpenSuccess = !!file.OpenCreate(pszFileName);
422 file.SetUnicoding(m_encoding.m_unicoding);
423 file.SetBom(m_encoding.m_bom);
424 file.SetCodepage(m_encoding.m_codepage);
425 sIntermediateFilename = env::GetTemporaryFileName(m_strTempPath,
426 _T("MRG_"), nullptr);
427 if (sIntermediateFilename.empty())
428 return SAVE_FAILED; //Nothing to do if even tempfile name fails
429 bOpenSuccess = !!file.OpenCreate(sIntermediateFilename);
434 UniFile::UniError uniErr = file.GetLastUniError();
435 if (uniErr.HasError())
437 sError = uniErr.GetError();
439 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
440 pszFileName, sError));
442 LogErrorString(strutils::format(_T("Opening file %s failed: %s"),
443 sIntermediateFilename, sError));
448 const size_t StdioBufSize = (std::min)(512 * 1024, BUFSIZ + nLines * 32);
449 file.SetVBuf(_IOFBF, StdioBufSize);
452 // line loop : get each real line and write it in the file
454 String sEol = GetStringEol(nCrlfStyle);
455 int lastRealLine = ApparentLastRealLine();
456 for (int line = nStartLine; line < nStartLine + nLines; ++line)
458 if (GetLineFlags(line) & LF_GHOST)
461 // get the characters of the line (excluding EOL)
462 if (GetLineLength(line) > 0)
464 int nLineLength = GetLineLength(line);
466 sLine.reserve(nLineLength + 4);
467 sLine.append(GetLineChars(line), nLineLength);
472 if (bTempFile && m_bTableEditing && m_bAllowNewlinesInQuotes)
474 strutils::replace(sLine, _T("\x1b"), _T("\x1b\x1b"));
475 strutils::replace(sLine, _T("\r"), _T("\x1br"));
476 strutils::replace(sLine, _T("\n"), _T("\x1bn"));
480 if (line == lastRealLine || lastRealLine == -1 )
482 // If original last line had no EOL, then we are done
483 if( !m_aLines[line].HasEol() )
485 file.WriteString(sLine);
488 // Otherwise, add the appropriate EOL to the last line ...
491 // normal line : append an EOL
492 if (nCrlfStyle == CRLFSTYLE::AUTOMATIC || nCrlfStyle == CRLFSTYLE::MIXED)
494 // either the EOL of the line (when preserve original EOL chars is on)
495 sLine += GetLineEol(line);
499 // or the default EOL for this file
503 // write this line to the file (codeset or unicode conversions are done there
504 file.WriteString(sLine);
506 if (line == lastRealLine || lastRealLine == -1)
508 // Last line, so now done
516 // If we are saving user files
517 // we need an unpacker/packer, at least a "do nothing" one
518 // repack the file here, overwrite the temporary file we did save in
519 bSaveSuccess = infoUnpacker.Packing(sIntermediateFilename, pszFileName, m_unpackerSubcodes, { pszFileName });
521 sError = GetSysError();
524 TFile(sIntermediateFilename).remove();
528 LogErrorStringUTF8(e.displayText());
532 // returns now, don't overwrite the original file
533 return m_unpackerSubcodes.empty() ? SAVE_FAILED : SAVE_PACK_FAILED;
536 if (bClearModifiedFlag)
539 m_nSyncPosition = m_nUndoPosition;
542 // remember revision number on save
543 m_dwRevisionNumberOnSave = m_dwCurrentRevisionNumber;
545 // redraw line revision marks
546 UpdateViews (nullptr, nullptr, UPDATE_FLAGSONLY);
550 if (bClearModifiedFlag)
553 m_nSyncPosition = m_nUndoPosition;
564 /// Replace line (removing any eol, and only including one if in strText)
565 void CDiffTextBuffer::ReplaceFullLines(CDiffTextBuffer& dbuf, CDiffTextBuffer& sbuf, CCrystalTextView * pSource, int nLineBegin, int nLineEnd, int nAction /*=CE_ACTION_UNKNOWN*/)
568 if (nLineBegin != nLineEnd || sbuf.GetLineLength(nLineEnd) > 0)
569 sbuf.GetTextWithoutEmptys(nLineBegin, 0, nLineEnd, sbuf.GetLineLength(nLineEnd), strText);
570 strText += sbuf.GetLineEol(nLineEnd);
572 if (nLineBegin != nLineEnd || dbuf.GetFullLineLength(nLineEnd) > 0)
574 int nLineEndSource = nLineEnd < dbuf.GetLineCount() ? nLineEnd : dbuf.GetLineCount();
575 if (nLineEnd+1 < GetLineCount())
576 dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource + 1, 0, nAction);
578 dbuf.DeleteText(pSource, nLineBegin, 0, nLineEndSource, dbuf.GetLineLength(nLineEndSource), nAction);
581 if (size_t cchText = strText.length())
584 dbuf.InsertText(pSource, nLineBegin, 0, strText.c_str(), cchText, endl, endc, nAction);
588 bool CDiffTextBuffer::curUndoGroup()
590 return (m_aUndoBuf.size() != 0 && m_aUndoBuf[0].m_dwFlags&UNDO_BEGINGROUP);
593 bool CDiffTextBuffer:: /* virtual override */
594 DeleteText2(CCrystalTextView * pSource, int nStartLine, int nStartChar,
595 int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
597 for (auto syncpnt : m_pOwnerDoc->GetSyncPointList())
599 const int nLineSyncPoint = syncpnt[m_nThisPane];
600 if (((nStartChar == 0 && nStartLine == nLineSyncPoint) || nStartLine < nLineSyncPoint) &&
601 nLineSyncPoint < nEndLine)
602 m_pOwnerDoc->DeleteSyncPoint(m_nThisPane, nLineSyncPoint, false);
604 return CGhostTextBuffer::DeleteText2(pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory);