OSDN Git Service

f7139a3aa53f9a1ac0df87db54093f89cc9c606c
[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
32 #include "StdAfx.h"
33 #include "GhostTextBuffer.h"
34 #include "MergeLineFlags.h"
35
36 #ifdef _DEBUG
37 #define new DEBUG_NEW
38 #endif
39
40 using std::vector;
41
42 BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
43 //{{AFX_MSG_MAP(CGhostTextBuffer)
44 //}}AFX_MSG_MAP
45 END_MESSAGE_MAP ()
46
47 IMPLEMENT_DYNCREATE (CGhostTextBuffer, CCrystalTextBuffer)
48
49 /**
50  * @brief Constructor.
51  */
52 CGhostTextBuffer::CGhostTextBuffer()
53 {
54 }
55
56 #if 0
57 /**
58  * @brief Insert a ghost line.
59  * @param [in] pSource View into which to insert the line.
60  * @param [in] nLine Line (apparent/screen) where to insert the ghost line.
61  * @return true if the insertion succeeded, false otherwise.
62  */
63 bool CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource,
64                 int nLine)
65 {
66         ASSERT (m_bInit);             //  Text buffer not yet initialized.
67         //  You must call InitNew() or LoadFromFile() first!
68
69         ASSERT (nLine >= 0 && nLine <= static_cast<intptr_t>(m_aLines.size ()));
70
71         CInsertContext context;
72         context.m_ptStart.x = 0;
73         context.m_ptStart.y = nLine;
74         context.m_ptEnd.x = 0;
75         context.m_ptEnd.y = nLine + 1;
76
77         CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
78         if (pSource != NULL)
79                 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
80
81         return true;
82 }
83 #endif
84
85 /** InternalDeleteGhostLine accepts only apparent line numbers */
86 /**
87  * @brief Delete a group of ghost lines.
88  * @param [in] pSource View from which to delete the lines.
89  * @param [in] nLine Line index where to delete the first ghost line.
90  * @param [in] nCount the number of ghost lines to delete
91  * @return true if the deletion succeeded, false otherwise.
92  * @note @p nLine must be an apparent line number (ghost lines added).
93  */
94 bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
95                 int nLine, int nCount)
96 {
97         ASSERT (m_bInit);             //  Text buffer not yet initialized.
98         //  You must call InitNew() or LoadFromFile() first!
99         ASSERT (nCount >= 0);
100         ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
101
102         if (nCount == 0)
103                 return true;
104
105         for (int i = nLine ; i < nLine + nCount; i++)
106         {
107                 ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 );
108                 m_aLines[i].Clear();
109         }
110
111         vector<LineInfo>::iterator iterBegin = m_aLines.begin() + nLine;
112         vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
113         m_aLines.erase(iterBegin, iterEnd);
114
115         if (pSource != NULL)
116         {
117                 CDeleteContext context;
118                 context.m_ptStart.y = nLine;
119                 context.m_ptStart.x = 0;
120                 context.m_ptEnd.y = nLine + nCount;
121                 context.m_ptEnd.x = 0;
122
123                 if (nLine == GetLineCount())
124                         nLine--;
125                 // The last parameter is optimization  
126                 //   - don't recompute lines preceeding the removed line.
127                 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
128                                 nLine);
129         }
130
131         return true;
132 }
133
134 /**
135  * @brief Get text of specified lines (ghost lines will not contribute to text).
136  * 
137  * @param nCrlfStyle determines the EOL type in the returned buffer.
138  * If nCrlfStyle equals CRLF_STYLE_AUTOMATIC, we read the EOL from the line buffer
139  * 
140  * @note This function has its base in CrystalTextBuffer
141  * CrystalTextBuffer::GetTextWithoutEmptys() is for a buffer with no ghost lines.
142  * CrystalTextBuffer::GetText() returns text including ghost lines.
143  * These two base functions never read the EOL from the line buffer, they
144  * use CRLF_STYLE_DOS when nCrlfStyle equals CRLF_STYLE_AUTOMATIC.
145  */
146 void CGhostTextBuffer::                 /* virtual override */
147 GetTextWithoutEmptys(int nStartLine, int nStartChar, 
148                  int nEndLine, int nEndChar, 
149                  CString &text, CRLFSTYLE nCrlfStyle /*= CRLF_STYLE_AUTOMATIC */,
150                  bool bExcludeInvisibleLines /*= true*/) const
151 {
152         const size_t lines = m_aLines.size();
153         ASSERT(nStartLine >= 0 && nStartLine < static_cast<intptr_t>(lines));
154         ASSERT(nStartChar >= 0 && nStartChar <= GetLineLength(nStartLine));
155         ASSERT(nEndLine >= 0 && nEndLine < static_cast<intptr_t>(lines));
156         ASSERT(nEndChar >= 0 && nEndChar <= GetFullLineLength(nEndLine));
157         ASSERT(nStartLine < nEndLine || nStartLine == nEndLine && nStartChar <= nEndChar);
158         // some edit functions (copy...) should do nothing when there is no selection.
159         // assert to be sure to catch these 'do nothing' cases.
160 //      ASSERT(nStartLine != nEndLine || nStartChar != nEndChar);
161
162         // estimate size (upper bound)
163         int nBufSize = 0;
164         int i = 0;
165         for (i = nStartLine; i <= nEndLine; ++i)
166                 nBufSize += (GetFullLineLength(i) + 2); // in case we insert EOLs
167         LPTSTR pszBuf = text.GetBuffer(nBufSize);
168
169         if (nCrlfStyle != CRLF_STYLE_AUTOMATIC)
170         {
171                 // we must copy this EOL type only
172                 const CString sEol = GetStringEol (nCrlfStyle);
173
174                 for (i = nStartLine; i <= nEndLine; ++i)
175                 {
176                         // exclude ghost lines
177                         if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && (GetLineFlags(i) & LF_INVISIBLE)))
178                                 continue;
179
180                         // copy the line, excluding the EOL
181                         int soffset = (i == nStartLine ? nStartChar : 0);
182                         int eoffset = (i == nEndLine ? nEndChar : GetLineLength(i));
183                         int chars = eoffset - soffset;
184                         LPCTSTR szLine = m_aLines[i].GetLine(soffset);
185                         CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
186                         pszBuf += chars;
187
188                         // copy the EOL of the requested type
189                         if (i != ApparentLastRealLine())
190                         {
191                                 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
192                                 pszBuf += sEol.GetLength();
193                         }
194                 }
195         } 
196         else 
197         {
198                 for (i = nStartLine; i <= nEndLine; ++i)
199                 {
200                         // exclude ghost lines
201                         if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && GetLineFlags(i) & LF_INVISIBLE))
202                                 continue;
203
204                         // copy the line including the EOL
205                         int soffset = (i == nStartLine ? nStartChar : 0);
206                         int eoffset = (i == nEndLine ? nEndChar : GetFullLineLength(i));
207                         int chars = eoffset - soffset;
208                         LPCTSTR szLine = m_aLines[i].GetLine(soffset);
209                         CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
210                         pszBuf += chars;
211
212                         // check that we really have an EOL
213                         if (i != ApparentLastRealLine() && GetLineLength(i) == GetFullLineLength(i))
214                         {
215                                 // Oops, real line lacks EOL
216                                 // (If this happens, editor probably has bug)
217                                 ASSERT(false);
218                                 CString sEol = GetStringEol (nCrlfStyle);
219                                 CopyMemory(pszBuf, sEol, sEol.GetLength());
220                                 pszBuf += sEol.GetLength();
221                         }
222                 }
223         }
224         text.ReleaseBuffer(static_cast<int>(pszBuf - text));
225         text.FreeExtra();
226 }
227
228 ////////////////////////////////////////////////////////////////////////////
229 // edition functions
230
231 /**
232  * @brief Insert text to the buffer.
233  * @param [in] pSource View into which to insert the text.
234  * @param [in] nLine Line number (apparent/screen) where the insertion starts.
235  * @param [in] nPos Character position where the insertion starts.
236  * @param [in] pszText The text to insert.
237  * @param [in] cchText The length of text in pszText.
238  * @param [out] nEndLine Line number of last added line in the buffer.
239  * @param [out] nEndChar Character position of the end of the added text
240  *   in the buffer.
241  * @param [in] nAction Edit action.
242  * @param [in] bHistory Save insertion for undo/redo?
243  * @return true if the insertion succeeded, false otherwise.
244  * @note Line numbers are apparent (screen) line numbers, not real
245  * line numbers in the file.
246  * @note @p nEndLine and @p nEndChar are valid as long as you do not call
247  *   FlushUndoGroup. If you need to call FlushUndoGroup, just store them in a
248  *   variable which is preserved with real line number during Rescan
249  *   (m_ptCursorPos, m_ptLastChange for example).
250  */
251 bool CGhostTextBuffer::                 /* virtual override */
252 InsertText (CCrystalTextView * pSource, int nLine,
253                 int nPos, LPCTSTR pszText, size_t cchText, int &nEndLine, int &nEndChar,
254                 int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
255 {
256         bool bGroupFlag = false;
257         bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
258         bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
259
260         if (bFirstLineGhost && cchText > 0)
261         {
262                 CString text = GetStringEol(GetCRLFMode());
263                 if (bHistory && !m_bUndoGroup)
264                 {
265                         BeginUndoGroup();
266                         bGroupFlag = true;
267                 }
268                 auto reverseFindRealLine = [&](int nLine) {
269                         for (; nLine >= 0; --nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
270                         return nLine;
271                 };
272                 int i = reverseFindRealLine(nLine);
273                 if (i >= 0 && !m_aLines[i].HasEol())
274                         CCrystalTextBuffer::InsertText(pSource, i, GetLineLength(i), text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
275                 else if (!LineInfo::IsEol(pszText[cchText - 1]))
276                         CCrystalTextBuffer::InsertText(pSource, nLine, 0, text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
277         }
278
279         if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText,
280                 cchText, nEndLine, nEndChar, nAction, bHistory))
281         {
282                 return false;
283         }
284
285         // when inserting an EOL terminated text into a ghost line,
286         // there is a discrepancy between nInsertedLines and nEndLine-nRealLine
287         bool bDiscrepancyInInsertedLines;
288         if (bFirstLineGhost && nEndChar == 0 && ApparentLastRealLine() >= nEndLine)
289                 bDiscrepancyInInsertedLines = true;
290         else
291                 bDiscrepancyInInsertedLines = false;
292
293         if (bSpecialLastLineHandling)
294         {
295                 // The special case of inserting text into the very last line of a file when
296                 //      that last line is marked as LF_GHOST.  Effectively, the new text is 
297                 //      supposed to go "before" the Ghost, but mechanically the text is inserted
298                 //      into the Ghost itself, with a new Ghost line appearing at the end of the
299                 //      file.  Later (below), the Ghost status of both the first and last inserted 
300                 //      lines will get straightened out, with the trailing Ghost line becomming  
301                 //      a NULL line.
302                 if ((GetLineFlags(nLine) & LF_GHOST) == 0)      // first line still marked GHOST
303                         bSpecialLastLineHandling = false;
304                 else
305                         bDiscrepancyInInsertedLines = false;
306         }
307
308         // compute the number of real lines created (for undo)
309         int nRealLinesCreated = nEndLine - nLine;
310         if (bFirstLineGhost && (nEndChar > 0 || ApparentLastRealLine() < nEndLine))
311                 // we create one more real line
312                 nRealLinesCreated ++;
313
314         int i;
315         for (i = nLine ; i < nEndLine ; i++)
316         {
317                 // update line revision numbers of modified lines
318                 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
319                 OnNotifyLineHasBeenEdited(i);
320         }
321         if (!bDiscrepancyInInsertedLines)
322         {
323                 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
324                 OnNotifyLineHasBeenEdited(i);
325         }
326
327         // when inserting into a ghost line block, we want to replace ghost lines
328         // with our text, so delete some ghost lines below the inserted text
329         if (bFirstLineGhost)
330         {
331                 // where is the first line after the inserted text ?
332                 int nInsertedTextLinesCount = nEndLine - nLine + (bDiscrepancyInInsertedLines ? 0 : 1);
333                 int nLineAfterInsertedBlock = nLine + nInsertedTextLinesCount;
334                 // delete at most nInsertedTextLinesCount - 1 ghost lines
335                 // as the first ghost line has been reused
336                 int nMaxGhostLineToDelete = min(nInsertedTextLinesCount - 1, GetLineCount()-nLineAfterInsertedBlock);
337                 if (nEndChar == 0 && ApparentLastRealLine() < nEndLine)
338                         nMaxGhostLineToDelete --;
339                 for (i = 0 ; i < nMaxGhostLineToDelete ; i++)
340                         if ((GetLineFlags(nLineAfterInsertedBlock+i) & LF_GHOST) == 0)
341                                 break;
342                 InternalDeleteGhostLine(pSource, nLineAfterInsertedBlock, i);
343         }
344
345         for (i = nLine ; i < nEndLine ; i++)
346                 SetLineFlag (i, LF_GHOST, false, false, false);
347         if (!bDiscrepancyInInsertedLines)
348                 // if there is no discrepancy, the final cursor line is real
349                 // as either some text was inserted in it, or it inherits the real status from the first line
350                 SetLineFlag (i, LF_GHOST, false, false, false);
351         else
352                 // if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
353                 ;
354                 
355         if (bSpecialLastLineHandling)
356         {
357                 // By setting the last line (in this special case, see above) to NULL, 
358                 //      the line will eventually be removed or become an actual LF_GHOST line.
359                 int nLastLine = GetLineCount()-1;
360                 ASSERT(m_aLines[nLastLine].FullLength() == 0);
361                 m_aLines[nLastLine].Clear();
362         }
363
364         // now we can recompute
365         if ((nEndLine > nLine) || bFirstLineGhost)
366         {
367                 // TODO: Be smarter, and don't recompute if it is easy to see what changed
368                 RecomputeRealityMapping();
369         }
370
371         if (bGroupFlag)
372                 FlushUndoGroup (pSource);
373
374         // nEndLine may have changed during Rescan
375         nEndLine = m_ptLastChange.y;
376
377         return true;
378 }
379
380 CDWordArray *CGhostTextBuffer::                 /* virtual override */
381 CopyRevisionNumbers(int nStartLine, int nEndLine) const
382 {
383         CDWordArray *paSavedRevisionNumbers = CCrystalTextBuffer::CopyRevisionNumbers(nStartLine, nEndLine);
384         for (int nLine = nEndLine; nLine >= nStartLine; --nLine)
385         {
386                 if ((GetLineFlags(nLine) & LF_GHOST) != 0)
387                         paSavedRevisionNumbers->RemoveAt(nLine - nStartLine);
388         }
389         if ((GetLineFlags(nEndLine) & LF_GHOST) != 0)
390         {
391                 for (int nLine = nEndLine + 1; nLine < GetLineCount(); ++nLine)
392                         if ((GetLineFlags(nLine) & LF_GHOST) == 0)
393                         {
394                                 paSavedRevisionNumbers->Add(GetLineFlags(nLine));
395                                 break;
396                         }
397         }
398         return paSavedRevisionNumbers;
399 }
400
401 void CGhostTextBuffer::                 /* virtual override */
402 RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
403 {
404         for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
405         {
406                 if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0)
407                 {
408                         m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
409                         ++i;
410                 }
411         }
412 }
413
414 bool CGhostTextBuffer::                 /* virtual override */
415 DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar,
416             int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
417 {
418         if ((GetLineFlags(nEndLine) & LF_GHOST) == 0)
419         {
420                 if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
421                         nEndLine, nEndChar, nAction, bHistory))
422                 {
423                         return false;
424                 }
425         }
426         else
427         {
428                 // if the last line in selection to be deleted is a ghost line, 
429                 // the EOL of last real line in selection should not be deleted.
430                 // Otherwise, a line with no EOL will appear.
431                 int nEndLine2 = nEndLine;
432                 int nEndChar2 = nEndChar;
433                 for (; nEndLine2 >= nStartLine; --nEndLine2)
434                 {
435                         if ((GetLineFlags(nEndLine2) & LF_GHOST) == 0)
436                                 break;
437                 }
438                 if (nStartLine <= nEndLine2)
439                 {
440                         if(nEndLine2 != nEndLine)
441                                 nEndChar2 = GetLineLength(nEndLine2);
442                         if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
443                                 nEndLine2, nEndChar2, nAction, bHistory))
444                         {
445                                 return false;
446                         }
447                         InternalDeleteGhostLine(pSource, nStartLine + 1, nEndLine - (nEndLine2 + 1) + 1);
448                 }
449                 else
450                 {
451                         if (bHistory && m_nUndoPosition < static_cast<int>(m_aUndoBuf.size()))
452                                 m_aUndoBuf.resize(m_nUndoPosition);
453                         InternalDeleteGhostLine(pSource, nEndLine2 + 1, nEndLine - (nEndLine2 + 1));
454                 }
455         }
456
457         if (nStartChar != 0 || nEndChar != 0)
458                 OnNotifyLineHasBeenEdited(nStartLine);
459
460         // now we can recompute
461         if (nStartLine != nEndLine)
462         {
463                 // TODO: Be smarter, and don't recompute if it is easy to see what changed
464                 RecomputeRealityMapping();
465         }
466                 
467         return true;
468 }
469
470 #if 0
471 /**
472  * @brief Insert a ghost line to the buffer (and view).
473  * @param [in] pSource The view to which to add the ghost line.
474  * @param [in] Line index (apparent/screen) where to add the ghost line.
475  * @return true if the addition succeeded, false otherwise.
476  */
477 bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
478 {
479         if (!InternalInsertGhostLine (pSource, nLine))
480                 return false;
481
482         // Set WinMerge flags  
483         SetLineFlag (nLine, LF_GHOST, true, false, false);
484         RecomputeRealityMapping();
485
486         // Don't need to recompute EOL as real lines are unchanged.
487         // Never AddUndoRecord as Rescan clears the ghost lines.
488         return true;
489 }
490 #endif
491
492 /**
493  * @brief Remove all the ghost lines from the buffer.
494  */
495 void CGhostTextBuffer::RemoveAllGhostLines()
496 {
497         int nlines = GetLineCount();
498         int nFirstGhost = -1;
499         // Free the buffer of ghost lines, 
500         // remember where the first ghost line occurs
501         for(int ct = 0; ct < nlines; ct++)
502         {
503                 if (GetLineFlags(ct) & LF_GHOST)
504                 {
505                         m_aLines[ct].FreeBuffer();
506                         if (nFirstGhost < 0)
507                                 nFirstGhost = ct;
508                 }
509         }
510         if (nFirstGhost >= 0)
511         {
512                 // Compact non-ghost lines, starting at the first ghost.
513                 // (we copy the buffer address, so the buffer doesn't move and we don't free it)
514                 int newnl = nFirstGhost;
515                 for (int ct = nFirstGhost; ct < nlines; ct++)
516                 {
517                         if ((GetLineFlags(ct) & LF_GHOST) == 0)
518                                 m_aLines[newnl++] = m_aLines[ct];
519                 }
520
521                 // Discard unused entries in one shot
522                 m_aLines.resize(newnl);
523                 RecomputeRealityMapping();
524         }
525 }
526
527 ////////////////////////////////////////////////////////////////////////////
528 // apparent <-> real line conversion
529
530 /**
531  * @brief Get last apparent (screen) line index.
532  * @return Last apparent line, or -1 if no lines in the buffer.
533  */
534 int CGhostTextBuffer::ApparentLastRealLine() const
535 {
536         if (m_RealityBlocks.size() == 0)
537                 return -1;
538         const RealityBlock &block = m_RealityBlocks.back();
539         return block.nStartApparent + block.nCount - 1;
540 }
541
542 /**
543  * @brief Get a real line for the apparent (screen) line.
544  * This function returns the real line for the given apparent (screen) line.
545  * For ghost lines we return next real line. For trailing ghost line we return
546  * last real line + 1). Ie, lines 0->0, 1->2, 2->4, for argument of 3,
547  * return 2.
548  * @param [in] nApparentLine Apparent line for which to get the real line.
549  * @return The real line for the apparent line.
550  */
551 int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
552 {
553         int decToReal;
554         return ComputeRealLineAndGhostAdjustment(nApparentLine, decToReal);
555 }
556
557 /**
558  * @brief Get an apparent (screen) line for the real line.
559  * @param [in] nRealLine Real line for which to get the apparent line.
560  * @return The apparent line for the real line. If real line is out of bounds
561  *   return last valid apparent line + 1.
562  */
563 int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
564 {
565         const int size = static_cast<int>(m_RealityBlocks.size());
566         if (size == 0)
567                 return 0;
568
569         // after last block ?
570         const RealityBlock & maxblock = m_RealityBlocks.back();
571         if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
572                 return GetLineCount();
573
574         // binary search to find correct (or nearest block)
575         int blo = 0;
576         int bhi = size - 1;
577         while (blo <= bhi)
578         {
579                 int i = (blo + bhi) / 2;
580                 const RealityBlock & block = m_RealityBlocks[i];
581                 if (nRealLine < block.nStartReal)
582                         bhi = i - 1;
583                 else if (nRealLine >= block.nStartReal + block.nCount)
584                         blo = i + 1;
585                 else
586                         return (nRealLine - block.nStartReal) + block.nStartApparent;
587         }
588         // Should have found it; all real lines should be in a block
589         ASSERT(false);
590         return -1;
591 }
592
593 /**
594  * @brief Get a real line for apparent (screen) line.
595  * This function returns the real line for the given apparent (screen) line.
596  * For ghost lines we return next real line. For trailing ghost line we return
597  * last real line + 1; i.e. lines 0->0, 1->2, 2->4, for argument of 3,
598  * return 2 and decToReal would be 1.
599  * @param [in] nApparentLine Apparent line for which to get the real line.
600  * @param [out] decToReal Difference of the apparent and real line.
601  * @return The real line for the apparent line.
602  */
603 int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine,
604                 int& decToReal) const
605 {
606         const int size = static_cast<int>(m_RealityBlocks.size());
607         if (size == 0) 
608         {
609                 decToReal = 0;
610                 return 0;
611         }
612
613         // after last apparent line ?
614         ASSERT(nApparentLine < GetLineCount());
615
616         // after last block ?
617         const RealityBlock & maxblock = m_RealityBlocks.back();
618         if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
619         {
620                 decToReal = GetLineCount() - nApparentLine;
621                 return maxblock.nStartReal + maxblock.nCount;
622         }
623
624         // binary search to find correct (or nearest block)
625         int blo = 0;
626         int bhi = size - 1;
627         while (blo <= bhi)
628         {
629                 int i = (blo + bhi) / 2;
630                 const RealityBlock & block = m_RealityBlocks[i];
631                 if (nApparentLine < block.nStartApparent)
632                         bhi = i - 1;
633                 else if (nApparentLine >= block.nStartApparent + block.nCount)
634                         blo = i + 1;
635                 else // found it inside this block
636                 {
637                         decToReal = 0;
638                         return (nApparentLine - block.nStartApparent) + block.nStartReal;
639                 }
640         }
641         // it is a ghost line just before block blo
642         decToReal = m_RealityBlocks[blo].nStartApparent - nApparentLine;
643         return m_RealityBlocks[blo].nStartReal;
644 }
645
646 /**
647  * @brief Get an apparent (screen) line for the real line.
648  * @param [in] nRealLine Real line for which to get the apparent line.
649  * @param [in] decToReal Difference of the apparent and real line.
650  * @return The apparent line for the real line. If real line is out of bounds
651  *   return last valid apparent line + 1.
652  */
653 int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
654 {
655         int nPreviousBlock;
656         int nApparent;
657
658         const int size = (int) m_RealityBlocks.size();
659         int blo = 0;
660         int bhi = size - 1;
661         int i;
662         if (size == 0)
663                 return 0;
664
665         // after last block ?
666         const RealityBlock & maxblock = m_RealityBlocks.back();
667         if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
668         {
669                 nPreviousBlock = size - 1;
670                 nApparent = GetLineCount() - 1;
671                 goto limitWithPreviousBlock;
672         }
673
674         // binary search to find correct (or nearest block)
675         while (blo <= bhi)
676         {
677                 i = (blo + bhi) / 2;
678                 const RealityBlock & block = m_RealityBlocks[i];
679                 if (nRealLine < block.nStartReal)
680                         bhi = i - 1;
681                 else if (nRealLine >= block.nStartReal + block.nCount)
682                         blo = i + 1;
683                 else
684                 {
685                         if (nRealLine > block.nStartReal)
686                                 // limited by the previous line in this block
687                                 return (nRealLine - block.nStartReal) + block.nStartApparent;
688                         nPreviousBlock = i - 1;
689                         nApparent = (nRealLine - block.nStartReal) + block.nStartApparent;
690                         goto limitWithPreviousBlock;
691                 }
692         }
693         // Should have found it; all real lines should be in a block
694         ASSERT(false);
695         return -1;
696
697 limitWithPreviousBlock:
698         // we must keep above the value lastApparentInPreviousBlock
699         int lastApparentInPreviousBlock;
700         if (nPreviousBlock == -1)
701                 lastApparentInPreviousBlock = -1;
702         else
703         {
704                 const RealityBlock & previousBlock = m_RealityBlocks[nPreviousBlock];
705                 lastApparentInPreviousBlock = previousBlock.nStartApparent + previousBlock.nCount - 1;
706         }
707
708         while (decToReal --) 
709         {
710                 nApparent --;
711                 if (nApparent == lastApparentInPreviousBlock)
712                         return nApparent + 1;
713         }
714         return nApparent;
715 }
716
717 /** Do what we need to do just after we've been reloaded */
718 void CGhostTextBuffer::FinishLoading()
719 {
720         if (!m_bInit) return;
721         RecomputeRealityMapping();
722 }
723
724 /** Recompute the reality mapping (this is fairly naive) */
725 void CGhostTextBuffer::RecomputeRealityMapping()
726 {
727         m_RealityBlocks.clear();
728         int reality = -1; // last encountered real line
729         int i = 0; // current line
730         int nLineCount = GetLineCount();
731         RealityBlock block; // current block being traversed (in state 2)
732
733         // This is a state machine with 2 states
734
735         // state 1, i-1 not real line
736 passingGhosts:
737         ASSERT( i <= nLineCount );
738         if (i == nLineCount)
739         {
740                 checkFlagsFromReality();
741                 return;
742         }
743         if (GetLineFlags(i) & LF_GHOST)
744         {
745                 ++i;
746                 goto passingGhosts;
747         }
748         // this is the first line of a reality block
749         block.nStartApparent = i;
750         block.nStartReal = reality + 1;
751         block.nCount = -1;
752         ++reality;
753         ++i;
754         // fall through to other state
755
756         // state 2, i - 1 is real line
757 inReality:
758         ASSERT( i <= nLineCount );
759         if (i == nLineCount || (GetLineFlags(i) & LF_GHOST))
760         {
761                 // i-1 is the last line of a reality block
762                 ASSERT(reality >= 0);
763                 block.nCount = i - block.nStartApparent;
764                 ASSERT(block.nCount > 0);
765                 ASSERT(reality + 1 - block.nStartReal == block.nCount);
766                 
767                 // Optimize memory allocation
768                 if (m_RealityBlocks.capacity() == m_RealityBlocks.size())
769                 {
770                         if (m_RealityBlocks.size() == 0)
771                                 m_RealityBlocks.reserve(16);
772                         else
773                                 // TODO: grow more slowly with really large RealityBlocks
774                                 m_RealityBlocks.reserve(m_RealityBlocks.size() * 2);
775                 }
776                 m_RealityBlocks.push_back(block);
777                 if (i == nLineCount)
778                 {
779                         checkFlagsFromReality();
780                         return;
781                 }
782                 ++i;
783                 goto passingGhosts;
784         }
785         ++reality;
786         ++i;
787         goto inReality;
788 }
789
790 /** 
791 Check all lines, and ASSERT if reality blocks differ from flags. 
792 This means that this only has effect in DEBUG build
793 */
794 void CGhostTextBuffer::checkFlagsFromReality() const
795 {
796 #ifdef _DEBUG
797         const int size = static_cast<int>(m_RealityBlocks.size());
798         int i = 0;
799         for (int b = 0 ; b < size ; b ++)
800         {
801                 const RealityBlock & block = m_RealityBlocks[b];
802                 for ( ; i < block.nStartApparent ; i++)
803                         ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
804                 for ( ; i < block.nStartApparent+block.nCount ; i++)
805                         ASSERT ((GetLineFlags(i) & LF_GHOST) == 0);
806         }
807
808         for ( ; i < GetLineCount() ; i++)
809                 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
810 #endif 
811 }
812
813 void CGhostTextBuffer::                 /* virtual base */
814 OnNotifyLineHasBeenEdited(int nLine)
815 {
816         return;
817 }
818
819 static int CountEol(LPCTSTR pszText, size_t cchText)
820 {
821         int nEol = 0;
822         for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
823         {
824                 if (LineInfo::IsEol(pszText[nTextPos]))
825                 {
826                         if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
827                                 ++nTextPos;
828                         ++nEol;
829                 }
830         }
831         return nEol;
832 }
833
834
835 void CGhostTextBuffer::                 /* virtual override */
836 AddUndoRecord(bool bInsert, const CPoint & ptStartPos,
837         const CPoint & ptEndPos, LPCTSTR pszText, size_t cchText,
838         int nActionType /*= CE_ACTION_UNKNOWN*/,
839         CDWordArray *paSavedRevisionNumbers)
840 {
841         CPoint real_ptStartPos(ptStartPos.x, ComputeRealLine(ptStartPos.y));
842         CPoint real_ptEndPos(ptEndPos.x, real_ptStartPos.y + CountEol(pszText, cchText));
843         CCrystalTextBuffer::AddUndoRecord(bInsert, real_ptStartPos, real_ptEndPos, pszText,
844                 cchText, nActionType, paSavedRevisionNumbers);
845 }
846
847 UndoRecord CGhostTextBuffer::                   /* virtual override */
848 GetUndoRecord(int nUndoPos) const
849 {
850         UndoRecord ur = m_aUndoBuf[nUndoPos];
851         ur.m_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, 0);
852         ur.m_ptEndPos.y = ComputeApparentLine(ur.m_ptEndPos.y, 0);
853         return ur;
854 }
855
856 bool CGhostTextBuffer::         /* virtual override */
857 UndoInsert(CCrystalTextView * pSource, CPoint & ptCursorPos, const CPoint apparent_ptStartPos, CPoint const apparent_ptEndPos, const UndoRecord & ur)
858 {    
859     // Check that text in the undo buffer matches text in file buffer.  
860         // If not, then rescan() has moved lines and undo fails.
861
862     // we need to put the cursor before the deleted section
863     CString text;
864     const size_t size = m_aLines.size();
865     if ((apparent_ptStartPos.y < static_cast<LONG>(size)) &&
866         (apparent_ptStartPos.x <= static_cast<LONG>(m_aLines[apparent_ptStartPos.y].Length())) &&
867         (apparent_ptEndPos.y < static_cast<LONG>(size)) &&
868         (apparent_ptEndPos.x <= static_cast<LONG>(m_aLines[apparent_ptEndPos.y].Length())))
869     {
870                 //  Try to ensure that we are undoing correctly...
871                 //  Just compare the text as it was before Undo operation
872         GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text, CRLF_STYLE_AUTOMATIC, false);
873         if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
874         {
875                         if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparent_ptEndPos, ur))
876                         {
877                                 // ptCursorPos = apparent_ptStartPos;
878                                 return true;
879                         }
880         }
881                 else
882                 // It is possible that the ptEndPos is at the last line of the file and originally pointed
883                 //      at an LF_GHOST line that followed (and has since been discarded).  Lets try to reconstruct
884                 //      that situation before we fail entirely...
885                 if (apparent_ptEndPos.y + 1 == static_cast<LONG>(size) && apparent_ptEndPos.x == 0)
886                 {
887                         CPoint apparentEnd2 = apparent_ptEndPos;
888                         apparentEnd2.x = static_cast<LONG>(m_aLines[apparentEnd2.y].FullLength());
889                         text.Empty();
890                         GetTextWithoutEmptys(apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparentEnd2.x, text, CRLF_STYLE_AUTOMATIC, false);
891                         if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
892                         {
893                                 if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparentEnd2, ur))
894                                 {
895                                         // ptCursorPos = apparent_ptStartPos;
896                                         const size_t nLastLine = m_aLines.size() - 1;
897                                         if (m_aLines[nLastLine].Length() == 0)
898                                         {
899                                                 m_aLines[nLastLine].Clear();
900                                                 if (static_cast<size_t>(ptCursorPos.y) == nLastLine)
901                                                         ptCursorPos.y--;
902                                         }
903                                         return true;
904                                 }
905                         }
906                 }
907     }
908         ASSERT(false);
909         return false;
910 }
911