X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=Src%2FGhostTextBuffer.cpp;h=99806a358c28ab930a1d9b872cc55366ff0bba21;hb=5a478c4115f3ffeb35fb51df893912409c793bb0;hp=b5c19ad7501e5128fd58c24e59fa54635aa1a7ae;hpb=657a2e64a4a958990e3206fe1aeae5d1d4b64128;p=winmerge-jp%2Fwinmerge-jp.git diff --git a/Src/GhostTextBuffer.cpp b/Src/GhostTextBuffer.cpp index b5c19ad75..99806a358 100644 --- a/Src/GhostTextBuffer.cpp +++ b/Src/GhostTextBuffer.cpp @@ -7,42 +7,20 @@ // 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: GhostTextBuffer.cpp 6878 2009-06-29 09:28:13Z kimmov $ #include "StdAfx.h" -#include #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; @@ -59,36 +37,22 @@ IMPLEMENT_DYNCREATE (CGhostTextBuffer, CCrystalTextBuffer) */ 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.size ()); - if (m_bReadOnly) - return FALSE; + ASSERT (nLine >= 0 && nLine <= static_cast(m_aLines.size ())); CInsertContext context; context.m_ptStart.x = 0; @@ -97,45 +61,36 @@ BOOL CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource, context.m_ptEnd.y = nLine + 1; CCrystalTextBuffer::InsertLine (_T(""), 0, nLine); - if (pSource != NULL) + if (pSource != nullptr) UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine); - 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.size ()); + ASSERT (nCount >= 0); + ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast(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); + ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 ); m_aLines[i].Clear(); } @@ -143,49 +98,47 @@ BOOL CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource, vector::iterator iterEnd = iterBegin + nCount; m_aLines.erase(iterBegin, iterEnd); - if (pSource != NULL) + if (pSource != nullptr) { - // The last parameter is optimization - don't recompute lines preceeding - // the removed line. + CDeleteContext context; + context.m_ptStart.y = nLine; + context.m_ptStart.x = 0; + context.m_ptEnd.y = nLine + nCount; + context.m_ptEnd.x = 0; + if (nLine == GetLineCount()) - { - UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, - GetLineCount() - 1); - } - else - { - UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, - nLine); - } + nLine--; + // The last parameter is optimization + // - don't recompute lines preceding the removed line. + UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, + nLine); } - 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 */, - BOOL bExcludeInvisibleLines/*=TRUE*/) + CString &text, CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC */, + bool bExcludeInvisibleLines /*= true*/) const { const size_t lines = m_aLines.size(); - ASSERT(nStartLine >= 0 && nStartLine < lines); + ASSERT(nStartLine >= 0 && nStartLine < static_cast(lines)); ASSERT(nStartChar >= 0 && nStartChar <= GetLineLength(nStartLine)); - ASSERT(nEndLine >= 0 && nEndLine < lines); + ASSERT(nEndLine >= 0 && nEndLine < static_cast(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. @@ -199,7 +152,7 @@ void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar, nBufSize += (GetFullLineLength(i) + 2); // in case we insert EOLs LPTSTR pszBuf = text.GetBuffer(nBufSize); - if (nCrlfStyle != CRLF_STYLE_AUTOMATIC) + if (nCrlfStyle != CRLFSTYLE::AUTOMATIC) { // we must copy this EOL type only const CString sEol = GetStringEol (nCrlfStyle); @@ -247,356 +200,18 @@ void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar, { // Oops, real line lacks EOL // (If this happens, editor probably has bug) - ASSERT(0); + ASSERT(false); CString sEol = GetStringEol (nCrlfStyle); - CopyMemory(pszBuf, sEol, sEol.GetLength()); + CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR)); pszBuf += sEol.GetLength(); } } } - text.ReleaseBuffer(pszBuf - text); + text.ReleaseBuffer(static_cast(pszBuf - text)); text.FreeExtra(); } //////////////////////////////////////////////////////////////////////////// -// undo/redo functions - -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; - GhostUndoRecord 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.size() || (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.size() || (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); - - const size_t size = m_aLines.size(); - if ((apparent_ptStartPos.y < size) && - (apparent_ptStartPos.x <= m_aLines[apparent_ptStartPos.y].Length()) && - (apparent_ptEndPos.y < size) && - (apparent_ptEndPos.x <= m_aLines[apparent_ptEndPos.y].Length())) - { - GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text, CRLF_STYLE_AUTOMATIC, FALSE); - 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, 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 && ApparentLastRealLine() >= nEndLine) - 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++) - { - if (i < nEndLine) - SetLineFlag (i, LF_GHOST, TRUE, FALSE, FALSE); - else if (apparent_ptStartPos.x != 0 || nEndChar != 0) - 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 naSavedRevisonNumbersSize = (int) ur.m_paSavedRevisonNumbers->GetSize(); - for (int 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) - { - GhostUndoRecord 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.size() || (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.size() || (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, CRLF_STYLE_AUTOMATIC, FALSE); - 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, 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_aUndoBuf[m_nUndoPosition - 1].GetTextLength ()); - } - } - 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 - GhostUndoRecord 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 /** @@ -605,12 +220,13 @@ void CGhostTextBuffer::AddUndoRecord (BOOL bInsert, const CPoint & ptStartPos, * @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 @@ -618,42 +234,71 @@ void CGhostTextBuffer::AddUndoRecord (BOOL bInsert, const CPoint & ptStartPos, * 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; - } + 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])) + { + 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; + // there is a discrepancy between nInsertedLines and nEndLine-nRealLine + bool bDiscrepancyInInsertedLines; if (bFirstLineGhost && nEndChar == 0 && ApparentLastRealLine() >= nEndLine) - bDiscrepancyInInsertedLines = TRUE; + 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; @@ -668,7 +313,7 @@ BOOL CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine, m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber; OnNotifyLineHasBeenEdited(i); } - if (bDiscrepancyInInsertedLines == 0) + if (!bDiscrepancyInInsertedLines) { m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber; OnNotifyLineHasBeenEdited(i); @@ -693,14 +338,23 @@ BOOL CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine, } 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) @@ -709,150 +363,64 @@ BOOL CGhostTextBuffer::InsertText (CCrystalTextView * pSource, int nLine, 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*/, BOOL bExcludeInvisibleLines /*=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 (bExcludeInvisibleLines && pSource && pSource->GetEnableHideLines ()) + if ((GetLineFlags(nEndLine) & LF_GHOST) != 0) { - for (int nLineIndex = nEndLine; nLineIndex >= nStartLine; nLineIndex--) - { - if (!(GetLineFlags (nLineIndex) & LF_INVISIBLE)) + for (int nLine = nEndLine + 1; nLine < GetLineCount(); ++nLine) + if ((GetLineFlags(nLine) & LF_GHOST) == 0) { - int nEndLine2 = nLineIndex; - int nStartLine2; - for (nStartLine2 = nLineIndex - 1; nStartLine2 >= nStartLine; nStartLine2--) - { - if (GetLineFlags (nStartLine2) & LF_INVISIBLE) - break; - } - nStartLine2++; - nLineIndex = nStartLine2; - int nStartChar2 = (nStartLine == nStartLine2) ? nStartChar : 0; - int nEndChar2; - if (nEndLine == nEndLine2) - nEndChar2 = nEndChar; - else - { - nEndChar2 = 0; - nEndLine2++; - } - if (!CGhostTextBuffer::DeleteText2 (pSource, nStartLine2, nStartChar2, nEndLine2, nEndChar2, nAction, bHistory)) - return FALSE; + paSavedRevisionNumbers->Add(GetLineFlags(nLine)); + break; } - } } - else + return paSavedRevisionNumbers; +} + +void CGhostTextBuffer:: /* virtual override */ +RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers) +{ + for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++) { - if (!CGhostTextBuffer::DeleteText2 (pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory)) - return FALSE; + if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0) + { + m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i]; + ++i; + } } - - if (bGroupFlag) - FlushUndoGroup (pSource); - - return TRUE; } -BOOL CGhostTextBuffer:: +bool CGhostTextBuffer:: /* virtual override */ DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar, - int nEndLine, int nEndChar, int nAction, BOOL bHistory /*=TRUE*/) + int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/) { - // 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++) - { - DWORD dwLineFlag = GetLineFlags(nStartLine + i); - if (!(dwLineFlag & LF_GHOST)) - (*paSavedRevisonNumbers)[j++] = m_aLines[nStartLine + i].m_dwRevisionNumber; - } - 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, + 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; } if (nStartChar != 0 || nEndChar != 0) 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); -// } // now we can recompute if (nStartLine != nEndLine) @@ -860,43 +428,31 @@ DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar, // 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); - - 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. @@ -904,25 +460,33 @@ BOOL CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine) void CGhostTextBuffer::RemoveAllGhostLines() { int nlines = GetLineCount(); - int newnl = 0; - int ct; - // Free the buffer of ghost lines - for(ct = 0; ct < nlines; ct++) + int nFirstGhost = -1; + // Free the buffer of ghost lines, + // remember where the first ghost line occurs + for(int ct = 0; ct < nlines; ct++) { if (GetLineFlags(ct) & LF_GHOST) + { m_aLines[ct].FreeBuffer(); + if (nFirstGhost < 0) + nFirstGhost = ct; + } } - // Compact non-ghost lines - // (we copy the buffer address, so the buffer don't move and we don't free it) - for(ct = 0; ct < nlines; ct++) + if (nFirstGhost >= 0) { - if ((GetLineFlags(ct) & LF_GHOST) == 0) - m_aLines[newnl++] = m_aLines[ct]; - } + // Compact non-ghost lines, starting at the first ghost. + // (we copy the buffer address, so the buffer doesn't move and we don't free it) + int newnl = nFirstGhost; + for (int ct = nFirstGhost; ct < nlines; ct++) + { + if ((GetLineFlags(ct) & LF_GHOST) == 0) + m_aLines[newnl++] = m_aLines[ct]; + } - // Discard unused entries in one shot - m_aLines.resize(newnl); - RecomputeRealityMapping(); + // Discard unused entries in one shot + m_aLines.resize(newnl); + RecomputeRealityMapping(); + } } //////////////////////////////////////////////////////////////////////////// @@ -934,8 +498,7 @@ void CGhostTextBuffer::RemoveAllGhostLines() */ 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; @@ -952,35 +515,8 @@ int CGhostTextBuffer::ApparentLastRealLine() const */ 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); } /** @@ -991,7 +527,7 @@ int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const */ int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const { - const int size = m_RealityBlocks.size(); + const int size = static_cast(m_RealityBlocks.size()); if (size == 0) return 0; @@ -1003,10 +539,9 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const // 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; @@ -1016,7 +551,7 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const return (nRealLine - block.nStartReal) + block.nStartApparent; } // Should have found it; all real lines should be in a block - ASSERT(0); + ASSERT(false); return -1; } @@ -1024,8 +559,8 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const * @brief Get a real line for apparent (screen) line. * This function returns the real line for the given apparent (screen) line. * For ghost lines we return next real line. For trailing ghost line we return - * last real line + 1). Ie, lines 0->0, 1->2, 2->4, for argument of 3, - * return 2. And decToReal would be 1. + * last real line + 1; i.e. lines 0->0, 1->2, 2->4, for argument of 3, + * return 2 and decToReal would be 1. * @param [in] nApparentLine Apparent line for which to get the real line. * @param [out] decToReal Difference of the apparent and real line. * @return The real line for the apparent line. @@ -1033,7 +568,7 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, int& decToReal) const { - const int size = m_RealityBlocks.size(); + const int size = static_cast(m_RealityBlocks.size()); if (size == 0) { decToReal = 0; @@ -1054,10 +589,9 @@ int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, // 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; @@ -1077,7 +611,7 @@ int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, /** * @brief Get an apparent (screen) line for the real line. * @param [in] nRealLine Real line for which to get the apparent line. - * @param [out] decToReal Difference of the apparent and real line. + * @param [in] decToReal Difference of the apparent and real line. * @return The apparent line for the real line. If real line is out of bounds * return last valid apparent line + 1. */ @@ -1098,7 +632,7 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const if (nRealLine >= maxblock.nStartReal + maxblock.nCount) { nPreviousBlock = size - 1; - nApparent = GetLineCount(); + nApparent = GetLineCount() - 1; goto limitWithPreviousBlock; } @@ -1122,7 +656,7 @@ int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const } } // Should have found it; all real lines should be in a block - ASSERT(0); + ASSERT(false); return -1; limitWithPreviousBlock: @@ -1158,14 +692,19 @@ void CGhostTextBuffer::RecomputeRealityMapping() m_RealityBlocks.clear(); int reality = -1; // last encountered real line int i = 0; // current line + int nLineCount = GetLineCount(); RealityBlock block; // current block being traversed (in state 2) // This is a state machine with 2 states // state 1, i-1 not real line passingGhosts: - if (i == GetLineCount()) + ASSERT( i <= nLineCount ); + if (i == nLineCount) + { + checkFlagsFromReality(); return; + } if (GetLineFlags(i) & LF_GHOST) { ++i; @@ -1174,13 +713,15 @@ passingGhosts: // this is the first line of a reality block block.nStartApparent = i; block.nStartReal = reality + 1; + block.nCount = -1; ++reality; ++i; // fall through to other state // state 2, i - 1 is real line inReality: - if (i == GetLineCount() || (GetLineFlags(i) & LF_GHOST)) + ASSERT( i <= nLineCount ); + if (i == nLineCount || (GetLineFlags(i) & LF_GHOST)) { // i-1 is the last line of a reality block ASSERT(reality >= 0); @@ -1194,11 +735,15 @@ inReality: if (m_RealityBlocks.size() == 0) m_RealityBlocks.reserve(16); else + // TODO: grow more slowly with really large RealityBlocks m_RealityBlocks.reserve(m_RealityBlocks.size() * 2); } m_RealityBlocks.push_back(block); - if (i == GetLineCount()) + if (i == nLineCount) + { + checkFlagsFromReality(); return; + } ++i; goto passingGhosts; } @@ -1207,66 +752,14 @@ inReality: goto inReality; } -/** we recompute EOL from the real line before nStartLine to nEndLine */ -void CGhostTextBuffer::RecomputeEOL(CCrystalTextView * pSource, int nStartLine, int nEndLine) -{ - if (ApparentLastRealLine() <= nEndLine) - { - // EOL may have to change on the real line before nStartLine - int nRealBeforeStart; - for (nRealBeforeStart = nStartLine-1 ; nRealBeforeStart >= 0 ; nRealBeforeStart--) - if ((GetLineFlags(nRealBeforeStart) & LF_GHOST) == 0) - break; - if (nRealBeforeStart >= 0) - nStartLine = nRealBeforeStart; - } - int bLastRealLine = (ApparentLastRealLine() <= nEndLine); - for (int i = nEndLine ; i >= nStartLine ; i --) - { - if ((GetLineFlags(i) & LF_GHOST) == 0) - { - if (bLastRealLine) - { - bLastRealLine = 0; - if (m_aLines[i].HasEol()) - { - // if the last real line has an EOL, remove it - m_aLines[i].RemoveEol(); - if (pSource != NULL) - UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i); - } - } - else - { - if (!m_aLines[i].HasEol()) - { - // 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].HasEol()) - { - // if a ghost line has an EOL, remove it - m_aLines[i].RemoveEol(); - 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(m_RealityBlocks.size()); int i = 0; for (int b = 0 ; b < size ; b ++) { @@ -1279,11 +772,139 @@ void CGhostTextBuffer::checkFlagsFromReality(BOOL bFlag) const for ( ; i < GetLineCount() ; i++) ASSERT ((GetLineFlags(i) & LF_GHOST) != 0); +#endif } -void CGhostTextBuffer::OnNotifyLineHasBeenEdited(int nLine) +void CGhostTextBuffer:: /* virtual base */ +OnNotifyLineHasBeenEdited(int nLine) { return; } +void CGhostTextBuffer:: +CountEolAndLastLineLength(const CPoint& ptStartPos, LPCTSTR pszText, size_t cchText, int &nLastLineLength, int &nEol) +{ + nLastLineLength = 0; + nEol = 0; + if (m_bTableEditing && m_bAllowNewlinesInQuotes) + { + bool bInQuote = false; + const TCHAR* pszLine = m_aLines[ptStartPos.y].GetLine(); + for (int j = 0; j < ptStartPos.x; ++j) + { + if (pszLine[j] == m_cFieldEnclosure) + bInQuote = !bInQuote; + } + for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos) + { + if (pszText[nTextPos] == m_cFieldEnclosure) + bInQuote = !bInQuote; + if (!bInQuote && LineInfo::IsEol(pszText[nTextPos])) + { + if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos])) + ++nTextPos; + ++nEol; + nLastLineLength = 0; + } + else + ++nLastLineLength; + } + } + else + { + for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos) + { + if (LineInfo::IsEol(pszText[nTextPos])) + { + if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos])) + ++nTextPos; + ++nEol; + nLastLineLength = 0; + } + else + ++nLastLineLength; + } + } +} + +void CGhostTextBuffer:: /* virtual override */ +AddUndoRecord(bool bInsert, const CPoint & ptStartPos, + const CPoint & ptEndPos, LPCTSTR pszText, size_t cchText, + int nActionType /*= CE_ACTION_UNKNOWN*/, + CDWordArray *paSavedRevisionNumbers /*= nullptr*/) +{ + CPoint real_ptStartPos(ptStartPos.x, ComputeRealLine(ptStartPos.y)); + int nLastLineLength, nEol; + CountEolAndLastLineLength(ptStartPos, pszText, cchText, nLastLineLength, nEol); + CPoint real_ptEndPos(ptEndPos.x, real_ptStartPos.y + nEol); + if (ptEndPos.x == 0 && cchText > 0 && !LineInfo::IsEol(pszText[cchText - 1])) + real_ptEndPos.x = nLastLineLength; + CCrystalTextBuffer::AddUndoRecord(bInsert, real_ptStartPos, real_ptEndPos, pszText, + cchText, nActionType, paSavedRevisionNumbers); +} + +UndoRecord CGhostTextBuffer:: /* virtual override */ +GetUndoRecord(int nUndoPos) const +{ + UndoRecord ur = m_aUndoBuf[nUndoPos]; + ur.m_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, 0); + ur.m_ptEndPos.y = ComputeApparentLine(ur.m_ptEndPos.y, 0); + return ur; +} + +bool CGhostTextBuffer:: /* virtual override */ +UndoInsert(CCrystalTextView * pSource, CPoint & ptCursorPos, const CPoint apparent_ptStartPos, CPoint const apparent_ptEndPos, const UndoRecord & ur) +{ + // Check that text in the undo buffer matches text in file buffer. + // If not, then rescan() has moved lines and undo fails. + + // we need to put the cursor before the deleted section + CString text; + const size_t size = m_aLines.size(); + if ((apparent_ptStartPos.y < static_cast(size)) && + (apparent_ptStartPos.x <= static_cast(m_aLines[apparent_ptStartPos.y].Length())) && + (apparent_ptEndPos.y < static_cast(size)) && + (apparent_ptEndPos.x <= static_cast(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(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(size) && apparent_ptEndPos.x == 0) + { + CPoint apparentEnd2 = apparent_ptEndPos; + apparentEnd2.x = static_cast(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(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(ptCursorPos.y) == nLastLine) + ptCursorPos.y--; + } + return true; + } + } + } + } + ASSERT(false); + return false; +}