// 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 <vector>
+#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 namespace std;
+using std::vector;
BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
//{{AFX_MSG_MAP(CGhostTextBuffer)
*/
CGhostTextBuffer::CGhostTextBuffer()
{
- m_bUndoGroup = FALSE;
- CCrystalTextBuffer::m_bUndoBeginGroup = m_bUndoBeginGroup = FALSE;
-}
-
-/**
- * @brief Initialize a new buffer.
- * @param [in] nCrlfStyle EOL style for the buffer.
- * @return TRUE if the initialization succeeded.
- */
-BOOL CGhostTextBuffer::InitNew (CRLFSTYLE nCrlfStyle /*= CRLF_STYLE_DOS*/ )
-{
- m_bUndoBeginGroup = FALSE;
- return CCrystalTextBuffer::InitNew(nCrlfStyle);
}
+#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.
+ * @return true if the insertion succeeded, false otherwise.
*/
-BOOL CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource,
+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_ptEnd.y = nLine + 1;
CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
- if (pSource != NULL)
+ 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 */
/**
- * @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.
- * @return TRUE if the deletion succeeded, FALSE otherwise.
+ * @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,
+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 ());
+ ASSERT (nCount >= 0);
+ ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
- if (m_bReadOnly)
- return FALSE;
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;
+ return true;
for (int i = nLine ; i < nLine + nCount; i++)
{
- ASSERT (GetLineFlags(i) & LF_GHOST);
- delete[] m_aLines[i].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 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);
}
- 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, CRLFSTYLE 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;
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);
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 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;
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 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;
{
// 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.clear();
- }
- 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.size ())
- 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.size ());
- if (m_nUndoPosition > 0)
- {
- pSource->OnEditOperation (m_aUndoBuf[m_nUndoPosition - 1].m_nAction,
- m_aUndoBuf[m_nUndoPosition - 1].GetText ());
- }
- }
- m_bUndoGroup = FALSE;
-}
-
-
-/** 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)
-{
- // Forgot to call BeginUndoGroup()?
- ASSERT (m_bUndoGroup);
- ASSERT (m_aUndoBuf.size () == 0 || (m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
-
- // Strip unnecessary undo records (edit after undo wipes all potential redo records)
- int nBufSize = (int) m_aUndoBuf.size ();
- if (m_nUndoPosition < nBufSize)
- {
- m_aUndoBuf.resize (m_nUndoPosition);
- }
-
- // 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;
-
- // Optimize memory allocation
- if (m_aUndoBuf.capacity() == m_aUndoBuf.size())
- {
- if (m_aUndoBuf.size() == 0)
- m_aUndoBuf.reserve(16);
- else if (m_aUndoBuf.size() < 1025)
- m_aUndoBuf.reserve(m_aUndoBuf.size() * 2);
- else
- m_aUndoBuf.reserve(m_aUndoBuf.size() + 1024);
- }
- m_aUndoBuf.push_back (ur);
- m_nUndoPosition = (int) m_aUndoBuf.size ();
-}
-
-
-
-
-////////////////////////////////////////////////////////////////////////////
// edition functions
/**
* @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.
+ * @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
* 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;
- if (bHistory)
+ bool bGroupFlag = false;
+ bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
+ bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
+
+ if (bFirstLineGhost && cchText > 0)
{
- if (!m_bUndoGroup)
+ CString text = GetStringEol(GetCRLFMode());
+ if (bHistory && !m_bUndoGroup)
+ {
+ BeginUndoGroup();
+ bGroupFlag = true;
+ }
+ 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]))
{
- BeginUndoGroup ();
- bGroupFlag = TRUE;
- }
+ 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);
+ }
}
- // 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))
{
- 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 ++;
m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
OnNotifyLineHasBeenEdited(i);
}
- if (bDiscrepancyInInsertedLines == 0)
+ if (!bDiscrepancyInInsertedLines)
{
m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
OnNotifyLineHasBeenEdited(i);
// 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;
}
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)
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;
}
-/**
- * @brief Remove text from the buffer.
- * @param [in] pSource View from which to remove the text.
- * @param [in] nLine Line number (apparent/screen) where the deletion starts.
- * @param [in] nPos Character position where the deletion starts.
- * @param [in] nEndLine Line number (apparent/screen) where the deletion ends.
- * @param [out] nEndChar Character position where the deletion ends.
- * @param [in] nAction Edit action.
- * @param [in] bHistory Save insertion for undo/redo?
- * @return TRUE if the deletion succeeded, FALSE otherwise.
- */
-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
{
- // If we want to add undo record, but haven't created undo group yet,
- // create new group for this action. It gets flushed at end of the
- // function.
- 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);
}
+ if ((GetLineFlags(nEndLine) & LF_GHOST) != 0)
+ {
+ for (int nLine = nEndLine + 1; nLine < GetLineCount(); ++nLine)
+ if ((GetLineFlags(nLine) & LF_GHOST) == 0)
+ {
+ paSavedRevisionNumbers->Add(GetLineFlags(nLine));
+ break;
+ }
+ }
+ return paSavedRevisionNumbers;
+}
- // 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++)
+void CGhostTextBuffer:: /* virtual override */
+RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
+{
+ for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
{
- DWORD dwLineFlag = GetLineFlags(nStartLine + i);
- if (!(dwLineFlag & LF_GHOST))
- (*paSavedRevisonNumbers)[j++] = m_aLines[nStartLine + i].m_dwRevisionNumber;
+ if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0)
+ {
+ m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
+ ++i;
+ }
}
- 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,
+}
+
+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))
{
- delete paSavedRevisonNumbers;
- return FALSE;
+ return false;
}
- 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
- {
- int bFlagException = (bFirstLineGhost == 0);
- if (bFlagException)
- SetLineFlag(nStartLine, LF_GHOST, FALSE, FALSE, FALSE);
- else
- SetLineFlag(nStartLine, LF_GHOST, TRUE, FALSE, FALSE);
- }
+ 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;
}
+#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.
+ * @return true if the addition succeeded, false otherwise.
*/
-BOOL CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
+bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
{
if (!InternalInsertGhostLine (pSource, nLine))
- return FALSE;
+ return false;
// Set WinMerge flags
- SetLineFlag (nLine, LF_GHOST, TRUE, FALSE, FALSE);
+ 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;
+ return true;
}
+#endif
/**
* @brief Remove all the ghost lines from the buffer.
void CGhostTextBuffer::RemoveAllGhostLines()
{
int nlines = GetLineCount();
- int newnl = 0;
- int ct;
- // Free the buffer of ghost lines
- for(ct = 0; ct < nlines; ct++)
+ int nFirstGhost = -1;
+ // Free the buffer of ghost lines,
+ // remember where the first ghost line occurs
+ for(int ct = 0; ct < nlines; ct++)
{
if (GetLineFlags(ct) & LF_GHOST)
- delete[] m_aLines[ct].m_pcLine;
+ {
+ 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.SetSize(newnl);
- RecomputeRealityMapping();
+ // Discard unused entries in one shot
+ m_aLines.resize(newnl);
+ RecomputeRealityMapping();
+ }
}
////////////////////////////////////////////////////////////////////////////
*/
int CGhostTextBuffer::ApparentLastRealLine() const
{
- const int size = m_RealityBlocks.size();
- if (size == 0)
+ if (m_RealityBlocks.size() == 0)
return -1;
const RealityBlock &block = m_RealityBlocks.back();
return block.nStartApparent + block.nCount - 1;
*/
int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
{
- const int size = m_RealityBlocks.size();
- if (size == 0)
- return 0;
-
- // after last apparent line ?
- ASSERT(nApparentLine < GetLineCount());
-
- // after last block ?
- const RealityBlock &maxblock = m_RealityBlocks.back();
- if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
- return maxblock.nStartReal + maxblock.nCount;
-
- // binary search to find correct (or nearest block)
- int blo = 0;
- int bhi = size - 1;
- 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);
}
/**
*/
int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
{
- const int size = m_RealityBlocks.size();
+ const int size = static_cast<int>(m_RealityBlocks.size());
if (size == 0)
return 0;
// binary search to find correct (or nearest block)
int blo = 0;
int bhi = size - 1;
- int i;
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;
return (nRealLine - block.nStartReal) + block.nStartApparent;
}
// Should have found it; all real lines should be in a block
- ASSERT(0);
+ ASSERT(false);
return -1;
}
* @brief Get a real line for apparent (screen) line.
* This function returns the real line for the given apparent (screen) line.
* For ghost lines we return next real line. For trailing ghost line we return
- * last real line + 1). Ie, lines 0->0, 1->2, 2->4, for argument of 3,
- * return 2. And decToReal would be 1.
+ * last real line + 1; i.e. lines 0->0, 1->2, 2->4, for argument of 3,
+ * return 2 and decToReal would be 1.
* @param [in] nApparentLine Apparent line for which to get the real line.
* @param [out] decToReal Difference of the apparent and real line.
* @return The real line for the apparent line.
int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine,
int& decToReal) const
{
- const int size = m_RealityBlocks.size();
+ const int size = static_cast<int>(m_RealityBlocks.size());
if (size == 0)
{
decToReal = 0;
// binary search to find correct (or nearest block)
int blo = 0;
int bhi = size - 1;
- int i;
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;
/**
* @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.
*/
int nApparent;
const int size = (int) m_RealityBlocks.size();
+ int blo = 0;
+ int bhi = size - 1;
+ int i;
if (size == 0)
return 0;
if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
{
nPreviousBlock = size - 1;
- nApparent = GetLineCount();
+ nApparent = GetLineCount() - 1;
goto limitWithPreviousBlock;
}
// binary search to find correct (or nearest block)
- int blo = 0;
- int bhi = size - 1;
- int i;
while (blo <= bhi)
{
i = (blo + bhi) / 2;
}
}
// Should have found it; all real lines should be in a block
- ASSERT(0);
+ ASSERT(false);
return -1;
limitWithPreviousBlock:
m_RealityBlocks.clear();
int reality = -1; // last encountered real line
int i = 0; // current line
+ int nLineCount = GetLineCount();
RealityBlock block; // current block being traversed (in state 2)
// This is a state machine with 2 states
// state 1, i-1 not real line
passingGhosts:
- if (i == GetLineCount())
+ ASSERT( i <= nLineCount );
+ if (i == nLineCount)
+ {
+ checkFlagsFromReality();
return;
+ }
if (GetLineFlags(i) & LF_GHOST)
{
++i;
// this is the first line of a reality block
block.nStartApparent = i;
block.nStartReal = reality + 1;
+ block.nCount = -1;
++reality;
++i;
// fall through to other state
// state 2, i - 1 is real line
inReality:
- if (i == GetLineCount() || (GetLineFlags(i) & LF_GHOST))
+ ASSERT( i <= nLineCount );
+ if (i == nLineCount || (GetLineFlags(i) & LF_GHOST))
{
// i-1 is the last line of a reality block
ASSERT(reality >= 0);
if (m_RealityBlocks.size() == 0)
m_RealityBlocks.reserve(16);
else
+ // TODO: grow more slowly with really large RealityBlocks
m_RealityBlocks.reserve(m_RealityBlocks.size() * 2);
}
m_RealityBlocks.push_back(block);
- if (i == GetLineCount())
+ if (i == nLineCount)
+ {
+ checkFlagsFromReality();
return;
+ }
++i;
goto passingGhosts;
}
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
{
- const int size = m_RealityBlocks.size();
+#ifdef _DEBUG
+ const int size = static_cast<int>(m_RealityBlocks.size());
int i = 0;
for (int b = 0 ; b < size ; b ++)
{
for ( ; i < GetLineCount() ; i++)
ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
+#endif
}
-void CGhostTextBuffer::OnNotifyLineHasBeenEdited(int nLine)
+void CGhostTextBuffer:: /* virtual base */
+OnNotifyLineHasBeenEdited(int nLine)
{
return;
}
+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;
+}