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 "ccrystaltextview.h"
21 #include "MergeLineFlags.h"
32 CGhostTextBuffer::CGhostTextBuffer()
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.
43 bool CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource,
46 ASSERT (m_bInit); // Text buffer not yet initialized.
47 // You must call InitNew() or LoadFromFile() first!
49 ASSERT (nLine >= 0 && nLine <= static_cast<intptr_t>(m_aLines.size ()));
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;
57 CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
58 if (pSource != nullptr)
59 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
65 /** InternalDeleteGhostLine accepts only apparent line numbers */
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).
74 bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
75 int nLine, int nCount)
77 ASSERT (m_bInit); // Text buffer not yet initialized.
78 // You must call InitNew() or LoadFromFile() first!
80 ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
85 for (int i = nLine ; i < nLine + nCount; i++)
87 ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 );
91 vector<LineInfo>::iterator iterBegin = m_aLines.begin() + nLine;
92 vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
93 m_aLines.erase(iterBegin, iterEnd);
95 if (pSource != nullptr)
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;
103 if (nLine == GetLineCount())
105 // The last parameter is optimization
106 // - don't recompute lines preceding the removed line.
107 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
115 * @brief Get text of specified lines (ghost lines will not contribute to text).
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
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.
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
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);
142 // estimate size (upper bound)
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();
150 if (nCrlfStyle != CRLFSTYLE::AUTOMATIC)
152 // we must copy this EOL type only
153 const CString sEol = GetStringEol (nCrlfStyle);
155 for (i = nStartLine; i <= nEndLine; ++i)
157 // exclude ghost lines
158 if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && (GetLineFlags(i) & LF_INVISIBLE)))
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));
169 // copy the EOL of the requested type
170 if (i != ApparentLastRealLine())
172 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(tchar_t));
173 pszBuf += sEol.GetLength();
179 for (i = nStartLine; i <= nEndLine; ++i)
181 // exclude ghost lines
182 if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && GetLineFlags(i) & LF_INVISIBLE))
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));
193 // check that we really have an EOL
194 if (i != ApparentLastRealLine() && GetLineLength(i) == GetFullLineLength(i))
196 // Oops, real line lacks EOL
197 // (If this happens, editor probably has bug)
199 CString sEol = GetStringEol (nCrlfStyle);
200 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(tchar_t));
201 pszBuf += sEol.GetLength();
205 text.resize(pszBuf - text.data());
208 ////////////////////////////////////////////////////////////////////////////
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
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).
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*/)
236 bool bGroupFlag = false;
237 bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
238 bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
240 if (bFirstLineGhost && cchText > 0)
242 CString text = GetStringEol(GetCRLFMode());
243 if (bHistory && !m_bUndoGroup)
248 auto reverseFindRealLine = [&](int nLine) {
249 for (; nLine >= 0; --nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
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]))
257 auto findRealLine = [&](int nLine) {
258 for (; nLine < GetLineCount(); ++nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
259 if (nLine == GetLineCount())
263 if (findRealLine(nLine) != -1)
264 CCrystalTextBuffer::InsertText(pSource, nLine, 0, text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
268 if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText,
269 cchText, nEndLine, nEndChar, nAction, bHistory))
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;
280 bDiscrepancyInInsertedLines = false;
282 if (bSpecialLastLineHandling)
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
291 if ((GetLineFlags(nLine) & LF_GHOST) != 0) // first line still marked GHOST
292 bSpecialLastLineHandling = false;
294 bDiscrepancyInInsertedLines = false;
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 ++;
304 for (i = nLine ; i < nEndLine ; i++)
306 // update line revision numbers of modified lines
307 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
308 OnNotifyLineHasBeenEdited(i);
310 if (!bDiscrepancyInInsertedLines)
312 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
313 OnNotifyLineHasBeenEdited(i);
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
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)
331 InternalDeleteGhostLine(pSource, nLineAfterInsertedBlock, i);
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);
341 // if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
344 if (bSpecialLastLineHandling)
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();
353 // now we can recompute
354 if ((nEndLine > nLine) || bFirstLineGhost)
356 // TODO: Be smarter, and don't recompute if it is easy to see what changed
357 RecomputeRealityMapping();
361 FlushUndoGroup (pSource);
363 // nEndLine may have changed during Rescan
364 nEndLine = m_ptLastChange.y;
369 std::vector<uint32_t> *CGhostTextBuffer:: /* virtual override */
370 CopyRevisionNumbers(int nStartLine, int nEndLine) const
372 std::vector<uint32_t> *paSavedRevisionNumbers = CCrystalTextBuffer::CopyRevisionNumbers(nStartLine, nEndLine);
373 for (int nLine = nEndLine; nLine >= nStartLine; --nLine)
375 if ((GetLineFlags(nLine) & LF_GHOST) != 0)
376 paSavedRevisionNumbers->erase(paSavedRevisionNumbers->begin() + (nLine - nStartLine));
378 if ((GetLineFlags(nEndLine) & LF_GHOST) != 0)
380 for (int nLine = nEndLine + 1; nLine < GetLineCount(); ++nLine)
381 if ((GetLineFlags(nLine) & LF_GHOST) == 0)
383 paSavedRevisionNumbers->push_back(GetLineFlags(nLine));
387 return paSavedRevisionNumbers;
390 void CGhostTextBuffer:: /* virtual override */
391 RestoreRevisionNumbers(int nStartLine, std::vector<uint32_t> *paSavedRevisionNumbers)
393 for (int i = 0, j = 0; i < static_cast<int>(paSavedRevisionNumbers->size()); j++)
395 if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0)
397 m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
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*/)
407 int const nLineCount = GetLineCount();
408 while (nEndLine < nLineCount - 1 && GetLineFlags(nEndLine) & LF_GHOST)
410 if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
411 nEndLine, nEndChar, nAction, bHistory))
416 if (nStartChar != 0 || nEndChar != 0)
417 OnNotifyLineHasBeenEdited(nStartLine);
419 // now we can recompute
420 if (nStartLine != nEndLine)
422 // TODO: Be smarter, and don't recompute if it is easy to see what changed
423 RecomputeRealityMapping();
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.
436 bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
438 if (!InternalInsertGhostLine (pSource, nLine))
441 // Set WinMerge flags
442 SetLineFlag (nLine, LF_GHOST, true, false, false);
443 RecomputeRealityMapping();
445 // Don't need to recompute EOL as real lines are unchanged.
446 // Never AddUndoRecord as Rescan clears the ghost lines.
452 * @brief Remove all the ghost lines from the buffer.
454 void CGhostTextBuffer::RemoveAllGhostLines()
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++)
462 if (GetLineFlags(ct) & LF_GHOST)
464 m_aLines[ct].FreeBuffer();
469 if (nFirstGhost >= 0)
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++)
476 if ((GetLineFlags(ct) & LF_GHOST) == 0)
477 m_aLines[newnl++] = m_aLines[ct];
480 // Discard unused entries in one shot
481 m_aLines.resize(newnl);
482 RecomputeRealityMapping();
486 ////////////////////////////////////////////////////////////////////////////
487 // apparent <-> real line conversion
490 * @brief Get last apparent (screen) line index.
491 * @return Last apparent line, or -1 if no lines in the buffer.
493 int CGhostTextBuffer::ApparentLastRealLine() const
495 if (m_RealityBlocks.size() == 0)
497 const RealityBlock &block = m_RealityBlocks.back();
498 return block.nStartApparent + block.nCount - 1;
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,
507 * @param [in] nApparentLine Apparent line for which to get the real line.
508 * @return The real line for the apparent line.
510 int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
513 return ComputeRealLineAndGhostAdjustment(nApparentLine, decToReal);
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.
522 int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
524 const int size = static_cast<int>(m_RealityBlocks.size());
528 // after last block ?
529 const RealityBlock & maxblock = m_RealityBlocks.back();
530 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
531 return GetLineCount();
533 // binary search to find correct (or nearest block)
538 int i = (blo + bhi) / 2;
539 const RealityBlock & block = m_RealityBlocks[i];
540 if (nRealLine < block.nStartReal)
542 else if (nRealLine >= block.nStartReal + block.nCount)
545 return (nRealLine - block.nStartReal) + block.nStartApparent;
547 // Should have found it; all real lines should be in a block
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.
562 int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine,
563 int& decToReal) const
565 const int size = static_cast<int>(m_RealityBlocks.size());
572 // after last apparent line ?
573 ASSERT(nApparentLine < GetLineCount());
575 // after last block ?
576 const RealityBlock & maxblock = m_RealityBlocks.back();
577 if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
579 decToReal = GetLineCount() - nApparentLine;
580 return maxblock.nStartReal + maxblock.nCount;
583 // binary search to find correct (or nearest block)
588 int i = (blo + bhi) / 2;
589 const RealityBlock & block = m_RealityBlocks[i];
590 if (nApparentLine < block.nStartApparent)
592 else if (nApparentLine >= block.nStartApparent + block.nCount)
594 else // found it inside this block
597 return (nApparentLine - block.nStartApparent) + block.nStartReal;
600 // it is a ghost line just before block blo
601 decToReal = m_RealityBlocks[blo].nStartApparent - nApparentLine;
602 return m_RealityBlocks[blo].nStartReal;
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.
612 int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
617 const int size = (int) m_RealityBlocks.size();
624 // after last block ?
625 const RealityBlock & maxblock = m_RealityBlocks.back();
626 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
628 nPreviousBlock = size - 1;
629 nApparent = GetLineCount() - 1;
630 goto limitWithPreviousBlock;
633 // binary search to find correct (or nearest block)
637 const RealityBlock & block = m_RealityBlocks[i];
638 if (nRealLine < block.nStartReal)
640 else if (nRealLine >= block.nStartReal + block.nCount)
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;
652 // Should have found it; all real lines should be in a block
656 limitWithPreviousBlock:
657 // we must keep above the value lastApparentInPreviousBlock
658 int lastApparentInPreviousBlock;
659 if (nPreviousBlock == -1)
660 lastApparentInPreviousBlock = -1;
663 const RealityBlock & previousBlock = m_RealityBlocks[nPreviousBlock];
664 lastApparentInPreviousBlock = previousBlock.nStartApparent + previousBlock.nCount - 1;
670 if (nApparent == lastApparentInPreviousBlock)
671 return nApparent + 1;
676 /** Do what we need to do just after we've been reloaded */
677 void CGhostTextBuffer::FinishLoading()
679 if (!m_bInit) return;
680 RecomputeRealityMapping();
683 /** Recompute the reality mapping (this is fairly naive) */
684 void CGhostTextBuffer::RecomputeRealityMapping()
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)
692 // This is a state machine with 2 states
694 // state 1, i-1 not real line
696 ASSERT( i <= nLineCount );
699 checkFlagsFromReality();
702 if (GetLineFlags(i) & LF_GHOST)
707 // this is the first line of a reality block
708 block.nStartApparent = i;
709 block.nStartReal = reality + 1;
713 // fall through to other state
715 // state 2, i - 1 is real line
717 ASSERT( i <= nLineCount );
718 if (i == nLineCount || (GetLineFlags(i) & LF_GHOST))
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);
726 // Optimize memory allocation
727 if (m_RealityBlocks.capacity() == m_RealityBlocks.size())
729 if (m_RealityBlocks.size() == 0)
730 m_RealityBlocks.reserve(16);
732 // TODO: grow more slowly with really large RealityBlocks
733 m_RealityBlocks.reserve(m_RealityBlocks.size() * 2);
735 m_RealityBlocks.push_back(block);
738 checkFlagsFromReality();
750 Check all lines, and ASSERT if reality blocks differ from flags.
751 This means that this only has effect in DEBUG build
753 void CGhostTextBuffer::checkFlagsFromReality() const
756 const int size = static_cast<int>(m_RealityBlocks.size());
758 for (int b = 0 ; b < size ; b ++)
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);
767 for ( ; i < GetLineCount() ; i++)
768 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
772 void CGhostTextBuffer:: /* virtual base */
773 OnNotifyLineHasBeenEdited(int nLine)
778 void CGhostTextBuffer::
779 CountEolAndLastLineLength(const CEPoint& ptStartPos, const tchar_t* pszText, size_t cchText, int &nLastLineLength, int &nEol)
783 if (m_bTableEditing && m_bAllowNewlinesInQuotes)
785 bool bInQuote = false;
786 const tchar_t* pszLine = m_aLines[ptStartPos.y].GetLine();
787 for (int j = 0; j < ptStartPos.x; ++j)
789 if (pszLine[j] == m_cFieldEnclosure)
790 bInQuote = !bInQuote;
792 for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
794 if (pszText[nTextPos] == m_cFieldEnclosure)
795 bInQuote = !bInQuote;
796 if (!bInQuote && LineInfo::IsEol(pszText[nTextPos]))
798 if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
809 for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
811 if (LineInfo::IsEol(pszText[nTextPos]))
813 if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
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*/)
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);
840 UndoRecord CGhostTextBuffer:: /* virtual override */
841 GetUndoRecord(int nUndoPos) const
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);
849 bool CGhostTextBuffer:: /* virtual override */
850 UndoInsert(CCrystalTextView * pSource, CEPoint & ptCursorPos, const CEPoint apparent_ptStartPos, CEPoint const apparent_ptEndPos, const UndoRecord & ur)
852 // Check that text in the undo buffer matches text in file buffer.
853 // If not, then rescan() has moved lines and undo fails.
855 // we need to put the cursor before the deleted section
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())))
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)
868 if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparent_ptEndPos, ur))
870 // ptCursorPos = apparent_ptStartPos;
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)
880 CEPoint apparentEnd2 = apparent_ptEndPos;
881 apparentEnd2.x = static_cast<LONG>(m_aLines[apparentEnd2.y].FullLength());
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)
886 if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparentEnd2, ur))
888 // ptCursorPos = apparent_ptStartPos;
889 const size_t nLastLine = m_aLines.size() - 1;
890 if (m_aLines[nLastLine].Length() == 0)
892 m_aLines[nLastLine].Clear();
893 if (static_cast<size_t>(ptCursorPos.y) == nLastLine)