// WinMerge: an interactive diff/merge utility
// Copyright (C) 1997-2000 Thingamahoochie Software
// Author: Dean Grimm
-//
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 2 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-//
+// SPDX-License-Identifier: GPL-2.0-or-later
/////////////////////////////////////////////////////////////////////////////
/**
* @file GhostTextBuffer.cpp
#include "StdAfx.h"
#include "GhostTextBuffer.h"
+#include "MergeLineFlags.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
-#ifdef _DEBUG
-#define _ADVANCED_BUGCHECK 1
-#endif
-
using std::vector;
BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
{
}
+#if 0
/**
* @brief Insert a ghost line.
* @param [in] pSource View into which to insert the line.
context.m_ptEnd.y = nLine + 1;
CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
- if (pSource != NULL)
+ if (pSource != nullptr)
UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
return true;
}
+#endif
/** InternalDeleteGhostLine accepts only apparent line numbers */
/**
- * @brief Delete a ghost line.
- * @param [in] pSource View into which to insert the line.
- * @param [in] nLine Line index where to insert the ghost line.
+ * @brief Delete a group of ghost lines.
+ * @param [in] pSource View from which to delete the lines.
+ * @param [in] nLine Line index where to delete the first ghost line.
+ * @param [in] nCount the number of ghost lines to delete
* @return true if the deletion succeeded, false otherwise.
* @note @p nLine must be an apparent line number (ghost lines added).
*/
{
ASSERT (m_bInit); // Text buffer not yet initialized.
// You must call InitNew() or LoadFromFile() first!
- ASSERT (nLine >= 0 && nLine <= static_cast<intptr_t>(m_aLines.size ()));
+ ASSERT (nCount >= 0);
+ ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
if (nCount == 0)
return true;
- CDeleteContext context;
- context.m_ptStart.y = nLine;
- context.m_ptStart.x = 0;
- context.m_ptEnd.y = nLine + nCount;
- context.m_ptEnd.x = 0;
-
for (int i = nLine ; i < nLine + nCount; i++)
{
- ASSERT (GetLineFlags(i) & LF_GHOST);
+ ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 );
m_aLines[i].Clear();
}
vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
m_aLines.erase(iterBegin, iterEnd);
- if (pSource != NULL)
+ if (pSource != nullptr)
{
- // The last parameter is optimization - don't recompute lines preceeding
- // the removed line.
+ CDeleteContext context;
+ context.m_ptStart.y = nLine;
+ context.m_ptStart.x = 0;
+ context.m_ptEnd.y = nLine + nCount;
+ context.m_ptEnd.x = 0;
+
if (nLine == GetLineCount())
- {
- UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
- GetLineCount() - 1);
- }
- else
- {
- UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
- nLine);
- }
+ nLine--;
+ // The last parameter is optimization
+ // - don't recompute lines preceding the removed line.
+ UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
+ nLine);
}
return true;
* @brief Get text of specified lines (ghost lines will not contribute to text).
*
* @param nCrlfStyle determines the EOL type in the returned buffer.
- * If nCrlfStyle equals CRLF_STYLE_AUTOMATIC, we read the EOL from the line buffer
+ * If nCrlfStyle equals CRLFSTYLE::AUTOMATIC, we read the EOL from the line buffer
*
* @note This function has its base in CrystalTextBuffer
* CrystalTextBuffer::GetTextWithoutEmptys() is for a buffer with no ghost lines.
* CrystalTextBuffer::GetText() returns text including ghost lines.
* These two base functions never read the EOL from the line buffer, they
- * use CRLF_STYLE_DOS when nCrlfStyle equals CRLF_STYLE_AUTOMATIC.
+ * use CRLFSTYLE::DOS when nCrlfStyle equals CRLFSTYLE::AUTOMATIC.
*/
-void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar,
+void CGhostTextBuffer:: /* virtual override */
+GetTextWithoutEmptys(int nStartLine, int nStartChar,
int nEndLine, int nEndChar,
- CString &text, CRLFSTYLE nCrlfStyle /* CRLF_STYLE_AUTOMATIC */,
- bool bExcludeInvisibleLines/*=true*/) const
+ CString &text, CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC */,
+ bool bExcludeInvisibleLines /*= true*/) const
{
const size_t lines = m_aLines.size();
ASSERT(nStartLine >= 0 && nStartLine < static_cast<intptr_t>(lines));
nBufSize += (GetFullLineLength(i) + 2); // in case we insert EOLs
LPTSTR pszBuf = text.GetBuffer(nBufSize);
- if (nCrlfStyle != CRLF_STYLE_AUTOMATIC)
+ if (nCrlfStyle != CRLFSTYLE::AUTOMATIC)
{
// we must copy this EOL type only
const CString sEol = GetStringEol (nCrlfStyle);
{
// Oops, real line lacks EOL
// (If this happens, editor probably has bug)
- ASSERT(0);
+ ASSERT(false);
CString sEol = GetStringEol (nCrlfStyle);
- CopyMemory(pszBuf, sEol, sEol.GetLength());
+ CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
pszBuf += sEol.GetLength();
}
}
* @param [in] nLine Line number (apparent/screen) where the insertion starts.
* @param [in] nPos Character position where the insertion starts.
* @param [in] pszText The text to insert.
+ * @param [in] cchText The length of text in pszText.
* @param [out] nEndLine Line number of last added line in the buffer.
* @param [out] nEndChar Character position of the end of the added text
* in the buffer.
* variable which is preserved with real line number during Rescan
* (m_ptCursorPos, m_ptLastChange for example).
*/
-bool CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine,
+bool CGhostTextBuffer:: /* virtual override */
+InsertText (CCrystalTextView * pSource, int nLine,
int nPos, LPCTSTR pszText, size_t cchText, int &nEndLine, int &nEndChar,
- int nAction, bool bHistory /*=true*/)
+ int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
{
bool bGroupFlag = false;
- int bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
+ bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
+ bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
- if (bFirstLineGhost && cchText > 0 && !LineInfo::IsEol(pszText[cchText - 1]))
+ if (bFirstLineGhost && cchText > 0)
{
CString text = GetStringEol(GetCRLFMode());
if (bHistory && !m_bUndoGroup)
BeginUndoGroup();
bGroupFlag = true;
}
- CCrystalTextBuffer::InsertText(pSource, nLine, 0, text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
+ auto reverseFindRealLine = [&](int nLine) {
+ for (; nLine >= 0; --nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
+ return nLine;
+ };
+ int i = reverseFindRealLine(nLine);
+ if (i >= 0 && !m_aLines[i].HasEol())
+ CCrystalTextBuffer::InsertText(pSource, i, GetLineLength(i), text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
+ else if (!LineInfo::IsEol(pszText[cchText - 1]))
+ {
+ auto findRealLine = [&](int nLine) {
+ for (; nLine < GetLineCount(); ++nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
+ if (nLine == GetLineCount())
+ return -1;
+ return nLine;
+ };
+ if (findRealLine(nLine) != -1)
+ CCrystalTextBuffer::InsertText(pSource, nLine, 0, text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
+ }
}
if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText,
}
// when inserting an EOL terminated text into a ghost line,
- // there is a dicrepancy between nInsertedLines and nEndLine-nRealLine
- int bDiscrepancyInInsertedLines;
+ // there is a discrepancy between nInsertedLines and nEndLine-nRealLine
+ bool bDiscrepancyInInsertedLines;
if (bFirstLineGhost && nEndChar == 0 && ApparentLastRealLine() >= nEndLine)
bDiscrepancyInInsertedLines = true;
else
bDiscrepancyInInsertedLines = false;
+ if (bSpecialLastLineHandling)
+ {
+ // The special case of inserting text into the very last line of a file when
+ // that last line is marked as LF_GHOST. Effectively, the new text is
+ // supposed to go "before" the Ghost, but mechanically the text is inserted
+ // into the Ghost itself, with a new Ghost line appearing at the end of the
+ // file. Later (below), the Ghost status of both the first and last inserted
+ // lines will get straightened out, with the trailing Ghost line becomming
+ // a NULL line.
+ if ((GetLineFlags(nLine) & LF_GHOST) != 0) // first line still marked GHOST
+ bSpecialLastLineHandling = false;
+ else
+ bDiscrepancyInInsertedLines = false;
+ }
+
// compute the number of real lines created (for undo)
int nRealLinesCreated = nEndLine - nLine;
if (bFirstLineGhost && (nEndChar > 0 || ApparentLastRealLine() < nEndLine))
m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
OnNotifyLineHasBeenEdited(i);
}
- if (bDiscrepancyInInsertedLines == 0)
+ if (!bDiscrepancyInInsertedLines)
{
m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
OnNotifyLineHasBeenEdited(i);
for (i = nLine ; i < nEndLine ; i++)
SetLineFlag (i, LF_GHOST, false, false, false);
- if (bDiscrepancyInInsertedLines == 0)
+ if (!bDiscrepancyInInsertedLines)
// if there is no discrepancy, the final cursor line is real
// as either some text was inserted in it, or it inherits the real status from the first line
SetLineFlag (i, LF_GHOST, false, false, false);
else
// if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
;
+
+ if (bSpecialLastLineHandling)
+ {
+ // By setting the last line (in this special case, see above) to `nullptr`,
+ // the line will eventually be removed or become an actual LF_GHOST line.
+ int nLastLine = GetLineCount()-1;
+ ASSERT(m_aLines[nLastLine].FullLength() == 0);
+ m_aLines[nLastLine].Clear();
+ }
// now we can recompute
if ((nEndLine > nLine) || bFirstLineGhost)
return true;
}
-CDWordArray *CGhostTextBuffer::
+CDWordArray *CGhostTextBuffer:: /* virtual override */
CopyRevisionNumbers(int nStartLine, int nEndLine) const
{
CDWordArray *paSavedRevisionNumbers = CCrystalTextBuffer::CopyRevisionNumbers(nStartLine, nEndLine);
return paSavedRevisionNumbers;
}
-void CGhostTextBuffer::
+void CGhostTextBuffer:: /* virtual override */
RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
{
for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
}
}
-bool CGhostTextBuffer::
+bool CGhostTextBuffer:: /* virtual override */
DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar,
- int nEndLine, int nEndChar, int nAction, bool bHistory /*=true*/)
+ int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
{
- CString sTextToDelete;
- GetTextWithoutEmptys (nStartLine, nStartChar, nEndLine, nEndChar, sTextToDelete);
- if (!CCrystalTextBuffer::DeleteText2 (pSource, nStartLine, nStartChar,
+ int const nLineCount = GetLineCount();
+ while (nEndLine < nLineCount - 1 && GetLineFlags(nEndLine) & LF_GHOST)
+ ++nEndLine;
+ if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
nEndLine, nEndChar, nAction, bHistory))
{
return false;
return true;
}
+#if 0
/**
* @brief Insert a ghost line to the buffer (and view).
* @param [in] pSource The view to which to add the ghost line.
// Never AddUndoRecord as Rescan clears the ghost lines.
return true;
}
+#endif
/**
* @brief Remove all the ghost lines from the buffer.
void CGhostTextBuffer::RemoveAllGhostLines()
{
int nlines = GetLineCount();
- int newnl = 0;
- int ct;
- // Free the buffer of ghost lines
- for(ct = 0; ct < nlines; ct++)
+ int nFirstGhost = -1;
+ // Free the buffer of ghost lines,
+ // remember where the first ghost line occurs
+ for(int ct = 0; ct < nlines; ct++)
{
if (GetLineFlags(ct) & LF_GHOST)
+ {
m_aLines[ct].FreeBuffer();
+ if (nFirstGhost < 0)
+ nFirstGhost = ct;
+ }
}
- // Compact non-ghost lines
- // (we copy the buffer address, so the buffer don't move and we don't free it)
- for(ct = 0; ct < nlines; ct++)
+ if (nFirstGhost >= 0)
{
- if ((GetLineFlags(ct) & LF_GHOST) == 0)
- m_aLines[newnl++] = m_aLines[ct];
- }
+ // Compact non-ghost lines, starting at the first ghost.
+ // (we copy the buffer address, so the buffer doesn't move and we don't free it)
+ int newnl = nFirstGhost;
+ for (int ct = nFirstGhost; ct < nlines; ct++)
+ {
+ if ((GetLineFlags(ct) & LF_GHOST) == 0)
+ m_aLines[newnl++] = m_aLines[ct];
+ }
- // Discard unused entries in one shot
- m_aLines.resize(newnl);
- RecomputeRealityMapping();
+ // Discard unused entries in one shot
+ m_aLines.resize(newnl);
+ RecomputeRealityMapping();
+ }
}
////////////////////////////////////////////////////////////////////////////
return (nRealLine - block.nStartReal) + block.nStartApparent;
}
// Should have found it; all real lines should be in a block
- ASSERT(0);
+ ASSERT(false);
return -1;
}
* @brief Get a real line for apparent (screen) line.
* This function returns the real line for the given apparent (screen) line.
* For ghost lines we return next real line. For trailing ghost line we return
- * last real line + 1). Ie, lines 0->0, 1->2, 2->4, for argument of 3,
- * return 2. And decToReal would be 1.
+ * last real line + 1; i.e. lines 0->0, 1->2, 2->4, for argument of 3,
+ * return 2 and decToReal would be 1.
* @param [in] nApparentLine Apparent line for which to get the real line.
* @param [out] decToReal Difference of the apparent and real line.
* @return The real line for the apparent line.
/**
* @brief Get an apparent (screen) line for the real line.
* @param [in] nRealLine Real line for which to get the apparent line.
- * @param [out] decToReal Difference of the apparent and real line.
+ * @param [in] decToReal Difference of the apparent and real line.
* @return The apparent line for the real line. If real line is out of bounds
* return last valid apparent line + 1.
*/
if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
{
nPreviousBlock = size - 1;
- nApparent = GetLineCount();
+ nApparent = GetLineCount() - 1;
goto limitWithPreviousBlock;
}
}
}
// Should have found it; all real lines should be in a block
- ASSERT(0);
+ ASSERT(false);
return -1;
limitWithPreviousBlock:
m_RealityBlocks.clear();
int reality = -1; // last encountered real line
int i = 0; // current line
+ int nLineCount = GetLineCount();
RealityBlock block; // current block being traversed (in state 2)
// This is a state machine with 2 states
// state 1, i-1 not real line
passingGhosts:
- if (i == GetLineCount())
+ ASSERT( i <= nLineCount );
+ if (i == nLineCount)
+ {
+ checkFlagsFromReality();
return;
+ }
if (GetLineFlags(i) & LF_GHOST)
{
++i;
// this is the first line of a reality block
block.nStartApparent = i;
block.nStartReal = reality + 1;
+ block.nCount = -1;
++reality;
++i;
// fall through to other state
// state 2, i - 1 is real line
inReality:
- if (i == GetLineCount() || (GetLineFlags(i) & LF_GHOST))
+ ASSERT( i <= nLineCount );
+ if (i == nLineCount || (GetLineFlags(i) & LF_GHOST))
{
// i-1 is the last line of a reality block
ASSERT(reality >= 0);
if (m_RealityBlocks.size() == 0)
m_RealityBlocks.reserve(16);
else
+ // TODO: grow more slowly with really large RealityBlocks
m_RealityBlocks.reserve(m_RealityBlocks.size() * 2);
}
m_RealityBlocks.push_back(block);
- if (i == GetLineCount())
+ if (i == nLineCount)
+ {
+ checkFlagsFromReality();
return;
+ }
++i;
goto passingGhosts;
}
Check all lines, and ASSERT if reality blocks differ from flags.
This means that this only has effect in DEBUG build
*/
-void CGhostTextBuffer::checkFlagsFromReality(bool bFlag) const
+void CGhostTextBuffer::checkFlagsFromReality() const
{
+#ifdef _DEBUG
const int size = static_cast<int>(m_RealityBlocks.size());
int i = 0;
for (int b = 0 ; b < size ; b ++)
for ( ; i < GetLineCount() ; i++)
ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
+#endif
}
-void CGhostTextBuffer::OnNotifyLineHasBeenEdited(int nLine)
+void CGhostTextBuffer:: /* virtual base */
+OnNotifyLineHasBeenEdited(int nLine)
{
return;
}
-static int CountEol(LPCTSTR pszText, size_t cchText)
+void CGhostTextBuffer::
+CountEolAndLastLineLength(const CPoint& ptStartPos, LPCTSTR pszText, size_t cchText, int &nLastLineLength, int &nEol)
{
- int nEol = 0;
- for (int nTextPos = 0; nTextPos < cchText; ++nTextPos)
+ nLastLineLength = 0;
+ nEol = 0;
+ if (m_bTableEditing && m_bAllowNewlinesInQuotes)
{
- if (LineInfo::IsEol(pszText[nTextPos]))
+ bool bInQuote = false;
+ const TCHAR* pszLine = m_aLines[ptStartPos.y].GetLine();
+ for (int j = 0; j < ptStartPos.x; ++j)
{
- if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
- ++nTextPos;
- ++nEol;
+ if (pszLine[j] == m_cFieldEnclosure)
+ bInQuote = !bInQuote;
+ }
+ for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
+ {
+ if (pszText[nTextPos] == m_cFieldEnclosure)
+ bInQuote = !bInQuote;
+ if (!bInQuote && LineInfo::IsEol(pszText[nTextPos]))
+ {
+ if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
+ ++nTextPos;
+ ++nEol;
+ nLastLineLength = 0;
+ }
+ else
+ ++nLastLineLength;
+ }
+ }
+ else
+ {
+ for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
+ {
+ if (LineInfo::IsEol(pszText[nTextPos]))
+ {
+ if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
+ ++nTextPos;
+ ++nEol;
+ nLastLineLength = 0;
+ }
+ else
+ ++nLastLineLength;
}
}
- return nEol;
}
-void CGhostTextBuffer::AddUndoRecord(bool bInsert, const CPoint & ptStartPos,
+void CGhostTextBuffer:: /* virtual override */
+AddUndoRecord(bool bInsert, const CPoint & ptStartPos,
const CPoint & ptEndPos, LPCTSTR pszText, size_t cchText,
int nActionType /*= CE_ACTION_UNKNOWN*/,
- CDWordArray *paSavedRevisionNumbers)
+ CDWordArray *paSavedRevisionNumbers /*= nullptr*/)
{
CPoint real_ptStartPos(ptStartPos.x, ComputeRealLine(ptStartPos.y));
- CPoint real_ptEndPos(ptEndPos.x, real_ptStartPos.y + CountEol(pszText, cchText));
+ int nLastLineLength, nEol;
+ CountEolAndLastLineLength(ptStartPos, pszText, cchText, nLastLineLength, nEol);
+ CPoint real_ptEndPos(ptEndPos.x, real_ptStartPos.y + nEol);
+ if (ptEndPos.x == 0 && cchText > 0 && !LineInfo::IsEol(pszText[cchText - 1]))
+ real_ptEndPos.x = nLastLineLength;
CCrystalTextBuffer::AddUndoRecord(bInsert, real_ptStartPos, real_ptEndPos, pszText,
cchText, nActionType, paSavedRevisionNumbers);
}
-UndoRecord CGhostTextBuffer::GetUndoRecord(int nUndoPos) const
+UndoRecord CGhostTextBuffer:: /* virtual override */
+GetUndoRecord(int nUndoPos) const
{
UndoRecord ur = m_aUndoBuf[nUndoPos];
ur.m_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, 0);
return ur;
}
+bool CGhostTextBuffer:: /* virtual override */
+UndoInsert(CCrystalTextView * pSource, CPoint & ptCursorPos, const CPoint apparent_ptStartPos, CPoint const apparent_ptEndPos, const UndoRecord & ur)
+{
+ // Check that text in the undo buffer matches text in file buffer.
+ // If not, then rescan() has moved lines and undo fails.
+
+ // we need to put the cursor before the deleted section
+ CString text;
+ const size_t size = m_aLines.size();
+ if ((apparent_ptStartPos.y < static_cast<LONG>(size)) &&
+ (apparent_ptStartPos.x <= static_cast<LONG>(m_aLines[apparent_ptStartPos.y].Length())) &&
+ (apparent_ptEndPos.y < static_cast<LONG>(size)) &&
+ (apparent_ptEndPos.x <= static_cast<LONG>(m_aLines[apparent_ptEndPos.y].Length())))
+ {
+ // Try to ensure that we are undoing correctly...
+ // Just compare the text as it was before Undo operation
+ GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text, CRLFSTYLE::AUTOMATIC, false);
+ if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
+ {
+ if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparent_ptEndPos, ur))
+ {
+ // ptCursorPos = apparent_ptStartPos;
+ return true;
+ }
+ }
+ else
+ // It is possible that the ptEndPos is at the last line of the file and originally pointed
+ // at an LF_GHOST line that followed (and has since been discarded). Lets try to reconstruct
+ // that situation before we fail entirely...
+ if (apparent_ptEndPos.y + 1 == static_cast<LONG>(size) && apparent_ptEndPos.x == 0)
+ {
+ CPoint apparentEnd2 = apparent_ptEndPos;
+ apparentEnd2.x = static_cast<LONG>(m_aLines[apparentEnd2.y].FullLength());
+ text.Empty();
+ GetTextWithoutEmptys(apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparentEnd2.x, text, CRLFSTYLE::AUTOMATIC, false);
+ if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
+ {
+ if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparentEnd2, ur))
+ {
+ // ptCursorPos = apparent_ptStartPos;
+ const size_t nLastLine = m_aLines.size() - 1;
+ if (m_aLines[nLastLine].Length() == 0)
+ {
+ m_aLines[nLastLine].Clear();
+ if (static_cast<size_t>(ptCursorPos.y) == nLastLine)
+ ptCursorPos.y--;
+ }
+ return true;
+ }
+ }
+ }
+ }
+ ASSERT(false);
+ return false;
+}
+