1 ////////////////////////////////////////////////////////////////////////////
2 // File: GhostTextBuffer.cpp
4 // Created: 31-Jul-2003
6 /////////////////////////////////////////////////////////////////////////////
7 // WinMerge: an interactive diff/merge utility
8 // Copyright (C) 1997-2000 Thingamahoochie Software
10 // SPDX-License-Identifier: GPL-2.0-or-later
11 /////////////////////////////////////////////////////////////////////////////
13 * @file GhostTextBuffer.cpp
15 * @brief Implementation of GhostTextBuffer class.
19 #include "GhostTextBuffer.h"
20 #include "MergeLineFlags.h"
28 BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
29 //{{AFX_MSG_MAP(CGhostTextBuffer)
33 IMPLEMENT_DYNCREATE (CGhostTextBuffer, CCrystalTextBuffer)
38 CGhostTextBuffer::CGhostTextBuffer()
44 * @brief Insert a ghost line.
45 * @param [in] pSource View into which to insert the line.
46 * @param [in] nLine Line (apparent/screen) where to insert the ghost line.
47 * @return true if the insertion succeeded, false otherwise.
49 bool CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource,
52 ASSERT (m_bInit); // Text buffer not yet initialized.
53 // You must call InitNew() or LoadFromFile() first!
55 ASSERT (nLine >= 0 && nLine <= static_cast<intptr_t>(m_aLines.size ()));
57 CInsertContext context;
58 context.m_ptStart.x = 0;
59 context.m_ptStart.y = nLine;
60 context.m_ptEnd.x = 0;
61 context.m_ptEnd.y = nLine + 1;
63 CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
64 if (pSource != nullptr)
65 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
71 /** InternalDeleteGhostLine accepts only apparent line numbers */
73 * @brief Delete a group of ghost lines.
74 * @param [in] pSource View from which to delete the lines.
75 * @param [in] nLine Line index where to delete the first ghost line.
76 * @param [in] nCount the number of ghost lines to delete
77 * @return true if the deletion succeeded, false otherwise.
78 * @note @p nLine must be an apparent line number (ghost lines added).
80 bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
81 int nLine, int nCount)
83 ASSERT (m_bInit); // Text buffer not yet initialized.
84 // You must call InitNew() or LoadFromFile() first!
86 ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
91 for (int i = nLine ; i < nLine + nCount; i++)
93 ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 );
97 vector<LineInfo>::iterator iterBegin = m_aLines.begin() + nLine;
98 vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
99 m_aLines.erase(iterBegin, iterEnd);
101 if (pSource != nullptr)
103 CDeleteContext context;
104 context.m_ptStart.y = nLine;
105 context.m_ptStart.x = 0;
106 context.m_ptEnd.y = nLine + nCount;
107 context.m_ptEnd.x = 0;
109 if (nLine == GetLineCount())
111 // The last parameter is optimization
112 // - don't recompute lines preceding the removed line.
113 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
121 * @brief Get text of specified lines (ghost lines will not contribute to text).
123 * @param nCrlfStyle determines the EOL type in the returned buffer.
124 * If nCrlfStyle equals CRLFSTYLE::AUTOMATIC, we read the EOL from the line buffer
126 * @note This function has its base in CrystalTextBuffer
127 * CrystalTextBuffer::GetTextWithoutEmptys() is for a buffer with no ghost lines.
128 * CrystalTextBuffer::GetText() returns text including ghost lines.
129 * These two base functions never read the EOL from the line buffer, they
130 * use CRLFSTYLE::DOS when nCrlfStyle equals CRLFSTYLE::AUTOMATIC.
132 void CGhostTextBuffer:: /* virtual override */
133 GetTextWithoutEmptys(int nStartLine, int nStartChar,
134 int nEndLine, int nEndChar,
135 CString &text, CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC */,
136 bool bExcludeInvisibleLines /*= true*/) const
138 const size_t lines = m_aLines.size();
139 ASSERT(nStartLine >= 0 && nStartLine < static_cast<intptr_t>(lines));
140 ASSERT(nStartChar >= 0 && nStartChar <= GetLineLength(nStartLine));
141 ASSERT(nEndLine >= 0 && nEndLine < static_cast<intptr_t>(lines));
142 ASSERT(nEndChar >= 0 && nEndChar <= GetFullLineLength(nEndLine));
143 ASSERT(nStartLine < nEndLine || nStartLine == nEndLine && nStartChar <= nEndChar);
144 // some edit functions (copy...) should do nothing when there is no selection.
145 // assert to be sure to catch these 'do nothing' cases.
146 // ASSERT(nStartLine != nEndLine || nStartChar != nEndChar);
148 // estimate size (upper bound)
151 for (i = nStartLine; i <= nEndLine; ++i)
152 nBufSize += (GetFullLineLength(i) + 2); // in case we insert EOLs
153 LPTSTR pszBuf = text.GetBuffer(nBufSize);
155 if (nCrlfStyle != CRLFSTYLE::AUTOMATIC)
157 // we must copy this EOL type only
158 const CString sEol = GetStringEol (nCrlfStyle);
160 for (i = nStartLine; i <= nEndLine; ++i)
162 // exclude ghost lines
163 if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && (GetLineFlags(i) & LF_INVISIBLE)))
166 // copy the line, excluding the EOL
167 int soffset = (i == nStartLine ? nStartChar : 0);
168 int eoffset = (i == nEndLine ? nEndChar : GetLineLength(i));
169 int chars = eoffset - soffset;
170 LPCTSTR szLine = m_aLines[i].GetLine(soffset);
171 CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
174 // copy the EOL of the requested type
175 if (i != ApparentLastRealLine())
177 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
178 pszBuf += sEol.GetLength();
184 for (i = nStartLine; i <= nEndLine; ++i)
186 // exclude ghost lines
187 if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && GetLineFlags(i) & LF_INVISIBLE))
190 // copy the line including the EOL
191 int soffset = (i == nStartLine ? nStartChar : 0);
192 int eoffset = (i == nEndLine ? nEndChar : GetFullLineLength(i));
193 int chars = eoffset - soffset;
194 LPCTSTR szLine = m_aLines[i].GetLine(soffset);
195 CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
198 // check that we really have an EOL
199 if (i != ApparentLastRealLine() && GetLineLength(i) == GetFullLineLength(i))
201 // Oops, real line lacks EOL
202 // (If this happens, editor probably has bug)
204 CString sEol = GetStringEol (nCrlfStyle);
205 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
206 pszBuf += sEol.GetLength();
210 text.ReleaseBuffer(static_cast<int>(pszBuf - text));
214 ////////////////////////////////////////////////////////////////////////////
218 * @brief Insert text to the buffer.
219 * @param [in] pSource View into which to insert the text.
220 * @param [in] nLine Line number (apparent/screen) where the insertion starts.
221 * @param [in] nPos Character position where the insertion starts.
222 * @param [in] pszText The text to insert.
223 * @param [in] cchText The length of text in pszText.
224 * @param [out] nEndLine Line number of last added line in the buffer.
225 * @param [out] nEndChar Character position of the end of the added text
227 * @param [in] nAction Edit action.
228 * @param [in] bHistory Save insertion for undo/redo?
229 * @return true if the insertion succeeded, false otherwise.
230 * @note Line numbers are apparent (screen) line numbers, not real
231 * line numbers in the file.
232 * @note @p nEndLine and @p nEndChar are valid as long as you do not call
233 * FlushUndoGroup. If you need to call FlushUndoGroup, just store them in a
234 * variable which is preserved with real line number during Rescan
235 * (m_ptCursorPos, m_ptLastChange for example).
237 bool CGhostTextBuffer:: /* virtual override */
238 InsertText (CCrystalTextView * pSource, int nLine,
239 int nPos, LPCTSTR pszText, size_t cchText, int &nEndLine, int &nEndChar,
240 int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
242 bool bGroupFlag = false;
243 bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
244 bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
246 if (bFirstLineGhost && cchText > 0)
248 CString text = GetStringEol(GetCRLFMode());
249 if (bHistory && !m_bUndoGroup)
254 auto reverseFindRealLine = [&](int nLine) {
255 for (; nLine >= 0; --nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
258 int i = reverseFindRealLine(nLine);
259 if (i >= 0 && !m_aLines[i].HasEol())
260 CCrystalTextBuffer::InsertText(pSource, i, GetLineLength(i), text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
261 else if (!LineInfo::IsEol(pszText[cchText - 1]))
263 auto findRealLine = [&](int nLine) {
264 for (; nLine < GetLineCount(); ++nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
265 if (nLine == GetLineCount())
269 if (findRealLine(nLine) != -1)
270 CCrystalTextBuffer::InsertText(pSource, nLine, 0, text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
274 if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText,
275 cchText, nEndLine, nEndChar, nAction, bHistory))
280 // when inserting an EOL terminated text into a ghost line,
281 // there is a discrepancy between nInsertedLines and nEndLine-nRealLine
282 bool bDiscrepancyInInsertedLines;
283 if (bFirstLineGhost && nEndChar == 0 && ApparentLastRealLine() >= nEndLine)
284 bDiscrepancyInInsertedLines = true;
286 bDiscrepancyInInsertedLines = false;
288 if (bSpecialLastLineHandling)
290 // The special case of inserting text into the very last line of a file when
291 // that last line is marked as LF_GHOST. Effectively, the new text is
292 // supposed to go "before" the Ghost, but mechanically the text is inserted
293 // into the Ghost itself, with a new Ghost line appearing at the end of the
294 // file. Later (below), the Ghost status of both the first and last inserted
295 // lines will get straightened out, with the trailing Ghost line becomming
297 if ((GetLineFlags(nLine) & LF_GHOST) != 0) // first line still marked GHOST
298 bSpecialLastLineHandling = false;
300 bDiscrepancyInInsertedLines = false;
303 // compute the number of real lines created (for undo)
304 int nRealLinesCreated = nEndLine - nLine;
305 if (bFirstLineGhost && (nEndChar > 0 || ApparentLastRealLine() < nEndLine))
306 // we create one more real line
307 nRealLinesCreated ++;
310 for (i = nLine ; i < nEndLine ; i++)
312 // update line revision numbers of modified lines
313 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
314 OnNotifyLineHasBeenEdited(i);
316 if (!bDiscrepancyInInsertedLines)
318 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
319 OnNotifyLineHasBeenEdited(i);
322 // when inserting into a ghost line block, we want to replace ghost lines
323 // with our text, so delete some ghost lines below the inserted text
326 // where is the first line after the inserted text ?
327 int nInsertedTextLinesCount = nEndLine - nLine + (bDiscrepancyInInsertedLines ? 0 : 1);
328 int nLineAfterInsertedBlock = nLine + nInsertedTextLinesCount;
329 // delete at most nInsertedTextLinesCount - 1 ghost lines
330 // as the first ghost line has been reused
331 int nMaxGhostLineToDelete = min(nInsertedTextLinesCount - 1, GetLineCount()-nLineAfterInsertedBlock);
332 if (nEndChar == 0 && ApparentLastRealLine() < nEndLine)
333 nMaxGhostLineToDelete --;
334 for (i = 0 ; i < nMaxGhostLineToDelete ; i++)
335 if ((GetLineFlags(nLineAfterInsertedBlock+i) & LF_GHOST) == 0)
337 InternalDeleteGhostLine(pSource, nLineAfterInsertedBlock, i);
340 for (i = nLine ; i < nEndLine ; i++)
341 SetLineFlag (i, LF_GHOST, false, false, false);
342 if (!bDiscrepancyInInsertedLines)
343 // if there is no discrepancy, the final cursor line is real
344 // as either some text was inserted in it, or it inherits the real status from the first line
345 SetLineFlag (i, LF_GHOST, false, false, false);
347 // if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
350 if (bSpecialLastLineHandling)
352 // By setting the last line (in this special case, see above) to `nullptr`,
353 // the line will eventually be removed or become an actual LF_GHOST line.
354 int nLastLine = GetLineCount()-1;
355 ASSERT(m_aLines[nLastLine].FullLength() == 0);
356 m_aLines[nLastLine].Clear();
359 // now we can recompute
360 if ((nEndLine > nLine) || bFirstLineGhost)
362 // TODO: Be smarter, and don't recompute if it is easy to see what changed
363 RecomputeRealityMapping();
367 FlushUndoGroup (pSource);
369 // nEndLine may have changed during Rescan
370 nEndLine = m_ptLastChange.y;
375 CDWordArray *CGhostTextBuffer:: /* virtual override */
376 CopyRevisionNumbers(int nStartLine, int nEndLine) const
378 CDWordArray *paSavedRevisionNumbers = CCrystalTextBuffer::CopyRevisionNumbers(nStartLine, nEndLine);
379 for (int nLine = nEndLine; nLine >= nStartLine; --nLine)
381 if ((GetLineFlags(nLine) & LF_GHOST) != 0)
382 paSavedRevisionNumbers->RemoveAt(nLine - nStartLine);
384 if ((GetLineFlags(nEndLine) & LF_GHOST) != 0)
386 for (int nLine = nEndLine + 1; nLine < GetLineCount(); ++nLine)
387 if ((GetLineFlags(nLine) & LF_GHOST) == 0)
389 paSavedRevisionNumbers->Add(GetLineFlags(nLine));
393 return paSavedRevisionNumbers;
396 void CGhostTextBuffer:: /* virtual override */
397 RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
399 for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
401 if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0)
403 m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
409 bool CGhostTextBuffer:: /* virtual override */
410 DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar,
411 int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
413 int const nLineCount = GetLineCount();
414 while (nEndLine < nLineCount - 1 && GetLineFlags(nEndLine) & LF_GHOST)
416 if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
417 nEndLine, nEndChar, nAction, bHistory))
422 if (nStartChar != 0 || nEndChar != 0)
423 OnNotifyLineHasBeenEdited(nStartLine);
425 // now we can recompute
426 if (nStartLine != nEndLine)
428 // TODO: Be smarter, and don't recompute if it is easy to see what changed
429 RecomputeRealityMapping();
437 * @brief Insert a ghost line to the buffer (and view).
438 * @param [in] pSource The view to which to add the ghost line.
439 * @param [in] Line index (apparent/screen) where to add the ghost line.
440 * @return true if the addition succeeded, false otherwise.
442 bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
444 if (!InternalInsertGhostLine (pSource, nLine))
447 // Set WinMerge flags
448 SetLineFlag (nLine, LF_GHOST, true, false, false);
449 RecomputeRealityMapping();
451 // Don't need to recompute EOL as real lines are unchanged.
452 // Never AddUndoRecord as Rescan clears the ghost lines.
458 * @brief Remove all the ghost lines from the buffer.
460 void CGhostTextBuffer::RemoveAllGhostLines()
462 int nlines = GetLineCount();
463 int nFirstGhost = -1;
464 // Free the buffer of ghost lines,
465 // remember where the first ghost line occurs
466 for(int ct = 0; ct < nlines; ct++)
468 if (GetLineFlags(ct) & LF_GHOST)
470 m_aLines[ct].FreeBuffer();
475 if (nFirstGhost >= 0)
477 // Compact non-ghost lines, starting at the first ghost.
478 // (we copy the buffer address, so the buffer doesn't move and we don't free it)
479 int newnl = nFirstGhost;
480 for (int ct = nFirstGhost; ct < nlines; ct++)
482 if ((GetLineFlags(ct) & LF_GHOST) == 0)
483 m_aLines[newnl++] = m_aLines[ct];
486 // Discard unused entries in one shot
487 m_aLines.resize(newnl);
488 RecomputeRealityMapping();
492 ////////////////////////////////////////////////////////////////////////////
493 // apparent <-> real line conversion
496 * @brief Get last apparent (screen) line index.
497 * @return Last apparent line, or -1 if no lines in the buffer.
499 int CGhostTextBuffer::ApparentLastRealLine() const
501 if (m_RealityBlocks.size() == 0)
503 const RealityBlock &block = m_RealityBlocks.back();
504 return block.nStartApparent + block.nCount - 1;
508 * @brief Get a real line for the apparent (screen) line.
509 * This function returns the real line for the given apparent (screen) line.
510 * For ghost lines we return next real line. For trailing ghost line we return
511 * last real line + 1). Ie, lines 0->0, 1->2, 2->4, for argument of 3,
513 * @param [in] nApparentLine Apparent line for which to get the real line.
514 * @return The real line for the apparent line.
516 int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
519 return ComputeRealLineAndGhostAdjustment(nApparentLine, decToReal);
523 * @brief Get an apparent (screen) line for the real line.
524 * @param [in] nRealLine Real line for which to get the apparent line.
525 * @return The apparent line for the real line. If real line is out of bounds
526 * return last valid apparent line + 1.
528 int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
530 const int size = static_cast<int>(m_RealityBlocks.size());
534 // after last block ?
535 const RealityBlock & maxblock = m_RealityBlocks.back();
536 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
537 return GetLineCount();
539 // binary search to find correct (or nearest block)
544 int i = (blo + bhi) / 2;
545 const RealityBlock & block = m_RealityBlocks[i];
546 if (nRealLine < block.nStartReal)
548 else if (nRealLine >= block.nStartReal + block.nCount)
551 return (nRealLine - block.nStartReal) + block.nStartApparent;
553 // Should have found it; all real lines should be in a block
559 * @brief Get a real line for apparent (screen) line.
560 * This function returns the real line for the given apparent (screen) line.
561 * For ghost lines we return next real line. For trailing ghost line we return
562 * last real line + 1; i.e. lines 0->0, 1->2, 2->4, for argument of 3,
563 * return 2 and decToReal would be 1.
564 * @param [in] nApparentLine Apparent line for which to get the real line.
565 * @param [out] decToReal Difference of the apparent and real line.
566 * @return The real line for the apparent line.
568 int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine,
569 int& decToReal) const
571 const int size = static_cast<int>(m_RealityBlocks.size());
578 // after last apparent line ?
579 ASSERT(nApparentLine < GetLineCount());
581 // after last block ?
582 const RealityBlock & maxblock = m_RealityBlocks.back();
583 if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
585 decToReal = GetLineCount() - nApparentLine;
586 return maxblock.nStartReal + maxblock.nCount;
589 // binary search to find correct (or nearest block)
594 int i = (blo + bhi) / 2;
595 const RealityBlock & block = m_RealityBlocks[i];
596 if (nApparentLine < block.nStartApparent)
598 else if (nApparentLine >= block.nStartApparent + block.nCount)
600 else // found it inside this block
603 return (nApparentLine - block.nStartApparent) + block.nStartReal;
606 // it is a ghost line just before block blo
607 decToReal = m_RealityBlocks[blo].nStartApparent - nApparentLine;
608 return m_RealityBlocks[blo].nStartReal;
612 * @brief Get an apparent (screen) line for the real line.
613 * @param [in] nRealLine Real line for which to get the apparent line.
614 * @param [in] decToReal Difference of the apparent and real line.
615 * @return The apparent line for the real line. If real line is out of bounds
616 * return last valid apparent line + 1.
618 int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
623 const int size = (int) m_RealityBlocks.size();
630 // after last block ?
631 const RealityBlock & maxblock = m_RealityBlocks.back();
632 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
634 nPreviousBlock = size - 1;
635 nApparent = GetLineCount() - 1;
636 goto limitWithPreviousBlock;
639 // binary search to find correct (or nearest block)
643 const RealityBlock & block = m_RealityBlocks[i];
644 if (nRealLine < block.nStartReal)
646 else if (nRealLine >= block.nStartReal + block.nCount)
650 if (nRealLine > block.nStartReal)
651 // limited by the previous line in this block
652 return (nRealLine - block.nStartReal) + block.nStartApparent;
653 nPreviousBlock = i - 1;
654 nApparent = (nRealLine - block.nStartReal) + block.nStartApparent;
655 goto limitWithPreviousBlock;
658 // Should have found it; all real lines should be in a block
662 limitWithPreviousBlock:
663 // we must keep above the value lastApparentInPreviousBlock
664 int lastApparentInPreviousBlock;
665 if (nPreviousBlock == -1)
666 lastApparentInPreviousBlock = -1;
669 const RealityBlock & previousBlock = m_RealityBlocks[nPreviousBlock];
670 lastApparentInPreviousBlock = previousBlock.nStartApparent + previousBlock.nCount - 1;
676 if (nApparent == lastApparentInPreviousBlock)
677 return nApparent + 1;
682 /** Do what we need to do just after we've been reloaded */
683 void CGhostTextBuffer::FinishLoading()
685 if (!m_bInit) return;
686 RecomputeRealityMapping();
689 /** Recompute the reality mapping (this is fairly naive) */
690 void CGhostTextBuffer::RecomputeRealityMapping()
692 m_RealityBlocks.clear();
693 int reality = -1; // last encountered real line
694 int i = 0; // current line
695 int nLineCount = GetLineCount();
696 RealityBlock block; // current block being traversed (in state 2)
698 // This is a state machine with 2 states
700 // state 1, i-1 not real line
702 ASSERT( i <= nLineCount );
705 checkFlagsFromReality();
708 if (GetLineFlags(i) & LF_GHOST)
713 // this is the first line of a reality block
714 block.nStartApparent = i;
715 block.nStartReal = reality + 1;
719 // fall through to other state
721 // state 2, i - 1 is real line
723 ASSERT( i <= nLineCount );
724 if (i == nLineCount || (GetLineFlags(i) & LF_GHOST))
726 // i-1 is the last line of a reality block
727 ASSERT(reality >= 0);
728 block.nCount = i - block.nStartApparent;
729 ASSERT(block.nCount > 0);
730 ASSERT(reality + 1 - block.nStartReal == block.nCount);
732 // Optimize memory allocation
733 if (m_RealityBlocks.capacity() == m_RealityBlocks.size())
735 if (m_RealityBlocks.size() == 0)
736 m_RealityBlocks.reserve(16);
738 // TODO: grow more slowly with really large RealityBlocks
739 m_RealityBlocks.reserve(m_RealityBlocks.size() * 2);
741 m_RealityBlocks.push_back(block);
744 checkFlagsFromReality();
756 Check all lines, and ASSERT if reality blocks differ from flags.
757 This means that this only has effect in DEBUG build
759 void CGhostTextBuffer::checkFlagsFromReality() const
762 const int size = static_cast<int>(m_RealityBlocks.size());
764 for (int b = 0 ; b < size ; b ++)
766 const RealityBlock & block = m_RealityBlocks[b];
767 for ( ; i < block.nStartApparent ; i++)
768 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
769 for ( ; i < block.nStartApparent+block.nCount ; i++)
770 ASSERT ((GetLineFlags(i) & LF_GHOST) == 0);
773 for ( ; i < GetLineCount() ; i++)
774 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
778 void CGhostTextBuffer:: /* virtual base */
779 OnNotifyLineHasBeenEdited(int nLine)
784 void CGhostTextBuffer::
785 CountEolAndLastLineLength(const CPoint& ptStartPos, LPCTSTR pszText, size_t cchText, int &nLastLineLength, int &nEol)
789 if (m_bTableEditing && m_bAllowNewlinesInQuotes)
791 bool bInQuote = false;
792 const TCHAR* pszLine = m_aLines[ptStartPos.y].GetLine();
793 for (int j = 0; j < ptStartPos.x; ++j)
795 if (pszLine[j] == m_cFieldEnclosure)
796 bInQuote = !bInQuote;
798 for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
800 if (pszText[nTextPos] == m_cFieldEnclosure)
801 bInQuote = !bInQuote;
802 if (!bInQuote && LineInfo::IsEol(pszText[nTextPos]))
804 if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
815 for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
817 if (LineInfo::IsEol(pszText[nTextPos]))
819 if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
830 void CGhostTextBuffer:: /* virtual override */
831 AddUndoRecord(bool bInsert, const CPoint & ptStartPos,
832 const CPoint & ptEndPos, LPCTSTR pszText, size_t cchText,
833 int nActionType /*= CE_ACTION_UNKNOWN*/,
834 CDWordArray *paSavedRevisionNumbers /*= nullptr*/)
836 CPoint real_ptStartPos(ptStartPos.x, ComputeRealLine(ptStartPos.y));
837 int nLastLineLength, nEol;
838 CountEolAndLastLineLength(ptStartPos, pszText, cchText, nLastLineLength, nEol);
839 CPoint real_ptEndPos(ptEndPos.x, real_ptStartPos.y + nEol);
840 if (ptEndPos.x == 0 && cchText > 0 && !LineInfo::IsEol(pszText[cchText - 1]))
841 real_ptEndPos.x = nLastLineLength;
842 CCrystalTextBuffer::AddUndoRecord(bInsert, real_ptStartPos, real_ptEndPos, pszText,
843 cchText, nActionType, paSavedRevisionNumbers);
846 UndoRecord CGhostTextBuffer:: /* virtual override */
847 GetUndoRecord(int nUndoPos) const
849 UndoRecord ur = m_aUndoBuf[nUndoPos];
850 ur.m_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, 0);
851 ur.m_ptEndPos.y = ComputeApparentLine(ur.m_ptEndPos.y, 0);
855 bool CGhostTextBuffer:: /* virtual override */
856 UndoInsert(CCrystalTextView * pSource, CPoint & ptCursorPos, const CPoint apparent_ptStartPos, CPoint const apparent_ptEndPos, const UndoRecord & ur)
858 // Check that text in the undo buffer matches text in file buffer.
859 // If not, then rescan() has moved lines and undo fails.
861 // we need to put the cursor before the deleted section
863 const size_t size = m_aLines.size();
864 if ((apparent_ptStartPos.y < static_cast<LONG>(size)) &&
865 (apparent_ptStartPos.x <= static_cast<LONG>(m_aLines[apparent_ptStartPos.y].Length())) &&
866 (apparent_ptEndPos.y < static_cast<LONG>(size)) &&
867 (apparent_ptEndPos.x <= static_cast<LONG>(m_aLines[apparent_ptEndPos.y].Length())))
869 // Try to ensure that we are undoing correctly...
870 // Just compare the text as it was before Undo operation
871 GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text, CRLFSTYLE::AUTOMATIC, false);
872 if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
874 if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparent_ptEndPos, ur))
876 // ptCursorPos = apparent_ptStartPos;
881 // It is possible that the ptEndPos is at the last line of the file and originally pointed
882 // at an LF_GHOST line that followed (and has since been discarded). Lets try to reconstruct
883 // that situation before we fail entirely...
884 if (apparent_ptEndPos.y + 1 == static_cast<LONG>(size) && apparent_ptEndPos.x == 0)
886 CPoint apparentEnd2 = apparent_ptEndPos;
887 apparentEnd2.x = static_cast<LONG>(m_aLines[apparentEnd2.y].FullLength());
889 GetTextWithoutEmptys(apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparentEnd2.x, text, CRLFSTYLE::AUTOMATIC, false);
890 if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
892 if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparentEnd2, ur))
894 // ptCursorPos = apparent_ptStartPos;
895 const size_t nLastLine = m_aLines.size() - 1;
896 if (m_aLines[nLastLine].Length() == 0)
898 m_aLines[nLastLine].Clear();
899 if (static_cast<size_t>(ptCursorPos.y) == nLastLine)