OSDN Git Service

65cc5774be66dff765c2fe70f710463b35937c4b
[winmerge-jp/winmerge-jp.git] / Src / GhostTextBuffer.cpp
1 ////////////////////////////////////////////////////////////////////////////
2 //  File:       GhostTextBuffer.cpp
3 //  Version:    1.0.0.0
4 //  Created:    31-Jul-2003
5 //
6 /////////////////////////////////////////////////////////////////////////////
7 //    WinMerge:  an interactive diff/merge utility
8 //    Copyright (C) 1997-2000  Thingamahoochie Software
9 //    Author: Dean Grimm
10 //
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.
15 //
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.
20 //
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.
24 //
25 /////////////////////////////////////////////////////////////////////////////
26 /** 
27  * @file  GhostTextBuffer.cpp
28  *
29  * @brief Implementation of GhostTextBuffer class.
30  */
31 // ID line follows -- this is updated by SVN
32 // $Id$
33
34 #include "stdafx.h"
35 #include "GhostTextBuffer.h"
36
37 #ifdef _DEBUG
38 #define new DEBUG_NEW
39 #undef THIS_FILE
40 static char THIS_FILE[] = __FILE__;
41 #endif
42
43 #ifdef _DEBUG
44 #define _ADVANCED_BUGCHECK  1
45 #endif
46
47 BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
48 //{{AFX_MSG_MAP(CGhostTextBuffer)
49 //}}AFX_MSG_MAP
50 END_MESSAGE_MAP ()
51
52 IMPLEMENT_DYNCREATE (CGhostTextBuffer, CCrystalTextBuffer)
53
54 CGhostTextBuffer::CGhostTextBuffer()
55 {
56         m_bUndoGroup = FALSE;
57         CCrystalTextBuffer::m_bUndoBeginGroup = m_bUndoBeginGroup = FALSE;
58 }
59
60 BOOL CGhostTextBuffer::
61 InitNew (int nCrlfStyle /*= CRLF_STYLE_DOS*/ )
62 {
63         m_bUndoBeginGroup = FALSE;
64         return CCrystalTextBuffer::InitNew(nCrlfStyle);
65 }
66
67
68 /** InternalInsertGhostLine accepts only apparent line numbers */
69 BOOL CGhostTextBuffer::
70 InternalInsertGhostLine (CCrystalTextView * pSource, int nLine)
71 {
72         ASSERT (m_bInit);             //  Text buffer not yet initialized.
73         //  You must call InitNew() or LoadFromFile() first!
74
75         ASSERT (nLine >= 0 && nLine <= m_aLines.GetSize ());
76         if (m_bReadOnly)
77                 return FALSE;
78
79         CInsertContext context;
80         context.m_ptStart.x = 0;
81         context.m_ptStart.y = nLine;
82
83         CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
84
85         context.m_ptEnd.x = 0;
86         context.m_ptEnd.y = nLine+1;
87
88         if (pSource!=NULL)
89                 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
90
91         if (!m_bModified)
92                 SetModified (TRUE);
93
94         OnNotifyLineHasBeenEdited(nLine);
95
96         return TRUE;
97 }
98
99
100 /** InternalDeleteGhostLine accepts only apparent line numbers */
101 BOOL CGhostTextBuffer::
102 InternalDeleteGhostLine (CCrystalTextView * pSource, int nLine, int nCount)
103 {
104         ASSERT (m_bInit);             //  Text buffer not yet initialized.
105         //  You must call InitNew() or LoadFromFile() first!
106
107         ASSERT (nLine >= 0 && nLine <= m_aLines.GetSize ());
108         if (m_bReadOnly)
109                 return FALSE;
110
111         if (nCount == 0)
112                 return TRUE;
113
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;
119
120         for (int L = nLine ; L < nLine+nCount; L++)
121         {
122                 ASSERT (GetLineFlags(L) & LF_GHOST);
123                 delete[] m_aLines[L].m_pcLine;
124         }
125         m_aLines.RemoveAt (nLine, nCount);
126
127         if (pSource!=NULL)
128         {
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);
133                 else
134                         UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
135         }
136
137         if (!m_bModified)
138                 SetModified (TRUE);
139
140         return TRUE;
141 }
142
143
144
145
146 /**
147  * @brief Get text of specified lines (ghost lines will not contribute to text).
148  * 
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
151  * 
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.
157  */
158 void CGhostTextBuffer::GetTextWithoutEmptys(int nStartLine, int nStartChar, 
159                  int nEndLine, int nEndChar, 
160                  CString &text, int nCrlfStyle /* CRLF_STYLE_AUTOMATIC */)
161 {
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);
171
172         // estimate size (upper bound)
173         int nBufSize = 0;
174         int i=0;
175         for (i=nStartLine; i<=nEndLine; ++i)
176                 nBufSize += (GetFullLineLength(i) + 2); // in case we insert EOLs
177         LPTSTR pszBuf = text.GetBuffer(nBufSize);
178
179         if (nCrlfStyle != CRLF_STYLE_AUTOMATIC)
180         {
181                 // we must copy this EOL type only
182                 CString sEol = GetStringEol (nCrlfStyle);
183
184                 for (i=nStartLine; i<=nEndLine; ++i)
185                 {
186                         // exclude ghost lines
187                         if (GetLineFlags(i) & LF_GHOST)
188                                 continue;
189
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));
196                         pszBuf += chars;
197
198                         // copy the EOL of the requested type
199                         if (i!=ApparentLastRealLine())
200                         {
201                                 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
202                                 pszBuf += sEol.GetLength();
203                         }
204                 }
205         } 
206         else 
207         {
208                 for (i=nStartLine; i<=nEndLine; ++i)
209                 {
210                         // exclude ghost lines
211                         if (GetLineFlags(i) & LF_GHOST)
212                                 continue;
213
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));
220                         pszBuf += chars;
221
222                         // check that we really have an EOL
223                         if (i!=ApparentLastRealLine() && GetLineLength(i)==GetFullLineLength(i))
224                         {
225                                 // Oops, real line lacks EOL
226                                 // (If this happens, editor probably has bug)
227                                 ASSERT(0);
228                                 CString sEol = GetStringEol (nCrlfStyle);
229                                 CopyMemory(pszBuf, sEol, sEol.GetLength());
230                                 pszBuf += sEol.GetLength();
231                         }
232                 }
233         }
234         text.ReleaseBuffer(pszBuf - text);
235         text.FreeExtra();
236 }
237
238 ////////////////////////////////////////////////////////////////////////////
239 // undo/redo functions
240
241 <void CGhostTextBuffer::SUndoRecord::
242 SetText (LPCTSTR pszText, int nLength)
243 {
244         FreeText();
245         if (nLength)
246         {
247                 if (nLength > 1)
248                 {
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
253                 }
254                 else
255                 {
256                         m_szText[0] = pszText[0];
257                 }
258         }
259 }
260
261 void CGhostTextBuffer::SUndoRecord::
262 FreeText ()
263 {
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)
268                 free(m_pszText);
269         m_pszText = NULL;
270 }
271
272 BOOL CGhostTextBuffer::
273 Undo (CCrystalTextView * pSource, CPoint & ptCursorPos)
274 {
275         ASSERT (CanUndo ());
276         ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
277         BOOL failed = FALSE;
278         int tmpPos = m_nUndoPosition;
279
280         while (!failed)
281         {
282                 tmpPos--;;
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);
290
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)
294                         {
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);
299                         } 
300
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)
305                         {
306                                 // if we don't find it, we insert it
307                                 InsertGhostLine (pSource, apparent_ptEndPos.y);
308                         }
309
310                 if (ur.m_dwFlags & UNDO_INSERT)
311                 {
312                         // WINMERGE -- Check that text in undo buffer matches text in
313                         // file buffer. If not, then rescan() has moved lines and undo
314                         // is skipped.
315
316                         // we need to put the cursor before the deleted section
317                         CString text;
318                         ur.m_redo_ptEndPos.x = apparent_ptEndPos.x;
319                         ur.m_redo_ptEndPos.y = ComputeRealLineAndGhostAdjustment (apparent_ptEndPos.y, ur.m_redo_ptEndPos_nGhost);
320
321                         // flags are going to be deleted so we store them now
322                         int bLastLineGhost = ((GetLineFlags(apparent_ptEndPos.y) & LF_GHOST) != 0);
323
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))
328                         {
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)
331                                 {
332                                         VERIFY (CCrystalTextBuffer::DeleteText (pSource, 
333                                                 apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x,
334                                                 0, FALSE));
335                                         ptCursorPos = apparent_ptStartPos;
336                                 }
337                                 else
338                                 {
339                                         //..Try to ensure that we are undoing correctly...
340                                         //  Just compare the text as it was before Undo operation
341 #ifdef _ADVANCED_BUGCHECK
342                                         ASSERT(0);
343 #endif
344                                         failed = TRUE;
345                                         break;
346                                 }
347
348                         }
349                         else
350                         {
351                                 failed = TRUE;
352                                 break;
353                         }
354
355                         OnNotifyLineHasBeenEdited(apparent_ptStartPos.y);
356
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);
359
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)
363                                 ;
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);
367                         else
368                                 ASSERT(0);
369
370                         // it is not easy to know when Recompute so we do it always
371                         RecomputeRealityMapping();
372
373                         RecomputeEOL (pSource, apparent_ptStartPos.y, apparent_ptStartPos.y);
374                 }
375                 else
376                 {
377                         int nEndLine, nEndChar;
378                         VERIFY(CCrystalTextBuffer::InsertText (pSource, 
379                                 apparent_ptStartPos.y, apparent_ptStartPos.x, ur.GetText (), ur.GetTextLength (), nEndLine, nEndChar, 
380                                 0, FALSE));
381                         ptCursorPos = m_ptLastChange;
382
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;
390                         else
391                                 bDiscrepancyInInsertedLines = FALSE;
392
393                         int i;
394                         for (i = apparent_ptStartPos.y ; i < nEndLine ; i++)
395                                 OnNotifyLineHasBeenEdited(i);
396                         if (bDiscrepancyInInsertedLines == 0)
397                                 OnNotifyLineHasBeenEdited(i);
398
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);
405
406                         // it is not easy to know when Recompute so we do it always
407                         RecomputeRealityMapping();
408
409                         RecomputeEOL (pSource, apparent_ptStartPos.y, nEndLine);
410                 }
411
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 );
417                 else
418                 {
419                         ur.m_redo_ptEndPos.x = m_ptLastChange.x;
420                         ur.m_redo_ptEndPos.y = ComputeRealLineAndGhostAdjustment (m_ptLastChange.y, ur.m_redo_ptEndPos_nGhost);
421                 }
422
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];
427
428                 m_aUndoBuf[tmpPos] = ur;
429
430                 if (ur.m_dwFlags & UNDO_BEGINGROUP)
431                         break;
432         }
433         if (m_bModified && m_nSyncPosition == tmpPos)
434                 SetModified (FALSE);
435         if (!m_bModified && m_nSyncPosition != tmpPos)
436                 SetModified (TRUE);
437         if (failed)
438         {
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)
442                 m_nUndoPosition = 0;
443                 m_aUndoBuf.SetSize (m_nUndoPosition);
444         }
445         else
446         {
447                 m_nUndoPosition = tmpPos;
448         }
449         return !failed;
450 }
451
452 BOOL CGhostTextBuffer::
453 Redo (CCrystalTextView * pSource, CPoint & ptCursorPos)
454 {
455         ASSERT (CanRedo ());
456         ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
457         ASSERT ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0);
458
459         while(1)
460         {
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);
466
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)
470                         {
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);
475                         } 
476
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)
481                         {
482                                 // if we don't find it, we insert it
483                                 InsertGhostLine (pSource, apparent_ptEndPos.y);
484                         }
485
486                 // now we can use normal (CGhostTextBuffer::) insertTxt or deleteText
487                 if (ur.m_dwFlags & UNDO_INSERT)
488                 {
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;
493                 }
494                 else
495                 {
496 #ifdef _ADVANCED_BUGCHECK
497                         CString text;
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);
500 #endif
501                         VERIFY(DeleteText(pSource, apparent_ptStartPos.y, apparent_ptStartPos.x, 
502                                 apparent_ptEndPos.y, apparent_ptEndPos.x, 0, FALSE));
503                         ptCursorPos = apparent_ptStartPos;
504                 }
505                 m_nUndoPosition++;
506                 if (m_nUndoPosition == m_aUndoBuf.GetSize ())
507                         break;
508                 if ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0)
509                         break;
510         }
511
512         if (m_bModified && m_nSyncPosition == m_nUndoPosition)
513                 SetModified (FALSE);
514         if (!m_bModified && m_nSyncPosition != m_nUndoPosition)
515                 SetModified (TRUE);
516         return TRUE;
517 }
518
519
520 /** 
521 we must set both our m_bUndoBeginGroup and the one of CCrystalTextBuffer
522 */
523 void CGhostTextBuffer::
524 BeginUndoGroup (BOOL bMergeWithPrevious /*= FALSE*/ )
525 {
526         ASSERT (!m_bUndoGroup);
527         m_bUndoGroup = TRUE;
528         m_bUndoBeginGroup = m_nUndoPosition == 0 || !bMergeWithPrevious;
529         CCrystalTextBuffer::m_bUndoBeginGroup = m_bUndoBeginGroup;
530 }
531
532 /** Use ou own flushing function as we need to use our own m_aUndoBuf */
533 void CGhostTextBuffer::
534 FlushUndoGroup (CCrystalTextView * pSource)
535 {
536         ASSERT (m_bUndoGroup);
537         if (pSource != NULL)
538         {
539                 ASSERT (m_nUndoPosition == m_aUndoBuf.GetSize ());
540                 if (m_nUndoPosition > 0)
541                 {
542                         pSource->OnEditOperation (m_aUndoBuf[m_nUndoPosition - 1].m_nAction, m_aUndoBuf[m_nUndoPosition - 1].GetText ());
543                 }
544         }
545         m_bUndoGroup = FALSE;
546 }
547
548
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)
552 {
553         //  Forgot to call BeginUndoGroup()?
554         ASSERT (m_bUndoGroup);
555         ASSERT (m_aUndoBuf.GetSize () == 0 || (m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
556
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)
560         {
561                 m_aUndoBuf.SetSize (m_nUndoPosition);
562         }
563
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)
568         {
569                 int nIndex = 0;
570                 for (;;)
571                 {
572                         nIndex++;
573                         if (nIndex == nBufSize || (m_aUndoBuf[nIndex].m_dwFlags & UNDO_BEGINGROUP) != 0)
574                                 break;
575                 }
576                 m_aUndoBuf.RemoveAt (0, nIndex);
577
578 //<jtuc 2003-06-28>
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)
583                 {
584                         m_nSyncPosition -= nIndex;              // Ã§Ã  c'est bien...mais non, test inutile ? Ou Apres !
585                 }
586                 if (nIndex < nBufSize)
587                 {
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
592                 }
593                 else
594                 {
595                         // No undo records left - begin a new group:
596                         m_bUndoBeginGroup = TRUE;
597                 }
598 //</jtuc>
599
600         }
601         ASSERT (m_aUndoBuf.GetSize () < m_nUndoBufSize);
602
603         //  Add new record
604         SUndoRecord ur;
605         ur.m_dwFlags = bInsert ? UNDO_INSERT : 0;
606         ur.m_nAction = nActionType;
607         if (m_bUndoBeginGroup)
608         {
609                 ur.m_dwFlags |= UNDO_BEGINGROUP;
610                 m_bUndoBeginGroup = FALSE;
611         }
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);
616         if (bInsert)
617                 ur.m_nRealLinesCreated = nRealLinesChanged;
618         else
619                 ur.m_nRealLinesInDeletedBlock = nRealLinesChanged;
620         ur.SetText (pszText, cchText);
621         ur.m_paSavedRevisonNumbers = paSavedRevisonNumbers;
622
623         m_aUndoBuf.Add (ur);
624         m_nUndoPosition = (int) m_aUndoBuf.GetSize ();
625
626         ASSERT (m_aUndoBuf.GetSize () <= m_nUndoBufSize);
627 }
628
629
630
631
632 ////////////////////////////////////////////////////////////////////////////
633 // edition functions
634
635 /**
636  *
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)
641  */
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*/)
645 {
646         BOOL bGroupFlag = FALSE;
647         if (bHistory)
648         {
649                 if (!m_bUndoGroup)
650                 {
651                         BeginUndoGroup ();
652                         bGroupFlag = TRUE;
653                 } 
654         }
655
656         // save line revision numbers for undo
657         CDWordArray *paSavedRevisonNumbers = new CDWordArray;
658         paSavedRevisonNumbers->SetSize(1);
659         (*paSavedRevisonNumbers)[0] = m_aLines[nLine].m_dwRevisionNumber;
660
661         if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText, cchText, nEndLine, nEndChar, nAction, bHistory))
662         {
663                 delete paSavedRevisonNumbers;
664                 return FALSE;
665         }
666
667         // set WinMerge flags
668         int bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
669
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;
675         else
676                 bDiscrepancyInInsertedLines = FALSE;
677
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 ++;
683
684         int i;
685         for (i = nLine ; i < nEndLine ; i++)
686         {
687                 // update line revision numbers of modified lines
688                 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
689                 OnNotifyLineHasBeenEdited(i);
690         }
691         if (bDiscrepancyInInsertedLines == 0)
692         {
693                 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
694                 OnNotifyLineHasBeenEdited(i);
695         }
696
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
699         if (bFirstLineGhost)
700         {
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)
709                                 break;
710                 InternalDeleteGhostLine(pSource, nLineAfterInsertedBlock, i);
711         }
712
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);
719         else
720                 // if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
721                 ;
722
723         // now we can recompute
724         if ((nEndLine > nLine) || bFirstLineGhost)
725         {
726                 // TODO: Be smarter, and don't recompute if it is easy to see what changed
727                 RecomputeRealityMapping();
728         }
729
730         RecomputeEOL (pSource, nLine, nEndLine);
731
732
733         if (bHistory == false)
734         {
735                 delete paSavedRevisonNumbers;
736                 return TRUE;
737         }
738
739
740         // little trick as we share the m_nUndoPosition with the base class
741         ASSERT (  m_nUndoPosition > 0);
742         m_nUndoPosition --;
743         AddUndoRecord (TRUE, CPoint (nPos, nLine), CPoint (nEndChar, nEndLine),
744                  pszText, cchText, nRealLinesCreated, nAction, paSavedRevisonNumbers);
745
746         if (bGroupFlag)
747                 FlushUndoGroup (pSource);
748
749         // nEndLine may have changed during Rescan
750         nEndLine = m_ptLastChange.y;
751
752         return TRUE;
753 }
754
755 BOOL CGhostTextBuffer::
756 DeleteText (CCrystalTextView * pSource, int nStartLine, int nStartChar,
757             int nEndLine, int nEndChar, int nAction, BOOL bHistory /*=TRUE*/)
758 {
759         BOOL bGroupFlag = FALSE;
760         if (bHistory)
761         {
762                 if (!m_bUndoGroup)
763                 {
764                         BeginUndoGroup ();
765                         bGroupFlag = TRUE;
766                 } 
767         }
768
769         // save line revision numbers for undo
770         CDWordArray *paSavedRevisonNumbers = new CDWordArray;
771         paSavedRevisonNumbers->SetSize(nEndLine - nStartLine + 1);
772         int i, j;
773         for (i = 0, j = 0; i < nEndLine - nStartLine + 1; i++)
774         {
775                 DWORD dwLineFlag = GetLineFlags(nStartLine + i);
776                 if (!(dwLineFlag & LF_GHOST))
777                         (*paSavedRevisonNumbers)[j++] = m_aLines[nStartLine + i].m_dwRevisionNumber;
778         }
779         paSavedRevisonNumbers->SetSize(j);
780
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);
786         if (!bLastLineGhost)
787                 nRealLinesInDeletedBlock ++;
788
789         CString sTextToDelete;
790         GetTextWithoutEmptys (nStartLine, nStartChar, nEndLine, nEndChar, sTextToDelete);
791         if (!CCrystalTextBuffer::DeleteText (pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory))
792         {
793                 delete paSavedRevisonNumbers;
794                 return FALSE;
795         }
796
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;
801
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);
807         else
808         {
809                 int bFlagException = (bFirstLineGhost == 0);
810                 if (bFlagException)
811                         SetLineFlag(nStartLine, LF_GHOST, FALSE, FALSE, FALSE);
812                 else
813                         SetLineFlag(nStartLine, LF_GHOST, TRUE, FALSE, FALSE);
814         }
815
816         // now we can recompute
817         if (nStartLine != nEndLine)
818         {
819                 // TODO: Be smarter, and don't recompute if it is easy to see what changed
820                 RecomputeRealityMapping();
821         }
822
823         RecomputeEOL (pSource, nStartLine, nStartLine);
824
825
826         if (bHistory == false)
827         {
828                 delete paSavedRevisonNumbers;
829                 return TRUE;
830         }
831
832         // little trick as we share the m_nUndoPosition with the base class
833         ASSERT (  m_nUndoPosition > 0);
834         m_nUndoPosition --;
835         AddUndoRecord (FALSE, CPoint (nStartChar, nStartLine), CPoint (0, -1),
836                  sTextToDelete, sTextToDelete.GetLength(), nRealLinesInDeletedBlock, nAction, paSavedRevisonNumbers);
837
838         if (bGroupFlag)
839                 FlushUndoGroup (pSource);
840         return TRUE;
841 }
842
843 BOOL CGhostTextBuffer::
844 InsertGhostLine (CCrystalTextView * pSource, int nLine)
845 {
846         if (!InternalInsertGhostLine (pSource, nLine))
847                 return FALSE;
848
849         // set WinMerge flags  
850         SetLineFlag (nLine, LF_GHOST, TRUE, FALSE, FALSE);
851
852         RecomputeRealityMapping();
853
854         // don't need to recompute EOL as real lines are unchanged
855
856         // never AddUndoRecord as Rescan clears the ghost lines
857
858         return TRUE;
859 }
860
861 void CGhostTextBuffer::
862 RemoveAllGhostLines()
863 {
864         int nlines = GetLineCount();
865         int newnl = 0;
866         int ct;
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];
876
877         // Discard unused entries in one shot
878         m_aLines.SetSize(newnl);
879
880         RecomputeRealityMapping();
881 }
882
883 ////////////////////////////////////////////////////////////////////////////
884 // apparent <-> real line conversion
885
886 /**
887 Return apparent line of highest real (file) line. 
888 Return -1 if no lines.
889 */
890 int CGhostTextBuffer::ApparentLastRealLine() const
891 {
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;
896 }
897
898 /**
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
902
903 ie, lines 0->0, 1->2, 2->4, 
904 for argument of 3, return 2
905 */
906 int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
907 {
908         int bmax = (int) m_RealityBlocks.GetUpperBound();
909         // first get the degenerate cases out of the way
910         // empty file ?
911         if (bmax<0)
912                 return 0;
913
914         // after last apparent line ?
915         ASSERT(nApparentLine < GetLineCount());
916
917         // after last block ?
918         const RealityBlock & maxblock = m_RealityBlocks[bmax];
919         if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
920                 return maxblock.nStartReal + maxblock.nCount;
921
922         // binary search to find correct (or nearest block)
923         int blo=0, bhi=bmax;
924         int i;
925         while (blo<=bhi)
926         {
927                 i = (blo+bhi)/2;
928                 const RealityBlock & block = m_RealityBlocks[i];
929                 if (nApparentLine < block.nStartApparent)
930                         bhi = i-1;
931                 else if (nApparentLine >= block.nStartApparent + block.nCount)
932                         blo = i+1;
933                 else // found it inside this block
934                         return (nApparentLine - block.nStartApparent) + block.nStartReal;
935         }
936         // it is a ghost line just before block blo
937         return m_RealityBlocks[blo].nStartReal;
938 }
939
940 /**
941 Return apparent line for this underlying real line. 
942 If real line is out of bounds, return last valid apparent line + 1
943 */
944 int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
945 {
946         int bmax = (int) m_RealityBlocks.GetUpperBound();
947         // first get the degenerate cases out of the way
948         // empty file ?
949         if (bmax<0)
950                 return 0;
951         // after last block ?
952         const RealityBlock & maxblock = m_RealityBlocks[bmax];
953         if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
954                 return GetLineCount();
955
956         // binary search to find correct (or nearest block)
957         int blo=0, bhi=bmax;
958         int i;
959         while (blo<=bhi)
960         {
961                 i = (blo+bhi)/2;
962                 const RealityBlock & block = m_RealityBlocks[i];
963                 if (nRealLine < block.nStartReal)
964                         bhi = i-1;
965                 else if (nRealLine >= block.nStartReal + block.nCount)
966                         blo = i+1;
967                 else
968                         return (nRealLine - block.nStartReal) + block.nStartApparent;
969         }
970         // Should have found it; all real lines should be in a block
971         ASSERT(0);
972         return -1;
973 }
974
975 /**
976 Return underlying real line and ghost adjustment 
977 as nApparentLine = apparent(nRealLine) - nGhostAdjustment 
978
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
981
982 ie, lines 0->0, 1->2, 2->4,  
983 for argument of 3, return 2, and decToReal = 1
984 */
985 int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine, int& decToReal) const
986 {
987         int bmax = (int) m_RealityBlocks.GetUpperBound();
988         // first get the degenerate cases out of the way
989         // empty file ?
990         if (bmax<0) 
991         {
992                 decToReal = 0;
993                 return 0;
994         }
995
996         // after last apparent line ?
997         ASSERT(nApparentLine < GetLineCount());
998
999         // after last block ?
1000         const RealityBlock & maxblock = m_RealityBlocks[bmax];
1001         if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
1002         {
1003                 decToReal = GetLineCount() - nApparentLine;
1004                 return maxblock.nStartReal + maxblock.nCount;
1005         }
1006
1007         // binary search to find correct (or nearest block)
1008         int blo=0, bhi=bmax;
1009         int i;
1010         while (blo<=bhi)
1011         {
1012                 i = (blo+bhi)/2;
1013                 const RealityBlock & block = m_RealityBlocks[i];
1014                 if (nApparentLine < block.nStartApparent)
1015                         bhi = i-1;
1016                 else if (nApparentLine >= block.nStartApparent + block.nCount)
1017                         blo = i+1;
1018                 else // found it inside this block
1019                 {
1020                         decToReal = 0;
1021                         return (nApparentLine - block.nStartApparent) + block.nStartReal;
1022                 }
1023         }
1024         // it is a ghost line just before block blo
1025         decToReal = m_RealityBlocks[blo].nStartApparent - nApparentLine;
1026         return m_RealityBlocks[blo].nStartReal;
1027 }
1028
1029 /**
1030 Return apparent line for this underlying real line, with adjustment : 
1031 nApparent = apparent(nReal) - decToReal
1032
1033 If the previous real line has apparent number   apparent(nReal) - dec, with dec < decToReal, 
1034 return apparent(nReal) - dec + 1
1035 */
1036 int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
1037 {
1038         int blo, bhi;
1039         int nPreviousBlock;
1040         int nApparent;
1041         int bmax = (int) m_RealityBlocks.GetUpperBound();
1042         // first get the degenerate cases out of the way
1043         // empty file ?
1044         if (bmax<0)
1045                 return 0;
1046         // after last block ?
1047         const RealityBlock & maxblock = m_RealityBlocks[bmax];
1048         if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
1049         {
1050                 nPreviousBlock = bmax;
1051                 nApparent = GetLineCount();
1052                 goto limitWithPreviousBlock;
1053         }
1054
1055         // binary search to find correct (or nearest block)
1056         blo=0;
1057         bhi=bmax;
1058         int i;
1059         while (blo<=bhi)
1060         {
1061                 i = (blo+bhi)/2;
1062                 const RealityBlock & block = m_RealityBlocks[i];
1063                 if (nRealLine < block.nStartReal)
1064                         bhi = i-1;
1065                 else if (nRealLine >= block.nStartReal + block.nCount)
1066                         blo = i+1;
1067                 else
1068                 {
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;
1075                 }
1076         }
1077         // Should have found it; all real lines should be in a block
1078         ASSERT(0);
1079         return -1;
1080
1081 limitWithPreviousBlock:
1082         // we must keep above the value lastApparentInPreviousBlock
1083         int lastApparentInPreviousBlock;
1084         if (nPreviousBlock == -1)
1085                 lastApparentInPreviousBlock = -1;
1086         else
1087         {
1088                 const RealityBlock & previousBlock = m_RealityBlocks[nPreviousBlock];
1089                 lastApparentInPreviousBlock = previousBlock.nStartApparent + previousBlock.nCount - 1;
1090         }
1091
1092         while (decToReal --) 
1093         {
1094                 nApparent --;
1095                 if (nApparent == lastApparentInPreviousBlock)
1096                         return nApparent+1;
1097         }
1098         return nApparent;
1099 }
1100
1101 /** Do what we need to do just after we've been reloaded */
1102 void CGhostTextBuffer::FinishLoading()
1103 {
1104         if (!m_bInit) return;
1105         RecomputeRealityMapping();
1106 }
1107
1108 /** Recompute the reality mapping (this is fairly naive) */
1109 void CGhostTextBuffer::RecomputeRealityMapping()
1110 {
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)
1115
1116         // This is a state machine with 2 states
1117
1118         // state 1, i-1 not real line
1119 passingGhosts:
1120         if (i==GetLineCount())
1121                 return;
1122         if (GetLineFlags(i) & LF_GHOST)
1123         {
1124                 ++i;
1125                 goto passingGhosts;
1126         }
1127         // this is the first line of a reality block
1128         block.nStartApparent = i;
1129         block.nStartReal = reality+1;
1130         ++reality;
1131         ++i;
1132         // fall through to other state
1133
1134         // state 2, i-1 is real line
1135 inReality:
1136         if (i==GetLineCount() || (GetLineFlags(i) & LF_GHOST))
1137         {
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())
1145                         return;
1146                 ++i;
1147                 goto passingGhosts;
1148         }
1149         ++reality;
1150         ++i;
1151         goto inReality;
1152 }
1153
1154 /** we recompute EOL from the real line before nStartLine to nEndLine */
1155 void CGhostTextBuffer::RecomputeEOL(CCrystalTextView * pSource, int nStartLine, int nEndLine)
1156 {
1157         if (ApparentLastRealLine() <= nEndLine)
1158         {
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)
1163                                 break;
1164                 if (nRealBeforeStart >= 0)
1165                         nStartLine = nRealBeforeStart;
1166         }
1167         int bLastRealLine = (ApparentLastRealLine() <= nEndLine);
1168         int i;
1169         for (i = nEndLine ; i >= nStartLine ; i --)
1170         {
1171                 if ((GetLineFlags(i) & LF_GHOST) == 0)
1172                 {
1173                         if (bLastRealLine)
1174                         {
1175                                 bLastRealLine = 0;
1176                                 if (m_aLines[i].m_nEolChars != 0) 
1177                                 {
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;
1181                                         if (pSource!=NULL)
1182                                                 UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
1183                                 }
1184                         }
1185                         else
1186                         {
1187                                 if (m_aLines[i].m_nEolChars == 0) 
1188                                 {
1189                                         // if a real line (not the last) has no EOL, add one
1190                                         AppendLine (i, GetDefaultEol(), (int) _tcslen(GetDefaultEol()));
1191                                         if (pSource!=NULL)
1192                                                 UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
1193                                 }
1194                         }
1195                 }
1196                 else 
1197                 {
1198                         if (m_aLines[i].m_nEolChars != 0) 
1199                         {
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;
1203                                 if (pSource!=NULL)
1204                                         UpdateViews (pSource, NULL, UPDATE_HORZRANGE | UPDATE_SINGLELINE, i);
1205                         }
1206                 }
1207         }
1208 }
1209
1210 /** 
1211 Check all lines, and ASSERT if reality blocks differ from flags. 
1212 This means that this only has effect in DEBUG build
1213 */
1214 void CGhostTextBuffer::checkFlagsFromReality(BOOL bFlag) const
1215 {
1216         int bmax = (int) m_RealityBlocks.GetUpperBound();
1217         int b;
1218         int i = 0;
1219         for (b = 0 ; b <= bmax ; b ++)
1220         {
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);
1226         }
1227
1228         for ( ; i < GetLineCount() ; i++)
1229                 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
1230 }
1231
1232 void CGhostTextBuffer::OnNotifyLineHasBeenEdited(int nLine)
1233 {
1234         return;
1235 }
1236
1237