OSDN Git Service

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