1 ////////////////////////////////////////////////////////////////////////////
2 // File: GhostTextBuffer.cpp
4 // Created: 31-Jul-2003
6 /////////////////////////////////////////////////////////////////////////////
7 // WinMerge: an interactive diff/merge utility
8 // Copyright (C) 1997-2000 Thingamahoochie Software
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 /////////////////////////////////////////////////////////////////////////////
27 * @file GhostTextBuffer.cpp
29 * @brief Implementation of GhostTextBuffer class.
31 // ID line follows -- this is updated by SVN
35 #include "GhostTextBuffer.h"
40 static char THIS_FILE[] = __FILE__;
44 #define _ADVANCED_BUGCHECK 1
47 BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
48 //{{AFX_MSG_MAP(CGhostTextBuffer)
52 IMPLEMENT_DYNCREATE (CGhostTextBuffer, CCrystalTextBuffer)
54 CGhostTextBuffer::CGhostTextBuffer()
57 CCrystalTextBuffer::m_bUndoBeginGroup = m_bUndoBeginGroup = FALSE;
60 BOOL CGhostTextBuffer::
61 InitNew (int nCrlfStyle /*= CRLF_STYLE_DOS*/ )
63 m_bUndoBeginGroup = FALSE;
64 return CCrystalTextBuffer::InitNew(nCrlfStyle);
68 /** InternalInsertGhostLine accepts only apparent line numbers */
69 BOOL CGhostTextBuffer::
70 InternalInsertGhostLine (CCrystalTextView * pSource, int nLine)
72 ASSERT (m_bInit); // Text buffer not yet initialized.
73 // You must call InitNew() or LoadFromFile() first!
75 ASSERT (nLine >= 0 && nLine <= m_aLines.GetSize ());
79 CInsertContext context;
80 context.m_ptStart.x = 0;
81 context.m_ptStart.y = nLine;
83 CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
85 context.m_ptEnd.x = 0;
86 context.m_ptEnd.y = nLine+1;
89 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
94 OnNotifyLineHasBeenEdited(nLine);
100 /** InternalDeleteGhostLine accepts only apparent line numbers */
101 BOOL CGhostTextBuffer::
102 InternalDeleteGhostLine (CCrystalTextView * pSource, int nLine, int nCount)
104 ASSERT (m_bInit); // Text buffer not yet initialized.
105 // You must call InitNew() or LoadFromFile() first!
107 ASSERT (nLine >= 0 && nLine <= m_aLines.GetSize ());
114 CDeleteContext context;
115 context.m_ptStart.y = nLine;
116 context.m_ptStart.x = 0;
117 context.m_ptEnd.y = nLine+nCount;
118 context.m_ptEnd.x = 0;
120 for (int L = nLine ; L < nLine+nCount; L++)
122 ASSERT (GetLineFlags(L) & LF_GHOST);
123 delete[] m_aLines[L].m_pcLine;
125 m_aLines.RemoveAt (nLine, nCount);
129 // the last parameter is just for speed : don't recompute lines before this one
130 // it must be a valid line number, so if we delete the last lines, we give the last of the remaining lines
131 if (nLine == GetLineCount())
132 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, GetLineCount()-1);
134 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
147 * @brief Get text of specified lines (ghost lines will not contribute to text).
149 * @param nCrlfStyle determines the EOL type in the returned buffer.
150 * If nCrlfStyle equals CRLF_STYLE_AUTOMATIC, we read the EOL from the line buffer
152 * @note This function has its base in CrystalTextBuffer
153 * CrystalTextBuffer::GetTextWithoutEmptys() is for a buffer with no ghost lines.
154 * CrystalTextBuffer::GetText() returns text including ghost lines.
155 * These two base functions never read the EOL from the line buffer, they
156 * use CRLF_STYLE_DOS when nCrlfStyle equals CRLF_STYLE_AUTOMATIC.
158 void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar,
159 int nEndLine, int nEndChar,
160 CString &text, int nCrlfStyle /* CRLF_STYLE_AUTOMATIC */)
162 int lines = (int) m_aLines.GetSize();
163 ASSERT(nStartLine >= 0 && nStartLine < lines);
164 ASSERT(nStartChar >= 0 && nStartChar <= GetLineLength(nStartLine));
165 ASSERT(nEndLine >= 0 && nEndLine < lines);
166 ASSERT(nEndChar >= 0 && nEndChar <= GetFullLineLength(nEndLine));
167 ASSERT(nStartLine < nEndLine || nStartLine == nEndLine && nStartChar <= nEndChar);
168 // some edit functions (copy...) should do nothing when there is no selection.
169 // assert to be sure to catch these 'do nothing' cases.
170 ASSERT(nStartLine != nEndLine || nStartChar != nEndChar);
172 // estimate size (upper bound)
175 for (i=nStartLine; i<=nEndLine; ++i)
176 nBufSize += (GetFullLineLength(i) + 2); // in case we insert EOLs
177 LPTSTR pszBuf = text.GetBuffer(nBufSize);
179 if (nCrlfStyle != CRLF_STYLE_AUTOMATIC)
181 // we must copy this EOL type only
182 CString sEol = GetStringEol (nCrlfStyle);
184 for (i=nStartLine; i<=nEndLine; ++i)
186 // exclude ghost lines
187 if (GetLineFlags(i) & LF_GHOST)
190 // copy the line, excluding the EOL
191 int soffset = (i==nStartLine ? nStartChar : 0);
192 int eoffset = (i==nEndLine ? nEndChar : GetLineLength(i));
193 int chars = eoffset - soffset;
194 LPCTSTR szLine = m_aLines[i].m_pcLine + soffset;
195 CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
198 // copy the EOL of the requested type
199 if (i!=ApparentLastRealLine())
201 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
202 pszBuf += sEol.GetLength();
208 for (i=nStartLine; i<=nEndLine; ++i)
210 // exclude ghost lines
211 if (GetLineFlags(i) & LF_GHOST)
214 // copy the line including the EOL
215 int soffset = (i==nStartLine ? nStartChar : 0);
216 int eoffset = (i==nEndLine ? nEndChar : GetFullLineLength(i));
217 int chars = eoffset - soffset;
218 LPCTSTR szLine = m_aLines[i].m_pcLine + soffset;
219 CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
222 // check that we really have an EOL
223 if (i!=ApparentLastRealLine() && GetLineLength(i)==GetFullLineLength(i))
225 // Oops, real line lacks EOL
226 // (If this happens, editor probably has bug)
228 CString sEol = GetStringEol (nCrlfStyle);
229 CopyMemory(pszBuf, sEol, sEol.GetLength());
230 pszBuf += sEol.GetLength();
234 text.ReleaseBuffer(pszBuf - text);
238 ////////////////////////////////////////////////////////////////////////////
239 // undo/redo functions
241 <void CGhostTextBuffer::SUndoRecord::
242 SetText (LPCTSTR pszText, int nLength)
249 m_pszText = (TextBuffer *)malloc(sizeof(TextBuffer) + nLength * sizeof(TCHAR));
250 m_pszText->size = nLength;
251 memcpy(m_pszText->data, pszText, nLength * sizeof(TCHAR));
252 m_pszText->data[nLength] = _T('?'); // debug sentinel
256 m_szText[0] = pszText[0];
261 void CGhostTextBuffer::SUndoRecord::
264 // See the m_szText/m_pszText definition
265 // Check if m_pszText is a pointer by removing bits having
266 // possible char value
267 if (((INT_PTR)m_pszText >> 16) != 0)
272 BOOL CGhostTextBuffer::
273 Undo (CCrystalTextView * pSource, CPoint & ptCursorPos)
276 ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
278 int tmpPos = m_nUndoPosition;
283 SUndoRecord ur = m_aUndoBuf[tmpPos];
284 // Undo records are stored in file line numbers
285 // and must be converted to apparent (screen) line numbers for use
286 CPoint apparent_ptStartPos = ur.m_ptStartPos;
287 apparent_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, ur.m_ptStartPos_nGhost);
288 CPoint apparent_ptEndPos = ur.m_ptEndPos;
289 apparent_ptEndPos.y = ComputeApparentLine(ur.m_ptEndPos.y, ur.m_ptEndPos_nGhost);
291 if (ur.m_ptStartPos_nGhost > 0)
292 // if we need a ghost line at position apparent_ptStartPos.y
293 if (apparent_ptStartPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptStartPos.y) & LF_GHOST) == 0)
295 // if we don't find it, we insert it
296 InsertGhostLine (pSource, apparent_ptStartPos.y);
297 // and recompute apparent_ptEndPos
298 apparent_ptEndPos.y = ComputeApparentLine (ur.m_ptEndPos.y, ur.m_ptEndPos_nGhost);
301 // EndPos defined only for UNDO_INSERT (when we delete)
302 if (ur.m_dwFlags & UNDO_INSERT && ur.m_ptEndPos_nGhost > 0)
303 // if we need a ghost line at position apparent_ptStartPos.y
304 if (apparent_ptEndPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptEndPos.y) & LF_GHOST) == 0)
306 // if we don't find it, we insert it
307 InsertGhostLine (pSource, apparent_ptEndPos.y);
310 if (ur.m_dwFlags & UNDO_INSERT)
312 // WINMERGE -- Check that text in undo buffer matches text in
313 // file buffer. If not, then rescan() has moved lines and undo
316 // we need to put the cursor before the deleted section
318 ur.m_redo_ptEndPos.x = apparent_ptEndPos.x;
319 ur.m_redo_ptEndPos.y = ComputeRealLineAndGhostAdjustment (apparent_ptEndPos.y, ur.m_redo_ptEndPos_nGhost);
321 // flags are going to be deleted so we store them now
322 int bLastLineGhost = ((GetLineFlags(apparent_ptEndPos.y) & LF_GHOST) != 0);
324 if ((apparent_ptStartPos.y < m_aLines.GetSize ()) &&
325 (apparent_ptStartPos.x <= m_aLines[apparent_ptStartPos.y].m_nLength) &&
326 (apparent_ptEndPos.y < m_aLines.GetSize ()) &&
327 (apparent_ptEndPos.x <= m_aLines[apparent_ptEndPos.y].m_nLength))
329 GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text);
330 if (text.GetLength() == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
332 VERIFY (CCrystalTextBuffer::DeleteText (pSource,
333 apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x,
335 ptCursorPos = apparent_ptStartPos;
339 //..Try to ensure that we are undoing correctly...
340 // Just compare the text as it was before Undo operation
341 #ifdef _ADVANCED_BUGCHECK
355 OnNotifyLineHasBeenEdited(apparent_ptStartPos.y);
357 // default : the remaining line inherits the status of the last line of the deleted block
358 SetLineFlag(apparent_ptStartPos.y, LF_GHOST, bLastLineGhost, FALSE, FALSE);
360 // the number of real lines must be the same before the action and after undo
361 int nNumberDeletedRealLines = ur.m_ptEndPos.y - ur.m_ptStartPos.y;
362 if (nNumberDeletedRealLines == ur.m_nRealLinesCreated)
364 else if (nNumberDeletedRealLines == ur.m_nRealLinesCreated-1)
365 // we inserted in a ghost line (which then became real), we must send it back to its world
366 SetLineFlag(apparent_ptStartPos.y, LF_GHOST, TRUE, FALSE, FALSE);
370 // it is not easy to know when Recompute so we do it always
371 RecomputeRealityMapping();
373 RecomputeEOL (pSource, apparent_ptStartPos.y, apparent_ptStartPos.y);
377 int nEndLine, nEndChar;
378 VERIFY(CCrystalTextBuffer::InsertText (pSource,
379 apparent_ptStartPos.y, apparent_ptStartPos.x, ur.GetText (), ur.GetTextLength (), nEndLine, nEndChar,
381 ptCursorPos = m_ptLastChange;
383 // for the flags, the logic is nearly the same as in insertText
384 int bFirstLineGhost = ((GetLineFlags(apparent_ptStartPos.y) & LF_GHOST) != 0);
385 // when inserting an EOL terminated text into a ghost line,
386 // there is a dicrepancy between nInsertedLines and nEndLine-nRealLine
387 int bDiscrepancyInInsertedLines;
388 if (bFirstLineGhost && nEndChar == 0)
389 bDiscrepancyInInsertedLines = TRUE;
391 bDiscrepancyInInsertedLines = FALSE;
394 for (i = apparent_ptStartPos.y ; i < nEndLine ; i++)
395 OnNotifyLineHasBeenEdited(i);
396 if (bDiscrepancyInInsertedLines == 0)
397 OnNotifyLineHasBeenEdited(i);
399 // We know the number of real lines in the deleted block (including partial lines for extremities)
400 // there may be more lines (difficult to explain) then they must be ghost
401 for (i = apparent_ptStartPos.y ; i < apparent_ptStartPos.y + ur.m_nRealLinesInDeletedBlock ; i++)
402 SetLineFlag (i, LF_GHOST, FALSE, FALSE, FALSE);
403 for ( ; i <= nEndLine ; i++)
404 SetLineFlag (i, LF_GHOST, TRUE, FALSE, FALSE);
406 // it is not easy to know when Recompute so we do it always
407 RecomputeRealityMapping();
409 RecomputeEOL (pSource, apparent_ptStartPos.y, nEndLine);
412 // store infos for redo
413 ur.m_redo_ptStartPos.x = apparent_ptStartPos.x;
414 ur.m_redo_ptStartPos.y = ComputeRealLineAndGhostAdjustment( apparent_ptStartPos.y, ur.m_redo_ptStartPos_nGhost);
415 if (ur.m_dwFlags & UNDO_INSERT)
416 ur.m_redo_ptEndPos = CPoint( -1, 0 );
419 ur.m_redo_ptEndPos.x = m_ptLastChange.x;
420 ur.m_redo_ptEndPos.y = ComputeRealLineAndGhostAdjustment (m_ptLastChange.y, ur.m_redo_ptEndPos_nGhost);
423 // restore line revision numbers
424 int i, naSavedRevisonNumbersSize = (int) ur.m_paSavedRevisonNumbers->GetSize();
425 for (i = 0; i < naSavedRevisonNumbersSize; i++)
426 m_aLines[apparent_ptStartPos.y + i].m_dwRevisionNumber = (*ur.m_paSavedRevisonNumbers)[i];
428 m_aUndoBuf[tmpPos] = ur;
430 if (ur.m_dwFlags & UNDO_BEGINGROUP)
433 if (m_bModified && m_nSyncPosition == tmpPos)
435 if (!m_bModified && m_nSyncPosition != tmpPos)
439 // If the Undo failed, clear the entire Undo/Redo stack
440 // Not only can we not Redo the failed Undo, but the Undo
441 // may have partially completed (if in a group)
443 m_aUndoBuf.SetSize (m_nUndoPosition);
447 m_nUndoPosition = tmpPos;
452 BOOL CGhostTextBuffer::
453 Redo (CCrystalTextView * pSource, CPoint & ptCursorPos)
456 ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
457 ASSERT ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0);
461 SUndoRecord ur = m_aUndoBuf[m_nUndoPosition];
462 CPoint apparent_ptStartPos = ur.m_redo_ptStartPos;
463 apparent_ptStartPos.y = ComputeApparentLine (ur.m_redo_ptStartPos.y, ur.m_redo_ptStartPos_nGhost);
464 CPoint apparent_ptEndPos = ur.m_redo_ptEndPos;
465 apparent_ptEndPos.y = ComputeApparentLine (ur.m_redo_ptEndPos.y, ur.m_redo_ptEndPos_nGhost);
467 if (ur.m_redo_ptStartPos_nGhost > 0)
468 // we need a ghost line at position apparent_ptStartPos.y
469 if (apparent_ptStartPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptStartPos.y) & LF_GHOST) == 0)
471 // if we don't find it, we insert it
472 InsertGhostLine (pSource, apparent_ptStartPos.y);
473 // and recompute apparent_ptEndPos
474 apparent_ptEndPos.y = ComputeApparentLine (ur.m_redo_ptEndPos.y, ur.m_redo_ptEndPos_nGhost);
477 // EndPos defined only for UNDO_DELETE (when we delete)
478 if ((ur.m_dwFlags & UNDO_INSERT) == 0 && ur.m_redo_ptEndPos_nGhost > 0)
479 // we need a ghost line at position apparent_ptStartPos.y
480 if (apparent_ptEndPos.y >= m_aLines.GetSize() || (GetLineFlags(apparent_ptEndPos.y) & LF_GHOST) == 0)
482 // if we don't find it, we insert it
483 InsertGhostLine (pSource, apparent_ptEndPos.y);
486 // now we can use normal (CGhostTextBuffer::) insertTxt or deleteText
487 if (ur.m_dwFlags & UNDO_INSERT)
489 int nEndLine, nEndChar;
490 VERIFY(InsertText (pSource, apparent_ptStartPos.y, apparent_ptStartPos.x,
491 ur.GetText(), ur.GetTextLength(), nEndLine, nEndChar, 0, FALSE));
492 ptCursorPos = m_ptLastChange;
496 #ifdef _ADVANCED_BUGCHECK
498 GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text);
499 ASSERT(text.GetLength() == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0);
501 VERIFY(DeleteText(pSource, apparent_ptStartPos.y, apparent_ptStartPos.x,
502 apparent_ptEndPos.y, apparent_ptEndPos.x, 0, FALSE));
503 ptCursorPos = apparent_ptStartPos;
506 if (m_nUndoPosition == m_aUndoBuf.GetSize ())
508 if ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0)
512 if (m_bModified && m_nSyncPosition == m_nUndoPosition)
514 if (!m_bModified && m_nSyncPosition != m_nUndoPosition)
521 we must set both our m_bUndoBeginGroup and the one of CCrystalTextBuffer
523 void CGhostTextBuffer::
524 BeginUndoGroup (BOOL bMergeWithPrevious /*= FALSE*/ )
526 ASSERT (!m_bUndoGroup);
528 m_bUndoBeginGroup = m_nUndoPosition == 0 || !bMergeWithPrevious;
529 CCrystalTextBuffer::m_bUndoBeginGroup = m_bUndoBeginGroup;
532 /** Use ou own flushing function as we need to use our own m_aUndoBuf */
533 void CGhostTextBuffer::
534 FlushUndoGroup (CCrystalTextView * pSource)
536 ASSERT (m_bUndoGroup);
539 ASSERT (m_nUndoPosition == m_aUndoBuf.GetSize ());
540 if (m_nUndoPosition > 0)
542 pSource->OnEditOperation (m_aUndoBuf[m_nUndoPosition - 1].m_nAction, m_aUndoBuf[m_nUndoPosition - 1].GetText ());
545 m_bUndoGroup = FALSE;
549 /** The CPoint received parameters are apparent (on screen) line numbers */
550 void CGhostTextBuffer::
551 AddUndoRecord (BOOL bInsert, const CPoint & ptStartPos, const CPoint & ptEndPos, LPCTSTR pszText, int cchText, int nRealLinesChanged, int nActionType, CDWordArray *paSavedRevisonNumbers)
553 // Forgot to call BeginUndoGroup()?
554 ASSERT (m_bUndoGroup);
555 ASSERT (m_aUndoBuf.GetSize () == 0 || (m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
557 // Strip unnecessary undo records (edit after undo wipes all potential redo records)
558 int nBufSize = (int) m_aUndoBuf.GetSize ();
559 if (m_nUndoPosition < nBufSize)
561 m_aUndoBuf.SetSize (m_nUndoPosition);
564 // If undo buffer size is close to critical, remove the oldest records
565 ASSERT (m_aUndoBuf.GetSize () <= m_nUndoBufSize);
566 nBufSize = (int) m_aUndoBuf.GetSize ();
567 if (nBufSize >= m_nUndoBufSize)
573 if (nIndex == nBufSize || (m_aUndoBuf[nIndex].m_dwFlags & UNDO_BEGINGROUP) != 0)
576 m_aUndoBuf.RemoveAt (0, nIndex);
579 //- Keep m_nSyncPosition in sync.
580 //- Ensure first undo record is flagged UNDO_BEGINGROUP since part of the code
581 //..relies on this condition.
582 if (m_nSyncPosition >= 0)
584 m_nSyncPosition -= nIndex; // çà c'est bien...mais non, test inutile ? Ou Apres !
586 if (nIndex < nBufSize)
588 // Not really necessary as long as groups are discarded as a whole.
589 // Just in case some day the loop above should be changed to limit
590 // the number of discarded undo records to some reasonable value...
591 m_aUndoBuf[0].m_dwFlags |= UNDO_BEGINGROUP; // çà c'est sale
595 // No undo records left - begin a new group:
596 m_bUndoBeginGroup = TRUE;
601 ASSERT (m_aUndoBuf.GetSize () < m_nUndoBufSize);
605 ur.m_dwFlags = bInsert ? UNDO_INSERT : 0;
606 ur.m_nAction = nActionType;
607 if (m_bUndoBeginGroup)
609 ur.m_dwFlags |= UNDO_BEGINGROUP;
610 m_bUndoBeginGroup = FALSE;
612 ur.m_ptStartPos = ptStartPos;
613 ur.m_ptEndPos = ptEndPos;
614 ur.m_ptStartPos.y = ComputeRealLineAndGhostAdjustment( ptStartPos.y, ur.m_ptStartPos_nGhost);
615 ur.m_ptEndPos.y = ComputeRealLineAndGhostAdjustment( ptEndPos.y, ur.m_ptEndPos_nGhost);
617 ur.m_nRealLinesCreated = nRealLinesChanged;
619 ur.m_nRealLinesInDeletedBlock = nRealLinesChanged;
620 ur.SetText (pszText, cchText);
621 ur.m_paSavedRevisonNumbers = paSavedRevisonNumbers;
624 m_nUndoPosition = (int) m_aUndoBuf.GetSize ();
626 ASSERT (m_aUndoBuf.GetSize () <= m_nUndoBufSize);
632 ////////////////////////////////////////////////////////////////////////////
637 * @param nEndLine and nEndChar are the coordinates of the end od the inserted text
638 * They are valid as long as you do not call FlushUndoGroup
639 * If you need to call FlushUndoGroup, just store them in a variable which
640 * is preserved with real line number during Rescan (m_ptCursorPos, m_ptLastChange for example)
642 BOOL CGhostTextBuffer::
643 InsertText (CCrystalTextView * pSource, int nLine, int nPos, LPCTSTR pszText, int cchText,
644 int &nEndLine, int &nEndChar, int nAction, BOOL bHistory /*=TRUE*/)
646 BOOL bGroupFlag = FALSE;
656 // save line revision numbers for undo
657 CDWordArray *paSavedRevisonNumbers = new CDWordArray;
658 paSavedRevisonNumbers->SetSize(1);
659 (*paSavedRevisonNumbers)[0] = m_aLines[nLine].m_dwRevisionNumber;
661 if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText, cchText, nEndLine, nEndChar, nAction, bHistory))
663 delete paSavedRevisonNumbers;
667 // set WinMerge flags
668 int bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
670 // when inserting an EOL terminated text into a ghost line,
671 // there is a dicrepancy between nInsertedLines and nEndLine-nRealLine
672 int bDiscrepancyInInsertedLines;
673 if (bFirstLineGhost && nEndChar == 0)
674 bDiscrepancyInInsertedLines = TRUE;
676 bDiscrepancyInInsertedLines = FALSE;
678 // compute the number of real lines created (for undo)
679 int nRealLinesCreated = nEndLine - nLine;
680 if (bFirstLineGhost && nEndChar > 0)
681 // we create one more real line
682 nRealLinesCreated ++;
685 for (i = nLine ; i < nEndLine ; i++)
687 // update line revision numbers of modified lines
688 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
689 OnNotifyLineHasBeenEdited(i);
691 if (bDiscrepancyInInsertedLines == 0)
693 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
694 OnNotifyLineHasBeenEdited(i);
697 // when inserting into a ghost line block, we want to replace ghost lines
698 // with our text, so delete some ghost lines below the inserted text
701 // where is the first line after the inserted text ?
702 int nInsertedTextLinesCount = nEndLine - nLine + (bDiscrepancyInInsertedLines ? 0 : 1);
703 int nLineAfterInsertedBlock = nLine + nInsertedTextLinesCount;
704 // delete at most nInsertedTextLinesCount - 1 ghost lines
705 // as the first ghost line has been reused
706 int nMaxGhostLineToDelete = min(nInsertedTextLinesCount - 1, GetLineCount()-nLineAfterInsertedBlock);
707 for (i = 0 ; i < nMaxGhostLineToDelete ; i++)
708 if ((GetLineFlags(nLineAfterInsertedBlock+i) & LF_GHOST) == 0)
710 InternalDeleteGhostLine(pSource, nLineAfterInsertedBlock, i);
713 for (i = nLine ; i < nEndLine ; i++)
714 SetLineFlag (i, LF_GHOST, FALSE, FALSE, FALSE);
715 if (bDiscrepancyInInsertedLines == 0)
716 // if there is no discrepancy, the final cursor line is real
717 // as either some text was inserted in it, or it inherits the real status from the first line
718 SetLineFlag (i, LF_GHOST, FALSE, FALSE, FALSE);
720 // if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
723 // now we can recompute
724 if ((nEndLine > nLine) || bFirstLineGhost)
726 // TODO: Be smarter, and don't recompute if it is easy to see what changed
727 RecomputeRealityMapping();
730 RecomputeEOL (pSource, nLine, nEndLine);
733 if (bHistory == false)
735 delete paSavedRevisonNumbers;
740 // little trick as we share the m_nUndoPosition with the base class
741 ASSERT ( m_nUndoPosition > 0);
743 AddUndoRecord (TRUE, CPoint (nPos, nLine), CPoint (nEndChar, nEndLine),
744 pszText, cchText, nRealLinesCreated, nAction, paSavedRevisonNumbers);
747 FlushUndoGroup (pSource);
749 // nEndLine may have changed during Rescan
750 nEndLine = m_ptLastChange.y;
755 BOOL CGhostTextBuffer::
756 DeleteText (CCrystalTextView * pSource, int nStartLine, int nStartChar,
757 int nEndLine, int nEndChar, int nAction, BOOL bHistory /*=TRUE*/)
759 BOOL bGroupFlag = FALSE;
769 // save line revision numbers for undo
770 CDWordArray *paSavedRevisonNumbers = new CDWordArray;
771 paSavedRevisonNumbers->SetSize(nEndLine - nStartLine + 1);
773 for (i = 0, j = 0; i < nEndLine - nStartLine + 1; i++)
775 DWORD dwLineFlag = GetLineFlags(nStartLine + i);
776 if (!(dwLineFlag & LF_GHOST))
777 (*paSavedRevisonNumbers)[j++] = m_aLines[nStartLine + i].m_dwRevisionNumber;
779 paSavedRevisonNumbers->SetSize(j);
781 // flags are going to be deleted so we store them now
782 int bLastLineGhost = ((GetLineFlags(nEndLine) & LF_GHOST) != 0);
783 int bFirstLineGhost = ((GetLineFlags(nStartLine) & LF_GHOST) != 0);
784 // count the number of real lines in the deleted block (for first/last line, include partial real lines)
785 int nRealLinesInDeletedBlock = ComputeRealLine(nEndLine) - ComputeRealLine(nStartLine);
787 nRealLinesInDeletedBlock ++;
789 CString sTextToDelete;
790 GetTextWithoutEmptys (nStartLine, nStartChar, nEndLine, nEndChar, sTextToDelete);
791 if (!CCrystalTextBuffer::DeleteText (pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory))
793 delete paSavedRevisonNumbers;
797 OnNotifyLineHasBeenEdited(nStartLine);
798 // update line revision numbers of modified lines
799 if (nStartChar != 0 || nEndChar != 0)
800 m_aLines[nStartLine].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
802 // the first line inherits the status of the last one
803 // but exception... if the last line is a ghost, we preserve the status of the first line
804 // (then if we use backspace in a ghost line, we don't delete the previous line)
805 if (bLastLineGhost == FALSE)
806 SetLineFlag(nStartLine, LF_GHOST, FALSE, FALSE, FALSE);
809 int bFlagException = (bFirstLineGhost == 0);
811 SetLineFlag(nStartLine, LF_GHOST, FALSE, FALSE, FALSE);
813 SetLineFlag(nStartLine, LF_GHOST, TRUE, FALSE, FALSE);
816 // now we can recompute
817 if (nStartLine != nEndLine)
819 // TODO: Be smarter, and don't recompute if it is easy to see what changed
820 RecomputeRealityMapping();
823 RecomputeEOL (pSource, nStartLine, nStartLine);
826 if (bHistory == false)
828 delete paSavedRevisonNumbers;
832 // little trick as we share the m_nUndoPosition with the base class
833 ASSERT ( m_nUndoPosition > 0);
835 AddUndoRecord (FALSE, CPoint (nStartChar, nStartLine), CPoint (0, -1),
836 sTextToDelete, sTextToDelete.GetLength(), nRealLinesInDeletedBlock, nAction, paSavedRevisonNumbers);
839 FlushUndoGroup (pSource);
843 BOOL CGhostTextBuffer::
844 InsertGhostLine (CCrystalTextView * pSource, int nLine)
846 if (!InternalInsertGhostLine (pSource, nLine))
849 // set WinMerge flags
850 SetLineFlag (nLine, LF_GHOST, TRUE, FALSE, FALSE);
852 RecomputeRealityMapping();
854 // don't need to recompute EOL as real lines are unchanged
856 // never AddUndoRecord as Rescan clears the ghost lines
861 void CGhostTextBuffer::
862 RemoveAllGhostLines()
864 int nlines = GetLineCount();
867 // Free the buffer of ghost lines
868 for(ct=0; ct < nlines; ct++)
869 if (GetLineFlags(ct) & LF_GHOST)
870 delete[] m_aLines[ct].m_pcLine;
871 // Compact non-ghost lines
872 // (we copy the buffer address, so the buffer don't move and we don't free it)
873 for(ct=0; ct < nlines; ct++)
874 if ((GetLineFlags(ct) & LF_GHOST) == 0)
875 m_aLines[newnl++] = m_aLines[ct];
877 // Discard unused entries in one shot
878 m_aLines.SetSize(newnl);
880 RecomputeRealityMapping();
883 ////////////////////////////////////////////////////////////////////////////
884 // apparent <-> real line conversion
887 Return apparent line of highest real (file) line.
888 Return -1 if no lines.
890 int CGhostTextBuffer::ApparentLastRealLine() const
892 int bmax = (int) m_RealityBlocks.GetUpperBound();
893 if (bmax<0) return -1;
894 const RealityBlock & block = m_RealityBlocks[bmax];
895 return block.nStartApparent + block.nCount - 1;
899 Return underlying real line.
900 For ghost lines, return NEXT HIGHER real line (for trailing ghost line, return last real line + 1).
901 If nApparentLine is greater than the last valid apparent line, ASSERT
903 ie, lines 0->0, 1->2, 2->4,
904 for argument of 3, return 2
906 int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
908 int bmax = (int) m_RealityBlocks.GetUpperBound();
909 // first get the degenerate cases out of the way
914 // after last apparent line ?
915 ASSERT(nApparentLine < GetLineCount());
917 // after last block ?
918 const RealityBlock & maxblock = m_RealityBlocks[bmax];
919 if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
920 return maxblock.nStartReal + maxblock.nCount;
922 // binary search to find correct (or nearest block)
928 const RealityBlock & block = m_RealityBlocks[i];
929 if (nApparentLine < block.nStartApparent)
931 else if (nApparentLine >= block.nStartApparent + block.nCount)
933 else // found it inside this block
934 return (nApparentLine - block.nStartApparent) + block.nStartReal;
936 // it is a ghost line just before block blo
937 return m_RealityBlocks[blo].nStartReal;
941 Return apparent line for this underlying real line.
942 If real line is out of bounds, return last valid apparent line + 1
944 int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
946 int bmax = (int) m_RealityBlocks.GetUpperBound();
947 // first get the degenerate cases out of the way
951 // after last block ?
952 const RealityBlock & maxblock = m_RealityBlocks[bmax];
953 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
954 return GetLineCount();
956 // binary search to find correct (or nearest block)
962 const RealityBlock & block = m_RealityBlocks[i];
963 if (nRealLine < block.nStartReal)
965 else if (nRealLine >= block.nStartReal + block.nCount)
968 return (nRealLine - block.nStartReal) + block.nStartApparent;
970 // Should have found it; all real lines should be in a block
976 Return underlying real line and ghost adjustment
977 as nApparentLine = apparent(nRealLine) - nGhostAdjustment
979 nRealLine for ghost lines is the NEXT HIGHER real line (for trailing ghost line, last real line + 1).
980 If nApparentLine is greater than the last valid apparent line, ASSERT
982 ie, lines 0->0, 1->2, 2->4,
983 for argument of 3, return 2, and decToReal = 1
985 int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, int& decToReal) const
987 int bmax = (int) m_RealityBlocks.GetUpperBound();
988 // first get the degenerate cases out of the way
996 // after last apparent line ?
997 ASSERT(nApparentLine < GetLineCount());
999 // after last block ?
1000 const RealityBlock & maxblock = m_RealityBlocks[bmax];
1001 if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
1003 decToReal = GetLineCount() - nApparentLine;
1004 return maxblock.nStartReal + maxblock.nCount;
1007 // binary search to find correct (or nearest block)
1008 int blo=0, bhi=bmax;
1013 const RealityBlock & block = m_RealityBlocks[i];
1014 if (nApparentLine < block.nStartApparent)
1016 else if (nApparentLine >= block.nStartApparent + block.nCount)
1018 else // found it inside this block
1021 return (nApparentLine - block.nStartApparent) + block.nStartReal;
1024 // it is a ghost line just before block blo
1025 decToReal = m_RealityBlocks[blo].nStartApparent - nApparentLine;
1026 return m_RealityBlocks[blo].nStartReal;
1030 Return apparent line for this underlying real line, with adjustment :
1031 nApparent = apparent(nReal) - decToReal
1033 If the previous real line has apparent number apparent(nReal) - dec, with dec < decToReal,
1034 return apparent(nReal) - dec + 1
1036 int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
1041 int bmax = (int) m_RealityBlocks.GetUpperBound();
1042 // first get the degenerate cases out of the way
1046 // after last block ?
1047 const RealityBlock & maxblock = m_RealityBlocks[bmax];
1048 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
1050 nPreviousBlock = bmax;
1051 nApparent = GetLineCount();
1052 goto limitWithPreviousBlock;
1055 // binary search to find correct (or nearest block)
1062 const RealityBlock & block = m_RealityBlocks[i];
1063 if (nRealLine < block.nStartReal)
1065 else if (nRealLine >= block.nStartReal + block.nCount)
1069 if (nRealLine > block.nStartReal)
1070 // limited by the previous line in this block
1071 return (nRealLine - block.nStartReal) + block.nStartApparent;
1072 nPreviousBlock = i - 1;
1073 nApparent = (nRealLine - block.nStartReal) + block.nStartApparent;
1074 goto limitWithPreviousBlock;
1077 // Should have found it; all real lines should be in a block
1081 limitWithPreviousBlock:
1082 // we must keep above the value lastApparentInPreviousBlock
1083 int lastApparentInPreviousBlock;
1084 if (nPreviousBlock == -1)
1085 lastApparentInPreviousBlock = -1;
1088 const RealityBlock & previousBlock = m_RealityBlocks[nPreviousBlock];
1089 lastApparentInPreviousBlock = previousBlock.nStartApparent + previousBlock.nCount - 1;
1092 while (decToReal --)
1095 if (nApparent == lastApparentInPreviousBlock)
1101 /** Do what we need to do just after we've been reloaded */
1102 void CGhostTextBuffer::FinishLoading()
1104 if (!m_bInit) return;
1105 RecomputeRealityMapping();
1108 /** Recompute the reality mapping (this is fairly naive) */
1109 void CGhostTextBuffer::RecomputeRealityMapping()
1111 m_RealityBlocks.RemoveAll();
1112 int reality=-1; // last encountered real line
1113 int i=0; // current line
1114 RealityBlock block; // current block being traversed (in state 2)
1116 // This is a state machine with 2 states
1118 // state 1, i-1 not real line
1120 if (i==GetLineCount())
1122 if (GetLineFlags(i) & LF_GHOST)
1127 // this is the first line of a reality block
1128 block.nStartApparent = i;
1129 block.nStartReal = reality+1;
1132 // fall through to other state
1134 // state 2, i-1 is real line
1136 if (i==GetLineCount() || (GetLineFlags(i) & LF_GHOST))
1138 // i-1 is the last line of a reality block
1139 ASSERT(reality >= 0);
1140 block.nCount = i - block.nStartApparent;
1141 ASSERT(block.nCount > 0);
1142 ASSERT(reality+1-block.nStartReal == block.nCount);
1143 m_RealityBlocks.Add(block);
1144 if (i==GetLineCount())
1154 /** we recompute EOL from the real line before nStartLine to nEndLine */
1155 void CGhostTextBuffer::RecomputeEOL(CCrystalTextView * pSource, int nStartLine, int nEndLine)
1157 if (ApparentLastRealLine() <= nEndLine)
1159 // EOL may have to change on the real line before nStartLine
1160 int nRealBeforeStart;
1161 for (nRealBeforeStart = nStartLine-1 ; nRealBeforeStart >= 0 ; nRealBeforeStart--)
1162 if ((GetLineFlags(nRealBeforeStart) & LF_GHOST) == 0)
1164 if (nRealBeforeStart >= 0)
1165 nStartLine = nRealBeforeStart;
1167 int bLastRealLine = (ApparentLastRealLine() <= nEndLine);
1169 for (i = nEndLine ; i >= nStartLine ; i --)
1171 if ((GetLineFlags(i) & LF_GHOST) == 0)
1176 if (m_aLines[i].m_nEolChars != 0)
1178 // if the last real line has an EOL, remove it
1179 m_aLines[i].m_pcLine[m_aLines[i].m_nLength] = '\0';
1180 m_aLines[i].m_nEolChars = 0;
1182 UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
1187 if (m_aLines[i].m_nEolChars == 0)
1189 // if a real line (not the last) has no EOL, add one
1190 AppendLine (i, GetDefaultEol(), (int) _tcslen(GetDefaultEol()));
1192 UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
1198 if (m_aLines[i].m_nEolChars != 0)
1200 // if a ghost line has an EOL, remove it
1201 m_aLines[i].m_pcLine[m_aLines[i].m_nLength] = '\0';
1202 m_aLines[i].m_nEolChars = 0;
1204 UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
1211 Check all lines, and ASSERT if reality blocks differ from flags.
1212 This means that this only has effect in DEBUG build
1214 void CGhostTextBuffer::checkFlagsFromReality(BOOL bFlag) const
1216 int bmax = (int) m_RealityBlocks.GetUpperBound();
1219 for (b = 0 ; b <= bmax ; b ++)
1221 const RealityBlock & block = m_RealityBlocks[b];
1222 for ( ; i < block.nStartApparent ; i++)
1223 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
1224 for ( ; i < block.nStartApparent+block.nCount ; i++)
1225 ASSERT ((GetLineFlags(i) & LF_GHOST) == 0);
1228 for ( ; i < GetLineCount() ; i++)
1229 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
1232 void CGhostTextBuffer::OnNotifyLineHasBeenEdited(int nLine)