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
11 // This program is free software; you can redistribute it and/or modify
12 // it under the terms of the GNU General Public License as published by
13 // the Free Software Foundation; either version 2 of the License, or
14 // (at your option) any later version.
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 /////////////////////////////////////////////////////////////////////////////
27 * @file GhostTextBuffer.cpp
29 * @brief Implementation of GhostTextBuffer class.
33 #include "GhostTextBuffer.h"
34 #include "MergeLineFlags.h"
42 BEGIN_MESSAGE_MAP (CGhostTextBuffer, CCrystalTextBuffer)
43 //{{AFX_MSG_MAP(CGhostTextBuffer)
47 IMPLEMENT_DYNCREATE (CGhostTextBuffer, CCrystalTextBuffer)
52 CGhostTextBuffer::CGhostTextBuffer()
58 * @brief Insert a ghost line.
59 * @param [in] pSource View into which to insert the line.
60 * @param [in] nLine Line (apparent/screen) where to insert the ghost line.
61 * @return true if the insertion succeeded, false otherwise.
63 bool CGhostTextBuffer::InternalInsertGhostLine (CCrystalTextView * pSource,
66 ASSERT (m_bInit); // Text buffer not yet initialized.
67 // You must call InitNew() or LoadFromFile() first!
69 ASSERT (nLine >= 0 && nLine <= static_cast<intptr_t>(m_aLines.size ()));
71 CInsertContext context;
72 context.m_ptStart.x = 0;
73 context.m_ptStart.y = nLine;
74 context.m_ptEnd.x = 0;
75 context.m_ptEnd.y = nLine + 1;
77 CCrystalTextBuffer::InsertLine (_T(""), 0, nLine);
79 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
85 /** InternalDeleteGhostLine accepts only apparent line numbers */
87 * @brief Delete a group of ghost lines.
88 * @param [in] pSource View from which to delete the lines.
89 * @param [in] nLine Line index where to delete the first ghost line.
90 * @param [in] nCount the number of ghost lines to delete
91 * @return true if the deletion succeeded, false otherwise.
92 * @note @p nLine must be an apparent line number (ghost lines added).
94 bool CGhostTextBuffer::InternalDeleteGhostLine (CCrystalTextView * pSource,
95 int nLine, int nCount)
97 ASSERT (m_bInit); // Text buffer not yet initialized.
98 // You must call InitNew() or LoadFromFile() first!
100 ASSERT (nLine >= 0 && (nLine + nCount) <= static_cast<intptr_t>(m_aLines.size ()));
105 for (int i = nLine ; i < nLine + nCount; i++)
107 ASSERT ( (GetLineFlags(i) & LF_GHOST) != 0 );
111 vector<LineInfo>::iterator iterBegin = m_aLines.begin() + nLine;
112 vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
113 m_aLines.erase(iterBegin, iterEnd);
117 CDeleteContext context;
118 context.m_ptStart.y = nLine;
119 context.m_ptStart.x = 0;
120 context.m_ptEnd.y = nLine + nCount;
121 context.m_ptEnd.x = 0;
123 if (nLine == GetLineCount())
125 // The last parameter is optimization
126 // - don't recompute lines preceeding the removed line.
127 UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE,
135 * @brief Get text of specified lines (ghost lines will not contribute to text).
137 * @param nCrlfStyle determines the EOL type in the returned buffer.
138 * If nCrlfStyle equals CRLF_STYLE_AUTOMATIC, we read the EOL from the line buffer
140 * @note This function has its base in CrystalTextBuffer
141 * CrystalTextBuffer::GetTextWithoutEmptys() is for a buffer with no ghost lines.
142 * CrystalTextBuffer::GetText() returns text including ghost lines.
143 * These two base functions never read the EOL from the line buffer, they
144 * use CRLF_STYLE_DOS when nCrlfStyle equals CRLF_STYLE_AUTOMATIC.
146 void CGhostTextBuffer:: /* virtual override */
147 GetTextWithoutEmptys(int nStartLine, int nStartChar,
148 int nEndLine, int nEndChar,
149 CString &text, CRLFSTYLE nCrlfStyle /*= CRLF_STYLE_AUTOMATIC */,
150 bool bExcludeInvisibleLines /*= true*/) const
152 const size_t lines = m_aLines.size();
153 ASSERT(nStartLine >= 0 && nStartLine < static_cast<intptr_t>(lines));
154 ASSERT(nStartChar >= 0 && nStartChar <= GetLineLength(nStartLine));
155 ASSERT(nEndLine >= 0 && nEndLine < static_cast<intptr_t>(lines));
156 ASSERT(nEndChar >= 0 && nEndChar <= GetFullLineLength(nEndLine));
157 ASSERT(nStartLine < nEndLine || nStartLine == nEndLine && nStartChar <= nEndChar);
158 // some edit functions (copy...) should do nothing when there is no selection.
159 // assert to be sure to catch these 'do nothing' cases.
160 // ASSERT(nStartLine != nEndLine || nStartChar != nEndChar);
162 // estimate size (upper bound)
165 for (i = nStartLine; i <= nEndLine; ++i)
166 nBufSize += (GetFullLineLength(i) + 2); // in case we insert EOLs
167 LPTSTR pszBuf = text.GetBuffer(nBufSize);
169 if (nCrlfStyle != CRLF_STYLE_AUTOMATIC)
171 // we must copy this EOL type only
172 const CString sEol = GetStringEol (nCrlfStyle);
174 for (i = nStartLine; i <= nEndLine; ++i)
176 // exclude ghost lines
177 if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && (GetLineFlags(i) & LF_INVISIBLE)))
180 // copy the line, excluding the EOL
181 int soffset = (i == nStartLine ? nStartChar : 0);
182 int eoffset = (i == nEndLine ? nEndChar : GetLineLength(i));
183 int chars = eoffset - soffset;
184 LPCTSTR szLine = m_aLines[i].GetLine(soffset);
185 CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
188 // copy the EOL of the requested type
189 if (i != ApparentLastRealLine())
191 CopyMemory(pszBuf, sEol, sEol.GetLength() * sizeof(TCHAR));
192 pszBuf += sEol.GetLength();
198 for (i = nStartLine; i <= nEndLine; ++i)
200 // exclude ghost lines
201 if ((GetLineFlags(i) & LF_GHOST) || (bExcludeInvisibleLines && GetLineFlags(i) & LF_INVISIBLE))
204 // copy the line including the EOL
205 int soffset = (i == nStartLine ? nStartChar : 0);
206 int eoffset = (i == nEndLine ? nEndChar : GetFullLineLength(i));
207 int chars = eoffset - soffset;
208 LPCTSTR szLine = m_aLines[i].GetLine(soffset);
209 CopyMemory(pszBuf, szLine, chars * sizeof(TCHAR));
212 // check that we really have an EOL
213 if (i != ApparentLastRealLine() && GetLineLength(i) == GetFullLineLength(i))
215 // Oops, real line lacks EOL
216 // (If this happens, editor probably has bug)
218 CString sEol = GetStringEol (nCrlfStyle);
219 CopyMemory(pszBuf, sEol, sEol.GetLength());
220 pszBuf += sEol.GetLength();
224 text.ReleaseBuffer(static_cast<int>(pszBuf - text));
228 ////////////////////////////////////////////////////////////////////////////
232 * @brief Insert text to the buffer.
233 * @param [in] pSource View into which to insert the text.
234 * @param [in] nLine Line number (apparent/screen) where the insertion starts.
235 * @param [in] nPos Character position where the insertion starts.
236 * @param [in] pszText The text to insert.
237 * @param [in] cchText The length of text in pszText.
238 * @param [out] nEndLine Line number of last added line in the buffer.
239 * @param [out] nEndChar Character position of the end of the added text
241 * @param [in] nAction Edit action.
242 * @param [in] bHistory Save insertion for undo/redo?
243 * @return true if the insertion succeeded, false otherwise.
244 * @note Line numbers are apparent (screen) line numbers, not real
245 * line numbers in the file.
246 * @note @p nEndLine and @p nEndChar are valid as long as you do not call
247 * FlushUndoGroup. If you need to call FlushUndoGroup, just store them in a
248 * variable which is preserved with real line number during Rescan
249 * (m_ptCursorPos, m_ptLastChange for example).
251 bool CGhostTextBuffer:: /* virtual override */
252 InsertText (CCrystalTextView * pSource, int nLine,
253 int nPos, LPCTSTR pszText, size_t cchText, int &nEndLine, int &nEndChar,
254 int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
256 bool bGroupFlag = false;
257 bool bFirstLineGhost = ((GetLineFlags(nLine) & LF_GHOST) != 0);
258 bool bSpecialLastLineHandling = bFirstLineGhost && (nLine == GetLineCount()-1);
260 if (bFirstLineGhost && cchText > 0)
262 CString text = GetStringEol(GetCRLFMode());
263 if (bHistory && !m_bUndoGroup)
268 auto reverseFindRealLine = [&](int nLine) {
269 for (; nLine >= 0; --nLine) { if ((GetLineFlags(nLine) & LF_GHOST) == 0) break; }
272 int i = reverseFindRealLine(nLine);
273 if (i >= 0 && !m_aLines[i].HasEol())
274 CCrystalTextBuffer::InsertText(pSource, i, GetLineLength(i), text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
275 else if (!LineInfo::IsEol(pszText[cchText - 1]))
276 CCrystalTextBuffer::InsertText(pSource, nLine, 0, text, text.GetLength(), nEndLine, nEndChar, 0, bHistory);
279 if (!CCrystalTextBuffer::InsertText (pSource, nLine, nPos, pszText,
280 cchText, nEndLine, nEndChar, nAction, bHistory))
285 // when inserting an EOL terminated text into a ghost line,
286 // there is a discrepancy between nInsertedLines and nEndLine-nRealLine
287 bool bDiscrepancyInInsertedLines;
288 if (bFirstLineGhost && nEndChar == 0 && ApparentLastRealLine() >= nEndLine)
289 bDiscrepancyInInsertedLines = true;
291 bDiscrepancyInInsertedLines = false;
293 if (bSpecialLastLineHandling)
295 // The special case of inserting text into the very last line of a file when
296 // that last line is marked as LF_GHOST. Effectively, the new text is
297 // supposed to go "before" the Ghost, but mechanically the text is inserted
298 // into the Ghost itself, with a new Ghost line appearing at the end of the
299 // file. Later (below), the Ghost status of both the first and last inserted
300 // lines will get straightened out, with the trailing Ghost line becomming
302 if ((GetLineFlags(nLine) & LF_GHOST) == 0) // first line still marked GHOST
303 bSpecialLastLineHandling = false;
305 bDiscrepancyInInsertedLines = false;
308 // compute the number of real lines created (for undo)
309 int nRealLinesCreated = nEndLine - nLine;
310 if (bFirstLineGhost && (nEndChar > 0 || ApparentLastRealLine() < nEndLine))
311 // we create one more real line
312 nRealLinesCreated ++;
315 for (i = nLine ; i < nEndLine ; i++)
317 // update line revision numbers of modified lines
318 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
319 OnNotifyLineHasBeenEdited(i);
321 if (!bDiscrepancyInInsertedLines)
323 m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
324 OnNotifyLineHasBeenEdited(i);
327 // when inserting into a ghost line block, we want to replace ghost lines
328 // with our text, so delete some ghost lines below the inserted text
331 // where is the first line after the inserted text ?
332 int nInsertedTextLinesCount = nEndLine - nLine + (bDiscrepancyInInsertedLines ? 0 : 1);
333 int nLineAfterInsertedBlock = nLine + nInsertedTextLinesCount;
334 // delete at most nInsertedTextLinesCount - 1 ghost lines
335 // as the first ghost line has been reused
336 int nMaxGhostLineToDelete = min(nInsertedTextLinesCount - 1, GetLineCount()-nLineAfterInsertedBlock);
337 if (nEndChar == 0 && ApparentLastRealLine() < nEndLine)
338 nMaxGhostLineToDelete --;
339 for (i = 0 ; i < nMaxGhostLineToDelete ; i++)
340 if ((GetLineFlags(nLineAfterInsertedBlock+i) & LF_GHOST) == 0)
342 InternalDeleteGhostLine(pSource, nLineAfterInsertedBlock, i);
345 for (i = nLine ; i < nEndLine ; i++)
346 SetLineFlag (i, LF_GHOST, false, false, false);
347 if (!bDiscrepancyInInsertedLines)
348 // if there is no discrepancy, the final cursor line is real
349 // as either some text was inserted in it, or it inherits the real status from the first line
350 SetLineFlag (i, LF_GHOST, false, false, false);
352 // if there is a discrepancy, the final cursor line was not changed during insertion so we do nothing
355 if (bSpecialLastLineHandling)
357 // By setting the last line (in this special case, see above) to NULL,
358 // the line will eventually be removed or become an actual LF_GHOST line.
359 int nLastLine = GetLineCount()-1;
360 ASSERT(m_aLines[nLastLine].FullLength() == 0);
361 m_aLines[nLastLine].Clear();
364 // now we can recompute
365 if ((nEndLine > nLine) || bFirstLineGhost)
367 // TODO: Be smarter, and don't recompute if it is easy to see what changed
368 RecomputeRealityMapping();
372 FlushUndoGroup (pSource);
374 // nEndLine may have changed during Rescan
375 nEndLine = m_ptLastChange.y;
380 CDWordArray *CGhostTextBuffer:: /* virtual override */
381 CopyRevisionNumbers(int nStartLine, int nEndLine) const
383 CDWordArray *paSavedRevisionNumbers = CCrystalTextBuffer::CopyRevisionNumbers(nStartLine, nEndLine);
384 for (int nLine = nEndLine; nLine >= nStartLine; --nLine)
386 if ((GetLineFlags(nLine) & LF_GHOST) != 0)
387 paSavedRevisionNumbers->RemoveAt(nLine - nStartLine);
389 if ((GetLineFlags(nEndLine) & LF_GHOST) != 0)
391 for (int nLine = nEndLine + 1; nLine < GetLineCount(); ++nLine)
392 if ((GetLineFlags(nLine) & LF_GHOST) == 0)
394 paSavedRevisionNumbers->Add(GetLineFlags(nLine));
398 return paSavedRevisionNumbers;
401 void CGhostTextBuffer:: /* virtual override */
402 RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
404 for (int i = 0, j = 0; i < paSavedRevisionNumbers->GetSize(); j++)
406 if ((GetLineFlags(nStartLine + j) & LF_GHOST) == 0)
408 m_aLines[nStartLine + j].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
414 bool CGhostTextBuffer:: /* virtual override */
415 DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar,
416 int nEndLine, int nEndChar, int nAction /*= CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
418 if ((GetLineFlags(nEndLine) & LF_GHOST) == 0)
420 if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
421 nEndLine, nEndChar, nAction, bHistory))
428 // if the last line in selection to be deleted is a ghost line,
429 // the EOL of last real line in selection should not be deleted.
430 // Otherwise, a line with no EOL will appear.
431 int nEndLine2 = nEndLine;
432 int nEndChar2 = nEndChar;
433 for (; nEndLine2 >= nStartLine; --nEndLine2)
435 if ((GetLineFlags(nEndLine2) & LF_GHOST) == 0)
438 if (nStartLine <= nEndLine2)
440 if(nEndLine2 != nEndLine)
441 nEndChar2 = GetLineLength(nEndLine2);
442 if (!CCrystalTextBuffer::DeleteText2(pSource, nStartLine, nStartChar,
443 nEndLine2, nEndChar2, nAction, bHistory))
447 InternalDeleteGhostLine(pSource, nStartLine + 1, nEndLine - (nEndLine2 + 1) + 1);
451 if (bHistory && m_nUndoPosition < static_cast<int>(m_aUndoBuf.size()))
452 m_aUndoBuf.resize(m_nUndoPosition);
453 InternalDeleteGhostLine(pSource, nEndLine2 + 1, nEndLine - (nEndLine2 + 1));
457 if (nStartChar != 0 || nEndChar != 0)
458 OnNotifyLineHasBeenEdited(nStartLine);
460 // now we can recompute
461 if (nStartLine != nEndLine)
463 // TODO: Be smarter, and don't recompute if it is easy to see what changed
464 RecomputeRealityMapping();
472 * @brief Insert a ghost line to the buffer (and view).
473 * @param [in] pSource The view to which to add the ghost line.
474 * @param [in] Line index (apparent/screen) where to add the ghost line.
475 * @return true if the addition succeeded, false otherwise.
477 bool CGhostTextBuffer::InsertGhostLine (CCrystalTextView * pSource, int nLine)
479 if (!InternalInsertGhostLine (pSource, nLine))
482 // Set WinMerge flags
483 SetLineFlag (nLine, LF_GHOST, true, false, false);
484 RecomputeRealityMapping();
486 // Don't need to recompute EOL as real lines are unchanged.
487 // Never AddUndoRecord as Rescan clears the ghost lines.
493 * @brief Remove all the ghost lines from the buffer.
495 void CGhostTextBuffer::RemoveAllGhostLines()
497 int nlines = GetLineCount();
498 int nFirstGhost = -1;
499 // Free the buffer of ghost lines,
500 // remember where the first ghost line occurs
501 for(int ct = 0; ct < nlines; ct++)
503 if (GetLineFlags(ct) & LF_GHOST)
505 m_aLines[ct].FreeBuffer();
510 if (nFirstGhost >= 0)
512 // Compact non-ghost lines, starting at the first ghost.
513 // (we copy the buffer address, so the buffer doesn't move and we don't free it)
514 int newnl = nFirstGhost;
515 for (int ct = nFirstGhost; ct < nlines; ct++)
517 if ((GetLineFlags(ct) & LF_GHOST) == 0)
518 m_aLines[newnl++] = m_aLines[ct];
521 // Discard unused entries in one shot
522 m_aLines.resize(newnl);
523 RecomputeRealityMapping();
527 ////////////////////////////////////////////////////////////////////////////
528 // apparent <-> real line conversion
531 * @brief Get last apparent (screen) line index.
532 * @return Last apparent line, or -1 if no lines in the buffer.
534 int CGhostTextBuffer::ApparentLastRealLine() const
536 if (m_RealityBlocks.size() == 0)
538 const RealityBlock &block = m_RealityBlocks.back();
539 return block.nStartApparent + block.nCount - 1;
543 * @brief Get a real line for the apparent (screen) line.
544 * This function returns the real line for the given apparent (screen) line.
545 * For ghost lines we return next real line. For trailing ghost line we return
546 * last real line + 1). Ie, lines 0->0, 1->2, 2->4, for argument of 3,
548 * @param [in] nApparentLine Apparent line for which to get the real line.
549 * @return The real line for the apparent line.
551 int CGhostTextBuffer::ComputeRealLine(int nApparentLine) const
554 return ComputeRealLineAndGhostAdjustment(nApparentLine, decToReal);
558 * @brief Get an apparent (screen) line for the real line.
559 * @param [in] nRealLine Real line for which to get the apparent line.
560 * @return The apparent line for the real line. If real line is out of bounds
561 * return last valid apparent line + 1.
563 int CGhostTextBuffer::ComputeApparentLine(int nRealLine) const
565 const int size = static_cast<int>(m_RealityBlocks.size());
569 // after last block ?
570 const RealityBlock & maxblock = m_RealityBlocks.back();
571 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
572 return GetLineCount();
574 // binary search to find correct (or nearest block)
579 int i = (blo + bhi) / 2;
580 const RealityBlock & block = m_RealityBlocks[i];
581 if (nRealLine < block.nStartReal)
583 else if (nRealLine >= block.nStartReal + block.nCount)
586 return (nRealLine - block.nStartReal) + block.nStartApparent;
588 // Should have found it; all real lines should be in a block
594 * @brief Get a real line for apparent (screen) line.
595 * This function returns the real line for the given apparent (screen) line.
596 * For ghost lines we return next real line. For trailing ghost line we return
597 * last real line + 1; i.e. lines 0->0, 1->2, 2->4, for argument of 3,
598 * return 2 and decToReal would be 1.
599 * @param [in] nApparentLine Apparent line for which to get the real line.
600 * @param [out] decToReal Difference of the apparent and real line.
601 * @return The real line for the apparent line.
603 int CGhostTextBuffer::ComputeRealLineAndGhostAdjustment(int nApparentLine,
604 int& decToReal) const
606 const int size = static_cast<int>(m_RealityBlocks.size());
613 // after last apparent line ?
614 ASSERT(nApparentLine < GetLineCount());
616 // after last block ?
617 const RealityBlock & maxblock = m_RealityBlocks.back();
618 if (nApparentLine >= maxblock.nStartApparent + maxblock.nCount)
620 decToReal = GetLineCount() - nApparentLine;
621 return maxblock.nStartReal + maxblock.nCount;
624 // binary search to find correct (or nearest block)
629 int i = (blo + bhi) / 2;
630 const RealityBlock & block = m_RealityBlocks[i];
631 if (nApparentLine < block.nStartApparent)
633 else if (nApparentLine >= block.nStartApparent + block.nCount)
635 else // found it inside this block
638 return (nApparentLine - block.nStartApparent) + block.nStartReal;
641 // it is a ghost line just before block blo
642 decToReal = m_RealityBlocks[blo].nStartApparent - nApparentLine;
643 return m_RealityBlocks[blo].nStartReal;
647 * @brief Get an apparent (screen) line for the real line.
648 * @param [in] nRealLine Real line for which to get the apparent line.
649 * @param [in] decToReal Difference of the apparent and real line.
650 * @return The apparent line for the real line. If real line is out of bounds
651 * return last valid apparent line + 1.
653 int CGhostTextBuffer::ComputeApparentLine(int nRealLine, int decToReal) const
658 const int size = (int) m_RealityBlocks.size();
665 // after last block ?
666 const RealityBlock & maxblock = m_RealityBlocks.back();
667 if (nRealLine >= maxblock.nStartReal + maxblock.nCount)
669 nPreviousBlock = size - 1;
670 nApparent = GetLineCount() - 1;
671 goto limitWithPreviousBlock;
674 // binary search to find correct (or nearest block)
678 const RealityBlock & block = m_RealityBlocks[i];
679 if (nRealLine < block.nStartReal)
681 else if (nRealLine >= block.nStartReal + block.nCount)
685 if (nRealLine > block.nStartReal)
686 // limited by the previous line in this block
687 return (nRealLine - block.nStartReal) + block.nStartApparent;
688 nPreviousBlock = i - 1;
689 nApparent = (nRealLine - block.nStartReal) + block.nStartApparent;
690 goto limitWithPreviousBlock;
693 // Should have found it; all real lines should be in a block
697 limitWithPreviousBlock:
698 // we must keep above the value lastApparentInPreviousBlock
699 int lastApparentInPreviousBlock;
700 if (nPreviousBlock == -1)
701 lastApparentInPreviousBlock = -1;
704 const RealityBlock & previousBlock = m_RealityBlocks[nPreviousBlock];
705 lastApparentInPreviousBlock = previousBlock.nStartApparent + previousBlock.nCount - 1;
711 if (nApparent == lastApparentInPreviousBlock)
712 return nApparent + 1;
717 /** Do what we need to do just after we've been reloaded */
718 void CGhostTextBuffer::FinishLoading()
720 if (!m_bInit) return;
721 RecomputeRealityMapping();
724 /** Recompute the reality mapping (this is fairly naive) */
725 void CGhostTextBuffer::RecomputeRealityMapping()
727 m_RealityBlocks.clear();
728 int reality = -1; // last encountered real line
729 int i = 0; // current line
730 int nLineCount = GetLineCount();
731 RealityBlock block; // current block being traversed (in state 2)
733 // This is a state machine with 2 states
735 // state 1, i-1 not real line
737 ASSERT( i <= nLineCount );
740 checkFlagsFromReality();
743 if (GetLineFlags(i) & LF_GHOST)
748 // this is the first line of a reality block
749 block.nStartApparent = i;
750 block.nStartReal = reality + 1;
754 // fall through to other state
756 // state 2, i - 1 is real line
758 ASSERT( i <= nLineCount );
759 if (i == nLineCount || (GetLineFlags(i) & LF_GHOST))
761 // i-1 is the last line of a reality block
762 ASSERT(reality >= 0);
763 block.nCount = i - block.nStartApparent;
764 ASSERT(block.nCount > 0);
765 ASSERT(reality + 1 - block.nStartReal == block.nCount);
767 // Optimize memory allocation
768 if (m_RealityBlocks.capacity() == m_RealityBlocks.size())
770 if (m_RealityBlocks.size() == 0)
771 m_RealityBlocks.reserve(16);
773 // TODO: grow more slowly with really large RealityBlocks
774 m_RealityBlocks.reserve(m_RealityBlocks.size() * 2);
776 m_RealityBlocks.push_back(block);
779 checkFlagsFromReality();
791 Check all lines, and ASSERT if reality blocks differ from flags.
792 This means that this only has effect in DEBUG build
794 void CGhostTextBuffer::checkFlagsFromReality() const
797 const int size = static_cast<int>(m_RealityBlocks.size());
799 for (int b = 0 ; b < size ; b ++)
801 const RealityBlock & block = m_RealityBlocks[b];
802 for ( ; i < block.nStartApparent ; i++)
803 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
804 for ( ; i < block.nStartApparent+block.nCount ; i++)
805 ASSERT ((GetLineFlags(i) & LF_GHOST) == 0);
808 for ( ; i < GetLineCount() ; i++)
809 ASSERT ((GetLineFlags(i) & LF_GHOST) != 0);
813 void CGhostTextBuffer:: /* virtual base */
814 OnNotifyLineHasBeenEdited(int nLine)
819 static int CountEol(LPCTSTR pszText, size_t cchText)
822 for (size_t nTextPos = 0; nTextPos < cchText; ++nTextPos)
824 if (LineInfo::IsEol(pszText[nTextPos]))
826 if (nTextPos + 1 < cchText && LineInfo::IsDosEol(&pszText[nTextPos]))
835 void CGhostTextBuffer:: /* virtual override */
836 AddUndoRecord(bool bInsert, const CPoint & ptStartPos,
837 const CPoint & ptEndPos, LPCTSTR pszText, size_t cchText,
838 int nActionType /*= CE_ACTION_UNKNOWN*/,
839 CDWordArray *paSavedRevisionNumbers)
841 CPoint real_ptStartPos(ptStartPos.x, ComputeRealLine(ptStartPos.y));
842 CPoint real_ptEndPos(ptEndPos.x, real_ptStartPos.y + CountEol(pszText, cchText));
843 CCrystalTextBuffer::AddUndoRecord(bInsert, real_ptStartPos, real_ptEndPos, pszText,
844 cchText, nActionType, paSavedRevisionNumbers);
847 UndoRecord CGhostTextBuffer:: /* virtual override */
848 GetUndoRecord(int nUndoPos) const
850 UndoRecord ur = m_aUndoBuf[nUndoPos];
851 ur.m_ptStartPos.y = ComputeApparentLine(ur.m_ptStartPos.y, 0);
852 ur.m_ptEndPos.y = ComputeApparentLine(ur.m_ptEndPos.y, 0);
856 bool CGhostTextBuffer:: /* virtual override */
857 UndoInsert(CCrystalTextView * pSource, CPoint & ptCursorPos, const CPoint apparent_ptStartPos, CPoint const apparent_ptEndPos, const UndoRecord & ur)
859 // Check that text in the undo buffer matches text in file buffer.
860 // If not, then rescan() has moved lines and undo fails.
862 // we need to put the cursor before the deleted section
864 const size_t size = m_aLines.size();
865 if ((apparent_ptStartPos.y < static_cast<LONG>(size)) &&
866 (apparent_ptStartPos.x <= static_cast<LONG>(m_aLines[apparent_ptStartPos.y].Length())) &&
867 (apparent_ptEndPos.y < static_cast<LONG>(size)) &&
868 (apparent_ptEndPos.x <= static_cast<LONG>(m_aLines[apparent_ptEndPos.y].Length())))
870 // Try to ensure that we are undoing correctly...
871 // Just compare the text as it was before Undo operation
872 GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text, CRLF_STYLE_AUTOMATIC, false);
873 if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
875 if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparent_ptEndPos, ur))
877 // ptCursorPos = apparent_ptStartPos;
882 // It is possible that the ptEndPos is at the last line of the file and originally pointed
883 // at an LF_GHOST line that followed (and has since been discarded). Lets try to reconstruct
884 // that situation before we fail entirely...
885 if (apparent_ptEndPos.y + 1 == static_cast<LONG>(size) && apparent_ptEndPos.x == 0)
887 CPoint apparentEnd2 = apparent_ptEndPos;
888 apparentEnd2.x = static_cast<LONG>(m_aLines[apparentEnd2.y].FullLength());
890 GetTextWithoutEmptys(apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparentEnd2.x, text, CRLF_STYLE_AUTOMATIC, false);
891 if (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0)
893 if (CCrystalTextBuffer::UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparentEnd2, ur))
895 // ptCursorPos = apparent_ptStartPos;
896 const size_t nLastLine = m_aLines.size() - 1;
897 if (m_aLines[nLastLine].Length() == 0)
899 m_aLines[nLastLine].Clear();
900 if (static_cast<size_t>(ptCursorPos.y) == nLastLine)