OSDN Git Service

crystaledit: Use GetProfile*()/WriteProfile*() to read and write the registry wheneve...
[winmerge-jp/winmerge-jp.git] / Src / GhostTextBuffer.cpp
index 65cc577..99806a3 100644 (file)
@@ -7,42 +7,23 @@
 //    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
  *
  * @brief Implementation of GhostTextBuffer class.
  */
-// ID line follows -- this is updated by SVN
-// $Id$
 
-#include "stdafx.h"
+#include "StdAfx.h"
 #include "GhostTextBuffer.h"
+#include "MergeLineFlags.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
-#undef THIS_FILE
-static char THIS_FILE[] = __FILE__;
 #endif
 
-#ifdef _DEBUG
-#define _ADVANCED_BUGCHECK  1
-#endif
+using std::vector;
 
 BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
 //{{AFX_MSG_MAP(CGhostTextBuffer)
@@ -51,152 +32,147 @@ END_MESSAGE_MAP ()
 
 IMPLEMENT_DYNCREATE (CGhostTextBuffer, CCrystalTextBuffer)
 
+/**
+ * @brief Constructor.
+ */
 CGhostTextBuffer::CGhostTextBuffer()
 {
-       m_bUndoGroup = FALSE;
-       CCrystalTextBuffer::m_bUndoBeginGroup = m_bUndoBeginGroup = FALSE;
-}
-
-BOOL CGhostTextBuffer::
-InitNew (int nCrlfStyle /*= CRLF_STYLE_DOS*/ )
-{
-       m_bUndoBeginGroup = FALSE;
-       return CCrystalTextBuffer::InitNew(nCrlfStyle);
 }
 
-
-/** InternalInsertGhostLine accepts only apparent line numbers */
-BOOL CGhostTextBuffer::
-InternalInsertGhostLine (CCrystalTextView * pSource, int nLine)
+#if 0
+/**
+ * @brief Insert a ghost line.
+ * @param [in] pSource View into which to insert the line.
+ * @param [in] nLine Line (apparent/screen) where to insert the ghost line.
+ * @return true if the insertion succeeded, false otherwise.
+ */
+bool CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource,
+               int nLine)
 {
        ASSERT (m_bInit);             //  Text buffer not yet initialized.
        //  You must call InitNew() or LoadFromFile() first!
 
-       ASSERT (nLine >= 0 && nLine <= m_aLines.GetSize ());
-       if (m_bReadOnly)
-               return FALSE;
+       ASSERT (nLine >= 0 && nLine <= static_cast<intptr_t>(m_aLines.size ()));
 
        CInsertContext context;
        context.m_ptStart.x = 0;
        context.m_ptStart.y = nLine;
-
-       CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
-
        context.m_ptEnd.x = 0;
-       context.m_ptEnd.y = nLine+1;
+       context.m_ptEnd.y = nLine + 1;
 
-       if (pSource!=NULL)
+       CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
+       if (pSource != nullptr)
                UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
 
-       if (!m_bModified)
-               SetModified (TRUE);
-
-       OnNotifyLineHasBeenEdited(nLine);
-
-       return TRUE;
+       return true;
 }
-
+#endif
 
 /** InternalDeleteGhostLine accepts only apparent line numbers */
-BOOL CGhostTextBuffer::
-InternalDeleteGhostLine (CCrystalTextView * pSource, int nLine, int nCount)
+/**
+ * @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).
+ */
+bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
+               int nLine, int nCount)
 {
        ASSERT (m_bInit);             //  Text buffer not yet initialized.
        //  You must call InitNew() or LoadFromFile() first!
-
-       ASSERT (nLine >= 0 && nLine <= m_aLines.GetSize ());
-       if (m_bReadOnly)
-               return FALSE;
+       ASSERT (nCount >= 0);
+       ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
 
        if (nCount == 0)
-               return TRUE;
+               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 L = nLine ; L < nLine+nCount; L++)
+       for (int i = nLine ; i < nLine + nCount; i++)
        {
-               ASSERT (GetLineFlags(L) & LF_GHOST);
-               delete[] m_aLines[L].m_pcLine;
+               ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 );
+               m_aLines[i].Clear();
        }
-       m_aLines.RemoveAt (nLine, nCount);
 
-       if (pSource!=NULL)
+       vector<LineInfo>::iterator iterBegin = m_aLines.begin() + nLine;
+       vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
+       m_aLines.erase(iterBegin, iterEnd);
+
+       if (pSource != nullptr)
        {
-               // the last parameter is just for speed : don't recompute lines before this one
-               // it must be a valid line number, so if we delete the last lines, we give the last of the remaining lines
+               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);
        }
 
-       if (!m_bModified)
-               SetModified (TRUE);
-
-       return TRUE;
+       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, int nCrlfStyle /* CRLF_STYLE_AUTOMATIC */)
+                 CString &text, CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC */,
+                 bool bExcludeInvisibleLines /*= true*/) const
 {
-       int lines = (int) m_aLines.GetSize();
-       ASSERT(nStartLine >= 0 && nStartLine < lines);
+       const size_t lines = m_aLines.size();
+       ASSERT(nStartLine >= 0 && nStartLine < static_cast<intptr_t>(lines));
        ASSERT(nStartChar >= 0 && nStartChar <= GetLineLength(nStartLine));
-       ASSERT(nEndLine >= 0 && nEndLine < lines);
+       ASSERT(nEndLine >= 0 && nEndLine < static_cast<intptr_t>(lines));
        ASSERT(nEndChar >= 0 && nEndChar <= GetFullLineLength(nEndLine));
        ASSERT(nStartLine < nEndLine || nStartLine == nEndLine && nStartChar <= nEndChar);
        // some edit functions (copy...) should do nothing when there is no selection.
        // assert to be sure to catch these 'do nothing' cases.
-       ASSERT(nStartLine != nEndLine || nStartChar != nEndChar);
+//     ASSERT(nStartLine != nEndLine || nStartChar != nEndChar);
 
        // estimate size (upper bound)
        int nBufSize = 0;
-       int i=0;
-       for (i=nStartLine; i<=nEndLine; ++i)
+       int i = 0;
+       for (i = nStartLine; i <= nEndLine; ++i)
                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
-               CString sEol = GetStringEol (nCrlfStyle);
+               const CString sEol = GetStringEol (nCrlfStyle);
 
-               for (i=nStartLine; i<=nEndLine; ++i)
+               for (i = nStartLine; i <= nEndLine; ++i)
                {
                        // exclude ghost lines
-                       if (GetLineFlags(i) & LF_GHOST)
+                       if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && (GetLineFlags(i) & LF_INVISIBLE)))
                                continue;
 
                        // copy the line, excluding the EOL
-                       int soffset = (i==nStartLine ? nStartChar : 0);
-                       int eoffset = (i==nEndLine ? nEndChar : GetLineLength(i));
+                       int soffset = (i == nStartLine ? nStartChar : 0);
+                       int eoffset = (i == nEndLine ? nEndChar : GetLineLength(i));
                        int chars = eoffset - soffset;
-                       LPCTSTR szLine = m_aLines[i].m_pcLine + soffset;
+                       LPCTSTR szLine = m_aLines[i].GetLine(soffset);
                        CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
                        pszBuf += chars;
 
                        // copy the EOL of the requested type
-                       if (i!=ApparentLastRealLine())
+                       if (i != ApparentLastRealLine())
                        {
                                CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
                                pszBuf += sEol.GetLength();
@@ -205,479 +181,128 @@ void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar,
        } 
        else 
        {
-               for (i=nStartLine; i<=nEndLine; ++i)
+               for (i = nStartLine; i <= nEndLine; ++i)
                {
                        // exclude ghost lines
-                       if (GetLineFlags(i) & LF_GHOST)
+                       if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && GetLineFlags(i) & LF_INVISIBLE))
                                continue;
 
                        // copy the line including the EOL
-                       int soffset = (i==nStartLine ? nStartChar : 0);
-                       int eoffset = (i==nEndLine ? nEndChar : GetFullLineLength(i));
+                       int soffset = (i == nStartLine ? nStartChar : 0);
+                       int eoffset = (i == nEndLine ? nEndChar : GetFullLineLength(i));
                        int chars = eoffset - soffset;
-                       LPCTSTR szLine = m_aLines[i].m_pcLine + soffset;
+                       LPCTSTR szLine = m_aLines[i].GetLine(soffset);
                        CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
                        pszBuf += chars;
 
                        // check that we really have an EOL
-                       if (i!=ApparentLastRealLine() && GetLineLength(i)==GetFullLineLength(i))
+                       if (i != ApparentLastRealLine() && GetLineLength(i) == GetFullLineLength(i))
                        {
                                // 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();
                        }
                }
        }
-       text.ReleaseBuffer(pszBuf - text);
+       text.ReleaseBuffer(static_cast<int>(pszBuf - text));
        text.FreeExtra();
 }
 
 ////////////////////////////////////////////////////////////////////////////
-// undo/redo functions
-
-<void CGhostTextBuffer::SUndoRecord::
-SetText (LPCTSTR pszText, int nLength)
-{
-       FreeText();
-       if (nLength)
-       {
-               if (nLength > 1)
-               {
-                       m_pszText = (TextBuffer *)malloc(sizeof(TextBuffer) + nLength * sizeof(TCHAR));
-                       m_pszText->size = nLength;
-                       memcpy(m_pszText->data, pszText, nLength * sizeof(TCHAR));
-                       m_pszText->data[nLength] = _T('?'); // debug sentinel
-               }
-               else
-               {
-                       m_szText[0] = pszText[0];
-               }
-       }
-}
-
-void CGhostTextBuffer::SUndoRecord::
-FreeText ()
-{
-       // See the m_szText/m_pszText definition
-       // Check if m_pszText is a pointer by removing bits having
-       // possible char value
-       if (((INT_PTR)m_pszText >> 16) != 0)
-               free(m_pszText);
-       m_pszText = NULL;
-}
-
-BOOL CGhostTextBuffer::
-Undo (CCrystalTextView * pSource, CPoint & ptCursorPos)
-{
-       ASSERT (CanUndo ());
-       ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
-       BOOL failed = FALSE;
-       int tmpPos = m_nUndoPosition;
-
-       while (!failed)
-       {
-               tmpPos--;;
-               SUndoRecord ur = m_aUndoBuf[tmpPos];
-               // Undo records are stored in file line numbers
-               // and must be converted to apparent (screen) line numbers for use
-               CPoint apparent_ptStartPos = ur.m_ptStartPos;
-               apparent_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, ur.m_ptStartPos_nGhost);
-               CPoint apparent_ptEndPos = ur.m_ptEndPos;
-               apparent_ptEndPos.y = ComputeApparentLine(ur.m_ptEndPos.y, ur.m_ptEndPos_nGhost);
-
-               if (ur.m_ptStartPos_nGhost > 0)
-                       // if we need a ghost line at position apparent_ptStartPos.y
-                       if (apparent_ptStartPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptStartPos.y) & LF_GHOST) == 0)
-                       {
-                               // if we don't find it, we insert it 
-                               InsertGhostLine (pSource, apparent_ptStartPos.y);
-                               // and recompute apparent_ptEndPos
-                               apparent_ptEndPos.y = ComputeApparentLine (ur.m_ptEndPos.y, ur.m_ptEndPos_nGhost);
-                       } 
-
-               // EndPos defined only for UNDO_INSERT (when we delete)
-               if (ur.m_dwFlags & UNDO_INSERT && ur.m_ptEndPos_nGhost > 0)
-                       // if we need a ghost line at position apparent_ptStartPos.y
-                       if (apparent_ptEndPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptEndPos.y) & LF_GHOST) == 0)
-                       {
-                               // if we don't find it, we insert it
-                               InsertGhostLine (pSource, apparent_ptEndPos.y);
-                       }
-
-               if (ur.m_dwFlags & UNDO_INSERT)
-               {
-                       // WINMERGE -- Check that text in undo buffer matches text in
-                       // file buffer. If not, then rescan() has moved lines and undo
-                       // is skipped.
-
-                       // we need to put the cursor before the deleted section
-                       CString text;
-                       ur.m_redo_ptEndPos.x = apparent_ptEndPos.x;
-                       ur.m_redo_ptEndPos.y = ComputeRealLineAndGhostAdjustment (apparent_ptEndPos.y, ur.m_redo_ptEndPos_nGhost);
-
-                       // flags are going to be deleted so we store them now
-                       int bLastLineGhost = ((GetLineFlags(apparent_ptEndPos.y) & LF_GHOST) != 0);
-
-                       if ((apparent_ptStartPos.y < m_aLines.GetSize ()) &&
-                                       (apparent_ptStartPos.x <= m_aLines[apparent_ptStartPos.y].m_nLength) &&
-                                       (apparent_ptEndPos.y < m_aLines.GetSize ()) &&
-                                       (apparent_ptEndPos.x <= m_aLines[apparent_ptEndPos.y].m_nLength))
-                       {
-                               GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text);
-                               if (text.GetLength() == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
-                               {
-                                       VERIFY (CCrystalTextBuffer::DeleteText (pSource, 
-                                               apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x,
-                                               0, FALSE));
-                                       ptCursorPos = apparent_ptStartPos;
-                               }
-                               else
-                               {
-                                       //..Try to ensure that we are undoing correctly...
-                                       //  Just compare the text as it was before Undo operation
-#ifdef _ADVANCED_BUGCHECK
-                                       ASSERT(0);
-#endif
-                                       failed = TRUE;
-                                       break;
-                               }
-
-                       }
-                       else
-                       {
-                               failed = TRUE;
-                               break;
-                       }
-
-                       OnNotifyLineHasBeenEdited(apparent_ptStartPos.y);
-
-                       // default : the remaining line inherits the status of the last line of the deleted block
-                       SetLineFlag(apparent_ptStartPos.y, LF_GHOST, bLastLineGhost, FALSE, FALSE);
-
-                       // the number of real lines must be the same before the action and after undo
-                       int nNumberDeletedRealLines = ur.m_ptEndPos.y - ur.m_ptStartPos.y;
-                       if (nNumberDeletedRealLines == ur.m_nRealLinesCreated)
-                               ;
-                       else if (nNumberDeletedRealLines == ur.m_nRealLinesCreated-1)
-                               // we inserted in a ghost line (which then became real), we must send it back to its world
-                               SetLineFlag(apparent_ptStartPos.y, LF_GHOST, TRUE, FALSE, FALSE);
-                       else
-                               ASSERT(0);
-
-                       // it is not easy to know when Recompute so we do it always
-                       RecomputeRealityMapping();
-
-                       RecomputeEOL (pSource, apparent_ptStartPos.y, apparent_ptStartPos.y);
-               }
-               else
-               {
-                       int nEndLine, nEndChar;
-                       VERIFY(CCrystalTextBuffer::InsertText (pSource, 
-                               apparent_ptStartPos.y, apparent_ptStartPos.x, ur.GetText (), ur.GetTextLength (), nEndLine, nEndChar, 
-                               0, FALSE));
-                       ptCursorPos = m_ptLastChange;
-
-                       // for the flags, the logic is nearly the same as in insertText
-                       int bFirstLineGhost = ((GetLineFlags(apparent_ptStartPos.y) & LF_GHOST) != 0);
-                       // when inserting an EOL terminated text into a ghost line,
-                       // there is a dicrepancy between nInsertedLines and nEndLine-nRealLine
-                       int bDiscrepancyInInsertedLines;
-                       if (bFirstLineGhost && nEndChar == 0)
-                               bDiscrepancyInInsertedLines = TRUE;
-                       else
-                               bDiscrepancyInInsertedLines = FALSE;
-
-                       int i;
-                       for (i = apparent_ptStartPos.y ; i < nEndLine ; i++)
-                               OnNotifyLineHasBeenEdited(i);
-                       if (bDiscrepancyInInsertedLines == 0)
-                               OnNotifyLineHasBeenEdited(i);
-
-                       // We know the number of real lines in the deleted block (including partial lines for extremities)
-                       // there may be more lines (difficult to explain) then they must be ghost
-                       for (i = apparent_ptStartPos.y ; i < apparent_ptStartPos.y + ur.m_nRealLinesInDeletedBlock ; i++)
-                               SetLineFlag (i, LF_GHOST, FALSE, FALSE, FALSE);
-                       for (   ; i <= nEndLine ; i++)
-                               SetLineFlag (i, LF_GHOST, TRUE, FALSE, FALSE);
-
-                       // it is not easy to know when Recompute so we do it always
-                       RecomputeRealityMapping();
-
-                       RecomputeEOL (pSource, apparent_ptStartPos.y, nEndLine);
-               }
-
-               // store infos for redo
-               ur.m_redo_ptStartPos.x = apparent_ptStartPos.x;
-               ur.m_redo_ptStartPos.y = ComputeRealLineAndGhostAdjustment( apparent_ptStartPos.y, ur.m_redo_ptStartPos_nGhost);
-               if (ur.m_dwFlags & UNDO_INSERT)
-                       ur.m_redo_ptEndPos = CPoint( -1, 0 );
-               else
-               {
-                       ur.m_redo_ptEndPos.x = m_ptLastChange.x;
-                       ur.m_redo_ptEndPos.y = ComputeRealLineAndGhostAdjustment (m_ptLastChange.y, ur.m_redo_ptEndPos_nGhost);
-               }
-
-               // restore line revision numbers
-               int i, naSavedRevisonNumbersSize = (int) ur.m_paSavedRevisonNumbers->GetSize();
-               for (i = 0; i < naSavedRevisonNumbersSize; i++)
-                       m_aLines[apparent_ptStartPos.y + i].m_dwRevisionNumber = (*ur.m_paSavedRevisonNumbers)[i];
-
-               m_aUndoBuf[tmpPos] = ur;
-
-               if (ur.m_dwFlags & UNDO_BEGINGROUP)
-                       break;
-       }
-       if (m_bModified && m_nSyncPosition == tmpPos)
-               SetModified (FALSE);
-       if (!m_bModified && m_nSyncPosition != tmpPos)
-               SetModified (TRUE);
-       if (failed)
-       {
-               // If the Undo failed, clear the entire Undo/Redo stack
-               // Not only can we not Redo the failed Undo, but the Undo
-               // may have partially completed (if in a group)
-               m_nUndoPosition = 0;
-               m_aUndoBuf.SetSize (m_nUndoPosition);
-       }
-       else
-       {
-               m_nUndoPosition = tmpPos;
-       }
-       return !failed;
-}
-
-BOOL CGhostTextBuffer::
-Redo (CCrystalTextView * pSource, CPoint & ptCursorPos)
-{
-       ASSERT (CanRedo ());
-       ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
-       ASSERT ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0);
-
-       while(1)
-       {
-               SUndoRecord ur = m_aUndoBuf[m_nUndoPosition];
-               CPoint apparent_ptStartPos = ur.m_redo_ptStartPos;
-               apparent_ptStartPos.y = ComputeApparentLine (ur.m_redo_ptStartPos.y, ur.m_redo_ptStartPos_nGhost);
-               CPoint apparent_ptEndPos = ur.m_redo_ptEndPos;
-               apparent_ptEndPos.y = ComputeApparentLine (ur.m_redo_ptEndPos.y, ur.m_redo_ptEndPos_nGhost);
-
-               if (ur.m_redo_ptStartPos_nGhost > 0) 
-                       // we need a ghost line at position apparent_ptStartPos.y
-                       if (apparent_ptStartPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptStartPos.y) & LF_GHOST) == 0)
-                       {
-                               // if we don't find it, we insert it 
-                               InsertGhostLine (pSource, apparent_ptStartPos.y);
-                               // and recompute apparent_ptEndPos
-                               apparent_ptEndPos.y = ComputeApparentLine (ur.m_redo_ptEndPos.y, ur.m_redo_ptEndPos_nGhost);
-                       } 
-
-               // EndPos defined only for UNDO_DELETE (when we delete)
-               if ((ur.m_dwFlags & UNDO_INSERT) == 0 && ur.m_redo_ptEndPos_nGhost > 0)
-                       // we need a ghost line at position apparent_ptStartPos.y
-                       if (apparent_ptEndPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptEndPos.y) & LF_GHOST) == 0)
-                       {
-                               // if we don't find it, we insert it
-                               InsertGhostLine (pSource, apparent_ptEndPos.y);
-                       }
-
-               // now we can use normal (CGhostTextBuffer::) insertTxt or deleteText
-               if (ur.m_dwFlags & UNDO_INSERT)
-               {
-                       int nEndLine, nEndChar;
-                       VERIFY(InsertText (pSource, apparent_ptStartPos.y, apparent_ptStartPos.x,
-                               ur.GetText(), ur.GetTextLength(), nEndLine, nEndChar, 0, FALSE));
-                       ptCursorPos = m_ptLastChange;
-               }
-               else
-               {
-#ifdef _ADVANCED_BUGCHECK
-                       CString text;
-                       GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text);
-                       ASSERT(text.GetLength() == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0);
-#endif
-                       VERIFY(DeleteText(pSource, apparent_ptStartPos.y, apparent_ptStartPos.x, 
-                               apparent_ptEndPos.y, apparent_ptEndPos.x, 0, FALSE));
-                       ptCursorPos = apparent_ptStartPos;
-               }
-               m_nUndoPosition++;
-               if (m_nUndoPosition == m_aUndoBuf.GetSize ())
-                       break;
-               if ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0)
-                       break;
-       }
-
-       if (m_bModified && m_nSyncPosition == m_nUndoPosition)
-               SetModified (FALSE);
-       if (!m_bModified && m_nSyncPosition != m_nUndoPosition)
-               SetModified (TRUE);
-       return TRUE;
-}
-
-
-/** 
-we must set both our m_bUndoBeginGroup and the one of CCrystalTextBuffer
-*/
-void CGhostTextBuffer::
-BeginUndoGroup (BOOL bMergeWithPrevious /*= FALSE*/ )
-{
-       ASSERT (!m_bUndoGroup);
-       m_bUndoGroup = TRUE;
-       m_bUndoBeginGroup = m_nUndoPosition == 0 || !bMergeWithPrevious;
-       CCrystalTextBuffer::m_bUndoBeginGroup = m_bUndoBeginGroup;
-}
-
-/** Use ou own flushing function as we need to use our own m_aUndoBuf */
-void CGhostTextBuffer::
-FlushUndoGroup (CCrystalTextView * pSource)
-{
-       ASSERT (m_bUndoGroup);
-       if (pSource != NULL)
-       {
-               ASSERT (m_nUndoPosition == m_aUndoBuf.GetSize ());
-               if (m_nUndoPosition > 0)
-               {
-                       pSource->OnEditOperation (m_aUndoBuf[m_nUndoPosition - 1].m_nAction, m_aUndoBuf[m_nUndoPosition - 1].GetText ());
-               }
-       }
-       m_bUndoGroup = FALSE;
-}
-
+// edition functions
 
-/** The CPoint received parameters are apparent (on screen) line numbers */
-void CGhostTextBuffer::
-AddUndoRecord (BOOL bInsert, const CPoint & ptStartPos, const CPoint & ptEndPos, LPCTSTR pszText, int cchText, int nRealLinesChanged, int nActionType, CDWordArray *paSavedRevisonNumbers)
+/**
+ * @brief Insert text to the buffer.
+ * @param [in] pSource View into which to insert the text.
+ * @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.
+ * @param [in] nAction Edit action.
+ * @param [in] bHistory Save insertion for undo/redo?
+ * @return true if the insertion succeeded, false otherwise.
+ * @note Line numbers are apparent (screen) line numbers, not real
+ * line numbers in the file.
+ * @note @p nEndLine and @p nEndChar are valid as long as you do not call
+ *   FlushUndoGroup. If you need to call FlushUndoGroup, just store them in a
+ *   variable which is preserved with real line number during Rescan
+ *   (m_ptCursorPos, m_ptLastChange for example).
+ */
+bool CGhostTextBuffer::                        /* virtual override */
+InsertText (CCrystalTextView * pSource, int nLine,
+               int nPos, LPCTSTR pszText, size_t cchText, int &nEndLine, int &nEndChar,
+               int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
 {
-       //  Forgot to call BeginUndoGroup()?
-       ASSERT (m_bUndoGroup);
-       ASSERT (m_aUndoBuf.GetSize () == 0 || (m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
+       bool bGroupFlag = false;
+       bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
+       bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
 
-       //  Strip unnecessary undo records (edit after undo wipes all potential redo records)
-       int nBufSize = (int) m_aUndoBuf.GetSize ();
-       if (m_nUndoPosition < nBufSize)
+       if (bFirstLineGhost && cchText > 0)
        {
-               m_aUndoBuf.SetSize (m_nUndoPosition);
-       }
-
-       //  If undo buffer size is close to critical, remove the oldest records
-       ASSERT (m_aUndoBuf.GetSize () <= m_nUndoBufSize);
-       nBufSize = (int) m_aUndoBuf.GetSize ();
-       if (nBufSize >= m_nUndoBufSize)
-       {
-               int nIndex = 0;
-               for (;;)
-               {
-                       nIndex++;
-                       if (nIndex == nBufSize || (m_aUndoBuf[nIndex].m_dwFlags & UNDO_BEGINGROUP) != 0)
-                               break;
-               }
-               m_aUndoBuf.RemoveAt (0, nIndex);
-
-//<jtuc 2003-06-28>
-//- Keep m_nSyncPosition in sync.
-//- Ensure first undo record is flagged UNDO_BEGINGROUP since part of the code
-//..relies on this condition.
-               if (m_nSyncPosition >= 0)
+               CString text = GetStringEol(GetCRLFMode());
+               if (bHistory && !m_bUndoGroup)
                {
-                       m_nSyncPosition -= nIndex;              // Ã§Ã  c'est bien...mais non, test inutile ? Ou Apres !
+                       BeginUndoGroup();
+                       bGroupFlag = true;
                }
-               if (nIndex < nBufSize)
+               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]))
                {
-                       // Not really necessary as long as groups are discarded as a whole.
-                       // Just in case some day the loop above should be changed to limit
-                       // the number of discarded undo records to some reasonable value...
-                       m_aUndoBuf[0].m_dwFlags |= UNDO_BEGINGROUP;             // Ã§Ã  c'est sale
+                       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);
                }
-               else
-               {
-                       // No undo records left - begin a new group:
-                       m_bUndoBeginGroup = TRUE;
-               }
-//</jtuc>
-
-       }
-       ASSERT (m_aUndoBuf.GetSize () < m_nUndoBufSize);
-
-       //  Add new record
-       SUndoRecord ur;
-       ur.m_dwFlags = bInsert ? UNDO_INSERT : 0;
-       ur.m_nAction = nActionType;
-       if (m_bUndoBeginGroup)
-       {
-               ur.m_dwFlags |= UNDO_BEGINGROUP;
-               m_bUndoBeginGroup = FALSE;
-       }
-       ur.m_ptStartPos = ptStartPos;
-       ur.m_ptEndPos = ptEndPos;
-       ur.m_ptStartPos.y = ComputeRealLineAndGhostAdjustment( ptStartPos.y, ur.m_ptStartPos_nGhost);
-       ur.m_ptEndPos.y = ComputeRealLineAndGhostAdjustment( ptEndPos.y, ur.m_ptEndPos_nGhost);
-       if (bInsert)
-               ur.m_nRealLinesCreated = nRealLinesChanged;
-       else
-               ur.m_nRealLinesInDeletedBlock = nRealLinesChanged;
-       ur.SetText (pszText, cchText);
-       ur.m_paSavedRevisonNumbers = paSavedRevisonNumbers;
-
-       m_aUndoBuf.Add (ur);
-       m_nUndoPosition = (int) m_aUndoBuf.GetSize ();
-
-       ASSERT (m_aUndoBuf.GetSize () <= m_nUndoBufSize);
-}
-
-
-
-
-////////////////////////////////////////////////////////////////////////////
-// edition functions
-
-/**
- *
- * @param nEndLine and nEndChar are the coordinates of the end od the inserted text
- * They are valid as long as you do not call FlushUndoGroup
- * If you need to call FlushUndoGroup, just store them in a variable which
- * is preserved with real line number during Rescan (m_ptCursorPos, m_ptLastChange for example)
- */
-BOOL CGhostTextBuffer::
-InsertText (CCrystalTextView * pSource, int nLine, int nPos, LPCTSTR pszText, int cchText,
-            int &nEndLine, int &nEndChar, int nAction, BOOL bHistory /*=TRUE*/)
-{
-       BOOL bGroupFlag = FALSE;
-       if (bHistory)
-       {
-               if (!m_bUndoGroup)
-               {
-                       BeginUndoGroup ();
-                       bGroupFlag = TRUE;
-               } 
        }
 
-       // save line revision numbers for undo
-       CDWordArray *paSavedRevisonNumbers = new CDWordArray;
-       paSavedRevisonNumbers->SetSize(1);
-       (*paSavedRevisonNumbers)[0] = m_aLines[nLine].m_dwRevisionNumber;
-
-       if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText, cchText, nEndLine, nEndChar, nAction, bHistory))
+       if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText,
+               cchText, nEndLine, nEndChar, nAction, bHistory))
        {
-               delete paSavedRevisonNumbers;
-               return FALSE;
+               return false;
        }
 
-       // set WinMerge flags
-       int bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
-
        // when inserting an EOL terminated text into a ghost line,
-       // there is a dicrepancy between nInsertedLines and nEndLine-nRealLine
-       int bDiscrepancyInInsertedLines;
-       if (bFirstLineGhost && nEndChar == 0)
-               bDiscrepancyInInsertedLines = TRUE;
+       // there is a discrepancy between nInsertedLines and nEndLine-nRealLine
+       bool bDiscrepancyInInsertedLines;
+       if (bFirstLineGhost && nEndChar == 0 && ApparentLastRealLine() >= nEndLine)
+               bDiscrepancyInInsertedLines = true;
        else
-               bDiscrepancyInInsertedLines = FALSE;
+               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)
+       if (bFirstLineGhost && (nEndChar > 0 || ApparentLastRealLine() < nEndLine))
                // we create one more real line
                nRealLinesCreated ++;
 
@@ -688,7 +313,7 @@ InsertText (CCrystalTextView * pSource, int nLine, int nPos, LPCTSTR pszText, in
                m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
                OnNotifyLineHasBeenEdited(i);
        }
-       if (bDiscrepancyInInsertedLines == 0)
+       if (!bDiscrepancyInInsertedLines)
        {
                m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
                OnNotifyLineHasBeenEdited(i);
@@ -704,6 +329,8 @@ InsertText (CCrystalTextView * pSource, int nLine, int nPos, LPCTSTR pszText, in
                // delete at most nInsertedTextLinesCount - 1 ghost lines
                // as the first ghost line has been reused
                int nMaxGhostLineToDelete = min(nInsertedTextLinesCount - 1, GetLineCount()-nLineAfterInsertedBlock);
+               if (nEndChar == 0 && ApparentLastRealLine() < nEndLine)
+                       nMaxGhostLineToDelete --;
                for (i = 0 ; i < nMaxGhostLineToDelete ; i++)
                        if ((GetLineFlags(nLineAfterInsertedBlock+i) & LF_GHOST) == 0)
                                break;
@@ -711,14 +338,23 @@ InsertText (CCrystalTextView * pSource, int nLine, int nPos, LPCTSTR pszText, in
        }
 
        for (i = nLine ; i < nEndLine ; i++)
-               SetLineFlag (i, LF_GHOST, FALSE, FALSE, FALSE);
-       if (bDiscrepancyInInsertedLines == 0)
+               SetLineFlag (i, LF_GHOST, false, false, false);
+       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);
+               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)
@@ -727,267 +363,213 @@ InsertText (CCrystalTextView * pSource, int nLine, int nPos, LPCTSTR pszText, in
                RecomputeRealityMapping();
        }
 
-       RecomputeEOL (pSource, nLine, nEndLine);
-
-
-       if (bHistory == false)
-       {
-               delete paSavedRevisonNumbers;
-               return TRUE;
-       }
-
-
-       // little trick as we share the m_nUndoPosition with the base class
-       ASSERT (  m_nUndoPosition > 0);
-       m_nUndoPosition --;
-       AddUndoRecord (TRUE, CPoint (nPos, nLine), CPoint (nEndChar, nEndLine),
-                 pszText, cchText, nRealLinesCreated, nAction, paSavedRevisonNumbers);
-
        if (bGroupFlag)
                FlushUndoGroup (pSource);
 
        // nEndLine may have changed during Rescan
        nEndLine = m_ptLastChange.y;
 
-       return TRUE;
+       return true;
 }
 
-BOOL CGhostTextBuffer::
-DeleteText (CCrystalTextView * pSource, int nStartLine, int nStartChar,
-            int nEndLine, int nEndChar, int nAction, BOOL bHistory /*=TRUE*/)
+CDWordArray *CGhostTextBuffer::                        /* virtual override */
+CopyRevisionNumbers(int nStartLine, int nEndLine) const
 {
-       BOOL bGroupFlag = FALSE;
-       if (bHistory)
+       CDWordArray *paSavedRevisionNumbers = CCrystalTextBuffer::CopyRevisionNumbers(nStartLine, nEndLine);
+       for (int nLine = nEndLine; nLine >= nStartLine; --nLine)
        {
-               if (!m_bUndoGroup)
-               {
-                       BeginUndoGroup ();
-                       bGroupFlag = TRUE;
-               } 
+               if ((GetLineFlags(nLine) & LF_GHOST) != 0)
+                       paSavedRevisionNumbers->RemoveAt(nLine - nStartLine);
        }
-
-       // save line revision numbers for undo
-       CDWordArray *paSavedRevisonNumbers = new CDWordArray;
-       paSavedRevisonNumbers->SetSize(nEndLine - nStartLine + 1);
-       int i, j;
-       for (i = 0, j = 0; i < nEndLine - nStartLine + 1; i++)
+       if ((GetLineFlags(nEndLine) & LF_GHOST) != 0)
        {
-               DWORD dwLineFlag = GetLineFlags(nStartLine + i);
-               if (!(dwLineFlag & LF_GHOST))
-                       (*paSavedRevisonNumbers)[j++] = m_aLines[nStartLine + i].m_dwRevisionNumber;
+               for (int nLine = nEndLine + 1; nLine < GetLineCount(); ++nLine)
+                       if ((GetLineFlags(nLine) & LF_GHOST) == 0)
+                       {
+                               paSavedRevisionNumbers->Add(GetLineFlags(nLine));
+                               break;
+                       }
        }
-       paSavedRevisonNumbers->SetSize(j);
-
-       // flags are going to be deleted so we store them now
-       int bLastLineGhost = ((GetLineFlags(nEndLine) & LF_GHOST) != 0);
-       int bFirstLineGhost = ((GetLineFlags(nStartLine) & LF_GHOST) != 0);
-       // count the number of real lines in the deleted block (for first/last line, include partial real lines)
-       int nRealLinesInDeletedBlock = ComputeRealLine(nEndLine) - ComputeRealLine(nStartLine);
-       if (!bLastLineGhost)
-               nRealLinesInDeletedBlock ++;
-
-       CString sTextToDelete;
-       GetTextWithoutEmptys (nStartLine, nStartChar, nEndLine, nEndChar, sTextToDelete);
-       if (!CCrystalTextBuffer::DeleteText (pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory))
+       return paSavedRevisionNumbers;
+}
+
+void CGhostTextBuffer::                        /* virtual override */
+RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
+{
+       for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
        {
-               delete paSavedRevisonNumbers;
-               return FALSE;
+               if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0)
+               {
+                       m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
+                       ++i;
+               }
        }
+}
 
-       OnNotifyLineHasBeenEdited(nStartLine);
-       // update line revision numbers of modified lines
-       if (nStartChar != 0 || nEndChar != 0)
-               m_aLines[nStartLine].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
-
-       // the first line inherits the status of the last one 
-       // but exception... if the last line is a ghost, we preserve the status of the first line
-       // (then if we use backspace in a ghost line, we don't delete the previous line)
-       if (bLastLineGhost == FALSE)
-               SetLineFlag(nStartLine, LF_GHOST, FALSE, FALSE, FALSE);
-       else
+bool CGhostTextBuffer::                        /* virtual override */
+DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar,
+            int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
+{
+       int const nLineCount = GetLineCount();
+       while (nEndLine < nLineCount - 1 && GetLineFlags(nEndLine) & LF_GHOST)
+               ++nEndLine;
+       if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
+               nEndLine, nEndChar, nAction, bHistory))
        {
-               int bFlagException = (bFirstLineGhost == 0);
-               if (bFlagException)
-                       SetLineFlag(nStartLine, LF_GHOST, FALSE, FALSE, FALSE);
-               else
-                       SetLineFlag(nStartLine, LF_GHOST, TRUE, FALSE, FALSE);
+               return false;
        }
 
+       if (nStartChar != 0 || nEndChar != 0)
+               OnNotifyLineHasBeenEdited(nStartLine);
+
        // now we can recompute
        if (nStartLine != nEndLine)
        {
                // TODO: Be smarter, and don't recompute if it is easy to see what changed
                RecomputeRealityMapping();
        }
-
-       RecomputeEOL (pSource, nStartLine, nStartLine);
-
-
-       if (bHistory == false)
-       {
-               delete paSavedRevisonNumbers;
-               return TRUE;
-       }
-
-       // little trick as we share the m_nUndoPosition with the base class
-       ASSERT (  m_nUndoPosition > 0);
-       m_nUndoPosition --;
-       AddUndoRecord (FALSE, CPoint (nStartChar, nStartLine), CPoint (0, -1),
-                 sTextToDelete, sTextToDelete.GetLength(), nRealLinesInDeletedBlock, nAction, paSavedRevisonNumbers);
-
-       if (bGroupFlag)
-               FlushUndoGroup (pSource);
-       return TRUE;
+               
+       return true;
 }
 
-BOOL CGhostTextBuffer::
-InsertGhostLine (CCrystalTextView * pSource, int nLine)
+#if 0
+/**
+ * @brief Insert a ghost line to the buffer (and view).
+ * @param [in] pSource The view to which to add the ghost line.
+ * @param [in] Line index (apparent/screen) where to add the ghost line.
+ * @return true if the addition succeeded, false otherwise.
+ */
+bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
 {
        if (!InternalInsertGhostLine (pSource, nLine))
-               return FALSE;
-
-       // set WinMerge flags  
-       SetLineFlag (nLine, LF_GHOST, TRUE, FALSE, FALSE);
+               return false;
 
+       // Set WinMerge flags  
+       SetLineFlag (nLine, LF_GHOST, true, false, false);
        RecomputeRealityMapping();
 
-       // don't need to recompute EOL as real lines are unchanged
-
-       // never AddUndoRecord as Rescan clears the ghost lines
-
-       return TRUE;
+       // Don't need to recompute EOL as real lines are unchanged.
+       // Never AddUndoRecord as Rescan clears the ghost lines.
+       return true;
 }
+#endif
 
-void CGhostTextBuffer::
-RemoveAllGhostLines()
+/**
+ * @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)
-                       delete[] m_aLines[ct].m_pcLine;
-       // 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 ((GetLineFlags(ct) & LF_GHOST) == 0)
-                       m_aLines[newnl++] = m_aLines[ct];
-
-       // Discard unused entries in one shot
-       m_aLines.SetSize(newnl);
+               {
+                       m_aLines[ct].FreeBuffer();
+                       if (nFirstGhost < 0)
+                               nFirstGhost = ct;
+               }
+       }
+       if (nFirstGhost >= 0)
+       {
+               // 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];
+               }
 
-       RecomputeRealityMapping();
+               // Discard unused entries in one shot
+               m_aLines.resize(newnl);
+               RecomputeRealityMapping();
+       }
 }
 
 ////////////////////////////////////////////////////////////////////////////
 // apparent <-> real line conversion
 
 /**
-Return apparent line of highest real (file) line. 
-Return -1 if no lines.
-*/
+ * @brief Get last apparent (screen) line index.
+ * @return Last apparent line, or -1 if no lines in the buffer.
+ */
 int CGhostTextBuffer::ApparentLastRealLine() const
 {
-       int bmax = (int) m_RealityBlocks.GetUpperBound();
-       if (bmax<0) return -1;
-       const RealityBlock & block = m_RealityBlocks[bmax];
+       if (m_RealityBlocks.size() == 0)
+               return -1;
+       const RealityBlock &block = m_RealityBlocks.back();
        return block.nStartApparent + block.nCount - 1;
 }
 
 /**
-Return underlying real line. 
-For ghost lines, return NEXT HIGHER real line (for trailing ghost line, return last real line + 1). 
-If nApparentLine is greater than the last valid apparent line, ASSERT
-
-ie, lines 0->0, 1->2, 2->4, 
-for argument of 3, return 2
-*/
+ * @brief Get a real line for the 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.
+ * @param [in] nApparentLine Apparent line for which to get the real line.
+ * @return The real line for the apparent line.
+ */
 int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
 {
-       int bmax = (int) m_RealityBlocks.GetUpperBound();
-       // first get the degenerate cases out of the way
-       // empty file ?
-       if (bmax<0)
-               return 0;
-
-       // after last apparent line ?
-       ASSERT(nApparentLine < GetLineCount());
-
-       // after last block ?
-       const RealityBlock & maxblock = m_RealityBlocks[bmax];
-       if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
-               return maxblock.nStartReal + maxblock.nCount;
-
-       // binary search to find correct (or nearest block)
-       int blo=0, bhi=bmax;
-       int i;
-       while (blo<=bhi)
-       {
-               i = (blo+bhi)/2;
-               const RealityBlock & block = m_RealityBlocks[i];
-               if (nApparentLine < block.nStartApparent)
-                       bhi = i-1;
-               else if (nApparentLine >= block.nStartApparent + block.nCount)
-                       blo = i+1;
-               else // found it inside this block
-                       return (nApparentLine - block.nStartApparent) + block.nStartReal;
-       }
-       // it is a ghost line just before block blo
-       return m_RealityBlocks[blo].nStartReal;
+       int decToReal;
+       return ComputeRealLineAndGhostAdjustment(nApparentLine, decToReal);
 }
 
 /**
-Return apparent line for this underlying real line. 
-If real line is out of bounds, return last valid apparent line + 1
-*/
+ * @brief Get an apparent (screen) line for the real line.
+ * @param [in] nRealLine Real line for which to get the apparent line.
+ * @return The apparent line for the real line. If real line is out of bounds
+ *   return last valid apparent line + 1.
+ */
 int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
 {
-       int bmax = (int) m_RealityBlocks.GetUpperBound();
-       // first get the degenerate cases out of the way
-       // empty file ?
-       if (bmax<0)
+       const int size = static_cast<int>(m_RealityBlocks.size());
+       if (size == 0)
                return 0;
+
        // after last block ?
-       const RealityBlock & maxblock = m_RealityBlocks[bmax];
+       const RealityBlock & maxblock = m_RealityBlocks.back();
        if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
                return GetLineCount();
 
        // binary search to find correct (or nearest block)
-       int blo=0, bhi=bmax;
-       int i;
-       while (blo<=bhi)
+       int blo = 0;
+       int bhi = size - 1;
+       while (blo <= bhi)
        {
-               i = (blo+bhi)/2;
+               int i = (blo + bhi) / 2;
                const RealityBlock & block = m_RealityBlocks[i];
                if (nRealLine < block.nStartReal)
-                       bhi = i-1;
+                       bhi = i - 1;
                else if (nRealLine >= block.nStartReal + block.nCount)
-                       blo = i+1;
+                       blo = i + 1;
                else
                        return (nRealLine - block.nStartReal) + block.nStartApparent;
        }
        // Should have found it; all real lines should be in a block
-       ASSERT(0);
+       ASSERT(false);
        return -1;
 }
 
 /**
-Return underlying real line and ghost adjustment 
-as nApparentLine = apparent(nRealLine) - nGhostAdjustment 
-
-nRealLine for ghost lines is the NEXT HIGHER real line (for trailing ghost line, last real line + 1).
-If nApparentLine is greater than the last valid apparent line, ASSERT
-
-ie, lines 0->0, 1->2, 2->4,  
-for argument of 3, return 2, and decToReal = 1
-*/
-int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, int& decToReal) const
+ * @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; 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.
+ */
+int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine,
+               int& decToReal) const
 {
-       int bmax = (int) m_RealityBlocks.GetUpperBound();
-       // first get the degenerate cases out of the way
-       // empty file ?
-       if (bmax<0) 
+       const int size = static_cast<int>(m_RealityBlocks.size());
+       if (size == 0) 
        {
                decToReal = 0;
                return 0;
@@ -997,7 +579,7 @@ int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, int&
        ASSERT(nApparentLine < GetLineCount());
 
        // after last block ?
-       const RealityBlock & maxblock = m_RealityBlocks[bmax];
+       const RealityBlock & maxblock = m_RealityBlocks.back();
        if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
        {
                decToReal = GetLineCount() - nApparentLine;
@@ -1005,16 +587,16 @@ int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, int&
        }
 
        // binary search to find correct (or nearest block)
-       int blo=0, bhi=bmax;
-       int i;
-       while (blo<=bhi)
+       int blo = 0;
+       int bhi = size - 1;
+       while (blo <= bhi)
        {
-               i = (blo+bhi)/2;
+               int i = (blo + bhi) / 2;
                const RealityBlock & block = m_RealityBlocks[i];
                if (nApparentLine < block.nStartApparent)
-                       bhi = i-1;
+                       bhi = i - 1;
                else if (nApparentLine >= block.nStartApparent + block.nCount)
-                       blo = i+1;
+                       blo = i + 1;
                else // found it inside this block
                {
                        decToReal = 0;
@@ -1027,43 +609,42 @@ int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, int&
 }
 
 /**
-Return apparent line for this underlying real line, with adjustment : 
-nApparent = apparent(nReal) - decToReal
-
-If the previous real line has apparent number   apparent(nReal) - dec, with dec < decToReal, 
-return apparent(nReal) - dec + 1
-*/
+ * @brief Get an apparent (screen) line for the real line.
+ * @param [in] nRealLine Real line for which to get the apparent 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.
+ */
 int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
 {
-       int blo, bhi;
        int nPreviousBlock;
        int nApparent;
-       int bmax = (int) m_RealityBlocks.GetUpperBound();
-       // first get the degenerate cases out of the way
-       // empty file ?
-       if (bmax<0)
+
+       const int size = (int) m_RealityBlocks.size();
+       int blo = 0;
+       int bhi = size - 1;
+       int i;
+       if (size == 0)
                return 0;
+
        // after last block ?
-       const RealityBlock & maxblock = m_RealityBlocks[bmax];
+       const RealityBlock & maxblock = m_RealityBlocks.back();
        if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
        {
-               nPreviousBlock = bmax;
-               nApparent = GetLineCount();
+               nPreviousBlock = size - 1;
+               nApparent = GetLineCount() - 1;
                goto limitWithPreviousBlock;
        }
 
        // binary search to find correct (or nearest block)
-       blo=0;
-       bhi=bmax;
-       int i;
-       while (blo<=bhi)
+       while (blo <= bhi)
        {
-               i = (blo+bhi)/2;
+               i = (blo + bhi) / 2;
                const RealityBlock & block = m_RealityBlocks[i];
                if (nRealLine < block.nStartReal)
-                       bhi = i-1;
+                       bhi = i - 1;
                else if (nRealLine >= block.nStartReal + block.nCount)
-                       blo = i+1;
+                       blo = i + 1;
                else
                {
                        if (nRealLine > block.nStartReal)
@@ -1075,7 +656,7 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
                }
        }
        // Should have found it; all real lines should be in a block
-       ASSERT(0);
+       ASSERT(false);
        return -1;
 
 limitWithPreviousBlock:
@@ -1093,7 +674,7 @@ limitWithPreviousBlock:
        {
                nApparent --;
                if (nApparent == lastApparentInPreviousBlock)
-                       return nApparent+1;
+                       return nApparent + 1;
        }
        return nApparent;
 }
@@ -1108,17 +689,22 @@ void CGhostTextBuffer::FinishLoading()
 /** Recompute the reality mapping (this is fairly naive) */
 void CGhostTextBuffer::RecomputeRealityMapping()
 {
-       m_RealityBlocks.RemoveAll();
-       int reality=-1; // last encountered real line
-       int i=0; // current line
+       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;
@@ -1126,23 +712,38 @@ passingGhosts:
        }
        // this is the first line of a reality block
        block.nStartApparent = i;
-       block.nStartReal = reality+1;
+       block.nStartReal = reality + 1;
+       block.nCount = -1;
        ++reality;
        ++i;
        // fall through to other state
 
-       // state 2, i-1 is real line
+       // 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);
                block.nCount = i - block.nStartApparent;
                ASSERT(block.nCount > 0);
-               ASSERT(reality+1-block.nStartReal == block.nCount);
-               m_RealityBlocks.Add(block);
-               if (i==GetLineCount())
+               ASSERT(reality + 1 - block.nStartReal == block.nCount);
+               
+               // Optimize memory allocation
+               if (m_RealityBlocks.capacity() == m_RealityBlocks.size())
+               {
+                       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 == nLineCount)
+               {
+                       checkFlagsFromReality();
                        return;
+               }
                ++i;
                goto passingGhosts;
        }
@@ -1151,72 +752,16 @@ inReality:
        goto inReality;
 }
 
-/** we recompute EOL from the real line before nStartLine to nEndLine */
-void CGhostTextBuffer::RecomputeEOL(CCrystalTextView * pSource, int nStartLine, int nEndLine)
-{
-       if (ApparentLastRealLine() <= nEndLine)
-       {
-               // EOL may have to change on the real line before nStartLine
-               int nRealBeforeStart;
-               for (nRealBeforeStart = nStartLine-1 ; nRealBeforeStart >= 0 ; nRealBeforeStart--)
-                       if ((GetLineFlags(nRealBeforeStart) & LF_GHOST) == 0)
-                               break;
-               if (nRealBeforeStart >= 0)
-                       nStartLine = nRealBeforeStart;
-       }
-       int bLastRealLine = (ApparentLastRealLine() <= nEndLine);
-       int i;
-       for (i = nEndLine ; i >= nStartLine ; i --)
-       {
-               if ((GetLineFlags(i) & LF_GHOST) == 0)
-               {
-                       if (bLastRealLine)
-                       {
-                               bLastRealLine = 0;
-                               if (m_aLines[i].m_nEolChars != 0) 
-                               {
-                                       // if the last real line has an EOL, remove it
-                                       m_aLines[i].m_pcLine[m_aLines[i].m_nLength] = '\0';
-                                       m_aLines[i].m_nEolChars = 0;
-                                       if (pSource!=NULL)
-                                               UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
-                               }
-                       }
-                       else
-                       {
-                               if (m_aLines[i].m_nEolChars == 0) 
-                               {
-                                       // if a real line (not the last) has no EOL, add one
-                                       AppendLine (i, GetDefaultEol(), (int) _tcslen(GetDefaultEol()));
-                                       if (pSource!=NULL)
-                                               UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
-                               }
-                       }
-               }
-               else 
-               {
-                       if (m_aLines[i].m_nEolChars != 0) 
-                       {
-                               // if a ghost line has an EOL, remove it
-                               m_aLines[i].m_pcLine[m_aLines[i].m_nLength] = '\0';
-                               m_aLines[i].m_nEolChars = 0;
-                               if (pSource!=NULL)
-                                       UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
-                       }
-               }
-       }
-}
-
 /** 
 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
 {
-       int bmax = (int) m_RealityBlocks.GetUpperBound();
-       int b;
+#ifdef _DEBUG
+       const int size = static_cast<int>(m_RealityBlocks.size());
        int i = 0;
-       for (b = 0 ; b <= bmax ; b ++)
+       for (int b = 0 ; b < size ; b ++)
        {
                const RealityBlock & block = m_RealityBlocks[b];
                for ( ; i < block.nStartApparent ; i++)
@@ -1227,11 +772,139 @@ void CGhostTextBuffer::checkFlagsFromReality(BOOL bFlag) const
 
        for ( ; i < GetLineCount() ; i++)
                ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
+#endif 
 }
 
-void CGhostTextBuffer::OnNotifyLineHasBeenEdited(int nLine)
+void CGhostTextBuffer::                        /* virtual base */
+OnNotifyLineHasBeenEdited(int nLine)
 {
        return;
 }
 
+void CGhostTextBuffer::
+CountEolAndLastLineLength(const CPoint& ptStartPos, LPCTSTR pszText, size_t cchText, int &nLastLineLength, int &nEol)
+{
+       nLastLineLength = 0;
+       nEol = 0;
+       if (m_bTableEditing && m_bAllowNewlinesInQuotes)
+       {
+               bool bInQuote = false;
+               const TCHAR* pszLine = m_aLines[ptStartPos.y].GetLine();
+               for (int j = 0; j < ptStartPos.x; ++j)
+               {
+                       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;
+               }
+       }
+}
+
+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 /*= nullptr*/)
+{
+       CPoint real_ptStartPos(ptStartPos.x, ComputeRealLine(ptStartPos.y));
+       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::                  /* virtual override */
+GetUndoRecord(int nUndoPos) const
+{
+       UndoRecord ur = m_aUndoBuf[nUndoPos];
+       ur.m_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, 0);
+       ur.m_ptEndPos.y = ComputeApparentLine(ur.m_ptEndPos.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;
+}