OSDN Git Service

crystaledit: Use GetProfile*()/WriteProfile*() to read and write the registry wheneve...
[winmerge-jp/winmerge-jp.git] / Src / GhostTextBuffer.cpp
index ab85936..99806a3 100644 (file)
@@ -7,21 +7,7 @@
 //    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)
@@ -56,6 +39,7 @@ CGhostTextBuffer::CGhostTextBuffer()
 {
 }
 
+#if 0
 /**
  * @brief Insert a ghost line.
  * @param [in] pSource View into which to insert the line.
@@ -77,17 +61,19 @@ bool CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource,
        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).
  */
@@ -96,20 +82,15 @@ bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
 {
        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();
        }
 
@@ -117,20 +98,20 @@ bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
        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;
@@ -140,18 +121,19 @@ bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
  * @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*/)
+                 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));
@@ -170,7 +152,7 @@ void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar,
                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);
@@ -218,9 +200,9 @@ void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar,
                        {
                                // 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();
                        }
                }
@@ -238,6 +220,7 @@ void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar,
  * @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.
@@ -251,14 +234,16 @@ void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar,
  *   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 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*/)
 {
        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)
@@ -266,7 +251,24 @@ bool CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine,
                        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,
@@ -276,13 +278,28 @@ bool CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine,
        }
 
        // 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))
@@ -296,7 +313,7 @@ bool CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine,
                m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
                OnNotifyLineHasBeenEdited(i);
        }
-       if (bDiscrepancyInInsertedLines == 0)
+       if (!bDiscrepancyInInsertedLines)
        {
                m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
                OnNotifyLineHasBeenEdited(i);
@@ -322,13 +339,22 @@ bool CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine,
 
        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)
@@ -346,7 +372,7 @@ bool CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine,
        return true;
 }
 
-CDWordArray *CGhostTextBuffer::
+CDWordArray *CGhostTextBuffer::                        /* virtual override */
 CopyRevisionNumbers(int nStartLine, int nEndLine) const
 {
        CDWordArray *paSavedRevisionNumbers = CCrystalTextBuffer::CopyRevisionNumbers(nStartLine, nEndLine);
@@ -367,7 +393,7 @@ CopyRevisionNumbers(int nStartLine, int nEndLine) const
        return paSavedRevisionNumbers;
 }
 
-void CGhostTextBuffer::
+void CGhostTextBuffer::                        /* virtual override */
 RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
 {
        for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
@@ -380,13 +406,14 @@ RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
        }
 }
 
-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;
@@ -405,6 +432,7 @@ DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar,
        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.
@@ -424,6 +452,7 @@ bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
        // Never AddUndoRecord as Rescan clears the ghost lines.
        return true;
 }
+#endif
 
 /**
  * @brief Remove all the ghost lines from the buffer.
@@ -431,25 +460,33 @@ bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
 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();
+       }
 }
 
 ////////////////////////////////////////////////////////////////////////////
@@ -514,7 +551,7 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
                        return (nRealLine - block.nStartReal) + block.nStartApparent;
        }
        // Should have found it; all real lines should be in a block
-       ASSERT(0);
+       ASSERT(false);
        return -1;
 }
 
@@ -522,8 +559,8 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine) 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). 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.
@@ -574,7 +611,7 @@ int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine,
 /**
  * @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.
  */
@@ -595,7 +632,7 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
        if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
        {
                nPreviousBlock = size - 1;
-               nApparent = GetLineCount();
+               nApparent = GetLineCount() - 1;
                goto limitWithPreviousBlock;
        }
 
@@ -619,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:
@@ -655,14 +692,19 @@ void CGhostTextBuffer::RecomputeRealityMapping()
        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;
@@ -671,13 +713,15 @@ passingGhosts:
        // 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);
@@ -691,11 +735,15 @@ inReality:
                        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;
        }
@@ -708,8 +756,9 @@ inReality:
 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 ++)
@@ -723,44 +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;
 }
 
-static int CountEol(LPCTSTR pszText, int 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,
-       const CPoint & ptEndPos, LPCTSTR pszText, int cchText,
+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);
-       ur.m_ptEndPos.y = ur.m_ptStartPos.y + (m_aUndoBuf[nUndoPos].m_ptEndPos.y - m_aUndoBuf[nUndoPos].m_ptStartPos.y);
+       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;
+}
+