// 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.
+ */
-
-#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)
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;
-
- 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);
+ return true;
+}
+#endif
- OnNotifyLineHasBeenEdited(nLine);
+/** InternalDeleteGhostLine accepts only apparent line numbers */
+/**
+ * @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 (nCount >= 0);
+ ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
- return TRUE;
-}
+ if (nCount == 0)
+ return true;
+ for (int i = nLine ; i < nLine + nCount; i++)
+ {
+ ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 );
+ m_aLines[i].Clear();
+ }
+ vector<LineInfo>::iterator iterBegin = m_aLines.begin() + nLine;
+ vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
+ m_aLines.erase(iterBegin, iterEnd);
+ if (pSource != nullptr)
+ {
+ 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())
+ nLine--;
+ // The last parameter is optimization
+ // - don't recompute lines preceding the removed line.
+ UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
+ nLine);
+ }
+
+ return true;
+}
/**
* @brief Get text of specified lines (ghost lines will not contribute to text).
*
* @param nCrlfStyle determines the EOL type in the returned buffer.
- * If nCrlfStyle equals CRLF_STYLE_AUTOMATIC, we read the EOL from the line buffer
+ * If nCrlfStyle equals CRLFSTYLE::AUTOMATIC, we read the EOL from the line buffer
*
* @note This function has its base in CrystalTextBuffer
* CrystalTextBuffer::GetTextWithoutEmptys() is for a buffer with no ghost lines.
* CrystalTextBuffer::GetText() returns text including ghost lines.
* These two base functions never read the EOL from the line buffer, they
- * use CRLF_STYLE_DOS when nCrlfStyle equals CRLF_STYLE_AUTOMATIC.
+ * use CRLFSTYLE::DOS when nCrlfStyle equals CRLFSTYLE::AUTOMATIC.
*/
-UINT 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 = 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);
+ 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);
// estimate size (upper bound)
int nBufSize = 0;
- for (int 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());
+ CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
pszBuf += sEol.GetLength();
}
}
}
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();
}
}
}
- pszBuf[0] = 0;
- text.ReleaseBuffer();
+ text.ReleaseBuffer(static_cast<int>(pszBuf - text));
text.FreeExtra();
- return text.GetLength();
}
////////////////////////////////////////////////////////////////////////////
-// undo/redo functions
-
-
-
-void CGhostTextBuffer::SUndoRecord::
-SetText (LPCTSTR pszText)
-{
- FreeText();
- if (pszText != NULL && pszText[0] != _T ('\0'))
- {
- int nLength = _tcslen (pszText);
- if (nLength > 1)
- {
- m_pszText = new TCHAR[(nLength + 1) * sizeof (TCHAR)];
- _tcscpy (m_pszText, pszText);
- }
- else
- {
- m_szText[0] = pszText[0];
- }
- }
-}
-
-void CGhostTextBuffer::SUndoRecord::
-FreeText ()
-{
- // see the m_szText/m_pszText definition about the use of HIWORD
- if (HIWORD ((DWORD) m_pszText) != 0)
- delete[] m_pszText;
- m_pszText = NULL;
-}
-
-
+// edition functions
-BOOL CGhostTextBuffer::
-Undo (CCrystalTextView * pSource, CPoint & ptCursorPos)
+/**
+ * @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*/)
{
- ASSERT (CanUndo ());
- ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
- BOOL failed = FALSE;
- int tmpPos = m_nUndoPosition;
+ bool bGroupFlag = false;
+ bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
+ bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
- while (!failed)
+ if (bFirstLineGhost && cchText > 0)
{
- 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_redo_ptEndPos.y, ur.m_redo_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)
+ CString text = GetStringEol(GetCRLFMode());
+ if (bHistory && !m_bUndoGroup)
{
- // 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);
-
- 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 (_tcscmp(text, ur.GetText()) == 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);
-
- // set WinMerge flags for first line
- if (ur.m_dwFlags & UNDO_VALID_FIRST)
- SetLineFlag(apparent_ptStartPos.y, LF_GHOST, FALSE, FALSE, FALSE);
- else
- SetLineFlag(apparent_ptStartPos.y, LF_GHOST, TRUE, FALSE, FALSE);
-
- // 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 (), 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);
-
- // set WinMerge flags for first line
- if (apparent_ptStartPos.y < nEndLine)
- SetLineFlag (apparent_ptStartPos.y, LF_GHOST, FALSE, FALSE, FALSE);
- // set WinMerge flags for last line
- if (bDiscrepancyInInsertedLines == 0)
- // if there is no discrepancy, the cursor line after undo is the same
- // as the last line of the deleted selection
- // because of exception (see deleteText), maybe this line was a ghost one
- // we use UNDO_VALID_LAST to know its original status
- if (ur.m_dwFlags & UNDO_VALID_LAST)
- SetLineFlag(m_ptLastChange.y, LF_GHOST, FALSE, FALSE, FALSE);
- else
- SetLineFlag(m_ptLastChange.y, LF_GHOST, TRUE, FALSE, FALSE);
- else
- // if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
- ;
-
- // it is not easy to know when Recompute so we do it always
- RecomputeRealityMapping();
-
- RecomputeEOL (pSource, apparent_ptStartPos.y, nEndLine);
+ BeginUndoGroup();
+ bGroupFlag = true;
}
-
- // 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
+ 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]))
{
- ur.m_redo_ptEndPos.x = m_ptLastChange.x;
- ur.m_redo_ptEndPos.y = ComputeRealLineAndGhostAdjustment (m_ptLastChange.y, ur.m_redo_ptEndPos_nGhost);
+ 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);
}
-
-
- 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
+
+ if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText,
+ cchText, nEndLine, nEndChar, nAction, bHistory))
{
- m_nUndoPosition = tmpPos;
+ return false;
}
- 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);
+ // when inserting an EOL terminated text into a ghost line,
+ // there is a discrepancy between nInsertedLines and nEndLine-nRealLine
+ bool bDiscrepancyInInsertedLines;
+ if (bFirstLineGhost && nEndChar == 0 && ApparentLastRealLine() >= nEndLine)
+ bDiscrepancyInInsertedLines = true;
+ else
+ bDiscrepancyInInsertedLines = false;
- while(1)
+ if (bSpecialLastLineHandling)
{
- 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(), nEndLine, nEndChar, 0, FALSE));
- ptCursorPos = m_ptLastChange;
- }
+ // 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
- {
-#ifdef _ADVANCED_BUGCHECK
- CString text;
- GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text);
- ASSERT (lstrcmp (text, ur.GetText ()) == 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;
+ bDiscrepancyInInsertedLines = false;
}
- if (m_bModified && m_nSyncPosition == m_nUndoPosition)
- SetModified (FALSE);
- if (!m_bModified && m_nSyncPosition != m_nUndoPosition)
- SetModified (TRUE);
- return TRUE;
-}
-
+ // compute the number of real lines created (for undo)
+ int nRealLinesCreated = nEndLine - nLine;
+ if (bFirstLineGhost && (nEndChar > 0 || ApparentLastRealLine() < nEndLine))
+ // we create one more real line
+ nRealLinesCreated ++;
-/**
-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)
+ int i;
+ for (i = nLine ; i < nEndLine ; i++)
{
- 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 ());
- }
+ // update line revision numbers of modified lines
+ m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
+ OnNotifyLineHasBeenEdited(i);
}
- 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 flags, int nActionType)
-{
- // Forgot to call BeginUndoGroup()?
- ASSERT (m_bUndoGroup);
- ASSERT (m_aUndoBuf.GetSize () == 0 || (m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
-
- // Strip unnecessary undo records (edit after undo wipes all potential redo records)
- int nBufSize = m_aUndoBuf.GetSize ();
- if (m_nUndoPosition < nBufSize)
+ if (!bDiscrepancyInInsertedLines)
{
- m_aUndoBuf.SetSize (m_nUndoPosition);
+ m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
+ OnNotifyLineHasBeenEdited(i);
}
- // If undo buffer size is close to critical, remove the oldest records
- ASSERT (m_aUndoBuf.GetSize () <= m_nUndoBufSize);
- nBufSize = m_aUndoBuf.GetSize ();
- if (nBufSize >= m_nUndoBufSize)
+ // when inserting into a ghost line block, we want to replace ghost lines
+ // with our text, so delete some ghost lines below the inserted text
+ if (bFirstLineGhost)
{
- int nIndex = 0;
- for (;;)
- {
- nIndex++;
- if (nIndex == nBufSize || (m_aUndoBuf[nIndex].m_dwFlags & UNDO_BEGINGROUP) != 0)
+ // where is the first line after the inserted text ?
+ int nInsertedTextLinesCount = nEndLine - nLine + (bDiscrepancyInInsertedLines ? 0 : 1);
+ int nLineAfterInsertedBlock = nLine + nInsertedTextLinesCount;
+ // 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;
- }
- 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)
- {
- m_nSyncPosition -= nIndex; // çà c'est bien...mais non, test inutile ? Ou Apres !
- }
- if (nIndex < nBufSize)
- {
- // 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
- }
- 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_dwFlags |= flags;
- ur.m_nAction = nActionType;
- if (m_bUndoBeginGroup)
- {
- ur.m_dwFlags |= UNDO_BEGINGROUP;
- m_bUndoBeginGroup = FALSE;
+ InternalDeleteGhostLine(pSource, nLineAfterInsertedBlock, i);
}
- 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);
- ur.SetText (pszText);
-
- m_aUndoBuf.Add (ur);
- m_nUndoPosition = 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 &nEndLine, int &nEndChar, int nAction, BOOL bHistory /*=TRUE*/)
-{
- BOOL bGroupFlag = FALSE;
- if (bHistory)
- {
- if (!m_bUndoGroup)
- {
- BeginUndoGroup ();
- bGroupFlag = TRUE;
- }
- }
-
- if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText, nEndLine, nEndChar, nAction, bHistory))
- 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;
- else
- bDiscrepancyInInsertedLines = FALSE;
-
- int i;
- for (i = nLine ; i < nEndLine ; i++)
- OnNotifyLineHasBeenEdited(i);
- if (bDiscrepancyInInsertedLines == 0)
- OnNotifyLineHasBeenEdited(i);
-
- int bCursorLineAfterUndoIsGhost;
- if (bDiscrepancyInInsertedLines)
- // if there is a discrepancy, the final cursor line didn't change during insertion
- // so we shall preserve the status of this line during undo
- // (this line will be the cursor line after undo)
- bCursorLineAfterUndoIsGhost = ((GetLineFlags(nEndLine) & LF_GHOST) != 0);
- else
- // no discrepancy, the cursor line after undo must be the same as
- // the insertion point line
- bCursorLineAfterUndoIsGhost = bFirstLineGhost;
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)
- 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, bCursorLineAfterUndoIsGhost ? 0 : UNDO_VALID_FIRST, nAction);
-
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);
}
+ 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;
+}
+void CGhostTextBuffer:: /* virtual override */
+RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
+{
+ for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
+ {
+ if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0)
+ {
+ m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
+ ++i;
+ }
+ }
+}
- // 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);
-
- CString sTextToDelete;
- GetTextWithoutEmptys (nStartLine, nStartChar, nEndLine, nEndChar, sTextToDelete);
- if (!CCrystalTextBuffer::DeleteText (pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory))
- return FALSE;
-
- OnNotifyLineHasBeenEdited(nStartLine);
+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))
+ {
+ return false;
+ }
- // 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)
- int bFlagException;
- if (bLastLineGhost)
- bFlagException = (bFirstLineGhost == 0);
- if (bLastLineGhost == 0 || bFlagException)
- SetLineFlag(nStartLine, LF_GHOST, FALSE, FALSE, FALSE);
- else
- SetLineFlag(nStartLine, LF_GHOST, TRUE, FALSE, 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)
- 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, bLastLineGhost ? 0 : UNDO_VALID_LAST, nAction);
-
- 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()
{
- for(int ct=GetLineCount()-1; ct>=0; --ct)
+ int nlines = GetLineCount();
+ 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)
- DeleteLine(ct);
+ {
+ 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 = 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 = 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 = 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 = 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;
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;
}
// 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;
}
/**
-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 = 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)
}
}
// Should have found it; all real lines should be in a block
- ASSERT(0);
+ ASSERT(false);
return -1;
limitWithPreviousBlock:
{
nApparent --;
if (nApparent == lastApparentInPreviousBlock)
- return nApparent+1;
+ return nApparent + 1;
}
return nApparent;
}
-
/** Do what we need to do just after we've been reloaded */
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;
}
// 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;
}
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(), _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 = 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++)
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;
+}