OSDN Git Service

3767ef9eb7d240034a12d979d9fce6e02f53babe
[winmerge-jp/winmerge-jp.git] / Externals / crystaledit / editlib / ccrystaltextbuffer.cpp
1 ////////////////////////////////////////////////////////////////////////////
2 //  File:       ccrystaltextbuffer.cpp
3 //  Version:    1.0.0.0
4 //  Created:    29-Dec-1998
5 //
6 //  Author:     Stcherbatchenko Andrei
7 //  E-mail:     windfall@gmx.de
8 //
9 //  Implementation of the CCrystalTextBuffer class, a part of Crystal Edit -
10 //  syntax coloring text editor.
11 //
12 //  You are free to use or modify this code to the following restrictions:
13 //  - Acknowledge me somewhere in your about box, simple "Parts of code by.."
14 //  will be enough. If you can't (or don't want to), contact me personally.
15 //  - LEAVE THIS HEADER INTACT
16 ////////////////////////////////////////////////////////////////////////////
17
18 ////////////////////////////////////////////////////////////////////////////
19 //  17-Feb-99
20 //  +   FIX: unnecessary 'HANDLE' in CCrystalTextBuffer::SaveToFile
21 ////////////////////////////////////////////////////////////////////////////
22
23 ////////////////////////////////////////////////////////////////////////////
24 //  21-Feb-99
25 //      Paul Selormey, James R. Twine:
26 //  +   FEATURE: description for Undo/Redo actions
27 //  +   FEATURE: multiple MSVC-like bookmarks
28 //  +   FEATURE: 'Disable backspace at beginning of line' option
29 //  +   FEATURE: 'Disable drag-n-drop editing' option
30 //
31 //  +   FEATURE: changed layout of SUndoRecord. Now takes less memory
32 ////////////////////////////////////////////////////////////////////////////
33
34 ////////////////////////////////////////////////////////////////////////////
35 //  19-Jul-99
36 //      Ferdinand Prantl:
37 //  +   FEATURE: some other things I've forgotten ...
38 //
39 //  ... it's being edited very rapidly so sorry for non-commented
40 //        and maybe "ugly" code ...
41 ////////////////////////////////////////////////////////////////////////////
42
43 ////////////////////////////////////////////////////////////////////////////
44 //  ??-Aug-99
45 //      Sven Wiegand (search for "//BEGIN SW" to find my changes):
46 //  + FEATURE: Remembering the text-position of the latest change.
47 ////////////////////////////////////////////////////////////////////////////
48
49 ////////////////////////////////////////////////////////////////////////////
50 //  24-Oct-99
51 //      Sven Wiegand
52 //  + FIX: Setting m_ptLastChange to the beginning of the selection in
53 //           InternalDeleteText(), so that position is valid in any case.
54 //           Editor won't crash any more i.e. by selecting whole buffer and
55 //           deleting it and then executing ID_EDIT_GOTO_LAST_CHANGE-command.
56 ////////////////////////////////////////////////////////////////////////////
57 /** 
58  * @file ccrystaltextbuffer.cpp
59  *
60  * @brief Code for CCrystalTextBuffer class
61  */
62
63 #include "StdAfx.h"
64 #include <vector>
65 #include <malloc.h>
66 #include "editcmd.h"
67 #include "LineInfo.h"
68 #include "UndoRecord.h"
69 #include "ccrystaltextbuffer.h"
70 #include "ccrystaltextview.h"
71 #include "utils/filesup.h"
72 #include "utils/cs2cs.h"
73
74 #ifndef __AFXPRIV_H__
75 #pragma message("Include <afxpriv.h> in your stdafx.h to avoid this message")
76 #include <afxpriv.h>
77 #endif
78
79 #ifdef _DEBUG
80 #define new DEBUG_NEW
81 #endif
82
83 using std::vector;
84
85 int CCrystalTextBuffer::m_nDefaultEncoding = -1;
86
87
88 /////////////////////////////////////////////////////////////////////////////
89 // CCrystalTextBuffer::CUpdateContext
90
91 void CCrystalTextBuffer::CInsertContext::
92 RecalcPoint (CPoint & ptPoint)
93 {
94   ASSERT (m_ptEnd.y > m_ptStart.y ||
95           m_ptEnd.y == m_ptStart.y && m_ptEnd.x >= m_ptStart.x);
96   if (ptPoint.y < m_ptStart.y)
97     return;
98   if (ptPoint.y > m_ptStart.y)
99     {
100       ptPoint.y += (m_ptEnd.y - m_ptStart.y);
101       return;
102     }
103   if (ptPoint.x <= m_ptStart.x)
104     return;
105   ptPoint.y += (m_ptEnd.y - m_ptStart.y);
106   ptPoint.x = m_ptEnd.x + (ptPoint.x - m_ptStart.x);
107 }
108
109 void CCrystalTextBuffer::CDeleteContext::
110 RecalcPoint (CPoint & ptPoint)
111 {
112   ASSERT (m_ptEnd.y > m_ptStart.y ||
113           m_ptEnd.y == m_ptStart.y && m_ptEnd.x >= m_ptStart.x);
114   if (ptPoint.y < m_ptStart.y)
115     return;
116   if (ptPoint.y > m_ptEnd.y)
117     {
118       ptPoint.y -= (m_ptEnd.y - m_ptStart.y);
119       return;
120     }
121   if (ptPoint.y == m_ptEnd.y && ptPoint.x >= m_ptEnd.x)
122     {
123       ptPoint.y = m_ptStart.y;
124       ptPoint.x = m_ptStart.x + (ptPoint.x - m_ptEnd.x);
125       return;
126     }
127   if (ptPoint.y == m_ptStart.y)
128     {
129       if (ptPoint.x > m_ptStart.x)
130         ptPoint.x = m_ptStart.x;
131       return;
132     }
133   ptPoint = m_ptStart;
134 }
135
136
137 /////////////////////////////////////////////////////////////////////////////
138 // CCrystalTextBuffer
139
140 IMPLEMENT_DYNCREATE (CCrystalTextBuffer, CCmdTarget)
141
142 CCrystalTextBuffer::CCrystalTextBuffer ()
143 {
144   m_bInit = false;
145   m_bReadOnly = false;
146   m_bModified = false;
147   m_nCRLFMode = CRLFSTYLE::DOS;
148   m_IgnoreEol = false;
149   m_bCreateBackupFile = false;
150   m_nSyncPosition = m_nUndoPosition = 0;
151   m_bInsertTabs = true;
152   m_nTabSize = 4;
153   //BEGIN SW
154   m_ptLastChange.x = m_ptLastChange.y = -1;
155   //END SW
156   m_nSourceEncoding = m_nDefaultEncoding;
157   m_dwCurrentRevisionNumber = 0;
158   m_dwRevisionNumberOnSave = 0;
159   m_bUndoGroup = m_bUndoBeginGroup = false;
160
161   // Table Editing
162   m_bAllowNewlinesInQuotes = true;
163   m_cFieldDelimiter = '\t';
164   m_cFieldEnclosure = '"';
165   m_bTableEditing = false;
166   m_pSharedTableProps.reset(new SharedTableProperties());
167   m_pSharedTableProps->m_textBufferList.push_back(this);
168 }
169
170 CCrystalTextBuffer:: ~ CCrystalTextBuffer ()
171 {
172   ASSERT (!m_bInit);            //  You must call FreeAll() before deleting the object
173 }
174
175
176 BEGIN_MESSAGE_MAP (CCrystalTextBuffer, CCmdTarget)
177 //{{AFX_MSG_MAP(CCrystalTextBuffer)
178 //}}AFX_MSG_MAP
179 END_MESSAGE_MAP ()
180
181
182 /////////////////////////////////////////////////////////////////////////////
183 // CCrystalTextBuffer message handlers
184
185 /** 
186  * @brief Insert the same line once or several times
187  *
188  * @param nPosition : not defined (or -1) = add lines at the end of array
189  */
190 void CCrystalTextBuffer::InsertLine (LPCTSTR pszLine, size_t nLength,
191     int nPosition /*= -1*/, int nCount /*= 1*/ )
192 {
193   ASSERT(nLength != -1);
194
195   LineInfo line;
196   line.Create(pszLine, nLength);
197
198   // nPosition not defined ? Insert at end of array
199   if (nPosition == -1)
200     nPosition = (int) m_aLines.size();
201
202   // insert all lines in one pass
203   std::vector<LineInfo>::iterator iter = m_aLines.begin() + nPosition;
204   m_aLines.insert(iter, nCount, line);
205
206   // create text data for lines after the first one
207   for (int ic = 1; ic < nCount; ic++)
208     {
209       LineInfo li ;
210       li.Create(pszLine, nLength);
211       m_aLines[nPosition + ic] = li;
212     }
213
214 #ifdef _DEBUG
215   // Warning : this function is also used during rescan
216   // and this trace will appear even after the initial load
217   int nLines = (int) m_aLines.size();
218   if (nLines / 5000 != (nLines-nCount) / 5000)
219     TRACE1 ("%d lines loaded!\n", nLines);
220 #endif
221 }
222
223 // Add characters to end of specified line
224 // Specified line must not have any EOL characters
225 void CCrystalTextBuffer::
226 AppendLine (int nLineIndex, LPCTSTR pszChars, size_t nLength, bool bDetectEol)
227 {
228   ASSERT(nLength != -1);
229
230   if (nLength == 0)
231     return;
232
233   LineInfo & li = m_aLines[nLineIndex];
234   li.Append(pszChars, nLength, bDetectEol);
235 }
236
237 /**
238  * @brief Copy line range [line1;line2] to range starting at newline1
239  *
240  * NB: Lines are assigned, not inserted
241  *
242  * Example#1:
243  *   MoveLine(5,7,100)
244  *    line1=5
245  *    line2=10
246  *    newline1=100
247  *    ldiff=95
248  *     l=10  lines[105] = lines[10]
249  *     l=9   lines[104] = lines[9]
250  *     l=8   lines[103] = lines[8]
251  *
252  * Example#2:
253  *   MoveLine(40,42,10)
254  *    line1=40
255  *    line2=42
256  *    newline1=10
257  *    ldiff=-30
258  *     l=40  lines[10] = lines[40]
259  *     l=41  lines[11] = lines[41]
260  *     l=42  lines[12] = lines[42]
261  */
262 void CCrystalTextBuffer::MoveLine(int line1, int line2, int newline1)
263 {
264   int ldiff = newline1 - line1;
265   if (ldiff > 0)
266     {
267       for (int l = line2; l >= line1; l--)
268         m_aLines[l+ldiff] = m_aLines[l];
269     }
270   else if (ldiff < 0)
271     {
272       for (int l = line1; l <= line2; l++)
273         m_aLines[l+ldiff] = m_aLines[l];
274     }
275 }
276
277 void CCrystalTextBuffer::SetEmptyLine (int nPosition, int nCount /*= 1*/ )
278 {
279   for (int i = 0; i < nCount; i++) 
280     {
281       LineInfo li;
282       li.CreateEmpty();
283       m_aLines[nPosition + i] = li;
284     }
285 }
286
287 void CCrystalTextBuffer::
288 FreeAll ()
289 {
290   //  Free text
291   std::vector<LineInfo>::iterator iter = m_aLines.begin();
292   std::vector<LineInfo>::iterator end = m_aLines.end();
293   while (iter != end)
294     {
295       (*iter).Clear();
296       ++iter;
297     }
298   m_aLines.clear();
299
300   // Undo buffer will be cleared by its destructor
301
302   m_bInit = false;
303   //BEGIN SW
304   m_ptLastChange.x = m_ptLastChange.y = -1;
305   //END SW
306 }
307
308 bool CCrystalTextBuffer::
309 InitNew (CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::DOS*/ )
310 {
311   ASSERT (!m_bInit);
312   ASSERT (m_aLines.size() == 0);
313   ASSERT (nCrlfStyle != CRLFSTYLE::AUTOMATIC && nCrlfStyle != CRLFSTYLE::MIXED);
314   InsertLine (_T (""), 0);
315   m_bInit = true;
316   m_bReadOnly = false;
317   m_nCRLFMode = nCrlfStyle;
318   m_bModified = false;
319   m_bInsertTabs = true;
320   m_nTabSize = 4;
321   m_nSyncPosition = m_nUndoPosition = 0;
322   m_bUndoGroup = m_bUndoBeginGroup = false;
323   ASSERT (m_aUndoBuf.size () == 0);
324   UpdateViews (nullptr, nullptr, UPDATE_RESET);
325   //BEGIN SW
326   m_ptLastChange.x = m_ptLastChange.y = -1;
327   //END SW
328   return true;
329 }
330
331 bool CCrystalTextBuffer::
332 GetReadOnly ()
333 const
334 {
335   ASSERT (m_bInit);        //  Text buffer not yet initialized.
336   //  You must call InitNew() or LoadFromFile() first!
337
338   return m_bReadOnly;
339 }
340
341 void CCrystalTextBuffer::SetReadOnly (bool bReadOnly /*= true*/ )
342 {
343   ASSERT (m_bInit);             //  Text buffer not yet initialized.
344   //  You must call InitNew() or LoadFromFile() first!
345
346   m_bReadOnly = bReadOnly;
347 }
348
349
350 // WinMerge has own routine for loading
351 #ifdef CRYSTALEDIT_ENABLELOADER
352
353 static LPCTSTR crlfs[] =
354 {
355   _T ("\x0d\x0a"), //  DOS/Windows style
356   _T ("\x0a"),     //  UNIX style
357   _T ("\x0a")      //  Macintosh style
358 };
359
360 bool CCrystalTextBuffer::
361 LoadFromFile (LPCTSTR pszFileName, CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC*/ )
362 {
363   ASSERT (!m_bInit);
364   ASSERT (m_aLines.size() == 0);
365
366   HANDLE hFile = nullptr;
367   int nCurrentMax = 256;
368   char *pcLineBuf = new char[nCurrentMax];
369
370   bool bSuccess = false;
371
372   int nExt = GetExtPosition (pszFileName);
373   if (pszFileName[nExt] == _T ('.'))
374     nExt++;
375   CrystalLineParser::TextDefinition *def = CrystalLineParser::GetTextType (pszFileName + nExt);
376   if (def && def->encoding != -1)
377     m_nSourceEncoding = def->encoding;
378
379   __try
380     {
381       DWORD dwFileAttributes =::GetFileAttributes (pszFileName);
382       if (dwFileAttributes == (DWORD) - 1)
383         __leave;
384
385       hFile =::CreateFile (pszFileName, GENERIC_READ, FILE_SHARE_READ + FILE_SHARE_WRITE, nullptr,
386         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
387       if (hFile == INVALID_HANDLE_VALUE)
388         __leave;
389
390       int nCurrentLength = 0;
391
392       const DWORD dwBufSize = 32768;
393       LPSTR pcBuf = (LPSTR) _alloca (dwBufSize);
394       DWORD dwCurSize;
395       if (!::ReadFile (hFile, pcBuf, dwBufSize, &dwCurSize, nullptr))
396         __leave;
397
398       if (nCrlfStyle == CRLFSTYLE::AUTOMATIC)
399         {
400           //  Try to determine current CRLF mode based on first line
401           DWORD I;
402           for (I = 0; I < dwCurSize; I++)
403             {
404               if ((pcBuf[I] == _T('\x0d')) || (pcBuf[I] == _T('\x0a')))
405                 break;
406             }
407           if (I == dwCurSize)
408             {
409               //  By default (or in the case of empty file), set DOS style
410               nCrlfStyle = CRLFSTYLE::DOS;
411             }
412           else
413             {
414               //  Otherwise, analyse the first occurance of line-feed character
415               if (pcBuf[I] == _T('\x0a'))
416                 {
417                   nCrlfStyle = CRLFSTYLE::UNIX;
418                 }
419               else
420                 {
421                   if (I < dwCurSize - 1 && pcBuf[I + 1] == _T ('\x0a'))
422                     nCrlfStyle = CRLFSTYLE::DOS;
423                   else
424                     nCrlfStyle = CRLFSTYLE::MAC;
425                 }
426             }
427         }
428
429       ASSERT (nCrlfStyle != CRLFSTYLE::AUTOMATIC && nCrlfStyle != CRLFSTYLE::MIXED);
430       m_nCRLFMode = nCrlfStyle;
431
432       m_aLines.reserve(4096);
433
434       DWORD dwBufPtr = 0;
435       while (dwBufPtr < dwCurSize)
436         {
437           char c = pcBuf[dwBufPtr];
438           dwBufPtr++;
439           if (dwBufPtr == dwCurSize && dwCurSize == dwBufSize)
440             {
441               if (!::ReadFile (hFile, pcBuf, dwBufSize, &dwCurSize, nullptr))
442                 __leave;
443               dwBufPtr = 0;
444             }
445
446           pcLineBuf[nCurrentLength] = c;
447           nCurrentLength++;
448           if (nCurrentLength == nCurrentMax)
449             {
450               //  Reallocate line buffer
451               nCurrentMax += 256;
452               char *pcNewLineBuf = new char[nCurrentMax];
453               memcpy(pcNewLineBuf, pcLineBuf, sizeof(char) * (nCurrentMax - 256));
454               delete [] pcLineBuf;
455               pcLineBuf = pcNewLineBuf;
456             }
457
458           // detect both types of EOL for each line
459           // handles mixed mode files.
460           // Perry (2002-11-26): What about MAC files ? They don't have 0x0A at all. I think this doesn't handle them.
461           if( c==0x0A )
462             {
463               pcLineBuf[nCurrentLength] = '\0';
464 #ifdef _UNICODE
465               wchar_t *buf = new wchar_t[nCurrentLength];
466               int len = MultiByteToWideChar(CP_UTF8, 0, pcLineBuf, nCurrentLength, buf, nCurrentLength);
467               if (m_nSourceEncoding >= 0)
468                 iconvert(buf, m_nSourceEncoding, 1, m_nSourceEncoding == 15);
469               InsertLine(buf, len);
470               delete[] buf;
471 #else
472               if (m_nSourceEncoding >= 0)
473                 iconvert (pcLineBuf, m_nSourceEncoding, 1, m_nSourceEncoding == 15);
474               InsertLine (pcLineBuf, nCurrentLength);
475 #endif
476               nCurrentLength = 0;
477             }
478         }
479
480       pcLineBuf[nCurrentLength] = 0;
481 #ifdef _UNICODE
482       wchar_t *buf = new wchar_t[nCurrentLength];
483       int len = MultiByteToWideChar(CP_UTF8, 0, pcLineBuf, nCurrentLength, buf, nCurrentLength);
484       InsertLine(buf, len);
485       delete[] buf;
486 #else
487       InsertLine (&pcLineBuf[0], nCurrentLength);
488 #endif
489
490       ASSERT (m_aLines.size() > 0);   //  At least one empty line must present
491
492       m_bInit = true;
493       m_bReadOnly = (dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0;
494       m_bModified = false;
495       m_bUndoGroup = m_bUndoBeginGroup = false;
496       m_nSyncPosition = m_nUndoPosition = 0;
497       ASSERT (m_aUndoBuf.size () == 0);
498       bSuccess = true;
499
500       RetypeViews (pszFileName);
501       UpdateViews (nullptr, nullptr, UPDATE_RESET);
502     }
503   __finally
504     {
505     }
506   if (hFile != nullptr && hFile != INVALID_HANDLE_VALUE)
507     ::CloseHandle (hFile);
508   delete [] pcLineBuf;
509   //BEGIN SW
510   m_ptLastChange.x = m_ptLastChange.y = -1;
511   //END SW
512   return bSuccess;
513 }
514 #endif // #if 0 loadfromfile
515
516 // WinMerge has own routine for saving
517 #ifdef CRYSTALEDIT_ENABLESAVER
518 bool CCrystalTextBuffer::SaveToFile(LPCTSTR pszFileName,
519                   CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC*/,
520                   bool bClearModifiedFlag /*= true*/)
521 {
522   ASSERT (nCrlfStyle == CRLFSTYLE::AUTOMATIC || nCrlfStyle == CRLFSTYLE::DOS ||
523           nCrlfStyle == CRLFSTYLE::UNIX || nCrlfStyle == CRLFSTYLE::MAC);
524   ASSERT (m_bInit);
525   HANDLE hTempFile = INVALID_HANDLE_VALUE;
526   HANDLE hSearch = INVALID_HANDLE_VALUE;
527   TCHAR szTempFileDir[_MAX_PATH + 1];
528   TCHAR szTempFileName[_MAX_PATH + 1];
529   TCHAR szBackupFileName[_MAX_PATH + 1];
530   bool bSuccess = false;
531   {
532     TCHAR drive[_MAX_PATH], dir[_MAX_PATH], name[_MAX_PATH], ext[_MAX_PATH];
533 #ifdef _UNICODE
534     _wsplitpath_s (pszFileName, drive, dir, name, ext);
535 #else
536     _splitpath_s (pszFileName, drive, dir, name, ext);
537 #endif
538     _tcscpy_s (szTempFileDir, drive);
539     _tcscat_s (szTempFileDir, dir);
540     _tcscpy_s (szBackupFileName, pszFileName);
541     _tcscat_s (szBackupFileName, _T (".bak"));
542
543     if (::GetTempFileName(szTempFileDir, _T("CRE"), 0, szTempFileName) == 0)
544       goto EXIT;
545
546     hTempFile =::CreateFile (szTempFileName, GENERIC_WRITE, 0, nullptr,
547       CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
548     if (hTempFile == INVALID_HANDLE_VALUE)
549       goto EXIT;
550
551     if (nCrlfStyle == CRLFSTYLE::AUTOMATIC)
552       nCrlfStyle = m_nCRLFMode;
553
554     ASSERT (nCrlfStyle != CRLFSTYLE::AUTOMATIC && nCrlfStyle != CRLFSTYLE::MIXED);
555     LPCTSTR pszCRLF = crlfs[static_cast<int>(nCrlfStyle)];
556     int nCRLFLength = static_cast<int>(_tcslen (pszCRLF));
557
558     int nLineCount = static_cast<int>(m_aLines.size());
559     for (int nLine = 0; nLine < nLineCount; nLine++)
560       {
561         int nLength = static_cast<int>(m_aLines[nLine].Length());
562         DWORD dwWrittenBytes;
563         if (nLength > 0)
564           {
565             LPCTSTR pszLine = m_aLines[nLine].GetLine(0);
566             if (m_nSourceEncoding >= 0)
567               {
568                 LPTSTR pszBuf;
569                 iconvert_new (m_aLines[nLine].GetLine(0), &pszBuf, 1, m_nSourceEncoding, m_nSourceEncoding == 15);
570 #ifdef _UNICODE
571                 std::unique_ptr<char> abuf{ new char[nLength * 4] };
572                 nLength = WideCharToMultiByte (CP_UTF8, 0, pszBuf, nLength, abuf.get(), nLength * 4, nullptr, nullptr);
573                 if (!::WriteFile (hTempFile, abuf.get(), nLength, &dwWrittenBytes, nullptr))
574 #else
575                 if (!::WriteFile (hTempFile, pszBuf, nLength, &dwWrittenBytes, nullptr))
576 #endif
577                   {
578                     free (pszBuf);
579                     goto EXIT;
580                   }
581                 free (pszBuf);
582               }
583             else
584               {
585 #ifdef _UNICODE
586                 std::unique_ptr<char> abuf{ new char[nLength * 4] };
587                 nLength = WideCharToMultiByte (CP_UTF8, 0, pszLine, nLength, abuf.get(), nLength * 4, nullptr, nullptr);
588                 if (!::WriteFile (hTempFile, abuf.get(), nLength, &dwWrittenBytes, nullptr))
589 #else
590                 if (!::WriteFile (hTempFile, pszLine, nLength, &dwWrittenBytes, nullptr))
591 #endif
592                   goto EXIT;
593               }
594             if (nLength != (int)dwWrittenBytes)
595               goto EXIT;
596           }
597         if (nLine < nLineCount - 1)     //  Last line must not end with CRLF
598
599           {
600 #ifdef _UNICODE
601             std::unique_ptr<char> abuf{ new char[nCRLFLength * 4] };
602             nCRLFLength = WideCharToMultiByte (CP_UTF8, 0, pszCRLF, nCRLFLength, abuf.get(), nCRLFLength * 4, nullptr, nullptr);
603             if (!::WriteFile (hTempFile, abuf.get(), nCRLFLength, &dwWrittenBytes, nullptr))
604 #else
605             if (!::WriteFile (hTempFile, pszCRLF, nCRLFLength, &dwWrittenBytes, nullptr))
606 #endif
607               goto EXIT;
608             if (nCRLFLength != (int) dwWrittenBytes)
609               goto EXIT;
610           }
611       }
612     ::CloseHandle (hTempFile);
613     hTempFile = INVALID_HANDLE_VALUE;
614
615     if (m_bCreateBackupFile)
616       {
617         WIN32_FIND_DATA wfd;
618         hSearch =::FindFirstFile (pszFileName, &wfd);
619         if (hSearch != INVALID_HANDLE_VALUE)
620           {
621             //  File exist - create backup file
622             ::DeleteFile (szBackupFileName);
623             if (!::MoveFile (pszFileName, szBackupFileName))
624               goto EXIT;
625             ::FindClose (hSearch);
626             hSearch = INVALID_HANDLE_VALUE;
627           }
628       }
629     else
630       {
631         ::DeleteFile (pszFileName);
632       }
633
634     //  Move temporary file to target name
635     if (!::MoveFile (szTempFileName, pszFileName))
636       goto EXIT;
637
638     if (bClearModifiedFlag)
639       {
640         SetModified (false);
641         m_nSyncPosition = m_nUndoPosition;
642       }
643     bSuccess = true;
644
645     // remember revision number on save
646     m_dwRevisionNumberOnSave = m_dwCurrentRevisionNumber;
647
648     // redraw line revision marks
649     UpdateViews (nullptr, nullptr, UPDATE_FLAGSONLY);
650   }
651 EXIT:
652   {
653     if (hSearch != INVALID_HANDLE_VALUE)
654       ::FindClose (hSearch);
655     if (hTempFile != INVALID_HANDLE_VALUE)
656       ::CloseHandle (hTempFile);
657     ::DeleteFile (szTempFileName);
658   }
659   return bSuccess;
660 }
661 #endif // #if 0 savetofile
662
663 // Default EOL to use if editor has to manufacture one
664 // (this occurs with ghost lines)
665 void CCrystalTextBuffer::
666 SetCRLFMode (CRLFSTYLE nCRLFMode)
667 {
668   if (nCRLFMode==CRLFSTYLE::AUTOMATIC)
669     nCRLFMode = CRLFSTYLE::DOS;
670   m_nCRLFMode = nCRLFMode;
671
672   ASSERT(m_nCRLFMode == CRLFSTYLE::DOS || m_nCRLFMode == CRLFSTYLE::UNIX ||
673       m_nCRLFMode == CRLFSTYLE::MAC || m_nCRLFMode == CRLFSTYLE::MIXED);
674 }
675
676 bool CCrystalTextBuffer::
677 applyEOLMode()
678 {
679   LPCTSTR lpEOLtoApply = GetDefaultEol();
680   bool bChanged = false;
681   for (size_t i = 0 ; i < m_aLines.size(); i++)
682     {
683       // the last real line has no EOL
684       if (!m_aLines[i].HasEol())
685         continue;
686       bChanged |= ChangeLineEol(static_cast<int>(i), lpEOLtoApply);
687     }
688
689   if (bChanged)
690     SetModified(true);
691
692   return bChanged;
693 }
694
695 int CCrystalTextBuffer::
696 GetLineCount () const
697 {
698   ASSERT (m_bInit);             //  Text buffer not yet initialized.
699   ASSERT (m_aLines.size() < INT_MAX);
700   //  You must call InitNew() or LoadFromFile() first!
701
702   return (int) m_aLines.size ();
703 }
704
705 // number of characters in line (excluding any trailing eol characters)
706 int CCrystalTextBuffer::
707 GetLineLength (int nLine) const
708 {
709   ASSERT (m_bInit);             //  Text buffer not yet initialized.
710   ASSERT (m_aLines[nLine].Length() < INT_MAX);
711   //  You must call InitNew() or LoadFromFile() first!
712
713   return static_cast<int>(m_aLines[nLine].Length());
714 }
715
716 // number of characters in line (including any trailing eol characters)
717 int CCrystalTextBuffer::
718 GetFullLineLength (int nLine) const
719 {
720   ASSERT (m_bInit);             //  Text buffer not yet initialized.
721   if (nLine >= static_cast<int>(m_aLines.size()))
722     {
723       ASSERT(false);
724       return 0;
725     }
726   ASSERT (m_aLines[nLine].FullLength() < INT_MAX);
727   //  You must call InitNew() or LoadFromFile() first!
728
729   return static_cast<int>(m_aLines[nLine].FullLength());
730 }
731
732 // get pointer to any trailing eol characters (pointer to empty string if none)
733 LPCTSTR CCrystalTextBuffer::
734 GetLineEol (int nLine) const
735 {
736   ASSERT (m_bInit);             //  Text buffer not yet initialized.
737   if (m_aLines[nLine].HasEol())
738     return m_aLines[nLine].GetEol();
739   else
740     return _T("");
741 }
742
743 bool CCrystalTextBuffer::
744 ChangeLineEol (int nLine, LPCTSTR lpEOL) 
745 {
746   LineInfo & li = m_aLines[nLine];
747   return li.ChangeEol(lpEOL);
748 }
749
750 LPCTSTR CCrystalTextBuffer::
751 GetLineChars (int nLine) const
752 {
753   ASSERT (m_bInit);             //  Text buffer not yet initialized.
754   //  You must call InitNew() or LoadFromFile() first!
755
756   return m_aLines[nLine].GetLine();
757 }
758
759 DWORD CCrystalTextBuffer::
760 GetLineFlags (int nLine) const
761 {
762   ASSERT (m_bInit);             //  Text buffer not yet initialized.
763   //  You must call InitNew() or LoadFromFile() first!
764
765   if (nLine < 0 || nLine >= static_cast<int>(m_aLines.size()))
766     {
767       ASSERT(false);      //  nLine is out of range.
768       return 0;
769     }
770
771   return m_aLines[nLine].m_dwFlags;
772 }
773
774 /** 
775  * @brief Get line revision number.
776  *
777  * @param nLine Index of the line to get the revision number.
778  */
779 DWORD CCrystalTextBuffer::
780 GetLineRevisionNumber (int nLine) const
781 {
782   ASSERT (m_bInit);             //  Text buffer not yet initialized.
783   //  You must call InitNew() or LoadFromFile() first!
784
785   return m_aLines[nLine].m_dwRevisionNumber;
786 }
787
788 static int
789 FlagToIndex (DWORD dwFlag)
790 {
791   int nIndex = 0;
792   while ((dwFlag & 1) == 0)
793     {
794       dwFlag = dwFlag >> 1;
795       nIndex++;
796       if (nIndex == 32)
797         return -1;
798     }
799   dwFlag = dwFlag & 0xFFFFFFFE;
800   if (dwFlag != 0)
801     return -1;
802   return nIndex;
803
804 }
805
806 int CCrystalTextBuffer::
807 FindLineWithFlag (DWORD dwFlag) const
808 {
809   const size_t nSize = m_aLines.size();
810   for (size_t L = 0; L < nSize; L++)
811     {
812       if ((m_aLines[L].m_dwFlags & dwFlag) != 0)
813         return (int) L;
814     }
815   return -1;
816 }
817
818 int CCrystalTextBuffer::
819 GetLineWithFlag (DWORD dwFlag) const
820 {
821   int nFlagIndex =::FlagToIndex (dwFlag);
822   if (nFlagIndex < 0)
823     {
824       ASSERT (false);           //  Invalid flag passed in
825
826       return -1;
827     }
828   return FindLineWithFlag (dwFlag);
829 }
830
831 void CCrystalTextBuffer::
832 SetLineFlag (int nLine, DWORD dwFlag, bool bSet, bool bRemoveFromPreviousLine /*= true*/, bool bUpdate /*= true*/)
833 {
834   ASSERT (m_bInit);             //  Text buffer not yet initialized.
835   //  You must call InitNew() or LoadFromFile() first!
836
837   int nFlagIndex =::FlagToIndex (dwFlag);
838   if (nFlagIndex < 0 && (nLine == -1 || bRemoveFromPreviousLine))
839     {
840       ASSERT (false);           //  Invalid flag passed in
841
842       return;
843     }
844
845   if (nLine == -1)
846     {
847       ASSERT (!bSet);
848       nLine = FindLineWithFlag (dwFlag);
849       if (nLine == -1)
850         return;
851       bRemoveFromPreviousLine = false;
852     }
853
854   if (nLine < 0 || nLine >= static_cast<int>(m_aLines.size()))
855     {
856       ASSERT(false);    //  nLine is out of range.
857       return;
858     }
859
860   DWORD dwNewFlags = m_aLines[nLine].m_dwFlags;
861   if (bSet)
862     {
863       if (dwFlag==0)
864         dwNewFlags=0;
865       else
866         dwNewFlags = dwNewFlags | dwFlag;
867     }
868   else
869     dwNewFlags = dwNewFlags & ~dwFlag;
870
871   if (m_aLines[nLine].m_dwFlags != dwNewFlags)
872     {
873       if (bRemoveFromPreviousLine)
874         {
875           int nPrevLine = FindLineWithFlag (dwFlag);
876           if (bSet)
877             {
878               if (nPrevLine >= 0)
879                 {
880                   ASSERT ((m_aLines[nPrevLine].m_dwFlags & dwFlag) != 0);
881                   m_aLines[nPrevLine].m_dwFlags &= ~dwFlag;
882                   if (bUpdate)
883                     UpdateViews (nullptr, nullptr, UPDATE_SINGLELINE | UPDATE_FLAGSONLY, nPrevLine);
884                 }
885             }
886           else
887             {
888               ASSERT (nPrevLine == nLine);
889             }
890         }
891
892       m_aLines[nLine].m_dwFlags = dwNewFlags;
893       if (bUpdate)
894         UpdateViews (nullptr, nullptr, UPDATE_SINGLELINE | UPDATE_FLAGSONLY, nLine);
895     }
896 }
897
898
899 /**
900  * @brief Get text of specified line range (excluding ghost lines)
901  */
902 void CCrystalTextBuffer::                       /* virtual base */
903 GetTextWithoutEmptys(int nStartLine, int nStartChar, 
904                  int nEndLine, int nEndChar, 
905                  CString &text, CRLFSTYLE nCrlfStyle /*= CRLFSTYLE::AUTOMATIC */,
906                  bool bExcludeInvisibleLines/*= true*/) const
907 {
908   GetText(nStartLine, nStartChar, nEndLine, nEndChar, text, (nCrlfStyle == CRLFSTYLE::AUTOMATIC) ? nullptr : GetStringEol (nCrlfStyle), bExcludeInvisibleLines);
909 }
910
911
912 void CCrystalTextBuffer::
913 GetText (int nStartLine, int nStartChar, int nEndLine, int nEndChar,
914          CString & text, LPCTSTR pszCRLF /*= nullptr*/, bool bExcludeInvisibleLines/*= true*/) const
915 {
916   ASSERT (m_bInit);             //  Text buffer not yet initialized.
917   //  You must call InitNew() or LoadFromFile() first!
918   
919   ASSERT (m_aLines.size() < INT_MAX);
920   ASSERT (m_aLines[nStartLine].Length() < INT_MAX);
921   ASSERT (nStartLine >= 0 && nStartLine < (int)m_aLines.size ());
922   ASSERT (nStartChar >= 0 && nStartChar <= (int)m_aLines[nStartLine].Length());
923   ASSERT (nEndLine >= 0 && nEndLine < (int)m_aLines.size ());
924   ASSERT (nEndChar >= 0 && nEndChar <= (int)m_aLines[nEndLine].Length());
925   ASSERT (nStartLine < nEndLine || nStartLine == nEndLine && nStartChar <= nEndChar);
926   // some edit functions (copy...) should do nothing when there is no selection.
927   // assert to be sure to catch these 'do nothing' cases.
928   ASSERT (nStartLine != nEndLine || nStartChar != nEndChar);
929
930   int nCRLFLength;
931   LPCTSTR pszCurCRLF;
932
933   size_t nBufSize = 0;
934   for (int L = nStartLine; L <= nEndLine; L++)
935     {
936       nBufSize += m_aLines[L].Length();
937       pszCurCRLF = pszCRLF ? pszCRLF : m_aLines[L].GetEol();
938       pszCurCRLF = pszCurCRLF ? pszCurCRLF : _T("");
939       nCRLFLength = static_cast<int>(_tcslen(pszCurCRLF));
940       nBufSize += nCRLFLength;
941     }
942
943   LPTSTR pszBuf = text.GetBuffer (static_cast<int>(nBufSize));
944
945   const LineInfo &startLine = m_aLines[nStartLine];
946   if (nStartLine < nEndLine)
947     {
948       ptrdiff_t nCount = startLine.Length() - nStartChar;
949       if (nCount > 0)
950         {
951           memcpy (pszBuf, startLine.GetLine(nStartChar), sizeof (TCHAR) * nCount);
952           pszBuf += nCount;
953         }
954       pszCurCRLF = pszCRLF ? pszCRLF : startLine.GetEol();
955       pszCurCRLF = pszCurCRLF ? pszCurCRLF : _T("");
956       nCRLFLength = static_cast<int>(_tcslen(pszCurCRLF));
957       memcpy (pszBuf, pszCurCRLF, sizeof (TCHAR) * nCRLFLength);
958       pszBuf += nCRLFLength;
959       for (int I = nStartLine + 1; I < nEndLine; I++)
960         {
961           if (bExcludeInvisibleLines && (GetLineFlags (I) & LF_INVISIBLE))
962             continue;
963           const LineInfo &li = m_aLines[I];
964           nCount = li.Length();
965           if (nCount > 0)
966             {
967               memcpy (pszBuf, li.GetLine(), sizeof (TCHAR) * nCount);
968               pszBuf += nCount;
969             }
970           pszCurCRLF = pszCRLF ? pszCRLF : li.GetEol();
971           pszCurCRLF = pszCurCRLF ? pszCurCRLF : _T("");
972           nCRLFLength = static_cast<int>(_tcslen(pszCurCRLF));
973           memcpy (pszBuf, pszCurCRLF, sizeof (TCHAR) * nCRLFLength);
974           pszBuf += nCRLFLength;
975         }
976       if (nEndChar > 0)
977         {
978           memcpy (pszBuf, m_aLines[nEndLine].GetLine(), sizeof (TCHAR) * nEndChar);
979           pszBuf += nEndChar;
980         }
981     }
982   else
983     {
984       int nCount = nEndChar - nStartChar;
985       memcpy (pszBuf, startLine.GetLine(nStartChar), sizeof (TCHAR) * nCount);
986       pszBuf += nCount;
987     }
988   text.ReleaseBuffer ((int) (pszBuf - text));
989   text.FreeExtra ();
990 }
991
992 void CCrystalTextBuffer::
993 AddView (CCrystalTextView * pView)
994 {
995   m_lpViews.AddTail (pView);
996 }
997
998 void CCrystalTextBuffer::
999 RemoveView (CCrystalTextView * pView)
1000 {
1001   POSITION pos = m_lpViews.GetHeadPosition ();
1002   while (pos != nullptr)
1003     {
1004       POSITION thispos = pos;
1005       CCrystalTextView *pvw = m_lpViews.GetNext (pos);
1006       if (pvw == pView)
1007         {
1008           m_lpViews.RemoveAt (thispos);
1009           return;
1010         }
1011     }
1012   ASSERT (false);
1013 }
1014
1015 CrystalLineParser::TextDefinition *CCrystalTextBuffer::
1016 RetypeViews (LPCTSTR lpszFileName)
1017 {
1018   POSITION pos = m_lpViews.GetHeadPosition ();
1019   CString sNew = GetExt (lpszFileName);
1020   CrystalLineParser::TextDefinition *def = CrystalLineParser::GetTextType (sNew);
1021   while (pos != nullptr)
1022     {
1023       CCrystalTextView *pView = m_lpViews.GetNext (pos);
1024       pView->SetTextType (def);
1025     }
1026   return def;
1027 }
1028
1029 void CCrystalTextBuffer::
1030 UpdateViews (CCrystalTextView * pSource, CUpdateContext * pContext, DWORD dwUpdateFlags, int nLineIndex /*= -1*/ )
1031 {
1032   POSITION pos = m_lpViews.GetHeadPosition ();
1033   while (pos != nullptr)
1034     {
1035       CCrystalTextView *pView = m_lpViews.GetNext (pos);
1036       pView->UpdateView (pSource, pContext, dwUpdateFlags, nLineIndex);
1037     }
1038 }
1039
1040 /**
1041  * @brief Delete text from the buffer.
1042  * @param [in] pSource A view from which the text is deleted.
1043  * @param [in] nStartLine Starting line for the deletion.
1044  * @param [in] nStartChar Starting char position for the deletion.
1045  * @param [in] nEndLine Ending line for the deletion.
1046  * @param [in] nEndChar Ending char position for the deletion.
1047  * @return true if the deletion succeeded, false otherwise.
1048  * @note Line numbers are apparent (screen) line numbers, not real
1049  * line numbers in the file.
1050  */
1051 bool CCrystalTextBuffer::
1052 InternalDeleteText (CCrystalTextView * pSource, int nStartLine, int nStartChar,
1053     int nEndLine, int nEndChar)
1054 {
1055   ASSERT (m_bInit);             //  Text buffer not yet initialized.
1056   //  You must call InitNew() or LoadFromFile() first!
1057
1058   ASSERT (m_aLines.size() < INT_MAX);
1059   ASSERT (m_aLines[nStartLine].Length() < INT_MAX);
1060   ASSERT (nStartLine >= 0 && nStartLine < (int)m_aLines.size ());
1061   ASSERT (nStartChar >= 0 && nStartChar <= (int)m_aLines[nStartLine].Length());
1062   ASSERT (nEndLine >= 0 && nEndLine < (int)m_aLines.size ());
1063   ASSERT (nEndChar >= 0 && nEndChar <= (int)m_aLines[nEndLine].FullLength());
1064   ASSERT (nStartLine < nEndLine || nStartLine == nEndLine && nStartChar <= nEndChar);
1065   // some edit functions (delete...) should do nothing when there is no selection.
1066   // assert to be sure to catch these 'do nothing' cases.
1067 //  ASSERT (nStartLine != nEndLine || nStartChar != nEndChar);
1068   if (m_bReadOnly)
1069     return false;
1070
1071   CDeleteContext context;
1072   context.m_ptStart.y = nStartLine;
1073   context.m_ptStart.x = nStartChar;
1074   context.m_ptEnd.y = nEndLine;
1075   context.m_ptEnd.x = nEndChar;
1076   if (nStartLine == nEndLine)
1077     {
1078       // delete part of one line
1079       m_aLines[nStartLine].Delete(nStartChar, nEndChar);
1080
1081       if (pSource!=nullptr)
1082         UpdateViews (pSource, &context, UPDATE_SINGLELINE | UPDATE_HORZRANGE, nStartLine);
1083     }
1084   else
1085     {
1086       // delete multiple lines
1087       const ptrdiff_t nRestCount = m_aLines[nEndLine].FullLength() - nEndChar;
1088       CString sTail(m_aLines[nEndLine].GetLine(nEndChar), static_cast<int>(nRestCount));
1089       DWORD dwFlags = GetLineFlags (nEndLine);
1090
1091       const int nDelCount = nEndLine - nStartLine;
1092       for (int L = nStartLine + 1; L <= nEndLine; L++)
1093         m_aLines[L].Clear();
1094       std::vector<LineInfo>::iterator iterBegin = m_aLines.begin() + nStartLine + 1;
1095       std::vector<LineInfo>::iterator iterEnd = iterBegin + nDelCount;
1096       m_aLines.erase(iterBegin, iterEnd);
1097
1098       //  nEndLine is no more valid
1099       m_aLines[nStartLine].DeleteEnd(nStartChar);
1100       if (nRestCount > 0)
1101         {
1102           AppendLine (nStartLine, sTail, sTail.GetLength());
1103         }
1104       if (nStartChar == 0)
1105         m_aLines[nStartLine].m_dwFlags = dwFlags;
1106
1107       if (pSource!=nullptr)
1108         UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nStartLine);
1109     }
1110
1111   if (!m_bModified)
1112     SetModified (true);
1113   //BEGIN SW
1114   // remember current cursor position as last editing position
1115   m_ptLastChange = context.m_ptStart;
1116   //END SW
1117
1118   return true;
1119 }
1120
1121 // Remove the last [bytes] characters from specified line, and return them
1122 // (EOL characters are included)
1123 CString CCrystalTextBuffer::
1124 StripTail (int i, size_t bytes)
1125 {
1126   LineInfo & li = m_aLines[i];
1127   // Must at least take off the EOL characters
1128   ASSERT(bytes >= li.FullLength() - li.Length());
1129
1130   const ptrdiff_t offset = li.FullLength() - bytes;
1131   // Must not take off more than exist
1132   ASSERT(offset >= 0);
1133
1134   CString ret(li.GetLine(offset), static_cast<int>(bytes));
1135   li.DeleteEnd(offset);
1136   return ret;
1137 }
1138
1139
1140 /**
1141  * @brief Insert text to the buffer.
1142  * @param [in] pSource A view to which the text is added.
1143  * @param [in] nLine Line to add the text.
1144  * @param [in] nPos Position in the line to insert the text.
1145  * @param [in] pszText The text to insert.
1146  * @param [in] cchText The length of the text.
1147  * @param [out] nEndLine Line number of last added line in the buffer.
1148  * @param [out] nEndChar Character position of the end of the added text
1149  *   in the buffer.
1150  * @return true if the insertion succeeded, false otherwise.
1151  * @note Line numbers are apparent (screen) line numbers, not real
1152  * line numbers in the file.
1153  */
1154 bool CCrystalTextBuffer::
1155 InternalInsertText (CCrystalTextView * pSource, int nLine, int nPos,
1156     LPCTSTR pszText, size_t cchText, int &nEndLine, int &nEndChar)
1157 {
1158   ASSERT (m_bInit);             //  Text buffer not yet initialized.
1159   //  You must call InitNew() or LoadFromFile() first!
1160   
1161   ASSERT (m_aLines.size() < INT_MAX);
1162   ASSERT (m_aLines[nLine].Length() < INT_MAX);
1163   ASSERT (nLine >= 0 && nLine < (int)m_aLines.size ());
1164   ASSERT (nPos >= 0 && nPos <= (int)m_aLines[nLine].Length());
1165   if (m_bReadOnly)
1166     return false;
1167
1168   CInsertContext context;
1169   context.m_ptStart.x = nPos;
1170   context.m_ptStart.y = nLine;
1171   nEndLine = 0;
1172   nEndChar = 0;
1173
1174   int nRestCount = GetFullLineLength(nLine) - nPos;
1175   CString sTail;
1176   if (nRestCount > 0)
1177     {
1178       // remove end of line (we'll put it back on afterwards)
1179       sTail = StripTail(nLine, nRestCount);
1180     }
1181
1182   bool bInQuote = false;
1183   if (m_bTableEditing && m_bAllowNewlinesInQuotes)
1184     {
1185       const TCHAR* pszLine = m_aLines[nLine].GetLine ();
1186       for (int j = 0; j < nPos; ++j)
1187         {
1188           if (pszLine[j] == m_cFieldEnclosure)
1189             bInQuote = !bInQuote;
1190         }
1191     }
1192
1193   int nInsertedLines = 0;
1194   int nCurrentLine = nLine;
1195   for (;;)
1196     {
1197       int haseol = 0;
1198       size_t nTextPos = 0;
1199       // advance to end of line
1200       if (m_bTableEditing && m_bAllowNewlinesInQuotes)
1201         {
1202           while (nTextPos < cchText && (bInQuote || !LineInfo::IsEol(pszText[nTextPos])))
1203             {
1204               if (pszText[nTextPos] == m_cFieldEnclosure)
1205                 bInQuote = !bInQuote;
1206               nTextPos++;
1207             }
1208         }
1209       else
1210         {
1211           while (nTextPos < cchText && !LineInfo::IsEol(pszText[nTextPos]))
1212             nTextPos++;
1213         }
1214       // advance after EOL of line
1215       if (nTextPos < cchText)
1216         {
1217           haseol = 1;
1218           LPCTSTR eol = &pszText[nTextPos];
1219           nTextPos++;
1220           if (nTextPos < cchText && LineInfo::IsDosEol(eol))
1221             nTextPos++;
1222         }
1223
1224       // The first line of the new text is appended to the start line
1225       // All succeeding lines are inserted
1226       if (nCurrentLine == nLine)
1227         {
1228           AppendLine (nLine, pszText, nTextPos, !bInQuote);
1229         }
1230       else
1231         {
1232           InsertLine (pszText, nTextPos, nCurrentLine);
1233           nInsertedLines ++;
1234         }
1235
1236
1237       if (nTextPos == cchText)
1238         {
1239           // we just finished our insert
1240           // now we have to reattach the tail
1241           if (haseol)
1242             {
1243               nEndLine = nCurrentLine+1;
1244               nEndChar = 0;
1245             }
1246           else
1247             {
1248               nEndLine = nCurrentLine;
1249               nEndChar = GetFullLineLength(nEndLine);
1250             }
1251           if (!sTail.IsEmpty())
1252             {
1253               if (haseol)
1254                 {
1255                   InsertLine(sTail, sTail.GetLength(), nEndLine);
1256                   nInsertedLines ++;
1257                 }
1258               else
1259                 AppendLine (nEndLine, sTail, nRestCount);
1260             }
1261           if (nEndLine == GetLineCount())
1262             {
1263               // We left cursor after last screen line
1264               // which is an illegal cursor position
1265               // so manufacture a new trailing line
1266               InsertLine(_T(""), 0);
1267               nInsertedLines ++;
1268             }
1269           break;
1270         }
1271
1272       ++nCurrentLine;
1273       pszText += nTextPos;
1274       cchText -= nTextPos;
1275     }
1276
1277   // Compute the context : all positions after context.m_ptStart are
1278   // shifted accordingly to (context.m_ptEnd - context.m_ptStart)
1279   // The begin point is the insertion point.
1280   // The end point is more tedious : if we insert in a ghost line, we reuse it, 
1281   // so we insert fewer lines than the number of lines in the text buffer
1282   if (nEndLine - nLine != nInsertedLines)
1283     {
1284       context.m_ptEnd.y = nLine + nInsertedLines;
1285       context.m_ptEnd.x = GetFullLineLength(context.m_ptEnd.y);
1286     }
1287   else
1288     {
1289       context.m_ptEnd.x = nEndChar;
1290       context.m_ptEnd.y = nEndLine;
1291     }
1292
1293
1294   if (pSource!=nullptr)
1295     {
1296       if (nInsertedLines > 0)
1297         UpdateViews (pSource, &context, UPDATE_HORZRANGE | UPDATE_VERTRANGE, nLine);
1298       else
1299         UpdateViews (pSource, &context, UPDATE_SINGLELINE | UPDATE_HORZRANGE, nLine);
1300     }
1301
1302   if (!m_bModified)
1303     SetModified (true);
1304
1305   // remember current cursor position as last editing position
1306   m_ptLastChange.x = nEndChar;
1307   m_ptLastChange.y = nEndLine;
1308   return true;
1309 }
1310
1311 bool CCrystalTextBuffer::
1312 CanUndo () const
1313 {
1314   ASSERT (m_aUndoBuf.size () < INT_MAX);
1315   ASSERT (m_nUndoPosition >= 0 && m_nUndoPosition <= (int)m_aUndoBuf.size ());
1316   return m_nUndoPosition > 0;
1317 }
1318
1319 bool CCrystalTextBuffer::
1320 CanRedo () const
1321 {
1322   ASSERT (m_aUndoBuf.size () < INT_MAX);
1323   ASSERT (m_nUndoPosition >= 0 && m_nUndoPosition <= (int)m_aUndoBuf.size ());
1324   return m_nUndoPosition < static_cast<int>(m_aUndoBuf.size ());
1325 }
1326
1327 POSITION CCrystalTextBuffer::
1328 GetUndoActionCode (int & nAction, POSITION pos /*= nullptr*/ ) const
1329 {
1330   ASSERT (CanUndo ());          //  Please call CanUndo() first
1331
1332   ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
1333
1334   intptr_t nPosition;
1335   if (pos == nullptr)
1336     {
1337       //  Start from beginning
1338       nPosition = m_nUndoPosition;
1339     }
1340   else
1341     {
1342       nPosition = reinterpret_cast<intptr_t>(pos);
1343       ASSERT (nPosition > 0 && nPosition < m_nUndoPosition);
1344       ASSERT ((m_aUndoBuf[nPosition].m_dwFlags & UNDO_BEGINGROUP) != 0);
1345     }
1346
1347   //  Advance to next undo group
1348   nPosition--;
1349   std::vector<UndoRecord>::const_iterator iter = m_aUndoBuf.begin () + nPosition;
1350   while (((*iter).m_dwFlags & UNDO_BEGINGROUP) == 0)
1351     {
1352       --iter;
1353       --nPosition;
1354     }
1355
1356   //  Get description
1357   nAction = (*iter).m_nAction;
1358
1359   //  Now, if we stop at zero position, this will be the last action,
1360   //  since we return (POSITION) nPosition
1361   return (POSITION) nPosition;
1362 }
1363
1364 POSITION CCrystalTextBuffer::
1365 GetRedoActionCode (int & nAction, POSITION pos /*= nullptr*/ ) const
1366 {
1367   ASSERT (CanRedo ());          //  Please call CanRedo() before!
1368
1369   ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
1370   ASSERT ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0);
1371
1372   intptr_t nPosition;
1373   if (pos == nullptr)
1374     {
1375       //  Start from beginning
1376       nPosition = m_nUndoPosition;
1377     }
1378   else
1379     {
1380       nPosition = reinterpret_cast<intptr_t>(pos);
1381       ASSERT (nPosition > m_nUndoPosition);
1382       ASSERT ((m_aUndoBuf[nPosition].m_dwFlags & UNDO_BEGINGROUP) != 0);
1383     }
1384
1385   //  Get description
1386   nAction = m_aUndoBuf[nPosition].m_nAction;
1387
1388   //  Advance to next undo group
1389   nPosition++;
1390   if (nPosition < static_cast<intptr_t>(m_aUndoBuf.size ()))
1391     {
1392       std::vector<UndoRecord>::const_iterator iter = m_aUndoBuf.begin () + nPosition;
1393       while (iter != m_aUndoBuf.end () && ((*iter).m_dwFlags & UNDO_BEGINGROUP) == 0)
1394         {
1395           ++iter;
1396           ++nPosition;
1397         }
1398     }
1399   if (nPosition >= static_cast<intptr_t>(m_aUndoBuf.size ()))
1400     return nullptr;                //  No more redo actions!
1401
1402   return (POSITION) nPosition;
1403 }
1404
1405 POSITION CCrystalTextBuffer::
1406 GetUndoDescription (CString & desc, POSITION pos /*= nullptr*/ ) const
1407 {
1408   int nAction;
1409   POSITION retValue = GetUndoActionCode(nAction, pos);
1410
1411   //  Read description
1412   if (!GetActionDescription (nAction, desc))
1413     desc.Empty ();              //  Use empty string as description
1414
1415   return retValue;
1416 }
1417
1418 POSITION CCrystalTextBuffer::
1419 GetRedoDescription (CString & desc, POSITION pos /*= nullptr*/ ) const
1420 {
1421   int nAction;
1422   POSITION retValue = GetRedoActionCode(nAction, pos);
1423
1424   //  Read description
1425   if (!GetActionDescription (nAction, desc))
1426     desc.Empty ();              //  Use empty string as description
1427
1428   return retValue;
1429 }
1430
1431
1432 bool CCrystalTextBuffer::               /* virtual base */              
1433 UndoInsert(CCrystalTextView * pSource, CPoint & ptCursorPos, const CPoint apparent_ptStartPos, CPoint const apparent_ptEndPos, const UndoRecord & ur)
1434 {
1435   if (DeleteText (pSource, apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, 0, false, false))
1436     {
1437       ptCursorPos = apparent_ptStartPos;
1438       return true;
1439     }
1440   ASSERT(false);
1441   return false;
1442 }
1443
1444 bool CCrystalTextBuffer::               /* virtual base */
1445 Undo (CCrystalTextView * pSource, CPoint & ptCursorPos)
1446 {
1447   ASSERT (CanUndo ());
1448   ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
1449   bool failed = false;
1450   int tmpPos = m_nUndoPosition;
1451
1452   // Hide caret to quickly undo changes made by Replace All operation
1453   bool bCursorHiddenSaved = false;
1454   if (pSource)
1455     {
1456       bCursorHiddenSaved = pSource->m_bCursorHidden;
1457       pSource->m_bCursorHidden = true;
1458     }
1459
1460   while (!failed)
1461     {
1462       --tmpPos;
1463       const UndoRecord ur = GetUndoRecord(tmpPos);
1464       // Undo records are stored in file line numbers
1465       // and must be converted to apparent (screen) line numbers for use
1466       CPoint apparent_ptStartPos = ur.m_ptStartPos;
1467       CPoint apparent_ptEndPos = ur.m_ptEndPos;
1468
1469       if (ur.m_dwFlags & UNDO_INSERT)
1470         {
1471           if (!UndoInsert(pSource, ptCursorPos, apparent_ptStartPos, apparent_ptEndPos, ur))
1472             {
1473               failed = true;
1474               break;
1475             }
1476           // ptCursorPos = apparent_ptStartPos;
1477         }
1478       else
1479         {
1480           int nEndLine, nEndChar;
1481           if (!InsertText(pSource, apparent_ptStartPos.y, apparent_ptStartPos.x, ur.GetText(), ur.GetTextLength(), nEndLine, nEndChar, 0, false))
1482             {
1483               ASSERT(false);
1484               failed = true;
1485               break;
1486             }
1487           ptCursorPos = m_ptLastChange;
1488         }
1489
1490       // restore line revision numbers
1491       RestoreRevisionNumbers(ur.m_ptStartPos.y, ur.m_paSavedRevisionNumbers);
1492
1493       if (ur.m_dwFlags & UNDO_BEGINGROUP)
1494         break;
1495     }
1496   if (m_bModified && m_nSyncPosition == tmpPos)
1497     SetModified (false);
1498   if (!m_bModified && m_nSyncPosition != tmpPos)
1499     SetModified (true);
1500   if (failed)
1501     {
1502       // If the Undo failed, clear the entire Undo/Redo stack
1503       // Not only can we not Redo the failed Undo, but the Undo
1504       // may have partially completed (if in a group)
1505       m_nUndoPosition = 0;
1506       m_aUndoBuf.clear ();
1507     }
1508   else
1509     {
1510       m_nUndoPosition = tmpPos;
1511     }
1512
1513   if (pSource)
1514     {
1515       pSource->m_bCursorHidden = bCursorHiddenSaved;
1516       pSource->UpdateCaret();
1517     }
1518
1519   return !failed;
1520 }
1521
1522 bool CCrystalTextBuffer::               /* virtual base */
1523 Redo (CCrystalTextView * pSource, CPoint & ptCursorPos)
1524 {
1525   ASSERT (CanRedo ());
1526   ASSERT ((m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
1527   ASSERT ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0);
1528
1529   // Hide caret to quickly redo changes made by Replace All operation
1530   bool bCursorHiddenSaved = false;
1531   if (pSource)
1532     {
1533       bCursorHiddenSaved = pSource->m_bCursorHidden;
1534       pSource->m_bCursorHidden = true;
1535     }
1536
1537   for (;;)
1538     {
1539       const UndoRecord ur = GetUndoRecord(m_nUndoPosition);
1540       CPoint apparent_ptStartPos = ur.m_ptStartPos;
1541       CPoint apparent_ptEndPos = ur.m_ptEndPos;
1542
1543       // now we can use normal insertTxt or deleteText
1544       if (ur.m_dwFlags & UNDO_INSERT)
1545         {
1546           int nEndLine, nEndChar;
1547           VERIFY(InsertText (pSource, apparent_ptStartPos.y, apparent_ptStartPos.x,
1548             ur.GetText(), ur.GetTextLength(), nEndLine, nEndChar, 0, false));
1549           ptCursorPos = m_ptLastChange;
1550         }
1551       else
1552         {
1553           if (apparent_ptStartPos != apparent_ptEndPos)
1554             {
1555 #ifdef _DEBUG
1556               CString text;
1557               GetTextWithoutEmptys (apparent_ptStartPos.y, apparent_ptStartPos.x, apparent_ptEndPos.y, apparent_ptEndPos.x, text, CRLFSTYLE::AUTOMATIC, false);
1558               ASSERT (static_cast<size_t>(text.GetLength()) == ur.GetTextLength() && memcmp(text, ur.GetText(), text.GetLength() * sizeof(TCHAR)) == 0);
1559 #endif
1560               VERIFY(DeleteText(pSource, apparent_ptStartPos.y, apparent_ptStartPos.x, 
1561                 apparent_ptEndPos.y, apparent_ptEndPos.x, 0, false, false));
1562             }
1563           ptCursorPos = apparent_ptStartPos;
1564         }
1565       m_nUndoPosition++;
1566       if (static_cast<size_t>(m_nUndoPosition) == m_aUndoBuf.size())
1567         break;
1568       if ((m_aUndoBuf[m_nUndoPosition].m_dwFlags & UNDO_BEGINGROUP) != 0)
1569         break;
1570     }
1571
1572   if (m_bModified && m_nSyncPosition == m_nUndoPosition)
1573     SetModified (false);
1574   if (!m_bModified && m_nSyncPosition != m_nUndoPosition)
1575     SetModified (true);
1576
1577   if (pSource)
1578     {
1579       pSource->m_bCursorHidden = bCursorHiddenSaved;
1580       pSource->UpdateCaret();
1581     }
1582
1583   return true;
1584 }
1585
1586 // the CPoint parameters are apparent (on screen) line numbers
1587
1588 void CCrystalTextBuffer::                       /* virtual base */
1589 AddUndoRecord (bool bInsert, const CPoint & ptStartPos,
1590     const CPoint & ptEndPos, LPCTSTR pszText, size_t cchText, int nActionType /*= CE_ACTION_UNKNOWN*/,
1591     CDWordArray *paSavedRevisionNumbers /*= nullptr*/)
1592 {
1593   //  Forgot to call BeginUndoGroup()?
1594   ASSERT (m_bUndoGroup);
1595   ASSERT (m_aUndoBuf.size () == 0 || (m_aUndoBuf[0].m_dwFlags & UNDO_BEGINGROUP) != 0);
1596
1597   //  Strip unnecessary undo records (edit after undo wipes all potential redo records)
1598   int nBufSize = (int) m_aUndoBuf.size ();
1599   if (m_nUndoPosition < nBufSize)
1600     {
1601       m_aUndoBuf.resize (m_nUndoPosition);
1602     }
1603
1604   //  Add new record
1605   UndoRecord ur;
1606   ur.m_dwFlags = bInsert ? UNDO_INSERT : 0;
1607   ur.m_nAction = nActionType;
1608   if (m_bUndoBeginGroup)
1609     {
1610       ur.m_dwFlags |= UNDO_BEGINGROUP;
1611       m_bUndoBeginGroup = false;
1612     }
1613   ur.m_ptStartPos = ptStartPos;
1614   ur.m_ptEndPos = ptEndPos;
1615   ur.SetText (pszText, cchText);
1616   ur.m_paSavedRevisionNumbers = paSavedRevisionNumbers;
1617
1618   // Optimize memory allocation
1619   if (m_aUndoBuf.capacity() == m_aUndoBuf.size())
1620     {
1621       if (m_aUndoBuf.size() == 0)
1622         m_aUndoBuf.reserve(16);
1623       else if (m_aUndoBuf.size() < 1025)
1624         m_aUndoBuf.reserve(m_aUndoBuf.size() * 2);
1625       else
1626         m_aUndoBuf.reserve(m_aUndoBuf.size() + 1024);
1627     }
1628   m_aUndoBuf.push_back (ur);
1629   m_nUndoPosition = (int) m_aUndoBuf.size ();
1630 }
1631
1632 /**
1633 /**
1634  * @brief Get EOL style string.
1635  * @param [in] nCRLFMode.
1636  * @return string of CRLF style.
1637  */
1638 LPCTSTR CCrystalTextBuffer::GetStringEol(CRLFSTYLE nCRLFMode)
1639 {
1640   switch(nCRLFMode)
1641   {
1642   case CRLFSTYLE::DOS: return _T("\r\n");
1643   case CRLFSTYLE::UNIX: return _T("\n");
1644   case CRLFSTYLE::MAC: return _T("\r");
1645       // If mixed or not defined
1646   default: return _T("\r\n");
1647   }
1648 }
1649
1650 LPCTSTR CCrystalTextBuffer::GetDefaultEol() const
1651 {
1652   return GetStringEol(m_nCRLFMode);
1653 }
1654
1655 /**
1656  * @brief Insert text to the buffer.
1657  * @param [in] pSource A view to which the text is added.
1658  * @param [in] nLine Line to add the text.
1659  * @param [in] nPos Position in the line to insert the text.
1660  * @param [in] pszText The text to insert.
1661  * @param [in] cchText The length of the text.
1662  * @param [out] nEndLine Line number of last added line in the buffer.
1663  * @param [out] nEndChar Character position of the end of the added text
1664  *   in the buffer.
1665  * @param [in] nAction Edit action.
1666  * @param [in] bHistory Save insertion for undo/redo?
1667  * @return true if the insertion succeeded, false otherwise.
1668  * @note Line numbers are apparent (screen) line numbers, not real
1669  * line numbers in the file.
1670  */
1671 bool CCrystalTextBuffer::                       /* virtual base */
1672 InsertText (CCrystalTextView * pSource, int nLine, int nPos, LPCTSTR pszText,
1673     size_t cchText, int &nEndLine, int &nEndChar, int nAction,
1674     bool bHistory /*= true*/)
1675 {
1676   // save line revision numbers for undo
1677   CDWordArray *paSavedRevisionNumbers = new CDWordArray;
1678   paSavedRevisionNumbers->SetSize(1);
1679   (*paSavedRevisionNumbers)[0] = m_aLines[nLine].m_dwRevisionNumber;
1680
1681   if (!InternalInsertText (pSource, nLine, nPos, pszText, cchText, nEndLine, nEndChar))
1682     {
1683       delete paSavedRevisionNumbers;
1684       return false;
1685     }
1686
1687   // update line revision numbers of modified lines
1688   m_dwCurrentRevisionNumber++;
1689   for (int i = nLine ; i < nEndLine; i++)
1690     m_aLines[i].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
1691   if (nPos != 0 || nEndChar != 0)
1692     m_aLines[nEndLine].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
1693
1694   if (!bHistory)
1695     {
1696       delete paSavedRevisionNumbers;
1697       return true;
1698     }
1699
1700   bool bGroupFlag = false;
1701   if (!m_bUndoGroup)
1702     {
1703       BeginUndoGroup ();
1704       bGroupFlag = true;
1705     }
1706
1707   AddUndoRecord (true, CPoint (nPos, nLine), CPoint (nEndChar, nEndLine),
1708                  pszText, cchText, nAction, paSavedRevisionNumbers);
1709
1710   if (bGroupFlag)
1711     FlushUndoGroup (pSource);
1712
1713   return true;
1714 }
1715
1716 /**
1717  * @brief Delete text from the buffer.
1718  * @param [in] pSource A view from which the text is deleted.
1719  * @param [in] nStartLine Starting line for the deletion.
1720  * @param [in] nStartChar Starting char position for the deletion.
1721  * @param [in] nEndLine Ending line for the deletion.
1722  * @param [in] nEndChar Ending char position for the deletion.
1723  * @param [in] nAction Edit action.
1724  * @param [in] bHistory Save deletion for undo/redo?
1725  * @param [in] bExcludeInvisibleLines Don't delete LF_INVISIBLE lines 
1726  * @return true if the deletion succeeded, false otherwise.
1727  * @note Line numbers are apparent (screen) line numbers, not real
1728  * line numbers in the file.
1729  */
1730 bool CCrystalTextBuffer::                       /* virtual base */
1731 DeleteText (CCrystalTextView * pSource, int nStartLine, int nStartChar,
1732             int nEndLine, int nEndChar, int nAction, bool bHistory /*= true*/, bool bExcludeInvisibleLines /*= true*/)
1733 {
1734   bool bGroupFlag = false;
1735   if (bHistory)
1736     {
1737       if (!m_bUndoGroup)
1738         {
1739           BeginUndoGroup ();
1740           bGroupFlag = true;
1741         }
1742     }
1743   if (bExcludeInvisibleLines && pSource != nullptr && pSource->GetEnableHideLines ())
1744     {
1745       for (int nLineIndex = nEndLine; nLineIndex >= nStartLine; nLineIndex--)
1746         {
1747           if (!(GetLineFlags (nLineIndex) & LF_INVISIBLE))
1748             {
1749               int nEndLine2 = nLineIndex;
1750               int nStartLine2;
1751               for (nStartLine2 = nLineIndex - 1; nStartLine2 >= nStartLine; nStartLine2--)
1752                 {
1753                   if (GetLineFlags (nStartLine2) & LF_INVISIBLE)
1754                     break;
1755                 }  
1756               nStartLine2++;
1757               nLineIndex = nStartLine2;
1758               int nStartChar2 = (nStartLine == nStartLine2) ? nStartChar : 0;
1759               int nEndChar2;
1760               if (nEndLine == nEndLine2)
1761                 nEndChar2 = nEndChar;
1762               else
1763                 {
1764                   nEndChar2 = 0;
1765                   nEndLine2++;
1766                 }
1767               if (!DeleteText2 (pSource, nStartLine2, nStartChar2, nEndLine2, nEndChar2, nAction, bHistory))
1768                 return false;
1769             }
1770         }
1771     }
1772   else
1773     {
1774       if (!DeleteText2 (pSource, nStartLine, nStartChar, nEndLine, nEndChar, nAction, bHistory))
1775         return false;
1776     }
1777
1778   if (bGroupFlag)
1779     FlushUndoGroup (pSource);
1780
1781   return true;
1782 }
1783
1784 CDWordArray *CCrystalTextBuffer::
1785 CopyRevisionNumbers(int nStartLine, int nEndLine) const
1786 {
1787   // save line revision numbers for undo
1788   CDWordArray *paSavedRevisionNumbers = new CDWordArray;
1789   paSavedRevisionNumbers->SetSize(nEndLine - nStartLine + 1);
1790   for (int i = 0; i < nEndLine - nStartLine + 1; i++)
1791     (*paSavedRevisionNumbers)[i] = m_aLines[nStartLine + i].m_dwRevisionNumber;
1792   return paSavedRevisionNumbers;
1793 }
1794
1795 void CCrystalTextBuffer::
1796 RestoreRevisionNumbers(int nStartLine, CDWordArray *paSavedRevisionNumbers)
1797 {
1798   for (int i = 0; i < paSavedRevisionNumbers->GetSize(); i++)
1799     m_aLines[nStartLine + i].m_dwRevisionNumber = (*paSavedRevisionNumbers)[i];
1800 }
1801
1802 bool CCrystalTextBuffer::                       /* virtual base */
1803 DeleteText2 (CCrystalTextView * pSource, int nStartLine, int nStartChar,
1804             int nEndLine, int nEndChar, int nAction /* = CE_ACTION_UNKNOWN*/, bool bHistory /*= true*/)
1805 {
1806   CString sTextToDelete;
1807   GetTextWithoutEmptys (nStartLine, nStartChar, nEndLine, nEndChar, sTextToDelete);
1808
1809   // save line revision numbers for undo
1810   CDWordArray *paSavedRevisionNumbers = CopyRevisionNumbers(nStartLine, nEndLine);
1811
1812   if (!InternalDeleteText (pSource, nStartLine, nStartChar, nEndLine, nEndChar))
1813     {
1814       delete paSavedRevisionNumbers;
1815       return false;
1816     }
1817
1818   // update line revision numbers of modified lines
1819   m_dwCurrentRevisionNumber++;
1820   m_aLines[nStartLine].m_dwRevisionNumber = m_dwCurrentRevisionNumber;
1821
1822   if (!bHistory)
1823     {
1824       delete paSavedRevisionNumbers;
1825       return true;
1826     }
1827
1828   AddUndoRecord (false, CPoint (nStartChar, nStartLine), CPoint (nEndChar, nEndLine),
1829                  sTextToDelete, sTextToDelete.GetLength(), nAction, paSavedRevisionNumbers);
1830
1831   return true;
1832 }
1833
1834 bool CCrystalTextBuffer::
1835 GetActionDescription (int nAction, CString & desc) const
1836 {
1837   HINSTANCE hOldResHandle = AfxGetResourceHandle ();
1838 #ifdef CRYSEDIT_RES_HANDLE
1839   AfxSetResourceHandle (CRYSEDIT_RES_HANDLE);
1840 #else
1841   if (CCrystalTextView::s_hResourceInst != nullptr)
1842     AfxSetResourceHandle (CCrystalTextView::s_hResourceInst);
1843 #endif
1844   bool bSuccess = false;
1845   switch (nAction)
1846     {
1847     case CE_ACTION_PASTE:
1848       bSuccess = !!desc.LoadString (IDS_EDITOP_PASTE);
1849       break;
1850     case CE_ACTION_DELSEL:
1851       bSuccess = !!desc.LoadString (IDS_EDITOP_DELSELECTION);
1852       break;
1853     case CE_ACTION_CUT:
1854       bSuccess = !!desc.LoadString (IDS_EDITOP_CUT);
1855       break;
1856     case CE_ACTION_TYPING:
1857       bSuccess = !!desc.LoadString (IDS_EDITOP_TYPING);
1858       break;
1859     case CE_ACTION_BACKSPACE:
1860       bSuccess = !!desc.LoadString (IDS_EDITOP_BACKSPACE);
1861       break;
1862     case CE_ACTION_INDENT:
1863       bSuccess = !!desc.LoadString (IDS_EDITOP_INDENT);
1864       break;
1865     case CE_ACTION_DRAGDROP:
1866       bSuccess = !!desc.LoadString (IDS_EDITOP_DRAGDROP);
1867       break;
1868     case CE_ACTION_REPLACE:
1869       bSuccess = !!desc.LoadString (IDS_EDITOP_REPLACE);
1870       break;
1871     case CE_ACTION_DELETE:
1872       bSuccess = !!desc.LoadString (IDS_EDITOP_DELETE);
1873       break;
1874     case CE_ACTION_AUTOINDENT:
1875       bSuccess = !!desc.LoadString (IDS_EDITOP_AUTOINDENT);
1876       break;
1877     case CE_ACTION_AUTOCOMPLETE:
1878       bSuccess = !!desc.LoadString (IDS_EDITOP_AUTOCOMPLETE);
1879       break;
1880     case CE_ACTION_AUTOEXPAND:
1881       bSuccess = !!desc.LoadString (IDS_EDITOP_AUTOEXPAND);
1882       break;
1883     case CE_ACTION_LOWERCASE:
1884       bSuccess = !!desc.LoadString (IDS_EDITOP_LOWERCASE);
1885       break;
1886     case CE_ACTION_UPPERCASE:
1887       bSuccess = !!desc.LoadString (IDS_EDITOP_UPPERCASE);
1888       break;
1889     case CE_ACTION_SWAPCASE:
1890       bSuccess = !!desc.LoadString (IDS_EDITOP_SWAPCASE);
1891       break;
1892     case CE_ACTION_CAPITALIZE:
1893       bSuccess = !!desc.LoadString (IDS_EDITOP_CAPITALIZE);
1894       break;
1895     case CE_ACTION_SENTENCIZE:
1896       bSuccess = !!desc.LoadString (IDS_EDITOP_SENTENCIZE);
1897       break;
1898     case CE_ACTION_RECODE:
1899       bSuccess = !!desc.LoadString (IDS_EDITOP_RECODE);
1900       break;
1901     case CE_ACTION_SPELL:
1902       bSuccess = !!desc.LoadString (IDS_EDITOP_SPELL);
1903       break;
1904     default: /* case CE_ACTION_UNKNOWN: */
1905       bSuccess = !!desc.LoadString (IDS_EDITOP_UNKNOWN);
1906     }
1907   AfxSetResourceHandle (hOldResHandle);
1908   return bSuccess;
1909 }
1910
1911 void CCrystalTextBuffer::                       /* virtual base */
1912 SetModified (bool bModified /*= true*/ )
1913 {
1914   m_bModified = bModified;
1915 }
1916
1917 void CCrystalTextBuffer::
1918 BeginUndoGroup (bool bMergeWithPrevious /*= false*/ )
1919 {
1920   ASSERT (!m_bUndoGroup);
1921   m_bUndoGroup = true;
1922   m_bUndoBeginGroup = m_nUndoPosition == 0 || !bMergeWithPrevious;
1923 }
1924
1925 void CCrystalTextBuffer::
1926 FlushUndoGroup (CCrystalTextView * pSource)
1927 {
1928   ASSERT (m_bUndoGroup);
1929   if (pSource != nullptr)
1930     {
1931       ASSERT (static_cast<size_t>(m_nUndoPosition) <= m_aUndoBuf.size());
1932       if (m_nUndoPosition > 0)
1933         {
1934           pSource->OnEditOperation (m_aUndoBuf[m_nUndoPosition - 1].m_nAction, m_aUndoBuf[m_nUndoPosition - 1].GetText (), m_aUndoBuf[m_nUndoPosition - 1].GetTextLength ());
1935         }
1936     }
1937   m_bUndoGroup = false;
1938 }
1939
1940 int CCrystalTextBuffer::
1941 FindNextBookmarkLine (int nCurrentLine) const
1942 {
1943   bool bWrapIt = true;
1944   DWORD dwFlags = GetLineFlags (nCurrentLine);
1945   if ((dwFlags & LF_BOOKMARKS) != 0)
1946     nCurrentLine++;
1947
1948   const size_t nSize = m_aLines.size ();
1949   for (;;)
1950     {
1951       while (nCurrentLine < static_cast<int>(nSize))
1952         {
1953           if ((m_aLines[nCurrentLine].m_dwFlags & LF_BOOKMARKS) != 0)
1954             return nCurrentLine;
1955           // Keep going
1956           nCurrentLine++;
1957         }
1958       // End of text reached
1959       if (!bWrapIt)
1960         return -1;
1961
1962       // Start from the beginning of text
1963       bWrapIt = false;
1964       nCurrentLine = 0;
1965     }
1966 //~  return -1;
1967 }
1968
1969 int CCrystalTextBuffer::
1970 FindPrevBookmarkLine (int nCurrentLine) const
1971 {
1972   bool bWrapIt = true;
1973   DWORD dwFlags = GetLineFlags (nCurrentLine);
1974   if ((dwFlags & LF_BOOKMARKS) != 0)
1975     nCurrentLine--;
1976
1977   const size_t nSize = m_aLines.size ();
1978   for (;;)
1979     {
1980       while (nCurrentLine >= 0)
1981         {
1982           if ((m_aLines[nCurrentLine].m_dwFlags & LF_BOOKMARKS) != 0)
1983             return nCurrentLine;
1984           // Keep moving up
1985           nCurrentLine--;
1986         }
1987       // Beginning of text reached
1988       if (!bWrapIt)
1989         return -1;
1990
1991       // Start from the end of text
1992       bWrapIt = false;
1993       nCurrentLine = (int) (nSize - 1);
1994     }
1995 //~  return -1;
1996 }
1997
1998 //BEGIN SW
1999 CPoint CCrystalTextBuffer::GetLastChangePos() const
2000 {
2001   return m_ptLastChange;
2002 }
2003 //END SW
2004 void CCrystalTextBuffer::RestoreLastChangePos(CPoint pt)
2005 {
2006   m_ptLastChange = pt;
2007 }
2008
2009
2010 /**
2011  * @brief Delete one or several lines
2012  */
2013 void CCrystalTextBuffer::DeleteLine(int line, int nCount /*=1*/)
2014 {
2015   for (int ic = 0; ic < nCount; ic++)
2016     m_aLines[line + ic].Clear();
2017   std::vector<LineInfo>::iterator iterBegin = m_aLines.begin() + line;
2018   std::vector<LineInfo>::iterator iterEnd = iterBegin + nCount;
2019   m_aLines.erase(iterBegin, iterEnd);
2020 }
2021
2022 int CCrystalTextBuffer::GetTabSize() const
2023 {
2024   ASSERT( m_nTabSize >= 0 && m_nTabSize <= 64 );
2025   return m_nTabSize;
2026 }
2027
2028 void CCrystalTextBuffer::SetTabSize(int nTabSize)
2029 {
2030   ASSERT( nTabSize >= 0 && nTabSize <= 64 );
2031   m_nTabSize = nTabSize;
2032 }
2033
2034 int CCrystalTextBuffer::GetColumnWidth (int nColumnIndex) const
2035 {
2036   ASSERT( nColumnIndex >= 0 );
2037   if (nColumnIndex < static_cast<int>(m_pSharedTableProps->m_aColumnWidths.size ()))
2038     return m_pSharedTableProps->m_aColumnWidths[nColumnIndex];
2039   else
2040     return m_nTabSize;
2041 }
2042
2043 void CCrystalTextBuffer::SetColumnWidth (int nColumnIndex, int nColumnWidth)
2044 {
2045   ASSERT( nColumnIndex >= 0 );
2046   ASSERT( nColumnWidth >= 0 );
2047   if (nColumnIndex >= static_cast<int>(m_pSharedTableProps->m_aColumnWidths.size ()))
2048     m_pSharedTableProps->m_aColumnWidths.resize (nColumnIndex + 1, m_nTabSize);
2049   m_pSharedTableProps->m_aColumnWidths[nColumnIndex] = nColumnWidth;
2050 }
2051
2052 int CCrystalTextBuffer::GetColumnCount (int nLineIndex) const
2053 {
2054   ASSERT( nLineIndex >= 0 );
2055   int nColumnCount = 0;
2056   const TCHAR* pszLine = GetLineChars (nLineIndex);
2057   int nLength = GetLineLength (nLineIndex);
2058   bool bInQuote = false;
2059   for (int j = 0; j < nLength; ++j)
2060     {
2061       if (pszLine[j] == m_cFieldEnclosure)
2062         bInQuote = !bInQuote;
2063       else if (!bInQuote && pszLine[j] == m_cFieldDelimiter)
2064         ++nColumnCount;
2065     }
2066   return nColumnCount;
2067 }
2068
2069 void CCrystalTextBuffer::JoinLinesForTableEditingMode ()
2070 {
2071   if (!m_bAllowNewlinesInQuotes)
2072       return;
2073   size_t nLineCount = m_aLines.size ();
2074   size_t j = 0;
2075   bool bInQuote = false;
2076   for (size_t i = 0; i < nLineCount;)
2077     {
2078       const TCHAR* pszChars = m_aLines[i].GetLine ();
2079       const size_t nLineLength = m_aLines[i].FullLength ();
2080       for (; j < nLineLength; ++j)
2081         {
2082           if (pszChars[j] == m_cFieldEnclosure)
2083             bInQuote = !bInQuote;
2084         }
2085       m_aLines[i].m_dwRevisionNumber = 0;
2086       if (bInQuote && i < nLineCount - 1)
2087         {
2088           std::basic_string<TCHAR> line(m_aLines[i].GetLine (), m_aLines[i].FullLength ());
2089           line.append (m_aLines[i + 1].GetLine (), m_aLines[i + 1].FullLength ());
2090           m_aLines[i].FreeBuffer ();
2091           m_aLines[i].Create (line.c_str (), line.size ());
2092           m_aLines[i + 1].FreeBuffer ();
2093           m_aLines.erase (m_aLines.begin () + i + 1);
2094           --nLineCount;
2095           continue;
2096         }
2097       ++i;
2098       j = 0;
2099       bInQuote = false;
2100     }
2101   m_aUndoBuf.clear();
2102   m_nUndoPosition = 0;
2103   m_bModified = false;
2104 }
2105
2106 void CCrystalTextBuffer::SplitLinesForTableEditingMode ()
2107 {
2108   size_t nLineCount = m_aLines.size ();
2109   for (size_t i = 0; i < nLineCount; ++i)
2110     {
2111       const TCHAR* pszChars = m_aLines[i].GetLine ();
2112       const size_t nLineLength = m_aLines[i].FullLength ();
2113       for (size_t j = 0; j < nLineLength; ++j)
2114         {
2115           int eols = 0;
2116           if (pszChars[j] == '\r')
2117             eols = (j < nLineLength - 1 && pszChars[j + 1] == '\n') ? 2 : 1;
2118           else if (pszChars[j] == '\n')
2119             eols = 1;
2120           if (eols > 0)
2121             {
2122               LineInfo lineInfo;
2123               lineInfo.Create (pszChars + j + eols, nLineLength - (j + eols));
2124               m_aLines.insert (m_aLines.begin () + i + 1, lineInfo);
2125               m_aLines[i].DeleteEnd (j + eols);
2126               m_aLines[i].m_dwRevisionNumber = 0;
2127             }
2128         }
2129     }
2130   m_aUndoBuf.clear ();
2131   m_nUndoPosition = 0;
2132   m_bModified = false;
2133 }
2134
2135 void CCrystalTextBuffer::
2136 InvalidateColumns ()
2137 {
2138   for (auto& buf : m_pSharedTableProps->m_textBufferList)
2139     {
2140       POSITION pos = buf->m_lpViews.GetHeadPosition ();
2141       while (pos != nullptr)
2142         {
2143           POSITION thispos = pos;
2144           CCrystalTextView* pView = buf->m_lpViews.GetNext (pos);
2145           pView->InvalidateScreenRect ();
2146           pView->UpdateView (nullptr, nullptr, UPDATE_HORZRANGE | UPDATE_VERTRANGE, -1);
2147         }
2148     }
2149 }
2150